Skip to content

Commit

Permalink
Learn2 adapt (#3424)
Browse files Browse the repository at this point in the history
* Learn2Adapt Low Latency bitrate adaptation algo

* Definition of L2ARule in ABRRules

* Update with abrL2A strategy

* comments

* Updated MPD url

* L2A implementation

* Remove duplicate constant

* Set BOLA to false when using L2A. Fix linting errors

* Remove unused settings parameter useBufferOccupancyABR

* Add L2A to default ABR rules

* Small refactor in RulesContext.js

* Add L2ALL sample page

* Add L2ALL to reference client

* Remove createAbrRulesCollection call from MediaPlayer.js

* Adjust logic in ABRRulesCollection.js

* Add mediaType sepcific handling to L2ARule.js and refactor some parts

Co-authored-by: thalka <theodore.karagkioules@gmail.com>
  • Loading branch information
dsilhavy and ThAlKa committed Nov 19, 2020
1 parent 8dd37d4 commit 23e559f
Show file tree
Hide file tree
Showing 14 changed files with 970 additions and 86 deletions.
1 change: 0 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ declare namespace dashjs {
ABRStrategy?: 'abrDynamic' | 'abrBola';
bandwidthSafetyFactor?: number;
useDefaultABRRules?: boolean;
useBufferOccupancyABR?: boolean;
useDeadTimeLatency?: boolean;
limitBitrateByPortal?: boolean;
usePixelRatioInLimitBitrateByPortal?: boolean;
Expand Down
5 changes: 5 additions & 0 deletions samples/dash-if-reference-player/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@
<input type="radio" id="abrThroughput" autocomplete="off" name="abrStrategy" ng-click="changeABRStrategy('abrThroughput')">
ABR Strategy: Throughput
</label>
<label data-toggle="tooltip" data-placement="right"
title="Choose bitrate based on recent throughput.">
<input type="radio" id="abrL2all" autocomplete="off" name="abrStrategy" ng-click="changeABRStrategy('abrL2A')">
ABR Strategy: L2A-LL
</label>
<label class="topcoat-checkbox" data-toggle="tooltip" data-placement="right"
title="ABR - Use custom ABR rules">
<input type="checkbox" id="customABRRules" ng-model="customABRRulesSelected" ng-change="toggleUseCustomABRRules()" ng-checked="customABRRulesSelected">
Expand Down
2 changes: 1 addition & 1 deletion samples/low-latency/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
'liveDelay': targetLatency,
'liveCatchUpMinDrift': minDrift,
'liveCatchUpPlaybackRate': catchupPlaybackRate,
"liveCatchupLatencyThreshold": liveCatchupLatencyThreshold,
'liveCatchupLatencyThreshold': liveCatchupLatencyThreshold,
}
});
}
Expand Down
286 changes: 286 additions & 0 deletions samples/low-latency/l2all_index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Low latency live stream instantiation example with local execution</title>
<script src="../../dist/dash.all.debug.js"></script>
<script src="../../dist/dash.all.debug.js"></script>
<script src="http://code.jquery.com/jquery-3.4.1.min.js"></script>


<script class="code">
var player, targetLatency, minDrift, catchupPlaybackRate, liveCatchupLatencyThreshold;

function init() {
var video,
url = 'https://livesim.dashif.org/livesim/chunkdur_1/ato_7/testpic4_8s/Manifest300.mpd';

video = document.querySelector("video");
player = dashjs.MediaPlayer().create();
player.initialize(video, url, true);
player.updateSettings({'streaming': {'lowLatencyEnabled': true}});
player.updateSettings({'debug': {'logLevel': dashjs.Debug.LOG_LEVEL_WARNING}});

player.updateSettings({
'streaming': {
'abr': {
'useDefaultABRRules': true,
'ABRStrategy': 'abrL2A'
}
}
});
applyParameters();

return player;
}

function applyParameters() {
targetLatency = parseFloat(document.getElementById("target-latency").value, 10);
minDrift = parseFloat(document.getElementById("min-drift").value, 10);
catchupPlaybackRate = parseFloat(document.getElementById("catchup-playback-rate").value, 10);
liveCatchupLatencyThreshold = parseFloat(document.getElementById("catchup-threshold").value, 10);

player.updateSettings({
'streaming': {
'liveDelay': targetLatency,
'liveCatchUpMinDrift': minDrift,
'liveCatchUpPlaybackRate': catchupPlaybackRate,
'liveCatchupLatencyThreshold': liveCatchupLatencyThreshold,
}
});
}
</script>

<style>
.video-wrapper {
display: flex;
flex-flow: row wrap;
}

.video-wrapper > div:nth-child(2) {
margin-left: 25px;
}

video {
width: 640px;
height: 360px;
}

ul {
margin: 0;
}

input {
width: 5em;
border: 1px solid gray;
padding: 0 4px 0 8px;
}

.help-container {
display: flex;
flex-flow: row wrap;
margin-top: 1em;
align-content: center;
background: white;
border: solid 1px #ddd;
padding: 0.5em;
}

.help-container > div {
width: 33.3%;
padding: 1em;
box-sizing: border-box;
}

.help-container h3 {
margin-top: 0;
}
</style>
</head>
<body>
<div>
<div class="video-wrapper">
<video controls="true" autoplay muted></video>
<div>
<div>
<form action="javascript:applyParameters()">
<fieldset>
<legend>Configurable parameters</legend>
<p>Target Latency (secs): <input type="number" id="target-latency" value="2" min="0" step="0.1">
</p>
<p>Min. drift (secs): <input type="number" id="min-drift" value="0.3" min="0.0" max="0.5"
step="0.01"></p>
<p>Catch-up playback rate (%): <input type="number" id="catchup-playback-rate" value="0.3"
min="0.0" max="0.5" step="0.01"></p>
<p>Live catchup latency threshold (secs): <input type="number" id="catchup-threshold" value="30">
</p>
<button type="submit">Apply</button>
</fieldset>
</form>
</div>
<br>
<fieldset>
<legend>Current values</legend>
<ul>
<li>Latency: <span id="latency-tag"></span></li>
<li>Min. drift: <span id="mindrift-tag"></span></li>
<li>Playback rate: <span id="playbackrate-tag"></span></li>
<li>Live catchup latency threshold : <span id="catchup-threshold-tag"></span></li>
<li>Buffer: <b><span id="buffer-tag"></span></b></li>
<li>Quality: <b><span id="quality-tag"></span></b></li>
</ul>
<div id="stats"></div>
</fieldset>
</div>
</div>

<p style="font-family:Arial,sans-serif; font-weight: bold; font-size: 1.1em">Concepts definition</p>
<div class="help-container">
<div id="latency-help">
<h3>Latency</h3>
<p>Lowering this value will lower latency but may decrease the player's ability to build a stable
buffer.</p>
<p><a href="http://cdn.dashjs.org/latest/jsdoc/module-MediaPlayer.html#setLiveDelay__anchor"
target="_blank">setLiveDelay() doc</a></p>
</div>

<div id="min-drift-help">
<h3>Min. drift</h3>
<p>Minimum latency deviation allowed before activating catch-up mechanism.</p>
<p><a href="http://cdn.dashjs.org/latest/jsdoc/module-MediaPlayer.html#setLowLatencyMinDrift__anchor"
target="_blank">setLowLatencyMinDrift() doc</a></p>
</div>

<div id="catch-up-playback-rate-help">
<h3>Catch-up playback rate</h3>
<p>Maximum catch-up rate, as a percentage, for low latency live streams.</p>
<p><a href="http://cdn.dashjs.org/latest/jsdoc/module-MediaPlayer.html#setCatchUpPlaybackRate__anchor"
target="_blank">setCatchUpPlaybackRate() doc</a></p>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
const player = init();
const video = document.querySelector("video")
let stallingAt = null;
const CMA = () => {
let average = 0;
let count = 0;

return {
average(val) {
if (isNaN(val)) {
return 0;
}
average = average + ((val - average) / ++count);
return average;
},
}
}

setInterval(function () {
var dashMetrics = player.getDashMetrics();
var settings = player.getSettings();

var currentLatency = parseFloat(player.getCurrentLiveLatency(), 10);
document.getElementById("latency-tag").innerHTML = currentLatency + " secs";

document.getElementById("mindrift-tag").innerHTML = settings.streaming.liveCatchUpMinDrift + " secs";

var currentPlaybackRate = player.getPlaybackRate();
document.getElementById("playbackrate-tag").innerHTML = Math.round(currentPlaybackRate * 100) / 100;

var currentBuffer = dashMetrics.getCurrentBufferLevel("video");
document.getElementById("buffer-tag").innerHTML = currentBuffer + " secs";

document.getElementById("catchup-threshold-tag").innerHTML = settings.streaming.liveCatchupLatencyThreshold + " secs";
}, 200);

player.on(dashjs.MediaPlayer.events.QUALITY_CHANGE_REQUESTED, (e) => {
console.warn('Quality changed requested', e);
});

player.on(dashjs.MediaPlayer.events.QUALITY_CHANGE_RENDERED, (e) => {
console.warn('Quality changed', e);
const quality = player.getBitrateInfoListFor('video')[e.newQuality];
if (!quality) {
return;
}
document.querySelector('#quality-tag').innerText = `${quality.width}x${quality.height}, ${quality.bitrate / 1000}Kbps`;
});

window.startRecording = () => {
console.info('Begin recording');

const latencyCMA = CMA();
const bufferCMA = CMA();
const history = window.abrHistory = {
switchHistory: [],
stallDuration: 0,
averageLatency: 0,
averageBufferLength: 0,
};

// Record the initial quality
recordSwitch(player.getBitrateInfoListFor('video')[player.getQualityFor('video')]);

let pollInterval = -1;
window.stopRecording = () => {
clearInterval(pollInterval);
checkStallResolution();
const lastQuality = history.switchHistory[history.switchHistory.length - 1];
if (lastQuality.end === null) {
lastQuality.end = video.currentTime;
}
console.warn('Run ended. Please navigate back to node for results.');
}

pollInterval = setInterval(function () {
const currentLatency = parseFloat(player.getCurrentLiveLatency(), 10);
const currentBuffer = player.getDashMetrics().getCurrentBufferLevel("video");
history.averageLatency = latencyCMA.average(currentLatency);
history.averageBufferLength = bufferCMA.average(currentBuffer);
console.log(history);
}, 200);


player.on(dashjs.MediaPlayer.events.QUALITY_CHANGE_RENDERED, (e) => {
recordSwitch(player.getBitrateInfoListFor('video')[e.newQuality]);
});

video.addEventListener('waiting', (e) => {
stallingAt = performance.now();
});

video.addEventListener('timeupdate', () => {
checkStallResolution();
});

function recordSwitch(quality) {
if (!quality) {
return;
}
const switchHistory = history.switchHistory;
const prev = switchHistory[switchHistory.length - 1];
const videoNow = video.currentTime;
if (prev) {
prev.end = videoNow;
}
switchHistory.push({start: videoNow, end: null, quality});
}

function checkStallResolution() {
if (stallingAt !== null) {
history.stallDuration += (performance.now() - stallingAt);
stallingAt = null;
}
}
}
});
</script>
<script src="../highlighter.js"></script>
</body>
</html>


5 changes: 5 additions & 0 deletions samples/samples.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@
"title": "Low latency",
"description": "Example showing how to use dash.js to play low latency streams.",
"href": "low-latency/index.html"
},
{
"title": "Low latency with L2ALL",
"description": "Example showing how to use dash.js to play low latency streams using the L2ALL ABR algorithm.",
"href": "low-latency/l2all_index.html"
}
]
},
Expand Down
2 changes: 0 additions & 2 deletions src/core/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';
* Standard ABR throughput rules multiply the throughput by this value. It should be between 0 and 1,
* with lower values giving less rebuffering (but also lower quality).
* @property {boolean} [useDefaultABRRules=true] Should the default ABR rules be used, or the custom ones added.
* @property {boolean} [useBufferOccupancyABR=false] Whether to use the BOLA abr rule.
* @property {boolean} [useDeadTimeLatency=true]
* If true, only the download portion will be considered part of the download bitrate
* and latency will be regarded as static. If false, the reciprocal of the whole
Expand Down Expand Up @@ -447,7 +446,6 @@ function Settings() {
ABRStrategy: Constants.ABR_STRATEGY_DYNAMIC,
bandwidthSafetyFactor: 0.9,
useDefaultABRRules: true,
useBufferOccupancyABR: false,
useDeadTimeLatency: true,
limitBitrateByPortal: false,
usePixelRatioInLimitBitrateByPortal: false,
Expand Down
2 changes: 1 addition & 1 deletion src/dash/utils/TemplateSegmentsGetter.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function TemplateSegmentsGetter(config, isDynamic) {

const seg = getIndexBasedSegment(timelineConverter, isDynamic, representation, index);
if (seg) {
seg.replacementTime = (index - 1) * representation.segmentDuration;
seg.replacementTime = Math.round((index - 1) * representation.segmentDuration * representation.timescale,10);

let url = template.media;
url = replaceTokenForTemplate(url, 'Number', seg.replacementNumber);
Expand Down
1 change: 0 additions & 1 deletion src/streaming/MediaPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2004,7 +2004,6 @@ function MediaPlayer() {
videoModel: videoModel,
settings: settings
});
abrController.createAbrRulesCollection();

textController.setConfig({
errHandler: errHandler,
Expand Down
11 changes: 9 additions & 2 deletions src/streaming/constants/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
*/
class Constants {

init () {
init() {
/**
* @constant {string} STREAM Stream media type. Mainly used to report metrics relative to the full stream
* @memberof Constants#
Expand Down Expand Up @@ -136,6 +136,13 @@ class Constants {
*/
this.ABR_STRATEGY_BOLA = 'abrBola';

/**
* @constant {string} ABR_STRATEGY_L2A Adaptive bitrate algorithm based on L2A (online learning)
* @memberof Constants#
* @static
*/
this.ABR_STRATEGY_L2A = 'abrL2A';

/**
* @constant {string} ABR_STRATEGY_THROUGHPUT Adaptive bitrate algorithm based on throughput
* @memberof Constants#
Expand Down Expand Up @@ -216,7 +223,7 @@ class Constants {
this.DVB_PROBABILITY = 'dvb:probability';
}

constructor () {
constructor() {
this.init();
}
}
Expand Down
Loading

0 comments on commit 23e559f

Please sign in to comment.