1

I am making a sample application where there will be two video elements and a button "Call". The first video element (#localVideo) will show the output of the local media stream. When i press the call button the remote video element will play the remote media stream. I have made the same application in raw JavaScript, which is working fine.

In VueJS, i am making a WebRTC component to get the user media and set the stream to the local video element. When users press the call button, i am creating both of the RTCPeerConnection objects, sending the offer, setting local description, sending answer and all.

here is the template of the component -

<template>
  <div class="webRTC">
    <video id = "localVideo" playsinline autoplay muted></video>
    <button id = "call" v-on:click='call'> Call </button>
    <video id = "remoteVideo" playsinline autoplay controls></video>
  </div>
</template>

<script src="./webRTC.js"></script>

And here is the script for the component -

export default {
  name: 'webRTC',
  sockets: {
    connect: function () {
      console.log('Socket IO connected!')
    },

    TestConnection: function () {
      console.log('Test connection successful!')
    }
  },

  data: function () {
    return {
      localStream: null,
      remoteStream: null,
      pc1: null,
      pc2: null
    }
  },

  methods: {
    call: function () {
      this.pc1 = new RTCPeerConnection()
      this.pc1.addEventListener('icecandidate', e => this.addIceCandidate(this.pc1, e))

      this.pc2 = new RTCPeerConnection()
      this.pc2.addEventListener('icecandidate', e => this.addIceCandidate(this.pc2, e))
      this.pc2.addEventListener('track', this.gotRemoteStrem)

      this.localStream.getTracks().forEach(track => {
        console.log('Adding local stream')
        this.pc1.addTrack(track, this.localStream)
      })

      this.pc1.createOffer({ offerToReceiveAudio: 1, offerToReceiveVideo: 1 }).then(this.gotDescription)
    },

    gotRemoteStrem: function (event) {
      console.log('Got Remote stream')
      let remoteVideo = document.querySelector('#remoteVideo')
      this.remoteStream = event.streams[0]
      remoteVideo.srcObject = event.streams[0]
    },

    gotDescription: function (description) {
      console.log('Got description 1')
      this.pc1.setLocalDescription(description)
      this.pc2.setRemoteDescription(description)

      this.pc2.createAnswer().then(this.gotDescription2)
    },

    gotDescription2: function (description) {
      console.log('Got description 2')
      this.pc2.setLocalDescription(description)
      this.pc1.setRemoteDescription(description)
    },

    addIceCandidate: function (pc, event) {
      this.getOtherPC(pc).addIceCandidate(event.candidate).then(this.addIceCandicateSuccess).catch(this.addIceCandicateFailure)
    },

    addIceCandicateSuccess: function () {
      console.log('Ice candidate added successfully')
    },

    addIceCandicateFailure: function () {
      console.log('Ice candidate failure')
    },

    getOtherPC: function (pc) {
      if (pc === this.pc1) {
        return this.pc2
      }

      return this.pc1
    },

    gotDevices: function (stream) {
      let localVideo = document.querySelector('#localVideo')
      this.localStream = stream
      localVideo.srcObject = stream
    },

    handleMediaError: function (error) {
      console.error('Error:' + error.name)
    }
  },

  created: function () {
    console.log('webRTC is created!')
    let prom = navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then(this.gotDevices).catch(this.handleMediaError)
  }
}

The local video is showing properly. The problem i am facing is that, when i am pressing the Call button, the remote video is not showing anything, rather i see a loading circle like in the screenshot. There is no console error as well.

enter image description here

I have debugged and saw the srcObject of both local and remote video and they appears to be same - enter image description here Can anyone please tell me if i am doing something wrong? Also is there any other way to debug this?

Note:

The project/source code can be downloaded from here, if you want you can download and check the code. I will try to prepare a codepen - https://drive.google.com/open?id=1e7C0ojZ0jT7EXFNtCKcWpJBpKd_mWi_s

Rasheduzzaman Sourov
  • 1,375
  • 2
  • 15
  • 36

1 Answers1

3

Using promises without async/await requires propagating errors manually and checking them.

Add a catch e.g. here to see errors:

this.pc1.createOffer({offerToReceiveAudio: 1, offerToReceiveVideo: 1})
  .then(this.gotDescription)
  .catch(e => console.log(e)); // <-- here

...and you should see that this is undefined inside gotDescription(). That's because you're passing a member function as a callback to the then function, which ends up invoking it without a this. Either use this.gotDescription.bind(this) or (nicer) arrow functions. E.g.:

this.pc1.createOffer({offerToReceiveAudio: 1, offerToReceiveVideo: 1})
  .then(offer => this.gotDescription(offer)) // arrow function invoking on this
  .catch(e => console.log(e));

Furthermore, unless you return all the promises, they won't form a single chain to catch all errors.

All these calls return promises, which you're ignoring:

/* const unusedPromise1 = */ this.pc1.setLocalDescription(description)
/* const unusedPromise2 = */ this.pc2.setRemoteDescription(description)
/* const unusedPromise3 = */ this.pc2.createAnswer().then(this.gotDescription2)

Luckily (and perhaps confusingly), RTCPeerConnection automatically queues operations done on it, so this may actually work, provided there are no errors, which may get swallowed, depending on browser.

Instead, to catch errors, either do this:

this.pc1.setLocalDescription(description)
.then(() => this.pc2.setRemoteDescription(description))
.then(() => this.pc2.createAnswer())
.then(answer => this.gotDescription2(answer))
.catch(e => console.log(e))

or use the nicer async/await syntax which propagates errors correctly:

gotDescription: async function (description) {
  console.log('Got description 1')
  await this.pc1.setLocalDescription(description)
  await this.pc2.setRemoteDescription(description)
  await this.gotDescription2(await this.pc2.createAnswer());
}
jib
  • 40,579
  • 17
  • 100
  • 158