1.基础知识回顾
WebRTC 服务器之Janus概述和环境搭建-CSDN博客
2.插件使用流程
我们要使⽤janus的功能时,通常要执⾏以下操作:
-
在你的⽹⻚引入 Janus.js 库,即是包含janus.js;
<script src="https://your-cdn-path/janus.js"></script> -
初始化 Janus 库,并传递其依赖项(依赖项可选);
javascript
Janus.init({
debug: true, // 开启调试日志
dependencies: {
// 可选依赖(如 adapter.js 用于跨浏览器兼容)
adapter: adapter
},
callback: function() {
console.log("Janus 库初始化完成");
// 初始化完成后连接服务器
connectToJanusServer();
}
});
- 连接服务器 & 创建会话连接到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);
}
});
}
- **创建插件句柄 (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);
}
});
}
- 信令交互示例 - 加入视频房间与创建交互(发送/接收消息,协商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();
}
});
- 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);
});
- 最后 关闭所有的handles并关闭相关的相应的PeerConnections; 消耗destroy会话session。
javascript
function cleanup() {
// 1. 销毁插件句柄
if (pluginHandle) {
pluginHandle.detach();
pluginHandle = null;
}
// 2. 销毁会话
if (janusSession) {
janusSession.destroy();
janusSession = null;
}
}
// 页面关闭时触发清理
window.onbeforeunload = cleanup;
完整流程总结
-
初始化
Janus.init({ debug: true, callback: createJanusSession });
-
会话与插件
创建会话 (
createSession
) → 创建插件句柄 (createHandle
) → 注册事件监听 (eventHandler
). -
信令交互
发送消息 (
sendMessage
) 加入房间 → 接收服务器事件 (handleEvent
). -
WebRTC 协商
创建 Offer (
createOffer
) → 发送 SDP (sendSDP
) → 接收 Answer → 创建 Answer (createAnswer
). -
收发av数据
-
资源释放
销毁句柄 (
destroyHandle
) → 销毁会话 (destroySession
).
2.1 协议基本类型
"janus"后⾯的字符串代表消息的类型,主要有:
- create:创建⼀个Janus会话命令;
- attach:attach⼀个插件到Janus会话命令;
- success:⼀个命令的成功结果;
- error:⼀个命令的失败结果;
- ack:⼀个命令的ack,因为不能直接返回结果,先回ack,后续的结果通过event返回;
- event:推送给客户端的异步事件,主要由插件发出,这些事件需要插件来⾃⼰定义;
- message:客户端发给插件的消息,message和event就构成了应⽤协议;
- trickle:客户端发送的candidate,会传递给ICE句柄;
- keepalive:客户端发送的⼼跳;
- webrtcup:ICE成功建⽴通道的通知;
- media:⾳频、视频媒体的接收通知;
- slowlink:链路恶化通知;
- hangup:挂断通知;
- detached:插件从Janus会话detach的通知,释放了⼀个插件句柄。
3.videoroom插件信令交互分析
Janus中所有插件都遵循以下基本数据流程:
- 客户端发送create创建一个Janus会话;
- Janus回复success返回Janus会话句柄;
- 客户端发送attach命令在Janus会话上attach指定插件;
- Janus回复success返回插件的句柄;
- 客户端给指定的插件发送message进行信令控制;
- Janus上的插件发送event通知事件给客户端;
- 客户端收集candidate并通过trickle消息发送给插件绑定的ICE通道;
- Janus发送webrtcup通知ICE通道建立;
- 客户端发送媒体数据;
- Janus发送media消息通知媒体数据的第一次到达;
- Janus进行媒体数据转发。

3.总体逻辑
-
Janus客户端->Janus网关: Janus客户端发送create请求创建会话
*JBJ-WEBSOCKET RECV: {"janus":"create","transaction":"aAMxRfsTsVVQ"}
-
Janus网关-->>Janus客户端: Janus网关创建会话,并返回会话IDJ
1.JBJ-WEBSOCKET responses:Sending Janus API response( { "janus": "success", "transaction": "aAMxRfsTsVVQ", "data": { "id": 5109637625847547 } } ) to janus.transport.websockets (0x7f5cd400d3c0)
-
Janus客户端->Janus网关: 客户端发送attach命令在Janus会话上attach指定插件 ;
1.JBJ-WEBSOCKET RECV: { "janus": "attach", "plugin": "janus.plugin.videoroom", "opaque_id": "videoroomtest-jIAzP2ZkPUrn", "transaction": "2kuM6pzfX4Zh", "session_id": 5109637625847547 }
-
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)
-
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)
-
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)
-
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 }
-
Janus网关-->>Janus客户端: Janus网关返回ack消息
1.javaJBJ-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)
学习资料分享