// screens-ingest.jsx — Upload, Recorders, Capture (SDI), Monitors
const { RECORDERS, NODES, SDI_PORTS_zampp2, PROJECTS } = window.ZAMPP_DATA;
/* ========== Upload ========== */
function Upload({ navigate }) {
const [files, setFiles] = React.useState([
{ id: 1, name: "Drone_Aerial_Lap_4.mov", size: "12.4 GB", progress: 68, status: "uploading" },
{ id: 2, name: "Interview_Director.mxf", size: "2.1 GB", progress: 100, status: "done" },
{ id: 3, name: "Sponsor_Logo_v4.mov", size: "120 MB", progress: 100, status: "done" },
{ id: 4, name: "Pit_Cam_3.mp4", size: "4.8 GB", progress: 24, status: "uploading" },
{ id: 5, name: "Backstage_Audio.wav", size: "920 MB", progress: 0, status: "queued" },
]);
React.useEffect(() => {
const i = setInterval(() => {
setFiles(fs => fs.map(f => {
if (f.status !== "uploading") return f;
const next = f.progress + Math.random() * 4;
if (next >= 100) return { ...f, progress: 100, status: "done" };
return { ...f, progress: next };
}));
}, 600);
return () => clearInterval(i);
}, []);
return (
Upload
Drop video, audio, or stills — we proxy and index automatically.
Drop files here or click to browse
Video, audio, and image files — up to 5 GB each
{["MOV", "MP4", "MXF", "ProRes", "DNxHR", "WAV", "AIFF"].map(f => {f})}
Queue
{files.length}
{files.map(f => (
{f.status === "done" ? "✓ done" : f.status === "queued" ? "queued" : `${Math.round(f.progress)}%`}
))}
);
}
/* ========== Recorders (live ingest dashboard) ========== */
function Recorders({ navigate, onNew }) {
return (
Recorders
Live ingest from SRT, RTMP, and SDI sources
4 recording · 1 armed · 1 error
);
}
function RecorderRow({ recorder }) {
const isRec = recorder.status === "recording";
return (
{recorder.audio ? (
) : isRec ? (
) : (
)}
{recorder.name}
{recorder.status.toUpperCase()}
{recorder.source}
{recorder.url}
{recorder.codec}·
{recorder.res}·
node: {recorder.node}
Elapsed
{recorder.elapsed}
Bitrate
{recorder.bitrate}
{isRec ? (
) : recorder.status === "error" ? (
) : (
)}
);
}
function HealthBar({ value }) {
const color = value > 80 ? "var(--success)" : value > 40 ? "var(--warning)" : "var(--danger)";
return (
);
}
function badgeForStatus(s) {
return { recording: "live", armed: "accent", idle: "neutral", error: "danger", offline: "neutral" }[s] || "neutral";
}
/* ========== Capture (rich SDI port picker) ========== */
function Capture({ navigate }) {
const [activePort, setActivePort] = React.useState(1);
const ports = SDI_PORTS_zampp2;
return (
Capture
DeckLink SDI ingest — multi-port routing across cluster nodes
);
}
function DeckLinkVisual({ ports, activePort, onSelect }) {
return (
DeckLink Duo 2
zampp2 · 172.18.91.217
ONLINE
{ports.map(p => (
))}
Ports: 4
Active: {ports.filter(p => p.active).length}
Recording: {ports.filter(p => p.recording).length}
);
}
function CaptureDetail({ port }) {
if (!port.active) {
return (
No signal on SDI {port.idx}
Connect a source, then click Refresh.
);
}
return (
{port.recording && (
REC
)}
{port.signal}
{!port.recording ? (
) : (
)}
);
}
function CaptureStat({ label, value }) {
return (
);
}
/* ========== Monitors (multi-cam grid) ========== */
function Monitors({ navigate }) {
const [grid, setGrid] = React.useState(4);
const allFeeds = [
...RECORDERS.filter(r => !r.audio).map(r => ({ ...r, kind: "video" })),
{ id: "audio1", name: "FOH Mix", kind: "audio" },
{ id: "audio2", name: "Stage Mics", kind: "audio" },
{ id: "audio3", name: "PGM Bus", kind: "audio" },
];
const feeds = allFeeds.slice(0, grid * grid - (grid === 2 ? 1 : 0));
const gridSize = grid;
return (
Monitors
Multi-cam live monitoring across all active feeds
{[2, 3, 4].map(n => (
))}
{feeds.map((f, i) => (
))}
);
}
function MonitorTile({ feed, seed }) {
if (feed.kind === "audio") {
return (
);
}
return (
{feed.status === "recording" && REC}
{feed.status === "armed" && ARMED}
{feed.status === "error" && ERR}
{feed.status === "idle" && IDLE}
{feed.name}
{feed.elapsed && feed.elapsed !== "00:00:00" && {feed.elapsed}}
);
}
Object.assign(window, { Upload, Recorders, Capture, Monitors });