autumn-media-plugin 0.10.0

Live-streaming media plugin (broadcast + rooms) for autumn-web applications
Documentation

autumn-media-plugin

autumn-media-plugin adds live-streaming media to an autumn-web application. It packages the two primitives an interactive streaming product needs:

  • Broadcast — one creator ingests (RTMP / WHIP / browser WebRTC) and a fan-out audience watches over low-latency WebRTC/WHEP with an HLS fallback, backed by MediaMTX; recordings become VODs plus clip/highlight encodes.
  • Room — a small mesh (no SFU) multi-participant call, capped by room_max_participants (default 6).

Installation

autumn plugin add autumn-media-plugin

One command adds the dependency at a version compatible with your app's autumn-web, mounts the plugin in your autumn_web::app() builder chain, and prints any configuration still needed. It is safe to re-run, and it refuses — before touching any file — to install into an app on an incompatible autumn-web version. See docs/plugins.md.

Manual install

If you would rather wire it yourself (or autumn plugin add could not find your builder chain and printed these lines for you):

[dependencies]
autumn-web = "0.10"
autumn-media-plugin = "0.10"

[media] configuration

The plugin is configured by a [media] section in your Autumn profile:

[media]
room_max_participants  = 6           # hard cap, mesh, no SFU (1..=6)
room_token_ttl_seconds = 300         # room session-token lifetime
# room_namespace       = "tenant-a"  # optional MediaMTX path namespace

[media.mediamtx]
api_base            = "http://127.0.0.1:9997"
rtmp_base           = "rtmp://127.0.0.1:1935/live"
hls_base            = "http://127.0.0.1:8888"
hls_probe_base      = "http://mediamtx:8888"     # falls back to hls_base
webrtc_base         = "http://127.0.0.1:8889"
playback_base       = "http://127.0.0.1:9996"
playback_probe_base = "http://mediamtx:9996"     # falls back to playback_base

[media.ffmpeg]
bin = "/usr/bin/ffmpeg"

[media.storage]
backend           = "s3"             # local | s3   (default: local)
bucket            = "${MEDIA_BUCKET}"
endpoint_url      = "https://t3.storage.dev"
region            = "auto"
access_key_id     = "${MEDIA_S3_KEY}"
secret_access_key = "${MEDIA_S3_SECRET}"
public_base_url   = "https://cdn.example.com/media"  # required unless endpoint_url is Tigris
key_prefix        = "media"
force_path_style  = false

[media.recording]
retention_days = 14                  # 0 disables the sweep

${VAR} placeholders resolve from the environment, and AUTUMN_MEDIA__<TABLE>__<FIELD> variables override individual leaves.

Mounting

Autumn resolves config after Plugin::build runs, so the plugin cannot read [media] itself. Resolve a MediaConfig up front and pass it in — the same from_config(&cfg) -> … pattern autumn-storage-s3 uses:

use autumn_media_plugin::{prelude::*, MediaPlugin};

let media = MediaConfig::from_autumn_toml("autumn.toml")?;
media.validate()?;

autumn_web::app()
    .plugin(MediaPlugin::new().config(media).with_broadcast().with_rooms())
    .run()
    .await;

Both primitives are off by default. If you enable neither, the plugin installs no routes, extensions or jobs. It logs a warning.

Builder call Installs
with_broadcast() MediaMtxClient and MediaUrls extensions
with_rooms() Room routes, RoomService and the room reaper
Either one MediaStorage, MediaWorkflows and the encode jobs

The retention sweep also starts if you set recordings_root. If the storage config is not valid, the plugin installs no storage, workflows, jobs or sweep.

Migrating from Arroyo

MediaConfig::from_arroyo_env() maps an existing Arroyo deployment's ARROYO_* (and AWS_* / BUCKET_NAME) environment onto a MediaConfig, so an operator changes nothing when adopting the plugin.

Room routes

with_rooms() nests five routes under the API prefix (default /api/media) and installs a RoomService on AppState:

Method Path Purpose
POST /api/media/rooms Create a room.
POST /api/media/rooms/{room_id}/join Join; returns a session token and mesh WHIP/WHEP targets. A display_name longer than 64 characters gets 400.
POST /api/media/rooms/{room_id}/leave Leave.
POST /api/media/rooms/{room_id}/heartbeat Hold the seat: refresh liveness, renew the token expiry.
GET /api/media/rooms/{room_id} Member-gated roster (Authorization: Bearer <token>).

Create and join are #[secured]: they need an authenticated session and return 401 to an anonymous caller. Leave, heartbeat and the roster are authorized by the per-room session token join returns. Set room_rate_limit_per_minute to limit each client IP per route (off by default). Behind a reverse proxy, also set [security.trusted_proxies], or all clients share the proxy's IP.

A heartbeat renews the token expiry, but never past joined_at + room_session_max_seconds (default 12 hours). After that, heartbeat and roster return 404. The client leaves (leave accepts a token of any age), then joins again.

A background reaper reclaims seats and rooms that go quiet. A client holds its seat by sending a heartbeat, or by polling the roster, on any interval under the idle TTL (default 15 minutes). Room state lives in process memory by default; set room_store_backend = "db" for a shared store that survives restarts and is safe across processes: joins from two processes never pass the seat cap.

Status

Usable. Broadcast (ingest, playback URLs, recording, retention), the encode workflows, the MediaMTX transport client, mesh rooms, and the from_arroyo_env shim all ship. See docs/guide/media.md for the full guide.