Skip to content

Commit

Permalink
Feature/content steering (Dash-Industry-Forum#4031)
Browse files Browse the repository at this point in the history
* Move ServiceDescriptionController.js to dash controller folder

* WiP: Parse content steering information

* Add initial request to content steering server

* Load steering data according to ttl and queryBeforeStart

* Fix unit tests

* Fix linting errors

* First working steering implementation

* Fix JSDoc

* Move content steering selection to dedicated class ContentSteeringSelector.js

* Fix linting error

* Add settings flag to enable/disable content steering

* Add support for RELOAD-URI specified absolute and relative

* Add API endpoint to trigger steering request

* Content steering demo page for local steering server

* Minor changes to the steering demo page

* Add support for proxy server url

* Add unit tests for Content Steering in DASHManifestModel

* Add flag to enable/disable content steering to reference UI

* Revert change to getLocation

* Revert change to getPatchLocation

* Remove unnecessary parameter in call to contentSteeringController.loadSteeringData()

* Formatting changes
  • Loading branch information
dsilhavy authored Aug 29, 2022
1 parent 87c36b4 commit c200845
Show file tree
Hide file tree
Showing 33 changed files with 1,054 additions and 45 deletions.
6 changes: 6 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ declare namespace dashjs {
wallclockTimeUpdateInterval?: number,
manifestUpdateRetryInterval?: number,
applyServiceDescription?: boolean,
applyProducerReferenceTime?: boolean,
applyContentSteering?: boolean,
cacheInitSegments?: boolean,
eventControllerRefreshDelay?: number,
enableManifestDurationMismatchFix?: boolean,
Expand Down Expand Up @@ -606,6 +608,10 @@ declare namespace dashjs {

getOfflineController(): OfflineController;

triggerSteeringRequest(): Promise<any>;

getCurrentSteeringResponseData(): object;

getSettings(): MediaPlayerSettingClass;

updateSettings(settings: MediaPlayerSettingClass): void;
Expand Down
233 changes: 233 additions & 0 deletions samples/advanced/content-steering.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Content Steering</title>

<script src="../../dist/dash.all.debug.js"></script>

<!-- Bootstrap core CSS -->
<link href="../lib/bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="../lib/main.css" rel="stylesheet">

<style>
video {
width: 640px;
height: 360px;
}

#start, #receive {
height: 500px;
margin-top: 20px;
font-size: 10px;
}

.border {
border: 2px solid #dee2e6 !important;
}
</style>

<script class="code">
let player;
let mpd = "";

function init() {
player = dashjs.MediaPlayer().create();
document.getElementById('load-button').addEventListener('click', function () {
_load();
})
player.initialize(document.querySelector("video"), null, true);

player.on(dashjs.MediaPlayer.events.FRAGMENT_LOADING_STARTED, _onFragmentLoadingStarted, null);
player.on(dashjs.MediaPlayer.events.CONTENT_STEERING_REQUEST_COMPLETED, _onContentSteeringRequestCompleted, null);
}

function _load() {
mpd = document.getElementById('manifest').value;
player.attachSource(mpd);
}

function _onFragmentLoadingStarted(e) {
try {
if (e && e.mediaType && (e.mediaType === 'video' || e.mediaType === 'audio') && e.request) {
if (e.request.serviceLocation) {
const element = document.getElementById(`${e.mediaType}-service-location`);
element.innerText = e.request.serviceLocation;

if (e.request.serviceLocation === 'alpha') {
document.getElementById('img-alpha-cdn').classList.remove('d-none');
document.getElementById('img-beta-cdn').classList.add('d-none');
} else {
document.getElementById('img-alpha-cdn').classList.add('d-none');
document.getElementById('img-beta-cdn').classList.remove('d-none');
}
}
if (e.request.url) {
const element = document.getElementById(`${e.mediaType}-request-url`);
element.innerText = e.request.url;
}
}
} catch (e) {
console.error(e);
}
}

function _onContentSteeringRequestCompleted(e) {
try {
if (e) {
document.getElementById(`steering-request-timestamp`).innerText = new Date().toISOString();
if (e.url) {
const element = document.getElementById(`steering-request-url`);
element.innerText = e.url;
}
if (e.currentSteeringResponseData) {
document.getElementById(`steering-version`).innerText = e.currentSteeringResponseData.version;
document.getElementById(`steering-ttl`).innerText = e.currentSteeringResponseData.ttl;
document.getElementById(`steering-reload-uri`).innerText = e.currentSteeringResponseData.reloadUri;
document.getElementById(`steering-service-location-priority`).innerText = e.currentSteeringResponseData.serviceLocationPriority.toString();
}
}
} catch (e) {
console.error(e);
}
}


</script>
</head>
<body>

<main>
<div class="container py-4">
<header class="pb-3 mb-4 border-bottom">
<img class=""
src="../lib/img/dashjs-logo.png"
width="200">
</header>
<div class="row">
<h1>Content Steering</h1>
<div class="col-md-5">
<div class="h-100 p-5 border rounded-3">
<h4>Description</h4>
<p>Content distributors often use multiple Content Delivery Networks (CDNs) to
distribute their content to the end-users. They may upload a copy of their catalogue
to each CDN, or more commonly have all CDNs pull the content from a common
origin. Alternate URLs are generated, one for each CDN, that point at identical
content. DASH players may access alternate URLs in the event of delivery
problems. </p>
<p><b>Content steering</b> describes a deterministic capability for a content
distributor to switch the content source that a player uses either at start-up or
midstream, by means of a remote steering service. The DASH implementation of
Content Steering also supports the notion of a proxy steering server which can
switch a mobile client between broadcast and unicast sources. </p>
</div>
</div>
<div class="col-md-7">
<div class="h-100 p-5 border rounded-3">
<h4>Architecture</h4>
<img src="img/steering.png" class="img-fluid" alt="Steering architecture illustration">
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<div class="input-group mb-3">
<input type="text" id="manifest" class="form-control" placeholder="MPD URL"
value="http://localhost:3333/steering-content/bbb/dash.mpd">
<button type="button" id="load-button" class="btn btn-success">Load MPD
</button>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-md-6">
<video controls="true" muted></video>
</div>
<div class="col-md-6">
<div class="h-100 p-5 border rounded-3">
<h4>CDN Selection</h4>
<img src="img/content-steering-simulation-alpha-cdn.drawio.png" id="img-alpha-cdn"
class="img-fluid d-none" alt="Steering alpha CDN">
<img src="img/content-steering-simulation-beta-cdn.drawio.png" id="img-beta-cdn"
class="img-fluid d-none" alt="Steering alpha beta">
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-md-12">
<div class="h-100 p-5 border rounded-3">
<h4> Fragment Requests</h4>
<table class="table">
<thead>
<tr>
<th scope="col">Type</th>
<th scope="col">Service Location</th>
<th scope="col">Request URL</th>
</tr>
</thead>
<tbody>
<tr>
<td>Audio</td>
<td id="audio-service-location"></td>
<td id="audio-request-url"></td>
</tr>
<tr>
<td>Video</td>
<td id="video-service-location"></td>
<td id="video-request-url"></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col-md-12 mt-2">
<div class="h-100 p-5 border rounded-3">
<h4> Steering Data</h4>
<table class="table table-condensed">
<thead>
<tr>
<th scope="col">Timestamp</th>
<th scope="col">Request URL</th>
<th scope="col">Response</th>
</tr>
</thead>
<tbody>
<tr>
<tr>
<td id="steering-request-timestamp"></td>
<td id="steering-request-url"></td>
<td id="steering-response">
<ul>
<li>Version: <span id="steering-version"></span></li>
<li>Reload URI: <span id="steering-reload-uri"></span></li>
<li>Service Location Priority: <span id="steering-service-location-priority"></span>
</li>
<li>TTL: <span id="steering-ttl"></span></li>
</ul>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div id="code-output"></div>
</div>
</div>
<footer class="pt-3 mt-4 text-muted border-top">
&copy; DASH-IF
</footer>
</div>
</main>


<script>
document.addEventListener('DOMContentLoaded', function () {
init();
});
</script>
<script src="../highlighter.js"></script>
</body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/advanced/img/steering.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions samples/advanced/listening-to-SCTE-EMSG-events.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

<script class="code">
var player;
const URL = "https://livesim.dashif.org/livesim/scte35_2/testpic_2s/Manifest.mpd";
const mpd = "https://livesim.dashif.org/livesim/scte35_2/testpic_2s/Manifest.mpd";
const SCHEMEIDURI = "urn:scte:scte35:2013:xml";
const EVENT_MODE_ON_START = dashjs.MediaPlayer.events.EVENT_MODE_ON_START;
const EVENT_MODE_ON_RECEIVE = dashjs.MediaPlayer.events.EVENT_MODE_ON_RECEIVE;
Expand All @@ -35,7 +35,7 @@
player.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_DEBUG }});
player.on(SCHEMEIDURI, showStartEvent, null); /* Default mode is onStart, no need to specify a mode */
player.on(SCHEMEIDURI, showReceiveEvent, null, { mode: EVENT_MODE_ON_RECEIVE });
player.initialize(document.querySelector("video"), URL, true);
player.initialize(document.querySelector("video"), mpd, true);
}

function showStartEvent(e) {
Expand Down
10 changes: 10 additions & 0 deletions samples/dash-if-reference-player/app/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ app.controller('DashController', ['$scope', '$window', 'sources', 'contributors'
$scope.jumpGapsSelected = true;
$scope.fastSwitchSelected = true;
$scope.applyServiceDescription = true;
$scope.applyContentSteering = true;
$scope.useSuggestedPresentationDelay = true;
$scope.videoAutoSwitchSelected = true;
$scope.forceQualitySwitchSelected = false;
Expand Down Expand Up @@ -570,6 +571,14 @@ app.controller('DashController', ['$scope', '$window', 'sources', 'contributors'
});
};

$scope.toggleApplyContentSteering = function () {
$scope.player.updateSettings({
streaming: {
applyContentSteering: $scope.applyContentSteering
}
});
};

$scope.toggleUseSuggestedPresentationDelay = function () {
$scope.player.updateSettings({
streaming: {
Expand Down Expand Up @@ -2037,6 +2046,7 @@ app.controller('DashController', ['$scope', '$window', 'sources', 'contributors'
function setAdditionalPlaybackOptions() {
var currentConfig = $scope.player.getSettings();
$scope.applyServiceDescription = currentConfig.streaming.applyServiceDescription;
$scope.applyContentSteering = currentConfig.streaming.applyContentSteering;
$scope.scheduleWhilePausedSelected = currentConfig.streaming.scheduling.scheduleWhilePaused;
$scope.calcSegmentAvailabilityRangeFromTimelineSelected = currentConfig.streaming.timeShiftBuffer.calcFromSegmentTimeline;
$scope.reuseExistingSourceBuffersSelected = currentConfig.streaming.buffer.reuseExistingSourceBuffers;
Expand Down
6 changes: 6 additions & 0 deletions samples/dash-if-reference-player/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,12 @@
ng-change="toggleLiveCatchupEnabled()" ng-checked="liveCatchupEnabled">
Live catchup
</label>
<label class="topcoat-checkbox" data-toggle="tooltip" data-placement="right"
title="Use the ContentSteering information from the MPD if enabled">
<input type="checkbox" id="applyContentSteering" ng-model="applyContentSteering"
ng-change="toggleApplyContentSteering()" ng-checked="applyContentSteering">
Apply ContentSteering
</label>
<div class="sub-options-item">
<div class="sub-options-item-title">Catchup mechanism</div>
<div class="sub-options-item-body">
Expand Down
4 changes: 4 additions & 0 deletions src/core/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import Events from './events/Events';
* cacheInitSegments: true,
* applyServiceDescription: true,
* applyProducerReferenceTime: true,
* applyContentSteering: true,
* eventControllerRefreshDelay: 100,
* enableManifestDurationMismatchFix: true,
* capabilities: {
Expand Down Expand Up @@ -655,6 +656,8 @@ import Events from './events/Events';
* Set to true if dash.js should use the parameters defined in ServiceDescription elements
* @property {boolean} [applyProducerReferenceTime=true]
* Set to true if dash.js should use the parameters defined in ProducerReferenceTime elements in combination with ServiceDescription elements.
* @property {boolean} [applyContentSteering=true]
* Set to true if dash.js should apply content steering during playback.
* @property {number} [eventControllerRefreshDelay=100]
* For multi-period streams, overwrite the manifest mediaPresentationDuration attribute with the sum of period durations if the manifest mediaPresentationDuration is greater than the sum of period durations
* @property {boolean} [enableManifestDurationMismatchFix=true]
Expand Down Expand Up @@ -760,6 +763,7 @@ function Settings() {
cacheInitSegments: false,
applyServiceDescription: true,
applyProducerReferenceTime: true,
applyContentSteering: true,
eventControllerRefreshDelay: 100,
enableManifestDurationMismatchFix: true,
capabilities: {
Expand Down
12 changes: 12 additions & 0 deletions src/dash/DashAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,17 @@ function DashAdapter() {
return dashManifestModel.getMpd(manifest);
}

/**
* Returns the ContentSteering element of the MPD
* @param {object} manifest
* @returns {object} contentSteering
* @memberOf module:DashAdapter
* @instance
*/
function getContentSteering(manifest) {
return dashManifestModel.getContentSteering(manifest);
}

/**
* Returns the location element of the MPD
* @param {object} manifest
Expand Down Expand Up @@ -1211,6 +1222,7 @@ function DashAdapter() {
getIsDynamic,
getDuration,
getRegularPeriods,
getContentSteering,
getLocation,
getPatchLocation,
getManifestUpdatePeriod,
Expand Down
11 changes: 11 additions & 0 deletions src/dash/constants/DashConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,17 @@ class DashConstants {
this.ORIGINAL_MPD_ID = 'mpdId';
this.WALL_CLOCK_TIME = 'wallClockTime';
this.PRESENTATION_TIME = 'presentationTime';
this.CONTENT_STEERING = 'ContentSteering';
this.CONTENT_STEERING_AS_ARRAY = 'ContentSteering_asArray';
this.DEFAULT_SERVICE_LOCATION = 'defaultServiceLocation';
this.QUERY_BEFORE_START = 'queryBeforeStart';
this.PROXY_SERVER_URL = 'proxyServerURL';
this.CONTENT_STEERING_RESPONSE = {
VERSION: 'VERSION',
TTL: 'TTL',
RELOAD_URI: 'RELOAD-URI',
SERVICE_LOCATION_PRIORITY : 'SERVICE-LOCATION-PRIORITY'
}
}

constructor () {
Expand Down
Loading

0 comments on commit c200845

Please sign in to comment.