2

我正在尝试使用 Angular 在 chrome 中播放 webRTC 视频。我有 IP 摄像机,我正在使用 Wowza 流引擎将他们的 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>

这是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 代码和 Angular 代码的控制台日志,它看起来和我一模一样。

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 代码控制台日志

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, …}

任何人都可以提出一些关于如何调试的建议吗?

4

1 回答 1

0

这里了解 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 浏览器中完美播放。

于 2020-06-17T08:28:53.163 回答