Added HLS discontinuity detection

This commit is contained in:
Trevor Mears
2025-12-29 17:45:09 -08:00
parent 32e0434785
commit 83ca622efa
2 changed files with 61 additions and 7 deletions
+54 -2
View File
@@ -68,12 +68,16 @@ class VideoPlayer {
maxBufferLength: 30, // Buffer up to 30 seconds of content
maxMaxBufferLength: 60, // Absolute max buffer 60 seconds
maxBufferSize: 60 * 1000 * 1000, // 60MB max buffer size
maxBufferHole: 0.5, // Allow 0.5s holes in buffer (helps with discontinuities)
// Live stream settings - stay further from live edge for stability
liveSyncDurationCount: 3, // Stay 3 segments behind live
liveMaxLatencyDurationCount: 10, // Allow up to 10 segments behind before catching up
// Handle audio discontinuities (fixes garbled audio during ad transitions)
liveBackBufferLength: 30, // Keep 30s of back buffer for seeking
// Audio discontinuity handling (fixes garbled audio during ad transitions)
stretchShortVideoTrack: true, // Stretch short segments to avoid gaps
forceKeyFrameOnDiscontinuity: true, // Force keyframe sync on discontinuity
// More aggressive discontinuity recovery
maxAudioFramesDrift: 1, // Allow minimal audio drift before correction
// Faster recovery from errors
levelLoadingMaxRetry: 4,
manifestLoadingMaxRetry: 4,
@@ -101,9 +105,10 @@ class VideoPlayer {
// Initialize HLS.js if supported
if (Hls.isSupported()) {
this.hls = new Hls(this.getHlsConfig());
this.lastDiscontinuity = -1; // Track discontinuity changes
this.hls.on(Hls.Events.ERROR, (event, data) => {
console.error('HLS error:', data);
console.error('HLS error:', data.type, data.details);
if (data.fatal) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
@@ -131,6 +136,10 @@ class VideoPlayer {
// Non-fatal media error - try to recover (handles audio codec issues)
console.log('Non-fatal media error, attempting recovery...');
this.hls.recoverMediaError();
} else if (data.details === 'bufferAppendError' || data.details === 'fragParsingError') {
// Buffer/parsing errors during ad transitions - try recovery
console.log('Buffer/parsing error during segment, recovering...');
this.hls.recoverMediaError();
}
});
@@ -145,6 +154,27 @@ class VideoPlayer {
this.hls.recoverMediaError();
});
// Detect discontinuity changes (ad transitions) and help decoder reset
this.hls.on(Hls.Events.FRAG_CHANGED, (event, data) => {
const frag = data.frag;
// Debug: log every fragment change
console.log(`[HLS] FRAG_CHANGED: sn=${frag?.sn}, cc=${frag?.cc}, level=${frag?.level}`);
if (frag && frag.sn !== 'initSegment') {
// Check if we crossed a discontinuity boundary using CC (Continuity Counter)
if (frag.cc !== undefined && frag.cc !== this.lastDiscontinuity) {
console.log(`[HLS] Discontinuity detected: CC ${this.lastDiscontinuity} -> ${frag.cc}`);
this.lastDiscontinuity = frag.cc;
// Small nudge to help decoder sync (only if playing)
if (!this.video.paused && this.video.currentTime > 0) {
const nudgeAmount = 0.01;
this.video.currentTime += nudgeAmount;
}
}
}
});
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
this.video.play().catch(e => console.log('Autoplay prevented:', e));
});
@@ -265,6 +295,28 @@ class VideoPlayer {
} else {
console.error('Fatal HLS error:', data);
}
} else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
// Non-fatal media error - just log it (auto-recovery can cause black screens)
console.log('Non-fatal media error:', data.details);
}
});
// Detect discontinuity changes (ad transitions) and help decoder reset
this.lastDiscontinuity = -1;
this.hls.on(Hls.Events.FRAG_CHANGED, (event, data) => {
const frag = data.frag;
if (frag && frag.sn !== 'initSegment') {
// Check if we crossed a discontinuity boundary using CC (Continuity Counter)
if (frag.cc !== undefined && frag.cc !== this.lastDiscontinuity) {
console.log(`[HLS] Discontinuity detected: CC ${this.lastDiscontinuity} -> ${frag.cc}`);
this.lastDiscontinuity = frag.cc;
// Small nudge to help decoder sync (only if playing)
if (!this.video.paused && this.video.currentTime > 0) {
const nudgeAmount = 0.01;
this.video.currentTime += nudgeAmount;
}
}
}
});
} else if (this.video.canPlayType('application/vnd.apple.mpegurl') === 'probably' ||