janus客户端源码分析
1 客户端源码框架
一、流程总览
二、分步详细解释
- 在网页中包含 janus.js
- 作用:引入 Janus 客户端核心库,提供与 Janus 服务端交互的 API(如
Janus.create()
、handle.send()
等)。 - 代码示例:
<script src="janus.js"></script> <!-- 加载 Janus 客户端库 -->
- 关键:
janus.js
封装了 WebSocket 通信、信令处理、WebRTC 协商等底层逻辑,让开发者无需关注复杂细节。
- 初始化 janus.js 库
- 作用:配置 Janus 客户端的依赖(如 WebRTC polyfill、日志级别),确保浏览器兼容性。
- 代码示例:
Janus.init({dependencies: {// 可选:加载 WebRTC polyfill(旧浏览器兼容)webrtcAdapter: "/path/to/adapter.js"},debug: "all" // 开启调试日志,方便排查问题 });
- 关键:
dependencies
可按需加载依赖(如adapter.js
处理浏览器 WebRTC 差异),debug
控制日志输出(开发阶段建议开启)。
- 连接 Janus Server 并创建会话(Session)
- 作用:与 Janus 服务端建立长连接(WebSocket/HTTP),生成唯一
session_id
标识通信上下文。 - 代码示例:
Janus.create({server: "ws://your-janus-server:8188", // Janus 服务端地址success: (janus) => {console.log("会话创建成功,session_id:", janus.getSessionId());// 后续操作(绑定插件、交互)},error: (error) => {console.error("会话创建失败:", error);} });
- 关键:
server
指定 Janus 服务端的 WebSocket 地址(默认端口8188
)。success
回调返回janus
实例,用于后续操作(绑定插件、发送信令)。
- 创建 Handle 并绑定插件(Plugin)
- 作用:通过
attach
操作绑定具体插件(如videoroom
实现视频房间、videocall
实现一对一通话),生成handle_id
标识插件实例。 - 代码示例(绑定 Videoroom 插件):
janus.attach({plugin: "janus.plugin.videoroom", // 插件名称success: (handle) => {console.log("插件绑定成功,handle_id:", handle.getId());// 后续操作(加入房间、发布流)},error: (error) => {console.error("插件绑定失败:", error);} });
- 关键:
- 一个
session
可绑定多个handle
(如同时使用videoroom
和audiobridge
插件)。 handle
是与插件交互的核心对象(发送信令、协商 WebRTC)。
- 一个
- 与插件交互(信令 + PeerConnection)
- 作用:通过
handle.send()
发送信令(如join
房间、publish
流),并通过 WebRTC 协商(RTCPeerConnection
)传输音视频数据。 - 代码示例(加入视频房间并发布流):
// 加入房间(作为发布者) handle.send({message: {request: "join",room: 1234, // 房间 IDptype: "publisher", // 角色:发布者display: "MyPublisher" // 显示名称},success: (response) => {console.log("加入房间成功:", response);// 发布音视频流navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then((stream) => {// 协商 WebRTC(创建 Offer)handle.createOffer({media: { audio: true, video: true },stream: stream,success: (jsep) => {// 发送 Offer 给 Janus 服务端handle.send({message: { request: "publish" },jsep: jsep});}});});} });
- 关键:
- 信令(如
join
/publish
)通过handle.send()
发送,由插件处理业务逻辑(如房间管理、媒体转发)。 createOffer
/createAnswer
触发 WebRTC 协商,建立 PeerConnection 传输音视频流。
- 信令(如
- 关闭 Handles 和 PeerConnection
- 作用:释放资源(关闭 WebRTC 连接、通知服务端销毁插件实例),避免内存泄漏。
- 代码示例:
// 关闭 Handle(解绑插件) handle.destroy({success: () => {console.log("Handle 已关闭");// 关闭 PeerConnection(可选,Janus.js 会自动处理)handle.getPeerConnection().close();} });
- 关键:
handle.destroy()
会发送detach
信令给服务端,销毁插件实例。- 需确保所有
handle
都关闭后,再销毁会话。
- 销毁会话(Session)
- 作用:通知 Janus 服务端销毁会话,释放服务端资源(如WebSocket连接、会话上下文)。
- 代码示例:
janus.destroy({success: () => {console.log("会话已销毁");} });
- 关键:会话销毁后,无法再通过
janus
实例进行任何操作,需重新创建会话。
总结
使用 Janus 的核心流程是 “引入库 → 初始化 → 建会话 → 绑插件 → 交互 → 关资源 → 毁会话”,每个步骤都围绕 “控制信令(WebSocket)” 和 “媒体流(WebRTC)” 两大通道展开。
通过 janus.js
封装的 API,开发者无需关注底层细节,只需按流程调用 create()
、attach()
、send()
等方法,即可实现复杂的音视频交互(如视频会议、直播)。
2 API
一、Session 相关 API(会话生命周期)
1. createSession
(创建会话)
- 作用:
与 Janus 服务端建立 WebSocket/HTTP 连接,创建全局会话(janus_session
),生成唯一session_id
。 - 调用时机:
应用初始化时,首次连接 Janus 服务端。 - 关联代码(简化):
Janus.create({server: "ws://janus-server:8188", // 服务端地址success: (janus) => { /* 会话创建成功,返回 janus 实例 */ },error: (error) => { /* 处理错误 */ } });
- 关键逻辑:
- 建立 WebSocket 长连接,监听
message
事件。 - 发送
create
信令,服务端返回session_id
。
- 建立 WebSocket 长连接,监听
2. destroySession
(销毁会话)
- 作用:
通知 Janus 服务端销毁会话,关闭 WebSocket 连接,释放服务端资源。 - 调用时机:
应用退出、用户登出或会话不再使用时。 - 关联代码(简化):
janus.destroy({success: () => { /* 会话销毁成功 */ },error: (error) => { /* 处理错误 */ } });
- 关键逻辑:
- 发送
destroy
信令给服务端。 - 关闭 WebSocket 连接,清理客户端
pendingRequests
。
- 发送
二、Plugin 相关 API(插件生命周期)
1. createHandle
(绑定插件)
- 作用:
通过attach
信令绑定 Janus 插件(如videoroom
),生成handle_id
,创建插件句柄(PluginHandle
)。 - 调用时机:
会话创建后,需要使用具体插件功能时(如加入视频房间)。 - 关联代码(简化):
janus.attach({plugin: "janus.plugin.videoroom", // 插件名称success: (handle) => { /* 返回 PluginHandle 实例 */ }, });
- 关键逻辑:
- 发送
attach
信令,服务端返回handle_id
。 - 创建
PluginHandle
实例,管理插件交互。
- 发送
2. destroyHandle
(解绑插件)
- 作用:
通知 Janus 服务端销毁插件句柄,关闭关联的 WebRTC 连接,释放资源。 - 调用时机:
插件功能使用完毕(如退出视频房间)。 - 关联代码(简化):
handle.destroy({success: () => { /* 插件句柄销毁成功 */ }, });
- 关键逻辑:
- 发送
detach
信令给服务端。 - 关闭关联的
RTCPeerConnection
,清理插件状态。
- 发送
三、WebRTC 相关 API(媒体协商)
1. prepareWebrtc
(初始化 WebRTC 环境)
- 作用:
初始化 WebRTC 相关资源(如RTCPeerConnection
、ICE 服务器配置),为媒体协商做准备。 - 调用时机:
绑定插件后,需要传输媒体流时(如发布/订阅流)。 - 关联代码(简化):
PluginHandle.prototype.prepareWebrtc = function(options) {this.pc = new RTCPeerConnection({ iceServers: options.iceServers });this.pc.onicecandidate = (event) => { /* 处理 ICE 候选 */ };this.pc.ontrack = (event) => { /* 处理媒体流 */ }; };
- 关键逻辑:
- 创建
RTCPeerConnection
,配置 ICE 服务器(STUN/TURN)。 - 监听
icecandidate
(候选交换)和ontrack
(媒体流接收)事件。
- 创建
2. prepareWebrtcPeer
(准备 Peer 连接)
- 作用:
为插件句柄关联RTCPeerConnection
,确保信令与媒体通道绑定。 - 调用时机:
prepareWebrtc
之后,创建 Offer/Answer 之前。 - 关联代码(简化):
PluginHandle.prototype.prepareWebrtcPeer = function() {this.webrtcStuff = { pc: this.pc }; // 关联 PeerConnection };
- 关键逻辑:
- 将
RTCPeerConnection
存入插件句柄的webrtcStuff
,方便后续操作(如createOffer
)。
- 将
3. createOffer
(创建 SDP Offer)
- 作用:
生成 WebRTC 的 SDP Offer,触发媒体协商流程(发布者视角)。 - 调用时机:
发布者需要推流时(如加入房间后调用publish
)。 - 关联代码(简化):
PluginHandle.prototype.createOffer = function(options, success, error) {this.pc.createOffer().then((offer) => {this.pc.setLocalDescription(offer);success(offer); // 返回 SDP Offer}).catch(error); };
- 关键逻辑:
- 调用
pc.createOffer()
生成 SDP。 - 设置本地描述,通过
success
回调返回 Offer。
- 调用
4. createAnswer
(创建 SDP Answer)
- 作用:
生成 WebRTC 的 SDP Answer,响应发布者的 Offer(订阅者视角)。 - 调用时机:
订阅者收到 SDP Offer 后,需要回复 Answer 时。 - 关联代码(简化):
PluginHandle.prototype.createAnswer = function(options, success, error) {this.pc.createAnswer().then((answer) => {this.pc.setLocalDescription(answer);success(answer); // 返回 SDP Answer}).catch(error); };
- 关键逻辑:
- 调用
pc.createAnswer()
生成 SDP。 - 设置本地描述,通过
success
回调返回 Answer。
- 调用
5. sendSDP
(发送 SDP 信令)
- 作用:
将 SDP Offer/Answer 封装为jsep
信令,发送给 Janus 服务端,完成媒体协商。 - 调用时机:
createOffer
/createAnswer
后,需要将 SDP 发送给服务端时。 - 关联代码(简化):
PluginHandle.prototype.sendSDP = function(jsep, success, error) {this.send({message: { request: "start" }, // 或 publish/join 等jsep: jsep,success: success,error: error}); };
- 关键逻辑:
- 将 SDP 放入
jsep
字段,通过handle.send()
发送信令。 - 服务端转发 SDP 给对端(发布者/订阅者)。
- 将 SDP 放入
6. streamDone
(媒体流结束处理)
- 作用:
处理媒体流结束事件(如用户挂断、流停止),关闭 PeerConnection 并通知服务端。 - 调用时机:
检测到RTCPeerConnection
的onended
事件,或主动停止流时。 - 关联代码(简化):
PluginHandle.prototype.streamDone = function() {this.pc.close(); // 关闭 PeerConnectionthis.send({ message: { request: "unpublish" } }); // 通知服务端停止转发 };
- 关键逻辑:
- 关闭
RTCPeerConnection
,释放媒体资源。 - 发送
unpublish
信令,通知 Janus 服务端停止流转发。
- 关闭
Offer:由发起连接的一方(通常是发布者) 生成的 SDP 提议,包含自己支持的媒体参数,发给对端(订阅者或 Janus 服务端)协商。
3 信令交互
服务端
一、信令处理的核心流程(分层视角)
- 传输层:接收信令(WebSocket/HTTP)
Janus 支持 WebSocket(实时信令)和 HTTP(非实时信令)传输,所有客户端信令(如create
/attach
/message
)先由传输层接收:
- WebSocket:基于
libwebsockets
库,建立长连接后,客户端与 Janus 实时双向通信。 - HTTP:基于
libmicrohttpd
库,客户端通过 POST 请求发信令,Janus 同步返回响应。
代码映射(Janus 服务端):
// 传输层回调,统一接收信令
static janus_transport_callbacks janus_handler_transport = {.transport_gone = janus_transport_gone,.notify_event = janus_transport_notify_event,.incoming_request = janus_transport_incoming_request // 核心回调
};// 信令入站时,触发此函数
void janus_transport_incoming_request(janus_transport *plugin, janus_transport_session *transport, void *request_id, gboolean admin, json_t *message, json_error_t *error) {// 将信令放入请求队列,等待处理g_async_queue_push(requests, request);
}
- Janus Core:分发与初步处理
Janus 核心层从队列中取出信令,根据信令类型(create
/attach
/message
等)分发到对应模块:
create
信令:创建全局会话(janus_session
),分配session_id
。attach
信令:绑定插件(如videoroom
),创建插件句柄(janus_plugin_session
),分配handle_id
。message
信令:转发给已绑定的插件处理(需携带handle_id
标识插件实例 )。
核心逻辑(Janus 插件框架):
// 信令处理线程从队列取信令
janus_request *request = g_async_queue_pop(requests);
json_t *message = request->message;
const char *janus_type = json_string_value(json_object_get(message, "janus"));if (g_strcmp0(janus_type, "create") == 0) {// 创建会话逻辑janus_session *session = janus_session_create();json_t *reply = json_pack("{s:s, s:s, s:i}", "janus", "success", "id", session->id);janus_transport_send_response(request->transport, request->transport_session, reply);
} else if (g_strcmp0(janus_type, "attach") == 0) {// 绑定插件逻辑janus_plugin *plugin = janus_plugin_find(json_string_value(json_object_get(message, "plugin")));janus_plugin_session *plugin_session = janus_plugin_session_create(session, plugin);json_t *reply = json_pack("{s:s, s:s, s:i}", "janus", "success", "id", plugin_session->id);janus_transport_send_response(request->transport, request->transport_session, reply);
}
- 插件层:业务逻辑处理
插件(如videoroom
/videocall
)是信令处理的“业务层”,根据信令内容执行具体操作(如房间管理、媒体协商 ):
join
信令(videoroom
插件):验证房间 ID,记录发布者/订阅者身份,返回房间内其他用户信息。publish
信令(videoroom
插件):触发 WebRTC 媒体协商,生成 SDP Offer 并转发给订阅者。
插件处理示例(videoroom
插件伪代码):
// 插件接收 message 信令
void janus_videoroom_handle_message(janus_plugin_session *plugin_session, json_t *message) {const char *request = json_string_value(json_object_get(message, "request"));if (g_strcmp0(request, "join") == 0) {// 解析参数:room、ptype(publisher/subscriber)、feed 等int room = json_integer_value(json_object_get(message, "room"));const char *ptype = json_string_value(json_object_get(message, "ptype"));// 业务逻辑:加入房间,记录角色janus_videoroom_room *room = janus_videoroom_find_room(room);janus_videoroom_participant *participant = janus_videoroom_add_participant(room, ptype);// 回复 event 信令,通知客户端加入成功json_t *event = json_pack("{s:s, s:i, s:o}", "janus", "event", "room", room->id, "participants", janus_videoroom_get_participants(room));janus_plugin_send_event(plugin_session, event);}
}
- 响应与异步通知
处理完成后,Janus 通过以下方式回复客户端:
- 同步响应:如
create
/attach
信令,直接返回success
响应(含session_id
/handle_id
)。 - 异步事件:如
join
/publish
信令,通过event
消息异步通知(如房间状态变更、媒体协商结果 )。
客户端接收示例(Janus.js):
janusSession.attach({plugin: "janus.plugin.videoroom",success: (pluginHandle) => {// 绑定插件成功,获取 handle_idpluginHandle.on("message", (msg, jsep) => {// 处理异步 event 信令if (msg.janus === "event" && msg.plugindata.data.request === "joined") {console.log("加入房间成功:", msg);}});}
});
二、典型信令交互时序(以 videoroom
为例)
以下是“客户端加入视频房间”的完整信令流程,体现 Janus 分层处理逻辑:
阶段 | 客户端信令(Janus.js) | Janus 处理逻辑 | 服务端响应/事件 |
---|---|---|---|
创建会话 | Janus.create({ server: "ws://..." }) | 核心层创建 janus_session ,分配 session_id | 返回 success (含 session_id ) |
绑定插件 | session.attach({ plugin: "videoroom" }) | 核心层绑定 videoroom 插件,分配 handle_id | 返回 success (含 handle_id ) |
加入房间 | handle.send({ request: "join", room: 123, ptype: "publisher" }) | 插件层验证房间,记录发布者身份 | 返回 event (含房间参与者信息 ) |
媒体协商 | handle.createOffer() 生成 SDP Offer | 插件层转发 SDP Offer 给订阅者,触发协商 | 返回 event (含 SDP Answer ) |
ICE 候选交换 | handle.trickle(candidate) 发送候选 | 核心层转发 ICE 候选到插件 | 返回 ack 确认接收 |
连接建立 | - | 核心层检测 ICE 连接状态 | 返回 webrtcup 事件(连接建立 ) |
媒体传输 | - | 插件层转发音视频流 | 返回 media 事件(媒体数据到达 ) |
三、关键设计:插件化与异步事件
-
插件化解耦:
Janus 核心不处理具体业务,通过插件(如videoroom
)实现音视频房间、通话等功能。不同插件可独立开发、动态加载,降低耦合性。 -
异步事件驱动:
除了简单的“请求-响应”(如create
/attach
),复杂操作(如媒体协商、房间状态变更 )通过event
异步通知客户端,避免阻塞信令通道。
四、总结:Janus 信令处理的本质
Janus 处理信令的核心逻辑是 “分层转发 + 插件业务处理”:
- 传输层负责“收发包”,核心层负责“会话/插件管理”,插件层负责“业务逻辑”。
- 信令类型(
create
/attach
/message
)决定了处理流程,异步事件(event
)实现复杂交互。
客户端
一、客户端消息处理核心流程(闭环图)
二、客户端接收消息:从 WebSocket
到 handleEvent
- WebSocket 监听与触发
janus.js
在创建 Session 时,会为 WebSocket 连接绑定message
事件监听:
// janus.js 核心代码(简化)
Janus.prototype.init = function() {this.websocket = new WebSocket(this.server);this.websocket.onmessage = (event) => {const json = JSON.parse(event.data);this.handleEvent(json); // 收到消息后,调用 handleEvent 处理};
};
- handleEvent 分支处理(核心逻辑)
handleEvent
是消息处理的“分发中心”,根据json.janus
字段区分消息类型,处理逻辑如下:
json.janus 类型 | 处理逻辑 | 关联流程 |
---|---|---|
keepalive | 心跳消息,重置超时定时器(this.resetTimeout() ) | 保活机制 |
ack | 确认消息,标记客户端发送的请求已被服务端接收(this.resolveRequest() ) | 可靠性保证(请求-确认) |
success | 操作成功响应,标记请求完成(this.resolveRequest() ),返回业务数据 | 如 create /attach 成功响应 |
trickle | ICE 候选消息,解析 candidate 并交给 RTCPeerConnection 处理 | 媒体协商(WebRTC) |
webrtcup | Peer 连接已建立,触发 pluginHandle 的 webrtcup 事件 | 媒体流开始传输 |
hangup | 用户挂断,通知 pluginHandle 执行挂断逻辑(如关闭 RTCPeerConnection ) | 通话终止 |
detached | 插件与 Janus Core 断开,清理 pluginHandle 资源 | 插件解绑 |
media | 媒体流状态变更(开始/停止),通知业务层更新 UI(如显示“对方关闭摄像头”) | 媒体状态同步 |
slowlink | 网络质量检测,通知业务层调整码率/分辨率(需业务实现) | 弱网适配 |
error | 错误响应,标记请求失败(this.rejectRequest() ),传递错误信息 | 异常处理 |
event | 插件业务事件(如房间用户加入/退出),交给 pluginHandle 处理 | 业务逻辑(如视频会议房间状态) |
timeout | 超时响应,重试或清理资源 | 容错机制 |
- 与
pluginHandle
的交互
对于插件相关消息(如event
/webrtcup
),handleEvent
会调用pluginHandle
的handleMessage
方法,让插件实例处理业务逻辑:
// janus.js 中 handleEvent 处理 `event` 消息的逻辑(简化)
if (json.janus === "event") {const pluginHandle = this.findPluginHandle(json.session_id, json.handle_id);if (pluginHandle) {pluginHandle.handleMessage(json, jsep); // 交给插件句柄处理}
}
pluginHandle
进一步解析 plugindata
字段,区分具体插件(如 videoroom
/audiobridge
)并触发业务事件:
// PluginHandle.prototype.handleMessage(简化)
handleMessage(json, jsep) {const plugindata = json.plugindata;if (plugindata.plugin === "janus.plugin.videoroom") {this.emit("roomEvent", plugindata.data, jsep); // 触发 videoroom 业务事件}this.emit("message", json, jsep); // 通用消息事件
}
三、客户端发送消息:从 send()
到服务端
- 发送消息的封装(
sendMessage
)
janus.js
为上层应用封装了send()
方法(实际调用sendMessage
),负责构造 JSON 信令并通过 WebSocket 发送:
// janus.js 核心发送逻辑(简化)
Janus.prototype.sendMessage = function(json) {const transaction = this.generateTransaction(); // 生成唯一事务 IDjson.transaction = transaction; // 标记请求this.pendingRequests[transaction] = { // 记录待响应的请求timeout: setTimeout(() => this.handleTimeout(transaction), this.timeout),resolve: null,reject: null};this.websocket.send(JSON.stringify(json)); // 通过 WebSocket 发送return new Promise((resolve, reject) => {this.pendingRequests[transaction].resolve = resolve;this.pendingRequests[transaction].reject = reject;});
};
2. 典型发送场景
-
创建 Session:
janus.sendMessage({ janus: "create" }).then((response) => {console.log("Session 创建成功,session_id:", response.id); });
-
绑定插件(
attach
):janus.sendMessage({ janus: "attach", session_id: "xxx", plugin: "janus.plugin.videoroom" }).then((response) => {const pluginHandle = new PluginHandle(janus, response.id);console.log("插件绑定成功,handle_id:", pluginHandle.id); });
-
加入房间(
join
):pluginHandle.sendMessage({ janus: "message", handle_id: "yyy", body: { request: "join", room: 123, ptype: "publisher" } }).then((response) => {console.log("加入房间成功:", response); });
3. 请求-响应闭环
发送消息时,janus.js
会记录 transaction
(事务 ID),服务端返回响应时,handleEvent
通过 transaction
找到对应的 Promise 并决议(resolve
/reject
):
// handleEvent 处理 success 消息时(简化)
if (json.janus === "success") {const request = this.pendingRequests[json.transaction];if (request) {request.resolve(json); // 决议 Promise,返回响应clearTimeout(request.timeout);delete this.pendingRequests[json.transaction];}
}
四、关键设计:可靠性与插件解耦
-
可靠性保证(
ack
/success
):ack
确保服务端已收到请求(但未处理完成)。success
确保服务端已处理完成(返回业务结果)。- 客户端通过 Promise 等待
success
,保证操作原子性。
-
插件解耦:
- 会话(
Janus
)与插件句柄(PluginHandle
)分离,不同插件的消息互不干扰。 - 业务层只需监听
pluginHandle
的事件(如roomEvent
),无需关心底层信令。
- 会话(
-
自动重连与保活:
keepalive
心跳消息自动发送,维持长连接。- 超时(
timeout
)时自动重试或清理资源,保证健壮性。
五、总结:客户端收发消息的本质
Janus 客户端收发消息的核心是 “基于 WebSocket 的请求-响应闭环 + 插件化事件驱动”:
- 接收:WebSocket 监听
message
事件 →handleEvent
分发消息 → 插件句柄处理业务事件。 - 发送:上层调用
send()
→sendMessage
构造信令 → WebSocket 发送 → 等待服务端响应并决议 Promise。
4 Janus音视频会话建立全流程:
一、流程总览:信令与媒体的协同逻辑
Janus的会话建立遵循 “信令协商先行,媒体流传输在后” 的设计,核心分为五大阶段:
每个阶段通过JSON信令交互完成,最终实现音视频数据的实时传输。
二、分步拆解:从信令到媒体流的每一步
- 阶段1:会话创建——建立基础连接
- 客户端动作:发送
create
信令,请求初始化会话。{"janus": "create","transaction": "Yddh20mU3s" }
- 服务端响应:返回
success
信令,分配唯一session_id
(如714784477113709
)。{"janus": "success","transaction": "Yddh20mU3s","data": { "id": 714784477113709 } }
- 关键作用:生成会话唯一标识,建立客户端与Janus服务端的基础通信上下文。
2. 阶段2:插件绑定——关联业务功能
- 客户端动作:发送
attach
信令,绑定videoroom
插件(视频房间业务)。{"janus": "attach","plugin": "janus.plugin.videoroom","session_id": 714784477113709,"transaction": "ZxL7Jf7W1U" }
- 服务端响应:返回
success
信令,分配handle_id
(如7668325947218508
)。{"janus": "success","session_id": 714784477113709,"handle_id": 7668325947218508,"transaction": "ZxL7Jf7W1U" }
- 关键作用:将“视频房间”业务逻辑与会话关联,后续操作通过
handle_id
执行。
3. 阶段3:房间加入——声明身份与状态同步
- 客户端动作:发送
message
信令,以发布者身份加入房间(ptype: "publisher"
)。{"janus": "message","session_id": 714784477113709,"handle_id": 7668325947218508,"transaction": "AbDmSrm3T","body": {"request": "join","room": 1234,"ptype": "publisher","display": "Ben"} }
- 服务端响应:
- 先返回
ack
信令(确认收到请求)。{"janus": "ack","session_id": 714784477113709,"transaction": "AbDmSrm3T" }
- 再返回
event
信令,同步房间状态(如房间ID、当前发布者列表)。{"janus": "event","session_id": 714784477113709,"handle_id": 7668325947218508,"plugindata": {"plugin": "janus.plugin.videoroom","data": {"videoroom": "joined","room": 1234,"publishers": []}} }
- 先返回
- 关键作用:完成房间加入,客户端获取实时状态(如当前无其他发布者)。
4. 阶段4:媒体协商——解决“怎么传流”的技术问题
(1)发送SDP Offer:提议媒体参数
- 客户端动作:发送
message
信令,携带SDP Offer(包含编码、分辨率等媒体参数)。{"janus": "message","session_id": 714784477113709,"handle_id": 7668325947218508,"transaction": "QvOYPH9VOj","body": {"request": "configure","audio": true,"video": true},"jsep": {"type": "offer","sdp": "v=0\r\no=- 1234567890 1234567890 IN IP4 0.0.0.0\r\ns=...",} }
- 作用:告诉服务端“我要推流,支持这些编码和网络参数”。
(2)ICE候选交换:协商网络路径
- 客户端动作:多次发送
trickle
信令,传递ICE候选(设备网络地址)。{"janus": "trickle","candidate": {"candidate": "candidate:2302048529 ... typ host generation 0","sdpMid": "audio","sdpMLineIndex": 0},"session_id": 714784477113709,"handle_id": 7668325947218508,"transaction": "A3n5mWz8S" }
- 服务端响应:返回
ack
信令确认接收。 - 作用:找到最优网络路径(P2P或中继),为流传输铺路。
(3)接收SDP Answer:确认媒体参数
- 服务端动作:通过
event
信令返回SDP Answer(确认双方兼容的媒体参数)。{"janus": "event","session_id": 714784477113709,"handle_id": 7668325947218508,"plugindata": {"plugin": "janus.plugin.videoroom","data": {"videoroom": "event","configured": {"audio": true,"video": true},"jsep": {"type": "answer","sdp": "v=0\r\no=- 9876543210 9876543210 IN IP4 0.0.0.0\r\ns=...",}}} }
- 客户端处理:解析Answer,完成WebRTC媒体协商。
- 作用:确保双方编码、传输协议兼容,建立媒体通道。
5. 阶段5:流传输——从信令到实际数据
- 服务端事件:返回
webrtcup
信令,标识WebRTC连接已建立。{"janus": "webrtcup","session_id": 714784477113709,"handle_id": 7668325947218508 }
- 服务端事件:返回
media
信令,标识音视频流开始传输。{"janus": "media","session_id": 714784477113709,"handle_id": 7668325947218508,"sender": 7668325947218508,"receiving": { "video": true, "audio": true } }
- 客户端动作:将音视频流渲染到页面(如绑定
<video>
标签)。 - 关键作用:完成从“信令协商”到“实际媒体流传输”的闭环。