import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { after, before, test } from "node:test"; import { createServer } from "vite"; let server; let fetchRecordedMediaArchive; let recordedMediaPresentationState; let recordedMediaSeekableCoverage; let recordedMediaFragmentUrl; let recordedMediaDecodeStartSequence; let recordedMediaSegmentAppendOrder; let recordedMediaSegmentSequenceAtTime; let recordedMediaCanRollTarget; let recordedMediaTimestampStallRecoveryTarget; let nextRecordedMediaRandomAccessSequence; let recordedMediaRecoveryTargetSequence; let selectRecordedMediaPreparationEpoch; before(async () => { server = await createServer({ appType: "custom", logLevel: "silent", server: { middlewareMode: true }, }); ({ fetchRecordedMediaArchive, recordedMediaPresentationState, recordedMediaSeekableCoverage, recordedMediaFragmentUrl, recordedMediaDecodeStartSequence, recordedMediaSegmentAppendOrder, recordedMediaSegmentSequenceAtTime, recordedMediaCanRollTarget, recordedMediaTimestampStallRecoveryTarget, nextRecordedMediaRandomAccessSequence, recordedMediaRecoveryTargetSequence, selectRecordedMediaPreparationEpoch, } = await server.ssrLoadModule("/src/components/RecordedFmp4Player.tsx")); }); after(async () => { await server?.close(); }); function fixture({ byteLength = 36_000_000_000 } = {}) { const segmentCount = 1_501; const manifestUrl = "/api/v1/observation-sessions/session-1/media/camera-1/manifest"; const generation = "a".repeat(64); const streamUrl = manifestUrl.replace( "/manifest", `/epochs/1/recording.mp4?generation=${generation}`, ); const manifest = { schema_version: "missioncore.observation-recorded-media/v4", source_id: "recorded.camera.camera-1", generation_sha256: generation, byte_length: byteLength, timeline_start_seconds: 0, timeline_end_seconds: 36_000, synchronization: "host-arrival-best-effort", epochs: [{ ordinal: 1, timeline_start_seconds: 0, timeline_end_seconds: 36_000, media_type: 'video/mp4; codecs="avc1.640028"', byte_length: byteLength, stream_url: streamUrl, segment_count: segmentCount, random_access_sequences: [1, 1491, 1501], segment_end_times_seconds: Array.from( { length: segmentCount }, (_value, index) => ((index + 1) * 36_000) / segmentCount, ), }], }; const source = { id: manifest.source_id, label: "Записанная камера", modality: "video", manifestUrl, manifestGenerationSha256: generation, byteLength, mediaType: "video/mp4", timelineStartSeconds: 0, timelineEndSeconds: 36_000, seekable: true, synchronization: "host-arrival-best-effort", }; return { source, manifest, generation, streamUrl }; } function jsonResponse(payload, generation) { return new Response(JSON.stringify(payload), { status: 200, headers: { "Content-Type": "application/json", ETag: `"sha256:${generation}"`, }, }); } test("multi-hour camera admission fetches only its compact generation-bound manifest", async () => { const { source, manifest, generation, streamUrl } = fixture(); const requested = []; const archive = await fetchRecordedMediaArchive(source, { fetcher: async (input, request = {}) => { requested.push(String(input)); assert.equal(new Headers(request.headers).get("If-Match"), `"sha256:${generation}"`); return jsonResponse(manifest, generation); }, }); assert.deepEqual(requested, [source.manifestUrl]); assert.equal(archive.byteLength, 36_000_000_000); assert.equal(archive.manifest.epochs[0].streamUrl, streamUrl); assert.equal(archive.manifest.epochs[0].segmentCount, 1_501); assert.deepEqual(archive.manifest.epochs[0].randomAccessSequences, [1, 1491, 1501]); }); test("first camera manifest request rejects a replaced generation", async () => { const { source, manifest, generation } = fixture(); const replacementGeneration = "d".repeat(64); await assert.rejects( fetchRecordedMediaArchive(source, { fetcher: async (_input, request = {}) => { assert.equal(new Headers(request.headers).get("If-Match"), `"sha256:${generation}"`); return jsonResponse( { ...manifest, generation_sha256: replacementGeneration }, replacementGeneration, ); }, }), /несовместим|заменён/, ); }); test("camera manifest fails closed when epoch bytes do not match launch bytes", async () => { const { source, manifest, generation } = fixture(); const mismatched = { ...manifest, epochs: [{ ...manifest.epochs[0], byte_length: manifest.byte_length - 1 }], }; await assert.rejects( fetchRecordedMediaArchive(source, { fetcher: async () => jsonResponse(mismatched, generation), }), /не совпадает/, ); }); test("camera presentation gate opens only for the seekable selected epoch", () => { assert.equal(recordedMediaPresentationState("loading", null, "g1", false), "loading"); assert.equal(recordedMediaPresentationState("ready", null, "g1", false), "loading"); assert.equal(recordedMediaPresentationState("ready", "g2", "g1", false), "loading"); assert.equal(recordedMediaPresentationState("ready", "g1", "g1", false), "ready"); assert.equal(recordedMediaPresentationState("ready", "g1", null, true), "waiting"); assert.equal(recordedMediaPresentationState("error", "g1", "g1", false), "error"); assert.equal(recordedMediaPresentationState("ready", "g1", "g1", false, "loading"), "loading"); assert.equal(recordedMediaPresentationState("ready", "g1", null, true, "loading"), "loading"); assert.equal(recordedMediaPresentationState("ready", "g1", "g1", false, "error"), "error"); }); test("decoded duration and seekable range cover the complete declared epoch", () => { assert.equal(recordedMediaSeekableCoverage(20, 20, 20), true); assert.equal(recordedMediaSeekableCoverage(19, 19, 20), true); assert.equal(recordedMediaSeekableCoverage(18.99, 20, 20), false); assert.equal(recordedMediaSeekableCoverage(20, 18.99, 20), false); assert.equal(recordedMediaSeekableCoverage(20, 20, 20, 1, 1), true); assert.equal(recordedMediaSeekableCoverage(20, 20, 20, 1, 1.01), false); }); test("production replay derives bounded fragments and retains native range fallback", async () => { const source = await readFile( new URL("../src/components/RecordedFmp4Player.tsx", import.meta.url), "utf8", ); assert.match(source, /video\.src\s*=\s*descriptor\.streamUrl/); assert.doesNotMatch(source, /new Blob\(/); assert.match(source, /new MediaSource\(\)/); assert.match(source, /RECORDED_MEDIA_SEGMENTS_AHEAD = 36/); assert.match(source, /pumpRecordedSegmentWindow/); assert.match(source, /waitForRecordedVideoTarget/); assert.match(source, /removeRecordedMediaRange/); assert.match(source, /hasPresentedFrame/); assert.match(source, /retainForwardFrame/); assert.match(source, /candidateTarget\.sequence >= previousTarget\.sequence/); assert.match(source, /effectiveSegmentCount = segmentCount \?\? epoch\?\.segmentCount \?\? null/); assert.match(source, /requestedSegmentSequence = segmentSequence \?\?/); assert.match(source, /waitForRecordedVideoInitialFrame/); assert.match(source, /setSegmentRecoveryGeneration/); assert.match(source, /resumePlaybackIfRequested/); assert.match(source, /!playbackPlayingRef\.current/); assert.match(source, /await video\.play\(\)/); assert.equal(recordedMediaDecodeStartSequence([1, 1491, 1501], 1500), 1491); assert.deepEqual( recordedMediaSegmentAppendOrder(new Set([1491, 1492]), 1491, 1500), [1493, 1494, 1495, 1496, 1497, 1498, 1499, 1500], ); const { manifest, generation } = fixture(); const epoch = { ordinal: 1, timelineStartSeconds: 0, timelineEndSeconds: 36_000, mediaType: manifest.epochs[0].media_type, byteLength: manifest.epochs[0].byte_length, streamUrl: manifest.epochs[0].stream_url, segmentCount: manifest.epochs[0].segment_count, randomAccessSequences: manifest.epochs[0].random_access_sequences, segmentEndTimesSeconds: manifest.epochs[0].segment_end_times_seconds, }; assert.equal( recordedMediaFragmentUrl(epoch, generation, "init"), `/api/v1/observation-sessions/session-1/media/camera-1/epochs/1/init.mp4?generation=${generation}`, ); assert.equal( recordedMediaFragmentUrl(epoch, generation, 17), `/api/v1/observation-sessions/session-1/media/camera-1/epochs/1/segments/17.m4s?generation=${generation}`, ); assert.throws( () => recordedMediaFragmentUrl(epoch, "b".repeat(64), 17), /не привязан/, ); }); test("production replay derives its bounded fragment directly from the source clock", () => { const ends = [0.101, 0.185, 0.286, 0.401]; const epochStart = 39.215263458; assert.equal(recordedMediaSegmentSequenceAtTime(ends, epochStart, epochStart), 1); assert.equal(recordedMediaSegmentSequenceAtTime(ends, epochStart, epochStart + 0.101), 1); assert.equal(recordedMediaSegmentSequenceAtTime(ends, epochStart, epochStart + 0.103), 2); assert.equal(recordedMediaSegmentSequenceAtTime(ends, epochStart, epochStart + 0.4), 4); assert.equal(recordedMediaSegmentSequenceAtTime(ends, epochStart, epochStart + 1), null); }); test("a corrupt fragment advances recovery to the next random-access frame", () => { assert.equal(nextRecordedMediaRandomAccessSequence([1, 11, 21, 49], 1), 11); assert.equal(nextRecordedMediaRandomAccessSequence([1, 11, 21, 49], 20), 21); assert.equal(nextRecordedMediaRandomAccessSequence([1, 11, 21, 49], 49), null); assert.equal(nextRecordedMediaRandomAccessSequence([1, 11, 21, 49], 0), null); }); test("paused seek displays the recovery keyframe until the source clock leaves the corrupt GOP", () => { assert.equal(recordedMediaRecoveryTargetSequence(120, 120, 149), 149); assert.equal(recordedMediaRecoveryTargetSequence(130, 120, 149), 149); assert.equal(recordedMediaRecoveryTargetSequence(119, 120, 149), 119); assert.equal(recordedMediaRecoveryTargetSequence(149, 120, 149), 149); assert.equal(recordedMediaRecoveryTargetSequence(151, 120, 149), 151); assert.equal(recordedMediaRecoveryTargetSequence(null, 120, 149), null); assert.equal(recordedMediaRecoveryTargetSequence(120, null, null), 120); }); test("camera admission selects one bounded epoch while the shared clock is outside video", () => { const epochs = [ { ordinal: 1, timelineStartSeconds: 39, timelineEndSeconds: 100 }, { ordinal: 2, timelineStartSeconds: 120, timelineEndSeconds: 180 }, ]; assert.equal(selectRecordedMediaPreparationEpoch(epochs, 0).ordinal, 1); assert.equal(selectRecordedMediaPreparationEpoch(epochs, 70).ordinal, 1); assert.equal(selectRecordedMediaPreparationEpoch(epochs, 110).ordinal, 2); assert.equal(selectRecordedMediaPreparationEpoch(epochs, 200).ordinal, 2); }); test("recorded player preserves forward rolling playback but seeks backward clip loops", () => { assert.equal(recordedMediaCanRollTarget(20, 21, true, true), true); assert.equal(recordedMediaCanRollTarget(20, 20, true, true), true); assert.equal(recordedMediaCanRollTarget(20, 1, true, true), false); assert.equal(recordedMediaCanRollTarget(20, 21, false, true), false); assert.equal(recordedMediaCanRollTarget(20, 21, true, false), false); }); test("recorded player skips only a proven buffered corrupt timestamp interval", () => { assert.equal(recordedMediaTimestampStallRecoveryTarget(11.422, [[0, 16.287]]), 11.602); assert.equal(recordedMediaTimestampStallRecoveryTarget(16.25, [[0, 16.287]]), null); assert.equal(recordedMediaTimestampStallRecoveryTarget(20, [[0, 16.287]]), null); }); test("loading and error overlays fully conceal recorded camera pixels", async () => { const css = await readFile( new URL("../../../packages/spatial-ui/src/observation.css", import.meta.url), "utf8", ); assert.match( css, /\.recorded-media-player:not\(\[data-state="ready"\]\) \.observation-media__asset\s*\{[^}]*visibility:\s*hidden/s, ); assert.match( css, /\.recorded-media-player__notice\s*\{[^}]*inset:\s*0;[^}]*background:\s*#070809/s, ); }); test("point-cloud fullscreen keeps the admitted recorded camera worker mounted", async () => { const source = await readFile( new URL("../src/workspaces/Workspaces.tsx", import.meta.url), "utf8", ); assert.match(source, /\{visibleMediaSources\.map\(\(source, index\) => \(/); assert.match(source, /hidden=\{pointCloudFocused \|\| unifiedPerception\}/); assert.doesNotMatch( source, /\{!pointCloudFocused \? visibleMediaSources\.map/, ); assert.doesNotMatch( source, /\(pointCloudFocused \|\| !observationLayout\.visibleSourceIds\.has\(source\.id\)\)/, ); });