diff --git a/demo/config.js b/demo/config.js index 25b0bb20e6..c52c04a43d 100644 --- a/demo/config.js +++ b/demo/config.js @@ -407,6 +407,14 @@ shakaDemo.Config = class { .addNumberInput_('Playback rate for live sync', 'streaming.liveSyncPlaybackRate', /* canBeDecimal= */ true, + /* canBeZero= */ false) + .addNumberInput_('Min latency for live sync', + 'streaming.liveSyncMinLatency', + /* canBeDecimal= */ true, + /* canBeZero= */ true) + .addNumberInput_('Min playback rate for live sync', + 'streaming.liveSyncMinPlaybackRate', + /* canBeDecimal= */ true, /* canBeZero= */ false); if (!shakaDemoMain.getNativeControlsEnabled()) { diff --git a/externs/shaka/manifest.js b/externs/shaka/manifest.js index 6546404b3f..eb04b55113 100644 --- a/externs/shaka/manifest.js +++ b/externs/shaka/manifest.js @@ -152,18 +152,26 @@ shaka.extern.InitDataOverride; /** * @typedef {{ * maxLatency: ?number, - * maxPlaybackRate: ?number + * maxPlaybackRate: ?number, + * minLatency: ?number, + * minPlaybackRate: ?number * }} * * @description - * Maximum latency and playback rate for a manifest. When max latency is reached - * playbackrate is updated to maxPlaybackRate to decrease latency. + * Maximum and minimun latency and playback rate for a manifest. When max + * latency is reached playbackrate is updated to maxPlaybackRate to decrease + * latency. When min latency is reached playbackrate is updated to + * minPlaybackRate to increase latency. * More information {@link https://dashif.org/docs/CR-Low-Latency-Live-r8.pdf here}. * * @property {?number} maxLatency * Maximum latency in seconds. * @property {?number} maxPlaybackRate * Maximum playback rate. + * @property {?number} minLatency + * Minimun latency in seconds. + * @property {?number} minPlaybackRate + * Minimun playback rate. * * @exportDoc */ diff --git a/externs/shaka/player.js b/externs/shaka/player.js index d3c8e0eeef..f947ef3f44 100644 --- a/externs/shaka/player.js +++ b/externs/shaka/player.js @@ -1105,7 +1105,9 @@ shaka.extern.ManifestConfiguration; * segmentPrefetchLimit: number, * liveSync: boolean, * liveSyncMaxLatency: number, - * liveSyncPlaybackRate: number + * liveSyncPlaybackRate: number, + * liveSyncMinLatency: number, + * liveSyncMinPlaybackRate: number * }} * * @description @@ -1234,6 +1236,13 @@ shaka.extern.ManifestConfiguration; * Playback rate used for latency chasing. It is recommended to use a value * between 1 and 2. Effective only if liveSync is true. Defaults to * 1.1. + * @property {number} liveSyncMinLatency + * Minimun acceptable latency, in seconds. Effective only if liveSync is + * true. Defaults to 0. + * @property {number} liveSyncMinPlaybackRate + * Minimun playback rate used for latency chasing. It is recommended to use a + * value between 0 and 1. Effective only if liveSync is true. Defaults to + * 1. * @exportDoc */ shaka.extern.StreamingConfiguration; diff --git a/lib/dash/dash_parser.js b/lib/dash/dash_parser.js index cd89ec7dc9..9b53ddea51 100644 --- a/lib/dash/dash_parser.js +++ b/lib/dash/dash_parser.js @@ -603,8 +603,19 @@ shaka.dash.DashParser = class { const maxPlaybackRate = playbackRateNode ? parseFloat(playbackRateNode.getAttribute('max')) : null; + const minLatency = latencyNode && latencyNode.getAttribute('min') ? + parseInt(latencyNode.getAttribute('min'), 10) / 1000 : + null; + const minPlaybackRate = playbackRateNode ? + parseFloat(playbackRateNode.getAttribute('min')) : + null; - return {maxLatency, maxPlaybackRate}; + return { + maxLatency, + maxPlaybackRate, + minLatency, + minPlaybackRate, + }; } return null; diff --git a/lib/player.js b/lib/player.js index a45fcaba77..a827bbae3c 100644 --- a/lib/player.js +++ b/lib/player.js @@ -5471,6 +5471,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget { } } + let liveSyncMinLatency; + let liveSyncMinPlaybackRate; + if (this.config_.streaming.liveSync) { + liveSyncMinLatency = this.config_.streaming.liveSyncMinLatency; + liveSyncMinPlaybackRate = this.config_.streaming.liveSyncMinPlaybackRate; + } else { + // serviceDescription must override if it is defined in the MPD and + // liveSync configuration is not set. + if (this.manifest_ && this.manifest_.serviceDescription) { + liveSyncMinLatency = this.manifest_.serviceDescription.minLatency || + this.config_.streaming.liveSyncMinLatency; + liveSyncMinPlaybackRate = + this.manifest_.serviceDescription.minPlaybackRate || + this.config_.streaming.liveSyncMinPlaybackRate; + } + } + const playbackRate = this.video_.playbackRate; const latency = seekRange.end - this.video_.currentTime; let offset = 0; @@ -5494,6 +5511,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget { 'Updating playbackRate to ' + liveSyncPlaybackRate); this.trickPlay(liveSyncPlaybackRate); } + } else if (liveSyncMinLatency && liveSyncMinPlaybackRate && + (latency - offset) < liveSyncMinLatency) { + if (playbackRate != liveSyncMinPlaybackRate) { + shaka.log.debug('Latency (' + latency + 's) ' + + 'is smaller than liveSyncMinLatency (' + liveSyncMinLatency + 's). ' + + 'Updating playbackRate to ' + liveSyncMinPlaybackRate); + this.trickPlay(liveSyncMinPlaybackRate); + } } else if (playbackRate !== this.playRateController_.getDefaultRate()) { this.cancelTrickPlay(); } diff --git a/lib/util/player_configuration.js b/lib/util/player_configuration.js index 8c29d6c873..2c1f707fc7 100644 --- a/lib/util/player_configuration.js +++ b/lib/util/player_configuration.js @@ -216,6 +216,8 @@ shaka.util.PlayerConfiguration = class { liveSync: false, liveSyncMaxLatency: 1, liveSyncPlaybackRate: 1.1, + liveSyncMinLatency: 0, + liveSyncMinPlaybackRate: 1, }; // WebOS, Tizen, Chromecast and Hisense have long hardware pipelines diff --git a/test/dash/dash_parser_manifest_unit.js b/test/dash/dash_parser_manifest_unit.js index 29c39989f4..99d7c3c19f 100644 --- a/test/dash/dash_parser_manifest_unit.js +++ b/test/dash/dash_parser_manifest_unit.js @@ -2611,8 +2611,8 @@ describe('DashParser Manifest', () => { '', ' ', - ' ', - ' ', + ' ', + ' ', ' ', '', ].join('\n'); @@ -2624,6 +2624,8 @@ describe('DashParser Manifest', () => { expect(manifest.serviceDescription.maxLatency).toBe(2); expect(manifest.serviceDescription.maxPlaybackRate).toBe(1.1); + expect(manifest.serviceDescription.minLatency).toBe(1); + expect(manifest.serviceDescription.minPlaybackRate).toBe(0.95); }); }); });