Allow latency compenstation if bandwidth is fast enough or there are enough segments buffered

This commit is contained in:
Gabe Kangas 2022-04-07 14:42:56 -07:00
parent 6b909b2c47
commit d4cbf07055
No known key found for this signature in database
GPG key ID: 9A56337728BC81EA

View file

@ -39,7 +39,7 @@ const HIGHEST_LATENCY_SEGMENT_LENGTH_MULTIPLIER = 2.6; // Segment length * this
const LOWEST_LATENCY_SEGMENT_LENGTH_MULTIPLIER = 1.8; // Segment length * this value is when we stop compensating. const LOWEST_LATENCY_SEGMENT_LENGTH_MULTIPLIER = 1.8; // Segment length * this value is when we stop compensating.
const MIN_LATENCY = 4 * 1000; // The absolute lowest we'll continue compensation to be running at. const MIN_LATENCY = 4 * 1000; // The absolute lowest we'll continue compensation to be running at.
const MAX_LATENCY = 15 * 1000; // The absolute highest we'll allow a target latency to be before we start compensating. const MAX_LATENCY = 15 * 1000; // The absolute highest we'll allow a target latency to be before we start compensating.
const MAX_JUMP_LATENCY = 7 * 1000; // How much behind the max latency we need to be behind before we allow a jump. const MAX_JUMP_LATENCY = 5 * 1000; // How much behind the max latency we need to be behind before we allow a jump.
const MAX_JUMP_FREQUENCY = 20 * 1000; // How often we'll allow a time jump. const MAX_JUMP_FREQUENCY = 20 * 1000; // How often we'll allow a time jump.
const STARTUP_WAIT_TIME = 10 * 1000; // The amount of time after we start up that we'll allow monitoring to occur. const STARTUP_WAIT_TIME = 10 * 1000; // The amount of time after we start up that we'll allow monitoring to occur.
@ -58,9 +58,11 @@ class LatencyCompensator {
this.playbackRate = 1.0; this.playbackRate = 1.0;
this.lastJumpOccurred = null; this.lastJumpOccurred = null;
this.startupTime = new Date(); this.startupTime = new Date();
this.player.on('playing', this.handlePlaying.bind(this)); this.player.on('playing', this.handlePlaying.bind(this));
this.player.on('error', this.handleError.bind(this)); this.player.on('error', this.handleError.bind(this));
this.player.on('waiting', this.handleBuffering.bind(this)); this.player.on('waiting', this.handleBuffering.bind(this));
this.player.on('stalled', this.handleBuffering.bind(this));
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));
@ -68,6 +70,9 @@ class LatencyCompensator {
// This is run on a timer to check if we should be compensating for latency. // This is run on a timer to check if we should be compensating for latency.
check() { check() {
// We have an arbitrary delay at startup to allow the player to run
// normally and hopefully get a bit of a buffer of segments before we
// start messing with it.
if (new Date().getTime() - this.startupTime.getTime() < STARTUP_WAIT_TIME) { if (new Date().getTime() - this.startupTime.getTime() < STARTUP_WAIT_TIME) {
return; return;
} }
@ -87,34 +92,40 @@ class LatencyCompensator {
} }
if (!this.enabled) { if (!this.enabled) {
console.log('not enabled...');
return; return;
} }
const tech = this.player.tech({ IWillNotUseThisInPlugins: true }); const tech = this.player.tech({ IWillNotUseThisInPlugins: true });
// We need access to the internal tech of VHS to move forward.
// If running under an Apple browser that uses CoreMedia (Safari)
// we do not have access to this as the tech is internal to the OS.
if (!tech || !tech.vhs) { if (!tech || !tech.vhs) {
return; return;
} }
// Network state 2 means we're actively using the network.
// We only want to attempt latency compensation if we're continuing to
// download new segments.
const networkState = this.player.networkState();
if (networkState !== 2) {
return;
}
let totalBuffered = 0;
try { try {
// Check the player buffers to make sure there's enough playable content // Check the player buffers to make sure there's enough playable content
// that we can safely play. // that we can safely play.
if (tech.vhs.stats.buffered.length === 0) { if (tech.vhs.stats.buffered.length === 0) {
console.log('timeout due to zero buffers');
this.timeout(); this.timeout();
return;
} }
let totalBuffered = 0;
tech.vhs.stats.buffered.forEach((buffer) => { tech.vhs.stats.buffered.forEach((buffer) => {
totalBuffered += buffer.end - buffer.start; totalBuffered += buffer.end - buffer.start;
}); });
console.log('buffered', totalBuffered); console.log('buffered', totalBuffered);
if (totalBuffered < 18) {
this.timeout();
}
} catch (e) {} } catch (e) {}
// Determine how much of the current playlist's bandwidth requirements // Determine how much of the current playlist's bandwidth requirements
@ -125,18 +136,22 @@ class LatencyCompensator {
const playerBandwidth = tech.vhs.systemBandwidth; const playerBandwidth = tech.vhs.systemBandwidth;
const bandwidthRatio = playerBandwidth / currentPlaylistBandwidth; const bandwidthRatio = playerBandwidth / currentPlaylistBandwidth;
// If we don't think we have the bandwidth to play faster, then don't do it.
if (bandwidthRatio < REQUIRED_BANDWIDTH_RATIO) {
this.timeout();
return;
}
try { try {
const segment = getCurrentlyPlayingSegment(tech); const segment = getCurrentlyPlayingSegment(tech);
if (!segment) { if (!segment) {
return; return;
} }
// If we're downloading media fast enough or we feel like we have a large
// enough buffer then continue. Otherwise timeout for a bit.
if (
bandwidthRatio < REQUIRED_BANDWIDTH_RATIO &&
totalBuffered < segment.duration * 6
) {
this.timeout();
return;
}
// 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.min( const maxLatencyThreshold = Math.min(
MAX_LATENCY, MAX_LATENCY,
@ -168,6 +183,13 @@ class LatencyCompensator {
proposedPlaybackRate = this.playbackRate + MAX_SPEEDUP_RAMP; proposedPlaybackRate = this.playbackRate + MAX_SPEEDUP_RAMP;
} }
console.log(
'proposedPlaybackRate',
proposedPlaybackRate,
'previous',
this.playbackRate
);
// Limit to 3 decimal places of precision. // Limit to 3 decimal places of precision.
proposedPlaybackRate = proposedPlaybackRate =
Math.round(proposedPlaybackRate * Math.pow(10, 3)) / Math.pow(10, 3); Math.round(proposedPlaybackRate * Math.pow(10, 3)) / Math.pow(10, 3);
@ -352,6 +374,8 @@ class LatencyCompensator {
setTimeout(() => { setTimeout(() => {
if (this.bufferingCounter > 0) { if (this.bufferingCounter > 0) {
this.bufferingCounter--; this.bufferingCounter--;
// Allow a time jump after a long buffer if applicable.
this.lastJumpOccurred = null;
} }
}, BUFFERING_AMNESTY_DURATION); }, BUFFERING_AMNESTY_DURATION);
} }