我正在使用 Janus VideoRoom 插件,我想订阅/取消订阅 2 个或更多不同的发布者。
前 4 或 5 个订阅(开始订阅 -> 让视频可见)需要 2-3 秒。
我称之为正常。
但下一次订阅需要更多时间。
我称之为不正常。
请告诉我我的错误在哪里?
这是我的 JS(打字稿)
declare var Janus: any;
export class SubscriberClientSimple {
private login:string;
private opaqueId:string;
private videoPlayer:any;
private streamName:string;
private serverUri:string;
private serverURL:string;
private api:any;
private plugin:any;
private remoteStream:any;
public static STARTING:string = "STARTING";
public static STARTED:string = "STARTED";
public static STOPPING:string = "STOPPING";
public static STOPPED:string = "STOPPED";
private state:string;
constructor(login:string){
this.login = login;
this.opaqueId = "subscriber-" + this.login + "-" + Janus.randomString(12);
}
public destroy():void{
}
public setPlayerElement(element:any):void{
this.videoPlayer = element;
}
public hasVideoPlayerElement():boolean{
return this.videoPlayer!=null;
}
public isAudioEnabled():boolean{
return false;
}
public isVideoEnabled():boolean{
return true;
}
public getStreamName():string{
return this.streamName;
}
public subscribe(streamName: string, serverUri: string, audio:boolean, video:boolean):void{
this.log("Start requested");
this.state = SubscriberClientSimple.STARTING;
this.streamName = streamName;
this.serverUri = serverUri;
this.serverURL = MEDIA_SERVER_URL + MEDIA_SERVER_PATH + "/janus-debug";
EventBus.dispatchEvent(StreamingEvent.REMOTE_VIDEO_RECEIVE_INTENT, null);
//this.log("server url : "+this.serverURL);
Janus.init({
userId: parseInt(this.login),
backendUrl: this.serverURL,
environment: environment,
debug: false, // fixme это вынужденно отключено, чтобы можно было использовать Janus.noop как коллбэк для логирования
callback: () => this.initCallback()
});
}
public unsubscribe():void{
this.log("unsubscribe state="+this.state);
if(this.state == SubscriberClientSimple.STARTED){
this.state = SubscriberClientSimple.STOPPING;
EventBus.dispatchEvent(StreamingEvent.SUBSCRIBER_BUSY, null);
if(this.plugin){
this.log("sending leave command...");
this.plugin.send({message: {request: "leave"}});
}
}
}
public disableAudio():void{
this.log("Disable AUDIO");
}
public disableVideo():void{
this.log("Disable VIDEO");
}
public enableAudio():void{
}
public enableVideo():void{
}
public reset():void{
}
public setSocketService(socketService:any):void{
}
public setModalService(modalService):void{
}
public isMyCurrentPublisher(id:string):boolean{
return this.streamName == id;
}
public getSubscribeState():any{
var data:any = {userId:this.login, audio: {state:""}, video:{state:"OFF"}};
data.audio.state = "OFF";
if(this.state == SubscriberClientSimple.STARTED){
data.video.state = "ON";
}
return data;
}
public isSubscribing():boolean{
return this.state == SubscriberClientSimple.STARTED;
}
private initCallback():void{
this.log("initCallback");
if (!Janus.isWebrtcSupported()) {
this.onWebRTCNotSupported();
}
this.createSession();
}
private createSession():void {
// Create session
this.log("createSession");
this.api = new Janus({
server: this.serverUri,
iceServers: ICE_SERVERS,
success: () => this.onStreamingServerCreateSuccess(),
error: (err) => this.onStreamingServerCreateError(err),
destroyed: () => this.onStreamingServerDestroyed()
});
}
private onStreamingServerCreateSuccess():void{
this.log("onStreamingServerCreateSuccess");
this.attachPlugin();
}
private attachPlugin():void{
this.log("attachPlugin");
let remoteFeed = null;
this.api.attach(
{
plugin: "janus.plugin.videoroom",
opaqueId: this.opaqueId,
success: (pluginHandle) => {
this.log("plugin attached");
remoteFeed = pluginHandle;
remoteFeed.simulcastStarted = false;
// We wait for the plugin to send us an offer
remoteFeed.send({
"message": {
"request": "listparticipants",
"room": +this.streamName
},
success: (res) => {
this.log("Total participants="+res.participants.length);
var canJoin:boolean = res.participants && res.participants.length > 0;
this.log("canJoin="+canJoin);
if (canJoin) {
var publisherParticipant:any = res.participants[0];
var publisherId:string = publisherParticipant.id;
const subscriptionData:any = {
"request": "join",
"room": +this.streamName,
"ptype": "subscriber",
"feed": publisherId,
"private_id": parseInt(this.login)
};
this.log("subscriptionData="+JSON.stringify(subscriptionData));
remoteFeed.send({"message": subscriptionData});
}
else {
this.log("Requested stream is not available anymore.");
}
}
});
},
error: (error) => {
const message = "Error attaching subscribe plugin. " + error.toString();
this.log(message);
},
onmessage: (msg, jsep) => {
this.onPluginMessage(msg, jsep, remoteFeed);
},
webrtcState: (on, reason) => {
if (reason === "Close PC") {
var message:string = "Closed PeerConnection";
this.log(message);
}
},
iceState: (state) => {
this.onICEState(state);
},
onlocalstream: function (stream) {
// The subscriber stream is recvonly, we don't expect anything here
},
onremotestream: (stream) => {
this.onSubscriberGotRemoteStream(stream);
},
oncleanup: () => {
this.onCleanUp();
}
});
}
private onPluginMessage(msg, jsep, remoteFeed):void{
this.log("onPluginMessage");
this.log("msg:"+JSON.stringify(msg));
this.log("jsep:"+JSON.stringify(jsep));
this.log("remoteFeed:"+remoteFeed);
const event = msg["videoroom"];
if (msg["error"]){
if (msg["error"] !== "No such room") {
this.log("Janus ERROR:"+msg["error"]);
}
}
else if (event) {
if (event === "attached") {
// Subscriber created and attached
this.log("Subscriber created and attached");
if (this.plugin === undefined || this.plugin === null) {
this.plugin = remoteFeed;
}
remoteFeed.rfid = msg["id"];
remoteFeed.rfdisplay = msg["display"];
}
else if(event === "event"){
var leftRoomResult:any = msg["left"];
if(leftRoomResult){
if(leftRoomResult == "ok"){
var roomId:string = msg["room"].toString();
this.onLeftRoom(roomId);
}
}
}
else if (event === "slow_link") {
}
}
if (jsep) {
// Answer and attach
this.log("create answer");
remoteFeed.createAnswer(
{
jsep: jsep,
media: {audioSend: false, videoSend: false}, // We want recvonly audio/video
success: (_jsep) => {
this.log("Got SDP!", _jsep);
const body = {"request": "start", "room": this.streamName};
remoteFeed.send({"message": body, "jsep": _jsep});
},
error: (err) => {
this.log("WebRTC error:", err);
}
});
}
}
private onSubscriberGotRemoteStream(stream:any):void{
this.log("onSubscriberGotRemoteStream this.state="+this.state);
if(this.state == SubscriberClientSimple.STARTING){
const videoTracks = stream.getVideoTracks();
this.remoteStream = stream;
this.attachRemoteStream();
this.startRemoteVideo();
this.state = SubscriberClientSimple.STARTED;
}
}
private onLeftRoom(roomId:string):void{
if(roomId == this.streamName){
this.log("Left room");
this.plugin.ondetached();
this.plugin.detach();
this.plugin = null;
this.log("VIDEO stopped");
this.state = SubscriberClientSimple.STOPPED;
EventBus.dispatchEvent(StreamingEvent.REMOTE_VIDEO_DISABLED, null);
}
}
private onCleanUp():void{
this.log("onCleanUp");
}
private onICEState(state:string):void{
this.log("ICE state of this WebRTC PeerConnection changed to " + state);
}
private attachRemoteStream():void {
Janus.attachMediaStream(this.videoPlayer, this.remoteStream );
this.videoPlayer.muted = "muted";
}
private startRemoteVideo():void {
this.videoPlayer.play();
this.log("VIDEO started");
EventBus.dispatchEvent(StreamingEvent.REMOTE_VIDEO_RECEIVING_STARTED, null);
}
private onStreamingServerCreateError(err:any):void{
this.log("Streaming Server Error:",err);
}
private onStreamingServerDestroyed():void{
this.log("Janus destroyed");
}
private onWebRTCNotSupported():void{
this.log("WebRTC Not Supported");
}
protected log(value:any, ...rest:any[]):void{
EventBus.dispatchEvent(AppEvent.SEND_LOG, {className:this.getClassName(), value:value, rest:rest});
}
protected getClassName():string{
return "SubscriberClientSimple";
}
}
这是我在 4 或 5 次正常订阅启动后的日志//观看
发布者的视频
// 停止上一个订阅
04:01:50 取消订阅状态=
已开始
04:01:50 发送离开命令...
04:01:50 取消订阅状态=STOPPING
04:01:50 unsubscribe state=STOPPING
04:01:50 onPluginMessage
04:01:50 msg:{"videoroom":"event","room":681365,"left":"ok"}
04:01:50 jsep:undefined
04:01:50 remoteFeed:[object Object]
04:01:50 离开房间
04:01:50 onCleanUp
04:01:50 视频停止
04:01:50 Closed PeerConnection
04:01:50 onCleanUp
// new订阅正常开始
04:01:
53 开始请求
04:01:53 initCallback
04:01:53 createSession
04:01:54 onStreamingServerCreateSuccess
04:01:54 attachPlugin 04:01:54
附加插件
04:01:54 参与者总数=1
04:01:54 canJoin=true
04: 01:54 subscriptionData={"request":"join","room":724240,"ptype":"subscriber","feed":3606687285964170,"private_id":680291}
04:01:54 onPluginMessage
04:01: 54 消息:{"videoroom":"attached","room":724240,"id":3606687285964170,"display":"724240"}
04:01:54 jsep:{"type":"offer","sdp":"v=0\r\no=- 1615780141548001 1 IN IP4 185.12.12.24\r\ns=VideoRoom 724240\r\nt=0 0\r\na=group:BUNDLE audio video\r\na=msid-semantic: WMS janus\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\nc=IN IP4 185.12.12.24\r\ na=sendonly\r\na=mid:audio\r\na=rtcp-mux\r\na=ice-ufrag:NVx5\r\na=ice-pwd:Tz3hjJNIxACUULUbBBREaL\r\na=ice-options:trickle\ r\na=指纹:sha-256 53:C2:82:E2:61:73:BC:B5:0D:66:E8:2E:11:90:97:66:92:52:62:FE:2C :6B:45:95:A1:EF:08:D6:05:C6:8E:A1\r\na=setup:actpass\r\na=rtpmap:111 opus/48000/2\r\na=extmap: 1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=rtcp-fb:111 transport-cc \r\na=msid:janus janusa0\r\na=ssrc:3479227141 cname:janus\r\na=ssrc:3479227141 msid:janus janusa0\r\na=ssrc:3479227141 mslabel:janus\r\na=ssrc:3479227141 标签:janusa0\r\nm=video 9 UDP/TLS/RTP/SAVPF 96 97\r\nc=IN IP4 185.12.12.24\r\na=sendonly\r\na=mid:video\r\na=rtcp -mux\r\na=ice-ufrag:NVx5\r\na=ice-pwd:Tz3hjJNIxACUULUbBBREaL\r\na=ice-options:trikle\r\na=指纹:sha-256 53:C2:82:E2: 61:73:BC:B5:0D:66:E8:2E:11:90:97:66:92:52:62:FE:2C:6B:45:95:A1:EF:08:D6:05: C6:8E:A1\r\na=setup:actpass\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\ na=rtcp-fb:96 nack pli\r\na=rtcp-fb:96 goog-remb\r\na=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap: 3A1\r\na=setup:actpass\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp- fb:96 nack pli\r\na=rtcp-fb:96 goog-remb\r\na=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:3A1\r\na=setup:actpass\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp- fb:96 nack pli\r\na=rtcp-fb:96 goog-remb\r\na=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:3http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext /playout-delay\r\na=extmap:13 urn:3gpp:video-orientation\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\na=ssrc-group: FID 462666388 1246572010\r\na=msid:janus janusv0\r\na=ssrc:462666388 cname:janus\r\na=ssrc:462666388 msid:janus janusv0\r\na=ssrc:462666388 mslabel:janus\r\na =ssrc:462666388 标签:janusv0\r\na=ssrc:1246572010 cname:janus\r\na=ssrc:1246572010 msid:janus janusv0\r\na=ssrc:1246572010 mslabel:janus\r\na=ssrc:1246572010 标签:janusv0\r\n"}
04:01:54 remoteFeed:[object Object]
04:01:54 创建并附加订阅者
04:01:54 创建答案
04:01:54 onSubscriberGotRemoteStream this.state=STARTING
04:01:54 视频开始
04:01:54 onSubscriberGotRemoteStream this.state=
STARTED 04:01:54 onSubscriberGotRemoteStream this.state=
STARTED 04:01:54 获得 SDP![RTCSessionDescription]
04:01:54 此 WebRTC PeerConnection 的 ICE 状态更改为检查
04:01:54 此 WebRTC PeerConnection 的 ICE 状态更改为已连接
04:01:55 onPluginMessage
04:01:55 msg:{"videoroom":" event","room":724240,"started":"ok"}
04:01:55 jsep:undefined
04:01:55 remoteFeed:[object Object]
//正常开始订阅耗时 2 秒
// 观看发布者的视频
/ / 停止上一个订阅
04:09:55 取消订阅状态=STARTED
04:09:
04:09:55 取消订阅状态=STOPPING
04:09:55 取消订阅状态=STOPPING
04:09:55 onPluginMessage
04:09:55 msg:{"videoroom":"event","room":681365,"left": “ok”}
04:09:55 jsep:undefined
04:09:55 remoteFeed:[object Object]
04:09:55 离开房间
04:09:55 onCleanUp
04:09:55 视频停止
04:09:56 关闭 PeerConnection
04:09:56 onCleanUp
// 新订阅UNNORMAL开始
04:09:58 开始请求
04:09:58 initCallback
04:09:58 createSession
04:09:58 onStreamingServerCreateSuccess
04:09:58 attachPlugin
//巨大的暂停!!!
04:10:17 附加插件
//巨大的停顿!!!
04:10:22 参与者总数=1
04:10:22 canJoin=true
04:10:22 subscriptionData={"request":"join","room":724240,"ptype":"subscriber","feed" :3606687285964170,"private_id":680291}
04:10:28 onPluginMessage
04:10:28 msg:{"videoroom":"attached","room":724240,"id":3606687285964170,"display":"724240" }
04:10:28 jsep:{"type":"offer","sdp":"v=0\r\no=- 1615780141548001 1 IN IP4 185.12.12.24\r\ns=VideoRoom 724240\r\nt=0 0\r\na=group:BUNDLE audio video\r\na=msid-semantic: WMS janus\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\nc=IN IP4 185.12.12.24\r\ na=sendonly\r\na=mid:audio\r\na=rtcp-mux\r\na=ice-ufrag:WETD\r\na=ice-pwd:ch2L786bEYlY6OIi+AJ4no\r\na=ice-options:涓涓细流\r\na=指纹:sha-256 53:C2:82:E2:61:73:BC:B5:0D:66:E8:2E:11:90:97:66:92:52:62:FE :2C:6B:45:95:A1:EF:08:D6:05:C6:8E:A1\r\na=setup:actpass\r\na=rtpmap:111 opus/48000/2\r\na= extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=rtcp-fb:111 传输-cc\r\na=msid:janus janusa0\r\na=ssrc:1576733354 cname:janus\r\na=ssrc:1576733354 msid:janus janusa0\r\na=ssrc:1576733354 mslabel:janus\r\na= ssrc:1576733354 标签:janusa0\r\nm=video 9 UDP/TLS/RTP/SAVPF 96 97\r\nc=IN IP4 185.12.12.24\r\na=sendonly\r\na=mid:video\r\na=rtcp -mux\r\na=ice-ufrag:WETD\r\na=ice-pwd:ch2L786bEYlY6OIi+AJ4no\r\na=ice-options:trikle\r\na=指纹:sha-256 53:C2:82: E2:61:73:BC:B5:0D:66:E8:2E:11:90:97:66:92:52:62:FE:2C:6B:45:95:A1:EF:08:D6: 05:C6:8E:A1\r\na=setup:actpass\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\ r\na=rtcp-fb:96 nack pli\r\na=rtcp-fb:96 goog-remb\r\na=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=外部地图:3A1\r\na=setup:actpass\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp- fb:96 nack pli\r\na=rtcp-fb:96 goog-remb\r\na=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:3A1\r\na=setup:actpass\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp- fb:96 nack pli\r\na=rtcp-fb:96 goog-remb\r\na=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:3http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext /playout-delay\r\na=extmap:13 urn:3gpp:video-orientation\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\na=ssrc-group: FID 4137684267 1240900261\r\na=msid:janus janusv0\r\na=ssrc:4137684267 cname:janus\r\na=ssrc:4137684267 msid:janus janusv0\r\na=ssrc:4137684267 mslabel:janus\r\na =ssrc:4137684267 标签:janusv0\r\na=ssrc:1240900261 cname:janus\r\na=ssrc:1240900261 msid:janus janusv0\r\na=ssrc:1240900261 mslabel:janus\r\na=ssrc:1240900261 标签:janusv0\r\n"}
04:10:28 remoteFeed:[object Object]
04:10:28 创建并附加订阅者
04:10:28 创建答案
04:10:28 onSubscriberGotRemoteStream this.state=STARTING
04:10:28 视频开始
04:10:28 onSubscriberGotRemoteStream this.state=
STARTED 04:10:28 onSubscriberGotRemoteStream this.state=
STARTED 04:10:28 获得 SDP![RTCSessionDescription]
04:10:28 此 WebRTC PeerConnection 的 ICE 状态更改为检查
04:10:28 此 WebRTC PeerConnection 的 ICE 状态更改为已连接
04:10:35 onSubscriberGotRemoteStream this.state=
STARTED 04:10:47 onPluginMessage
04: 10:47 msg:{"videoroom":"event","room":724240,"started":"ok"}
04:10:47 jsep:undefined
04:10:47 remoteFeed:[object Object]
// UNNORMAL开始订阅耗时 49 秒