1
votes

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);
      }
    }
  }

}
1

1 Answers

0
votes
  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.