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