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

相关推荐
cuijiecheng20183 小时前
Windows下编译WebRTC源码
webrtc
从后端到QT1 天前
WebRTC 服务器之Janus架构分析
服务器·架构·webrtc·janus
linkingvision2 天前
Chrome 136 H265 WebRTC 支持 正式版本已包含
前端·chrome·webrtc
fanged2 天前
WebRTC(TODO)
webrtc
学而知不足~4 天前
WebRtc09:网络基础P2P/STUN/TURN/ICE
webrtc
桃花岛主705 天前
WebRTC基于网页的视频会议,手写WebRTC流程(html)
webrtc
学而知不足~6 天前
WebRtc08:WebRtc信令服务器实现
webrtc
桃花岛主707 天前
如何使用WebRTC
webrtc
唯独失去了从容8 天前
WebRTC服务器Coturn服务器的管理平台功能
运维·服务器·webrtc