首页 > 解决方案 > NativeScript WebRTC / TNSRTC - 无法获取 remoteStream 视频

问题描述

试图让 WebRtc 只为一侧工作。我有媒体流服务器,上面安装了烤箱媒体引擎。信号已打开,并通过 Oven Media Player 进行测试,一切正常。但是当尝试通过 NativeScript 或任何 TypeScript 免费代码访问该内容时,坚持处理“答案”,结果缺少 RemoteVideoStream !

onTrack(event) - 无论我做什么都没有得到 RemoteVideo 并尝试......

PS - WebRtc 的第一步,也许没有弄清楚该解决方案的所有逻辑,所有示例都基于两种方式的连接/呼叫和应答,我想要做的只是获取该流并在没有任何本地流的情况下播放。

试图实现这个图 - 'https://airensoft.gitbook.io/ovenmediaengine/streaming/webrtc-publishing#signalling-protocol' - 但不成功

流服务器 - 'ws://3.123.0.22:3333/app/test_o' - 已打开并发出信号

烤箱媒体播放器 - 'http://demo.ovenplayer.com'

import {TNSRTCTrackEvent,TNSRTCPeerConnection,TNSRTCSessionDescription,TNSRTCIceCandidate} from'nativescript-webrtc-plugin';

import { LoggerService } from '../../services';

require('nativescript-websockets');

export class TestTnsRtc {
    public webSocket: any;
    public connection: any = null;

    constructor(private readonly _logger: LoggerService) {
        this.connection = new TNSRTCPeerConnection();

        this.connection.onIceCandidate((candidate: TNSRTCIceCandidate) => {
            this._logger.log('onIceCandidate -> candidate');

            const createdCandidate: any = {
                candidate: candidate.candidate,
                sdp: candidate.sdp,
                sdpMid: candidate.sdpMid,
                sdpMLineIndex: candidate.sdpMLineIndex,
                serverUrl: candidate.serverUrl
            };

            this._addIceCandidate([createdCandidate]);
        });

        this.connection.onTrack((track: TNSRTCTrackEvent) => {
            if (track.streams) {
                this._logger.log('onTrack -> Executed', track.streams[0]);
                // this.remoteView.srcObject = track.streams[0];
            }
        });

        this._createSocketConnection();
    }

    private _createSocketConnection(): void {
        const mySocket: any = new WebSocket('ws://3.123.0.22:3333/app/test_o');

        mySocket.addEventListener('open', (evt: any) => {
            this._logger.log('WebSocket Connected !');
            this.webSocket = evt.target;
            this._emitSocket({ command: 'request_offer' });
        });

            mySocket.addEventListener('message', (evt: any) => {
            this._logger.log('WebSocket got MESSAGE with type: ', JSON.parse(evt.data).command);

            if (JSON.parse(evt.data).command === 'offer') {
                const offer: any = {
                    id: JSON.parse(evt.data).id,
                    sdp: JSON.parse(evt.data).sdp,
                    candidates: JSON.parse(evt.data).candidates
                };

                this._answerOnOffer(offer);
            }
        });

        mySocket.addEventListener('close', (evt: any) => {
            this._logger.log(`WebSocket CLOSED with code: ${evt.code} && reason: ${evt.reason}`);
        });

        mySocket.addEventListener('error', (evt: any) => {
            this._logger.log(`WebSocket got ERROR: ${evt.error}`);
        });
    }

    private _answerOnOffer(offer: any): void {
        if (!this.connection || !offer) return;
        this.connection
            .setRemoteDescription(new TNSRTCSessionDescription(offer.sdp.type, offer.sdp.sdp))
            .then(() => {
                this._addIceCandidate(offer.candidates);
                this.connection
                    .createAnswer({})
                    .then((sdp: TNSRTCSessionDescription) => {
                        this._setLocalDescription(sdp, offer.id);
                    })
                    .catch((e: any) => {
                        this._logger.log(`createAnswer : Error -> ${e}`);
                    });
            })
            .catch((error: any) => {
                this._logger.log(`setRemoteDescription : Error -> ${error}`);
            });
    }

    private _setLocalDescription(sdp: TNSRTCSessionDescription, offerId: any): void {
        if (this.connection == null) return;
        this.connection
            .setLocalDescription(new TNSRTCSessionDescription(sdp.type, sdp.sdp))
            .then(() => {
                this._logger.log('setLocalDescription Done  with offer id: ', offerId);
                this._emitSocket({
                    command: 'answer',
                    id: offerId,
                    sdp: { type: 'answer', sdp: sdp.sdp },
                    candidates: []
                });
            })
            .catch((error: any) => {
                this._logger.log(`setLocalDescription : Error -> ${error}`);
            });
    }

    private _addIceCandidate(iceCandidates: TNSRTCIceCandidate[]): void {
        for (let iceCandidate of iceCandidates) {
            this.connection.addIceCandidate(iceCandidate);
        }
    }

    private _emitSocket(command: any): void {
        if (!this.webSocket) {
            return;
        }

        this.webSocket.send(JSON.stringify(command));
    }
}

更新后的版本

我试图实现方法[A] - 并削减大量代码以尽可能简化。

mySocket.addEventListener('open', (evt: any) => {
    evt.target.send(JSON.stringify({ command: 'request_offer' }));
    this._webSocket = evt.target;
});

mySocket.addEventListener('message', (evt: any) => {
    // if type is answer, calling handler
    this._handleOffer(JSON.parse(evt.data));
});

private _handleOffer(offerObject: any): void {
    const config: TNSRTCConfiguration = new TNSRTCConfiguration({
        iceServers: [new TNSRTCIceServer(['stun:stun.l.google.com:19302'])]
    });

    const peerConnection: TNSRTCPeerConnection = new TNSRTCPeerConnection(config);
    const offer: any = {
        command: offerObject.command,
        id: offerObject.id,
        code: offerObject.code,
        peerId: offerObject.peer_id,
        sdp: offerObject.sdp,
        candidates: offerObject.candidates
    };

    for (const ice of offer.candidates) {
        ice.candidate = this._updateIceIp(ice);
        peerConnection.addIceCandidate(ice);
    }

    const remoteDescription: TNSRTCSessionDescription = new TNSRTCSessionDescription(offer.sdp.type, offer.sdp.sdp);
    peerConnection
        .setRemoteDescription(remoteDescription)
        .then(() => {
            peerConnection
            .createAnswer({})
            .then((sdp: TNSRTCSessionDescription) => {
                const localDescription: TNSRTCSessionDescription = new TNSRTCSessionDescription(sdp.type, sdp.sdp);
                peerConnection
                .setLocalDescription(localDescription)
                .then(() => {
                    const generatedAnswer: any = JSON.stringify({
                        id: offer.id,
                        peer_id: offer.peerId,
                        command: 'answer',
                        sdp: { type: sdp.type, sdp: sdp.sdp }
                    });
                    this._webSocket.send(generatedAnswer);
                })
                .catch((err: any) => {
                    this._logger.log(`setLocalDescription -> catch: ${err}`);
                });
            })
            .catch((err: any) => {
                this._logger.log(`createAnswer -> catch: ${err}`);
            });
        })
        .catch((err: any) => {
            this._logger.log(`setRemoteDescription -> catch: ${err}`);
        });

    peerConnection.onIceCandidate((candidate: TNSRTCIceCandidate) => {
        this._logger.log('EVENT -> onIceCandidate');
        if (candidate && candidate.candidate) {
            const iceCandidate: any = {
                candidate: candidate.candidate,
                sdpMid: candidate.sdpMid,
                sdpMLineIndex: candidate.sdpMLineIndex
            };

            const sendData: any = JSON.stringify({
                id: offer.id,
                peer_id: offer.peerId,
                command: 'candidate',
                candidates: [iceCandidate]
            });

            this._webSocket.send(sendData);
        }
    });

    peerConnection.onSignalingStateChange(() => {
        this._logger.log('EVENT -> onSignalingStateChange', peerConnection.connectionState);
    });

    peerConnection.onIceGatheringStateChange(() => {
        this._logger.log('EVENT -> onIceGatheringStateChange', peerConnection.connectionState);
    });

    peerConnection.onConnectionStateChange(() => {
        this._logger.log('EVENT -> onConnectionStateChange', peerConnection.connectionState);
    });

    peerConnection.onTrack((track: TNSRTCTrackEvent) => {
        this._logger.log('EVENT -> onTrack');
    });
}

private _updateIceIp(candidate: TNSRTCIceCandidate): string {
    if (candidate.candidate) {
        const ipRegex: RegExp = new RegExp(
            /\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/,
            'gi'
        );
        const ipOfCandidate: any = candidate.candidate.match(ipRegex)[0];

        return candidate.candidate.replace(ipOfCandidate, '3.123.0.22');
    }
}
  1. 这是来自 OME 的报价回复 - https://prnt.sc/tvccti
  2. .setRemoteDescription - 成功
  3. .createAnswer - 成功
  4. .setLocalDescription - 成功
  5. 这是来自 .onIceCandidate 事件的候选人,我将其发送到 OME 服务器 - https://prnt.sc/tvcg9d
  6. 这是我的日志输出 - https://prnt.sc/tvch52

我的 peerConnection.connectionState 始终是“新的”,并且 .onTrack() 事件在第 4 点和第 5 点之前调用,但主体为空。

标签: typescriptwebrtcnativescriptnativescript-angularnativescript-plugin

解决方案


感谢您使用我们的开源产品。

[A] 在 NAT 之后运行 OvenMediaEngine 时可能会出现流式传输问题。

在 NAT 之后运行的 OME 使用私有 IP 地址创建“ICE 候选者”,因为 ICE 候选者只提供玩家无法连接到 OME 的私有 IP 地址。

例如,候选人来自 OME,在 NAT 后面运行,看起来像这样。“172.17.0.2”是一个私有IP地址:

"candidate:0 1 UDP 50 172.17.0.2 10001 typ host"

为避免这种情况,OvenPlayer 从 OME 复制候选者并引用 WebRTC 信号地址以将候选者的私有 IP 地址替换为公共 IP 地址。然后 OvenPlayer 也将重复的候选者添加到对等连接中。

如果信令地址是“'ws://3.123.0.22:3333/app/test_o”,那么重复的候选人看起来像这样:

"candidate:0 1 UDP 50 3.123.0.22 10001 typ host"

因此,这些功能需要在将来自 OME 的 ICE 候选添加到本地对等连接时实现。

Add Ice 候选者来自 OME,这里(点击)是 OvenPlayer 的做法。然后,请参考这里(点击)克隆和替换 IP 地址。

顺便说一句,看看你的代码,我认为你不需要,this._addIceCandidate([createdCandidate]);因为你不需要将玩家的候选人设置为玩家的对等连接。

this.connection.onIceCandidate((candidate: TNSRTCIceCandidate) => {
    this._logger.log('onIceCandidate -> candidate');

    const createdCandidate: any = {
        candidate: candidate.candidate,
        sdp: candidate.sdp,
        sdpMid: candidate.sdpMid,
        sdpMLineIndex: candidate.sdpMLineIndex,
        serverUrl: candidate.serverUrl
    };

    this._addIceCandidate([createdCandidate]);
});

[B] 在 OME 的 Server.xml 中,有一种方法可以放置经过认证的 IP 而不是 *. OME 根据 * 设置自动查找并放置 IP 地址。但是,问题是使用云时输入了私有IP。

现有设置:

<IceCandidates>
    <IceCandidate>*:10000-10005/udp</IceCandidate>
</IceCandidates>

例子:

<IceCandidates>
    <IceCandidate><Public IP>:10000-10005/udp</IceCandidate>
</IceCandidates>

但是,即使不更改上述设置,安装在云端的 OME 和 OvenPlayer Demo 也能正常工作的原因是它按照 [A] 中的说明工作。

这是 OvenPlayer 的一个特殊功能,因此您可以按照 [A] 中的说明执行它,或者在没有 [A] 的情况下按照 [B] 中的说明执行。

谢谢你。


推荐阅读