PRD: K-TV Post-Restructure Roadmap — Playout Service, Interstitials, Worker Integration #2

Closed
opened 2026-07-12 05:46:10 +00:00 by GKaszewski · 0 comments
Owner

Problem Statement

K-TV's backend has been restructured into a proper DDD hexagonal architecture (13 crates, 3 ADRs, domain glossary). The domain model now describes concepts — Playout Service, Interstitials, Mid-Roll Breaks, Chapters, Gap Filler, Overlays, 6 FillStrategies — that have data structures in the code but no runtime behavior. The system still streams via provider-proxied URLs rather than owning the stream, background tasks exist in a Worker binary but the Playout Service binary doesn't exist yet, and the frontend assumes the old API shape.

The Operator has 120 movies and 12 TV shows. They want an agent (via MCP) to act as a creative programming director — designing thematic channels, scheduling marathons, inserting bumpers — but the engine can't do most of that yet.

Solution

Build the remaining runtime components in priority order: wire the schedule engine's new concepts (interstitials, mid-roll breaks, new fill strategies), build the Playout Service binary (HLS via FFmpeg, SCTE-35, shared broadcast), update the frontend and deployment for the 3-binary architecture, and add iCalendar import/export.

User Stories

  1. As an Operator, I want the schedule engine to insert bumpers between shows in a ProgrammingBlock, so that my channels feel like real TV
  2. As an Operator, I want to configure mid-roll commercial breaks during movies, so that long content has natural pauses like broadcast TV
  3. As an Operator, I want the system to prefer chapter markers for mid-roll break points, so that breaks happen at natural scene boundaries
  4. As an Operator, I want a gap filler per channel that plays test patterns or filler content during unscheduled time, so there's no dead air
  5. As an Operator, I want the Alternating fill strategy to cycle between shows (Mon=Friends, Tue=Seinfeld), so I can create checkerboard schedules
  6. As an Operator, I want the Weighted fill strategy to favor recently added content, so fresh additions get airtime quickly
  7. As an Operator, I want the Marathon fill strategy to binge a single series in one block, so I can create weekend marathon events
  8. As a Viewer, I want to tune to a channel and see an HLS stream that k-tv controls end-to-end, so playback is consistent regardless of which provider the content came from
  9. As a Viewer, I want all audio tracks and subtitles from the source file available in the stream, so I can watch in my preferred language
  10. As an Operator, I want chapters extracted from my media files during library sync, so mid-roll breaks can use them automatically
  11. As an Operator, I want the library sync to detect MediaRole (Program vs Interstitial) based on collection or tag conventions, so I don't have to manually classify every item
  12. As an Operator, I want to export my channel's ScheduleConfig as an iCalendar file, so I can visualize it in any calendar app or share it
  13. As an Operator, I want to import an iCalendar file to create or update a channel's schedule, so I can design schedules in familiar tools
  14. As a Viewer, I want "Coming up next" overlay metadata emitted in the HLS stream, so capable clients show what's playing next
  15. As an Operator, I want SCTE-35 markers in the HLS stream at mid-roll break points, so IPTV clients handle commercial breaks correctly
  16. As an Operator, I want to deploy presentation, worker, and playout as three separate Docker containers, so I can scale them independently
  17. As an Operator, I want the Playout Service to manage disk space for HLS segments with a configurable sliding window, so my server doesn't fill up
  18. As an Operator, I want segment storage to be configurable (local disk, NAS mount, tmpfs), so I can optimize for my hardware
  19. As a Developer, I want OpenAPI documentation auto-generated from the api-types crate, so API consumers have accurate docs
  20. As an Operator, I want the auto-scheduler to self-heal — always ensuring N days of schedule exist ahead, so channels never go to dead air unexpectedly
  21. As an Operator using MCP, I want the agent to browse my library, understand genres/series/durations, and design thematic channels, so I don't have to manually create schedules
  22. As an Operator, I want the frontend to work with the new SourceUri API shape, so the TV page plays content through the Playout Service

Implementation Decisions

Architecture (established)

  • Three binaries: presentation (HTTP API), worker (background jobs), playout (HLS streaming). Per ADR-0002.
  • Library is the single source of truth for media. Schedule engine queries the library, not providers. Per ADR-0001.
  • Providers expose source URIs (network URL or file path), not viewer-facing stream URLs. Per ADR-0002.
  • Database-backed event queue with DLQ for inter-process communication. Per ADR-0003.
  • All configuration via environment variables.

Phase 1: Schedule Engine Wiring

  • Wire InterstitialRule in schedule engine: after filling a block with program items, insert interstitials between them using the rule's pool_filter and strategy.
  • Wire MidRollRule: when a program item is longer than the break interval, split it into segments with interstitial slots at break points. Check MediaItem.chapters first; fall back to fixed interval.
  • Wire gap filler: the Playout Service checks Channel.gap_filler when no ScheduledSlot covers the current time.
  • Implement the actual fill algorithms for Alternating, Weighted, Marathon (data structures and basic fill functions exist, but the schedule engine's resolve_block doesn't dispatch to them yet — verify this).

Phase 2: Chapter Extraction

  • During library sync, use ffprobe to extract chapter metadata from media files.
  • Store chapters as JSON on the MediaItem (chapters column in library_items table, migration already exists).
  • Only extract for items where content_type is Movie or duration_secs > a threshold (e.g., 45 minutes) — don't waste time probing 22-minute episodes.

Phase 3: Playout Service Binary

  • New crate: crates/playout/ — binary k-tv-playout.
  • Core loop: for each active Channel, determine the current ScheduledSlot from the GeneratedSchedule, resolve the source URI from the provider, feed it to FFmpeg for HLS segmentation.
  • FFmpeg reads from provider source URI on-demand (network URL or local path — no full download).
  • Produces HLS output: .m3u8 playlist + .ts segments.
  • Shared broadcast: one stream per channel, all viewers share segments.
  • Sliding window segment retention: keep N segments around current position, delete older ones. Window size configurable via env.
  • SegmentStore port: abstracts where segments are written (local FS, NAS, tmpfs). Adapter selected via env config.
  • SCTE-35 markers: inject EXT-X-DATERANGE tags in the HLS playlist at mid-roll break points.
  • Timed metadata: inject ID3 tags for overlay data (e.g., "Coming up next" triggers).
  • Include all audio tracks and subtitle tracks from the source container.

Phase 4: Frontend + Deployment

  • Frontend: update useStreamUrl hook and stream API route to work with SourceUri. The Playout Service serves HLS — frontend points hls.js at the playout endpoint.
  • Docker: three Dockerfiles (or multi-stage), three services in compose.yml. Shared SQLite volume for single-machine deployment.
  • OpenAPI: wire utoipa properly in presentation — merge all api-types schemas into a served /api/docs endpoint.

Phase 5: iCalendar Import/Export

  • Export: ScheduleConfig → iCalendar. Each ProgrammingBlock becomes a VEVENT with RRULE. K-tv-specific properties (filter, strategy, interstitial_rule, mid_roll_rule) in X-KTV-* custom properties.
  • Import: iCalendar → ScheduleConfig. Parse VEVENTs, extract RRULE for weekday mapping, read X-KTV-* properties.
  • Pure functions in domain services (like IPTV generation), tested without I/O.

Testing Decisions

What makes a good test

Tests verify behavior through the module's public interface (the seam), not implementation details. A test should break only when behavior changes, not when internals are refactored.

Testing seams

  • Schedule engine (interstitials, mid-roll, new strategies): test through ScheduleEngineService.generate_schedule() with InMemoryLibraryRepository seeded with known items. Assert slot ordering, interstitial placement, break points.
  • Playout Service → SegmentStore port: test segment lifecycle (write, read, cleanup) through the SegmentStore interface with an InMemory adapter.
  • Playout Service → IProviderRegistry (get_source_uri): test source resolution with NoopMediaProvider returning known URIs.
  • Event flow: test publish → consume → ack/nack through InMemoryEventBus.
  • iCalendar: pure function tests — ScheduleConfig → ical string → ScheduleConfig roundtrip.
  • Chapter extraction: test the parsing logic (ffprobe JSON output → Vec of Chapter) as a pure function. Integration test with a real file is optional.

Prior art

  • domain/src/services/schedule/tests/fill.rs — fill strategy tests with synthetic candidate pools
  • domain/src/services/schedule/tests/rotation.rs — rotation policy tests
  • application/src/auth/tests/ — use case tests with InMemory repos
  • domain/src/services/tests/iptv.rs — pure function output tests

Out of Scope

  • Multi-machine distributed deployment with Postgres — SQLite single-machine is the target. Postgres adapter can be re-added later.
  • Server-side overlay compositing — overlays are metadata-only per glossary decision.
  • NATS JetStream event bus — database-backed queue is sufficient. NATS is a future adapter swap.
  • User-level permissions for channel access — glossary notes this as future work.
  • BXF format support — not needed for a self-hosted tool.
  • Real-time adaptive bitrate — Playout Service produces a single quality HLS stream. ABR (multiple renditions) is future work.

Further Notes

  • The domain glossary at crates/CONTEXT.md is the source of truth for terminology. All implementation should use glossary terms.
  • ADR-0001 (library as source of truth), ADR-0002 (k-tv owns the stream), ADR-0003 (database-backed event queue) are all accepted and partially implemented. This roadmap completes them.
  • The MCP crate should be expanded alongside each phase — as new capabilities land (interstitials, fill strategies, schedule analysis), expose them as MCP tools so the agent can use them creatively.
  • Suggested phase order: 1 → 2 → 3 → 4 → 5. Phases 1-2 are pure domain work with no infrastructure dependencies. Phase 3 is the big infrastructure build. Phase 4 integrates everything. Phase 5 is a nice-to-have.
## Problem Statement K-TV's backend has been restructured into a proper DDD hexagonal architecture (13 crates, 3 ADRs, domain glossary). The domain model now describes concepts — Playout Service, Interstitials, Mid-Roll Breaks, Chapters, Gap Filler, Overlays, 6 FillStrategies — that have data structures in the code but no runtime behavior. The system still streams via provider-proxied URLs rather than owning the stream, background tasks exist in a Worker binary but the Playout Service binary doesn't exist yet, and the frontend assumes the old API shape. The Operator has 120 movies and 12 TV shows. They want an agent (via MCP) to act as a creative programming director — designing thematic channels, scheduling marathons, inserting bumpers — but the engine can't do most of that yet. ## Solution Build the remaining runtime components in priority order: wire the schedule engine's new concepts (interstitials, mid-roll breaks, new fill strategies), build the Playout Service binary (HLS via FFmpeg, SCTE-35, shared broadcast), update the frontend and deployment for the 3-binary architecture, and add iCalendar import/export. ## User Stories 1. As an Operator, I want the schedule engine to insert bumpers between shows in a ProgrammingBlock, so that my channels feel like real TV 2. As an Operator, I want to configure mid-roll commercial breaks during movies, so that long content has natural pauses like broadcast TV 3. As an Operator, I want the system to prefer chapter markers for mid-roll break points, so that breaks happen at natural scene boundaries 4. As an Operator, I want a gap filler per channel that plays test patterns or filler content during unscheduled time, so there's no dead air 5. As an Operator, I want the Alternating fill strategy to cycle between shows (Mon=Friends, Tue=Seinfeld), so I can create checkerboard schedules 6. As an Operator, I want the Weighted fill strategy to favor recently added content, so fresh additions get airtime quickly 7. As an Operator, I want the Marathon fill strategy to binge a single series in one block, so I can create weekend marathon events 8. As a Viewer, I want to tune to a channel and see an HLS stream that k-tv controls end-to-end, so playback is consistent regardless of which provider the content came from 9. As a Viewer, I want all audio tracks and subtitles from the source file available in the stream, so I can watch in my preferred language 10. As an Operator, I want chapters extracted from my media files during library sync, so mid-roll breaks can use them automatically 11. As an Operator, I want the library sync to detect MediaRole (Program vs Interstitial) based on collection or tag conventions, so I don't have to manually classify every item 12. As an Operator, I want to export my channel's ScheduleConfig as an iCalendar file, so I can visualize it in any calendar app or share it 13. As an Operator, I want to import an iCalendar file to create or update a channel's schedule, so I can design schedules in familiar tools 14. As a Viewer, I want "Coming up next" overlay metadata emitted in the HLS stream, so capable clients show what's playing next 15. As an Operator, I want SCTE-35 markers in the HLS stream at mid-roll break points, so IPTV clients handle commercial breaks correctly 16. As an Operator, I want to deploy presentation, worker, and playout as three separate Docker containers, so I can scale them independently 17. As an Operator, I want the Playout Service to manage disk space for HLS segments with a configurable sliding window, so my server doesn't fill up 18. As an Operator, I want segment storage to be configurable (local disk, NAS mount, tmpfs), so I can optimize for my hardware 19. As a Developer, I want OpenAPI documentation auto-generated from the api-types crate, so API consumers have accurate docs 20. As an Operator, I want the auto-scheduler to self-heal — always ensuring N days of schedule exist ahead, so channels never go to dead air unexpectedly 21. As an Operator using MCP, I want the agent to browse my library, understand genres/series/durations, and design thematic channels, so I don't have to manually create schedules 22. As an Operator, I want the frontend to work with the new SourceUri API shape, so the TV page plays content through the Playout Service ## Implementation Decisions ### Architecture (established) - Three binaries: presentation (HTTP API), worker (background jobs), playout (HLS streaming). Per ADR-0002. - Library is the single source of truth for media. Schedule engine queries the library, not providers. Per ADR-0001. - Providers expose source URIs (network URL or file path), not viewer-facing stream URLs. Per ADR-0002. - Database-backed event queue with DLQ for inter-process communication. Per ADR-0003. - All configuration via environment variables. ### Phase 1: Schedule Engine Wiring - Wire InterstitialRule in schedule engine: after filling a block with program items, insert interstitials between them using the rule's pool_filter and strategy. - Wire MidRollRule: when a program item is longer than the break interval, split it into segments with interstitial slots at break points. Check MediaItem.chapters first; fall back to fixed interval. - Wire gap filler: the Playout Service checks Channel.gap_filler when no ScheduledSlot covers the current time. - Implement the actual fill algorithms for Alternating, Weighted, Marathon (data structures and basic fill functions exist, but the schedule engine's resolve_block doesn't dispatch to them yet — verify this). ### Phase 2: Chapter Extraction - During library sync, use ffprobe to extract chapter metadata from media files. - Store chapters as JSON on the MediaItem (chapters column in library_items table, migration already exists). - Only extract for items where content_type is Movie or duration_secs > a threshold (e.g., 45 minutes) — don't waste time probing 22-minute episodes. ### Phase 3: Playout Service Binary - New crate: `crates/playout/` — binary `k-tv-playout`. - Core loop: for each active Channel, determine the current ScheduledSlot from the GeneratedSchedule, resolve the source URI from the provider, feed it to FFmpeg for HLS segmentation. - FFmpeg reads from provider source URI on-demand (network URL or local path — no full download). - Produces HLS output: `.m3u8` playlist + `.ts` segments. - Shared broadcast: one stream per channel, all viewers share segments. - Sliding window segment retention: keep N segments around current position, delete older ones. Window size configurable via env. - SegmentStore port: abstracts where segments are written (local FS, NAS, tmpfs). Adapter selected via env config. - SCTE-35 markers: inject EXT-X-DATERANGE tags in the HLS playlist at mid-roll break points. - Timed metadata: inject ID3 tags for overlay data (e.g., "Coming up next" triggers). - Include all audio tracks and subtitle tracks from the source container. ### Phase 4: Frontend + Deployment - Frontend: update useStreamUrl hook and stream API route to work with SourceUri. The Playout Service serves HLS — frontend points hls.js at the playout endpoint. - Docker: three Dockerfiles (or multi-stage), three services in compose.yml. Shared SQLite volume for single-machine deployment. - OpenAPI: wire utoipa properly in presentation — merge all api-types schemas into a served `/api/docs` endpoint. ### Phase 5: iCalendar Import/Export - Export: ScheduleConfig → iCalendar. Each ProgrammingBlock becomes a VEVENT with RRULE. K-tv-specific properties (filter, strategy, interstitial_rule, mid_roll_rule) in X-KTV-* custom properties. - Import: iCalendar → ScheduleConfig. Parse VEVENTs, extract RRULE for weekday mapping, read X-KTV-* properties. - Pure functions in domain services (like IPTV generation), tested without I/O. ## Testing Decisions ### What makes a good test Tests verify behavior through the module's public interface (the seam), not implementation details. A test should break only when behavior changes, not when internals are refactored. ### Testing seams - **Schedule engine (interstitials, mid-roll, new strategies)**: test through ScheduleEngineService.generate_schedule() with InMemoryLibraryRepository seeded with known items. Assert slot ordering, interstitial placement, break points. - **Playout Service → SegmentStore port**: test segment lifecycle (write, read, cleanup) through the SegmentStore interface with an InMemory adapter. - **Playout Service → IProviderRegistry (get_source_uri)**: test source resolution with NoopMediaProvider returning known URIs. - **Event flow**: test publish → consume → ack/nack through InMemoryEventBus. - **iCalendar**: pure function tests — ScheduleConfig → ical string → ScheduleConfig roundtrip. - **Chapter extraction**: test the parsing logic (ffprobe JSON output → Vec of Chapter) as a pure function. Integration test with a real file is optional. ### Prior art - `domain/src/services/schedule/tests/fill.rs` — fill strategy tests with synthetic candidate pools - `domain/src/services/schedule/tests/rotation.rs` — rotation policy tests - `application/src/auth/tests/` — use case tests with InMemory repos - `domain/src/services/tests/iptv.rs` — pure function output tests ## Out of Scope - **Multi-machine distributed deployment with Postgres** — SQLite single-machine is the target. Postgres adapter can be re-added later. - **Server-side overlay compositing** — overlays are metadata-only per glossary decision. - **NATS JetStream event bus** — database-backed queue is sufficient. NATS is a future adapter swap. - **User-level permissions for channel access** — glossary notes this as future work. - **BXF format support** — not needed for a self-hosted tool. - **Real-time adaptive bitrate** — Playout Service produces a single quality HLS stream. ABR (multiple renditions) is future work. ## Further Notes - The domain glossary at `crates/CONTEXT.md` is the source of truth for terminology. All implementation should use glossary terms. - ADR-0001 (library as source of truth), ADR-0002 (k-tv owns the stream), ADR-0003 (database-backed event queue) are all accepted and partially implemented. This roadmap completes them. - The MCP crate should be expanded alongside each phase — as new capabilities land (interstitials, fill strategies, schedule analysis), expose them as MCP tools so the agent can use them creatively. - Suggested phase order: 1 → 2 → 3 → 4 → 5. Phases 1-2 are pure domain work with no infrastructure dependencies. Phase 3 is the big infrastructure build. Phase 4 integrates everything. Phase 5 is a nice-to-have.
GKaszewski added the ready-for-agentprd labels 2026-07-12 05:46:10 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: GKaszewski/k-tv#2