Skip to content

Commit

Permalink
feat: request Content Steering manifest
Browse files Browse the repository at this point in the history
  • Loading branch information
adrums86 committed Aug 16, 2023
1 parent 6cdf147 commit ebaf833
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 1 deletion.
6 changes: 6 additions & 0 deletions src/dash-playlist-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ export default class DashPlaylistLoader extends EventTarget {
this.vhs_ = vhs;
this.withCredentials = withCredentials;
this.addMetadataToTextTrack = options.addMetadataToTextTrack;
this.contentSteering = options.contentSteering;

if (!srcUrlOrPlaylist) {
throw new Error('A non-empty playlist URL or object is required');
Expand Down Expand Up @@ -465,6 +466,7 @@ export default class DashPlaylistLoader extends EventTarget {
this.mediaUpdateTimeout = null;
this.mediaRequest_ = null;
this.minimumUpdatePeriodTimeout_ = null;
this.contentSteering.dispose();

if (this.mainPlaylistLoader_.createMupOnMedia_) {
this.off('loadedmetadata', this.mainPlaylistLoader_.createMupOnMedia_);
Expand Down Expand Up @@ -776,6 +778,10 @@ export default class DashPlaylistLoader extends EventTarget {

this.addEventStreamToMetadataTrack_(newMain);

if (this.main.contentSteering) {
this.contentSteering.handleContentSteeringTags(this.vhs_.xhr, this.main.uri, this.main.contentSteering);
}

return Boolean(newMain);
}

Expand Down
5 changes: 4 additions & 1 deletion src/playlist-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { createMediaTypes, setupMediaGroups } from './media-groups';
import logger from './util/logger';
import {merge, createTimeRanges} from './util/vjs-compat';
import { addMetadata, createMetadataTrackIfNotExists, addDateRangeMetadata } from './util/text-tracks';
import ContentSteering from './util/content-steering';

const ABORT_EARLY_EXCLUSION_SECONDS = 10;

Expand Down Expand Up @@ -200,7 +201,9 @@ export class PlaylistController extends videojs.EventTarget {
this.requestOptions_ = {
withCredentials,
maxPlaylistRetries,
timeout: null
timeout: null,
// pass content steering parsing down to both playlist loaders.
contentSteering: new ContentSteering()
};

this.on('error', this.pauseLoading);
Expand Down
6 changes: 6 additions & 0 deletions src/playlist-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ export default class PlaylistLoader extends EventTarget {
this.vhs_ = vhs;
this.withCredentials = withCredentials;
this.addDateRangesToTextTrack_ = options.addDateRangesToTextTrack;
this.contentSteering = options.contentSteering;

const vhsOptions = vhs.options_;

Expand Down Expand Up @@ -545,6 +546,10 @@ export default class PlaylistLoader extends EventTarget {

this.updateMediaUpdateTimeout_(refreshDelay(this.media(), !!update));

if (this.main.contentSteering) {
this.contentSteering.handleContentSteeringTags(this.vhs_.xhr, this.main.uri, this.main.contentSteering);
}

this.trigger('loadedplaylist');
}

Expand All @@ -557,6 +562,7 @@ export default class PlaylistLoader extends EventTarget {
window.clearTimeout(this.mediaUpdateTimeout);
window.clearTimeout(this.finalRenditionTimeout);
this.dateRangesStorage_ = new DateRangesStorage();
this.contentSteering.dispose();

this.off();
}
Expand Down
122 changes: 122 additions & 0 deletions src/util/content-steering.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import resolveUrl from '../resolve-url';
import window from 'global/window';

// Content Steering manifest format
//
// VERSION: number (required)
// TTL: number in seconds (optional), default is 300 seconds.
// RELOAD-URI: string (optional)
// SERVICE-LOCATION-PRIORITY or PATHWAY-PRIORITY array of strings (handle quoted and not quoted) (optional) default = false in DASH
// TODO: add spec references.

/**
* This class represents a content steering manifest and associated state.
*/
export default class ContentSteering {
constructor() {
this.version = null;
this.ttl = null;
this.reloadUri = null;
this.cdnPriority = null;
this.ttlTimeout = null;
this.currentCdn = null;
this.request = null;
}

/**
* This function will extract the content steering data from both DASH and HLS manifest objects.
*
* @param {Function} xhr the tech xhr function
* @param {string} manifestUri the uri of the main manifest for path resolution
* @param {Object} steeringTag the content steering tag from the manifest
*/
handleContentSteeringTags(xhr, manifestUri, steeringTag) {
if (!xhr || !steeringTag) {
return;
}
// serverUri is HLS serverURL is DASH
const steeringUri = steeringTag.serverUri || steeringTag.serverURL;

// pathwayId is HLS defaultServiceLocation is DASH
this.currentCdn = steeringTag.pathwayId || steeringTag.defaultServiceLocation;
// resolve the URI to an absolute URI.
const uri = resolveUrl(manifestUri, steeringUri);

this.requestContentSteeringManifest_(uri, xhr);
}

/**
* Requests the steering manifest and parse response.
*
* @param {string} uri the uri to request the steering manifest from
* @param {Function} xhr the tech xhr function
*/
requestContentSteeringManifest_(uri, xhr) {
// TODO: Handle steering query parameters.
this.request = xhr({
uri
}, (error) => {
if (error) {
// TODO: Add error handling.
}
const steeringManifestJson = JSON.parse(this.request.responseText);

this.assignSteeringProperties_(steeringManifestJson, uri);
this.startTTLTimeout_(uri, xhr);
});
}

/**
* Assigns the current steering manifest properties and to the ContentSteering class.
*
* @param {Object} steeringManifest the raw JSON steering manifest
* @param {string} baseUri the baseUri for url path resolution
*/
assignSteeringProperties_(steeringManifest, baseUri) {
this.version = steeringManifest.VERSION;
// time-to-live default = 300 seconds
this.ttl = steeringManifest.TTL || 300;
// RELOAD-URI is optional and can be relative, if absent use current manifest uri.
this.reloadUri = steeringManifest['RELOAD-URI'] ? resolveUrl(baseUri, steeringManifest['RELOAD-URI']) : baseUri;
// HLS = PATHWAY-PRIORITY, DASH = SERVICE-LOCATION-PRIORITY default = false
this.cdnPriority = steeringManifest['PATHWAY-PRIORITY'] || steeringManifest['SERVICE-LOCATION-PRIORITY'] || false;
}

/**
* Start the timeout for re-requesting the steering manifest at the TTL interval.
*
* @param {string} uri the uri to request the steering manifest from after the ttl interval
* @param {Function} xhr the tech xhr function
*/
startTTLTimeout_(uri, xhr) {
const ttlMS = this.ttl * 1000;

this.ttlTimeout = window.setTimeout(() => {
this.requestContentSteeringManifest_(uri, xhr);
}, ttlMS);
}

/**
* Clear the TTL timeout if necessary.
*/
clearTTLTimeout_() {
window.clearTimeout(this.ttlTimeout);
this.ttlTimeout = null;
}

/**
* aborts any current steering xhr and sets the current request object to null
*/
abort() {
this.request.abort();
this.request = null;
}

/**
* aborts and clears the timeout on any steering manifest requests.
*/
dispose() {
this.abort();
this.clearTTLTimeout_();
}
}

0 comments on commit ebaf833

Please sign in to comment.