Browse Source

fix(cockpit): 实时观看接受不含 deviceCode 的会话响应

后端 CockpitVideoService.preparedResult 只返回 sessionId/status/mode/media,
不含 deviceCode;原判据要求它必须等于当前设备,导致真实链路被误判为「会话与设备不匹配」。
改为:deviceCode 存在且不一致时才拒绝,缺失时按 mode 与后续 status 校验。
补 contract.mjs 锁定真实 prepared-live 载荷可正常起播。
ChenJZ 10 giờ trước cách đây
mục cha
commit
dcbe5691c3

+ 4 - 1
src/modules/cockpit/services/videoLiveSession.js

@@ -116,7 +116,10 @@ export function createVideoLiveSession(api, update, timers = globalThis) {
                 attempt.identity.released = true; clear(attempt); current = null
                 publish({ media: null, phase: 'failed', error: '观看会话未建立或已停止' }); return
             }
-            if (data.deviceCode !== deviceCode || data.mode !== 'LIVE') throw new VideoRequestError('INVALID_RESPONSE', '返回的播放会话与当前设备不匹配')
+            if (data.mode !== 'LIVE') throw new VideoRequestError('INVALID_RESPONSE', '返回的播放会话与当前设备不匹配')
+            // 后端 preparedResult 只回 sessionId/status/mode/media;deviceCode 可选,只有在确实回了且不一致时才算不匹配。
+            if (data.deviceCode !== undefined && data.deviceCode !== deviceCode)
+                throw new VideoRequestError('INVALID_RESPONSE', '返回的播放会话与当前设备不匹配')
             if (data.status !== 'ACTIVE') throw new VideoRequestError('SESSION_PENDING', '媒体尚未就绪,结束本次观看')
             attempt.media = selectVideoMedia(data.media)
             timers.clearTimeout(attempt.startTimer)

+ 26 - 0
tests/cockpit-video/contract.mjs

@@ -0,0 +1,26 @@
+import test from 'node:test'
+import assert from 'node:assert/strict'
+import { createVideoLiveSession } from '../../src/modules/cockpit/services/videoLiveSession.js'
+
+// 后端 CockpitVideoService.preparedResult 只回 sessionId/status/mode/media,
+// 不含 deviceCode。真实链路必须能接受这个契约,不能误判成"设备不匹配"。
+const prepared = (code) => ({ deviceCode: code, sessionId: 'session-x', sessionToken: 'owner-x', mode: 'LIVE', status: 'PREPARED' })
+const liveWithoutDeviceCode = { sessionId: 'session-x', status: 'ACTIVE', mode: 'LIVE',
+    media: { native: true, url: 'https://media.invalid/live.mp4' } }
+
+test('the real prepared-live payload without deviceCode still starts one active video', async () => {
+    const updates = [], releases = []
+    const live = createVideoLiveSession({
+        prepare: async (code) => prepared(code),
+        start: async () => liveWithoutDeviceCode,
+        status: async () => liveWithoutDeviceCode,
+        heartbeat: async () => {},
+        stop: async (session) => { releases.push(session.sessionId); return { sessionId: session.sessionId, status: 'STOPPED' } },
+    }, (patch) => updates.push(patch))
+    try {
+        await live.start('CAM/契约')
+        assert.ok(updates.some((patch) => patch.media?.url === 'https://media.invalid/live.mp4'),
+            '真实后端不回 deviceCode 时也必须进入播放,而不是被判为设备不匹配')
+        assert.deepEqual(releases, [], '正常启动不得触发释放')
+    } finally { await live.dispose() }
+})