Commit e509c8a95825cea371b3271752bdb1f689955165

Authored by YunaiV
1 parent 2558d8c8

fix(im): 修复 LiveKit 本地媒体连接与房间管理权限

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