dragonflight/services/mam-api/src/db/migrations/033-playout-scte.sql
Zac Gaetano 984a73e8ec feat(playout): redesigned MCR screen + SCTE-35 end-to-end
Drop in the redesigned timeline-centric Playout (PGM monitor, transport,
SCTE-35 card, as-run drawer) from the on-node redesign, fully wired to the
real playout API (channels/transport/HLS preview w/ error-recovery/as-run);
no mock data. In-page ConfirmModal for destructive actions.

SCTE-35: new playout_scte_breaks table (migration 033), endpoints to
schedule/trigger/list/cancel breaks (POST/GET/DELETE /channels/:id/scte[/trigger]),
scheduler due-break sweep, engine triggerScte + auto-return + as-run 'scte'
rows + on-air SCTE-BREAK state and timeline AD markers. In-stream SCTE-35
cue injection is a documented stub (CasparCG FFMPEG consumer exposes no
scte35 muxer) — scheduling/triggering/countdown/as-run are functional.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 19:58:02 -04:00

52 lines
2.8 KiB
SQL

-- Migration 033 — SCTE-35 ad-break markers for playout.
--
-- Adds the missing SCTE-35 splice feature to the playout (MCR) subsystem. An
-- operator can either schedule an ad break on a channel's timeline (relative to
-- the active playlist position, or at a wall-clock time) or fire one immediately
-- ("splice now"). Each break is recorded here and, when fired, also written to
-- the append-only as-run log so it shows in the compliance record alongside the
-- clips that aired.
--
-- type:
-- splice_insert — a scheduled break (out → return), duration_s seconds long
-- immediate — fire-now splice (operator pressed "Trigger ad break now")
-- splice_out — open-ended avail out (provider break start)
-- splice_in — return-to-network (provider break end)
--
-- status: pending → fired (when the engine acts on it) → done (when the break
-- window has elapsed). cancelled is set if the operator removes a pending break.
--
-- The engine (services/playout) acts on a break by logging the cue, marking the
-- as-run row, and — where the output path supports it — injecting a real
-- SCTE-35 cue (see playout-manager.triggerScte for the injection point/TODO).
CREATE TABLE IF NOT EXISTS playout_scte_breaks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
channel_id UUID NOT NULL REFERENCES playout_channels(id) ON DELETE CASCADE,
-- Position on the active playlist this break should fire after (0-based item
-- index). NULL for immediate/wall-clock breaks.
playlist_pos INTEGER,
-- Wall-clock fire time for scheduled breaks. NULL for immediate breaks.
scheduled_at TIMESTAMPTZ,
duration_s INTEGER NOT NULL DEFAULT 30,
-- SCTE-35 event id (the splice_event_id carried in the cue). Auto-assigned.
event_id INTEGER NOT NULL DEFAULT 1,
type TEXT NOT NULL DEFAULT 'splice_insert',
status TEXT NOT NULL DEFAULT 'pending',
fired_at TIMESTAMPTZ,
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CHECK (type IN ('splice_insert','immediate','splice_out','splice_in')),
CHECK (status IN ('pending','fired','done','cancelled'))
);
CREATE INDEX IF NOT EXISTS idx_playout_scte_channel ON playout_scte_breaks (channel_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_playout_scte_status ON playout_scte_breaks (status);
-- As-run gains a 'scte' result so fired breaks land in the compliance log next to
-- the clips. The original migration constrained result to played/skipped/error;
-- widen it.
ALTER TABLE playout_as_run DROP CONSTRAINT IF EXISTS playout_as_run_result_check;
ALTER TABLE playout_as_run ADD CONSTRAINT playout_as_run_result_check
CHECK (result IN ('played','skipped','error','scte'));