Skip to content

Commit

Permalink
Merge pull request #2813 from epiclabsDASH/fix_safari_firefox_low_lat…
Browse files Browse the repository at this point in the history
…ency

Low latency improvements
  • Loading branch information
epiclabsDASH authored Oct 17, 2018
2 parents 79d785d + a6ed5b9 commit 96898ae
Show file tree
Hide file tree
Showing 27 changed files with 430 additions and 194 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jsdoc
dist
build/temp
build/es5
build/typings
coverage

#################
Expand Down
9 changes: 5 additions & 4 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,11 @@ declare namespace dashjs {
removeAllABRCustomRule(): void;
getLowLatencyEnabled(): boolean;
setLowLatencyEnabled(value: boolean): void;
getCatchUpPlaybackRate(): number;
setCatchUpPlaybackRate(value: number): void;
enableLowLatencyCatchUp(value: boolean): void;
getLowLatencyMinDrift(): number;
setLowLatencyMinDrift(value: number): void;
getLowLatencyMaxDriftBeforeSeeking(): number;
setLowLatencyMaxDriftBeforeSeeking(value: number): void;
getUseDeadTimeLatencyForAbr(): boolean;
setUseDeadTimeLatencyForAbr(value: boolean): void;
getCurrentLiveLatency(): number;
Expand Down Expand Up @@ -349,8 +352,6 @@ declare namespace dashjs {
METRIC_UPDATED: 'metricUpdated';
PERIOD_SWITCH_COMPLETED: 'periodSwitchCompleted';
PERIOD_SWITCH_STARTED: 'periodSwitchStarted';
PLAYBACK_CATCHUP_END: 'playbackCatchupEnd';
PLAYBACK_CATCHUP_START: 'playbackCatchupStart';
PLAYBACK_ENDED: 'playbackEnded';
PLAYBACK_ERROR: 'playbackError';
PLAYBACK_METADATA_LOADED: 'playbackMetaDataLoaded';
Expand Down
43 changes: 31 additions & 12 deletions samples/dash-if-reference-player/app/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,14 +321,14 @@ app.controller('DashController', function ($scope, sources, contributors, dashif
$scope.player.on(dashjs.MediaPlayer.events.QUALITY_CHANGE_REQUESTED, function (e) { /* jshint ignore:line */
$scope[e.mediaType + 'Index'] = e.oldQuality + 1;
$scope[e.mediaType + 'PendingIndex'] = e.newQuality + 1;
$scope.plotPoint('pendingIndex', e.mediaType, e.newQuality + 1);
$scope.plotPoint('pendingIndex', e.mediaType, e.newQuality + 1, getTimeForPlot());
$scope.safeApply();
}, $scope);

$scope.player.on(dashjs.MediaPlayer.events.QUALITY_CHANGE_RENDERED, function (e) { /* jshint ignore:line */
$scope[e.mediaType + 'Index'] = e.newQuality + 1;
$scope[e.mediaType + 'PendingIndex'] = e.newQuality + 1;
$scope.plotPoint('index', e.mediaType, e.newQuality + 1);
$scope.plotPoint('index', e.mediaType, e.newQuality + 1, getTimeForPlot());
$scope.safeApply();
}, $scope);

Expand Down Expand Up @@ -661,12 +661,12 @@ app.controller('DashController', function ($scope, sources, contributors, dashif
}
};

$scope.plotPoint = function (name, type, value) {
$scope.plotPoint = function (name, type, value, time) {
if ($scope.chartEnabled) {
var specificChart = $scope.chartState[type];
if (specificChart) {
var data = specificChart[name].data;
data.push([$scope.video.currentTime, value]);
data.push([time, value]);
if (data.length > $scope.maxPointsToChart) {
data.splice(0, 1);
}
Expand Down Expand Up @@ -711,6 +711,11 @@ app.controller('DashController', function ($scope, sources, contributors, dashif
$scope.chartOptions.legend.noColumns = Math.min($scope.chartData.length, 5);
};

function getTimeForPlot() {
var now = new Date().getTime() / 1000;
return Math.max(now - $scope.sessionStartTime, 0);
}

function updateMetrics(type) {
var metrics = $scope.player.getMetricsFor(type);
var dashMetrics = $scope.player.getDashMetrics();
Expand Down Expand Up @@ -742,16 +747,17 @@ app.controller('DashController', function ($scope, sources, contributors, dashif
}

if ($scope.chartCount % 2 === 0) {
$scope.plotPoint('buffer', type, bufferLevel);
$scope.plotPoint('index', type, index);
$scope.plotPoint('bitrate', type, bitrate);
$scope.plotPoint('droppedFPS', type, droppedFPS);
$scope.plotPoint('liveLatency', type, liveLatency);
var time = getTimeForPlot();
$scope.plotPoint('buffer', type, bufferLevel, time);
$scope.plotPoint('index', type, index, time);
$scope.plotPoint('bitrate', type, bitrate, time);
$scope.plotPoint('droppedFPS', type, droppedFPS, time);
$scope.plotPoint('liveLatency', type, liveLatency, time);

if (httpMetrics) {
$scope.plotPoint('download', type, httpMetrics.download[type].average.toFixed(2));
$scope.plotPoint('latency', type, httpMetrics.latency[type].average.toFixed(2));
$scope.plotPoint('ratio', type, httpMetrics.ratio[type].average.toFixed(2));
$scope.plotPoint('download', type, httpMetrics.download[type].average.toFixed(2), time);
$scope.plotPoint('latency', type, httpMetrics.latency[type].average.toFixed(2), time);
$scope.plotPoint('ratio', type, httpMetrics.ratio[type].average.toFixed(2), time);
}
$scope.safeApply();
}
Expand Down Expand Up @@ -833,6 +839,19 @@ app.controller('DashController', function ($scope, sources, contributors, dashif
} catch (e) {}
}


if (vars && vars.hasOwnProperty('targetLatency')) {
let targetLatency = parseInt(vars.targetLatency, 10);
if (!isNaN(targetLatency)) {
item.bufferConfig = {
lowLatencyMode: true,
liveDelay: targetLatency / 1000
};

$scope.lowLatencyModeSelected = true;
}
}

if (item.url) {
var startPlayback = false;

Expand Down
12 changes: 10 additions & 2 deletions samples/dash-if-reference-player/app/sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,23 @@
"name": "Low Latency (Single-Rate) (livesim-chunked)",
"bufferConfig" : {
"lowLatencyMode": true,
"liveDelay": 2.8
"liveDelay": 4
}
},
{
"url": "https://livesim.dashif.org/livesim-chunked/chunkdur_1/ato_7/testpic4_8s/Manifest.mpd",
"name": "Low Latency (Multi-Rate) (livesim-chunked)",
"bufferConfig" : {
"lowLatencyMode": true,
"liveDelay": 2.8
"liveDelay": 4
}
},
{
"url": "https://akamaibroadcasteruseast.akamaized.net/cmaf/live/657078/akasource/out.mpd",
"name": "Akamai Low Latency Stream",
"bufferConfig" : {
"lowLatencyMode": true,
"liveDelay": 3
}
},
{
Expand Down
1 change: 0 additions & 1 deletion src/dash/utils/TemplateSegmentsGetter.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ function TemplateSegmentsGetter(config, isDynamic) {
endIdx = segmentRange.end;

for (periodSegIdx = startIdx; periodSegIdx <= endIdx; periodSegIdx++) {

seg = getIndexBasedSegment(timelineConverter, isDynamic, representation, periodSegIdx);
seg.replacementTime = (start + periodSegIdx - 1) * representation.segmentDuration;
url = template.media;
Expand Down
1 change: 0 additions & 1 deletion src/dash/utils/TimelineConverter.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,6 @@ function TimelineConverter() {
if (isClientServerTimeSyncCompleted) return;

if (e.offset !== undefined) {

setClientTimeOffset(e.offset / 1000);
isClientServerTimeSyncCompleted = true;

Expand Down
6 changes: 5 additions & 1 deletion src/streaming/FragmentLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import HTTPLoader from './net/HTTPLoader';
import HeadRequest from './vo/HeadRequest';
import DashJSError from './vo/DashJSError';
import EventBus from './../core/EventBus';
import BoxParser from '../streaming/utils/BoxParser';
import Events from './../core/events/Events';
import Errors from './../core/errors/Errors';
import FactoryMaker from '../core/FactoryMaker';
Expand All @@ -46,11 +47,13 @@ function FragmentLoader(config) {
httpLoader;

function setup() {
const boxParser = BoxParser(context).getInstance();
httpLoader = HTTPLoader(context).create({
errHandler: config.errHandler,
metricsModel: config.metricsModel,
mediaPlayerModel: config.mediaPlayerModel,
requestModifier: config.requestModifier,
boxParser: boxParser,
useFetch: config.mediaPlayerModel.getLowLatencyEnabled()
});
}
Expand Down Expand Up @@ -97,7 +100,8 @@ function FragmentLoader(config) {
request: request,
progress: function (event) {
eventBus.trigger(Events.LOADING_PROGRESS, {
request: request
request: request,
stream: event.stream
});
if (event.data) {
eventBus.trigger(Events.LOADING_DATA_PROGRESS, {
Expand Down
104 changes: 89 additions & 15 deletions src/streaming/MediaPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ function MediaPlayer() {
const ELEMENT_NOT_ATTACHED_ERROR = 'You must first call attachView() to set the video element before calling this method';
const SOURCE_NOT_ATTACHED_ERROR = 'You must first call attachSource() with a valid source before calling this method';
const MEDIA_PLAYER_NOT_INITIALIZED_ERROR = 'MediaPlayer not initialized!';
const PLAYBACK_CATCHUP_RATE_BAD_ARGUMENT_ERROR = 'Playback catchup rate invalid argument! Use a number from 0 to 0.2';
const PLAYBACK_LOW_LATENCY_MIN_DRIFT_BAD_ARGUMENT_ERROR = 'Playback minimum drift has an invalid value! Use a number from 0 to 0.5';
const PLAYBACK_LOW_LATENCY_MAX_DRIFT_BAD_ARGUMENT_ERROR = 'Playback maximum drift has an invalid value! Use a number greater or equal to 0';

const context = this.context;
const eventBus = EventBus(context).getInstance();
Expand Down Expand Up @@ -484,25 +485,22 @@ function MediaPlayer() {
}

/**
* Use this method to set the catch up rate, as a percentage, for low latency live streams. In low latency mode,
* when measured latency is higher than the target one ({@link module:MediaPlayer#setLiveDelay setLiveDelay()}),
* dash.js increases playback rate the percentage defined with this method until target is reached.
* Use this method 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 ({@link module:MediaPlayer#setLiveDelay setLiveDelay()}),
* 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-20%. Set it to 0% to turn off live catch up feature.
* 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.
*
* @param {number} value Percentage in which playback rate is increased when live catch up mechanism is activated.
* @param {number} value Percentage in which playback rate is increased/decreased when live catch up mechanism is activated.
* @memberof module:MediaPlayer
* @see {@link module:MediaPlayer#setLiveDelay setLiveDelay()}
* @default {number} 0.05
* @default {number} 0.5
* @instance
*/
function setCatchUpPlaybackRate(value) {
if ( typeof value !== 'number' || isNaN(value) || value < 0.0 || value > 0.20) {
throw PLAYBACK_CATCHUP_RATE_BAD_ARGUMENT_ERROR;
}
playbackController.setCatchUpPlaybackRate(value);
mediaPlayerModel.setCatchUpPlaybackRate(value);
}

/**
Expand All @@ -513,7 +511,78 @@ function MediaPlayer() {
* @instance
*/
function getCatchUpPlaybackRate() {
return playbackController.getCatchUpPlaybackRate();
return mediaPlayerModel.getCatchUpPlaybackRate();
}


/**
* Use this method to set the minimum latency deviation allowed before activating catch-up mechanism. In low latency mode,
* when the difference between the measured latency and the target one ({@link module:MediaPlayer#setLiveDelay setLiveDelay()}),
* as an absolute number, is higher than the one sets with this method, then dash.js increases/decreases
* playback rate until target latency is reached.
*
* LowLatencyMinDrift should be provided in seconds, and it uses values between 0.0 and 0.5.
*
* Note: Catch-up mechanism is only applied when playing low latency live streams.
*
* @param {number} value Maximum difference between measured latency and the target one before applying playback rate modifications.
* @memberof module:MediaPlayer
* @see {@link module:MediaPlayer#setLiveDelay setLiveDelay()}
* @default {number} 0.05
* @instance
*/
function setLowLatencyMinDrift(value) {
if ( typeof value !== 'number' || isNaN(value) || value < 0.0 || value > 0.50) {
throw PLAYBACK_LOW_LATENCY_MIN_DRIFT_BAD_ARGUMENT_ERROR;
}
mediaPlayerModel.setLowLatencyMinDrift(value);
}

/**
* Returns the current latency minimum allowed drift.
* @returns {number}
* @see {@link module:MediaPlayer#setLowLatencyMinDrift setLowLatencyMinDrift()}
* @memberof module:MediaPlayer
* @instance
*/
function getLowLatencyMinDrift() {
return mediaPlayerModel.getLowLatencyMinDrift();
}

/**
* Use this method to set the maximum latency deviation allowed before dash.js to do a seeking to live position. In low latency mode,
* when the difference between the measured latency and the target one ({@link module:MediaPlayer#setLiveDelay setLiveDelay()}),
* as an absolute number, is higher than the one sets with this method, then dash.js does a seek to live edge position minus
* the target live delay.
*
* LowLatencyMaxDriftBeforeSeeking should be provided in seconds. If 0, then seeking operations won't be used for
* fixing latency deviations.
*
* Note: Catch-up mechanism is only applied when playing low latency live streams.
*
* @param {number} value Maximum difference between measured latency and the target one before using seek to
* fix drastically live latency deviations.
* @memberof module:MediaPlayer
* @see {@link module:MediaPlayer#setLiveDelay setLiveDelay()}
* @default {number} 0
* @instance
*/
function setLowLatencyMaxDriftBeforeSeeking(value) {
if ( typeof value !== 'number' || isNaN(value) || value < 0) {
throw PLAYBACK_LOW_LATENCY_MAX_DRIFT_BAD_ARGUMENT_ERROR;
}
mediaPlayerModel.setLowLatencyMaxDriftBeforeSeeking(value);
}

/**
* Returns the maximum latency drift before applying a seek operation to reduce the latency.
* @returns {number}
* @see {@link module:MediaPlayer#setLowLatencyMaxDriftBeforeSeeking setLowLatencyMaxDriftBeforeSeeking()}
* @memberof module:MediaPlayer
* @instance
*/
function getLowLatencyMaxDriftBeforeSeeking() {
return mediaPlayerModel.getLowLatencyMaxDriftBeforeSeeking();
}

/**
Expand Down Expand Up @@ -2686,6 +2755,7 @@ function MediaPlayer() {
dashManifestModel: dashManifestModel,
adapter: adapter,
videoModel: videoModel,
timelineConverter: timelineConverter,
uriFragmentModel: uriFragmentModel
});

Expand Down Expand Up @@ -2860,8 +2930,6 @@ function MediaPlayer() {
seek: seek,
setPlaybackRate: setPlaybackRate,
getPlaybackRate: getPlaybackRate,
setCatchUpPlaybackRate: setCatchUpPlaybackRate,
getCatchUpPlaybackRate: getCatchUpPlaybackRate,
setMute: setMute,
isMuted: isMuted,
setVolume: setVolume,
Expand Down Expand Up @@ -2973,8 +3041,14 @@ function MediaPlayer() {
getJumpGaps: getJumpGaps,
setSmallGapLimit: setSmallGapLimit,
getSmallGapLimit: getSmallGapLimit,
getLowLatencyEnabled: getLowLatencyEnabled,
setLowLatencyEnabled: setLowLatencyEnabled,
getLowLatencyEnabled: getLowLatencyEnabled,
setCatchUpPlaybackRate: setCatchUpPlaybackRate,
getCatchUpPlaybackRate: getCatchUpPlaybackRate,
setLowLatencyMinDrift: setLowLatencyMinDrift,
getLowLatencyMinDrift: getLowLatencyMinDrift,
setLowLatencyMaxDriftBeforeSeeking: setLowLatencyMaxDriftBeforeSeeking,
getLowLatencyMaxDriftBeforeSeeking: getLowLatencyMaxDriftBeforeSeeking,
setManifestUpdateRetryInterval: setManifestUpdateRetryInterval,
getManifestUpdateRetryInterval: getManifestUpdateRetryInterval,
setLongFormContentDurationThreshold: setLongFormContentDurationThreshold,
Expand Down
17 changes: 0 additions & 17 deletions src/streaming/MediaPlayerEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,23 +225,6 @@ class MediaPlayerEvents extends EventsBase {
*/
this.CAN_PLAY = 'canPlay';

/**
* Sent when live catch mechanism has been activated, which implies the measured latency of the low latency
* stream that is been played has gone beyond the target one.
* @see {@link module:MediaPlayer#setCatchUpPlaybackRate setCatchUpPlaybackRate()}
* @see {@link module:MediaPlayer#setLiveDelay setLiveDelay()}
* @event MediaPlayerEvents#PLAYBACK_CATCHUP_START
*/
this.PLAYBACK_CATCHUP_START = 'playbackCatchupStart';

/**
* Sent live catch up mechanism has been deactivated.
* @see {@link module:MediaPlayer#setCatchUpPlaybackRate setCatchUpPlaybackRate()}
* @see {@link module:MediaPlayer#setLiveDelay setLiveDelay()}
* @event MediaPlayerEvents#PLAYBACK_CATCHUP_END
*/
this.PLAYBACK_CATCHUP_END = 'playbackCatchupEnd';

/**
* Sent when playback completes.
* @event MediaPlayerEvents#PLAYBACK_ENDED
Expand Down
Loading

0 comments on commit 96898ae

Please sign in to comment.