fix(lab): seal RAV004 spatial replay transport
This commit is contained in:
@@ -28,6 +28,7 @@ export function RecordedEvidenceVideoScene({
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
boxes,
|
||||
overlaySeconds,
|
||||
semanticOverlay,
|
||||
pointCloudOverlay,
|
||||
ariaLabel,
|
||||
@@ -46,6 +47,7 @@ export function RecordedEvidenceVideoScene({
|
||||
imageWidth: number;
|
||||
imageHeight: number;
|
||||
boxes: readonly RecordedEvidenceBox[];
|
||||
overlaySeconds?: number;
|
||||
semanticOverlay?: RecordedEvidenceSemanticOverlay;
|
||||
pointCloudOverlay?: RecordedEvidencePointCloudOverlayData;
|
||||
ariaLabel: string;
|
||||
@@ -77,7 +79,8 @@ export function RecordedEvidenceVideoScene({
|
||||
const sourceReady = admissionPhase === "ready";
|
||||
const overlaysPresented = sourceReady
|
||||
&& presentedSeconds !== null
|
||||
&& Math.abs(presentedSeconds - playback.currentSeconds) <= 0.25;
|
||||
&& Math.abs(presentedSeconds - playback.currentSeconds) <= 0.25
|
||||
&& (overlaySeconds === undefined || Math.abs(presentedSeconds - overlaySeconds) <= 0.25);
|
||||
const handlePlaybackChange = (next: RecordedObservationPlayback) => {
|
||||
setPresentedSeconds(next.currentSeconds);
|
||||
// During a paused operator seek the existing media element can emit its old
|
||||
|
||||
@@ -238,7 +238,7 @@ export interface M4ThreatPlaybackProgress {
|
||||
|
||||
export interface M4ThreatPlaybackPointPack {
|
||||
resultId: string;
|
||||
frameCount: 4489;
|
||||
frameCount: number;
|
||||
pointCount: number;
|
||||
pointOffsets: Uint32Array;
|
||||
pointsMapXyzM: Float32Array;
|
||||
@@ -261,7 +261,7 @@ export interface M4ThreatPlaybackChunkDescriptor {
|
||||
|
||||
export interface M4ThreatPlaybackManifest {
|
||||
resultId: string;
|
||||
frameCount: 4489;
|
||||
frameCount: number;
|
||||
pointCount: number;
|
||||
pointOffsets: Uint32Array;
|
||||
chunkFrameCount: 24;
|
||||
@@ -341,7 +341,7 @@ const motion = (value: unknown): M4ThreatMotion => {
|
||||
};
|
||||
const resultId = (value: unknown): string => {
|
||||
const parsed = text(value, "M4.6 result id");
|
||||
if (!/^m4-threat-replay-[a-f0-9]{64}$/.test(parsed)) {
|
||||
if (!/^[a-z0-9][a-z0-9-]{0,127}-[a-f0-9]{64}$/.test(parsed)) {
|
||||
throw new M4ThreatContractError("M4.6 result id: нарушена идентичность.");
|
||||
}
|
||||
return parsed;
|
||||
@@ -1001,7 +1001,10 @@ export async function fetchM4ThreatPlaybackManifest(
|
||||
exact(manifest.result_id, result, "M4.6 playback result");
|
||||
exact(manifest.coordinate_frame, "map", "M4.6 playback coordinate frame");
|
||||
exact(manifest.access, "read-only-sealed-binary-playback", "M4.6 playback access");
|
||||
const frameCount = exact(integer(manifest.frame_count, "M4.6 playback frames"), 4489, "M4.6 playback frames");
|
||||
const frameCount = integer(manifest.frame_count, "M4.6 playback frames");
|
||||
if (frameCount < 1) {
|
||||
throw new M4ThreatContractError("M4.6 playback frames: пустой playback недопустим.");
|
||||
}
|
||||
const pointCount = integer(manifest.point_count, "M4.6 playback points");
|
||||
const offsetsRaw = array(manifest.point_offsets, "M4.6 playback offsets");
|
||||
if (offsetsRaw.length !== frameCount + 1) {
|
||||
|
||||
@@ -180,6 +180,7 @@ export function M4ReplayThreatVisual({
|
||||
showReferenceMediaLayers = true,
|
||||
showSpatialOverlaySummary = true,
|
||||
playbackTransport = "epoch-stream",
|
||||
spatialPlaybackTransport = "auto",
|
||||
recoverTimestampStalls = false,
|
||||
onActiveSequenceChange,
|
||||
}: {
|
||||
@@ -198,6 +199,7 @@ export function M4ReplayThreatVisual({
|
||||
showReferenceMediaLayers?: boolean;
|
||||
showSpatialOverlaySummary?: boolean;
|
||||
playbackTransport?: "segmented" | "epoch-stream";
|
||||
spatialPlaybackTransport?: "auto" | "sealed-binary" | "json";
|
||||
recoverTimestampStalls?: boolean;
|
||||
onActiveSequenceChange?: (sequence: number | null) => void;
|
||||
}) {
|
||||
@@ -293,6 +295,7 @@ export function M4ReplayThreatVisual({
|
||||
currentSeconds: playbackController.playback.currentSeconds,
|
||||
includeSpatialPoints: evidenceDemand.sourceSpatialPoints,
|
||||
endpointRoot: timelineEndpointRoot,
|
||||
spatialPlaybackTransport,
|
||||
});
|
||||
const [videoSource, setVideoSource] = useState<ObservationSourceDescriptor | null>(null);
|
||||
const [videoLoading, setVideoLoading] = useState(false);
|
||||
@@ -1143,6 +1146,7 @@ export function M4ReplayThreatVisual({
|
||||
imageWidth={timeline.imageWidth}
|
||||
imageHeight={timeline.imageHeight}
|
||||
boxes={activeBoxes}
|
||||
overlaySeconds={frame?.sessionSeconds}
|
||||
semanticOverlay={mediaMode === "video" ? semanticOverlay : undefined}
|
||||
pointCloudOverlay={mediaMode === "video" ? pointCloudOverlay : undefined}
|
||||
ariaLabel={`${evidenceLabel} recorded-realtime frame ${frame?.sequence ?? 0}: ${activeBoxes.length} proposals`}
|
||||
|
||||
@@ -80,6 +80,7 @@ function FullRouteReviewEvidence({
|
||||
classifiedSpatialLayer={sealedSpatialGap}
|
||||
evidenceLabel="RAVNOVES004TREE"
|
||||
playbackTransport="segmented"
|
||||
spatialPlaybackTransport="sealed-binary"
|
||||
recoverTimestampStalls
|
||||
showReferenceMediaLayers
|
||||
showSpatialOverlaySummary
|
||||
|
||||
@@ -34,14 +34,14 @@ export function m4ThreatChunkWindowStarts(
|
||||
if (chunkSize < 1 || frameCount < 1) return [];
|
||||
return [
|
||||
activeChunkStart,
|
||||
...Array.from(
|
||||
{ length: RETAINED_CHUNKS_BEHIND },
|
||||
(_, index) => activeChunkStart - (index + 1) * chunkSize,
|
||||
),
|
||||
...Array.from(
|
||||
{ length: PREFETCH_CHUNKS_AHEAD },
|
||||
(_, index) => activeChunkStart + (index + 1) * chunkSize,
|
||||
),
|
||||
...Array.from(
|
||||
{ length: RETAINED_CHUNKS_BEHIND },
|
||||
(_, index) => activeChunkStart - (index + 1) * chunkSize,
|
||||
),
|
||||
].filter((start) => start >= 0 && start < frameCount);
|
||||
}
|
||||
|
||||
@@ -86,12 +86,14 @@ export function useM4ThreatTimelineFrame({
|
||||
currentSeconds,
|
||||
includeSpatialPoints = true,
|
||||
endpointRoot,
|
||||
spatialPlaybackTransport = "auto",
|
||||
}: {
|
||||
resultId: string;
|
||||
timeline: M4ThreatTimeline | null;
|
||||
currentSeconds: number;
|
||||
includeSpatialPoints?: boolean;
|
||||
endpointRoot?: string;
|
||||
spatialPlaybackTransport?: "auto" | "sealed-binary" | "json";
|
||||
}) {
|
||||
const [chunks, setChunks] = useState<ReadonlyMap<number, M4ThreatTimelineChunk>>(
|
||||
() => new Map(),
|
||||
@@ -104,8 +106,10 @@ export function useM4ThreatTimelineFrame({
|
||||
totalBytes: 0,
|
||||
});
|
||||
const [playbackError, setPlaybackError] = useState<string | null>(null);
|
||||
const binaryPlayback = endpointRoot === undefined
|
||||
|| endpointRoot === M4_THREAT_TIMELINE_ENDPOINT_ROOT;
|
||||
const binaryPlayback = spatialPlaybackTransport === "sealed-binary"
|
||||
|| (spatialPlaybackTransport === "auto" && (
|
||||
endpointRoot === undefined || endpointRoot === M4_THREAT_TIMELINE_ENDPOINT_ROOT
|
||||
));
|
||||
const inFlight = useRef(new Map<number, AbortController>());
|
||||
const chunksRef = useRef(chunks);
|
||||
const activeChunkStartRef = useRef<number | null>(null);
|
||||
|
||||
Reference in New Issue
Block a user