Стабилизация отображения камеры при повторном подключении

This commit is contained in:
DCCONSTRUCTIONS
2026-08-23 15:33:18 +03:00
parent 99eafaf02d
commit 5ebc9fc27c
2 changed files with 64 additions and 5 deletions
@@ -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 (
<WorkspaceWindow
@@ -29,6 +29,7 @@ let cameraTransportCallbackIsCurrent;
let cameraBrowserTransportIdentity;
let liveRerunRecoveryAuthorityIdentity;
let initialObservationWindowRect;
let FloatingObservationWindow;
let ObservationTimeline;
let shouldCaptureWorkspacePointer;
let normalizeTimelineRange;
@@ -86,9 +87,11 @@ before(async () => {
} = 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, /<video/);
assert.match(markup, /Правая камера/);
});
test("observation camera windows stack without overlap when the viewport is narrow", () => {
const bounds = { width: 420, height: 700 };
const lower = initialObservationWindowRect(0, 2, bounds);