Continued tweaking of latency compensation

This commit is contained in:
Gabe Kangas 2022-03-25 14:21:07 -07:00
parent 22578f0ac2
commit 5a2584d8ea
No known key found for this signature in database
GPG key ID: 9A56337728BC81EA
2 changed files with 56 additions and 63 deletions

View file

@ -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);
}
}

View file

@ -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();