diff --git a/script/livekit-poc/README.md b/script/livekit-poc/README.md index 104cdb2654..41e731877c 100644 --- a/script/livekit-poc/README.md +++ b/script/livekit-poc/README.md @@ -15,7 +15,7 @@ bash verify.sh - 7880:HTTP / WebSocket 信令; - 7881:WebRTC TCP fallback; - 7882/UDP:WebRTC 媒体; -- macOS / Windows:当前 `docker-compose.yml` 走端口映射模式,webhook URL 用 `host.docker.internal:48080` 让容器访问到宿主机 yudao 后端; +- macOS / Windows:当前 `docker-compose.yml` 走端口映射模式,`rtc.node_ip` 使用 `127.0.0.1` 让浏览器访问映射后的媒体端口,webhook URL 用 `host.docker.internal:48080` 让容器访问到宿主机 yudao 后端; - macOS 上 host network(`network_mode: host`)需要 Docker Desktop 4.34+ 并在 Settings → Resources → Network 勾选「Enable host networking」,老版本静默失败(容器跑得起来但端口完全不通); - Linux:可以把 `docker-compose.yml` 改成 `network_mode: host` + 删 `ports:` 段,并把 `livekit.yaml` 的 webhook URL 改为 `http://127.0.0.1:48080/admin-api/im/livekit/webhook`。 diff --git a/script/livekit-poc/livekit.yaml b/script/livekit-poc/livekit.yaml index 0dd255ebf2..8e52aca46a 100644 --- a/script/livekit-poc/livekit.yaml +++ b/script/livekit-poc/livekit.yaml @@ -10,6 +10,8 @@ rtc: tcp_port: 7881 udp_port: 7882 use_external_ip: false + # macOS / Windows 端口映射模式下显式广播宿主机回环地址,避免把容器 172.x 地址发给浏览器 + node_ip: 127.0.0.1 # Webhook:成员离开 / 房间结束等事件回调到 yudao 后端做业务态兜底清理 # host.docker.internal 让容器访问宿主机 macOS / Windows 上的 yudao 后端 diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitClient.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitClient.java index 9aea1839c2..2d1b2dc3b4 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitClient.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitClient.java @@ -208,17 +208,21 @@ public class LiveKitClient { /** * 签发管理 Token;用于调 Server API(DeleteRoom / ListParticipants / RemoveParticipant 等) * + * @param room 房间名;LiveKit 的 roomAdmin 权限必须限定到具体房间 * @return JWT 字符串 */ - private String signAdminToken() { + private String signAdminToken(String room) { ImProperties.Rtc cfg = imProperties.getRtc(); // roomAdmin claim 给管理类 API 必备 + Map video = new HashMap<>(); + video.put("roomAdmin", true); + video.put("room", room); long nowSec = Instant.now().getEpochSecond(); return JWT.create() .setIssuer(cfg.getApiKey()) .setNotBefore(new Date(nowSec * 1000)) .setExpiresAt(new Date((nowSec + ADMIN_TOKEN_TTL.getSeconds()) * 1000)) - .setPayload("video", MapUtil.of("roomAdmin", true)) + .setPayload("video", video) .setSigner(JWTSignerUtil.hs256(cfg.getApiSecret().getBytes(StandardCharsets.UTF_8))) .sign(); } @@ -232,7 +236,7 @@ public class LiveKitClient { */ private HttpResponse postTwirp(String path, String room) { Assert.notBlank(room, "room 不可为空"); - String token = signAdminToken(); + String token = signAdminToken(room); return HttpRequest.post(HttpUtils.wsUrlToHttp(imProperties.getRtc().getLivekitUrl()) + path) .header("Authorization", "Bearer " + token) .header("Content-Type", "application/json")