我正在尝试使用角度播放Chrome中的webRTC视频。我拥有IP摄像机,并且正在使用Wowza流引擎将其RTSP流转换为webRTC。
Wowza共享了一个示例index.html
,该示例使用webrtc.js
github link for these files进行视频流传输,我可以在其中提及要播放的摄像机流,并且它在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>
这是component.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代码和角度代码的控制台日志,似乎与我相同。
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=0\r\no=WowzaStreamingEngine-next 1888398353 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=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:67\r\na=group:BUNDLE video\r\na=ice-options:trickle\r\na=msid-semantic:WMS *\r\nm=video 9 RTP/SAVPF 97\r\na=rtpmap:97 H264/90000\r\na=fmtp:97 packetization-mode=1;profile-level-id=42C01E;sprop-parameter-sets=Z0LAHtkDxWhAAAADAEAAAAwDxYuS,aMuMsg==\r\na=cliprect:0,0,160,240\r\na=framesize:97 240-160\r\na=framerate:24.0\r\na=control:trackID=1\r\nc=IN IP4 0.0.0.0\r\na=sendrecv\r\na=ice-pwd:58e0f39dedf7e3d6096a6b90ebe72155\r\na=ice-ufrag:46613e36\r\na=mid:video\r\na=msid:{31104203-d432-4bca-a4ce-e478a708a162} {6ef45b35-493d-4075-8029-747aae04e340}\r\na=rtcp-fb:97 nack\r\na=rtcp-fb:97 nack pli\r\na=rtcp-fb:97 ccm fir\r\na=rtcp-mux\r\na=setup:actpass\r\na=ssrc:457240419 cname:{e3dff516-6bf1-475b-bca9-5f156fa39990}\r\n"}}
webrtc.js:155 sdp: {"type":"offer","sdp":"v=0\r\no=WowzaStreamingEngine-next 1888398353 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=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:67\r\na=group:BUNDLE video\r\na=ice-options:trickle\r\na=msid-semantic:WMS *\r\nm=video 9 RTP/SAVPF 97\r\na=rtpmap:97 H264/90000\r\na=fmtp:97 packetization-mode=1;profile-level-id=42C01E;sprop-parameter-sets=Z0LAHtkDxWhAAAADAEAAAAwDxYuS,aMuMsg==\r\na=cliprect:0,0,160,240\r\na=framesize:97 240-160\r\na=framerate:24.0\r\na=control:trackID=1\r\nc=IN IP4 0.0.0.0\r\na=sendrecv\r\na=ice-pwd:58e0f39dedf7e3d6096a6b90ebe72155\r\na=ice-ufrag:46613e36\r\na=mid:video\r\na=msid:{31104203-d432-4bca-a4ce-e478a708a162} {6ef45b35-493d-4075-8029-747aae04e340}\r\na=rtcp-fb:97 nack\r\na=rtcp-fb:97 nack pli\r\na=rtcp-fb:97 ccm fir\r\na=rtcp-mux\r\na=setup:actpass\r\na=ssrc:457240419 cname:{e3dff516-6bf1-475b-bca9-5f156fa39990}\r\n"}
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, …}
Angular Code Console日志
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=0\r\no=WowzaStreamingEngine-next 1006993747 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=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:67\r\na=group:BUNDLE video\r\na=ice-options:trickle\r\na=msid-semantic:WMS *\r\nm=video 9 RTP/SAVPF 97\r\na=rtpmap:97 H264/90000\r\na=fmtp:97 packetization-mode=1;profile-level-id=42C01E;sprop-parameter-sets=Z0LAHtkDxWhAAAADAEAAAAwDxYuS,aMuMsg==\r\na=cliprect:0,0,160,240\r\na=framesize:97 240-160\r\na=framerate:24.0\r\na=control:trackID=1\r\nc=IN IP4 0.0.0.0\r\na=sendrecv\r\na=ice-pwd:30ac7ef1eaa28bfd4e7d7c3f20b6e2b2\r\na=ice-ufrag:f6a6a6f7\r\na=mid:video\r\na=msid:{24f3d923-96ef-4286-b232-5ef77b793d19} {ed46e803-398b-4065-b2c9-f92d142bd9a9}\r\na=rtcp-fb:97 nack\r\na=rtcp-fb:97 nack pli\r\na=rtcp-fb:97 ccm fir\r\na=rtcp-mux\r\na=setup:actpass\r\na=ssrc:1674869182 cname:{9ceeb6e4-2745-4052-9c42-127092b3ff74}\r\n"}}
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=0\r\no=WowzaStreamingEngine-next 1006993747 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=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:67\r\na=group:BUNDLE video\r\na=ice-options:trickle\r\na=msid-semantic:WMS *\r\nm=video 9 RTP/SAVPF 97\r\na=rtpmap:97 H264/90000\r\na=fmtp:97 packetization-mode=1;profile-level-id=42C01E;sprop-parameter-sets=Z0LAHtkDxWhAAAADAEAAAAwDxYuS,aMuMsg==\r\na=cliprect:0,0,160,240\r\na=framesize:97 240-160\r\na=framerate:24.0\r\na=control:trackID=1\r\nc=IN IP4 0.0.0.0\r\na=sendrecv\r\na=ice-pwd:30ac7ef1eaa28bfd4e7d7c3f20b6e2b2\r\na=ice-ufrag:f6a6a6f7\r\na=mid:video\r\na=msid:{24f3d923-96ef-4286-b232-5ef77b793d19} {ed46e803-398b-4065-b2c9-f92d142bd9a9}\r\na=rtcp-fb:97 nack\r\na=rtcp-fb:97 nack pli\r\na=rtcp-fb:97 ccm fir\r\na=rtcp-mux\r\na=setup:actpass\r\na=ssrc:1674869182 cname:{9ceeb6e4-2745-4052-9c42-127092b3ff74}\r\n"}
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, …}
任何人都可以提出一些调试建议吗?
了解了here中的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浏览器中可以完美播放。