Search code examples
typescriptwebrtc

Can't hear MediaStream through WebRTC


I'm trying to get a sort of voice chat working using WebRTC and a WebSocket for exchanging offers.

First I create my RTCPeerConection

const pc = new RTCPeerConnection(configuration);

I then have this function as success callback of navigator.getUserMedia

let localStream: MediaStream;
export function setupRtc(stream: MediaStream) {
    localStream = stream;
    // add local media stream tracks to the connection
    console.log("adding tracks");
    console.log("localStream.getTracks()", localStream.getTracks());
    localStream.getTracks().forEach((track: MediaStreamTrack) => {
        pc.addTrack(track, localStream);
        console.log("added track ", track);
    });
    console.log("done adding tracks");

    // handle pcs tracks
    let remoteStream = new MediaStream();
    pc.ontrack = function(event: RTCTrackEvent) {
        console.log("ontrack", event);
        //add audio Tag
        event.streams[0].getTracks().forEach((track) => {
            remoteStream.addTrack(track);
        });
    }
    let remoteAudio = <HTMLMediaElement> document.getElementById('remoteAudio');
    remoteAudio.srcObject = remoteStream;
}

The Caller sends his offer like this:

async function sendOffer(connectionId: string) {
    console.log("SENDING OFFER...");
    //create offer desc
    let sessionDesc: RTCSessionDescriptionInit = await pc.createOffer();
    await pc.setLocalDescription(sessionDesc);
    //send offer
    socket.send(JSON.stringify(
        {
            type: "signaling_offer",
            desc: {
                sdp: sessionDesc.sdp,
                type: sessionDesc.type
            },
            connectionId: connectionId
        }
    ));
    console.log("SEND OFFER");
}

The connectionId is an id that both Caller and Callee have.

After the WebSocket Server forwards the offer to the Callee he answers using this function:

function sendAnswer(connectionId: string, offer: RTCSessionDescriptionInit) {
    console.log("SENDING ANSWER...");
    pc.setRemoteDescription(new RTCSessionDescription(offer)).then(async () => {
        const answerDesc = await pc.createAnswer();
        await pc.setLocalDescription(answerDesc);
        //send answer
        socket.send(JSON.stringify(
            {
                type: "signaling_answer",
                desc: {
                    sdp: answerDesc.sdp,
                    type: answerDesc.type
                },
                connectionId: connectionId
            }
        ));
        console.log("SEND ANSWER!");
    });
}

After the WebSocket Server forwards that again the Caller handles the answer using this:

function handleAnswered(connectionId: string, answer: RTCSessionDescriptionInit) {
    console.log("HANDLING ANSWER...");
    pc.setRemoteDescription(new RTCSessionDescription(answer));
    console.log("HANDLED ANSWER!");
}

In the tutorials and instructions I followed the audio element then seems to play the audio mine still looks like an audio element without a source: audio element that's not playing

Both Caller and Callee have added tracks and streams and have received some. I have also attached their console outputs:

Caller: caller console

Callee: callee console


Solution

  • So it turns out I completely missed a step. Both Caller and Callee need to exchange their ice candidates. So I added the following code:

        pc.onicecandidate = event => {
            if(event.candidate != undefined) {
                let candidateInit: RTCIceCandidateInit = event.candidate.toJSON();
                //send candidate
                socket.send(JSON.stringify(
                    {
                        type: "signaling_add_candidate",
                        candidate: candidateInit,
                        connectionId: connectionId
                    }
                ));
                console.log("send candidate from caller", event.candidate);
            }
        };
    

    The server forwards that candidate to the other party. So from caller to callee and form callee to caller. To then add the forwarded candidates on the other side to the PeerConnection I added this code:

    function handleNewCandidate(connectionId: string, candidateInit: RTCIceCandidateInit) {
        pc.addIceCandidate(new RTCIceCandidate(candidateInit));
    }