Я пытаюсь воспроизводить видео webRTC в Chrome с помощью angular. У меня есть IP-камеры, и я конвертирую их потоки RTSP в webRTC с помощью Wowza Streaming Engine.

Wowza поделился образцом index.html, в котором используется webrtc.js ссылка на эти файлы на github для потоковой передачи видео, где я могу упомянуть поток камеры, который я хочу воспроизвести, и он работает в хроме.

Я реализовал ту же логику в коде angular. Я сравнил запрос / ответ как в коде Wowza, так и в коде angular, и он идентичен, но видео не воспроизводится. Я просто вижу черный экран со знаком загрузки видео.

Я очень новичок в 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, …}

Может ли кто-нибудь предложить некоторые моменты, как это отладить?

2
Aak 15 Июн 2020 в 08:49

1 ответ

Лучший ответ

После понимания концепций IceCandidate из здесь, я решил свою проблему. Я неправильно проверял IceCandidate в предложении sdp. Я изменился

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.

0
Aak 17 Июн 2020 в 08:28