WebRTC, HLS, and FLV Livestreaming
What This Config Controls
Section titled “What This Config Controls”livestream controls WHIP and RTMP publishing, external WHEP/RTMP/RTSP/HTTP-FLV pulls, WebRTC sessions, HLS segments, HTTP-FLV connections, and live caches.
If you only use on-demand media providers, most values can remain at their defaults.
Livestream Boundaries
Section titled “Livestream Boundaries”The livestream subsystem handles ingest, RTP/frame distribution, WHEP/FLV playback, HLS remuxing, HLS storage, and cross-node publisher registration. On-demand providers resolve external media URLs and Range proxy requests. The livestream subsystem carries a stream while a publisher produces it.
- WHIP ingest: A publisher sends an SDP offer to the main HTTP service. SyncTV negotiates H.264/Opus, keeps RTP, and generates H.264/AAC frames.
- WHEP playback: A client sends an SDP offer and receives WebRTC media. The source must provide RTP.
- RTMP ingest: Publishers connect through RTMP. During authentication, the publisher is registered on the local node or shared registry.
- RTSP ingest: External sources are pulled through DESCRIBE/SETUP/PLAY and enter the same StreamHub after RTP depacketization.
- StreamHub: RTP packets and remuxed frames use separate in-process channels. RTMP, RTSP, and HTTP-FLV sources provide frames only.
- HTTP-FLV: Best for low latency. Clients hold long-lived responses, and write timeouts protect the server from slow readers.
- HLS: Best for broad player compatibility and shared storage/CDN-like deployments. It has higher latency but scales well across replicas.
Choosing a Playback Protocol
Section titled “Choosing a Playback Protocol”| Protocol | Strength | Cost | Best fit |
|---|---|---|---|
| WHEP | WebRTC latency, browser media tracks, and ICE/TURN connectivity | The source must retain RTP; both peers need reachable ICE candidates | WHIP ingest, external WHEP proxying, low-latency browser playback |
| HTTP-FLV | Low latency and short server path | Many long-lived connections; slow clients need strict timeouts | Interactive livestreaming and room-synchronized viewing |
| HLS | Broad player compatibility; playlist/segment pull model; works with shared storage | Higher latency and segment storage management | Mobile clients, generic players, multi-replica deployments, object storage |
WHIP Publishing and WHEP Playback
Section titled “WHIP Publishing and WHEP Playback”SyncTV serves WHIP and WHEP resources on the main HTTP(S) service. Both protocols use one SDP offer and answer. The server gathers a complete candidate set and does not implement trickle ICE PATCH.
| Operation | HTTP resource | Authentication |
|---|---|---|
| Create publish key | POST /api/playback-providers/{roomId}/rtmp/{mediaId}/publish-key |
Current user token and publish permission |
| Create WHIP publish | POST /api/playback-providers/{roomId}/rtmp/{mediaId}/whip |
Bearer <publish-key> |
| Delete WHIP publish | DELETE <Location from POST> |
The same publish key |
| Play managed live | POST /api/playback-providers/{roomId}/live/{mediaId}/whep |
Current user or guest access token |
| Play LiveProxy | POST /api/playback-providers/{roomId}/live-proxy/{mediaId}/whep |
Current user or guest access token |
| Delete WHEP playback | DELETE <Location from POST> |
The same viewer identity |
POST requests use Content-Type: application/sdp. A successful response contains 201 Created, an SDP answer, and a same-origin Location. Send DELETE when the publisher or player stops. SyncTV also cleans local resources after disconnect, session expiry, and server shutdown.
The public name live identifies a WHIP/RTMP source managed by SyncTV. liveProxy identifies an external WHEP/RTSP/RTMP/HTTP-FLV source pulled by SyncTV. The protobuf wire/source-config oneof uses RTMP/rtmp for SyncTV-managed live sources.
Use POST /api/providers/prepare/live for the Provider prepare API. Generated HLS and HTTP-FLV playback URLs also use /api/playback-providers/{roomId}/live/....
Use live to create managed live media from the CLI. rtmp remains an input alias:
synctv media add <ROOM_ID> --username alice \ --source-provider live \ --source-config-json '{"mode":"default"}' \ --name "Livestream"The publish-key API returns rtmp_url, stream_key, and whip_url. Set the public HTTP(S) origin in production:
livestream: public_webrtc_base_url: "https://live.example.com" webrtc: enabled: true ice_gathering_timeout_seconds: 10 max_sdp_bytes: 262144 max_sessions: 1000 max_session_duration_seconds: 86400An empty public_webrtc_base_url makes the publish-key response return a WHIP URL relative to the current API origin. The configured value must be an HTTP(S) origin without a path, query, or fragment.
SDP API Examples
Section titled “SDP API Examples”A WHIP client sends the publish key as a Bearer credential:
curl -i -X POST "$WHIP_URL" \ -H "Authorization: Bearer $PUBLISH_KEY" \ -H "Content-Type: application/sdp" \ --data-binary @offer.sdpA WHEP client sends a token that can access the room:
curl -i -X POST "$WHEP_URL" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/sdp" \ --data-binary @offer.sdpcurl verifies signaling only. Media transfer requires a WebRTC peer. A browser client creates recvonly audio and video transceivers, waits for ICE gathering, POSTs pc.localDescription.sdp, and sets the response as its remote answer. It sends DELETE to Location when playback ends.
In OBS, select the WHIP service and enter the returned whip_url and publish key. GStreamer requires the rswebrtc plugin with whipclientsink and H.264/Opus output. Property names differ by plugin version; gst-inspect-1.0 whipclientsink lists the endpoint and auth-token properties. FFmpeg builds do not expose one consistent WHIP muxer. Check ffmpeg -muxers; a build without WHIP can publish H.264/AAC to the returned rtmp_url.
Codecs and Conversion
Section titled “Codecs and Conversion”WHIP and external WHEP accept H.264 constrained baseline (profile-level-id=42e01f, packetization-mode=1) and Opus. Fixing the H.264 profile keeps untranscoded WHEP RTP output consistent with its SDP. SyncTV depacketizes H.264 into AVC frames and decodes Opus before encoding AAC, so the same input feeds HLS and HTTP-FLV.
SyncTV implements standard WHIP resource semantics and WHEP draft resource semantics. Vendor-specific signatures, signaling APIs, simulcast controls, and ingest SDKs require a gateway that exposes a standard WHIP or WHEP endpoint.
External WHEP Sources
Section titled “External WHEP Sources”LiveProxy can relay a remote WHEP resource to local WHEP, HLS, and HTTP-FLV clients:
{ "provider": "liveProxy", "source": { "protocol": "whep", "url": "https://origin.example.com/whep/channel-1", "authorization": "Bearer upstream-token" }}SyncTV applies SSRF checks and DNS pinning to the endpoint, rejects redirects, and requires 201 Created, application/sdp, and a same-origin Location. SDP reads have size and time limits. SyncTV DELETEs the upstream resource when the local pull ends. Management responses omit authorization, and logs omit both the upstream URL and credential.
The WHEP URL may carry a query token. It cannot contain userinfo or a fragment; place HTTP header credentials in authorization.
ICE, STUN, and TURN
Section titled “ICE, STUN, and TURN”livestream.webrtc.ice_servers configures ICE servers for livestream PeerConnections. The top-level webrtc section controls room voice/media P2P and the built-in STUN listener. These settings apply to separate WebRTC features.
livestream: webrtc: ice_servers: - urls: - "stun:stun.example.com:3478" - "turn:turn.example.com:3478?transport=udp" - "turns:turn.example.com:5349?transport=tcp" username: "synctv" credential_file: "/run/secrets/livestream_turn_credential"STUN lets a peer discover an address. A server behind Kubernetes, NAT, or a strict firewall needs a TURN service reachable by clients and pods. TURN/UDP gives lower latency. TURN/TLS over TCP works on networks that block UDP.
WHIP/WHEP signaling uses the existing HTTP Service. ICE media uses dynamic UDP ports, so the Helm chart does not create a fixed WebRTC media UDP Service. Kubernetes deployments should use TURN. A hostNetwork deployment must expose the node’s ICE UDP port range. stunService exposes the built-in STUN listener and does not relay media.
External RTSP Sources
Section titled “External RTSP Sources”RTSP sources use an explicit protocol configuration. Each media item owns an independent pull session, so multiple media items can pull multiple cameras or streaming servers concurrently. A node accepts up to 100 external streams by default.
{ "provider": "liveProxy", "source": { "protocol": "rtsp", "url": "rtsp://camera.example/live", "transport": "tcp", "videoTrack": { "mode": "firstCompatible" }, "audioTrack": { "mode": "firstCompatible" } }}Available settings:
| Field | Values | Meaning |
|---|---|---|
transport |
tcp / udp |
TCP uses interleaved RTP; UDP targets controlled LAN deployments |
videoTrack |
firstCompatible / index / disabled |
Select an H.264/H.265 video track |
audioTrack |
firstCompatible / index / disabled |
Select an AAC audio track |
Select SDP video track 1 and disable audio:
{ "videoTrack": { "mode": "index", "index": 1 }, "audioTrack": { "mode": "disabled" }}Basic and Digest authentication accept RTSP URL userinfo, for example rtsp://user:[email protected]/live. Logs remove userinfo, query, and fragment. Production deployments should manage credential-bearing source configs through controlled configuration paths.
RTSP ingest supports H.264, H.265, and AAC. SyncTV maps these codecs to AVC/HEVC/AAC FLV tags, then reuses the existing HTTP-FLV, HLS, cluster registration, cross-node relay, idle cleanup, and reconnect paths.
External RTMP Sources
Section titled “External RTMP Sources”External RTMP sources use mode to select which media messages enter the local stream. RTMP carries audio and video as separate message types, so this setting filters message types instead of selecting an RTSP track index.
{ "provider": "liveProxy", "source": { "protocol": "rtmp", "url": "rtmp://origin.example/live/stream", "mode": "default" }}mode accepts default (audio and video), videoOnly (video only), or audioOnly (audio only). External RTMP pulls filter in the pull session, while SyncTV-managed RTMP publishing filters in the authenticated server session. HTTP-FLV and HLS receive the same filtered Live StreamHub data.
RTSP videoTrack and audioTrack must enable at least one track. A double-disabled configuration is rejected while adding the media; track index existence and codec compatibility remain validated when the RTSP SDP connection is established.
RTSP, external RTMP, and internal RTMP publishing enter the same Live StreamHub. SyncTV generates HLS and HTTP-FLV for all three; HLS uses a short sliding window, and TS segments are evicted with that window. Arbitrary seeking and durable recording are outside the current product scope.
HLS Backend Design
Section titled “HLS Backend Design”memory is the default. Segments exist only inside the current process. It is the simplest option for single-replica, development, and low-traffic multi-replica deployments.
Limitations: process restarts lose segments; in multi-replica deployments, non-publisher nodes must read playlist/segment data from the publisher node through the HLS gRPC proxy. It works, but it is not the preferred backend for high-traffic production HLS.
file writes segments to this node’s hls_storage.path. Single replicas can use local disk. Multi-replica deployments can use local disk with publisher-node proxying.
Do not configure shared storage as file; if every replica can read and write the same path, use shared_file.
shared_file writes segments to hls_storage.path visible to every replica. The publisher node writes segments, and any node that receives a .ts request reads the file from its own shared mount.
Typical choices: NFS, RWX PVCs, or CSI shared volumes. Do not configure emptyDir, /tmp, or node-local disks as shared_file.
s3 stores segments in S3-compatible object storage. It gives Kubernetes and cross-node deployments an explicit shared-storage boundary.
Typical choices: AWS S3, MinIO, Cloudflare R2, or compatible services. Configure endpoint, bucket, access key, and secret key.
How Clustered Livestreaming Works
Section titled “How Clustered Livestreaming Works”- A publisher connects through WHIP or RTMP to any SyncTV node. LiveProxy may also pull external WHEP on any node.
- After authentication, that node registers
room_id/media_id -> node_id/api_addressin the publisher registry. In cluster mode, the registry uses Redis. - The local StreamHub receives frames. WHIP/WHEP sources also retain RTP. HTTP-FLV and HLS subscribe to frames; WHEP subscribes to RTP.
- HLS segments are written to the selected
memory,file,shared_file, ors3backend. - A non-publisher node locates the owner through the shared registry. Separate gRPC streams relay frames and RTP, and generation ID plus lease epoch fence stale publishers.
- With
shared_file, TS segment requests are read by the current node from the shared path. Local backends still use publisher-node HLS gRPC proxying for remote playlist/segment reads.
livestream.rtmp_port default: 1935.
OBS push URLs usually look like:
rtmp://your-domain:1935/live/<stream-key>The managed live provider media source config accepts the same mode field:
{ "provider": "live", "mode": "audioOnly"}This setting controls which media types enter StreamHub. Playback continues through SyncTV-generated HLS or HTTP-FLV URLs.
Change the port if another RTMP service already uses 1935.
livestream.public_rtmp_host default: empty.
Set it in production when the address returned to streamers must be a public domain, LoadBalancer address, or node public address instead of a local bind address:
livestream: public_rtmp_host: "live.example.com"If empty, SyncTV falls back only to the local bind address, which is suitable for local development or single-node LAN testing. It does not use Pod IPs or the internal server.advertise_host as the public RTMP address.
GOP Cache
Section titled “GOP Cache”livestream.gop_cache_size default: 2.
GOP cache helps new viewers start playback sooner without waiting for the next keyframe.
livestream.gop_cache_max_memory_mb default: 100.
This is a per-stream limit. Total GOP cache memory can approach this value multiplied by the number of active live streams.
Increase it when:
- Bitrate is high.
- New viewers join frequently.
Decrease it when memory is limited.
Pull Retry Settings
Section titled “Pull Retry Settings”| Field | Default | Purpose |
|---|---|---|
livestream.stream_timeout_seconds |
300 |
Stop idle pull streams after this duration |
livestream.pull_max_retries |
10 |
Maximum pull retry attempts |
livestream.pull_initial_backoff_ms |
1000 |
Initial retry backoff |
livestream.pull_max_backoff_ms |
30000 |
Maximum retry backoff |
Backoff grows over time to avoid hammering unstable upstream sources.
FLV Safety
Section titled “FLV Safety”livestream.max_flv_tag_size_bytes default: 10485760, which is 10 MiB.
This prevents abnormal streams from forcing excessive memory allocation. Do not increase it unless you know the upstream emits larger valid FLV tags.
livestream.hls_storage.type default: memory.
Allowed values:
| Value | Meaning | Use case |
|---|---|---|
memory |
Keep HLS segments in process memory | Single replica, temporary livestreams, development |
file |
Write HLS segments to this node’s livestream.hls_storage.path |
Single-replica file storage or small multi-replica deployments with publisher-node proxying |
shared_file |
Write HLS segments to a shared livestream.hls_storage.path visible to every replica |
Multi-replica shared filesystem; TS segments are read by the current node from the shared path |
s3 |
Write HLS segments to S3-compatible object storage | Multi-replica Kubernetes or cross-node storage |
Canonical values are memory, file, shared_file, and s3.
Cluster mode can use memory or file. Non-publisher nodes read playlist/segment data from the publisher node through the HLS gRPC proxy. This is acceptable for small deployments or validation environments; high-traffic production HLS should use either:
shared_filewithhls_storage.pathmounted from a filesystem all replicas can read and write..tsrequests are served by the current node from that shared path.s3withlivestream.hls_storage.*configured.
livestream.hls_storage.memory_max_mb default: 0, which means the built-in default is used.
This applies when hls_storage.type=memory. The current built-in default is 512 MB.
Shared File Backend
Section titled “Shared File Backend”livestream: hls_storage: type: "shared_file" path: "/var/lib/synctv/hls"shared_file explicitly means the path is shared by all replicas, for example through NFS, an RWX PVC, or a CSI volume. Do not configure pod-local emptyDir, /tmp, or node-local disks as shared_file.
In shared_file mode, the publisher node writes HLS segments; any node that receives a .ts request reads the segment from its own mounted shared path instead of fetching the TS file from the publisher node.
livestream.hls_storage.path default: empty.
This is used when hls_storage.type=file or shared_file. Relative paths are resolved under data_dir:
data_dir: "/var/lib/synctv"livestream: hls_storage: type: "file" path: "livestream/hls"Effective path:
/var/lib/synctv/livestream/hlsS3-Compatible Object Storage
Section titled “S3-Compatible Object Storage”When hls_storage.type=s3, SyncTV stores HLS segments in S3-compatible object storage:
livestream: hls_storage: type: "s3" endpoint: "https://s3.example.com" bucket: "synctv-hls" region: "auto" base_path: "synctv/hls/"Inject credentials through environment variables or secret files:
export SYNCTV_LIVESTREAM_HLS_STORAGE_ACCESS_KEY_ID="..."export SYNCTV_LIVESTREAM_HLS_STORAGE_SECRET_ACCESS_KEY="..."| Field | Default | Meaning |
|---|---|---|
livestream.hls_storage.endpoint |
"" |
S3-compatible endpoint such as AWS S3, MinIO, Cloudflare R2, or a compatible service |
livestream.hls_storage.bucket |
"" |
Bucket used for HLS segments |
livestream.hls_storage.access_key_id |
"" |
Access key ID; supports access_key_id_file |
livestream.hls_storage.secret_access_key |
"" |
Secret access key; supports secret_access_key_file |
livestream.hls_storage.region |
null |
S3 region; leave empty or use the provider-required value for compatible services |
livestream.hls_storage.base_path |
hls/ |
Object key prefix inside the bucket; normalized without a leading / and with a trailing / |
HTTP-FLV
Section titled “HTTP-FLV”livestream.flv_max_connection_duration_seconds default: 86400.
This limits a single HTTP-FLV connection to 24 hours. Setting it to 0 disables the limit, but that is not recommended for production.
livestream.flv_write_timeout_seconds default: 30.
If a client is too slow and writes remain blocked beyond this timeout, SyncTV disconnects the client to protect server resources.