01
A guide that matches the stream
ProblemA program guide has to say what will be playing at any point in the next two days. If the guide and the stream choose media by different rules, especially with schedule blocks and shuffle, the guide is wrong.
DecisionFor dynamic channels the guide calls the same playlist resolver the stream uses, at each point in time, and re-checks at the exact times schedule blocks change rather than on a fixed interval. Shuffle is date-seeded, so both ask for the same order and get it.
Why it matteredThere is one definition of “what plays when,” and guide entries land on real block boundaries.
02
Replacing tracked state with arithmetic
ProblemThe first timeline design stored each channel's file index and position and advanced them on a timer while it streamed. That state had to be kept in step with the stream and survive pauses and restarts, and it produced progression bugs.
DecisionReplaced it with a single anchor per channel. Position is derived from elapsed time whenever it's needed, and the periodic update loop was removed.
Why it matteredThe stream, the guide, and a freshly restarted server all compute the same position from one stored value.
03
From one FFmpeg per file to one per channel
ProblemThe original pipeline spawned an FFmpeg process for every file. Each episode boundary meant stopping one encoder and starting the next, with bumper segments spliced into the live playlist to cover the gap, and races around those transitions.
DecisionMoved to FFmpeg's concat demuxer: one looping process per channel over a playlist of episodes and bumpers. Everything is re-encoded to identical parameters, timestamps are regenerated across files, and keyframes are forced onto segment boundaries.
Why it matteredTransitions happen inside the encoder rather than between processes, and seeking into a channel works (to the nearest keyframe for some codecs).
04
Output strict players accept
ProblemSome clients reject what others tolerate. The code records Roku's strictness about timestamp continuity and compatibility work for Wine/MediaFoundation-based playback.
DecisionBumpers are re-encoded rather than stream-copied so their timestamps reset, audio is AAC-LC, frame rate is constant, and segments moved from MPEG-TS to fragmented MP4. Segment caching dropped from an hour to 30 seconds with revalidation, so a restarted channel doesn't serve stale segments.
Why it matteredChannels play in stricter clients, not just in a browser player.
05
Recovering from failure and restarts
ProblemA server restart leaves channels marked as streaming with no encoder behind them, FFmpeg can crash mid-stream, and a bumper overwritten while it's being read corrupts the stream.
DecisionChannels follow an explicit state machine, and on boot orphaned states are walked back to idle. State is auto-saved and restored, with streaming resumed only after media has been scanned. Crashed encoders are restarted, bumper writes are atomic, integrity-checked, and retried, and a per-channel mutex guards transition state.
Why it matteredRestarts and encoder failures recover on their own instead of leaving channels stuck.
06
Filesystem paths as untrusted input
ProblemLibrary paths and filenames flow into FFmpeg arguments and concat files, and filesystem errors can leak server paths through the API.
DecisionPaths are checked for traversal and constrained to allowed library roots, slugs are validated, and concat entries are escaped unquoted to avoid FFmpeg's quote-escaping bugs. Error messages are scrubbed of filesystem paths and API responses return relative paths. Admin accounts use bcrypt, with session or API-key auth, Helmet headers, and rate limiting.
Why it matteredA self-hosted service that shells out to FFmpeg shouldn't trust its own media library.