perf: switch to Low-Latency HLS for ~1-3s live stream latency
Build and Push Container / build (push) Has been cancelled

Previously mediamtx used standard MPEG-TS HLS (2s segments, 3-segment
playlist window) and hls.js was configured with lowLatencyMode:false.
The default liveSyncDurationCount:3 and liveMaxLatencyDurationCount:10
allowed 20+ second drift before catch-up triggered.

Changes:
- mediamtx: hlsVariant lowLatency — generates 200ms parts inside 2s
  segments; LL-HLS clients receive each part as it completes instead of
  waiting for the full segment. hlsSegmentCount raised to 7 for playlist
  compatibility with non-LL-HLS clients.
- hls.js: lowLatencyMode:true enables part fetching and blocking playlist
  requests (_HLS_msn/_HLS_part); maxBufferLength:4 prevents buffering far
  ahead; liveSyncDurationCount:1 / liveMaxLatencyDurationCount:3 keep the
  player within ~2-4s of the live edge.
- Express schedule gate: endsWith('.m3u8') → includes('.m3u8') so blocking
  playlist requests (index.m3u8?_HLS_msn=N&_HLS_part=N) are also gated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-01 18:20:36 -05:00
co-authored by Claude Sonnet 4.6
parent 72f740ddcc
commit 531743dc6d
4 changed files with 15 additions and 6 deletions
+8 -1
View File
@@ -51,7 +51,14 @@ export default function VideoPlayer({ src, onOffline }: Props) {
};
if (Hls.isSupported()) {
const hls = new Hls({ enableWorker: true, lowLatencyMode: false });
const hls = new Hls({
enableWorker: true,
lowLatencyMode: true, // use LL-HLS parts + blocking playlist requests
backBufferLength: 30, // keep 30s of playback history (seeking)
maxBufferLength: 4, // don't buffer more than 4s ahead of playback
liveSyncDurationCount: 1, // target 1 segment from the live edge
liveMaxLatencyDurationCount: 3, // catch up if drift exceeds 3 segments
});
hlsRef.current = hls;
hls.loadSource(src);
+3 -2
View File
@@ -28,9 +28,10 @@ rtspAddress: 127.0.0.1:8554
hls: yes
hlsAddress: :8888
hlsAlwaysRemux: yes
hlsVariant: mpegts
hlsVariant: lowLatency
hlsSegmentCount: 7
hlsSegmentDuration: 2s
hlsSegmentCount: 3
hlsPartDuration: 200ms
hlsAllowOrigins: ['*']
# Authenticate every publish action via Express
+3 -2
View File
@@ -22,9 +22,10 @@ srtAddress: :7000
hls: yes
hlsAddress: :8888
hlsAlwaysRemux: yes
hlsVariant: mpegts
hlsVariant: lowLatency
hlsSegmentCount: 7
hlsSegmentDuration: 2s
hlsSegmentCount: 3
hlsPartDuration: 200ms
hlsAllowOrigins: ['*']
# Authenticate every publish action via Express
+1 -1
View File
@@ -67,7 +67,7 @@ async function main() {
// the early-start window (host connected, but scheduled window not open yet).
// Only check .m3u8 playlist requests — segment requests are already gated
// by the playlist not being delivered.
if (streamKey && req.url.endsWith('.m3u8')) {
if (streamKey && req.url.includes('.m3u8')) {
try {
const stream = await getStreamByKey(streamKey);
if (stream && !await isWithinSchedule(stream.id)) {