Skip to content

Commit

Permalink
feat(DASH): Add support for Content Steering (#5710)
Browse files Browse the repository at this point in the history
Related to #5704
  • Loading branch information
avelad authored Nov 9, 2023
1 parent da38b36 commit 42f491f
Show file tree
Hide file tree
Showing 14 changed files with 1,173 additions and 9 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,18 @@ Shaka Player supports:
- AWS MediaTailor overlays


## Content Steering support
Shaka Player supports Content Steering (v1) in DASH.

Content Steering features supported:
- TTL, if missing, the default value is 300 seconds.
- RELOAD-URI, if missing we use the url provided in the manifest as fallback.
- PATHWAY-PRIORITY only HOST replacement

Content Steering features **not** supported:
- PATHWAY-CLONES other replacements than HOST.


## Documentation & Important Links ##

* [Demo](https://shaka-player-demo.appspot.com)([sources](demo/))
Expand Down
1 change: 1 addition & 0 deletions build/types/core
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
+../../lib/util/buffer_utils.js
+../../lib/util/cmcd_manager.js
+../../lib/util/config_utils.js
+../../lib/util/content_steering_manager.js
+../../lib/util/data_view_reader.js
+../../lib/util/delayed_tick.js
+../../lib/util/destroyer.js
Expand Down
11 changes: 10 additions & 1 deletion externs/shaka/manifest_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,21 @@ shaka.extern.ManifestParser = class {
onExpirationUpdated(sessionId, expiration) {}

/**
* Tell the parser that the initial variant has been chosen.
* Tells the parser that the initial variant has been chosen.
*
* @param {shaka.extern.Variant} variant
* @exportDoc
*/
onInitialVariantChosen(variant) {}

/**
* Tells the parser that a location should be banned. This is called on
* retry.
*
* @param {string} uri
* @exportDoc
*/
banLocation(uri) {}
};


Expand Down
87 changes: 81 additions & 6 deletions lib/dash/dash_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ goog.require('shaka.media.PresentationTimeline');
goog.require('shaka.media.SegmentIndex');
goog.require('shaka.net.NetworkingEngine');
goog.require('shaka.text.TextEngine');
goog.require('shaka.util.ContentSteeringManager');
goog.require('shaka.util.Error');
goog.require('shaka.util.Functional');
goog.require('shaka.util.LanguageUtils');
Expand Down Expand Up @@ -115,6 +116,9 @@ shaka.dash.DashParser = class {

/** @private {boolean} */
this.lowLatencyMode_ = false;

/** @private {?shaka.util.ContentSteeringManager} */
this.contentSteeringManager_ = null;
}

/**
Expand All @@ -126,6 +130,10 @@ shaka.dash.DashParser = class {
'DashManifestConfiguration should not be null!');

this.config_ = config;

if (this.contentSteeringManager_) {
this.contentSteeringManager_.configure(this.config_);
}
}

/**
Expand Down Expand Up @@ -185,6 +193,10 @@ shaka.dash.DashParser = class {
this.updateTimer_ = null;
}

if (this.contentSteeringManager_) {
this.contentSteeringManager_.destroy();
}

return this.operationManager_.destroy();
}

Expand Down Expand Up @@ -239,6 +251,16 @@ shaka.dash.DashParser = class {
}
}

/**
* @override
* @exportInterface
*/
banLocation(uri) {
if (this.contentSteeringManager_) {
this.contentSteeringManager_.banLocation(uri);
}
}

/**
* Makes a network request for the manifest and parses the resulting data.
*
Expand Down Expand Up @@ -347,16 +369,67 @@ shaka.dash.DashParser = class {
manifestBaseUris = absoluteLocations;
}

const uriObjs = XmlUtils.findChildren(mpd, 'BaseURL');
const uris = uriObjs.map(XmlUtils.getContents);
let contentSteeringPromise = Promise.resolve();

const contentSteering = XmlUtils.findChild(mpd, 'ContentSteering');
if (contentSteering && this.playerInterface_) {
const hasPrevContentSteeringManager = !!this.contentSteeringManager_;
if (!this.contentSteeringManager_) {
this.contentSteeringManager_ =
new shaka.util.ContentSteeringManager(this.playerInterface_);
}
this.contentSteeringManager_.configure(this.config_);
this.contentSteeringManager_.setBaseUris(manifestBaseUris);
this.contentSteeringManager_.setManifestType(
shaka.media.ManifestParser.DASH);
const defaultPathwayId =
contentSteering.getAttribute('defaultServiceLocation');
this.contentSteeringManager_.setDefaultPathwayId(defaultPathwayId);
if (!hasPrevContentSteeringManager) {
const uri = XmlUtils.getContents(contentSteering);
if (uri) {
const queryBeforeStart =
XmlUtils.parseAttr(contentSteering, 'queryBeforeStart',
XmlUtils.parseBoolean, /* defaultValue= */ false);
if (queryBeforeStart) {
contentSteeringPromise =
this.contentSteeringManager_.requestInfo(uri);
} else {
this.contentSteeringManager_.requestInfo(uri);
}
}
}
}

const uriObjs = XmlUtils.findChildren(mpd, 'BaseURL');
let calculatedBaseUris;
let someLocationValid = false;
if (this.contentSteeringManager_) {
this.contentSteeringManager_.clearPreviousLocations();
for (const uriObj of uriObjs) {
const serviceLocation = uriObj.getAttribute('serviceLocation');
const uri = XmlUtils.getContents(uriObj);
if (serviceLocation && uri) {
this.contentSteeringManager_.addLocation(
'BaseURL', serviceLocation, uri);
someLocationValid = true;
}
}
}
if (!someLocationValid || !this.contentSteeringManager_) {
const uris = uriObjs.map(XmlUtils.getContents);
calculatedBaseUris = shaka.util.ManifestParserUtils.resolveUris(
manifestBaseUris, uris);
}

const getBaseUris = () => {
if (!calculatedBaseUris) {
calculatedBaseUris = shaka.util.ManifestParserUtils.resolveUris(
manifestBaseUris, uris);
if (this.contentSteeringManager_) {
return this.contentSteeringManager_.getLocations('BaseURL');
}
return calculatedBaseUris || [];
if (calculatedBaseUris) {
return calculatedBaseUris;
}
return [];
};

let availabilityTimeOffset = 0;
Expand Down Expand Up @@ -517,6 +590,8 @@ shaka.dash.DashParser = class {

await this.periodCombiner_.combinePeriods(periods, context.dynamic);

await contentSteeringPromise;

// Set minBufferTime to 0 for low-latency DASH live stream to achieve the
// best latency
if (this.lowLatencyMode_) {
Expand Down
8 changes: 8 additions & 0 deletions lib/hls/hls_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,14 @@ shaka.hls.HlsParser = class {
// No-op
}

/**
* @override
* @exportInterface
*/
banLocation(uri) {
// No-op
}

/**
* Align the streams by sequence number by dropping early segments. Then
* offset the streams to begin at presentation time 0.
Expand Down
8 changes: 8 additions & 0 deletions lib/mss/mss_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,14 @@ shaka.mss.MssParser = class {
// No-op
}

/**
* @override
* @exportInterface
*/
banLocation(uri) {
// No-op
}

/**
* Makes a network request for the manifest and parses the resulting data.
*
Expand Down
29 changes: 28 additions & 1 deletion lib/net/networking_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
* Called when a download fails, for any reason.
* @param {shaka.net.NetworkingEngine.OnRequest=} onRequest
* Called when a request is made
* @param {shaka.net.NetworkingEngine.OnRetry=} onRetry
* Called when a request retry is made
*/
constructor(onProgressUpdated, onHeadersReceived, onDownloadFailed,
onRequest) {
onRequest, onRetry) {
super();

/** @private {boolean} */
Expand All @@ -85,6 +87,9 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
/** @private {?shaka.net.NetworkingEngine.OnRequest} */
this.onRequest_ = onRequest || null;

/** @private {?shaka.net.NetworkingEngine.OnRetry} */
this.onRetry_ = onRetry || null;

/** @private {boolean} */
this.forceHTTPS_ = false;
}
Expand Down Expand Up @@ -440,6 +445,12 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
request.uris[index] = request.uris[index].replace('http://', 'https://');
}

if (index > 0 && this.onRetry_) {
const newUri = request.uris[index];
const oldUri = request.uris[index - 1];
this.onRetry_(type, context, newUri, oldUri);
}

const uri = new goog.Uri(request.uris[index]);
let scheme = uri.getScheme();
// Whether it got a progress event.
Expand Down Expand Up @@ -797,6 +808,7 @@ shaka.net.NetworkingEngine.RequestType = {
'SERVER_CERTIFICATE': 5,
'KEY': 6,
'ADS': 7,
'CONTENT_STEERING': 8,
};

/**
Expand Down Expand Up @@ -914,3 +926,18 @@ shaka.net.NetworkingEngine.OnDownloadFailed;
* @export
*/
shaka.net.NetworkingEngine.OnRequest;


/**
* @typedef {function(
* !shaka.net.NetworkingEngine.RequestType,
* (shaka.extern.RequestContext|undefined),
* string,
* string)}
*
* @description
* A callback function called on every request retry. The first string is the
* new URI and the second string is the old URI.
* @export
*/
shaka.net.NetworkingEngine.OnRetry;
5 changes: 5 additions & 0 deletions lib/offline/offline_manifest_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ shaka.offline.OfflineManifestParser = class {
onInitialVariantChosen(variant) {
// No-op
}

/** @override */
banLocation(uri) {
// No-op
}
};


Expand Down
10 changes: 9 additions & 1 deletion lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -2624,8 +2624,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
this.cmcdManager_.applyData(type, request, context);
};


const onRetry_ = (type, context, newUrl, oldUrl) => {
if (this.parser_ && this.parser_.banLocation) {
this.parser_.banLocation(oldUrl);
}
};

return new shaka.net.NetworkingEngine(
onProgressUpdated_, onHeadersReceived_, onDownloadFailed_, onRequest_);
onProgressUpdated_, onHeadersReceived_, onDownloadFailed_, onRequest_,
onRetry_);
}

/**
Expand Down
Loading

0 comments on commit 42f491f

Please sign in to comment.