我正在尝试使用角度在chrome中播放webRTC视频。我有IP摄像机,我正在使用Wowza Streaming Engine将他们的RTSP流转换为webRTC。
Wowza 分享了一个示例index.html
它使用这些文件webrtc.js
github 链接来流式传输视频,我可以在其中提及我想播放的相机流,它正在 chrome 中工作。
我在角度代码中实现了相同的逻辑。我已经比较了Wowza代码和角度代码中的请求/响应,它是相同的,但视频无法播放。我只看到一个带有视频加载标志的黑屏。
我对webRTC很陌生,所以不确定我应该在哪里调试它。
这是我的 HTML 代码
<div id="container">
<div id="buttons">
<button id="startButton" (click)="start()">Start</button>
</div>
<video id="remoteVideo" autoplay playsinline controls muted style="height:480px;"></video>
</div>
这是组件.ts文件
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
const PEER_CONNECTION_CONFIG: RTCConfiguration = {
iceServers: []
};
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
private peerConnection: RTCPeerConnection;
private signalingConnection: WebSocket;
userData = { param1: "value1" };
streamInfo = { applicationName: "webrtc", streamName: "1.stream", sessionId: "[empty]" };
constructor(fb: FormBuilder) {
}
ngOnInit(): void {
}
start() {
this.setupSignalingServer();
}
private setupSignalingServer() {
const self = this;
this.signalingConnection = new WebSocket(`wss://******.com/webrtc-session.json`);
this.signalingConnection.binaryType = 'arraybuffer';
this.signalingConnection.onopen = function (res) {
console.log('connection open');
self.setupPeerServer();
self.signalingConnection.onmessage = self.getSignalMessageCallback();
self.signalingConnection.onerror = self.errorHandler;
};
this.signalingConnection.onclose = function (r) {
console.log('close');
};
}
private setupPeerServer() {
const self = this;
this.peerConnection = new RTCPeerConnection(PEER_CONNECTION_CONFIG);
this.peerConnection.onicecandidate = this.getIceCandidateCallback();
this.peerConnection.ontrack = this.gotRemoteTrack;
console.log("sendPlayGetOffer: " + JSON.stringify(self.streamInfo));
self.signalingConnection.send('{"direction":"play", "command":"getOffer", "streamInfo":' +
JSON.stringify(self.streamInfo) + ', "userData":' + JSON.stringify(self.userData) + '}');
}
gotRemoteTrack(event) {
console.log(event);
console.log('gotRemoteTrack: kind:' + event.track.kind + ' stream:' + event.streams[0]);
const remoteVideo = document.querySelector('video');
try {
remoteVideo.srcObject = event.streams[0];
} catch (error) {
console.log(error);
}
}
private getSignalMessageCallback(): (string) => void {
return (message) => {
console.log("wsConnection.onmessage: " + message.data);
const signal = JSON.parse(message.data);
const streamInfoResponse = signal['streamInfo'];
if (streamInfoResponse !== undefined) {
this.streamInfo.sessionId = streamInfoResponse.sessionId;
}
console.log('Received signal');
console.log(signal);
const msgCommand = signal['command'];
if (signal.sdp) {
console.log('sdp: ' + JSON.stringify(signal['sdp']));
this.peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp))
.then(() => {
if (signal.sdp) {
this.peerConnection.createAnswer()
.then(this.setDescription())
.catch(this.errorHandler);
}
})
.catch(this.errorHandler);
} else if (signal.ice) {
console.log('ice: ' + JSON.stringify(signal.ice));
this.peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(this.errorHandler);
}
if ('sendResponse'.localeCompare(msgCommand) == 0) {
if (this.signalingConnection != null) {
this.signalingConnection.close();
}
this.signalingConnection = null;
}
};
}
private getIceCandidateCallback(): (string) => void {
return (event) => {
console.log(`got ice candidate:`);
console.log(event);
if (event.candidate != null) {
}
};
}
private setDescription(): (string) => void {
return (description) => {
console.log('got description ');
console.log(description);
this.peerConnection.setLocalDescription(description)
.then(() => {
console.log('sendAnswer');
this.signalingConnection.send('{"direction":"play", "command":"sendResponse", "streamInfo":' +
JSON.stringify(this.streamInfo) + ', "sdp":' + JSON.stringify(description) + ',"userData":' + JSON.stringify(this.userData) + '}');
})
.catch(this.errorHandler);
};
}
private errorHandler(error) {
console.log(error);
}
}
我已经比较了Wowza代码和角度代码的控制台日志,对我来说似乎相同。
沃扎示例控制台日志
webrtc.js:218 startPlay: wsURL:wss://******.com/webrtc-session.json streamInfo:{"applicationName":"webrtc","streamName":"1.stream","sessionId":"[empty]"}
webrtc.js:68 websockerURL: wss://*******/webrtc-session.json
webrtc.js:73 wsConnection.onopen
webrtc.js:77 peerConnectionConfig
webrtc.js:89 wsURL: wss://*******.com/webrtc-session.json
webrtc.js:103 sendPlayGetOffer: {"applicationName":"webrtc","streamName":"1.stream","sessionId":"[empty]"}
webrtc.js:117 wsConnection.onmessage: {"status":200,"statusDescription":"OK","direction":"play","command":"getOffer","streamInfo":{"applicationName":"webrtc/_definst_","streamName":"1.stream","sessionId":"371518302"},"sdp":{"type":"offer","sdp":"v=0rno=WowzaStreamingEngine-next 1888398353 2 IN IP4 127.0.0.1rns=-rnt=0 0rna=fingerprint:sha-256 D9:D4:58:EF:E6:F7:B4:A2:93:C1:2A:FA:FB:FD:B1:EB:65:10:79:D5:E5:6A:BB:89:E5:6C:6E:F9:AB:56:54:67rna=group:BUNDLE videorna=ice-options:tricklerna=msid-semantic:WMS *rnm=video 9 RTP/SAVPF 97rna=rtpmap:97 H264/90000rna=fmtp:97 packetization-mode=1;profile-level-id=42C01E;sprop-parameter-sets=Z0LAHtkDxWhAAAADAEAAAAwDxYuS,aMuMsg==rna=cliprect:0,0,160,240rna=framesize:97 240-160rna=framerate:24.0rna=control:trackID=1rnc=IN IP4 0.0.0.0rna=sendrecvrna=ice-pwd:58e0f39dedf7e3d6096a6b90ebe72155rna=ice-ufrag:46613e36rna=mid:videorna=msid:{31104203-d432-4bca-a4ce-e478a708a162} {6ef45b35-493d-4075-8029-747aae04e340}rna=rtcp-fb:97 nackrna=rtcp-fb:97 nack plirna=rtcp-fb:97 ccm firrna=rtcp-muxrna=setup:actpassrna=ssrc:457240419 cname:{e3dff516-6bf1-475b-bca9-5f156fa39990}rn"}}
webrtc.js:155 sdp: {"type":"offer","sdp":"v=0rno=WowzaStreamingEngine-next 1888398353 2 IN IP4 127.0.0.1rns=-rnt=0 0rna=fingerprint:sha-256 D9:D4:58:EF:E6:F7:B4:A2:93:C1:2A:FA:FB:FD:B1:EB:65:10:79:D5:E5:6A:BB:89:E5:6C:6E:F9:AB:56:54:67rna=group:BUNDLE videorna=ice-options:tricklerna=msid-semantic:WMS *rnm=video 9 RTP/SAVPF 97rna=rtpmap:97 H264/90000rna=fmtp:97 packetization-mode=1;profile-level-id=42C01E;sprop-parameter-sets=Z0LAHtkDxWhAAAADAEAAAAwDxYuS,aMuMsg==rna=cliprect:0,0,160,240rna=framesize:97 240-160rna=framerate:24.0rna=control:trackID=1rnc=IN IP4 0.0.0.0rna=sendrecvrna=ice-pwd:58e0f39dedf7e3d6096a6b90ebe72155rna=ice-ufrag:46613e36rna=mid:videorna=msid:{31104203-d432-4bca-a4ce-e478a708a162} {6ef45b35-493d-4075-8029-747aae04e340}rna=rtcp-fb:97 nackrna=rtcp-fb:97 nack plirna=rtcp-fb:97 ccm firrna=rtcp-muxrna=setup:actpassrna=ssrc:457240419 cname:{e3dff516-6bf1-475b-bca9-5f156fa39990}rn"}
webrtc.js:309 RTCTrackEvent {isTrusted: true, receiver: RTCRtpReceiver, track: MediaStreamTrack, streams: Array(1), transceiver: RTCRtpTransceiver, …}
webrtc.js:311 gotRemoteTrack: kind:video stream:[object MediaStream]
webrtc.js:297 gotDescription
webrtc.js:300 sendAnswer
webrtc.js:287 got ice candidate
webrtc.js:288 RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …}
webrtc.js:117 wsConnection.onmessage: {"status":200,"statusDescription":"OK","direction":"play","command":"sendResponse","streamInfo":{"applicationName":"webrtc/_definst_","streamName":"1.stream","sessionId":"371518302"},"iceCandidates":[{"candidate":"candidate:0 1 TCP 50 172.30.6.139 1935 typ host generation 0","sdpMid":"","sdpMLineIndex":0}]}
webrtc.js:167 iceCandidates: {"candidate":"candidate:0 1 TCP 50 172.30.6.139 1935 typ host generation 0","sdpMid":"","sdpMLineIndex":0}
webrtc.js:189 wsConnection.onclose
webrtc.js:287 got ice candidate
webrtc.js:288 RTCPeerConnectionIceEvent {isTrusted: true, candidate: null, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …}
角度代码控制台日志
app.component.ts:34 connection open
app.component.ts:50 sendPlayGetOffer: {"applicationName":"webrtc","streamName":"1.stream","sessionId":"[empty]"}
app.component.ts:67 wsConnection.onmessage: {"status":200,"statusDescription":"OK","direction":"play","command":"getOffer","streamInfo":{"applicationName":"webrtc/_definst_","streamName":"1.stream","sessionId":"500786050"},"sdp":{"type":"offer","sdp":"v=0rno=WowzaStreamingEngine-next 1006993747 2 IN IP4 127.0.0.1rns=-rnt=0 0rna=fingerprint:sha-256 D9:D4:58:EF:E6:F7:B4:A2:93:C1:2A:FA:FB:FD:B1:EB:65:10:79:D5:E5:6A:BB:89:E5:6C:6E:F9:AB:56:54:67rna=group:BUNDLE videorna=ice-options:tricklerna=msid-semantic:WMS *rnm=video 9 RTP/SAVPF 97rna=rtpmap:97 H264/90000rna=fmtp:97 packetization-mode=1;profile-level-id=42C01E;sprop-parameter-sets=Z0LAHtkDxWhAAAADAEAAAAwDxYuS,aMuMsg==rna=cliprect:0,0,160,240rna=framesize:97 240-160rna=framerate:24.0rna=control:trackID=1rnc=IN IP4 0.0.0.0rna=sendrecvrna=ice-pwd:30ac7ef1eaa28bfd4e7d7c3f20b6e2b2rna=ice-ufrag:f6a6a6f7rna=mid:videorna=msid:{24f3d923-96ef-4286-b232-5ef77b793d19} {ed46e803-398b-4065-b2c9-f92d142bd9a9}rna=rtcp-fb:97 nackrna=rtcp-fb:97 nack plirna=rtcp-fb:97 ccm firrna=rtcp-muxrna=setup:actpassrna=ssrc:1674869182 cname:{9ceeb6e4-2745-4052-9c42-127092b3ff74}rn"}}
app.component.ts:74 Received signal
app.component.ts:75 {status: 200, statusDescription: "OK", direction: "play", command: "getOffer", streamInfo: {…}, …}
app.component.ts:79 sdp: {"type":"offer","sdp":"v=0rno=WowzaStreamingEngine-next 1006993747 2 IN IP4 127.0.0.1rns=-rnt=0 0rna=fingerprint:sha-256 D9:D4:58:EF:E6:F7:B4:A2:93:C1:2A:FA:FB:FD:B1:EB:65:10:79:D5:E5:6A:BB:89:E5:6C:6E:F9:AB:56:54:67rna=group:BUNDLE videorna=ice-options:tricklerna=msid-semantic:WMS *rnm=video 9 RTP/SAVPF 97rna=rtpmap:97 H264/90000rna=fmtp:97 packetization-mode=1;profile-level-id=42C01E;sprop-parameter-sets=Z0LAHtkDxWhAAAADAEAAAAwDxYuS,aMuMsg==rna=cliprect:0,0,160,240rna=framesize:97 240-160rna=framerate:24.0rna=control:trackID=1rnc=IN IP4 0.0.0.0rna=sendrecvrna=ice-pwd:30ac7ef1eaa28bfd4e7d7c3f20b6e2b2rna=ice-ufrag:f6a6a6f7rna=mid:videorna=msid:{24f3d923-96ef-4286-b232-5ef77b793d19} {ed46e803-398b-4065-b2c9-f92d142bd9a9}rna=rtcp-fb:97 nackrna=rtcp-fb:97 nack plirna=rtcp-fb:97 ccm firrna=rtcp-muxrna=setup:actpassrna=ssrc:1674869182 cname:{9ceeb6e4-2745-4052-9c42-127092b3ff74}rn"}
app.component.ts:56 RTCTrackEvent {isTrusted: true, receiver: RTCRtpReceiver, track: MediaStreamTrack, streams: Array(1), transceiver: RTCRtpTransceiver, …}
app.component.ts:57 gotRemoteTrack: kind:video stream:[object MediaStream]
app.component.ts:114 got description
app.component.ts:115 RTCSessionDescription {type: "answer", sdp: "v=0
↵o=- 554753582248755363 2 IN IP4 127.0.0.1
↵s=…=1;packetization-mode=1;profile-level-id=42e01e
↵"}
app.component.ts:119 sendAnswer
app.component.ts:104 got ice candidate:
app.component.ts:105 RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …}
app.component.ts:67 wsConnection.onmessage: {"status":200,"statusDescription":"OK","direction":"play","command":"sendResponse","streamInfo":{"applicationName":"webrtc/_definst_","streamName":"1.stream","sessionId":"500786050"},"iceCandidates":[{"candidate":"candidate:0 1 TCP 50 172.30.6.139 1935 typ host generation 0","sdpMid":"","sdpMLineIndex":0}]}
app.component.ts:74 Received signal
app.component.ts:75 {status: 200, statusDescription: "OK", direction: "play", command: "sendResponse", streamInfo: {…}, …}
app.component.ts:41 close
app.component.ts:104 got ice candidate:
app.component.ts:105 RTCPeerConnectionIceEvent {isTrusted: true, candidate: null, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …}
任何人都可以提出一些关于如何调试这一点的要点吗?
从这里了解了 IceCandidate 的概念后,我已经解决了我的问题。 我没有在 sdp 报价中正确检查 IceCandidate 。 我变了
else if (signal.ice) {
console.log('ice: ' + JSON.stringify(signal.ice));
this.peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(this.errorHandler);
}
else if (signal.iceCandidates) {
console.log('ice: ' + JSON.stringify(signal.iceCandidates));
this.peerConnection.addIceCandidate(new RTCIceCandidate(signal.iceCandidates[0])).catch(this.errorHandler);
}
现在视频在Chrome浏览器中完美播放。