108 lines
4.4 KiB
JavaScript
108 lines
4.4 KiB
JavaScript
/** Chromium can retain an invalid audio decoder state across HLS ad discontinuities. */
|
|
class HlsAudioRecovery {
|
|
constructor(hls, video, events, isCurrent, options = {}) {
|
|
this.hls = hls;
|
|
this.video = video;
|
|
this.events = events;
|
|
this.isCurrent = isCurrent;
|
|
const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : '';
|
|
this.chromium = options.chromium ?? /(?:Chrome|Chromium|Edg)\//.test(userAgent);
|
|
this.now = options.now || (() => Date.now());
|
|
this.beforeRecovery = options.beforeRecovery;
|
|
this.lastCC = null;
|
|
this.lastRecovery = -Infinity;
|
|
this.attempts = [];
|
|
this.pendingBoundary = false;
|
|
this.disposed = false;
|
|
|
|
this.onFragment = (event, data) => {
|
|
if (!this.active() || !data?.frag || data.frag.sn === 'initSegment') return;
|
|
const cc = data.frag.cc;
|
|
if (!Number.isInteger(cc) || cc === this.lastCC) return;
|
|
const previous = this.lastCC;
|
|
this.lastCC = cc;
|
|
if (previous === null || !this.chromium) return;
|
|
this.pendingBoundary = true;
|
|
this.onPlaying();
|
|
};
|
|
this.onPlaying = () => {
|
|
if (!this.active() || !this.pendingBoundary || this.video.paused) return;
|
|
this.pendingBoundary = false;
|
|
this.requestRecovery('audio discontinuity');
|
|
};
|
|
this.onDestroy = () => this.dispose();
|
|
this.onManifest = () => {
|
|
if (!this.active()) return;
|
|
this.lastCC = null;
|
|
this.pendingBoundary = false;
|
|
this.cancelRecovery();
|
|
};
|
|
hls.on(events.FRAG_CHANGED, this.onFragment);
|
|
hls.on(events.DESTROYING, this.onDestroy);
|
|
hls.on(events.MANIFEST_LOADING, this.onManifest);
|
|
video.addEventListener?.('playing', this.onPlaying);
|
|
}
|
|
|
|
active() {
|
|
return !this.disposed && this.isCurrent();
|
|
}
|
|
|
|
requestRecovery(reason) {
|
|
if (!this.active()) return false;
|
|
if (this.timer !== undefined || this.onAttached) return true;
|
|
const now = this.now();
|
|
this.attempts = this.attempts.filter(time => now - time < 30000);
|
|
if (now - this.lastRecovery < 2000 || this.attempts.length >= 3) return false;
|
|
|
|
// Let HLS finish dispatching the fragment/error event before detaching its media.
|
|
this.timer = setTimeout(() => {
|
|
this.timer = undefined;
|
|
if (!this.active()) return;
|
|
const position = this.video.currentTime;
|
|
const resume = !this.video.paused && !this.video.ended;
|
|
const restoreCaptions = this.beforeRecovery?.();
|
|
this.previousStartPosition = this.hls.config.startPosition;
|
|
if (Number.isFinite(position) && position >= 0) this.hls.config.startPosition = position;
|
|
this.lastRecovery = this.now();
|
|
this.attempts.push(this.lastRecovery);
|
|
this.onAttached = () => {
|
|
if (!this.active() || !this.onAttached) return;
|
|
this.hls.off(this.events.MEDIA_ATTACHED, this.onAttached);
|
|
this.onAttached = null;
|
|
this.hls.config.startPosition = this.previousStartPosition;
|
|
if (!this.active()) return;
|
|
restoreCaptions?.();
|
|
if (resume) this.video.play().catch(() => {});
|
|
else this.video.pause();
|
|
};
|
|
this.hls.on(this.events.MEDIA_ATTACHED, this.onAttached);
|
|
console.info(`[HLS] Refreshing media decoder: ${reason}`);
|
|
this.hls.recoverMediaError();
|
|
}, 0);
|
|
return true;
|
|
}
|
|
|
|
cancelRecovery() {
|
|
clearTimeout(this.timer);
|
|
this.timer = undefined;
|
|
if (this.onAttached) {
|
|
this.hls.off(this.events.MEDIA_ATTACHED, this.onAttached);
|
|
this.hls.config.startPosition = this.previousStartPosition;
|
|
this.onAttached = null;
|
|
}
|
|
}
|
|
|
|
dispose() {
|
|
if (this.disposed) return;
|
|
this.disposed = true;
|
|
this.cancelRecovery();
|
|
this.hls.off(this.events.FRAG_CHANGED, this.onFragment);
|
|
this.hls.off(this.events.DESTROYING, this.onDestroy);
|
|
this.hls.off(this.events.MANIFEST_LOADING, this.onManifest);
|
|
this.video.removeEventListener?.('playing', this.onPlaying);
|
|
}
|
|
}
|
|
|
|
if (typeof window !== 'undefined') window.HlsAudioRecovery = HlsAudioRecovery;
|
|
if (typeof module !== 'undefined') module.exports = HlsAudioRecovery;
|