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(default6).
Installation
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):
[]
= "0.10"
= "0.10"
[media] configuration
The plugin is configured by a [media] section in your Autumn profile:
[]
= 6 # hard cap, mesh, no SFU (1..=6)
= 300 # room session-token lifetime
# room_namespace = "tenant-a" # optional MediaMTX path namespace
[]
= "http://127.0.0.1:9997"
= "rtmp://127.0.0.1:1935/live"
= "http://127.0.0.1:8888"
= "http://mediamtx:8888" # falls back to hls_base
= "http://127.0.0.1:8889"
= "http://127.0.0.1:9996"
= "http://mediamtx:9996" # falls back to playback_base
[]
= "/usr/bin/ffmpeg"
[]
= "s3" # local | s3 (default: local)
= "${MEDIA_BUCKET}"
= "https://t3.storage.dev"
= "auto"
= "${MEDIA_S3_KEY}"
= "${MEDIA_S3_SECRET}"
= "https://cdn.example.com/media" # required unless endpoint_url is Tigris
= "media"
= false
[]
= 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 ;
let media = from_autumn_toml?;
media.validate?;
app
.plugin
.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.