fix(lab): запускать RAV004 с первого кадра
This commit is contained in:
@@ -34,7 +34,9 @@ import {
|
||||
RECORDED_BASE_POINT_COLOR_KEY,
|
||||
canPublishRecordedPlaybackController,
|
||||
createRecordedAutoplayGate,
|
||||
createRecordedInitialSeekGate,
|
||||
createRecordedOpenWatchdog,
|
||||
shouldReapplyRecordedBlueprint,
|
||||
} from "../core/observation/recordedRerunLifecycle";
|
||||
import type {
|
||||
RecordedPerceptionLayers,
|
||||
@@ -59,6 +61,7 @@ export {
|
||||
attemptRecordedAutoplay,
|
||||
canPublishRecordedPlaybackController,
|
||||
createRecordedAutoplayGate,
|
||||
createRecordedInitialSeekGate,
|
||||
createRecordedOpenWatchdog,
|
||||
isRecordedPlaybackFullyBuffered,
|
||||
isRecordedPlaybackPresentationReady,
|
||||
@@ -69,6 +72,7 @@ export {
|
||||
recordedPlaybackRangeWhenReady,
|
||||
recordedPointColorKey,
|
||||
rerunPresentationStatus,
|
||||
shouldReapplyRecordedBlueprint,
|
||||
type RecordedPlaybackBufferState,
|
||||
} from "../core/observation/recordedRerunLifecycle";
|
||||
export {
|
||||
@@ -828,6 +832,7 @@ export function RerunViewport({
|
||||
let publishPlaybackBufferState: (() => void) | null = null;
|
||||
let playbackState: RerunPlaybackState | null = null;
|
||||
const recordedAutoplay = createRecordedAutoplayGate();
|
||||
const recordedInitialSeek = createRecordedInitialSeekGate();
|
||||
let blueprintChannel: RerunBlueprintChannel | null = null;
|
||||
let perceptionChannel: RerunBlueprintChannel | null = null;
|
||||
let perceptionReceiver: RerunNativeReceiver | null = null;
|
||||
@@ -1144,9 +1149,25 @@ export function RerunViewport({
|
||||
}) => {
|
||||
if (
|
||||
disposed ||
|
||||
recordingOpened ||
|
||||
(isRecordedSource && event.application_id !== "nodedc_mission_core_recorded")
|
||||
) return;
|
||||
if (recordingOpened) {
|
||||
const identity = recordedIdentityRef.current;
|
||||
if (shouldReapplyRecordedBlueprint(
|
||||
recordingOpened,
|
||||
isRecordedSource,
|
||||
recordedBlueprintUrl !== null,
|
||||
identity,
|
||||
event,
|
||||
)) {
|
||||
// A LAB sidecar is a second upstream receiver for the same
|
||||
// recording store. Rerun re-opens that store only after the
|
||||
// sidecar has been decoded, so reapply the canonical blueprint
|
||||
// at this exact boundary instead of racing it during download.
|
||||
setBlueprintChannelRevision((revision) => revision + 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
recordingOpened = true;
|
||||
if (!isRecordedSource) {
|
||||
// Store discovery only establishes a candidate. Admission is
|
||||
@@ -1314,6 +1335,23 @@ export function RerunViewport({
|
||||
playing = true;
|
||||
}
|
||||
}
|
||||
if (!autoplayWhenReady && !followLive && readyToRender) {
|
||||
recordedInitialSeek.attempt(
|
||||
viewerStartResolved,
|
||||
recordedBuffer.fullyBuffered,
|
||||
presentationReady,
|
||||
rangeNs,
|
||||
(initialStartNs) => {
|
||||
viewer.set_current_time(event.recording_id, timeline, initialStartNs);
|
||||
currentNs = initialStartNs;
|
||||
},
|
||||
initialPlaybackStartSeconds === undefined
|
||||
? expectedTimelineStartSeconds === undefined
|
||||
? undefined
|
||||
: expectedTimelineStartSeconds * 1_000_000_000
|
||||
: initialPlaybackStartSeconds * 1_000_000_000,
|
||||
);
|
||||
}
|
||||
emitPlayback({
|
||||
rangeNs: followLive
|
||||
? rangeNs
|
||||
|
||||
@@ -196,6 +196,20 @@ export function isRecordedPlaybackFullyBuffered(
|
||||
return recordedPlaybackBufferState(rangeNs, expectedTimelineEndSeconds).fullyBuffered;
|
||||
}
|
||||
|
||||
export function shouldReapplyRecordedBlueprint(
|
||||
recordingOpened: boolean,
|
||||
recordedSource: boolean,
|
||||
hasBlueprint: boolean,
|
||||
identity: { applicationId: string; recordingId: string } | null,
|
||||
event: { application_id: string; recording_id: string },
|
||||
): boolean {
|
||||
return recordingOpened
|
||||
&& recordedSource
|
||||
&& hasBlueprint
|
||||
&& identity?.applicationId === event.application_id
|
||||
&& identity.recordingId === event.recording_id;
|
||||
}
|
||||
|
||||
export function attemptRecordedAutoplay(
|
||||
seekToStart: () => void,
|
||||
startPlaying: () => void,
|
||||
@@ -209,6 +223,49 @@ export function attemptRecordedAutoplay(
|
||||
}
|
||||
}
|
||||
|
||||
export function createRecordedInitialSeekGate(): {
|
||||
attempt: (
|
||||
viewerStarted: boolean,
|
||||
fullyBuffered: boolean,
|
||||
presentationReady: boolean,
|
||||
rangeNs: { min: number; max: number } | null,
|
||||
seekToStart: (startNs: number) => void,
|
||||
preferredStartNs?: number,
|
||||
) => boolean;
|
||||
attempted: () => boolean;
|
||||
} {
|
||||
let consumed = false;
|
||||
return {
|
||||
attempt(
|
||||
viewerStarted,
|
||||
fullyBuffered,
|
||||
presentationReady,
|
||||
rangeNs,
|
||||
seekToStart,
|
||||
preferredStartNs,
|
||||
) {
|
||||
if (
|
||||
consumed
|
||||
|| !viewerStarted
|
||||
|| !fullyBuffered
|
||||
|| !presentationReady
|
||||
|| !isUsableRecordedPlaybackRange(rangeNs)
|
||||
) return false;
|
||||
consumed = true;
|
||||
const startNs = Number.isFinite(preferredStartNs)
|
||||
? Math.min(Math.max(preferredStartNs as number, rangeNs.min), rangeNs.max)
|
||||
: rangeNs.min;
|
||||
try {
|
||||
seekToStart(startNs);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
attempted: () => consumed,
|
||||
};
|
||||
}
|
||||
|
||||
export function createRecordedAutoplayGate(): {
|
||||
attempt: (
|
||||
viewerStarted: boolean,
|
||||
|
||||
Reference in New Issue
Block a user