Janus Gateway API 文档
目录
概述
Janus Gateway 是一个开源的 WebRTC 服务器,提供多种通信接口:
- HTTP REST API: 基于 HTTP 的 RESTful 接口
- WebSocket: 实时双向通信
- RabbitMQ: 消息队列集成
- MQTT: 物联网协议支持
- Nanomsg: 高性能消息传递
- Unix Sockets: 本地进程间通信
连接方式
HTTP REST API
端点: http://<host>:8088/janus
特点:
- 端口: 8088
- 路径:
/janus - 请求方式: POST
- Content-Type:
application/json
示例:
curl -X POST http://localhost:8088/janus \
-H "Content-Type: application/json" \
-d '{"janus": "create", "transaction": "123456"}'
WebSocket
端点: ws://<host>:8188/
特点:
- 端口: 8188
- 路径: 无(注意:WebSocket 不需要
/janus路径) - 双向实时通信
- 自动重连支持
示例 (JavaScript):
const ws = new WebSocket('ws://localhost:8188/');
ws.onopen = () => {
ws.send(JSON.stringify({
janus: "create",
transaction: "123456"
}));
};
示例 (C++ Qt):
QWebSocket *ws = new QWebSocket();
ws->open(QUrl("ws://localhost:8188/"));
消息格式
请求消息结构
{
"janus": "消息类型",
"session_id": 1234567890,
"handle_id": 9876543210,
"transaction": "唯一事务ID",
"body": {},
"jsep": {}
}
字段说明:
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
janus |
string | 是 | 消息类型(create, attach, message等) |
session_id |
number | 否 | 会话 ID(创建会话后必需) |
handle_id |
number | 否 | 插件句柄 ID(附加插件后必需) |
transaction |
string | 是 | 唯一事务标识符,用于匹配响应 |
body |
object | 否 | 插件特定的消息体 |
jsep |
object | 否 | WebRTC SDP 信息(offer/answer) |
响应消息结构
{
"janus": "success|event|error|ack",
"session_id": 1234567890,
"transaction": "123456",
"data": {},
"plugindata": {},
"jsep": {},
"error": {}
}
响应类型:
| 类型 | 说明 |
|---|---|
success |
操作成功 |
event |
插件事件或通知 |
error |
操作失败 |
ack |
确认收到(异步操作) |
会话管理
创建会话
创建与 Janus Gateway 的会话。
请求:
{
"janus": "create",
"transaction": "123456"
}
成功响应:
{
"janus": "success",
"transaction": "123456",
"data": {
"id": 1234567890
}
}
销毁会话
终止会话并释放所有资源。
请求:
{
"janus": "destroy",
"session_id": 1234567890,
"transaction": "123456"
}
成功响应:
{
"janus": "success",
"transaction": "123456"
}
保活
保持会话活跃,防止超时断开。
请求:
{
"janus": "keepalive",
"session_id": 1234567890,
"transaction": "123456"
}
建议: 每 30 秒发送一次 keepalive 消息。
插件管理
附加插件
将插件附加到会话。
请求:
{
"janus": "attach",
"session_id": 1234567890,
"plugin": "janus.plugin.videoroom",
"transaction": "123456"
}
成功响应:
{
"janus": "success",
"transaction": "123456",
"data": {
"id": 9876543210
}
}
常用插件:
| 插件名称 | 功能 |
|---|---|
janus.plugin.videoroom |
视频会议室 |
janus.plugin.sip |
SIP 网关 |
janus.plugin.recordplay |
录制回放 |
janus.plugin.voicemail |
语音信箱 |
janus.plugin.textroom |
文本聊天室 |
janus.plugin.echotest |
回声测试 |
分离插件
从会话中移除插件。
请求:
{
"janus": "detach",
"session_id": 1234567890,
"handle_id": 9876543210,
"transaction": "123456"
}
VideoRoom 插件
VideoRoom 是 Janus 最常用的插件,用于实现多人视频会议。
创建房间
创建一个新的视频会议室。
请求:
{
"janus": "message",
"session_id": 1234567890,
"handle_id": 9876543210,
"transaction": "123456",
"body": {
"request": "create",
"room": 1234,
"description": "会议室描述",
"publishers": 10,
"bitrate": 128000,
"fir_freq": 10,
"record": false,
"rec_dir": "/tmp/janus-recordings"
}
}
参数说明:
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
room |
number | 是 | - | 房间 ID |
description |
string | 否 | - | 房间描述 |
publishers |
number | 否 | 3 | 最大发布者数量 |
bitrate |
number | 否 | 128000 | 视频比特率(bps) |
fir_freq |
number | 否 | 10 | 关键帧请求频率(秒) |
record |
boolean | 否 | false | 是否录制 |
rec_dir |
string | 否 | - | 录制目录 |
成功响应:
{
"janus": "success",
"transaction": "123456",
"plugindata": {
"data": {
"videoroom": "created",
"room": 1234
}
}
}
销毁房间
删除已存在的房间。
请求:
{
"janus": "message",
"session_id": 1234567890,
"handle_id": 9876543210,
"transaction": "123456",
"body": {
"request": "destroy",
"room": 1234
}
}
加入房间(发布者)
作为发布者加入房间,可以发送音视频流。
请求:
{
"janus": "message",
"session_id": 1234567890,
"handle_id": 9876543210,
"transaction": "123456",
"body": {
"request": "join",
"room": 1234,
"ptype": "publisher",
"display": "用户名",
"id": 5678
}
}
参数说明:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
room |
number | 是 | 房间 ID |
ptype |
string | 是 | 参与者类型(publisher/subscriber) |
display |
string | 否 | 显示名称 |
id |
number | 否 | 自定义用户 ID |
成功响应:
{
"janus": "event",
"session_id": 1234567890,
"transaction": "123456",
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "joined",
"room": 1234,
"description": "会议室描述",
"id": 5678,
"private_id": 12345678,
"publishers": [
{
"id": 123456789,
"display": "其他用户",
"talking": false
}
]
}
}
}
加入房间(订阅者)
作为订阅者加入房间,只能接收音视频流。
请求:
{
"janus": "message",
"session_id": 1234567890,
"handle_id": 9876543210,
"transaction": "123456",
"body": {
"request": "join",
"room": 1234,
"ptype": "subscriber",
"feed": 123456789
}
}
参数说明:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
room |
number | 是 | 房间 ID |
ptype |
string | 是 | 必须为 "subscriber" |
feed |
number | 是 | 要订阅的发布者 ID |
发布流
开始发送音视频流到房间。
请求:
{
"janus": "message",
"session_id": 1234567890,
"handle_id": 9876543210,
"transaction": "123456",
"body": {
"request": "publish",
"audio": true,
"video": true,
"data": true
}
}
参数说明:
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
audio |
boolean | 否 | true | 是否发送音频 |
video |
boolean | 否 | true | 是否发送视频 |
data |
boolean | 否 | false | 是否发送数据通道 |
成功响应:
{
"janus": "event",
"session_id": 1234567890,
"transaction": "123456",
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "event",
"configured": "ok"
}
},
"jsep": {
"type": "answer",
"sdp": "v=0\r\no=- 1234567890 2 IN IP4 127.0.0.1\r\n..."
}
}
取消发布
停止发送音视频流。
请求:
{
"janus": "message",
"session_id": 1234567890,
"handle_id": 9876543210,
"transaction": "123456",
"body": {
"request": "unpublish"
}
}
离开房间
退出当前房间。
请求:
{
"janus": "message",
"session_id": 1234567890,
"handle_id": 9876543210,
"transaction": "123456",
"body": {
"request": "leave"
}
}
配置订阅者
设置订阅者的媒体参数。
请求:
{
"janus": "message",
"session_id": 1234567890,
"handle_id": 9876543210,
"transaction": "123456",
"body": {
"request": "configure",
"audio": true,
"video": true,
"data": false
}
}
订阅发布者
订阅房间中的特定发布者。
请求:
{
"janus": "message",
"session_id": 1234567890,
"handle_id": 9876543210,
"transaction": "123456",
"body": {
"request": "subscribe",
"room": 1234,
"feed": 123456789,
"audio": true,
"video": true,
"data": false
}
}
取消订阅
停止订阅某个发布者。
请求:
{
"janus": "message",
"session_id": 1234567890,
"handle_id": 9876543210,
"transaction": "123456",
"body": {
"request": "unsubscribe",
"feed": 123456789
}
}
列出房间
获取所有可用房间列表。
请求:
{
"janus": "message",
"session_id": 1234567890,
"handle_id": 9876543210,
"transaction": "123456",
"body": {
"request": "list"
}
}
成功响应:
{
"janus": "success",
"transaction": "123456",
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "success",
"list": [
{
"room": 1234,
"description": "会议室1",
"num_participants": 3
},
{
"room": 5678,
"description": "会议室2",
"num_participants": 5
}
]
}
}
}
列出参与者
获取房间中的参与者列表。
请求:
{
"janus": "message",
"session_id": 1234567890,
"handle_id": 9876543210,
"transaction": "123456",
"body": {
"request": "listparticipants",
"room": 1234
}
}
完整示例
JavaScript 完整流程
const ws = new WebSocket('ws://localhost:8188/');
let sessionId = null;
let handleId = null;
ws.onopen = () => {
console.log('Connected to Janus');
createSession();
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
handleMessage(msg);
};
function createSession() {
ws.send(JSON.stringify({
janus: 'create',
transaction: 'create-session'
}));
}
function attachPlugin() {
ws.send(JSON.stringify({
janus: 'attach',
session_id: sessionId,
plugin: 'janus.plugin.videoroom',
transaction: 'attach-plugin'
}));
}
function createRoom() {
ws.send(JSON.stringify({
janus: 'message',
session_id: sessionId,
handle_id: handleId,
transaction: 'create-room',
body: {
request: 'create',
room: 1234,
description: 'Demo Room',
publishers: 10
}
}));
}
function joinRoom() {
ws.send(JSON.stringify({
janus: 'message',
session_id: sessionId,
handle_id: handleId,
transaction: 'join-room',
body: {
request: 'join',
room: 1234,
ptype: 'publisher',
display: 'DemoUser'
}
}));
}
function publishStream() {
ws.send(JSON.stringify({
janus: 'message',
session_id: sessionId,
handle_id: handleId,
transaction: 'publish',
body: {
request: 'publish',
audio: true,
video: true
}
}));
}
function handleMessage(msg) {
if (msg.janus === 'success') {
if (msg.data && msg.data.id) {
sessionId = msg.data.id;
console.log('Session created:', sessionId);
attachPlugin();
}
if (msg.data && msg.data.id && handleId === null) {
handleId = msg.data.id;
console.log('Plugin attached:', handleId);
createRoom();
}
} else if (msg.janus === 'event') {
if (msg.plugindata && msg.plugindata.data) {
const data = msg.plugindata.data;
if (data.videoroom === 'created') {
console.log('Room created');
joinRoom();
} else if (data.videoroom === 'joined') {
console.log('Joined room');
publishStream();
}
}
}
}
C++ Qt 完整流程
#include <QWebSocket>
#include <QJsonObject>
#include <QJsonDocument>
class JanusClient : public QObject {
Q_OBJECT
public:
void connectToServer(const QString &url) {
m_webSocket->open(QUrl(url));
}
void createSession() {
QJsonObject msg;
msg["janus"] = "create";
msg["transaction"] = "create-session";
sendMessage(msg);
}
void attachPlugin() {
QJsonObject msg;
msg["janus"] = "attach";
msg["session_id"] = static_cast<qint64>(m_sessionId);
msg["plugin"] = "janus.plugin.videoroom";
msg["transaction"] = "attach-plugin";
sendMessage(msg);
}
void createRoom(int roomId) {
QJsonObject msg;
msg["janus"] = "message";
msg["session_id"] = static_cast<qint64>(m_sessionId);
msg["handle_id"] = static_cast<qint64>(m_handleId);
msg["transaction"] = "create-room";
QJsonObject body;
body["request"] = "create";
body["room"] = roomId;
body["description"] = "Demo Room";
body["publishers"] = 10;
msg["body"] = body;
sendMessage(msg);
}
void joinRoom(int roomId, const QString &username) {
QJsonObject msg;
msg["janus"] = "message";
msg["session_id"] = static_cast<qint64>(m_sessionId);
msg["handle_id"] = static_cast<qint64>(m_handleId);
msg["transaction"] = "join-room";
QJsonObject body;
body["request"] = "join";
body["room"] = roomId;
body["ptype"] = "publisher";
body["display"] = username;
msg["body"] = body;
sendMessage(msg);
}
void publishStream() {
QJsonObject msg;
msg["janus"] = "message";
msg["session_id"] = static_cast<qint64>(m_sessionId);
msg["handle_id"] = static_cast<qint64>(m_handleId);
msg["transaction"] = "publish";
QJsonObject body;
body["request"] = "publish";
body["audio"] = true;
body["video"] = true;
msg["body"] = body;
sendMessage(msg);
}
private:
void sendMessage(const QJsonObject &msg) {
QJsonDocument doc(msg);
m_webSocket->sendTextMessage(doc.toJson(QJsonDocument::Compact));
}
QWebSocket *m_webSocket;
quint64 m_sessionId;
quint64 m_handleId;
};
curl 示例
# 创建会话
curl -X POST http://localhost:8088/janus \
-H "Content-Type: application/json" \
-d '{"janus": "create", "transaction": "123456"}'
# 附加 VideoRoom 插件
curl -X POST http://localhost:8088/janus/SESSION_ID \
-H "Content-Type: application/json" \
-d '{"janus": "attach", "plugin": "janus.plugin.videoroom", "transaction": "123456"}'
# 创建房间
curl -X POST http://localhost:8088/janus/SESSION_ID/HANDLE_ID \
-H "Content-Type: application/json" \
-d '{
"janus": "message",
"body": {
"request": "create",
"room": 1234,
"description": "Demo Room"
},
"transaction": "123456"
}'
# 加入房间
curl -X POST http://localhost:8088/janus/SESSION_ID/HANDLE_ID \
-H "Content-Type: application/json" \
-d '{
"janus": "message",
"body": {
"request": "join",
"room": 1234,
"ptype": "publisher",
"display": "DemoUser"
},
"transaction": "123456"
}'
错误处理
常见错误代码
| 错误代码 | 说明 |
|---|---|
| 401 | 未授权(需要认证令牌) |
| 403 | 禁止访问 |
| 404 | 资源不存在 |
| 426 | 协议错误 |
| 478 | 未知会话 |
| 477 | 未知插件 |
| 478 | 未知句柄 |
| 477 | 插件错误 |
错误响应示例
{
"janus": "error",
"transaction": "123456",
"error": {
"code": 478,
"reason": "No such session"
}
}
最佳实践
- 事务 ID: 使用唯一的事务 ID(如时间戳)来匹配请求和响应
- 保活: 定期发送 keepalive 消息(建议 30 秒间隔)
- 错误处理: 始终检查响应中的
janus字段 - 资源清理: 应用退出时销毁会话和分离插件
- 重连机制: WebSocket 断开时实现自动重连
- 心跳检测: 定期检查连接状态
浙公网安备 33010602011771号