refactor(viewer): profile rerun loading by playback stage

This commit is contained in:
DCCONSTRUCTIONS
2026-08-29 17:46:07 +03:00
parent 856b61be99
commit c06b709fd7
21 changed files with 1313 additions and 609 deletions
@@ -12,6 +12,7 @@ import type {
RecordedCameraAdmissionState,
} from "../core/observation/recordedSessionAdmission";
import { liveRerunRecoveryAuthorityIdentity } from "../core/observation/liveReceiverWatchdog";
import { liveAcquisitionRerunProfile, recordedSessionRerunProfile } from "../core/observation/viewerProfile";
import type { ObservationSourceDescriptor } from "../core/runtime/contracts";
import {
RerunViewport,
@@ -46,7 +47,6 @@ function statusTone(status: CapabilityStatus): "success" | "accent" | "warning"
if (status === "contract") return "warning";
return "neutral";
}
function FeatureInventory({ definition }: { definition: WorkspaceDefinition }) {
return (
<div className="feature-inventory">
@@ -76,7 +76,6 @@ function FeatureInventory({ definition }: { definition: WorkspaceDefinition }) {
</div>
);
}
function WorkspaceLead({ definition, note }: { definition: WorkspaceDefinition; note?: string }) {
return (
<section className="workspace-lead workspace-lead--compact">
@@ -89,7 +88,6 @@ function WorkspaceLead({ definition, note }: { definition: WorkspaceDefinition;
</section>
);
}
function EmptySpatialStage({ settings }: { settings: SceneSettings }) {
return (
<div className="empty-spatial-stage" data-grid={settings.showGrid ? "true" : undefined}>
@@ -181,7 +179,7 @@ function SpatialWorkspace({
);
const recordedPerceptionSupported =
recordedSource && perceptionLoad.phase !== "unavailable";
const recordedPerceptionReady = recordedSource && perceptionLoad.phase === "ready";
const recordedPerceptionLoading = recordedSource && perceptionLoad.phase === "loading";
const recordedPerceptionEnabled =
showDetections2d || showSegmentation || showCuboids3d;
// The native recorded camera remains the authoritative original. Only 2D
@@ -261,7 +259,7 @@ function SpatialWorkspace({
const shouldPrepareRecordedSource = useCallback((sourceId: string) => {
if (!recordedSessionAdmission) return false;
return recordedSessionAdmission.activeCameraSourceIds.has(sourceId) ||
recordedSessionAdmission.cameras[sourceId]?.phase === "ready";
["ready", "error"].includes(recordedSessionAdmission.cameras[sourceId]?.phase ?? "loading");
}, [recordedSessionAdmission]);
const onSelectionChange = useCallback((next: RerunSelection | null) => setSelection(next), []);
const onPlaybackChange = useCallback(
@@ -381,6 +379,35 @@ function SpatialWorkspace({
: presentedViewerStatus === "error"
? "danger"
: "neutral";
const rerunViewerProfile = recordedSource
? recordedSessionRerunProfile({
sourceUrl,
artifact: recordedReplay,
autoplayWhenReady: true,
presentationGate: recordedSessionGate,
expectedTimelineStartSeconds: state?.observationTimeline?.range?.startSeconds,
expectedTimelineEndSeconds: state?.observationTimeline?.range?.endSeconds,
initialPlaybackStartSeconds: initialRecordedPlaybackStartSeconds,
view: "spatial",
viewResetGeneration: recordedViewResetGeneration,
followTrajectory: followRecordedTrajectory,
perceptionLayers: {
enabled: recordedPerceptionSupported && recordedPerceptionEnabled,
detections2d: showDetections2d,
segmentation: showSegmentation,
cuboids3d: showCuboids3d,
},
perceptionRetryGeneration,
lockPerceptionCameraInteraction: unifiedPerception,
})
: liveAcquisitionRerunProfile({
sourceUrl,
liveActivitySequence: livePresentationActivitySequence,
liveStreamId: state?.spatialSource?.id ?? null,
liveRecoveryAuthorityIdentity: streamActive
? liveRerunRecoveryAuthorityIdentity(pointCloudSource, state?.spatialSource)
: null,
});
return (
<div
@@ -405,7 +432,7 @@ function SpatialWorkspace({
variant={detections2dActive ? "primary" : "secondary"}
icon={<Icon name="target" />}
aria-pressed={detections2dActive}
disabled={recordedSource && !recordedPerceptionReady}
disabled={recordedPerceptionLoading}
onClick={() => recordedSource
? setShowDetections2d((current) => !current)
: onLivePerceptionLayersChange({
@@ -420,7 +447,7 @@ function SpatialWorkspace({
variant={segmentationActive ? "primary" : "secondary"}
icon={<Icon name="image" />}
aria-pressed={segmentationActive}
disabled={recordedSource && !recordedPerceptionReady}
disabled={recordedPerceptionLoading}
onClick={() => recordedSource
? setShowSegmentation((current) => !current)
: onLivePerceptionLayersChange({
@@ -435,7 +462,7 @@ function SpatialWorkspace({
variant={cuboids3dActive ? "primary" : "secondary"}
icon={<Icon name="apps" />}
aria-pressed={cuboids3dActive}
disabled={recordedSource && !recordedPerceptionReady}
disabled={recordedPerceptionLoading}
onClick={() => recordedSource
? setShowCuboids3d((current) => !current)
: onLivePerceptionLayersChange({
@@ -489,35 +516,8 @@ function SpatialWorkspace({
>
{sourceUrl.trim() && pointCloudVisible && !intentionalSourceEnd ? (
<RerunViewport
sourceUrl={sourceUrl}
recordedArtifact={recordedSource ? recordedReplay : null}
followLive={liveRerunSource}
liveActivitySequence={livePresentationActivitySequence}
liveStreamId={state?.spatialSource?.id}
liveRecoveryAuthorityIdentity={!recordedSource && streamActive ? liveRerunRecoveryAuthorityIdentity(pointCloudSource, state?.spatialSource) : null}
autoplayWhenReady={recordedSource}
presentationGate={recordedSessionGate}
expectedTimelineStartSeconds={recordedSource
? state?.observationTimeline?.range?.startSeconds
: undefined}
expectedTimelineEndSeconds={recordedSource
? state?.observationTimeline?.range?.endSeconds
: undefined}
initialPlaybackStartSeconds={initialRecordedPlaybackStartSeconds}
profile={rerunViewerProfile}
sceneSettings={sceneSettings}
recordedViewResetGeneration={recordedViewResetGeneration}
recordedFollowTrajectory={followRecordedTrajectory}
recordedPerceptionLayers={{
enabled:
recordedPerceptionSupported &&
recordedPerceptionReady &&
recordedPerceptionEnabled,
detections2d: showDetections2d,
segmentation: showSegmentation,
cuboids3d: showCuboids3d,
}}
recordedPerceptionRetryGeneration={perceptionRetryGeneration}
lockPerceptionCameraInteraction={unifiedPerception}
onPerceptionLoadChange={onPerceptionLoadChange}
onPointColorLoadChange={onPointColorLoadChange}
onStatusChange={onStatusChange}
@@ -905,7 +905,7 @@ function CamerasWorkspace({
if (!recordedReplay) return true;
if (!recordedSessionAdmission) return false;
return recordedSessionAdmission.activeCameraSourceIds.has(sourceId) ||
recordedSessionAdmission.cameras[sourceId]?.phase === "ready";
["ready", "error"].includes(recordedSessionAdmission.cameras[sourceId]?.phase ?? "loading");
}, [recordedReplay, recordedSessionAdmission]);
return (
<div className="standard-workspace cameras-workspace" data-focused={focusedSource ? "true" : undefined}>
@@ -35,6 +35,7 @@ import {
type E47SemanticClass,
type E47SemanticTimelineFrame,
} from "../../core/laboratory/e47SemanticSlam";
import { laboratoryRecordedEvidenceDemand } from "../../core/laboratory/recordedEvidenceProfile";
import type {
M4ThreatCameraProposal,
M4ThreatTimelineFrame,
@@ -234,6 +235,26 @@ export function M4ReplayThreatVisual({
const activeSemantic = availableSemanticLayers.find(
(layer, index) => (layer.id ?? `${layer.resultId}:${index}`) === selectedSemanticLayerId,
) ?? availableSemanticLayers[0];
const evidenceDemand = useMemo(() => laboratoryRecordedEvidenceDemand({
mediaMode,
spatialMode,
showMediaSemantic: Boolean(activeSemantic) && showMediaSemantic,
showSpatialSemantic: Boolean(activeSemantic) && showSpatialSemantic,
showMediaPoints,
classifiedSpatialMode: !classifiedSpatialLayer
? "none"
: classifiedSpatialLayer.replacePointCloud
? "replace-source"
: "overlay",
}), [
activeSemantic,
classifiedSpatialLayer,
mediaMode,
showMediaPoints,
showMediaSemantic,
showSpatialSemantic,
spatialMode,
]);
const metricSceneRef = useRef<LaboratoryMetricEvidenceSceneHandle | null>(null);
const metadata = useM4ThreatTimelineMetadata(resultId, timelineEndpointRoot);
const playbackRange = useMemo(() => metadata.timeline ? ({
@@ -249,6 +270,7 @@ export function M4ReplayThreatVisual({
resultId,
timeline: metadata.timeline,
currentSeconds: playbackController.playback.currentSeconds,
includeSpatialPoints: evidenceDemand.sourceSpatialPoints,
endpointRoot: timelineEndpointRoot,
});
const [videoSource, setVideoSource] = useState<ObservationSourceDescriptor | null>(null);
@@ -270,6 +292,10 @@ export function M4ReplayThreatVisual({
useEffect(() => {
const timeline = metadata.timeline;
if (!evidenceDemand.recordedVideo) {
setVideoLoading(false);
return;
}
if (!timeline || videoSource) return;
const controller = new AbortController();
setVideoLoading(true);
@@ -304,7 +330,7 @@ export function M4ReplayThreatVisual({
if (!controller.signal.aborted) setVideoLoading(false);
});
return () => controller.abort();
}, [metadata.timeline, videoSource]);
}, [evidenceDemand.recordedVideo, metadata.timeline, videoSource]);
const lastFrameRef = useRef<M4ThreatTimelineFrame | null>(null);
useEffect(() => {
@@ -319,6 +345,9 @@ export function M4ReplayThreatVisual({
resultId: string;
frame: M4ThreatTimelineFrame;
} | null>(null);
useEffect(() => {
lastSpatialFrameRef.current = null;
}, [evidenceDemand.sourceSpatialPoints, resultId]);
if (frame?.spatialAvailable) {
lastSpatialFrameRef.current = { resultId, frame };
}
@@ -328,7 +357,7 @@ export function M4ReplayThreatVisual({
? lastSpatialFrameRef.current.frame
: null;
const cameraPointOverlay = useM4ThreatCameraPointOverlay({
enabled: showReferenceMediaLayers && showMediaPoints,
enabled: showReferenceMediaLayers && evidenceDemand.cameraPointOverlay,
resultId,
sequence: frame?.sequence ?? null,
endpointRoot: timelineEndpointRoot,
@@ -354,6 +383,7 @@ export function M4ReplayThreatVisual({
activeSequence: frame?.sequence ?? timelineFrame.activeSequence,
frameCount: metadata.timeline?.frameCount ?? 0,
taxonomy: spatialSemanticTaxonomy,
enabled: evidenceDemand.selectedSemanticPoints,
});
const displayingBufferedFrame = Boolean(
frame
@@ -647,7 +677,7 @@ export function M4ReplayThreatVisual({
},
), [metadata.timeline, spatialFrame, timelineFrame.availableFrames]);
const semanticOverlay: RecordedEvidenceSemanticOverlay | undefined =
activeSemantic && showMediaSemantic && frame
activeSemantic && evidenceDemand.selectedSemanticMask && frame
? {
src: activeSemantic.maskUrl?.(frame.sequence)
?? e47SemanticMaskUrl(activeSemantic.resultId, frame.sequence),
@@ -694,10 +724,14 @@ export function M4ReplayThreatVisual({
};
useEffect(() => {
if (playbackController.playback.playing || !frame) return;
if (
playbackController.playback.playing
|| !evidenceDemand.exactCameraFrame
|| !frame
) return;
const image = new Image();
image.src = frame.cameraUrl;
}, [frame?.cameraUrl, playbackController.playback.playing]);
}, [evidenceDemand.exactCameraFrame, frame?.cameraUrl, playbackController.playback.playing]);
const splitView = mediaMode !== null && spatialMode !== null;
@@ -11,11 +11,30 @@ const CHUNK_SIZE = 24;
const RETAINED_CHUNK_COUNT = 8;
const PREFETCH_CHUNKS_AHEAD = 2;
function chunkWindowStarts(activeStart: number, frameCount: number): readonly number[] {
return Array.from(
{ length: PREFETCH_CHUNKS_AHEAD + 2 },
(_, index) => activeStart + (index - 1) * CHUNK_SIZE,
).filter((start) => start >= 0 && start < frameCount);
export function e47SemanticChunkWindowStarts(
activeStart: number,
frameCount: number,
): readonly number[] {
return [
activeStart,
...Array.from(
{ length: PREFETCH_CHUNKS_AHEAD },
(_, index) => activeStart + (index + 1) * CHUNK_SIZE,
),
activeStart - CHUNK_SIZE,
].filter((start) => start >= 0 && start < frameCount);
}
export function cancelE47SemanticRequestsOutsideWindow<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);
}
}
function errorMessage(error: unknown): string {
@@ -29,11 +48,13 @@ export function useE47SemanticTimelineFrame({
activeSequence,
frameCount,
taxonomy,
enabled = true,
}: {
resultId: string | null;
activeSequence: number | null;
frameCount: number;
taxonomy: readonly E47SemanticClass[];
enabled?: boolean;
}) {
const [chunks, setChunks] = useState<ReadonlyMap<number, E47SemanticTimelineChunk>>(
() => new Map(),
@@ -55,16 +76,21 @@ export function useE47SemanticTimelineFrame({
for (const controller of inFlight.current.values()) controller.abort();
inFlight.current.clear();
};
}, [resultId]);
}, [enabled, resultId]);
const activeStart = activeSequence === null
const activeStart = !enabled || activeSequence === null
? null
: Math.floor(activeSequence / CHUNK_SIZE) * CHUNK_SIZE;
activeStartRef.current = activeStart;
useEffect(() => {
if (!resultId || activeStart === null || frameCount < 1) return;
for (const start of chunkWindowStarts(activeStart, frameCount)) {
if (!enabled || !resultId || activeStart === null || frameCount < 1) {
cancelE47SemanticRequestsOutsideWindow(inFlight.current, []);
return;
}
const starts = e47SemanticChunkWindowStarts(activeStart, frameCount);
cancelE47SemanticRequestsOutsideWindow(inFlight.current, starts);
for (const start of starts) {
if (chunksRef.current.has(start) || inFlight.current.has(start)) continue;
const controller = new AbortController();
inFlight.current.set(start, controller);
@@ -95,8 +121,11 @@ export function useE47SemanticTimelineFrame({
.finally(() => {
if (inFlight.current.get(start) === controller) inFlight.current.delete(start);
});
// Semantic point arrays are large JSON payloads. Admit the active chunk
// first, then advance the bounded prefetch window one request per render.
break;
}
}, [activeStart, frameCount, resultId, taxonomy]);
}, [activeStart, chunks, enabled, frameCount, resultId, taxonomy]);
const activeFrame: E47SemanticTimelineFrame | null = useMemo(() => {
if (activeSequence === null || activeStart === null) return null;
@@ -107,7 +136,7 @@ export function useE47SemanticTimelineFrame({
return {
activeFrame,
loading: Boolean(resultId) && activeSequence !== null && !activeFrame && !error,
loading: enabled && Boolean(resultId) && activeSequence !== null && !activeFrame && !error,
error,
};
}
@@ -76,11 +76,13 @@ export function useM4ThreatTimelineFrame({
resultId,
timeline,
currentSeconds,
includeSpatialPoints = true,
endpointRoot,
}: {
resultId: string;
timeline: M4ThreatTimeline | null;
currentSeconds: number;
includeSpatialPoints?: boolean;
endpointRoot?: string;
}) {
const [chunks, setChunks] = useState<ReadonlyMap<number, M4ThreatTimelineChunk>>(
@@ -107,7 +109,7 @@ export function useM4ThreatTimelineFrame({
setPlaybackError(null);
setPlaybackProgress({ phase: "manifest", loadedBytes: 0, totalBytes: 0 });
if (!timeline) return () => controller.abort();
if (!binaryPlayback) {
if (!binaryPlayback || !includeSpatialPoints) {
setPlaybackProgress({ phase: "ready", loadedBytes: 0, totalBytes: 0 });
return () => controller.abort();
}
@@ -127,7 +129,7 @@ export function useM4ThreatTimelineFrame({
}
});
return () => controller.abort();
}, [binaryPlayback, endpointRoot, resultId, timeline]);
}, [binaryPlayback, endpointRoot, includeSpatialPoints, resultId, timeline]);
useEffect(() => {
for (const controller of inFlight.current.values()) controller.abort();
@@ -140,7 +142,7 @@ export function useM4ThreatTimelineFrame({
for (const controller of inFlight.current.values()) controller.abort();
inFlight.current.clear();
};
}, [resultId, timeline]);
}, [includeSpatialPoints, resultId, timeline]);
const activeSequence = useMemo(
() => timeline
@@ -158,7 +160,11 @@ export function useM4ThreatTimelineFrame({
activeChunkStartRef.current = activeChunkStart;
useEffect(() => {
if (!timeline || activeChunkStart === null || (binaryPlayback && !playbackManifest)) return;
if (
!timeline
|| activeChunkStart === null
|| (binaryPlayback && includeSpatialPoints && !playbackManifest)
) return;
const starts = m4ThreatChunkWindowStarts(
activeChunkStart,
chunkSize,
@@ -170,7 +176,7 @@ export function useM4ThreatTimelineFrame({
const controller = new AbortController();
inFlight.current.set(start, controller);
void (async () => {
const playbackPointPack = binaryPlayback && playbackManifest
const playbackPointPack = binaryPlayback && includeSpatialPoints && playbackManifest
? await fetchM4ThreatPlaybackPointChunk(
playbackManifest,
Math.floor(start / playbackManifest.chunkFrameCount),
@@ -189,6 +195,7 @@ export function useM4ThreatTimelineFrame({
endpointRoot,
cameraObstacleProjectionDelivery: timeline.cameraObstacleProjectionDelivery,
playbackPointPack,
includePoints: includeSpatialPoints,
});
})()
.then((chunk) => {
@@ -220,7 +227,7 @@ export function useM4ThreatTimelineFrame({
// loaded first, then the next chunk is prefetched on the following render.
break;
}
}, [activeChunkStart, binaryPlayback, chunkSize, chunks, endpointRoot, playbackManifest, resultId, timeline]);
}, [activeChunkStart, binaryPlayback, chunkSize, chunks, endpointRoot, includeSpatialPoints, playbackManifest, resultId, timeline]);
const activeFrame: M4ThreatTimelineFrame | null = useMemo(() => {
if (activeSequence === null || activeChunkStart === null) return null;