fix(lab): make recorded replay seek-safe
This commit is contained in:
@@ -37,7 +37,7 @@ import type {
|
||||
} from "../../core/laboratory/m4ReplayThreat";
|
||||
import { buildM4LocalSurface } from "../../core/laboratory/m4LocalSurface";
|
||||
import { recordedObservationSources } from "../../core/observation/recordedObservationSources";
|
||||
import { replayObservationSession } from "../../core/observation/sessionArchive";
|
||||
import { resolveObservationSessionReplay } from "../../core/observation/useObservationSessions";
|
||||
import type { ObservationSourceDescriptor } from "../../core/runtime/contracts";
|
||||
import {
|
||||
useM4ThreatTimelineFrame,
|
||||
@@ -148,14 +148,11 @@ export function M4ReplayThreatVisual({
|
||||
const controller = new AbortController();
|
||||
setVideoLoading(true);
|
||||
setVideoError(null);
|
||||
void replayObservationSession(timeline.recordedSourceSessionId, {
|
||||
void resolveObservationSessionReplay(timeline.recordedSourceSessionId, {
|
||||
signal: controller.signal,
|
||||
})
|
||||
.then((replay) => {
|
||||
if (replay.kind !== "ready") {
|
||||
throw new Error("RIGHT-видео RAVNOVES00 ещё готовится к воспроизведению.");
|
||||
}
|
||||
const source = recordedObservationSources(replay.launch).find(
|
||||
.then((launch) => {
|
||||
const source = recordedObservationSources(launch).find(
|
||||
(candidate) => candidate.modality === "video"
|
||||
&& candidate.semanticChannelId === "camera.video.recorded",
|
||||
);
|
||||
@@ -532,9 +529,14 @@ export function M4ReplayThreatVisual({
|
||||
semanticOverlay={mediaMode === "video" ? semanticOverlay : undefined}
|
||||
ariaLabel={`M4.6 recorded-realtime frame ${frame?.sequence ?? 0}: ${activeBoxes.length} proposals`}
|
||||
interactive={false}
|
||||
segmentSequence={frame ? frame.sequence + 1 : undefined}
|
||||
segmentSequence={
|
||||
timelineFrame.activeSequence === null
|
||||
? undefined
|
||||
: timelineFrame.activeSequence + 1
|
||||
}
|
||||
segmentCount={timeline.frameCount}
|
||||
onPlaybackChange={playbackController.synchronize}
|
||||
onPlayingRejected={() => playbackController.setPlaying(false)}
|
||||
/>
|
||||
) : videoError ? (
|
||||
<SpatialState message={videoError} />
|
||||
|
||||
@@ -29,6 +29,18 @@ export function m4ThreatChunkWindowStarts(
|
||||
).filter((start) => start >= 0 && start < frameCount);
|
||||
}
|
||||
|
||||
export function cancelM4ThreatChunkRequestsOutsideWindow<T extends { abort(): void }>(
|
||||
inFlight: Map<number, T>,
|
||||
desiredStarts: readonly number[],
|
||||
): void {
|
||||
const desired = new Set(desiredStarts);
|
||||
for (const [start, controller] of inFlight) {
|
||||
if (desired.has(start)) continue;
|
||||
controller.abort();
|
||||
inFlight.delete(start);
|
||||
}
|
||||
}
|
||||
|
||||
export function useM4ThreatTimelineMetadata(resultId: string) {
|
||||
const [timeline, setTimeline] = useState<M4ThreatTimeline | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -105,6 +117,7 @@ export function useM4ThreatTimelineFrame({
|
||||
chunkSize,
|
||||
timeline.frameCount,
|
||||
);
|
||||
cancelM4ThreatChunkRequestsOutsideWindow(inFlight.current, starts);
|
||||
for (const start of starts) {
|
||||
if (chunksRef.current.has(start) || inFlight.current.has(start)) continue;
|
||||
const controller = new AbortController();
|
||||
|
||||
Reference in New Issue
Block a user