Having trouble getting Angular 8 WebRTC to function properly on two tabs

I've been tasked with creating an audio chat room for 2 users.

Initially, I used the following app example: Peer connection: audio only

After converting the code to TypeScript, it successfully ran:

Stackblitz

However, I'm facing challenges getting it to work between two users (not in one tab; I open 2 Firefox windows). My knowledge of Angular is quite basic. Although not an Angular developer by profession (I typically work on desktop apps), I am venturing into Angular development for this specific project.

The objective is to send an offer event to a communication platform (to connect with another user within the same room - always 2 users per room). The event requires a type field set to offer and includes sdp.

The other user responds with an answer event within the same platform and room. This response also includes sdp, with its type set to answer.

We are utilizing matrix.org. For detailed VOIP specifications, refer to VOIP specs

Although I have successfully implemented sending these events to the communication platform and reading events from the room, I'm stuck at making the functionality fully operational.

I've dedicated time over weekends trying to troubleshoot the issues, yet setbacks like

DOMException: "Cannot set remote answer in state stable"
continue to persist.

I don't expect a complete working code solution (I understand it's a task that may span several days), but any insights or advice to make the implementation smoother and feasible for someone with minimal Angular and webRTC expertise would be highly valued.

Interestingly, the Stackblitz example doesn't seem to function properly in Chrome for me, although it runs smoothly in Firefox.

//EDIT

I'm currently exploring a new approach like the one below:

placeCall() {
    this.turnServer();
    this.uuid = this.createUuid();
    this.initCall();
}

initCall(){
    this.pc1 = new RTCPeerConnection(PEER_CONNECTION_CONFIG);
    navigator.mediaDevices
        .getUserMedia({
          audio: true,
          video: false
        })
        .then(stream => this.gotStreamSetOffer(stream))
        .catch(e => {
          console.log('error');
          alert(`getUserMedia() error: ${e.name}`);
        });
}

gotStreamSetOffer(stream) {
    this.hangupButton.nativeElement.disabled = false;
    this.localStream = stream;
    let audioTracks = this.localStream.getAudioTracks();
    console.log(audioTracks);
    if (audioTracks.length > 0) {
      console.log(`Using Audio device: ${audioTracks[0].label}`);
    }
    
    this.localStream.getTracks().forEach(track => this.pc1.addTrack(track, this.localStream));
    this.pc1.createOffer(this.offerOptions)
        .then(v => {
          this.pc1.setLocalDescription(v);
          this.sdp = v.sdp;
          this.localOfferSDP = v;
          this.messageService.callInvite(JSON.stringify(new CallInviteModel(this.uuid, 0, 30000, new OfferModel("offer", v.sdp))), this.roomId)
              .subscribe(res => {
                  this.candidates();
              });
        });
    this.pc1.onicecandidate = e => {
        if (e.candidate) return;
    };
       // Wait for answer in room and save answer to v

     this.hangupButton.nativeElement.disabled = false;
     this.callId = v.content.call_id;
     this.remoteOfferSDP = v.content.answer;
     this.pc1.setRemoteDescription(new RTCSessionDescription(this.remoteOfferSDP));

}

answer() {
    this.pc1 = new RTCPeerConnection(PEER_CONNECTION_CONFIG);
    let answer = new AnswerModel("answer", this.localOfferSDP.sdp); 
    let callAnswer = new CallAnswerModel(this.callId, 0, answer);
    let json = JSON.stringify(callAnswer);
    this.messageService.callAnswer(json, this.roomId)
        .subscribe(res => {

        });
    this.remoteOfferSDP = this.localOfferSDP;
    this.remoteOfferSDP.type = 'answer';
    this.pc1.setRemoteDescription(new RTCSessionDescription(this.localOfferSDP))
        .then((a) => this.pc1.createAnswer())
        .then(d => {
          this.pc1.setLocalDescription(this.remoteOfferSDP)
        }).catch(e => console.log(e));
    this.pc1.onicecandidate = e => {
        if (e.candidate) return;

    };  

}

Despite these efforts, I encountered a recurrence of the error:

DOMException: "Cannot set remote answer in state stable"

//EDIT2 Recently, another exception surfaced:

DOMException: "Answer contains illegal setup attribute "actpass" at level 0"
. The presence of actpass in the sdp is auto-generated and leads to this particular issue when using Firefox.

Answer №1

Here are the steps I took to solve this:

  1. Integration of adapter.js for cross-browser compatibility.
  2. Incorporated ontrack functionality for audio support.
  3. Generated an offer, configured codecs and local description (essential before sending event data to matrix room for candidate transmission).
  4. Sent an offer event to matrix room once all candidates have been processed.
  5. Upon receiving an offer event, established remote description based on the incoming offer.
  6. Created an answer, defined codecs, set local description, and transmitted an answer event to the room.
  7. Upon encountering an answer event in the room, updated remote description accordingly.

Although this may not be a comprehensive guide, it could be beneficial for someone facing similar challenges.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Any tips on making Angular 8's sort table function seamlessly integrate with data retrieved from Firebase?

I am currently working on an angular PWA project that utilizes a material table to display data from Firebase. The data is being shown properly and the paginator is functioning as expected. However, I am facing an issue with sorting the data using mat-sort ...

Unable to programmatically uncheck a checkbox after it has been manually checked: Angular

After being selected through the UI by clicking on the checkbox, I am encountering an issue where I cannot unselect the checkbox programmatically. To see this behavior in action, visit the sample app, where you can click on the checkbox to select it and t ...

How to modify a specific property of an array object in JavaScript

I have an array of objects that looks like this: [ { number: 1, name: "A" }, { number: 2, name: "e", }, { number: 3, name: "EE", } ] I am looking for a way to insert an object into the array at a specific position and ...

Organize an array based on its ratio

I am attempting to organize an array based on the win and lose ratio of each player. This is how my code currently looks: const array = [{playerName: 'toto', win: 2, lose: 2}, {playerName: 'titi', win: 0, lose: 0}, {playerName: &apo ...

Challenges arise with data updating following a mutation in @tanstack/react-query

As I work on building an e-commerce website using React, I have a specific feature where users can add products to their favorites by clicking a button. Following this action, I aim to update the profile request to display the user's information along ...

Discovering the interface type of class properties in order to implement a factory method

I am struggling with implementing a factory method in my code. I want to be able to pass not only a Class type to instantiate but also a set of default values for the properties within the class. My goal is to have the compiler notify me if I try to pass i ...

What is the best way to hide the button when a user is viewing their own profile in an Angular application?

Each user has their own profile on the platform. A unique feature allows users to send messages by clicking a button when viewing other profiles. However, an issue arises where this messaging button also appears on a user's own profile. Is there a way ...

Is there a way to have my accordion adjust automatically?

I have developed a dynamic accordion component that populates its values from the parent component. However, I am facing an issue where each accordion does not respond individually to clicks. Whenever I click on any accordion, only the first one expands an ...

The Primeng ConfirmDialog does not close when either the Yes, No, or Close(X) buttons are clicked

When using the PrimeNg ConfirmDialog (p-confirmDialog) in a P-table to delete a record, I am experiencing an issue where the confirm dialog does not close after clicking Yes/No/close(X). Below is the method that I am calling when clicking on delete: conf ...

Bring in properties from a separate file in Vue3

Currently, I am utilizing Vue3 along with the options API. Within my setup, there are various Vue components that rely on a shared prop defined as: exercise: { type: Object as PropType<Exercise>, required: true, }, To streamline this pro ...

What could be causing the absence of the exported Object when importing into a different Typescript project?

I am currently in the process of developing a React component library using Typescript that I want to import into another Typescript project. Specifically, I want to import an Analytics chart library into a storybook for demonstration and testing purposes. ...

The autocomplete feature is not functioning properly when attempting to prefill form inputs with values from another component

After utilizing a service to transfer values from the first component to the second, I encountered an issue. When trying to fill out a form on the first component (url: localhost:4200) and submitting it, the redirection to url: localhost:4200/results where ...

Issue with migrating TypeOrm due to raw SQL statement

Is it possible to use a regular INSERT INTO statement with TypeOrm? I've tried various ways of formatting the string and quotes, but I'm running out of patience. await queryRunner.query('INSERT INTO "table"(column1,column2) VALUES ...

Integration of Mocha with WebStorm

WebStorm offers a useful feature that adds a small arrow next to describe() and it() keywords when writing tests with Mocha, allowing for easy manual execution. However, there is a challenge: I require additional setup before each test, leading me to use ...

Encountered an unexpected token error when executing karma-coverage in a project using TypeScript

I have been working on a simple Angular/Typescript project that includes 12 basic unit tests which all pass successfully. However, I am now looking to measure the code coverage of these tests. Despite trying different methods, I have not been able to achie ...

Is it possible to create a single model with different variations, each with specific required fields?

If you're working with our API, you'll likely need to create an Order. While the order model remains consistent across different endpoints, the required fields may vary: For a POST request, only a few fields are required. With a PATCH request, ...

`Angular application having trouble loading Azure Maps`

Hey there! I'm currently working on integrating Azure Maps into a basic Angular application. The error message I encountered is shown below. Any insights on why this might be happening and what steps can be taken to resolve it? atlas.min.js:2509 Err ...

Angular 2 routing malfunctioning

I'm encountering an issue while setting up routing in my application. The error displayed in the console is as follows: angular2-polyfills.js:138 Error: XHR error (404 Not Found) loading http://localhost:9000/angular2/router.js(…) Below is the co ...

Unveiling the Ultimate Method to Package Angular 2 Application using SystemJS and SystemJS-Builder

I'm currently in the process of developing an application and I am faced with a challenge of optimizing the performance of Angular 2 by improving the loading speed of all the scripts. However, I have encountered an error that is hindering my progress: ...

Apply CSS styling to the shadow root

In my preact project, I am creating a Shadow DOM and injecting a style element into the Shadow root using the following code: import style from "./layout/main.css"; loader(window, defaultConfig, window.document.currentScript, (el, config) => ...