Skip to content

WebRTC, HLS, and FLV Livestreaming

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.

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.

SyncTV livestream pipeline showing WHIP, RTMP, and external pulls entering StreamHub, then reaching WHEP, HTTP-FLV, and HLS through local or cross-node frame and RTP relays.SyncTV livestream pipeline showing WHIP, RTMP, and external pulls entering StreamHub, then reaching WHEP, HTTP-FLV, and HLS through local or cross-node frame and RTP relays.
WHIP and upstream WHEP retain RTP and produce remuxed frames. WHEP consumes RTP; HTTP-FLV and HLS consume frames. Cluster relays carry one or both channels according to source capability.
  • 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.
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

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: 86400

An 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.

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.sdp

A 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.sdp

curl 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.

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.

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.

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.

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 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.

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.

  1. A publisher connects through WHIP or RTMP to any SyncTV node. LiveProxy may also pull external WHEP on any node.
  2. After authentication, that node registers room_id/media_id -> node_id/api_address in the publisher registry. In cluster mode, the registry uses Redis.
  3. The local StreamHub receives frames. WHIP/WHEP sources also retain RTP. HTTP-FLV and HLS subscribe to frames; WHEP subscribes to RTP.
  4. HLS segments are written to the selected memory, file, shared_file, or s3 backend.
  5. 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.
  6. 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.

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.

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.

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_file with hls_storage.path mounted from a filesystem all replicas can read and write. .ts requests are served by the current node from that shared path.
  • s3 with livestream.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.

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/hls

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 /

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.