Dockerized MPEG-TS over SRT redundancy gateways built with TSDuck.
tsswitchlistens onPRIMARY_LISTEN_PORTandBACKUP_LISTEN_PORT.- Input
0is preferred (--primary-input 0); failover happens on receive timeout. - Selected output is exposed on
INPUT_FAILOVER_OUTPUT_LISTEN_PORT. --event-udpevents can be consumed byinput-event-observer(observability profile).
tsswitchlistens onNODE_A_PORTandNODE_B_PORT; selected output is exposed onOUTPUT_FAILOVER_OUTPUT_LISTEN_PORT.watchdog/watchdog.pypollsNODE_A_HEALTH_URLandNODE_B_HEALTH_URL(optional regex matchers).- Watchdog controls
tsswitchover UDP remote commands and reads--event-udpevents. - Policy: prefer A; switch to B only when A is stably unhealthy and B is stably healthy; fail back when A is stably healthy.
Both gateways wrap SRT legs with -I fork "tsp -I srt ... -O file -" so dropped sessions can restart without retiring a leg.
cp .env.example .envOptional (only if tsp is not installed locally and you run examples/*.sh):
make build-tsduck-tools-localmake up-input-demoGenerate traffic:
bash examples/generate-primary.sh
bash examples/generate-backup.shVerify failover by stopping generate-primary.sh; output remains available on INPUT_FAILOVER_OUTPUT_LISTEN_PORT.
make up-output-demo-healthGenerate traffic and consume output:
bash examples/generate-node-a.sh
bash examples/generate-node-b.sh
bash examples/consume-output-output.shTrigger node A health changes:
bash examples/set-health.sh 18081 unhealthy
bash examples/set-health.sh 18081 healthyEnable:
docker compose -f compose/output-failover.yml --profile bridge-connectors up --buildRelevant envs:
BRIDGE_A_SOURCE_HOST/BRIDGE_A_SOURCE_PORTBRIDGE_B_SOURCE_HOST/BRIDGE_B_SOURCE_PORTBRIDGE_A_TARGET_HOST/BRIDGE_A_TARGET_PORTBRIDGE_B_TARGET_HOST/BRIDGE_B_TARGET_PORT
Input:
INPUT_SRT_SOURCE_COMMON_FLAGSINPUT_SRT_PRIMARY_EXTRA_FLAGSINPUT_SRT_BACKUP_EXTRA_FLAGSINPUT_SRT_OUTPUT_EXTRA_FLAGS
Output:
OUTPUT_SRT_SOURCE_COMMON_FLAGSOUTPUT_SRT_NODE_A_EXTRA_FLAGSOUTPUT_SRT_NODE_B_EXTRA_FLAGSOUTPUT_SRT_OUTPUT_EXTRA_FLAGS
Example:
INPUT_SRT_SOURCE_COMMON_FLAGS="--multiple --transtype live --messageapi --passphrase input-secret --pbkeylen 16"
OUTPUT_SRT_NODE_A_EXTRA_FLAGS="--passphrase node-a-secret --pbkeylen 16"
OUTPUT_SRT_NODE_B_EXTRA_FLAGS="--passphrase node-b-secret --pbkeylen 16"Start both gateways with shared observability stack:
make up-allEndpoints:
- Input observer metrics:
http://127.0.0.1:9107/metrics(when input observability profile is enabled) - Output watchdog metrics:
http://127.0.0.1:9108/metrics - Prometheus:
http://127.0.0.1:9090 - Grafana:
http://127.0.0.1:3000(admin/admin) - Tempo:
http://127.0.0.1:3200 - OTEL Collector:
127.0.0.1:4318(HTTP),127.0.0.1:4317(gRPC)
Key metrics:
gateway_active_inputgateway_waiting_for_inputwatchdog_switch_commands_totalwatchdog_switch_events_totalwatchdog_node_healthwatchdog_health_checks_totalgateway_bytes_total
See scripts/README.md.