Skip to content

Commit

Permalink
Implement new catchup seeking/pausing logic as discussed during F2F (D…
Browse files Browse the repository at this point in the history
…ash-Industry-Forum#4003)

* Implement new catchup seeking logic as discussed during F2F

* Refactoring of some of the new functions

* Fix linting errors

* Use getOriginalLiveDelay in StreamController.js

* Adjust targetLiveDelay when user triggers play

* Fix attribute in index.d.ts

* Adjust LL testplayer sample
  • Loading branch information
dsilhavy authored Jul 27, 2022
1 parent 5dc7ec0 commit f5422bd
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 90 deletions.
2 changes: 1 addition & 1 deletion contrib/akamai/controlbar/ControlBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ var ControlBar = function (dashjsMediaPlayer, displayUTCTimeCodes) {
};

var seekLive = function () {
self.player.seek(self.player.duration());
self.player.seekToOriginalLive();
};

//************************************************************************************
Expand Down
3 changes: 2 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ declare namespace dashjs {
liveCatchup?: {
maxDrift?: number;
playbackRate?: number;
latencyThreshold?: number,
playbackBufferMin?: number,
enabled?: boolean
mode?: string
Expand Down Expand Up @@ -467,6 +466,8 @@ declare namespace dashjs {

seek(value: number): void;

seekToOriginalLive(): void;

setPlaybackRate(value: number): void;

getPlaybackRate(): number;
Expand Down
14 changes: 14 additions & 0 deletions samples/low-latency/testplayer/main.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
.videoContainer{
position: relative;
}

video {
width: 100%;
height: auto;
margin: auto;
}

#manifest {
Expand Down Expand Up @@ -29,4 +35,12 @@ video {
#metric-chart {
max-height: 400px;
min-height: 400px;
}

.video-controller {
margin-top: -5px !important;
}

.dash-video-player {
background: #000000;
}
13 changes: 3 additions & 10 deletions samples/low-latency/testplayer/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var METRIC_INTERVAL = 300;

var App = function () {
this.player = null;
this.controlbar = null;
this.video = null;
this.chart = null;
this.domElements = {
Expand Down Expand Up @@ -30,7 +31,6 @@ App.prototype._setDomElements = function () {
this.domElements.settings.targetLatency = document.getElementById('target-latency');
this.domElements.settings.maxDrift = document.getElementById('max-drift');
this.domElements.settings.catchupPlaybackRate = document.getElementById('catchup-playback-rate');
this.domElements.settings.liveCatchupLatencyThreshold = document.getElementById('catchup-threshold');
this.domElements.settings.abrAdditionalInsufficientBufferRule = document.getElementById('abr-additional-insufficient')
this.domElements.settings.abrAdditionalDroppedFramesRule = document.getElementById('abr-additional-dropped');
this.domElements.settings.abrAdditionalAbandonRequestRule = document.getElementById('abr-additional-abandon');
Expand All @@ -46,7 +46,6 @@ App.prototype._setDomElements = function () {
this.domElements.metrics.latencyTag = document.getElementById('latency-tag');
this.domElements.metrics.playbackrateTag = document.getElementById('playbackrate-tag');
this.domElements.metrics.bufferTag = document.getElementById('buffer-tag');
this.domElements.metrics.catchupThresholdTag = document.getElementById('catchup-threshold-tag');
this.domElements.metrics.sec = document.getElementById('sec');
this.domElements.metrics.min = document.getElementById('min');
this.domElements.metrics.videoMaxIndex = document.getElementById('video-max-index');
Expand All @@ -71,6 +70,8 @@ App.prototype._load = function () {
this._registerDashEventHandler();
this._applyParameters();
this.player.initialize(this.video, url, true);
this.controlbar = new ControlBar(this.player);
this.controlbar.initialize();
}

App.prototype._applyParameters = function () {
Expand All @@ -89,7 +90,6 @@ App.prototype._applyParameters = function () {
liveCatchup: {
maxDrift: settings.maxDrift,
playbackRate: settings.catchupPlaybackRate,
latencyThreshold: settings.liveCatchupLatencyThreshold,
mode: settings.catchupMechanism
},
abr: {
Expand Down Expand Up @@ -133,9 +133,6 @@ App.prototype._adjustSettingsByUrlParameters = function () {
if (params.catchupPlaybackRate !== undefined) {
this.domElements.settings.catchupPlaybackRate.value = parseFloat(params.catchupPlaybackRate).toFixed(1);
}
if (params.liveCatchupLatencyThreshold !== undefined) {
this.domElements.settings.liveCatchupLatencyThreshold.value = parseFloat(params.liveCatchupLatencyThreshold).toFixed(0);
}
if (params.abrAdditionalInsufficientBufferRule !== undefined) {
this.domElements.settings.abrAdditionalInsufficientBufferRule.checked = params.abrAdditionalInsufficientBufferRule === 'true';
}
Expand Down Expand Up @@ -165,7 +162,6 @@ App.prototype._getCurrentSettings = function () {
var targetLatency = parseFloat(this.domElements.settings.targetLatency.value, 10);
var maxDrift = parseFloat(this.domElements.settings.maxDrift.value, 10);
var catchupPlaybackRate = parseFloat(this.domElements.settings.catchupPlaybackRate.value, 10);
var liveCatchupLatencyThreshold = parseFloat(this.domElements.settings.liveCatchupLatencyThreshold.value, 10);
var abrAdditionalInsufficientBufferRule = this.domElements.settings.abrAdditionalInsufficientBufferRule.checked;
var abrAdditionalDroppedFramesRule = this.domElements.settings.abrAdditionalDroppedFramesRule.checked;
var abrAdditionalAbandonRequestRule = this.domElements.settings.abrAdditionalAbandonRequestRule.checked;
Expand All @@ -178,7 +174,6 @@ App.prototype._getCurrentSettings = function () {
targetLatency,
maxDrift,
catchupPlaybackRate,
liveCatchupLatencyThreshold,
abrGeneral,
abrAdditionalInsufficientBufferRule,
abrAdditionalDroppedFramesRule,
Expand Down Expand Up @@ -344,8 +339,6 @@ App.prototype._startIntervalHandler = function () {
var currentBuffer = dashMetrics.getCurrentBufferLevel('video');
self.domElements.metrics.bufferTag.innerHTML = currentBuffer + ' secs';

self.domElements.metrics.catchupThresholdTag.innerHTML = settings.streaming.liveCatchup.latencyThreshold + ' secs';

var d = new Date();
var seconds = d.getSeconds();
self.domElements.metrics.sec.innerHTML = (seconds < 10 ? '0' : '') + seconds;
Expand Down
51 changes: 38 additions & 13 deletions samples/low-latency/testplayer/testplayer.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
<meta charset="utf-8">
<title>Low latency streaming - Testplayer</title>

<script src="../../../dist/dash.all.debug.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.3.2/dist/chart.min.js"></script>

<!-- Bootstrap core CSS -->
<link href="../../lib/bootstrap/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css">
<link rel="stylesheet" href="../../../contrib/akamai/controlbar/controlbar.css">
<link href="../../lib/main.css" rel="stylesheet">
<link href="main.css" rel="stylesheet">

<script src="../../../dist/dash.all.debug.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.3.2/dist/chart.min.js"></script>
<script src="../../../contrib/akamai/controlbar/Controlbar.js"></script>

</head>
<body>

Expand Down Expand Up @@ -71,11 +73,6 @@ <h6>General</h6>
<input type="number" id="catchup-playback-rate" class="form-control" value="0.1"
step="0.01" max="0.5" min="0.0">
</div>
<div class="input-group input-group-sm mb-3">
<span class="input-group-text"> Live catchup latency threshold (sec):</span>
<input type="number" class="form-control" value="60" min="0"
id="catchup-threshold">
</div>
</div>
<div class="col-md-3">
<h6>ABR - General</h6>
Expand Down Expand Up @@ -243,8 +240,39 @@ <h4>Export settings</h4>
</div>
</div>
<div class="row mt-2">
<div class="col-md-7">
<video controls="true"></video>
<div class="col-md-7 dash-video-player">
<div id="videoContainer" class="videoContainer">
<video></video>
<div id="videoController" class="video-controller unselectable">
<div id="playPauseBtn" class="btn-play-pause" title="Play/Pause">
<span id="iconPlayPause" class="icon-play"></span>
</div>
<span id="videoTime" class="time-display">00:00:00</span>
<div id="fullscreenBtn" class="btn-fullscreen control-icon-layout" title="Fullscreen">
<span class="icon-fullscreen-enter"></span>
</div>
<div id="bitrateListBtn" class="control-icon-layout" title="Bitrate List">
<span class="icon-bitrate"></span>
</div>
<input type="range" id="volumebar" class="volumebar" value="1" min="0" max="1" step=".01"/>
<div id="muteBtn" class="btn-mute control-icon-layout" title="Mute">
<span id="iconMute" class="icon-mute-off"></span>
</div>
<div id="trackSwitchBtn" class="control-icon-layout" title="A/V Tracks">
<span class="icon-tracks"></span>
</div>
<div id="captionBtn" class="btn-caption control-icon-layout" title="Closed Caption">
<span class="icon-caption"></span>
</div>
<span id="videoDuration" class="duration-display">00:00:00</span>
<div class="seekContainer">
<div id="seekbar" class="seekbar seekbar-complete">
<div id="seekbar-buffer" class="seekbar seekbar-buffer"></div>
<div id="seekbar-play" class="seekbar seekbar-play"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-5">
<div class="p-5 border rounded-3 mt-1">
Expand All @@ -267,9 +295,6 @@ <h5>Wall Clock reference time</h5>
<div><span class="metric-value"> Playback rate: </span><span
id="playbackrate-tag"></span>
</div>
<div><span
class="metric-value">Live catchup latency threshold: </span><span
id="catchup-threshold-tag"></span></div>
</div>
</div>
</div>
Expand Down
11 changes: 0 additions & 11 deletions src/core/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ import Events from './events/Events';
* maxDrift: NaN,
* playbackRate: NaN,
* playbackBufferMin: 0.5,
* latencyThreshold: 60,
* enabled: false,
* mode: Constants.LIVE_CATCHUP_MODE_DEFAULT
* },
Expand Down Expand Up @@ -462,15 +461,6 @@ import Events from './events/Events';
* Set it to NaN to turn off live catch up feature.
*
* Note: Catch-up mechanism is only applied when playing low latency live streams.
* @property {number} [latencyThreshold=60]
* 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 catchup mode will always be applied
*
* @property {number} [playbackBufferMin=NaN]
* Use this parameter to specify the minimum buffer which is used for LoL+ based playback rate reduction.
*
Expand Down Expand Up @@ -846,7 +836,6 @@ function Settings() {
playbackRate: NaN,
playbackBufferMin: 0.5,
enabled: null,
latencyThreshold: 60,
mode: Constants.LIVE_CATCHUP_MODE_DEFAULT
},
lastBitrateCachingInfo: {
Expand Down
24 changes: 18 additions & 6 deletions src/streaming/MediaPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ function MediaPlayer() {
throw PLAYBACK_NOT_INITIALIZED_ERROR;
}
if (!autoPlay || (isPaused() && playbackInitialized)) {
playbackController.play();
playbackController.play(true);
}
}

Expand Down Expand Up @@ -565,7 +565,8 @@ function MediaPlayer() {
* Sets the currentTime property of the attached video element. If it is a live stream with a
* timeShiftBufferLength, then the DVR window offset will be automatically calculated.
*
* @param {number} value - A relative time, in seconds, based on the return value of the {@link module:MediaPlayer#duration duration()} method is expected
* @param {number} value - A relative time, in seconds, based on the return value of the {@link module:MediaPlayer#duration duration()} method is expected.
* For dynamic streams duration() returns DVRWindow.end - DVRWindow.start. Consequently, the value provided to this function should be relative to DVRWindow.start.
* @see {@link module:MediaPlayer#getDVRSeekOffset getDVRSeekOffset()}
* @throws {@link module:MediaPlayer~PLAYBACK_NOT_INITIALIZED_ERROR PLAYBACK_NOT_INITIALIZED_ERROR} if called before initializePlayback function
* @throws {@link Constants#BAD_ARGUMENT_ERROR BAD_ARGUMENT_ERROR} if called with an invalid argument, not number type or is NaN.
Expand All @@ -584,7 +585,18 @@ function MediaPlayer() {
}

let s = playbackController.getIsDynamic() ? getDVRSeekOffset(value) : value;
playbackController.seek(s);
playbackController.seek(s, false, false, true);
}

/**
* Seeks back to the original live edge (live edge as calculated at playback start). Only applies to live streams, for VoD streams this call will be ignored.
*/
function seekToOriginalLive() {
if (!playbackInitialized || !isDynamic()) {
return;
}

playbackController.seekToOriginalLive();
}

/**
Expand Down Expand Up @@ -763,7 +775,7 @@ function MediaPlayer() {
return 0;
}

let liveDelay = playbackController.getLiveDelay();
let liveDelay = playbackController.getOriginalLiveDelay();

let val = metric.range.start + value;

Expand All @@ -785,7 +797,7 @@ function MediaPlayer() {
throw PLAYBACK_NOT_INITIALIZED_ERROR;
}

return playbackController.getLiveDelay();
return playbackController.getOriginalLiveDelay();
}

/**
Expand Down Expand Up @@ -2064,7 +2076,6 @@ function MediaPlayer() {
streamController,
playbackController,
mediaPlayerModel,
dashMetrics,
videoModel,
settings
})
Expand Down Expand Up @@ -2312,6 +2323,7 @@ function MediaPlayer() {
isDynamic,
getLowLatencyModeEnabled,
seek,
seekToOriginalLive,
setPlaybackRate,
getPlaybackRate,
setMute,
Expand Down
23 changes: 2 additions & 21 deletions src/streaming/controllers/CatchupController.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ function CatchupController() {
streamController,
playbackController,
mediaPlayerModel,
dashMetrics,
playbackStalled,
logger;

Expand Down Expand Up @@ -77,10 +76,6 @@ function CatchupController() {
playbackController = config.playbackController;
}

if (config.dashMetrics) {
dashMetrics = config.dashMetrics;
}

if (config.mediaPlayerModel) {
mediaPlayerModel = config.mediaPlayerModel;
}
Expand Down Expand Up @@ -206,7 +201,7 @@ function CatchupController() {
deltaLatency > maxDrift) {
logger.info('[CatchupController]: Low Latency catchup mechanism. Latency too high, doing a seek to live point');
isCatchupSeekInProgress = true;
_seekToLive();
playbackController.seekToCurrentLive(true, false);
}

// try to reach the target latency by adjusting the playback rate
Expand Down Expand Up @@ -250,8 +245,7 @@ function CatchupController() {
*/
function _shouldStartCatchUp() {
try {
const latencyThreshold = mediaPlayerModel.getLiveCatchupLatencyThreshold();
if (!playbackController.getTime() > 0 || isCatchupSeekInProgress || (!isNaN(latencyThreshold) && playbackController.getCurrentLiveLatency() >= latencyThreshold)) {
if (!playbackController.getTime() > 0 || isCatchupSeekInProgress) {
return false;
}

Expand Down Expand Up @@ -413,19 +407,6 @@ function CatchupController() {
return newRate
}

/**
* Seek to live edge
*/
function _seekToLive() {
const type = streamController && streamController.hasVideoTrack() ? Constants.VIDEO : Constants.AUDIO;
const DVRMetrics = dashMetrics.getCurrentDVRInfo(type);
const DVRWindow = DVRMetrics ? DVRMetrics.range : null;

if (DVRWindow && !isNaN(DVRWindow.end)) {
playbackController.seek(DVRWindow.end - playbackController.getLiveDelay(), true, false);
}
}

instance = {
reset,
setConfig,
Expand Down
Loading

0 comments on commit f5422bd

Please sign in to comment.