import { Component, OnInit, ElementRef, ViewChild } from '@angular/core';
import { Socket } from 'ngx-socket-io';
import { ToastController } from '@ionic/angular';
import { AppointmentService } from 'src/app/services/appointment.service';
@Component({
selector: 'app-videocall',
templateUrl: './videocall.page.html',
styleUrls: ['./videocall.page.scss'],
})
export class VideocallPage implements OnInit {
@ViewChild('localVideo', { static: true }) localVideo: ElementRef;
@ViewChild('remoteVideo', { static: true }) remoteVideo: ElementRef;
consultationRoomId: any;
callBtn: any;
muteBtn: any;
localStream: MediaStream;
remoteStream: MediaStream;
rtcPeerConnection: RTCPeerConnection;
constructor(public socket: Socket, private toastCtrl: ToastController, private appoint: AppointmentService,
private elRef: ElementRef) { }
ngOnInit() {
this.callBtn = document.getElementById("call");
this.muteBtn = document.getElementById("mute");
var endBtn = document.getElementById("end");
var divConsultingRoom = document.getElementById("consultingRoom");
var iceServers = {
'iceServers': [
{ urls: 'stun:stun.services.mozilla.com' },
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun01.sipphone.com' },
{ urls: 'stun:stun.ekiga.net' },
{ urls: 'stun:stun.fwdnet.net' },
{ urls: 'stun:stun.ideasip.com' },
{ urls: 'stun:stun.iptel.org' },
{ urls: 'stun:stun.rixtelecom.se' },
{ urls: 'stun:stun.schlund.de' },
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
{ urls: 'stun:stun2.l.google.com:19302' },
{ urls: 'stun:stun3.l.google.com:19302' },
{ urls: 'stun:stun4.l.google.com:19302' },
{ urls: 'stun:stunserver.org' },
{ urls: 'stun:stun.softjoys.com' },
{ urls: 'stun:stun.voiparound.com' },
{ urls: 'stun:stun.voipbuster.com' },
{ urls: 'stun:stun.voipstunt.com' },
{ urls: 'stun:stun.voxgratia.org' },
{ urls: 'stun:stun.xten.com' },
{
urls: 'turn:192.158.29.39:3478?transport=udp',
credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
username: '28224511:1379330808'
},
{
urls: 'turn:192.158.29.39:3478?transport=tcp',
credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
username: '28224511:1379330808'
}
]
}
var streamConstraints = { audio: true, video: true };
var isCaller;
//this.consultationRoomId = this.appoint.getConsultationId();
this.consultationRoomId = '1234';
this.socket.connect();
this.socket.on('created', (room) => {
//debugger;
alert('Starting consultation');
navigator.mediaDevices.getUserMedia(streamConstraints).then((stream) => {
this.localStream = stream;
this.localVideo.nativeElement.srcObject = stream;
isCaller = true;
}).catch(function (err) {
console.log('An error ocurred when accessing media devices', err);
});
});
this.socket.on('joined', (room) => {
alert('Joining consultation');
navigator.mediaDevices.getUserMedia(streamConstraints).then((stream) => {
this.localStream = stream;
this.localVideo.nativeElement.srcObject = stream;
this.socket.emit('ready', this.consultationRoomId);
}).catch(function (err) {
console.log('An error ocurred when accessing media devices', err);
});
});
this.socket.on('disconnect', (room) => {
console.log('Doctor has ended the consultation>>>' + room);
});
this.socket.on('candidate', (event) => {
var candidate = new RTCIceCandidate({
sdpMLineIndex: event.label,
candidate: event.candidate
});
this.rtcPeerConnection.addIceCandidate(candidate);
});
this.socket.on('ready', () => {
if (isCaller) {
this.rtcPeerConnection = new RTCPeerConnection(iceServers);
this.rtcPeerConnection.onicecandidate = this.onIceCandidate;
this.rtcPeerConnection.ontrack = this.onAddStream;
this.rtcPeerConnection.addTrack(this.localStream.getTracks()[0], this.localStream);
this.rtcPeerConnection.addTrack(this.localStream.getTracks()[1], this.localStream);
this.rtcPeerConnection.createOffer()
.then(sessionDescription => {
this.rtcPeerConnection.setLocalDescription(sessionDescription);
this.socket.emit('offer', {
type: 'offer',
sdp: sessionDescription,
room: this.consultationRoomId
});
})
.catch(error => {
console.log(error)
})
}
});
this.socket.on('offer', (event) => {
if (!isCaller) {
this.rtcPeerConnection = new RTCPeerConnection(iceServers);
this.rtcPeerConnection.onicecandidate = this.onIceCandidate;
this.rtcPeerConnection.ontrack = this.onAddStream;
this.rtcPeerConnection.addTrack(this.localStream.getTracks()[0], this.localStream);
this.rtcPeerConnection.addTrack(this.localStream.getTracks()[1], this.localStream);
this.rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(event));
this.rtcPeerConnection.createAnswer()
.then(sessionDescription => {
this.rtcPeerConnection.setLocalDescription(sessionDescription);
this.socket.emit('answer', {
type: 'answer',
sdp: sessionDescription,
room: this.consultationRoomId
});
})
.catch(error => {
console.log(error)
})
}
});
this.socket.on('answer', (event) => {
console.log("on answer>>>>" + event);
this.rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(event));
})
}
call() {
//this.divConsultingRoom.style = "display: block;";
this.socket.emit('create or join', this.consultationRoomId);
}
mute() {
if (this.muteBtn.innerHTML == "Mute") {
this.localStream.getAudioTracks()[0].enabled = false;
this.muteBtn.innerHTML = "Unmute";
}
else {
this.localStream.getAudioTracks()[0].enabled = true;
this.muteBtn.innerHTML = "Mute";
}
}
end() {
this.socket.emit('disconnect', this.consultationRoomId);
}
onIceCandidate = (event) => {
if (event.candidate) {
console.log('sending ice candidate');
this.socket.emit('candidate', {
type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate,
room: this.consultationRoomId
})
}
}
onAddStream = (event) => {
this.remoteVideo.nativeElement.srcObject = event.streams[0];
this.remoteStream = event.streams[0];
}
}
下面也是HTML页面
<ion-content>
<div id="consultingRoom">
<video id="localVideo" #localVideo inline autoplay></video>
<video id="remoteVideo" #remoteVideo inline autoplay></video>
</div>
<ion-button id="call" color="primary" (click)="call()">Call</ion-button>
<ion-button id="mute" color="secondary" (click)="mute()">Mute</ion-button>
<ion-button id="end" color="tertiary" (click)="end()">End</ion-button>
</ion-content>
addStreamMethod 中的 event.streams[0] 对象显示带有接收者 id 的远程 MediaStream,但前端的 remoteVideo 元素仍然是空白的。信令服务器是在 AWS EC2 实例上运行的节点套接字服务器。
下面是远程媒体流的调试
以下是来自 Chrome 的 webrtc 内部日志
9/3/2020, 4:39:00 PM
transceiverAdded
9/3/2020, 4:39:00 PM
transceiverAdded
9/3/2020, 4:39:00 PM
createOffer
options: {offerToReceiveVideo: -1, offerToReceiveAudio: -1, voiceActivityDetection: true, iceRestart: false}
9/3/2020, 4:39:00 PM negotiationneeded
9/3/2020, 4:39:00 PM
createOfferOnSuccess
9/3/2020, 4:39:00 PM
setLocalDescription
9/3/2020, 4:39:00 PM
transceiverModified
9/3/2020, 4:39:00 PM
transceiverModified
9/3/2020, 4:39:00 PM
signalingstatechange
9/3/2020, 4:39:00 PM setLocalDescriptionOnSuccess
9/3/2020, 4:39:00 PM
icegatheringstatechange
9/3/2020, 4:39:00 PM
icecandidate (host)
9/3/2020, 4:39:00 PM
icecandidate (host)
9/3/2020, 4:39:00 PM
icecandidate (host)
9/3/2020, 4:39:00 PM
icecandidate (host)
9/3/2020, 4:39:00 PM
icecandidate (host)
9/3/2020, 4:39:00 PM
icecandidate (host)
9/3/2020, 4:39:00 PM
icecandidate (host)
9/3/2020, 4:39:00 PM
icecandidate (host)
9/3/2020, 4:39:00 PM
icecandidateerror
9/3/2020, 4:39:00 PM
icecandidateerror
9/3/2020, 4:39:00 PM
icecandidateerror
9/3/2020, 4:39:00 PM
icecandidateerror
9/3/2020, 4:39:00 PM
icecandidate (srflx)
9/3/2020, 4:39:00 PM
icecandidate (srflx)
9/3/2020, 4:39:00 PM
setRemoteDescription
9/3/2020, 4:39:00 PM
icecandidate (srflx)
9/3/2020, 4:39:00 PM
transceiverModified
9/3/2020, 4:39:00 PM
transceiverModified
9/3/2020, 4:41:38 PM
signalingstatechange
9/3/2020, 4:41:38 PM setRemoteDescriptionOnSuccess
9/3/2020, 4:41:39 PM
icecandidate (srflx)
9/3/2020, 4:41:39 PM
icecandidate (srflx)
9/3/2020, 4:41:39 PM
icecandidate (srflx)
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidate (srflx)
9/3/2020, 4:41:39 PM
icecandidate (srflx)
9/3/2020, 4:41:39 PM
icecandidate (srflx)
9/3/2020, 4:41:39 PM
icecandidate (srflx)
9/3/2020, 4:41:39 PM
icecandidate (srflx)
9/3/2020, 4:41:39 PM
icecandidate (srflx)
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icecandidateerror
9/3/2020, 4:41:39 PM
icegatheringstatechange
complete
9/3/2020, 4:41:39 PM
addIceCandidate (host)
9/3/2020, 4:41:39 PM
addIceCandidate (host)
9/3/2020, 4:41:39 PM
iceconnectionstatechange
checking
9/3/2020, 4:41:39 PM
addIceCandidate (host)
9/3/2020, 4:41:39 PM
iceconnectionstatechange (legacy)
checking
9/3/2020, 4:41:39 PM
connectionstatechange
connecting
9/3/2020, 4:41:39 PM
addIceCandidate (host)
9/3/2020, 4:41:39 PM
addIceCandidate (host)
9/3/2020, 4:41:39 PM
addIceCandidate (host)
9/3/2020, 4:41:39 PM
addIceCandidate (srflx)
9/3/2020, 4:41:39 PM
addIceCandidate (srflx)
9/3/2020, 4:41:54 PM
iceconnectionstatechange
disconnected
9/3/2020, 4:41:54 PM
iceconnectionstatechange (legacy)
failed
9/3/2020, 4:41:54 PM
connectionstatechange
failed
目前我只在浏览器上进行测试,尚未在 Android 和 IOS 上进行测试请让我知道我错过了什么?