typescript - 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');
}
}
- 这是来自 OME 的报价回复 - https://prnt.sc/tvccti
- .setRemoteDescription - 成功
- .createAnswer - 成功
- .setLocalDescription - 成功
- 这是来自 .onIceCandidate 事件的候选人,我将其发送到 OME 服务器 - https://prnt.sc/tvcg9d
- 这是我的日志输出 - https://prnt.sc/tvch52
我的 peerConnection.connectionState 始终是“新的”,并且 .onTrack() 事件在第 4 点和第 5 点之前调用,但主体为空。
解决方案
感谢您使用我们的开源产品。
[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] 中的说明执行。
谢谢你。
推荐阅读
- flutter - Dart/Flutter 中的全局变量
- spring - 我如何使用 @Retryable(label="...") 进行日志记录或监控?
- java - @RequestBody 使用多个参数
- javascript - 无法使用 Node.js 连接到 mssql
- javascript - 获取PHP中多个选择框的值并将它们添加到数组中
- c - 未定义对 getdelim() 错误的引用 (Windows) (C 语言)
- ag-grid-angular - 我们可以在 Ag-grid 角度动态更改“rowSelection”吗?
- r - ggplot - 集中 facet_grid 标题并且只出现一次
- postgresql - 选择一种方式不返回
- javascript - 丢失转义字符,将 json 中的路径从 Xamarin Forms c# 传递到 javascript