WebRTC Relay API v1

会话管理、设备控制、群控管理、系统监控、竞争记录接口文档

在线演示

DEMO 创建会话 → 获取播放地址 → 播放投屏 完整流程演示

填写设备信息,点击「创建会话」即可获取播放地址,并直接在页面内预览投屏画面。

会话管理

POST /api/v1/session/create 创建推流会话
字段类型说明
device_ipstring必填设备 IP 地址
device_tcp_portint必填设备 TCP 端口 (scrcpy)
device_udp_portint必填设备 UDP 端口 (WebRTC)
qualityint可选画质等级(见下方说明),默认 720
codecstring可选编码格式:"h264"(默认)或 "h265"
分辨率推流尺寸 (sw x sh)说明
480480p854 x 480低画质,节省带宽,适合弱网环境
720720p(默认)1280 x 720标清,画质与流畅度平衡,推荐日常使用
10801080p1920 x 1080高清,画面清晰但带宽消耗大
{
  "device_ip": "192.168.1.10",
  "device_tcp_port": 30007,
  "device_udp_port": 30008,
  "quality": 720,
  "codec": "h264"
}
{
  "code": 0,
  "data": {
    "session_id": "abc123",
    "proxy_tcp_port": 3478,
    "proxy_udp_port": 3478,
    "play_url": "/player.html?session_id=s_abc123&token=t_xxx",
    "orientation": "portrait",
    "created_at": "2026-04-07T12:00:00Z",
    "preempted_session": null
  }
}
POST /api/v1/session/close 关闭会话
{
  "session_id": "你的会话ID"
}
{
  "code": 0,
  "message": "session closed"
}
GET /api/v1/session/{id} 查询会话详情
字段类型说明
idstring必填会话 ID
{
  "code": 0,
  "data": {
    "session_id": "abc123",
    "device_ip": "192.168.1.10",
    "status": "active",
    "idle_seconds": 12,
    "heartbeat_status": "healthy",
    ...
  }
}
GET /api/v1/sessions 获取会话列表
字段类型说明
statusstring可选状态过滤:"active"、"closed"
pageint可选页码,默认 1
page_sizeint可选每页条数,默认 50(最大 200)
{
  "code": 0,
  "data": {
    "total": 5,
    "sessions": [ ... ]
  }
}

设备控制

POST /api/v1/session/orientation 设置屏幕方向
{
  "session_id": "你的会话ID",
  "orientation": "portrait"   // 或 "landscape"
}
{
  "code": 0,
  "message": "orientation updated",
  "data": { "session_id": "...", "orientation": "portrait" }
}
POST /api/v1/session/command 发送设备指令
{
  "session_id": "你的会话ID",
  "command": "goHome"
}
指令说明
volUp音量加
volDown音量减
goHome回到桌面
goBack返回上一级
VK_RETURN回车键
goClean清除最近任务
GET /api/v1/session/commands?session_id=xxx 轮询待执行指令
字段类型说明
session_idstring必填要轮询指令的会话 ID
{
  "code": 0,
  "data": { "commands": ["goHome", "volUp"] }
}
POST /api/v1/session/quality 调整画质
{
  "session_id": "你的会话ID",
  "quality": 720    // 可选值: 480, 720, 1080
}

系统监控

GET /api/v1/stats 获取系统状态
{
  "code": 0,
  "data": {
    "uptime": "2h30m",          // 运行时长
    "goroutines": 42,            // 协程数
    "memory": {                  // 内存使用
      "alloc_mb": "12.34",
      "total_alloc_mb": "56.78",
      "sys_mb": "30.00",
      "gc_count": 15
    },
    "port_pool": {               // 端口池
      "total": 20,
      "used": 3,
      "available": 17,
      "usage_percent": "15.00"
    },
    "devices": {                 // 设备统计
      "active_devices": 3,
      "proxy_connections": 6,
      "stale_devices": 0
    },
    "traffic": {                 // 流量统计
      "active_forwarded_bytes": 1048576,
      "throughput_bps": "524288.00"
    },
    "startup": {                 // 启动失败
      "recent_failures": 0,
      "last_failure_time": null,
      "last_failure_reason": ""
    },
    "contention": {              // 竞争统计
      "total_count": 0,
      "last_minute_count": 0,
      "frequent_devices": []
    }
  }
}

竞争记录

GET /api/v1/contentions 获取竞争记录列表
字段类型说明
pageint可选页码,默认 1
page_sizeint可选每页条数,默认 50(最大 200)
POST /api/v1/contentions/clear 清空竞争记录
{
  "code": 0,
  "message": "contentions cleared"
}

群控管理

对等群控:群组内所有设备角色平等,任意设备上的操作(触摸、滑动、按钮)会自动同步到其余所有设备。通过 WebSocket 实时广播,发送者自身不会收到回弹。

POST /api/v1/group/create 创建群组
参数类型必填说明
session_ids string[] 必填 初始成员的 session_id 数组,至少一个
{
  "session_ids": ["s_abc123", "s_def456"]
}
{
  "code": 0,
  "data": {
    "group_id": "g_98b16c56bf523c7e",
    "members": ["s_abc123", "s_def456"],
    "created_at": "2026-04-07T12:00:00Z"
  }
}
POST /api/v1/group/join 加入群组
参数类型必填说明
group_id string 必填 目标群组 ID
session_id string 必填 要加入的会话 ID
{
  "group_id": "g_98b16c56bf523c7e",
  "session_id": "s_newmember"
}
{
  "code": 0,
  "message": "joined group"
}
POST /api/v1/group/leave 离开群组
参数类型必填说明
session_id string 必填 要离开的会话 ID
{
  "code": 0,
  "message": "left group"
}

最后一个成员离开时群组自动解散。

POST /api/v1/group/disband 解散群组
参数类型必填说明
group_id string 必填 要解散的群组 ID
{
  "code": 0,
  "message": "group disbanded"
}
GET /api/v1/group/{id} 查询群组详情
{
  "code": 0,
  "data": {
    "group_id": "g_98b16c56bf523c7e",
    "members": ["s_abc123", "s_def456", "s_ghi789"],
    "member_count": 3,
    "created_at": "2026-04-07T12:00:00Z"
  }
}
GET /api/v1/groups 列出所有群组
{
  "code": 0,
  "data": {
    "total": 1,
    "groups": [{
      "group_id": "g_98b16c56bf523c7e",
      "members": ["s_abc123", "s_def456"],
      "member_count": 2,
      "created_at": "2026-04-07T12:00:00Z"
    }]
  }
}
GET /api/v1/group/ws?session_id={id} 群控 WebSocket

WebSocket 连接,用于实时同步群组内的操作事件。每个成员建立一个连接,既发送又接收。

ws://host/api/v1/group/ws?session_id=s_abc123
方向说明
发送本端操作事件(触摸/滑动/按钮)JSON 发给服务器
接收服务器广播其他成员的操作事件(自动排除发送者)
// SDK 触摸事件(由 fun_send_data 发出)
{"t":1, "d":{"mx":360,"my":640,"sw":720,"sh":1280,...}}
// 控制按钮(home/back/volUp 等)
{"_groupCmd":true, "command":"goHome"}
  1. 调用 /api/v1/group/create 创建群组并传入所有 session_id
  2. 每个设备的 iframe 通过 postMessage 发送 start-group-peer 消息启动 peer 模式
  3. 后续加入的设备调用 /api/v1/group/join 后同样启动 peer 模式
  4. 操作任意设备,其余设备自动同步
GET /api/v1/player/bootstrap?session_id=SESSION_ID&token=TOKEN 播放器 bootstrap 参数

Go 壳播放器通常会自动调用这个接口。它根据 session_idtoken 返回固定端口播放所需的运行时参数,前端一般不需要手动拼接 shostrtc_isportrtc_p

参数类型必填说明
session_idstring会话 ID
tokenstring播放鉴权 token
{
  "code": 0,
  "data": {
    "session_id": "s_abc123",
    "token": "t_xxx",
    "shost": "relay.example.com",
    "sport": 3478,
    "rtc_i": "relay.example.com",
    "rtc_p": 3478,
    "q": 2,
    "v": "h264",
    "sw": 1280,
    "sh": 720,
    "tshow": 0
  }
}

集成指南

指南 如何在你的页面中播放投屏 前端集成方法

调用创建会话 API 后,将返回的 play_url 转换为播放器地址,嵌入 iframe 即可。生产环境里,返回的 shost / rtc_i 必须是客户端可访问的公网 IP 或域名,不能是 127.0.0.1

// 1. 调用 API 创建会话
const res = await fetch('/api/v1/session/create', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    device_ip: '192.168.1.10',
    device_tcp_port: 30007,
    device_udp_port: 30008,
    quality: 720,
    codec: 'h264'
  })
});
const { data } = await res.json();

// 2. play_url 已经是 Go 壳播放器地址
const embedUrl = data.play_url;

// 3. 嵌入 iframe
document.getElementById('myPlayer').src = embedUrl;

// 也可以直接用 play_url 在新窗口打开
// window.open(data.play_url);
<!-- HTML: 9:16 竖屏容器 -->
<div style="width:360px; position:relative;">
  <div style="padding-top:177.78%; position:relative;">
    <iframe id="myPlayer"
      style="position:absolute;top:0;left:0;width:100%;height:100%;border:none;">
    </iframe>
  </div>
</div>

最简单的方式,直接用 play_url 打开新窗口。

// 创建会话后直接打开
window.open(data.play_url);

// 或者完整地址(跨域时需要用完整地址)
window.open('https://relay.example.com' + data.play_url);

嵌入 iframe 后,还可以通过 postMessage 向播放器发送指令。

const frame = document.getElementById('myPlayer');

// 发送设备按键指令(直接通过播放器,无需再调 API)
frame.contentWindow.postMessage({
  type: 'control-command',
  command: 'goHome'       // goBack, volUp, volDown, VK_RETURN, goClean
}, '*');

// 切换屏幕方向
frame.contentWindow.postMessage({
  type: 'set-orientation',
  orientation: 'landscape' // 或 'portrait'
}, '*');

// 截图
frame.contentWindow.postMessage({ type: 'take-screenshot' }, '*');
window.addEventListener('message', function(e) {
  if (e.data.type === 'screenshot-result') {
    // e.data.dataUrl 就是 base64 PNG 图片
    document.getElementById('screenshotImg').src = e.data.dataUrl;
  }
});
地址说明适用场景
/player.html?...Go 壳播放器,内含方向控制与固定端口参数iframe 嵌入、直接打开、新窗口播放

代码示例

JS JavaScript / Fetch 完整调用流程
const BASE = '';  // 同源,留空即可

// 1. 创建会话
const res = await fetch(BASE + '/api/v1/session/create', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    device_ip: '192.168.1.10',
    device_tcp_port: 30007,
    device_udp_port: 30008,
    quality: 720,
    codec: 'h264'
  })
});
const { data } = await res.json();
const sessionId = data.session_id;

// 2. 嵌入播放器 iframe
document.getElementById('myPlayer').src = data.play_url;

// 3. 发送设备指令
await fetch(BASE + '/api/v1/session/command', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    session_id: sessionId,
    command: 'goHome'
  })
});

// 4. 查询系统状态
const stats = await fetch(BASE + '/api/v1/stats').then(r => r.json());
console.log(stats.data);

// 5. 关闭会话
await fetch(BASE + '/api/v1/session/close', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ session_id: sessionId })
});
cURL cURL 命令行 终端调用示例
# 创建会话(返回 play_url)
curl -X POST https://relay.example.com/api/v1/session/create \
  -H "Content-Type: application/json" \
  -d '{"device_ip":"192.168.1.10","device_tcp_port":30007,"device_udp_port":30008,"quality":720,"codec":"h264"}'

# 拿到 play_url 后在浏览器打开即可播放,例如:
# https://relay.example.com/player.html?session_id=s_abc123&token=t_xxx

# 获取会话列表
curl https://relay.example.com/api/v1/sessions

# 发送设备指令
curl -X POST https://relay.example.com/api/v1/session/command \
  -H "Content-Type: application/json" \
  -d '{"session_id":"SESSION_ID","command":"goHome"}'

# 查询系统状态
curl https://relay.example.com/api/v1/stats

# 关闭会话
curl -X POST https://relay.example.com/api/v1/session/close \
  -H "Content-Type: application/json" \
  -d '{"session_id":"SESSION_ID"}'
PY Python / requests Python 调用示例
import requests

BASE = "https://relay.example.com"

# 创建会话
r = requests.post(f"{BASE}/api/v1/session/create", json={
    "device_ip": "192.168.1.10",
    "device_tcp_port": 30007,
    "device_udp_port": 30008,
    "quality": 720,
    "codec": "h264"
})
data = r.json()["data"]
session_id = data["session_id"]
play_url = data["play_url"]

# play_url 就是播放地址,拼接完整 URL 后可在浏览器打开
full_url = f"{BASE}{play_url}"
print(f"播放地址: {full_url}")

# 发送设备指令
requests.post(f"{BASE}/api/v1/session/command", json={
    "session_id": session_id,
    "command": "goHome"
})

# 查询系统状态
stats = requests.get(f"{BASE}/api/v1/stats").json()

# 关闭会话
requests.post(f"{BASE}/api/v1/session/close", json={
    "session_id": session_id
})
JAVA Java / HttpClient Java 后端调用示例
import java.net.http.*;
import java.net.URI;
import com.google.gson.*;

String BASE = "https://relay.example.com";
HttpClient client = HttpClient.newHttpClient();
Gson gson = new Gson();

// 1. 创建会话
String body = gson.toJson(Map.of(
    "device_ip", "192.168.1.10",
    "device_tcp_port", 30007,
    "device_udp_port", 30008,
    "quality", 720,
    "codec", "h264"
));

HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create(BASE + "/api/v1/session/create"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(body))
    .build();

HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
JsonObject data = gson.fromJson(res.body(), JsonObject.class)
    .getAsJsonObject("data");

String sessionId = data.get("session_id").getAsString();
String playUrl = data.get("play_url").getAsString();

// 2. 拼接完整播放地址,传给前端
String fullPlayUrl = BASE + playUrl;
// 前端拿到这个 URL 放进 iframe 或 window.open() 即可播放

// 3. 关闭会话
String closeBody = gson.toJson(Map.of("session_id", sessionId));
HttpRequest closeReq = HttpRequest.newBuilder()
    .uri(URI.create(BASE + "/api/v1/session/close"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(closeBody))
    .build();
client.send(closeReq, HttpResponse.BodyHandlers.ofString());
WebRTC Relay · API v1