Source failover¶
media.source.failover
A stream can have multiple sources. Sapsan monitors the active source, automatically switches to the next one when it degrades, and switches back when the primary recovers. This logic is called LSI (Live Source Input).
Multiple inputs¶
Inputs are listed in priority order — the first one is the primary:
streams:
- name: tv1
inputs:
- udp:
host: 239.0.0.1
port: 5000
- tshttp:
url: http://backup-origin.example.com/tv1/mpegts
How switching works¶
Key properties of the algorithm that matter on air:
- A source becomes active only when ready: it must deliver stream parameters (MediaInfo) and a suitable start point (keyframe, audio frame or fragment boundary). Until then viewers keep receiving the current source.
- Failback after a clean window: a recovered higher-priority input is probed in the background. For inputs opened by Sapsan, it must reach a suitable start point and remain free of rule violations for
return_alive_secsbefore switching. - A failed input reconnects with growing delays (
reconnect_initial_delay→reconnect_delay_step→reconnect_max_delay); when all inputs are exhausted, rotation wraps back to the first. - A source whose bitrate exceeds
max_bitrateis forcibly stopped. - Separate no-video and no-audio timeouts: lost audio with live video also counts as degradation.
Switching policy¶
The lsi section sets the stream-wide policy. An input's source_options can override its timeouts, bitrate limits and clean window; reconnection delays and the background recheck interval are set only in lsi:
streams:
- name: tv1
inputs:
- udp: {host: 239.0.0.1, port: 5000}
- udp:
host: 239.0.0.2
port: 5000
source_options:
source_timeout: 10
lsi:
source_timeout: 5
video_timeout: 3
audio_timeout: 3
Timeouts and the background recheck interval are in seconds; reconnection delays are in milliseconds. Main parameters:
| Parameter | Description |
|---|---|
source_timeout, video_timeout, audio_timeout |
no-data/no-video/no-audio timeouts |
max_bitrate |
source bitrate limit |
reconnect_initial_delay, reconnect_delay_step, reconnect_max_delay |
reconnection policy (lsi only), milliseconds |
recheck_secondary_inputs_interval |
background check interval for inactive inputs (lsi only), seconds |
allow_if, deny_if (source_options only) |
enable/disable an input via an external flag file |
allow_if/deny_if let you control inputs externally without editing the config: an input is used only while the flag file allows it.
Secondary source state¶
Apply the configuration through central. Set the background recheck interval explicitly in lsi; this example checks inactive inputs at a 10-second interval:
streams:
- name: tv1
inputs:
- udp: {host: 239.0.0.1, port: 5000}
- udp: {host: 239.0.0.2, port: 5000}
lsi:
source_timeout: 5
recheck_secondary_inputs_interval: 10
Read state from the central management API. With two configured inputs, inputs[1] is the second source and current tells whether it is currently feeding the stream:
curl -fsS -u "$ADMIN_USER:$ADMIN_PASSWORD" \
http://central:8080/central/api-v4/streams/stats/tv1 \
| jq '{active: [.inputs[] | select(.current)], secondary: .inputs[1] | {url, current, status, problems, checked_at, error, failure_cause}}'
status is the last observed health, not a guarantee of seamless switching. checked_at is the RFC 3339 UTC observation time; an absent field means no check has occurred. Health values:
working— frames arriving, clean window without violations.starting— input opened, no frames yet.degraded— frames arriving, a quality rule is violated.dead— no frames due to an error or timeout.unchecked— input not observed yet.
Inspect problems, error and failure_cause for the cause. current: false alone is not a failure: a healthy backup is not selected for delivery. Check the age of checked_at and compare both inputs’ media_info: different codecs or geometry do not imply seamless switching.
Time on primary and backup inputs¶
Read the detailed input section directly from the streamer ingesting tv1. Central serves compact current state and does not fetch this section from the streamer:
curl -fsS -u "$ADMIN_USER:$ADMIN_PASSWORD" \
http://streamer:8080/streamer/api-v4/streams/stats/tv1 \
| jq '.input | {primary_s: (.num_sec_on_primary_input // 0), secondary_s: (.num_sec_on_secondary_input // 0), no_data_s: (.num_sec_no_data // 0), switches: (.input_switches // 0), retries: (.retries // 0)}'
These cumulative values use one-second ticks over the lifetime of stream statistics. num_sec_on_secondary_input combines every input after the first, not only the second. num_sec_no_data counts seconds after the no-data timeout. Zero fields may be omitted; use // 0 only inside an actual returned input section. An absent section does not mean zero time.
Example of two samples:
{"t0":{"primary_s":3600,"secondary_s":120,"no_data_s":0},
"t1":{"primary_s":3640,"secondary_s":140,"no_data_s":0}}
During the interval, the primary was used for 40 s and backups for 20 s; the backup share of time with data is 20 / (40 + 20) = 33.3%. Subtract successive snapshots. If counters decrease after a restart or stream reassignment, start a new interval; do not add negative deltas.