Hari Gyan Vashisth
Hari Gyan Vashisth

Reputation: 57

RTCPeerConnection not listening for track event of remote

I am trying to use WebRTC to implement video calling in an application. Connection is being established successfully between peers after sharing the offer, answer and ice-candidate.

Remote peer is sending the track (video, audio), but local peer (receiving peer) is not listing for the track of remote peer connection.

Adding track to the peer connection :

const localStream = await window.navigator.mediaDevices.getUserMedia({video: true, audio: true});
localStream.getTracks().forEach(track => {
    this.peerConnection.addTrack(track, localStream);
});

Listening track event on other side :

this.peerConnection.addEventListener('track', async (event) => {
          console.log("Remote video stream added to the video element...");
            remoteStream.addTrack(event.track);
        });
  1. How to receive and show the video of remote peer?
  2. Is there any way to inspect the stream/tracks of RTCPeerConnection?

We are using angular for our application. Here is the complete JS code :

  1. Triggering video call component to offer call
  makeVideoCall(){
    var videoCallDialog = this.matDialog.open(VideoCallComponent, { 
      height: '600px', 
      width: '700px', 
      data : { 
        'friendUser' : this.friendUser
      }
    });
  }
  1. Listening video call offer and trigger video-call component
    if (chatMessage.type == ChatType.VIDEO_CALL_OFFER){
      this.userChatService.findFriend(chatMessage.textFrom).subscribe(user=>{
        chatMessage.data = JSON.parse(chatMessage.data);
        var videoCallDialog = this.matDialog.open(VideoCallComponent, { 
          height: '600px', 
          width: '700px', 
          data : { 
            'friendUser' : user,
            'videoCallOfferChat' : chatMessage
          }
        });    
      });
    }
import { Component, OnInit, Inject } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { User } from 'src/app/user/model/user';
import { RxStompService } from '@stomp/ng2-stompjs';
import { Subscription } from 'rxjs';
import { LoginService } from 'src/app/login/service/login.service';
import { Chat } from 'src/app/chats/model/chat';
import { ChatType } from 'src/app/chats/model/chat-type';
import { v4 as uuidv4 } from 'uuid';
import { CryptoService } from 'src/app/crypto/service/crypto.service';

@Component({
  selector: 'app-video-call',
  templateUrl: './video-call.component.html',
  styleUrls: ['./video-call.component.css']
})
export class VideoCallComponent implements OnInit {

  currentUser : User;
  friendUser : User;
  chatSubscription : Subscription;
  peerConnection : RTCPeerConnection;
  offer : Chat;
  answer : Chat;
  iceCandidate : Chat;
  generatedIceCandidateCount = 0;
  receivedIceCandidateCount = 0; 
  iceCandidates = new Array<any>(); 

  constructor(
    private loginService : LoginService,
    @Inject(MAT_DIALOG_DATA) public data: any,
    private stompService : RxStompService,
    private cryptoService : CryptoService
  ) {
    this.currentUser = this.loginService.getCurrentUser();
    if(data.friendUser){
      this.friendUser = data.friendUser;
      const configuration = {'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]};
      this.peerConnection = new RTCPeerConnection(configuration);

      this.startReceivingStreams();

      // Listen for local ICE candidates on the local RTCPeerConnection
      this.peerConnection.addEventListener('icecandidate', event => {
        if (event.candidate) {
          // console.log('ICE Candidate generated', event.candidate);
          console.log("Genrated Candidate ", ++this.generatedIceCandidateCount);
          this.iceCandidates.push(event.candidate);
          // this.sendToSocket(this.createChat(null, ChatType.ICE_CANDIDATE, {'iceCandidate' : event.candidate}));
        }else{
          console.log("Candidates in buffer : ", this.generatedIceCandidateCount); 
          this.iceCandidates.forEach((candidate) => {
            this.sendToSocket(this.createChat(null, ChatType.ICE_CANDIDATE, {'iceCandidate' : candidate}))
          });    
          if(this.iceCandidates.length>0)
            this.startTransmittingStreams();
        }
      });

      // Listen for connectionstatechange on the local RTCPeerConnection
      this.peerConnection.addEventListener('connectionstatechange', event => {
        if (this.peerConnection.connectionState === 'connected') {
            console.log("Connection done...");
          }
      });

    }
    this.startListeningVideoCalls();
    if (data.videoCallOfferChat){
      this.startReceivingStreams();
      this.answerVideoCall(data.videoCallOfferChat);
    }
    else{
      this.offerVideoCall();
    }

  }
  
  ngOnInit(): void {}

  async startTransmittingStreams(){
    const localStream = await window.navigator.mediaDevices.getUserMedia({video: true, audio: true});
    localStream.getTracks().forEach(async track => {
        console.log("Adding track...", track);
        await this.peerConnection.addTrack(track, localStream);
    });
  }

   startReceivingStreams(){
    console.log("Start receiving...");
    let remoteStream = new MediaStream();
    this.peerConnection.ontrack = event => {
      console.log("[addEventListener] Remote video stream added to the video element...", event);
      let remoteVideo : any = document.getElementById('friendUserVideoTrack');
      remoteVideo.srcObject = remoteStream;
      remoteStream.addTrack(event.track);
    }
  }

  private startListeningVideoCalls(){
    if(this.stompService.connected()){
      this.chatSubscription = this.stompService.watch('/text/'+this.currentUser.id).subscribe((data:any)=>{
        let chat = JSON.parse(data.body);
        if(chat.data)
          chat.data = JSON.parse(chat.data);
        if (chat.type == ChatType.VIDEO_CALL_ANSWER) {
          console.log('Video Call Answer ...', chat);
          if (chat.data.answer) {
            // Contains RTCConnection answer then connect
            console.log('Call accepted', chat.data.answer);
            this.videoCallAnswered(chat);
          }else{
            // Doesn't contains RTCConnection answer : call rejected by recipient
            console.log('Call rejected...');
          }
        }
        if(chat.type == ChatType.ICE_CANDIDATE){
          console.log('Chat with ICE Candidate ', chat);
          this.iceCandidateReceived(chat.data.iceCandidate);
        }
      });
    }
  }

  async offerVideoCall() {
    var options = { offerToReceiveVideo: true, offerToReceiveAudio: true };
    const offer = await this.peerConnection.createOffer(options);
    await this.peerConnection.setLocalDescription(offer);
    this.sendToSocket(this.createChat(null, ChatType.VIDEO_CALL_OFFER, {'offer' : offer}));
  }

  private async answerVideoCall(receivedChat : Chat){
    if (receivedChat.data.offer) {
      let remoteDescription = new RTCSessionDescription(receivedChat.data.offer);
      await this.peerConnection.setRemoteDescription(remoteDescription);
      var options = { offerToReceiveVideo: true, offerToReceiveAudio: true };
      const answer = await this.peerConnection.createAnswer(options);
      await this.peerConnection.setLocalDescription(answer);
      this.sendToSocket(this.createChat(receivedChat.id, ChatType.VIDEO_CALL_ANSWER, {'answer' : answer}));
    }
  }

  private async videoCallAnswered(chat : Chat){
    const remoteDesc = new RTCSessionDescription(chat.data.answer);
    await this.peerConnection.setRemoteDescription(remoteDesc);
  }

  private createChat(id : string, chatType : ChatType, data : any){
    let chat : Chat = new Chat();
    chat.id = uuidv4();
    chat.textFrom = this.currentUser.id;
    chat.textTo = this.friendUser.id;
    chat.textFromName = this.currentUser.name;
    chat.textToName = this.friendUser.name;
    chat.date = new Date().getTime();
    chat.type = chatType;
    chat.message = this.cryptoService.encrypt(chat.message, this.friendUser.userChat.sk);
    chat.data = JSON.stringify(data);
    return chat;
  }

  private sendToSocket(chat : Chat){
    if(this.stompService.connected()){
      this.stompService.publish({
        destination : "/app/text/"+this.friendUser.id,
        body : JSON.stringify(chat)
      });  
    }
  }

  private async iceCandidateReceived(iceCandidate : any){
    if(this.peerConnection.remoteDescription){
      try {
        await this.peerConnection.addIceCandidate(new RTCIceCandidate(iceCandidate));
        console.log("Received Count ", ++this.receivedIceCandidateCount); 
      } catch (e) {
          console.error('Error adding received ice candidate', e);
      }
    }
  }

}

Upvotes: 1

Views: 3482

Answers (2)

I had to create the offer SDP differently.

const offer = await peerConnection.createOffer({
    offerToReceiveAudio: true,
    offerToReceiveVideo: true,
});

Upvotes: 0

Liju
Liju

Reputation: 2313

  1. Please try
this.peerConnection.addEventListener('track',function(event){
remoteVideo=document.getElementById('remoteVideo'); //Change your remote 'video element' id here
remoteVideo.srcObject = event.streams[0];
});
  1. Since WebRTC connections are peer to peer, we cannot inspect or monitor video from server. Only things signal servers will know are the SDPs and ice candidates shared via them.

Upvotes: 0

Related Questions