feat(planning): consolidate recorded-route localization and spatial scene
Preserve the completed teach-and-repeat laboratory stage: reference preparation, cascaded acquisition, local tracking and recovery, recording lifecycle, replay qualification, and persistent Rerun scene controls. Document the open grid-picking regression and Rerun upgrade contract. No autonomous driving or loop-closure optimization is claimed.
This commit is contained in:
@@ -28,6 +28,7 @@ export interface CameraLeaseRetryBudget {
|
||||
const CAMERA_LEASE_RETRY_DELAYS = [400, 1_000, 2_000, 5_000] as const;
|
||||
export const CAMERA_FIRST_MEDIA_TIMEOUT_MS = 8_000;
|
||||
export const CAMERA_FIRST_PLAYABLE_FRAME_TIMEOUT_MS = 8_000;
|
||||
export const CAMERA_BUFFERING_NOTICE_DELAY_MS = 1_000;
|
||||
// The gateway may release an 8 MiB / 64-fragment slow-reader backlog after a
|
||||
// main-thread stall. Keep one bounded append margin above that complete batch;
|
||||
// crossing either limit replaces the MSE epoch instead of dropping fragments.
|
||||
@@ -284,6 +285,7 @@ export function MseFmp4WebSocketPlayer({
|
||||
let onBufferError: (() => void) | null = null;
|
||||
let objectUrl = "";
|
||||
let retryTimer: number | undefined;
|
||||
let bufferingNoticeTimer: number | undefined;
|
||||
let receivedMedia = false;
|
||||
let failed = false;
|
||||
let playingReported = false;
|
||||
@@ -295,10 +297,15 @@ export function MseFmp4WebSocketPlayer({
|
||||
);
|
||||
const queue: ArrayBuffer[] = [];
|
||||
let queuedBytes = 0;
|
||||
const clearBufferingNotice = () => {
|
||||
if (bufferingNoticeTimer !== undefined) window.clearTimeout(bufferingNoticeTimer);
|
||||
bufferingNoticeTimer = undefined;
|
||||
};
|
||||
|
||||
const fail = (copy: string) => {
|
||||
if (!transportIsCurrent() || failed) return;
|
||||
startupWatchdog?.clear();
|
||||
clearBufferingNotice();
|
||||
failed = true;
|
||||
transportHealthyRef.current = false;
|
||||
recoveryPendingAuthorityRef.current = null;
|
||||
@@ -335,6 +342,7 @@ export function MseFmp4WebSocketPlayer({
|
||||
return;
|
||||
}
|
||||
startupWatchdog?.clear();
|
||||
clearBufferingNotice();
|
||||
failed = true;
|
||||
transportHealthyRef.current = false;
|
||||
const retry = consumeCameraLeaseRetry(leaseRetryRef.current, delivery.id);
|
||||
@@ -392,6 +400,7 @@ export function MseFmp4WebSocketPlayer({
|
||||
|
||||
const onPlaying = () => {
|
||||
if (!transportIsCurrent() || failed) return;
|
||||
clearBufferingNotice();
|
||||
startupWatchdog?.markPlaying();
|
||||
transportHealthyRef.current = true;
|
||||
leaseRetryRef.current = resetCameraLeaseRetryBudget(delivery.id);
|
||||
@@ -410,7 +419,21 @@ export function MseFmp4WebSocketPlayer({
|
||||
}
|
||||
};
|
||||
|
||||
const onWaiting = () => {
|
||||
if (!transportIsCurrent() || failed || !playingReported) return;
|
||||
// A short receipt gap must not flicker or restart the acquisition-owned
|
||||
// stream. A sustained decoder wait must not retain a playing presentation.
|
||||
if (bufferingNoticeTimer !== undefined) return;
|
||||
bufferingNoticeTimer = window.setTimeout(() => {
|
||||
bufferingNoticeTimer = undefined;
|
||||
if (!transportIsCurrent() || failed || video.readyState >= 3) return;
|
||||
setStatus("buffering");
|
||||
setMessage("Ожидание новых кадров.");
|
||||
}, CAMERA_BUFFERING_NOTICE_DELAY_MS);
|
||||
};
|
||||
video.addEventListener("playing", onPlaying);
|
||||
video.addEventListener("waiting", onWaiting);
|
||||
video.addEventListener("stalled", onWaiting);
|
||||
|
||||
const appendNext = () => {
|
||||
if (
|
||||
@@ -594,10 +617,13 @@ export function MseFmp4WebSocketPlayer({
|
||||
disposed = true;
|
||||
transportHealthyRef.current = false;
|
||||
startupWatchdog?.clear();
|
||||
clearBufferingNotice();
|
||||
if (retryTimer !== undefined) window.clearTimeout(retryTimer);
|
||||
queue.length = 0;
|
||||
socket?.close(1000, "Browser preview transport replaced or hidden");
|
||||
video.removeEventListener("playing", onPlaying);
|
||||
video.removeEventListener("waiting", onWaiting);
|
||||
video.removeEventListener("stalled", onWaiting);
|
||||
mediaSource.removeEventListener("sourceopen", onSourceOpen);
|
||||
if (sourceBuffer && onBufferUpdateEnd) {
|
||||
sourceBuffer.removeEventListener("updateend", onBufferUpdateEnd);
|
||||
@@ -708,7 +734,7 @@ export function MseFmp4WebSocketPlayer({
|
||||
{status !== "playing" ? (
|
||||
<div className="mse-fmp4-player__status" role="status" aria-live="polite">
|
||||
<Icon name={status === "error" ? "alert" : "video"} size={20} />
|
||||
<strong>{status === "error" ? "Канал прерван" : "Подготовка камеры"}</strong>
|
||||
<strong>{status === "error" ? "Канал прерван" : status === "buffering" ? "Ожидание изображения" : "Подготовка камеры"}</strong>
|
||||
<span>{message}</span>
|
||||
{status === "error" ? (
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user