"use client"; import { useEffect, useMemo, useState } from "react"; import { useAuth } from "../../../lib/auth-store"; import { API_URL, ApiError, ApiUnreachableError } from "../../../lib/api-client"; import { getSessionToken } from "../../../lib/session-token"; import { Badge, Button } from "../../../components/ui/primitives"; import { AudioMonitor } from "../../../components/control-room/audio-monitor"; import { MultiviewGrid, liveTapIds, type GridTier } from "../../../components/control-room/multiview-grid"; import { OverlayEditor, type RenderInput } from "../../../components/control-room/overlay-editor"; import { PstPreview } from "../../../components/control-room/pst-preview"; import { SwitcherBar, type DirectorAction } from "../../../components/control-room/switcher-bar"; import { controlRequest } from "../../../components/control-room/client"; import { fixtureChannel } from "../../../components/control-room/fixtures"; import { RealtimeClient } from "../../../components/control-room/state-store"; import { initialTally, refetchTally, tallyReducer, type TallyState } from "../../../components/control-room/tally"; import type { Receipt, Status } from "../../../components/control-room/types"; import "./control-room.css"; export default function ControlRoom(): React.ReactElement { const { session, can, status: authStatus } = useAuth(); const [channels, setChannels] = useState([]); const [selected, setSelected] = useState(""); const [state, setState] = useState(null); const [fixture, setFixture] = useState(false); const [connection, setConnection] = useState("loading"); const [message, setMessage] = useState(""); const [busy, setBusy] = useState(false); const [mobile, setMobile] = useState(true); const [tier, setTier] = useState("2x2"); const [focused, setFocused] = useState(null); const [upgrades, setUpgrades] = useState([]); const [reload, setReload] = useState(0); useEffect(() => { const query = matchMedia("(max-width: 1023px)"); const update = (): void => setMobile(query.matches); update(); query.addEventListener("change", update); return () => query.removeEventListener("change", update); }, []); useEffect(() => { if (authStatus === "loading") return; let cancelled = false; const abort = new AbortController(); setConnection("loading"); setState(null); setFixture(false); setMessage(""); void controlRequest("/media-control/status", undefined, abort.signal).then(result => { if (cancelled) return; setChannels(result.channels); setSelected(result.channels[0]?.channelId ?? ""); setConnection(result.channels.length ? "connecting" : "empty"); }).catch(error => { if (cancelled) return; if (error instanceof ApiUnreachableError || error instanceof ApiError && error.status >= 500) { const sample = fixtureChannel(); setFixture(true); setChannels([sample]); setSelected(sample.channelId); setConnection("fixture"); } else { setChannels([]); setSelected(""); setConnection("error"); setMessage(error instanceof Error ? error.message : "Status unavailable"); } }); return () => { cancelled = true; abort.abort(); }; }, [authStatus, session?.workspace.id, reload]); useEffect(() => { const channel = channels.find(item => item.channelId === selected); setFocused(null); setUpgrades([]); if (!channel) { setState(null); return; } setState(initialTally(channel)); if (fixture || !session) return; const url = new URL(`${API_URL}/realtime`); url.protocol = url.protocol === "https:" ? "wss:" : "ws:"; const client = new RealtimeClient({ url: url.href, token: getSessionToken, workspaceId: session.workspace.id, channelId: selected, socket: address => new WebSocket(address), status: () => controlRequest("/media-control/status"), onFrame: frame => setState(previous => previous ? tallyReducer(previous, frame) : previous), onStatus: result => { const current = result.channels.find(item => item.channelId === selected); setState(previous => current ? refetchTally(current, previous) : null); }, onConnection: setConnection }); client.start(); return () => client.stop(); }, [channels, selected, fixture, session]); const production = state?.channel.desired?.production; const liveIds = useMemo(() => liveTapIds(production?.sources ?? [], tier, focused, upgrades, mobile), [production, tier, focused, upgrades, mobile]); const action = async (operation: DirectorAction, body: object): Promise => { if (!state?.channel.desired) return; setBusy(true); setMessage(""); try { if (fixture) { const desired = structuredClone(state.channel.desired); if (desired.production) { if (operation === "pst-select" && "sceneId" in body) desired.production.buses.preview = String(body.sceneId); if (operation === "cut" || operation === "take") desired.production.buses.program = desired.production.buses.preview; if (operation === "lock" && "enabled" in body && desired.director) desired.director.locked = Boolean(body.enabled); if (operation === "override" && "mode" in body && desired.director) desired.director.override = body.mode === null ? null : { mode: body.mode as NonNullable["mode"], expiresAt: null }; } desired.desiredVersion += 1; setState(previous => previous ? tallyReducer(previous, { type: "state", workspaceId: "fixture", channelId: selected, kind: "desired", seq: previous.lastSeq + 1, data: desired }) : previous); setMessage("Fixture request recorded. Tally awaits a simulated observed frame."); } else { const receipt = await controlRequest(`/media-control/channels/${selected}/${operation}`, body); setMessage(`Request accepted · version ${receipt.desiredVersion}. Waiting for observed state.`); } } catch (error) { setMessage(error instanceof Error ? error.message : "Command failed"); } finally { setBusy(false); } }; const render = async (id: string, input: RenderInput): Promise => { if (fixture) throw new Error("Fixture mode has no render service. Reconnect to render an overlay."); await controlRequest(`/production/overlays/${id}`, input); }; const simulate = (): void => setState(previous => previous ? tallyReducer(previous, { type: "state", workspaceId: "fixture", channelId: selected, kind: "observed", seq: previous.lastSeq + 1, data: { workspaceId: "fixture", channelId: selected, workerId: previous.channel.workerId, assignmentEpoch: previous.channel.assignmentEpoch, realizedVersion: previous.channel.desired?.desiredVersion ?? null, state: "RUNNING", reason: "Fixture simulation", updatedAt: new Date().toISOString() } }) : previous); const offline = connection !== "live" && connection !== "polling" && !fixture; return

Control Room

Preview taps · server composition · observed tally

{connection === "fixture" ? "FIXTURE · sample data" : connection}
{fixture &&

API unavailable. This is a fixture rehearsal with sample scenes, no broadcast, and no actual program state. Actions stay local.

} {message &&

{message}

} {channels.length > 0 && } {!production || production.scenes.length === 0 ?

{connection === "loading" ? "Loading production status…" : connection === "error" ? "Production status unavailable" : "No production scenes configured"}

{connection === "error" ? "Reconnect or ask a workspace administrator to check your media.view permission. Production state cannot be determined from this response." : "Configure a virtual channel, assign a media worker, add ingest sources and production scenes, then select the preview bus. Program tally appears only after the worker confirms a realized version."}

: <>
Desired v{state?.channel.desired?.desiredVersion}Observed {state?.channel.observed?.realizedVersion === null || !state?.channel.observed ? "unconfirmed" : `v${state.channel.observed.realizedVersion} · ${state.channel.observed.state}`}{state?.observedProduction ? "Tally confirmed" : "Tally unknown · awaiting matching observed version"}
{ if (!fixture && !can(operation === "lock" ? "director.lock_program" : operation === "override" ? "director.override_auto" : "director.take")) { setMessage("Your role cannot perform this action."); return; } void action(operation, body); }} />
{ setTier(value); setUpgrades([]); }} upgrades={upgrades} onUpgrade={id => setUpgrades(previous => previous.includes(id) ? previous.filter(item => item !== id) : liveIds.length < 4 ? [...previous, id] : previous)} />
}
; }