diff --git a/samples/dash-if-reference-player/dashjs_config.json b/samples/dash-if-reference-player/dashjs_config.json index f9811a8386..5385eb4085 100644 --- a/samples/dash-if-reference-player/dashjs_config.json +++ b/samples/dash-if-reference-player/dashjs_config.json @@ -27,6 +27,7 @@ "liveCatchUpMinDrift": 0.02, "liveCatchUpMaxDrift": 0, "liveCatchUpPlaybackRate": 0.5, + "liveCatchupLatencyThreshold": null, "lastBitrateCachingInfo": { "enabled": true, "ttl": 360000}, "lastMediaSettingsCachingInfo": { "enabled": true, "ttl": 360000}, "cacheLoadThresholds": {"video": 50, "audio": 5}, diff --git a/src/core/Settings.js b/src/core/Settings.js index 82471904b8..4b6b932fbe 100644 --- a/src/core/Settings.js +++ b/src/core/Settings.js @@ -296,13 +296,22 @@ import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest'; * * Note: Catch-up mechanism is only applied when playing low latency live streams. * @property {number} [liveCatchUpPlaybackRate=0.5] - * Use this method to set the maximum catch up rate, as a percentage, for low latency live streams. In low latency mode, + * Use this parameter to set the maximum catch up rate, as a percentage, for low latency live streams. In low latency mode, * when measured latency is higher/lower than the target one, * dash.js increases/decreases playback rate respectively up to (+/-) the percentage defined with this method until target is reached. * * Valid values for catch up rate are in range 0-0.5 (0-50%). Set it to 0 to turn off live catch up feature. * * Note: Catch-up mechanism is only applied when playing low latency live streams. + * @property {number} [liveCatchupLatencyThreshold=NaN] + * Use this parameter to set the maximum threshold for which live catch up is applied. For instance, if this value is set to 8 seconds, + * then live catchup is only applied if the current live latency is equal or below 8 seconds. The reason behind this parameter is to avoid an increase + * of the playback rate if the user seeks within the DVR window. + * + * If no value is specified this will be twice the maximum live delay. The maximum live delay is either specified in the manifest as part of a ServiceDescriptor or calculated the following: + * maximumLiveDelay = targetDelay + liveCatchupMinDrift + * + * Note: Catch-up mechanism is only applied when playing low latency live streams. * @property {module:Settings~CachingInfoSettings} [lastBitrateCachingInfo={enabled: true, ttl: 360000}] * Set to false if you would like to disable the last known bit rate from being stored during playback and used * to set the initial bit rate for subsequent playback within the expiration window. @@ -398,6 +407,7 @@ function Settings() { liveCatchUpMinDrift: 0.02, liveCatchUpMaxDrift: 0, liveCatchUpPlaybackRate: 0.5, + liveCatchupLatencyThreshold: NaN, lastBitrateCachingInfo: {enabled: true, ttl: 360000}, lastMediaSettingsCachingInfo: {enabled: true, ttl: 360000}, cacheLoadThresholds: {video: 50, audio: 5}, diff --git a/src/streaming/controllers/PlaybackController.js b/src/streaming/controllers/PlaybackController.js index dd4510a51c..7d774c2441 100644 --- a/src/streaming/controllers/PlaybackController.js +++ b/src/streaming/controllers/PlaybackController.js @@ -644,8 +644,12 @@ function PlaybackController() { } function needToCatchUp() { - return settings.get().streaming.liveCatchUpPlaybackRate > 0 && getTime() > 0 && - Math.abs(getCurrentLiveLatency() - mediaPlayerModel.getLiveDelay()) > settings.get().streaming.liveCatchUpMinDrift; + const currentLiveLatency = getCurrentLiveLatency(); + const latencyDrift = Math.abs(currentLiveLatency - mediaPlayerModel.getLiveDelay()); + const liveCatchupLatencyThreshold = mediaPlayerModel.getLiveCatchupLatencyThreshold(); + + return settings.get().streaming.lowLatencyEnabled && settings.get().streaming.liveCatchUpPlaybackRate > 0 && getTime() > 0 && + latencyDrift > settings.get().streaming.liveCatchUpMinDrift && (isNaN(liveCatchupLatencyThreshold) || currentLiveLatency <= liveCatchupLatencyThreshold ) ; } function startPlaybackCatchUp() { diff --git a/src/streaming/models/MediaPlayerModel.js b/src/streaming/models/MediaPlayerModel.js index a5cd3f1c52..c2f2ee3fad 100644 --- a/src/streaming/models/MediaPlayerModel.js +++ b/src/streaming/models/MediaPlayerModel.js @@ -33,7 +33,7 @@ import FactoryMaker from '../../core/FactoryMaker'; import Constants from '../constants/Constants'; import ABRRulesCollection from '../rules/abr/ABRRulesCollection'; import Settings from '../../core/Settings'; -import { checkParameterType} from '../utils/SupervisorTools'; +import {checkParameterType} from '../utils/SupervisorTools'; const DEFAULT_MIN_BUFFER_TIME = 12; @@ -42,6 +42,7 @@ const DEFAULT_MIN_BUFFER_TIME_FAST_SWITCH = 20; const DEFAULT_LOW_LATENCY_LIVE_DELAY = 3.0; const LOW_LATENCY_REDUCTION_FACTOR = 10; const LOW_LATENCY_MULTIPLY_FACTOR = 5; +const DEFAULT_LIVE_LATENCY_CATCHUP_THRESHOLD_FACTOR = 2; const DEFAULT_XHR_WITH_CREDENTIALS = false; @@ -53,9 +54,9 @@ function MediaPlayerModel() { customABRRule; const DEFAULT_UTC_TIMING_SOURCE = { - scheme: 'urn:mpeg:dash:utc:http-xsdate:2014', - value: 'http://time.akamai.com/?iso&ms' - }; + scheme: 'urn:mpeg:dash:utc:http-xsdate:2014', + value: 'http://time.akamai.com/?iso&ms' + }; const context = this.context; const settings = Settings(context).getInstance(); @@ -144,6 +145,29 @@ function MediaPlayerModel() { return settings.get().streaming.liveDelay; } + function getLiveCatchupLatencyThreshold() { + try { + const liveCatchupLatencyThreshold = settings.get().streaming.liveCatchupLatencyThreshold; + + if (liveCatchupLatencyThreshold !== null && !isNaN(liveCatchupLatencyThreshold)) { + return liveCatchupLatencyThreshold; + } + + const liveDelay = getLiveDelay(); + const liveCatchupMinDrift = settings.get().streaming.liveCatchUpMinDrift; + const maximumLiveDelay = !isNaN(liveDelay) && liveDelay ? !isNaN(liveCatchupMinDrift) ? settings.get().streaming.liveCatchUpMinDrift + getLiveDelay() : getLiveDelay() : NaN; + + if (maximumLiveDelay && !isNaN(maximumLiveDelay)) { + return maximumLiveDelay * DEFAULT_LIVE_LATENCY_CATCHUP_THRESHOLD_FACTOR; + } + + return NaN; + + } catch (e) { + return NaN; + } + } + function addUTCTimingSource(schemeIdUri, value) { removeUTCTimingSource(schemeIdUri, value); //check if it already exists and remove if so. let vo = new UTCTiming(); @@ -200,22 +224,23 @@ function MediaPlayerModel() { } instance = { - getABRCustomRules: getABRCustomRules, - addABRCustomRule: addABRCustomRule, - removeABRCustomRule: removeABRCustomRule, - getStableBufferTime: getStableBufferTime, - getRetryAttemptsForType: getRetryAttemptsForType, - getRetryIntervalsForType: getRetryIntervalsForType, - getLiveDelay: getLiveDelay, - addUTCTimingSource: addUTCTimingSource, - removeUTCTimingSource: removeUTCTimingSource, - getUTCTimingSources: getUTCTimingSources, - clearDefaultUTCTimingSources: clearDefaultUTCTimingSources, - restoreDefaultUTCTimingSources: restoreDefaultUTCTimingSources, - setXHRWithCredentialsForType: setXHRWithCredentialsForType, - getXHRWithCredentialsForType: getXHRWithCredentialsForType, - getDefaultUtcTimingSource: getDefaultUtcTimingSource, - reset: reset + getABRCustomRules, + addABRCustomRule, + removeABRCustomRule, + getStableBufferTime, + getRetryAttemptsForType, + getRetryIntervalsForType, + getLiveDelay, + getLiveCatchupLatencyThreshold, + addUTCTimingSource, + removeUTCTimingSource, + getUTCTimingSources, + clearDefaultUTCTimingSources, + restoreDefaultUTCTimingSources, + setXHRWithCredentialsForType, + getXHRWithCredentialsForType, + getDefaultUtcTimingSource, + reset }; setup(); diff --git a/test/unit/streaming.models.MediaPlayerModel.js b/test/unit/streaming.models.MediaPlayerModel.js index 1c6d134545..cc6925fe69 100644 --- a/test/unit/streaming.models.MediaPlayerModel.js +++ b/test/unit/streaming.models.MediaPlayerModel.js @@ -138,4 +138,24 @@ describe('MediaPlayerModel', function () { expect(StableBufferTime).to.equal(50); }); + it('should configure liveCatchupLatencyThreshold', function () { + let liveCatchupLatencyThreshold = mediaPlayerModel.getLiveCatchupLatencyThreshold(); + expect(liveCatchupLatencyThreshold).to.be.NaN; // jshint ignore:line + + settings.update({streaming: {lowLatencyEnabled: true, liveDelay: 3, liveCatchUpMinDrift: 3}}); + + liveCatchupLatencyThreshold = mediaPlayerModel.getLiveCatchupLatencyThreshold(); + expect(liveCatchupLatencyThreshold).to.equal(12); + + settings.update({streaming: {liveCatchUpMinDrift: NaN}}); + + liveCatchupLatencyThreshold = mediaPlayerModel.getLiveCatchupLatencyThreshold(); + expect(liveCatchupLatencyThreshold).to.equal(6); + + settings.update({streaming: {liveCatchupLatencyThreshold: 50}}); + + liveCatchupLatencyThreshold = mediaPlayerModel.getLiveCatchupLatencyThreshold(); + expect(liveCatchupLatencyThreshold).to.equal(50); + }); + });