Files
archy/apps/podsteadr-mediamtx/manifest.yml
ssmithx 4bbd70281b chore(apps): bump podsteadr-mediamtx to 1.20.0
Released 2026-08-05. Fixes land directly on this app's feature surface:
HLS muxer recomputes AAC PTS in MPEG-TS segments (iOS playback precision,
relevant since the app's config sets hlsVariant: mpegts), fixes a
goroutine leak during HLS part rotation, fixes a Chrome WebRTC "packet
lost" false-positive and non-deterministic WebRTC track ordering (WHIP
ingest), and fixes OBS multitrack RTMP URL parsing. No breaking config
changes; new opt-in features (native forwarding, MoQ draft support)
don't affect this config (moq: no already). Matches the same bump on
the podsteadr repo's docker-compose.yml.
2026-08-08 00:18:02 +00:00

165 lines
5.9 KiB
YAML

app:
id: podsteadr-mediamtx
name: podsteadr MediaMTX
version: "1.20.0"
description: MediaMTX ingest/output backend for podsteadr — RTMP + WebRTC/WHIP ingest, HLS playback, stream recording.
category: media
# Hyphenated name matches the podsteadr repo's docker-compose container_name
# (podsteadr-mediamtx); alias `mediamtx` is the short hostname podsteadr's
# server reaches it by (MEDIAMTX_API_URL=http://mediamtx:9997) and the one
# baked into mediamtx.yml's authHTTPAddress callback below.
container_name: podsteadr-mediamtx
container:
image: docker.io/bluenviron/mediamtx:1.20.0
pull_policy: if-not-present
network: podsteadr-net
network_aliases: [mediamtx]
derived_env:
# Browsers need a reachable ICE host candidate for WebRTC/WHIP; without
# this, the offer only advertises container-internal addresses and
# publish/playback negotiation fails for anyone off-host.
- key: MTX_WEBRTCADDITIONALHOSTS
template: "{{HOST_MDNS}}"
dependencies:
- storage: 10Gi
resources:
cpu_limit: 1
memory_limit: 512Mi
disk_limit: 10Gi
security:
# Stock mediamtx image runs as container-root (no USER directive) but
# only ever writes to the bind-mounted /recordings — CHOWN/DAC_OVERRIDE
# cover the fresh-bind-dir-ownership gap the same way apps/botfights and
# apps/immich document (root uid inside the container does not
# automatically bypass DAC checks once cap-drop ALL applies). Unverified
# against a real install; check first-boot logs on initial deploy.
capabilities: [CHOWN, DAC_OVERRIDE]
readonly_root: true
no_new_privileges: true
network_policy: isolated
ports:
- host: 1935
container: 1935
protocol: tcp
auth: none
auth_rationale: >-
RTMP ingest (OBS). Not HTTP, so the node's session gate has no login
page to serve here; publish auth is delegated to podsteadr's own
HTTP auth webhook (authHTTPAddress below), which checks a per-stream
secret key never exposed in this port mapping.
- host: 8889
container: 8889
protocol: tcp
auth: none
auth_rationale: >-
WebRTC/WHIP ingest — browsers publish directly with a per-stream
bearer secret checked by podsteadr's auth webhook, the same
protocol-level auth as the RTMP port above.
- host: 8189
container: 8189
protocol: udp
auth: none
auth_rationale: >-
WebRTC ICE/UDP media transport. Raw UDP has no HTTP session concept
for the gate to enforce.
- host: 8890
container: 8888
protocol: tcp
auth: none
auth_rationale: >-
Public HLS playback URL, handed out to viewers and podcast/livestream
clients outside the node (zap.stream, third-party players). A login
page here would break every external viewer; playback is read-only.
volumes:
- type: bind
# Shared with apps/podsteadr (mounted read-only there) so the app can
# list and remux finished recordings for one-click episode publishing.
source: /var/lib/archipelago/podsteadr/recordings
target: /recordings
options: [rw]
- type: bind
source: /var/lib/archipelago/podsteadr-mediamtx/config/mediamtx.yml
target: /mediamtx.yml
options: [ro]
environment: []
files:
- path: /var/lib/archipelago/podsteadr-mediamtx/config/mediamtx.yml
overwrite: true
content: |
# MediaMTX configuration for podsteadr.
# Ingest: RTMP (OBS) + WebRTC/WHIP (browser). Output: HLS. Publish auth is
# delegated to podsteadr via HTTP; stream status is polled from the API.
logLevel: info
api: yes
apiAddress: :9997
# ---- authentication ------------------------------------------------------
authMethod: http
authHTTPAddress: http://podsteadr-app:8095/api/mediamtx/auth
authHTTPExclude:
- action: api
- action: metrics
- action: pprof
# ---- protocols -----------------------------------------------------------
rtsp: no
srt: no
moq: no
rtmp: yes
rtmpAddress: :1935
hls: yes
hlsAddress: :8888
# Standard HLS, not lowLatency: LL-HLS's small per-part buffering window has very little
# tolerance for B-frame reordering (common in most OBS encoder presets), and a real test
# stream crashed the muxer twice in ~2 minutes with "too many reordered frames" / "unable to
# extract DTS" once frame timing got even slightly irregular. Standard HLS buffers a full
# segment before finalizing, which absorbs that jitter — a few extra seconds of latency
# instead of intermittent muxer crashes / viewer buffering.
hlsVariant: mpegts
hlsAlwaysRemux: yes
hlsAllowOrigins: ["*"]
webrtc: yes
webrtcAddress: :8889
webrtcLocalUDPAddress: :8189
webrtcAllowOrigins: ["*"]
# ---- recording -----------------------------------------------------------
pathDefaults:
record: yes
recordPath: /recordings/%path/%Y-%m-%d_%H-%M-%S-%f
recordFormat: fmp4
recordPartDuration: 1s
recordSegmentDuration: 1h
recordDeleteAfter: 168h
paths:
# Streams live at live/<streamId>; publish requires the stream secret,
# which podsteadr checks in the auth webhook.
"~^live/[A-Za-z0-9]+$": {}
health_check:
# Stock mediamtx image has no shell, so an in-container HTTP probe of the
# API isn't meaningfully cheaper than TCP; RTMP liveness is enough (same
# polling-not-hooks rationale as podsteadr's own status poller, which
# exists precisely because runOn*-style shell hooks aren't available on
# this image).
type: tcp
endpoint: localhost:1935
interval: 30s
timeout: 5s
retries: 3