From 5a2584d8ea2db90158d6795d462a42cca65b3d12 Mon Sep 17 00:00:00 2001 From: Gabe Kangas Date: Fri, 25 Mar 2022 14:21:07 -0700 Subject: [PATCH] Continued tweaking of latency compensation --- webroot/js/components/latencyCompensator.js | 87 ++++++++++++--------- webroot/js/components/player.js | 32 ++------ 2 files changed, 56 insertions(+), 63 deletions(-) diff --git a/webroot/js/components/latencyCompensator.js b/webroot/js/components/latencyCompensator.js index 32b69f0eb..58da0be8a 100644 --- a/webroot/js/components/latencyCompensator.js +++ b/webroot/js/components/latencyCompensator.js @@ -13,10 +13,10 @@ It will: const REBUFFER_EVENT_LIMIT = 5; // Max number of buffering events before we stop compensating for latency. const MIN_BUFFER_DURATION = 300; // Min duration a buffer event must last to be counted. -const MAX_SPEEDUP_RATE = 1.07; // The playback rate when compensating for latency. +const MAX_SPEEDUP_RATE = 1.08; // The playback rate when compensating for latency. const TIMEOUT_DURATION = 20_000; // The amount of time we stop handling latency after certain events. const CHECK_TIMER_INTERVAL = 5_000; // How often we check if we should be compensating for latency. -const BUFFERING_AMNESTY_DURATION = 2 * 1000 * 60; // How often until a buffering event expires. +const BUFFERING_AMNESTY_DURATION = 4 * 1000 * 60; // How often until a buffering event expires. const HIGH_LATENCY_ENABLE_THRESHOLD = 20_000; // ms; const LOW_LATENCY_DISABLE_THRESHOLD = 4500; // ms; const REQUIRED_BANDWIDTH_RATIO = 2.0; // The player:bitrate ratio required to enable compensating for latency. @@ -33,7 +33,7 @@ class LatencyCompensator { this.minLatencyThreshold = 8000; this.bufferingCounter = 0; this.bufferingTimer = 0; - this.bufferStartedTimestamp = 0; + this.playbackRate = 1.0; this.player.on('playing', this.handlePlaying.bind(this)); this.player.on('error', this.handleError.bind(this)); this.player.on('waiting', this.handleBuffering.bind(this)); @@ -44,12 +44,43 @@ class LatencyCompensator { // This is run on a timer to check if we should be compensating for latency. check() { + console.log( + 'playback rate', + this.playbackRate, + 'enabled:', + this.enabled, + 'running: ', + this.running, + 'timeout: ', + this.inTimeout, + 'buffer count:', + this.bufferingCounter + ); + if (this.inTimeout) { return; } const tech = this.player.tech({ IWillNotUseThisInPlugins: true }); + try { + // Check the player buffers to make sure there's enough playable content + // that we can safely play. + if (tech.vhs.stats.buffered.length === 0) { + this.timeout(); + } + + let totalBuffered = 0; + + tech.vhs.stats.buffered.forEach((buffer) => { + totalBuffered += buffer.end - buffer.start; + }); + + if (totalBuffered < 20) { + this.timeout(); + } + } catch (e) {} + // Determine how much of the current playlist's bandwidth requirements // we're utilizing. If it's too high then we can't afford to push // further into the future because we're downloading too slowly. @@ -70,18 +101,7 @@ class LatencyCompensator { Math.min(proposedPlaybackRate, MAX_SPEEDUP_RATE), 1.0 ); - console.log( - 'playback rate', - proposedPlaybackRate, - 'enabled:', - this.enabled, - 'running: ', - this.running, - 'timedout: ', - this.inTimeout, - 'buffer count:', - this.bufferingCounter - ); + try { const segment = getCurrentlyPlayingSegment(tech); if (!segment) { @@ -113,18 +133,23 @@ class LatencyCompensator { } } + setPlaybackRate(rate) { + this.playbackRate = rate; + this.player.playbackRate(rate); + } + start(rate = 1.0) { - if (this.inTimeout || !this.enabled) { + if (this.inTimeout || !this.enabled || rate === this.playbackRate) { return; } this.running = true; - this.player.playbackRate(rate); + this.setPlaybackRate(rate); } stop() { this.running = false; - this.player.playbackRate(1); + this.setPlaybackRate(1.0); } enable() { @@ -161,24 +186,11 @@ class LatencyCompensator { } handlePlaying() { + clearTimeout(this.bufferingTimer); + if (!this.enabled) { return; } - - if (this.bufferStartedTimestamp !== 0) { - const bufferingDuration = - new Date().getTime() - this.bufferStartedTimestamp; - this.bufferStartedTimestamp = 0; - - // If the buffering event lasted long enough then we will stay in - // a timeout and count it as a real buffering event. Otherwise - // we will ignore it. - if (bufferingDuration > MIN_BUFFER_DURATION) { - this.countBufferingEvent(); - } else { - this.endTimeout(); - } - } } handleEnded() { @@ -189,18 +201,18 @@ class LatencyCompensator { this.disable(); } - handleError() { + handleError(e) { if (!this.enabled) { return; } + console.log('handle error', e); this.timeout(); } countBufferingEvent() { this.bufferingCounter++; if (this.bufferingCounter > REBUFFER_EVENT_LIMIT) { - console.log('disabling latency compensation'); this.disable(); return; } @@ -220,8 +232,11 @@ class LatencyCompensator { return; } - this.bufferStartedTimestamp = new Date().getTime(); this.timeout(); + + this.bufferingTimer = setTimeout(() => { + this.countBufferingEvent(); + }, MIN_BUFFER_DURATION); } } diff --git a/webroot/js/components/player.js b/webroot/js/components/player.js index 08574878c..6e08a5fa5 100644 --- a/webroot/js/components/player.js +++ b/webroot/js/components/player.js @@ -8,32 +8,6 @@ import LatencyCompensator from './latencyCompensator.js'; const VIDEO_ID = 'video'; -const EVENTS = [ - 'loadstart', - 'progress', - 'suspend', - 'abort', - 'error', - 'emptied', - 'stalled', - 'loadedmetadata', - 'loadeddata', - 'canplay', - 'canplaythrough', - 'playing', - 'waiting', - 'seeking', - 'seeked', - 'ended', - 'durationchange', - 'timeupdate', - 'play', - 'pause', - 'ratechange', - 'resize', - 'volumechange', -]; - // Video setup const VIDEO_SRC = { src: URL_STREAM, @@ -108,6 +82,7 @@ class OwncastPlayer { this.appPlayerReadyCallback = null; this.appPlayerPlayingCallback = null; this.appPlayerEndedCallback = null; + this.hasStartedPlayback = false; // bind all the things because safari this.startPlayer = this.startPlayer.bind(this); @@ -236,7 +211,10 @@ class OwncastPlayer { this.appPlayerPlayingCallback(); } - this.latencyCompensator.enable(); + if (!this.hasStartedPlayback) { + this.latencyCompensator.enable(); + this.hasStartedPlayback = true; + } setInterval(() => { this.collectPlaybackMetrics();