0

Allow adjusting latency targets based on buffering events

This commit is contained in:
Gabe Kangas 2022-06-02 23:37:20 -07:00
parent e7015d0e48
commit 677700414b
No known key found for this signature in database
GPG Key ID: 9A56337728BC81EA

View File

@ -60,6 +60,11 @@ class LatencyCompensator {
this.lastJumpOccurred = null; this.lastJumpOccurred = null;
this.startupTime = new Date(); this.startupTime = new Date();
this.clockSkewMs = 0; this.clockSkewMs = 0;
this.currentLatency = null;
// Keep track of all the latencies we encountered buffering events
// in order to determine a new minimum latency.
this.bufferedAtLatency = [];
this.player.on('playing', this.handlePlaying.bind(this)); this.player.on('playing', this.handlePlaying.bind(this));
this.player.on('pause', this.handlePause.bind(this)); this.player.on('pause', this.handlePause.bind(this));
@ -69,6 +74,11 @@ class LatencyCompensator {
this.player.on('ended', this.handleEnded.bind(this)); this.player.on('ended', this.handleEnded.bind(this));
this.player.on('canplaythrough', this.handlePlaying.bind(this)); this.player.on('canplaythrough', this.handlePlaying.bind(this));
this.player.on('canplay', this.handlePlaying.bind(this)); this.player.on('canplay', this.handlePlaying.bind(this));
this.check = this.check.bind(this);
this.start = this.start.bind(this);
this.enable = this.enable.bind(this);
this.countBufferingEvent = this.countBufferingEvent.bind(this);
} }
// To keep our client clock in sync with the server clock to determine // To keep our client clock in sync with the server clock to determine
@ -163,13 +173,27 @@ class LatencyCompensator {
} }
// How far away from live edge do we stop the compensator. // How far away from live edge do we stop the compensator.
const minLatencyThreshold = Math.max( const computedMinLatencyThreshold = Math.max(
MIN_LATENCY, MIN_LATENCY,
segment.duration * 1000 * LOWEST_LATENCY_SEGMENT_LENGTH_MULTIPLIER segment.duration * 1000 * LOWEST_LATENCY_SEGMENT_LENGTH_MULTIPLIER
); );
// Create an array of all the buffering events in the past along with
// the computed min latency above.
const targetLatencies = this.bufferedAtLatency.concat([
computedMinLatencyThreshold,
]);
// Determine if we need to reduce the minimum latency we computed
// above based on buffering events that have taken place in the past by
// creating an array of all the buffering events and the above computed
// minimum latency target and averaging all those values.
const minLatencyThreshold =
targetLatencies.reduce((sum, current) => sum + current, 0) /
targetLatencies.length;
// How far away from live edge do we start the compensator. // How far away from live edge do we start the compensator.
const maxLatencyThreshold = Math.max( let maxLatencyThreshold = Math.max(
minLatencyThreshold * 1.4, minLatencyThreshold * 1.4,
Math.min( Math.min(
segment.duration * 1000 * HIGHEST_LATENCY_SEGMENT_LENGTH_MULTIPLIER, segment.duration * 1000 * HIGHEST_LATENCY_SEGMENT_LENGTH_MULTIPLIER,
@ -177,9 +201,17 @@ class LatencyCompensator {
) )
); );
// If this newly adjusted minimum latency ends up being greater than
// the previously computed maximum latency then reset the maximum
// value using the minimum + an offset.
if (minLatencyThreshold >= maxLatencyThreshold) {
maxLatencyThreshold = minLatencyThreshold + 3000;
}
const segmentTime = segment.dateTimeObject.getTime(); const segmentTime = segment.dateTimeObject.getTime();
const now = new Date().getTime() + this.clockSkewMs; const now = new Date().getTime() + this.clockSkewMs;
const latency = now - segmentTime; const latency = now - segmentTime;
this.currentLatency = latency;
// Since the calculation of latency is based on clock times, it's possible // Since the calculation of latency is based on clock times, it's possible
// things can be reported incorrectly. So we use a sanity check here to // things can be reported incorrectly. So we use a sanity check here to
@ -347,10 +379,6 @@ class LatencyCompensator {
} }
timeout() { timeout() {
if (this.inTimeout) {
return;
}
if (this.jumpingToLiveIgnoreBuffer) { if (this.jumpingToLiveIgnoreBuffer) {
return; return;
} }
@ -417,18 +445,20 @@ class LatencyCompensator {
countBufferingEvent() { countBufferingEvent() {
this.bufferingCounter++; this.bufferingCounter++;
if (this.bufferingCounter > REBUFFER_EVENT_LIMIT) { if (this.bufferingCounter > REBUFFER_EVENT_LIMIT) {
this.disable(); this.disable();
return; return;
} }
this.bufferedAtLatency.push(this.currentLatency);
console.log( console.log(
'latency compensation timeout due to buffering:', 'latency compensation timeout due to buffering:',
this.bufferingCounter, this.bufferingCounter,
'buffering events of', 'buffering events of',
REBUFFER_EVENT_LIMIT REBUFFER_EVENT_LIMIT
); );
this.timeout();
// Allow us to forget about old buffering events if enough time goes by. // Allow us to forget about old buffering events if enough time goes by.
setTimeout(() => { setTimeout(() => {
@ -439,7 +469,7 @@ class LatencyCompensator {
} }
handleBuffering() { handleBuffering() {
if (!this.enabled) { if (!this.enabled || this.inTimeout) {
return; return;
} }
@ -448,6 +478,9 @@ class LatencyCompensator {
return; return;
} }
this.timeout();
clearTimeout(this.bufferingTimer);
this.bufferingTimer = setTimeout(() => { this.bufferingTimer = setTimeout(() => {
this.countBufferingEvent(); this.countBufferingEvent();
}, MIN_BUFFER_DURATION); }, MIN_BUFFER_DURATION);