Skip to content

Commit

Permalink
Add min/max playbackRate config settings
Browse files Browse the repository at this point in the history
  • Loading branch information
Rob Walch committed Nov 5, 2020
1 parent f3008c1 commit 9274472
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 26 deletions.
3 changes: 3 additions & 0 deletions demo/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -1009,7 +1009,10 @@ function checkBuffer () {

if ($('#statsDisplayTab').is(':visible')) {
let log = `Duration: ${video.duration}\nBuffered: ${timeRangesToString(video.buffered)}\nSeekable: ${timeRangesToString(video.seekable)}\nPlayed: ${timeRangesToString(video.played)}\n`;
log += `Max Latency: ${hls.maxLatency}\n`;
log += `Target Latency: ${hls.targetLatency}\n`;
log += `Latency: ${hls.latency}\n`;
log += `Edge Stall: ${hls.latencyController.edgeStalled}\n`;
log += `Playback rate: ${video.playbackRate.toFixed(2)}\n`;
if (hls.media) {
for (const type in tracks) {
Expand Down
31 changes: 30 additions & 1 deletion docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
- [`liveMaxLatencyDurationCount`](#livemaxlatencydurationcount)
- [`liveSyncDuration`](#livesyncduration)
- [`liveMaxLatencyDuration`](#livemaxlatencyduration)
- [`minLiveSyncPlaybackRate`](#minLiveSyncPlaybackRate)
- [`maxLiveSyncPlaybackRate`](#maxLiveSyncPlaybackRate)
- [`liveDurationInfinity`](#livedurationinfinity)
- [`liveBackBufferLength`](#livebackbufferlength)
- [`enableWorker`](#enableworker)
Expand Down Expand Up @@ -124,6 +126,8 @@
- [Live stream API](#live-stream-api)
- [`hls.liveSyncPosition`](#hlslivesyncposition)
- [`hls.latency`](#hlslatency)
- [`hls.maxLatency`](#hlsmaxlatency)
- [`hls.targetLatency`](#hlstargetlatency)
- [Runtime Events](#runtime-events)
- [Loader Composition](#loader-composition)
- [Errors](#errors)
Expand Down Expand Up @@ -583,6 +587,20 @@ If set, this value must be stricly superior to `liveSyncDuration` which must be
You can't define this parameter and either `liveSyncDurationCount` or `liveMaxLatencyDurationCount` in your configuration object at the same time.
A value too close from `liveSyncDuration` is likely to cause playback stalls.

### `minLiveSyncPlaybackRate`

(default: `0.75` min: `0.5` max: `1`)

Decrease `video.playbackRate` down to this value when latency falls below target (`liveSyncDuration(Count)` or manifest (PART-)HOLD-BACK) in a live stream.
Set both `minLiveSyncPlaybackRate` and `maxLiveSyncPlaybackRate` to `1` to disable setting of playback rate.

### `maxLiveSyncPlaybackRate`

(default: `1.5` min: `1` max: `2`)

Increase `video.playbackRate` up to this value to catch up to target latency (`liveSyncDuration(Count)` or manifest (PART-)HOLD-BACK) in a live stream.
Set both `minLiveSyncPlaybackRate` and `maxLiveSyncPlaybackRate` to `1` to disable setting of playback rate.

### `liveDurationInfinity`

(default: `false`)
Expand Down Expand Up @@ -1308,7 +1326,18 @@ get : position of live sync point (ie edge of live position minus safety delay d

### `hls.latency`

get : estimated position of live edge (ie edge of live playlist plus time sync playlist advanced)
get : estimated position (in seconds) of live edge (ie edge of live playlist plus time sync playlist advanced)
returns 0 before first playlist is loaded

### `hls.maxLatency`

get : maximum distance from the edge before the player seeks forward to ```hls.liveSyncPosition```
configured using ```liveMaxLatencyDurationCount``` (multiple of target duration) or ```liveMaxLatencyDuration```
returns 0 before first playlist is loaded

### `hls.targetLatency`

get : target distance from the edge as calculated by the latency controller

## Runtime Events

Expand Down
26 changes: 16 additions & 10 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,21 +99,24 @@ type StreamControllerConfig = {
maxBufferLength: number,
maxBufferSize: number,
maxBufferHole: number,

highBufferWatchdogPeriod: number,
nudgeOffset: number,
nudgeMaxRetry: number,
maxFragLookUpTolerance: number,
liveSyncDurationCount: number,
liveMaxLatencyDurationCount: number,
liveSyncDuration?: number,
liveMaxLatencyDuration?: number,
maxMaxBufferLength: number,

startFragPrefetch: boolean,
testBandwidth: boolean
};

type LatencyControllerConfig = {
liveSyncDurationCount: number,
liveMaxLatencyDurationCount: number,
liveSyncDuration?: number,
liveMaxLatencyDuration?: number,
maxLiveSyncPlaybackRate: number,
minLiveSyncPlaybackRate: number
}

type TimelineControllerConfig = {
cueHandler: Cues.CuesInterface,
enableCEA708Captions: boolean,
Expand Down Expand Up @@ -170,6 +173,7 @@ export type HlsConfig =
MP4RemuxerConfig &
PlaylistLoaderConfig &
StreamControllerConfig &
LatencyControllerConfig &
TimelineControllerConfig &
TSDemuxerConfig;

Expand All @@ -191,10 +195,12 @@ export const hlsDefaultConfig: HlsConfig = {
nudgeOffset: 0.1, // used by stream-controller
nudgeMaxRetry: 3, // used by stream-controller
maxFragLookUpTolerance: 0.25, // used by stream-controller
liveSyncDurationCount: 3, // used by stream-controller
liveMaxLatencyDurationCount: Infinity, // used by stream-controller
liveSyncDuration: void 0, // used by stream-controller
liveMaxLatencyDuration: void 0, // used by stream-controller
liveSyncDurationCount: 3, // used by latency-controller
liveMaxLatencyDurationCount: Infinity, // used by latency-controller
liveSyncDuration: void 0, // used by latency-controller
liveMaxLatencyDuration: void 0, // used by latency-controller
minLiveSyncPlaybackRate: 0.75, // used by latency-controller
maxLiveSyncPlaybackRate: 1.5, // used by latency-controller
liveDurationInfinity: false, // used by buffer-controller
liveBackBufferLength: Infinity, // used by buffer-controller
maxMaxBufferLength: 600, // used by stream-controller
Expand Down
1 change: 0 additions & 1 deletion src/controller/base-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,6 @@ export default class BaseStreamController extends TaskLoop implements NetworkCom
this.log(`Start time offset found in playlist, adjust startPosition to ${startTimeOffset}`);
this.startPosition = startTimeOffset;
} else {
// if live playlist, set start position to be fragment N-this.config.liveSyncDurationCount (usually 3)
if (details.live) {
this.startPosition = this.hls.liveSyncPosition || sliding;
this.log(`Configure startPosition to ${this.startPosition}`);
Expand Down
50 changes: 36 additions & 14 deletions src/controller/latency-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,8 @@ import type Hls from '../hls';
import type { HlsConfig } from '../config';

// TODO: LatencyController config options:
// - maxLiveSyncPlaybackRate
// - minLiveSyncPlaybackRate
// - maxLevelUpdateAge (rename or existing option we could use?)
// - adjustLatencyOnErrorMax
// - liveSyncOnStallIncrease
// - maxLiveSyncOnStallIncrease
const L = 1.5; // Change playback rate by up to 1.5x
const k = 0.75;
const sigmoid = (x, x0) => L / (1 + Math.exp(-k * (x - x0)));

// TODO: LatencyController unit tests
// - latency estimate
Expand Down Expand Up @@ -47,9 +40,26 @@ export default class LatencyController implements ComponentAPI {
if (liveEdge === null || targetLatency === null || this.levelDetails === null) {
return null;
}
const maxLevelUpdateAge = this.levelDetails.targetduration * 3;
const edgeStalled = Math.max(this.levelDetails.age - maxLevelUpdateAge, 0);
return Math.min(this.levelDetails.edge, liveEdge - targetLatency - edgeStalled);
return Math.min(this.levelDetails.edge, liveEdge - targetLatency - this.edgeStalled);
}

get edgeStalled (): number {
const { levelDetails } = this;
if (levelDetails === null) {
return 0;
}
const maxLevelUpdateAge = ((this.config.lowLatencyMode && levelDetails.partTarget) || levelDetails.targetduration) * 3;
return Math.max(levelDetails.age - maxLevelUpdateAge, 0);
}

get maxLatency (): number {
const { config, levelDetails } = this;
if (levelDetails === null) {
return 0;
}
return config.liveMaxLatencyDuration !== undefined
? config.liveMaxLatencyDuration
: config.liveMaxLatencyDurationCount * levelDetails.targetduration;
}

public destroy (): void {
Expand Down Expand Up @@ -126,9 +136,21 @@ export default class LatencyController implements ComponentAPI {
if (latencyTarget === null) {
return;
}
const distance = latency - latencyTarget;
if (distance && levelDetails.live) {
media.playbackRate = Math.max(0.1, sigmoid(latency, latencyTarget));
const { minLiveSyncPlaybackRate, maxLiveSyncPlaybackRate } = this.config;
if (minLiveSyncPlaybackRate === 1 && maxLiveSyncPlaybackRate === 1) {
return;
}
const distanceFromTarget = latency - latencyTarget;
if (distanceFromTarget && levelDetails.live) {
const distanceFromEdge = levelDetails.edge - this.currentTime;
const min = Math.min(1, Math.max(0.5, minLiveSyncPlaybackRate));
if (distanceFromEdge > 0.5) {
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
const rate = 2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled));
media.playbackRate = Math.min(max, Math.max(min, rate));
} else {
media.playbackRate = Math.min(1, Math.max(min, media.playbackRate - 0.125));
}
} else if (media.playbackRate !== 1) {
media.playbackRate = 1;
}
Expand All @@ -150,7 +172,7 @@ export default class LatencyController implements ComponentAPI {
return liveEdge - this.currentTime;
}

private computeTargetLatency (): number | null {
public computeTargetLatency (): number | null {
const { levelDetails } = this;
if (levelDetails === null) {
return null;
Expand Down
19 changes: 19 additions & 0 deletions src/hls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -722,9 +722,28 @@ export default class Hls implements HlsEventEmitter {

/**
* estimated position (in seconds) of live edge (ie edge of live playlist plus time sync playlist advanced)
* returns 0 before first playlist is loaded
* @type {number}
*/
get latency () {
return this.latencyController.latency;
}

/**
* maximum distance from the edge before the player seeks forward to ```hls.liveSyncPosition```
* configured using ```liveMaxLatencyDurationCount``` (multiple of target duration) or ```liveMaxLatencyDuration```
* returns 0 before first playlist is loaded
* @type {number}
*/
get maxLatency (): number {
return this.latencyController.maxLatency;
}

/**
* target distance from the edge as calculated by the latency controller
* @type {number}
*/
get targetLatency (): number | null {
return this.latencyController.computeTargetLatency();
}
}

0 comments on commit 9274472

Please sign in to comment.