From 5ebc9fc27c387335d285cbd44aa333fd21edbcb3 Mon Sep 17 00:00:00 2001 From: DCCONSTRUCTIONS Date: Sun, 23 Aug 2026 15:33:18 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D1=82=D0=B0=D0=B1=D0=B8=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D1=80=D1=8B=20=D0=BF=D1=80=D0=B8=20=D0=BF=D0=BE=D0=B2=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=BD=D0=BE=D0=BC=20=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB?= =?UTF-8?q?=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/FloatingObservationWindow.tsx | 7 ++- .../test/observationSources.test.mjs | 62 ++++++++++++++++++- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/apps/control-station/src/components/FloatingObservationWindow.tsx b/apps/control-station/src/components/FloatingObservationWindow.tsx index 1db8273..4db59a0 100644 --- a/apps/control-station/src/components/FloatingObservationWindow.tsx +++ b/apps/control-station/src/components/FloatingObservationWindow.tsx @@ -123,9 +123,12 @@ export function FloatingObservationWindow({ return () => observer.disconnect(); }, [boundsRef]); - const initialRect = bounds ? initialObservationWindowRect(index, count, bounds) : null; + // Camera media admission must not wait for the viewport observer. A source + // can arrive in the same commit as a workspace transition; mounting it with + // the canonical fallback geometry keeps the browser transport alive until + // the real bounds are measured immediately afterward. + const initialRect = initialObservationWindowRect(index, count, bounds ?? undefined); const windowRect = rect ?? initialRect; - if (!windowRect) return null; return ( { } = await server.ssrLoadModule( "/src/core/observation/liveCameraRecovery.ts", )); - ({ initialObservationWindowRect, shouldCaptureWorkspacePointer } = await server.ssrLoadModule( - "/src/components/FloatingObservationWindow.tsx", - )); + ({ + FloatingObservationWindow, + initialObservationWindowRect, + shouldCaptureWorkspacePointer, + } = await server.ssrLoadModule("/src/components/FloatingObservationWindow.tsx")); ({ liveRerunRecoveryAuthorityIdentity } = await server.ssrLoadModule( "/src/core/observation/liveReceiverWatchdog.ts", )); @@ -133,6 +136,59 @@ test("observation camera windows tile from the bottom-right above the live timel assert.ok(right.y + right.height <= bounds.height - 64); }); +test("live camera window keeps its player mounted before viewport measurement", () => { + const source = { + id: "k1:sensor.camera.right", + sourceId: "sensor.camera.right", + label: "Правая камера", + description: "Live camera", + modality: "video", + availability: "streaming", + transport: "websocket", + endpointLabel: "Локальный видеопоток", + previewUrl: null, + delivery: { + id: "camera-preview-4", + kind: "mse-fmp4-websocket", + url: "/camera-preview/4", + mediaType: 'video/mp4; codecs="avc1.641028"', + }, + activation: { + groupId: "k1:device-session:camera.preview.decoder", + maxActive: 1, + selected: true, + controllable: false, + }, + binding: { + deviceId: "k1-a", + deviceSessionId: "device-session", + acquisitionId: "acquisition-quick", + }, + capabilities: { + defaultVisible: true, + overlay: true, + resizable: true, + timelineMode: "live-only", + }, + }; + const noop = () => undefined; + const markup = renderToStaticMarkup(createElement(FloatingObservationWindow, { + source, + index: 0, + count: 1, + boundsRef: { current: null }, + maximized: false, + active: true, + onRectChange: noop, + onMaximizedChange: noop, + onActivate: noop, + onClose: noop, + })); + + assert.match(markup, /