fix(viewer): make recorded layers deterministic

This commit is contained in:
DCCONSTRUCTIONS
2026-07-23 11:42:19 +03:00
parent 3a64f54dea
commit ee15160862
6 changed files with 222 additions and 34 deletions
@@ -49,6 +49,7 @@ export interface RerunViewportProps {
presentationGate?: RecordedAdmissionPhase;
expectedTimelineStartSeconds?: number;
expectedTimelineEndSeconds?: number;
initialPlaybackStartSeconds?: number;
onStatusChange?: (status: RerunViewportStatus, message?: string) => void;
onSelectionChange?: (selection: RerunSelection | null) => void;
onPlaybackChange?: (state: RerunPlaybackState | null) => void;
@@ -295,12 +296,21 @@ export function createRecordedAutoplayGate(): {
rangeNs: { min: number; max: number } | null,
seekToStart: (startNs: number) => void,
startPlaying: () => void,
preferredStartNs?: number,
) => boolean;
attempted: () => boolean;
} {
let consumed = false;
return {
attempt(viewerStarted, fullyBuffered, presentationReady, rangeNs, seekToStart, startPlaying) {
attempt(
viewerStarted,
fullyBuffered,
presentationReady,
rangeNs,
seekToStart,
startPlaying,
preferredStartNs,
) {
if (
consumed ||
!viewerStarted ||
@@ -309,8 +319,11 @@ export function createRecordedAutoplayGate(): {
!isUsableRecordedPlaybackRange(rangeNs)
) return false;
consumed = true;
const startNs = Number.isFinite(preferredStartNs)
? Math.min(Math.max(preferredStartNs as number, rangeNs.min), rangeNs.max)
: rangeNs.min;
return attemptRecordedAutoplay(
() => seekToStart(rangeNs.min),
() => seekToStart(startNs),
startPlaying,
);
},
@@ -515,7 +528,8 @@ export async function fetchRecordedBlueprintRrd(
custom_color: settings.customColor,
active_view: activeView,
view_reset_generation: viewResetGeneration,
unified_perception: perceptionLayers.enabled,
unified_perception:
perceptionLayers.detections2d || perceptionLayers.segmentation,
show_detections_2d: perceptionLayers.detections2d,
show_segmentation: perceptionLayers.segmentation,
show_cuboids_3d: perceptionLayers.cuboids3d,
@@ -616,6 +630,7 @@ export function RerunViewport({
presentationGate = "ready",
expectedTimelineStartSeconds,
expectedTimelineEndSeconds,
initialPlaybackStartSeconds,
onStatusChange,
onSelectionChange,
onPlaybackChange,
@@ -982,6 +997,9 @@ export function RerunViewport({
() => {
viewer.set_playing(event.recording_id, true);
},
initialPlaybackStartSeconds === undefined
? undefined
: initialPlaybackStartSeconds * 1_000_000_000,
);
if (autoplayStarted) {
playing = true;
@@ -1153,6 +1171,7 @@ export function RerunViewport({
expectedTimelineEndSeconds,
expectedTimelineStartSeconds,
followLive,
initialPlaybackStartSeconds,
onPlaybackChange,
onPlaybackControllerChange,
onSelectionChange,
@@ -336,10 +336,13 @@ function SpatialWorkspace({
(source) => source.capabilities.overlay && source.modality !== "point-cloud",
);
const recordedPerceptionSupported = recordedSource && perceptionAvailability !== "unavailable";
// Keep one stable original-video + world composition for every admitted
// perception recording. Layer buttons then only change entity visibility;
// they never reparent the 3D view or invalidate the operator's camera.
const unifiedPerception = recordedPerceptionSupported;
const recordedPerceptionEnabled =
showDetections2d || showSegmentation || showCuboids3d;
// The native recorded camera remains the authoritative original. Only 2D
// image-space overlays need Rerun's paired camera/world composition; 3D
// cuboids are added directly to the stable spatial view.
const unifiedPerception = recordedPerceptionSupported &&
(showDetections2d || showSegmentation);
const livePerceptionAvailable = !recordedSource && streamActive;
const detections2dActive = recordedSource
? showDetections2d
@@ -354,6 +357,16 @@ function SpatialWorkspace({
observationLayout.visibleSourceIds.has(source.id) &&
!source.id.startsWith("recorded.perception."),
);
const initialRecordedPlaybackStartSeconds = recordedSource
? mediaSources.reduce<number | undefined>((earliest, source) => {
if (
source.id.startsWith("recorded.perception.") ||
source.delivery?.kind !== "recorded-fmp4-manifest"
) return earliest;
const start = source.delivery.timelineStartSeconds;
return earliest === undefined ? start : Math.min(earliest, start);
}, undefined)
: undefined;
const presentedMediaSourceCount = unifiedPerception ? 0 : visibleMediaSources.length;
const pointCloudFocused = Boolean(
pointCloudSource && observationLayout.focusedSourceId === pointCloudSource.id,
@@ -574,10 +587,11 @@ function SpatialWorkspace({
expectedTimelineEndSeconds={recordedSource
? state?.observationTimeline?.range?.endSeconds
: undefined}
initialPlaybackStartSeconds={initialRecordedPlaybackStartSeconds}
sceneSettings={sceneSettings}
recordedViewResetGeneration={recordedViewResetGeneration}
recordedPerceptionLayers={{
enabled: unifiedPerception,
enabled: recordedPerceptionSupported && recordedPerceptionEnabled,
detections2d: showDetections2d,
segmentation: showSegmentation,
cuboids3d: showCuboids3d,