Search code examples
javascriptwebrtcsdppeerpeer-connection

Failed to create PeerConnection, exception: Failed to construct 'RTCSessionDescription': parameter 1 ('descriptionInitDict') is not an object


enter image description here
Frontend code:-

     let socket = io();


            socket.on(`connect`, () => {
                console.log(`new User Connected`);
            });


            jQuery(`#video-form`).on(`submit`, (event) => {
                event.preventDefault();

                socket.emit(`offer`, () => {
                    console.log(`Starting call`);
                });
            });


            /////////////////////////////

            'use strict';

            let isChannelReady = false;
            let isInitiator = false;
            let isStarted = false;
            let localStream;
            let pc;
            let remoteStream;
            let turnReady;

            let pcConfig = {
                'iceServers': [{
                    'url': 'stun:stun.1.google.com:19302'
                }]
            };

            // Set up audio and video regardless of what devices are present.
            let sdpConstraints = {
                'mandatory': {
                    'OfferToReceiveAudio': true,
                    'OfferToReceiveVideo': true
                }
            };

            /////////////////////////////////////////////

            let room = 'foo';
            // Could prompt for room name:
            // room = prompt('Enter room name:');

            if (room !== '') {
                socket.emit('create or join', room);
                console.log('Attempted to create or  join room', room);
            }

            socket.on('created', function (room) {
                console.log('Created room ' + room);
                isInitiator = true;
            });

            socket.on('full', function (room) {
                console.log('Room ' + room + ' is full');
            });

            socket.on('join', function (room) {
                console.log('Another peer made a request to join room ' + room);
                console.log('This peer is the initiator of room ' + room + '!');
                isChannelReady = true;
            });

            socket.on('joined', function (room) {
                console.log('joined: ' + room);
                isChannelReady = true;
            });

            socket.on('log', function (array) {
                console.log.apply(console, array);
            });

            ////////////////////////////////////////////////

            function sendMessage(message) {
                console.log('Client sending message: ', message);
                socket.emit('message', message);
            }

            // This client receives a message
            socket.on('message', function (message) {
                console.log('Client received message:', message);
                if (message === 'got user media') {
                    //isStarted = true;
                    isInitiator = true;
                    isChannelReady = true;
                    maybeStart(message);
                } else if (message.type === 'offer') {
                    if (!isInitiator && !isStarted) {
                        maybeStart(message);
                    }
                    pc.setRemoteDescription(new RTCSessionDescription(message));
                    doAnswer(message);
                } else if (message.type === 'answer' && isStarted) {
                    pc.setRemoteDescription(new RTCSessionDescription(message));
                } else if (message.type === 'candidate' && isStarted) {
                    let candidate = new RTCIceCandidate({
                        sdpMLineIndex: message.label,
                        candidate: message.candidate
                    });
                    pc.addIceCandidate(candidate);
                } else if (message === 'bye' && isStarted) {
                    handleRemoteHangup();
                }
            });




            ////////////////////////////////////////////////////

            let localVideo = document.querySelector('#localVideo');
            let remoteVideo = document.querySelector('#remoteVideo');

            navigator.getUserMedia = navigator.getUserMedia || navigator.mediaDevices.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;

            let constraints = {
                audio: false,
                video: true
            };

            navigator.mediaDevices.getUserMedia(constraints)
                .then(gotStream)
                .catch(function (e) {
                    alert('getUserMedia() error: ' + e.name);
                });

            function gotStream(stream) {
                console.log('Adding local stream.');

                // if (navigator.webkitGetUserMedia) {
                    localVideo.src = window.URL.createObjectURL(stream);
                // }
                // else {
                //     localStream.src = stream;
                // }
                localStream = stream;
                sendMessage('got user media');
                if (isInitiator) {
                    maybeStart();
                }
            }


            console.log('Getting user media with constraints', constraints);

            if (location.hostname !== 'localhost') {
                requestTurn();
            }

            function maybeStart(message) {
                // isChannelReady = true;
                // isInitiator = true;
                console.log('>>>>>>> maybeStart() ', isStarted, localStream, isChannelReady);
                if (!isStarted && typeof localStream !== 'undefined' && isChannelReady) {
                    console.log('>>>>>> creating peer connection');
                    createPeerConnection(message);
                    pc.addStream(localStream);
                    isStarted = true;
                    console.log('isInitiator', isInitiator);
                    if (isInitiator) {
                        doCall();
                    }
                }
            }

            window.onbeforeunload = function () {
                sendMessage('bye');
            };

            /////////////////////////////////////////////////////////

            function createPeerConnection(message) {
                try {
                    //let msg = JSON.parse(message.data);

                    pc = new RTCPeerConnection(pcConfig) || webkitRTCPeerConnection(pcConfig) || mozRTCPeerConnection(pcConfig);
                    pc.setRemoteDescription(new RTCSessionDescription(message));
                    console.log(`SetRemomteDescription`);
                    pc.onicecandidate = handleIceCandidate;
                    console.log(`handleIceCandidate`);
                    pc.onaddstream = handleRemoteStreamAdded;
                    console.log(`handleRemoteStreamAdde`);
                    pc.onremovestream = handleRemoteStreamRemoved;
                    console.log('Created RTCPeerConnnection');
                } catch (e) {
                    console.log('Failed to create PeerConnection, exception: ' + e.message);
                    alert('Cannot create RTCPeerConnection object.');
                }
            }

            function handleIceCandidate(event) {
                console.log('icecandidate event: ', event);
                if (event.candidate) {
                    sendMessage({
                        type: 'candidate',
                        label: event.candidate.sdpMLineIndex,
                        id: event.candidate.sdpMid,
                        candidate: event.candidate.candidate
                    });
                } else {
                    console.log('End of candidates.');
                }
            }

            function handleRemoteStreamAdded(event) {
                //if (navigator.webkitGetUserMedia) {
                    console.log('Remote stream added.');
                    remoteVideo.src = window.URL.createObjectURL(event.stream);
                // }
                // else {
                //     remoteStream.srcObject = event.stream;
                // }
                remoteStream = event.stream;
            }

            function handleCreateOfferError(event) {
                console.log('createOffer() error: ', event);
            }

            function doCall() {
                console.log('Sending offer to peer');
                pc.createOffer(setLocalAndSendMessage, handleCreateOfferError);
            }

            function doAnswer(message) {
                console.log('Sending answer to peer.');
                pc.createAnswer(message.id).then(
                    setLocalAndSendMessage).catch(
                    onCreateSessionDescriptionError);
            }

            function setLocalAndSendMessage(sessionDescription) {
                // Set Opus as the preferred codec in SDP if Opus is present.
                //  sessionDescription.sdp = preferOpus(sessionDescription.sdp);
                pc.setLocalDescription(sessionDescription);
                console.log('setLocalAndSendMessage sending message', sessionDescription);
                sendMessage(sessionDescription);
            }

            function onCreateSessionDescriptionError(error) {
                trace('Failed to create session description: ' + error.toString());
            }

            function requestTurn(turnURL) {
                let turnExists = false;
                for (let i in pcConfig.iceServers) {
                    if (pcConfig.iceServers[i].url.substr(0, 5) === 'turn:') {
                        turnExists = true;
                        turnReady = true;
                        break;
                    }
                }
                if (!turnExists) {
                    console.log('Getting TURN server from ', turnURL);
                    // No TURN server. Get one from computeengineondemand.appspot.com:
                    let xhr = new XMLHttpRequest();
                    xhr.onreadystatechange = function () {
                        if (xhr.readyState === 4 && xhr.status === 200) {
                            let turnServer = JSON.parse(xhr.responseText);
                            console.log('Got TURN server: ', turnServer);
                            pcConfig.iceServers.push({
                                'url': 'turn:192.158.29.39:3478?transport=tcp', //+ turnServer.username + '@' + turnServer.turn,
                                'username':'28224511:1379330808',
                                'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA='
                            });
                            turnReady = true;
                        }
                    };
                    //xhr.open('GET', turnURL, true);
                    //xhr.send();
                }
            }

            // function handleRemoteStreamAdded(event) {
            //     console.log('Remote stream added.');
            //     remoteVideo.src = window.URL.createObjectURL(event.stream);
            //     remoteStream = event.stream;
            // }

            function handleRemoteStreamRemoved(event) {
                console.log('Remote stream removed. Event: ', event);
            }

            function hangup() {
                console.log('Hanging up.');
                stop();
                sendMessage('bye');
            }

            function handleRemoteHangup() {
                console.log('Session terminated.');
                stop();
                isInitiator = false;
            }

            function stop() {
                isStarted = false;
                // isAudioMuted = false;
                // isVideoMuted = false;
                pc.close();
                pc = null;
            }

            ///////////////////////////////////////////

            // Set Opus as the default audio codec if it's present.
            function preferOpus(sdp) {
                let sdpLines = sdp.split('\r\n');
                let mLineIndex;
                // Search for m line.
                for (let i = 0; i < sdpLines.length; i++) {
                    if (sdpLines[i].search('m=audio') !== -1) {
                        mLineIndex = i;
                        break;
                    }
                }
                if (mLineIndex === null) {
                    return sdp;
                }

                // If Opus is available, set it as the default in m line.
                for (let i = 0; i < sdpLines.length; i++) {
                    if (sdpLines[i].search('opus/48000') !== -1) {
                        let opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i);
                        if (opusPayload) {
                            sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex],
                                opusPayload);
                        }
                        break;
                    }
                }

                // Remove CN in m line and sdp.
                sdpLines = removeCN(sdpLines, mLineIndex);

                sdp = sdpLines.join('\r\n');
                return sdp;
            }

            function extractSdp(sdpLine, pattern) {
                let result = sdpLine.match(pattern);
                return result && result.length === 2 ? result[1] : null;
            }

            // Set the selected codec to the first in m line.
            function setDefaultCodec(mLine, payload) {
                let elements = mLine.split(' ');
                let newLine = [];
                let index = 0;
                for (let i = 0; i < elements.length; i++) {
                    if (index === 3) { // Format of media starts from the fourth.
                        newLine[index++] = payload; // Put target payload to the first.
                    }
                    if (elements[i] !== payload) {
                        newLine[index++] = elements[i];
                    }
                }
                return newLine.join(' ');
            }

            // Strip CN from sdp before CN constraints is ready.
            function removeCN(sdpLines, mLineIndex) {
                let mLineElements = sdpLines[mLineIndex].split(' ');
                // Scan from end for the convenience of removing an item.
                for (let i = sdpLines.length - 1; i >= 0; i--) {
                    let payload = extractSdp(sdpLines[i], /a=rtpmap:(\d+) CN\/\d+/i);
                    if (payload) {
                        let cnPos = mLineElements.indexOf(payload);
                        if (cnPos !== -1) {
                            // Remove CN payload from m line.
                            mLineElements.splice(cnPos, 1);
                        }
                        // Remove CN line in sdp
                        sdpLines.splice(i, 1);
                    }
                }

                sdpLines[mLineIndex] = mLineElements.join(' ');
                return sdpLines;
            }

Backend Code:

        const path = require(`path`);
        const http = require(`http`);
        const express = require(`express`);
        const socketIO = require(`socket.io`);

        const {generateMessage} = require(`./utils/message`);
        const {isRealString} = require(`./utils/validation`);

        const publicPath = path.join(__dirname,`../public`);


        let app =  express();
        let server = http.createServer(app);
        let io = socketIO(server);

        app.use(express.static(publicPath));

        app.get(`/video`,(req,res)=>{
            res.sendFile(path.join(__dirname,`../public/videochat.html`));
        });

        io.on(`connection`,(socket)=>{
            console.log(`New User Connected`);

            socket.emit(`newMessage`,generateMessage(`Admin`, `Welcome to Chat App`));

            socket.broadcast.emit(`newMessage`,generateMessage(`Admin`,`New User Joined`));

            socket.on(`join`,(params,callback)=>{
                if(!isRealString(params.name) || !isRealString(params.room)){
                    callback(`Name and Room name is required`);
                }

                callback();
            });

            socket.on('createMessage',(message,callback)=>{
                console.log(`create Message`,message);
                io.emit(`newMessage`,generateMessage(message.from,message.text));
                callback();
            });

            socket.on(`offer`,(callback)=>{
                console.log(`Call is Starting`);
                callback();
            });



            /////////////////////////////////////////
            function log() {
                let array = ['Message from server:'];
                array.push.apply(array, arguments);
                socket.emit('log', array);
            }

            socket.on('message', function (message) {
                log('Client said: ', message);
                // for a real app, would be room-only (not broadcast)
                socket.emit('message', message);
            });

            socket.on(`sdp`,(data)=>{
                console.log('Received SDP from ' + socket.id);
                socket.emit('sdp received', data.sdp);
            });
            socket.on('create or join', function (room) {
                log('Received request to create or join room ' + room);

                let numClients = io.sockets.sockets.length;
                log('Room ' + room + ' now has ' + numClients + ' client(s)');

                if (numClients === 1) {
                    socket.join(room);
                    log('Client ID ' + socket.id + ' created room ' + room);
                    socket.emit('created', room, socket.id);

                } else if (numClients === 2) {
                    log('Client ID ' + socket.id + ' joined room ' + room);
                    io.sockets.in(room).emit('join', room);
                    socket.join(room);
                    socket.emit('joined', room, socket.id);
                    io.sockets.in(room).emit('ready');
                } else { // max two clients
                    socket.emit('full', room);
                }
            });

            socket.on('ipaddr', function () {
                let ifaces = os.networkInterfaces();
                for (let dev in ifaces) {
                    ifaces[dev].forEach(function (details) {
                        if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
                            socket.emit('ipaddr', details.address);
                        }
                    });
                }
            });

            socket.on('bye', function () {
                console.log('received bye');
            });
        });

        server.listen(3000,`192.168.0.105`,()=>{
            console.log(`Server is Up`)
        });

I'm getting this Error:- Failed to create PeerConnection, exception: Failed to construct 'RTCSessionDescription': parameter 1 ('descriptionInitDict') is not an object.

Also getting Uncaught (in promise) DOMException: Failed to set remote offer sdp: Called in wrong state: kHaveLocalOffer


Solution

  • This line:

            function doCall() {
                console.log('Sending offer to peer');
                pc.createOffer(setLocalAndSendMessage, handleCreateOfferError);
            }
    

    Change to:

    pc.createOffer().then(setDescription).catch(errorHandler);
    
        function setDescription(description) {
            console.log('Got description', description);
    
            pc.setLocalDescription(description).then(function() {
                console.log("Sending SDP");
                socket.emit('signal', {'sdp': description});
            }).catch(errorHandler);
        }
    

    You should than handle the emitted SDP:

        function gotMessageFromServer(signal) {
            console.log('Got message', signal);
    
            if(signal.sdp) {
                console.log("Set remote description: ", signal);
                pc.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() {
                    // Only create answers in response to offers
                    if(signal.sdp.type === 'offer') {
                        console.log("Sending answer");
                        pc.createAnswer().then(setDescription).catch(errorHandler);
                    }
                }).catch(errorHandler);
            } else if(signal.ice) {
                console.log('Got remote ice candidate: ', signal);
                pc.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(errorHandler);
            }
        }