Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new catchup seeking/pausing logic as discussed during F2F #4003

Merged
merged 7 commits into from
Jul 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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