WebRTC 服务器之Janus视频会议插件信令交互

1.基础知识回顾

WebRTC 服务器之Janus概述和环境搭建-CSDN博客

WebRTC 服务器之Janus架构分析-CSDN博客

2.插件使用流程

我们要使⽤janus的功能时,通常要执⾏以下操作:

  1. 在你的⽹⻚引入 Janus.js 库,即是包含janus.js;

    <script src="https://your-cdn-path/janus.js"></script>
  2. 初始化 Janus 库,并传递其依赖项(依赖项可选);

javascript 复制代码
Janus.init({
  debug: true,    // 开启调试日志
  dependencies: {
    // 可选依赖(如 adapter.js 用于跨浏览器兼容)
    adapter: adapter
  },
  callback: function() {
    console.log("Janus 库初始化完成");
    // 初始化完成后连接服务器
    connectToJanusServer();
  }
});
  1. 连接服务器 & 创建会话连接到janus server并创建create⼀个会话session;
javascript 复制代码
let janusSession;

function connectToJanusServer() {
  const serverUrl = "wss://your-janus-server:8989/janus";

  new Janus({
    server: serverUrl,
    success: function(session) {
      janusSession = session;
      console.log("会话创建成功:", session.getId());
      // 创建插件句柄(例如加入视频房间)
      attachToVideoRoomPlugin();
    },
    error: function(error) {
      console.error("连接失败:", error);
    }
  });
}
  1. **创建插件句柄 (Handle)**创建⼀个或多个handle 以attach到插件(plugin)(例如videoroom、videocall等插件);
javascript 复制代码
let pluginHandle;

function attachToVideoRoomPlugin() {
  janusSession.attach({
    plugin: "janus.plugin.videoroom",  // 插件名称
    success: function(handle) {
      pluginHandle = handle;
      console.log("成功附加到插件,句柄 ID:", handle.getId());
      // 发送加入房间请求
      joinVideoRoom();
    },
    error: function(error) {
      console.error("附加插件失败:", error);
    }
  });
}
  1. 信令交互示例 - 加入视频房间与创建交互(发送/接收消息,协商PeerConnection);
javascript 复制代码
function joinVideoRoom() {
  const joinMsg = {
    request: "join",
    room: 1234,       // 房间 ID
    ptype: "publisher",
    display: "用户1"
  };

  pluginHandle.send({ message: joinMsg });
}

// 处理服务器事件(如房间通知)
pluginHandle.on("message", (msg, jsep) => {
  console.log("收到服务器消息:", msg);
  if (msg["videoroom"] === "joined") {
    console.log("成功加入房间!");
    // 处理媒体协商(如创建 Offer)
    startWebRTCNegotiation();
  }
});
  1. WebRTC 协商流程
javascript 复制代码
function startWebRTCNegotiation() {
  // 创建媒体配置
  const mediaConfig = {
    audio: true,
    video: true,
    data: true
  };

  // 准备 PeerConnection
  pluginHandle.createOffer({
    media: mediaConfig,
    success: function(sdp) {
      // 发送 SDP Offer
      pluginHandle.sendSDP(sdp);
    },
    error: function(error) {
      console.error("创建 Offer 失败:", error);
    }
  });
}

// 处理 ICE Candidate
pluginHandle.on("ice", (candidate) => {
  // 发送候选到服务器
  pluginHandle.trickle(candidate);
});
  1. 最后 关闭所有的handles并关闭相关的相应的PeerConnections; 消耗destroy会话session。
javascript 复制代码
function cleanup() {
  // 1. 销毁插件句柄
  if (pluginHandle) {
    pluginHandle.detach();
    pluginHandle = null;
  }

  // 2. 销毁会话
  if (janusSession) {
    janusSession.destroy();
    janusSession = null;
  }
}

// 页面关闭时触发清理
window.onbeforeunload = cleanup;

完整流程总结

  1. 初始化

    Janus.init({ debug: true, callback: createJanusSession });

  2. 会话与插件

    创建会话 (createSession) → 创建插件句柄 (createHandle) → 注册事件监听 (eventHandler).

  3. 信令交互

    发送消息 (sendMessage) 加入房间 → 接收服务器事件 (handleEvent).

  4. WebRTC 协商

    创建 Offer (createOffer) → 发送 SDP (sendSDP) → 接收 Answer → 创建 Answer (createAnswer).

  5. 收发av数据

  6. 资源释放

    销毁句柄 (destroyHandle) → 销毁会话 (destroySession).

2.1 协议基本类型

"janus"后⾯的字符串代表消息的类型,主要有:

  1. create:创建⼀个Janus会话命令;
  2. attach:attach⼀个插件到Janus会话命令;
  3. success:⼀个命令的成功结果;
  4. error:⼀个命令的失败结果;
  5. ack:⼀个命令的ack,因为不能直接返回结果,先回ack,后续的结果通过event返回;
  6. event:推送给客户端的异步事件,主要由插件发出,这些事件需要插件来⾃⼰定义;
  7. message:客户端发给插件的消息,message和event就构成了应⽤协议;
  8. trickle:客户端发送的candidate,会传递给ICE句柄;
  9. keepalive:客户端发送的⼼跳;
  10. webrtcup:ICE成功建⽴通道的通知;
  11. media:⾳频、视频媒体的接收通知;
  12. slowlink:链路恶化通知;
  13. hangup:挂断通知;
  14. detached:插件从Janus会话detach的通知,释放了⼀个插件句柄。

3.videoroom插件信令交互分析

Janus中所有插件都遵循以下基本数据流程:

  1. 客户端发送create创建一个Janus会话;
  2. Janus回复success返回Janus会话句柄;
  3. 客户端发送attach命令在Janus会话上attach指定插件;
  4. Janus回复success返回插件的句柄;
  5. 客户端给指定的插件发送message进行信令控制;
  6. Janus上的插件发送event通知事件给客户端;
  7. 客户端收集candidate并通过trickle消息发送给插件绑定的ICE通道;
  8. Janus发送webrtcup通知ICE通道建立;
  9. 客户端发送媒体数据;
  10. Janus发送media消息通知媒体数据的第一次到达;
  11. Janus进行媒体数据转发。

3.总体逻辑

  1. Janus客户端->Janus网关: Janus客户端发送create请求创建会话
    *

    复制代码
      JBJ-WEBSOCKET RECV: {"janus":"create","transaction":"aAMxRfsTsVVQ"}
  2. Janus网关-->>Janus客户端: Janus网关创建会话,并返回会话IDJ
    1.

    复制代码
       JBJ-WEBSOCKET responses:Sending Janus API response(
       {
       	"janus": "success",
       	"transaction": "aAMxRfsTsVVQ",
       	"data": {
       		"id": 5109637625847547
       	}
       }
       ) to janus.transport.websockets (0x7f5cd400d3c0)
  3. Janus客户端->Janus网关: 客户端发送attach命令在Janus会话上attach指定插件
    1.

    复制代码
       JBJ-WEBSOCKET RECV: {
       	"janus": "attach",
       	"plugin": "janus.plugin.videoroom",
       	"opaque_id": "videoroomtest-jIAzP2ZkPUrn",
       	"transaction": "2kuM6pzfX4Zh",
       	"session_id": 5109637625847547
       }
  4. Janus网关-->>Janus客户端: Janus回复success返回插件的句柄
    1.

    复制代码
       JBJ-WEBSOCKET responses:Sending Janus API response({
       	"janus": "success",
       	"session_id": 5109637625847547,
       	"transaction": "2kuM6pzfX4Zh",
       	"data": {
       		"id": 6769547295474368
       	}
       }) to janus.transport.websockets (0x7f5cd400d3c0)
  5. Janus客户端->Janus网关: Janus客户端给指定插件发送message(Join)
    1.

    复制代码
       JBJ-WEBSOCKET RECV: {
       	"janus": "message",
       	"body": {
       		"request": "join",
       		"room": 1234,
       		"ptype": "publisher",
       		"display": "321"
       	},
       	"transaction": "11JDzIbsbK91",
       	"session_id": 5109637625847547,
       	"handle_id": 6769547295474368
       }
    
       JBJ-WEBSOCKET responses:Sending Janus API response({
       	"janus": "ack",
       	"session_id": 5109637625847547,
       	"transaction": "11JDzIbsbK91"
       }) to janus.transport.websockets (0x7f5cd400d3c0)
  6. Janus网关-->>Janus客户端: Janus网关处理message,并将结果通过event事件发送给客户端
    1.

    复制代码
       Preparing JSON event as a reply
       JBJ-video.room.event: [6769547295474368] Sending event is: {
       	"janus": "event",
       	"session_id": 5109637625847547,
       	"transaction": "11JDzIbsbK91",
       	"sender": 6769547295474368,
       	"plugindata": {
       		"plugin": "janus.plugin.videoroom",
       		"data": {
       			"videoroom": "joined",
       			"room": 1234,
       			"description": "Demo Room",
       			"id": 6538458198168085,
       			"private_id": 1041514801,
       			"publishers": []
       		}
       	}} to transport...
       Sending event to janus.transport.websockets (0x7f5cd400d3c0)
         >> 0 (Success)
  7. Janus客户端->Janus网关: Janus客户端给指定插件发送message(Configure & offer)
    1.

    java 复制代码
    ####################Configure & offer
    JBJ-WEBSOCKETRECV: {
    	"janus": "message",
    	"body": {
    		"request": "configure",
    		"audio": true,
    		"video": true
    	},
    	"transaction": "MHTliRCHqY5y",
    	"jsep": {
    		"type": "offer",
    		"sdp": "v=0\r\no=- 6239954181818903803 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS 900722df-7e7a-4951-b1bf-1902cb7fe6e0\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 126\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:eHT/\r\na=ice-pwd:aAxznBSHiqTXMkZAzJs5xv3B\r\na=ice-options:trickle\r\na=fingerprint:sha-256 75:80:26:F9:EB:47:14:51:61:D3:CD:8B:60:C1:5E:F5:67:53:1E:88:9C:45:53:3B:32:D0:D2:5F:CD:45:63:7F\r\na=setup:actpass\r\na=mid:0\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=sendonly\r\na=msid:900722df-7e7a-4951-b1bf-1902cb7fe6e0 9718362e-2152-4c32-9e79-da62a8a609c5\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:111 opus/48000/2\r\na=rtcp-fb:111 transport-cc\r\na=fmtp:111 minptime=10;useinbandfec=1\r\na=rtpmap:63 red/48000/2\r\na=fmtp:63 111/111\r\na=rtpmap:9 G722/8000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:13 CN/8000\r\na=rtpmap:110 telephone-event/48000\r\na=rtpmap:126 telephone-event/8000\r\na=ssrc:2665350871 cname:+3Rknr5a6Axa5msx\r\na=ssrc:2665350871 msid:900722df-7e7a-4951-b1bf-1902cb7fe6e0 9718362e-2152-4c32-9e79-da62a8a609c5\r\nm=video 9 UDP/TLS/RTP/SAVPF 96 97 103 104 107 108 109 114 115 116 117 118 39 40 45 46 98 99 100 101 119 120 123 124 125\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:eHT/\r\na=ice-pwd:aAxznBSHiqTXMkZAzJs5xv3B\r\na=ice-options:trickle\r\na=fingerprint:sha-256 75:80:26:F9:EB:47:14:51:61:D3:CD:8B:60:C1:5E:F5:67:53:1E:88:9C:45:53:3B:32:D0:D2:5F:CD:45:63:7F\r\na=setup:actpass\r\na=mid:1\r\na=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:13 urn:3gpp:video-orientation\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\na=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\na=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=sendonly\r\na=msid:900722df-7e7a-4951-b1bf-1902cb7fe6e0 d97d2702-4a0f-4693-8499-71c66f8c03fe\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\na=rtpmap:103 H264/90000\r\na=rtcp-fb:103 goog-remb\r\na=rtcp-fb:103 transport-cc\r\na=rtcp-fb:103 ccm fir\r\na=rtcp-fb:103 nack\r\na=rtcp-fb:103 nack pli\r\na=fmtp:103 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\r\na=rtpmap:104 rtx/90000\r\na=fmtp:104 apt=103\r\na=rtpmap:107 H264/90000\r\na=rtcp-fb:107 goog-remb\r\na=rtcp-fb:107 transport-cc\r\na=rtcp-fb:107 ccm fir\r\na=rtcp-fb:107 nack\r\na=rtcp-fb:107 nack pli\r\na=fmtp:107 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f\r\na=rtpmap:108 rtx/90000\r\na=fmtp:108 apt=107\r\na=rtpmap:109 H264/90000\r\na=rtcp-fb:109 goog-remb\r\na=rtcp-fb:109 transport-cc\r\na=rtcp-fb:109 ccm fir\r\na=rtcp-fb:109 nack\r\na=rtcp-fb:109 nack pli\r\na=fmtp:109 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\na=rtpmap:114 rtx/90000\r\na=fmtp:114 apt=109\r\na=rtpmap:115 H264/90000\r\na=rtcp-fb:115 goog-remb\r\na=rtcp-fb:115 transport-cc\r\na=rtcp-fb:115 ccm fir\r\na=rtcp-fb:115 nack\r\na=rtcp-fb:115 nack pli\r\na=fmtp:115 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f\r\na=rtpmap:116 rt
    }
    
    JBJ-video.room.event: [6769547295474368] Sending event is: {
    	"janus": "event",
    	"session_id": 5109637625847547,
    	"transaction": "MHTliRCHqY5y",
    	"sender": 6769547295474368,
    	"plugindata": {
    		"plugin": "janus.plugin.videoroom",
    		"data": {
    			"videoroom": "event",
    			"room": 1234,
    			"configured": "ok",
    			"audio_codec": "opus",
    			"video_codec": "vp8"
    		}
    	},
    	"jsep": {
    		"type": "answer",
    		"sdp": "v=0\r\no=- 6239954181818903803 2 IN IP4 117.72.13.81\r\ns=VideoRoom 1234\r\nt=0 0\r\na=group:BUNDLE 0 1\r\na=msid-semantic: WMS janus\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\nc=IN IP4 117.72.13.81\r\na=recvonly\r\na=mid:0\r\na=rtcp-mux\r\na=ice-ufrag:0pFN\r\na=ice-pwd:wGcEE+9WQLoHULcXGP8KFn\r\na=ice-options:trickle\r\na=fingerprint:sha-256 C7:C1:43:EE:67:07:A0:6D:42:33:6A:CA:4E:EA:95:E0:55:17:03:CB:88:FD:B6:37:AB:1C:7B:85:65:9A:95:C6\r\na=setup:active\r\na=rtpmap:111 opus/48000/2\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=msid:janus janusa0\r\na=ssrc:3338353236 cname:janus\r\na=ssrc:3338353236 msid:janus janusa0\r\na=ssrc:3338353236 mslabel:janus\r\na=ssrc:3338353236 label:janusa0\r\na=candidate:1 1 udp 2013266431 117.72.13.81 53576 typ host\r\na=candidate:2 1 udp 1677722111 117.72.13.81 53576 typ srflx raddr 172.16.0.4 rport 53576\r\na=candidate:3 1 udp 503316991 172.16.0.4 54497 typ relay raddr 172.16.0.4 rport 53576\r\na=end-of-candidates\r\nm=video 9 UDP/TLS/RTP/SAVPF 96 97\r\nc=IN IP4 117.72.13.81\r\na=recvonly\r\na=mid:1\r\na=rtcp-mux\r\na=ice-ufrag:0pFN\r\na=ice-pwd:wGcEE+9WQLoHULcXGP8KFn\r\na=ice-options:trickle\r\na=fingerprint:sha-256 C7:C1:43:EE:67:07:A0:6D:42:33:6A:CA:4E:EA:95:E0:55:17:03:CB:88:FD:B6:37:AB:1C:7B:85:65:9A:95:C6\r\na=setup:active\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=rtcp-fb:96 transport-cc\r\na=extmap:13 urn:3gpp:video-orientation\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\na=msid:janus janusv0\r\na=ssrc:3467980833 cname:janus\r\na=ssrc:3467980833 msid:janus janusv0\r\na=ssrc:3467980833 mslabel:janus\r\na=ssrc:3467980833 label:janusv0\r\na=ssrc:3074101642 cname:janus\r\na=ssrc:3074101642 msid:janus janusv0\r\na=ssrc:3074101642 mslabel:janus\r\na=ssrc:3074101642 label:janusv0\r\na=candidate:1 1 udp 2013266431 117.72.13.81 53576 typ host\r\na=candidate:2 1 udp 1677722111 117.72.13.81 53576 typ srflx raddr 172.16.0.4 rport 53576\r\na=candidate:3 1 udp 503316991 172.16.0.4 54497 typ relay raddr 172.16.0.4 rport 53576\r\na=end-of-candidates\r\n"
    	}
    }
    
    #######################trickle
    JBJ-WEBSOCKET RECV: {
    	"janus": "trickle",
    	"candidate": {
    		"candidate": "candidate:980779705 1 udp 2122260223 192.168.126.1 60475 typ host generation 0 ufrag eHT/ network-id 2",
    		"sdpMid": "0",
    		"sdpMLineIndex": 0
    	},
    	"transaction": "0WWSoZXbCxhA",
    	"session_id": 5109637625847547,
    	"handle_id": 6769547295474368
    }
    
    JBJ-WEBSOCKET RECV: {
    	"janus": "trickle",
    	"candidate": {
    		"candidate": "candidate:3993315412 1 udp 2122194687 192.168.248.1 60476 typ host generation 0 ufrag eHT/ network-id 3",
    		"sdpMid": "0",
    		"sdpMLineIndex": 0
    	},
    	"transaction": "DddvxlDlWuZN",
    	"session_id": 5109637625847547,
    	"handle_id": 6769547295474368
    }
    JBJ-WEBSOCKET RECV: {
    	"janus": "trickle",
    	"candidate": {
    		"candidate": "candidate:2923601174 1 udp 2122129151 172.25.0.107 60477 typ host generation 0 ufrag eHT/ network-id 1 network-cost 10",
    		"sdpMid": "0",
    		"sdpMLineIndex": 0
    	},
    	"transaction": "HCdvw7ihHaaZ",
    	"session_id": 5109637625847547,
    	"handle_id": 6769547295474368
    }
  8. Janus网关-->>Janus客户端: Janus网关返回ack消息
    1.

    java 复制代码
    JBJ-WEBSOCKET responses:Sending Janus API response({
    	"janus": "ack",
    	"session_id": 5109637625847547,
    	"transaction": "MHTliRCHqY5y"
    }) to janus.transport.websockets (0x7f5cd400d3c0)
    
    JBJ-WEBSOCKET responses:Sending Janus API response({
    	"janus": "ack",
    	"session_id": 5109637625847547,
    	"transaction": "0WWSoZXbCxhA"
    }) to janus.transport.websockets (0x7f5cd400d3c0)
    
    JBJ-WEBSOCKET responses:Sending Janus API response({
    	"janus": "ack",
    	"session_id": 5109637625847547,
    	"transaction": "DddvxlDlWuZN"
    }) to janus.transport.websockets (0x7f5cd400d3c0)

学习资料分享

0voice · GitHub

相关推荐
赖small强2 分钟前
【ZeroRange WebRTC】Amazon Kinesis Video Streams WebRTC SDK 音视频传输技术分析
音视频·webrtc·nack·pli·twcc·带宽自适应
赖small强2 小时前
【ZeroRange WebRTC】Amazon Kinesis Video Streams WebRTC Data Plane REST API 深度解析
https·webrtc·data plane rest·sigv4 签名
赖small强7 小时前
【ZeroRange WebRTC】Kinesis Video Streams WebRTC 三大平面职责与协同关系总结
websocket·webrtc·control plane·data plane
赖small强10 小时前
【ZeroRange WebRTC】Amazon Kinesis Video Streams WebRTC Control Plane API 深度解析
https·webrtc·control plane
赖small强10 小时前
【ZeroRange WebRTC】Kinesis Video Streams WebRTC Data Plane WebSocket API 深度解析
websocket·webrtc·sdp·offer/answer·master/viewer
赖small强17 小时前
【ZeroRnge WebRTC】RFC 8445:ICE 协议规范(中文整理与译注)
webrtc·ice·rfc 8445
赖small强21 小时前
【ZeroRange WebRTC】RFC 5766:TURN 协议规范(中文整理与译注)
webrtc·turn·ice·rfc 5766
赖small强21 小时前
【ZeroRange WebRTC】ICE 服务器列表解析(KVS WebRTC)
webrtc·stun·turn·ice
xinyu_Jina1 天前
WebRTC的P2P实践:局域网文件传输中的信令、ICE与DataChannel架构解析
架构·webrtc·p2p
赖small强1 天前
【ZeroRange WebRTC】TLS 底层原理与工作机制(深入解析)
webrtc·tls·ecdhe·tls 1.3·前向保密(pfs)·密钥派生(hkdf)·流量密钥