All projects

Broadcast delivery and continuity

Croccante

An RTMP relay with independently supervised destinations, explicit broadcast lifecycle control, and prepared continuity media.

I designed and built this project independently, using my own resources.

Explore the project

Public repository

Technical documentation

Self-hosted relay. Docker setup, architecture, and operation notes are in the repository.

Technologies

  • Python
  • nginx-rtmp
  • FFmpeg
  • RTMP

01 / Context

The problem behind the project

A broadcaster needs to deliver one program to several destinations without allowing one outage to interrupt every output.

The operating problem is a live publisher losing its uplink while the audience is still watching. A captive portal, a dropped connection, or a restarting encoder should not force every destination to end the show. The relay therefore runs away from the publisher and treats the broadcast session as something longer lived than its incoming network connection.

The same program may go to several platforms. A destination rejecting a connection needs its own retry behavior; restarting the entire fan-out would turn one platform’s failure into an outage for the others.

02 / Implementation

Design and implementation

I build the relay lifecycle, per-destination supervision, and continuity behavior used by the composed broadcast path.

I built the container, private control service, destination resolver, supervisor scripts, durable filler preparation, health checks, telemetry, and media smoke harness. nginx-rtmp receives one program; a separate supervised FFmpeg process copies its encoded audio and video to each selected destination.

The control path is Alcántara → Alana → Croccante. Alcántara chooses the destinations, Alana prepares and coordinates the renderer, and Croccante resolves the exact credential versions and owns the outgoing connections. A publisher can connect while the service is stopped without making anything public.

For continuity, a source image or video is checksum-verified and prepared once against the live H.264/AAC profile. The completed artifact is installed atomically on durable storage. A broadcast binds one immutable filler version, so preparing a replacement cannot silently change an active show.

03 / Architecture

Architecture and workflow

  1. Prepare the session

    Resolve the complete destination selection and verify a ready filler version before opening outputs. A failed preparation leaves the session stopped.

  2. Start deliberately

    An authenticated, idempotent command opens the session. Increasing command sequences prevent a delayed Start from overriding a newer Stop.

  3. Supervise each destination

    Each destination moves through waiting, relaying, filler, and retry backoff independently. Fast repeated failures back off; healthy destinations continue.

  4. Survive publisher loss

    After live media has appeared, loss of the publisher selects prepared filler until it returns. Losing media or the control connection never means Stop.

  5. Stop explicitly

    Stop terminates every outgoing publisher, removes resolved destination state, and returns supervisors to idle without restarting the container.

04 / Approach

Engineering decisions

Relay without another encode

The program is already encoded upstream, so each destination uses stream copy. This avoids another generation of quality loss and per-destination encoder cost; bandwidth, connection supervision, and packet forwarding still grow with the number of outputs.

One failure domain per destination

I chose long-lived supervisors over opaque nginx push retries so each output has an observable process, mode, retry budget, and health state. A rejecting platform can back off without recycling another platform’s healthy connection.

Prepare before going live

Checksum verification, transcoding, and media-profile checks happen before Start. An atomic installation protects already-ready filler from a failed preparation, and the active session pins one version so a later edit cannot alter the live recovery path.

Keep session authority outside media observations

A publisher connecting or disappearing is only an observation. Authenticated sequenced commands own Start and Stop. This prevents an uplink interruption, a stale command, or a control-plane outage from making an unintended publication decision.

05 / Evidence

Evidence and outcomes

The architecture notes record a real YouTube continuity experiment: the live-to-filler transition had a 0.18-second outbound gap, the return to live had a 2.40-second gap, and one continuous broadcast survived a five-minute publisher absence. These are observations from that experiment, rather than a latency promise for every platform.

The Docker smoke harness uses local RTMP and RTMPS recorders and checks received media bytes, authenticated lifecycle commands, process ownership, and transition gaps. Its five-second transition threshold turns continuity into a regression check rather than a visual assumption.

Health checks reconcile nginx’s publisher view with hook markers and supervisor processes. This catches the case where the ingest port is healthy but a broken hook prevents any media from reaching the outputs. Private metrics expose mode changes, retries, backoff, and filler activity without stream keys or destination URLs.

Source and technical notes

06 / Current scope

Where it stands

A broadcast infrastructure component with documented lifecycle and recovery contracts.

Switching between live input and filler reconnects the outgoing RTMP process. A persistent FIFO-fed output could avoid that reconnect, but would require a different fan-out architecture. The recorded test supported retaining the simpler supervised design; a less tolerant destination would require revisiting it.

The relay copies an already encoded program. Encoding and composition remain Alana’s responsibility. Prepared filler must match the live profile, and an active destination selection stays fixed until an explicit Stop and new Start.

The implementation also accounts for nginx-rtmp’s worker-local stream state, FFmpeg socket timeouts, and the different permissions of nginx hooks and root-owned relay processes. A native I/O shim keeps resolved publish URLs out of FFmpeg process arguments.