1

I have been having this error for a while

enter image description here

Have been trying to use async to wait for the local description to be updated but as how my code works right now it would not be able to integrate it and also I heard that socket.on already does async itself.

I have also tried using breakpoints in vs code to debug the code which doesn't work well.

Would greatly appreciate if anyone knows a workaround for this. The code is attached below

'use strict';

var localStream;
var remoteStream;
var isInitiator;
var configuration = {
  iceServers: [
    {
      urls: 'stun:stun.l.google.com:19302'
    }
  ]
};
var pc = new RTCPeerConnection(configuration);

// Define action buttons.
const callButton = document.getElementById('callButton');
const hangupButton = document.getElementById('hangupButton');

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

window.room = prompt('Enter room name:');

var socket = io.connect();

if (room !== '') {
  console.log('Message from client: Asking to join room ' + room);
  socket.emit('create or join', room);
}

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

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

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

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

function sendMessage(message) {
  socket.emit('message', message);
}

// This client receives a message
socket.on('message', function(message) {
  try {
    if (message.type === 'offer') {
      pc.setRemoteDescription(new RTCSessionDescription(message));
      // const stream = navigator.mediaDevices.getUserMedia({
      //   audio: true,
      //   video: true
      // });
      // stream.getTracks().forEach(track => pc.addTrack(track, localStream));
      pc.setLocalDescription(
        pc.createAnswer(setLocalAndSendMessage, function(err) {
          console
            .log(err.name + ': ' + err.message)
            .then(pc.setLocalDescription);
        })
      );
    } else if (message.type === 'answer') {
      console.log('This is to check if answer was returned');
      pc.setRemoteDescription(new RTCSessionDescription(message));
    } else if (message.type === 'candidate') {
      pc.addIceCandidate(candidate);
    }
  } catch (err) {
    console.error(err);
  }
});

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

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

// Set up initial action buttons status: disable call and hangup.
callButton.disabled = true;
hangupButton.disabled = true;

// Add click event handlers for buttons.
callButton.addEventListener('click', callStart);
hangupButton.addEventListener('click', hangupCall);

function startVideo() {
  navigator.mediaDevices
    .getUserMedia({
      audio: true,
      video: true
    })
    .then(function(mediaStream) {
      localStream = mediaStream;
      localVideo.srcObject = mediaStream;
    })
    .catch(function(err) {
      console.log('getUserMedia() error: ' + err.name);
    });
  callButton.disabled = false;
}

function callStart() {
  createPeerConnection();
  //pc.addTrack(mediaStream);
  //stream.getTracks().forEach(track => pc.addTrack(track, localStream));
  callButton.disabled = true;
  hangupButton.disabled = false;
  if (isInitiator) {
    console.log('Sending offer to peer');
    pc.createOffer(setLocalAndSendMessage, function(err) {
      console.log(err.name + ': ' + err.message).then(pc.setLocalDescription);
    });
  }
}

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

function createPeerConnection() {
  try {
    pc = new RTCPeerConnection(null);
    pc.onicecandidate = ({ candidate }) => sendMessage({ candidate });
    pc.ontrack = event => {
      if (remoteVideo.srcObject) return;
      remoteVideo.srcObject = event.streams[0];
    };
    console.log('Created RTCPeerConnnection');
  } catch (e) {
    console.log('Failed to create PeerConnection, exception: ' + e.message);
    alert('Cannot create RTCPeerConnection object.');
    return;
  }
}

function setLocalAndSendMessage(sessionDescription) {
  console.log('setLocalAndSendMessage sending message', sessionDescription);
  pc.setLocalDescription(sessionDescription);
  sendMessage(sessionDescription);
}

function hangupCall() {
  pc.close();
  pc = null;
}
Catarina Ferreira
  • 1,824
  • 5
  • 17
  • 26
tngrj
  • 141
  • 3
  • 14

1 Answers1

2

There is no workaround to understanding asynchronous code. You're cut'n'pasting your way here.

If you're not going to use async/await, then you need to contend with that JavaScript is single-threaded, and can't ever block to wait for an asynchronous operation to finish, so you can never just use the return value from an asynchronous method directly, like you're trying to do here:

createAnswer is an asynchronous method, returning a promise, not an answer, so this is wrong:

  pc.setLocalDescription(                                    // <-- wrong
    pc.createAnswer(setLocalAndSendMessage, function(err) {  //
      console
        .log(err.name + ': ' + err.message)
        .then(pc.setLocalDescription);
    })
  );

You're calling setLocalDescription(promise), which gives you the error you mention, since a promise is not a valid description. Instead, a promise is an object you attach callbacks to:

const promise = pc.createAnswer();
promise.then(setLocalAndSendMessage, function(err) {
  console.log(err.name + ': ' + err.message);
});

or simply:

pc.createAnswer()
  .then(setLocalAndSendMessage, function(err) {
    console.log(err.name + ': ' + err.message);
  });

We can even use then successively to form a promise chain:

pc.createAnswer()
  .then(function(answer) {
    return pc.setLocalDescription(answer);
  })
  .then(function() {
    sendMessage(pc.localDescription);
  })
  .catch(function(err) {
    console.log(err.name + ': ' + err.message);
  });

Also, I shouldn't have to tell you console.log() does not return a promise!

Sadly, you're copying really old code here, and RTCPeerConnection has some legacy APIs and does some tricks to sometimes let callers get away with calling these negotiation methods without truly promise-chaining or checking for errors. But it inevitably leads to trouble.

jib
  • 40,579
  • 17
  • 100
  • 158
  • Thanks so much for your reply. I have just started coding WebRTC/js recently which is why I might be making rookie mistakes as pointed in my lack of knowledge in what outputs from the console.log(). I'll be referring to your blog for more information going forward as the code I have been referencing was from the official WebRTC Codelabs which I had initially thought was up to date til I had to completely break it apart to fix all the issues in it. – tngrj Nov 20 '18 at 01:24
  • I did the changes to use async and also updated all the syntax to the new standard but the remote video still does not display. Also is it possible to have more than 2 users in the room as some of the examples uses pc1 and pc2 instead of just having pc = new RTCpeerconnection(configuration)? https://pastebin.com/0EHAUnEc – tngrj Nov 20 '18 at 19:08
  • The `pc` inside `createPeerConnection()` appears to be an entirely different peer connection than `pc` the rest of your code is using, so I'm not surprised its `ontrack` never fires. – jib Nov 20 '18 at 20:55
  • I removed the duplicated new RTCconnection from the createPeerConnection() function and also added a console log inside the pc.ontrack to check if it fires and it does return the message inside. Edit: Turns out its my hangup button causing the candidates to immediately trigger to null right after connection which causes it to not display the remote video – tngrj Nov 21 '18 at 01:30