140 lines
4.4 KiB
TypeScript
140 lines
4.4 KiB
TypeScript
import {sourceIcon,observationSourceStatusLabel} from "../../../../packages/spatial-ui/src/ObservationSourcePicker";
|
|
export {ObservationSourcePicker,observationSourceStatusLabel} from "../../../../packages/spatial-ui/src/ObservationSourcePicker";
|
|
import { Icon } from "@nodedc/ui-react";
|
|
|
|
import type {
|
|
ObservationSourceDescriptor,
|
|
} from "../core/runtime/contracts";
|
|
import { MseFmp4WebSocketPlayer } from "./MseFmp4WebSocketPlayer";
|
|
import {
|
|
RecordedFmp4Player,
|
|
type RecordedObservationPlayback,
|
|
} from "./RecordedFmp4Player";
|
|
import type {
|
|
RecordedAdmissionPhase,
|
|
RecordedCameraAdmissionState,
|
|
} from "../core/observation/recordedSessionAdmission";
|
|
import { liveCameraPlaybackAuthorityIdentity } from "../core/observation/liveCameraRecovery";
|
|
|
|
export function ObservationMedia({
|
|
source,
|
|
playback,
|
|
prepareRecorded = true,
|
|
recordedSessionGate = "ready",
|
|
recordedAdmissionKey = null,
|
|
onRecordedAdmissionChange,
|
|
}: {
|
|
source: ObservationSourceDescriptor;
|
|
playback?: RecordedObservationPlayback | null;
|
|
prepareRecorded?: boolean;
|
|
recordedSessionGate?: RecordedAdmissionPhase;
|
|
recordedAdmissionKey?: string | null;
|
|
onRecordedAdmissionChange?: (
|
|
sourceId: string,
|
|
state: RecordedCameraAdmissionState,
|
|
) => void;
|
|
}) {
|
|
const sourceSelected = source.activation ? source.activation.selected : true;
|
|
const deliveryActive = Boolean(source.delivery && sourceSelected);
|
|
|
|
if (
|
|
deliveryActive &&
|
|
source.delivery?.kind === "mse-fmp4-websocket" &&
|
|
source.modality === "video"
|
|
) {
|
|
return (
|
|
<MseFmp4WebSocketPlayer
|
|
delivery={source.delivery}
|
|
label={source.label}
|
|
recoveryAuthorityIdentity={liveCameraPlaybackAuthorityIdentity(source)}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (
|
|
deliveryActive &&
|
|
source.delivery?.kind === "recorded-fmp4-manifest" &&
|
|
source.modality === "video"
|
|
) {
|
|
if (!prepareRecorded) {
|
|
return (
|
|
<div className="observation-media__empty" role="status">
|
|
<span className="busy-indicator" aria-hidden="true" />
|
|
<strong>Ожидает подготовки</strong>
|
|
<span>Канал будет проверен последовательно в рамках атомарной сессии.</span>
|
|
</div>
|
|
);
|
|
}
|
|
return (
|
|
<RecordedFmp4Player
|
|
source={source}
|
|
playback={playback}
|
|
prepare
|
|
sessionGate={recordedSessionGate}
|
|
admissionKey={recordedAdmissionKey}
|
|
onAdmissionChange={(state) => onRecordedAdmissionChange?.(source.id, state)}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (deliveryActive && source.delivery?.kind === "video-url" && source.modality === "video") {
|
|
return (
|
|
<video
|
|
className="observation-media__asset"
|
|
src={source.delivery.url}
|
|
autoPlay
|
|
muted
|
|
playsInline
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (
|
|
deliveryActive &&
|
|
source.delivery?.kind === "image-url" &&
|
|
(source.modality === "image" || source.modality === "depth")
|
|
) {
|
|
return <img className="observation-media__asset" src={source.delivery.url} alt={source.label} />;
|
|
}
|
|
|
|
if (sourceSelected && source.previewUrl && source.modality === "video") {
|
|
return (
|
|
<video
|
|
className="observation-media__asset"
|
|
src={source.previewUrl}
|
|
autoPlay
|
|
muted
|
|
playsInline
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (
|
|
sourceSelected &&
|
|
source.previewUrl &&
|
|
(source.modality === "image" || source.modality === "depth")
|
|
) {
|
|
return <img className="observation-media__asset" src={source.previewUrl} alt={source.label} />;
|
|
}
|
|
|
|
return (
|
|
<div className="observation-media__empty">
|
|
<Icon name={sourceIcon[source.modality]} size={20} />
|
|
<strong>
|
|
{source.modality === "video" && source.activation && !source.activation.selected
|
|
? "Источник отключён"
|
|
: observationSourceStatusLabel(source)}
|
|
</strong>
|
|
<span>
|
|
{source.modality === "video"
|
|
? source.activation?.controllable
|
|
? source.activation.selected
|
|
? "Повторите подключение — локальный адаптер перезапустит выбранную камеру"
|
|
: "Канал остановлен оператором; при необходимости его можно открыть снова"
|
|
: "Канал известен, browser-preview сейчас недоступен"
|
|
: source.description}
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|