diff --git a/lib/dash/container_segment_index_source.js b/lib/dash/container_segment_index_source.js index 93de3d2025..2f17c6cd8b 100644 --- a/lib/dash/container_segment_index_source.js +++ b/lib/dash/container_segment_index_source.js @@ -46,13 +46,14 @@ goog.require('shaka.util.TypedBind'); * for MP4 containers. * @param {number} manifestCreationTime The time, in seconds, when the manifest * was created. + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @constructor * @struct * @implements {shaka.media.ISegmentIndexSource} */ shaka.dash.ContainerSegmentIndexSource = function( mpd, period, containerType, indexMetadata, initMetadata, - manifestCreationTime) { + manifestCreationTime, networkCallback) { shaka.asserts.assert(containerType != 'webm' || initMetadata); /** @private {!shaka.dash.mpd.Mpd} */ @@ -78,6 +79,9 @@ shaka.dash.ContainerSegmentIndexSource = function( /** @private {shaka.media.SegmentIndex} */ this.segmentIndex_ = null; + + /** @private {shaka.util.FailoverUri.NetworkCallback} */ + this.networkCallback_ = networkCallback; }; @@ -88,6 +92,7 @@ shaka.dash.ContainerSegmentIndexSource = function( shaka.dash.ContainerSegmentIndexSource.prototype.destroy = function() { this.mpd_ = null; this.period_ = null; + this.networkCallback_ = null; this.indexMetadata_.abortFetch(); this.indexMetadata_ = null; @@ -128,13 +133,15 @@ shaka.dash.ContainerSegmentIndexSource.prototype.create = function() { var parser = new shaka.media.Mp4SegmentIndexParser(); references = parser.parse(new DataView(indexData), this.indexMetadata_.startByte, - this.indexMetadata_.urls); + this.indexMetadata_.urls, + this.networkCallback_); } else if (this.containerType_ == 'webm') { shaka.asserts.assert(initData); var parser = new shaka.media.WebmSegmentIndexParser(); references = parser.parse(new DataView(indexData), new DataView(initData), - this.indexMetadata_.urls); + this.indexMetadata_.urls, + this.networkCallback_); } else { shaka.asserts.unreachable(); } diff --git a/lib/dash/duration_segment_index_source.js b/lib/dash/duration_segment_index_source.js index 0d2dbe8d53..fef0a4dbe0 100644 --- a/lib/dash/duration_segment_index_source.js +++ b/lib/dash/duration_segment_index_source.js @@ -38,12 +38,13 @@ goog.require('shaka.util.TypedBind'); * @param {!shaka.dash.mpd.Representation} representation * @param {number} manifestCreationTime The time, in seconds, when the manifest * was created. + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @constructor * @struct * @implements {shaka.media.ISegmentIndexSource} */ shaka.dash.DurationSegmentIndexSource = function( - mpd, period, representation, manifestCreationTime) { + mpd, period, representation, manifestCreationTime, networkCallback) { shaka.asserts.assert(period.start != null); shaka.asserts.assert((mpd.type == 'dynamic') || (period.duration != null)); shaka.asserts.assert(representation.segmentTemplate); @@ -64,6 +65,9 @@ shaka.dash.DurationSegmentIndexSource = function( /** @private {shaka.media.SegmentIndex} */ this.segmentIndex_ = null; + + /** @private {shaka.util.FailoverUri.NetworkCallback} */ + this.networkCallback_ = networkCallback; }; @@ -75,6 +79,7 @@ shaka.dash.DurationSegmentIndexSource.prototype.destroy = function() { this.mpd_ = null; this.period_ = null; this.representation_ = null; + this.networkCallback_ = null; if (this.segmentIndex_) { this.segmentIndex_.destroy(); @@ -93,7 +98,7 @@ shaka.dash.DurationSegmentIndexSource.prototype.create = function() { try { this.segmentIndex_ = new shaka.dash.DynamicLiveSegmentIndex( this.mpd_, this.period_, this.representation_, - this.manifestCreationTime_); + this.manifestCreationTime_, this.networkCallback_); } catch (exception) { return Promise.reject(exception); } @@ -103,7 +108,7 @@ shaka.dash.DurationSegmentIndexSource.prototype.create = function() { segmentTemplate.segmentDuration / segmentTemplate.timescale; var numSegments = Math.ceil(this.period_.duration / scaledSegmentDuration); var references = shaka.dash.MpdUtils.generateSegmentReferences( - this.representation_, 1, numSegments); + this.networkCallback_, this.representation_, 1, numSegments); if (!references) { var error = new Error('Failed to generate SegmentReferences'); error.type = 'stream'; diff --git a/lib/dash/dynamic_live_segment_index.js b/lib/dash/dynamic_live_segment_index.js index 163dd36300..2c9a6049c0 100644 --- a/lib/dash/dynamic_live_segment_index.js +++ b/lib/dash/dynamic_live_segment_index.js @@ -43,6 +43,7 @@ goog.require('shaka.util.ArrayUtils'); * @param {!shaka.dash.mpd.Representation} representation * @param {number} manifestCreationTime The time, in seconds, when the manifest * was created. + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @throws {Error} If the SegmentIndex's corresponding stream is available but * the initial SegmentReferences could not be generated. * @constructor @@ -50,7 +51,7 @@ goog.require('shaka.util.ArrayUtils'); * @extends {shaka.dash.LiveSegmentIndex} */ shaka.dash.DynamicLiveSegmentIndex = function( - mpd, period, representation, manifestCreationTime) { + mpd, period, representation, manifestCreationTime, networkCallback) { shaka.asserts.assert(mpd.availabilityStartTime != null); shaka.asserts.assert(period.start != null); shaka.asserts.assert(representation.segmentTemplate); @@ -70,7 +71,7 @@ shaka.dash.DynamicLiveSegmentIndex = function( } var references = shaka.dash.MpdUtils.generateSegmentReferences( - representation, earliestSegmentNumber, numSegments); + networkCallback, representation, earliestSegmentNumber, numSegments); if (references == null) { var error = new Error('Failed to generate SegmentReferences.'); error.type = 'stream'; @@ -114,6 +115,9 @@ shaka.dash.DynamicLiveSegmentIndex = function( * @private {?number} */ this.nextSegmentNumber_ = pair ? pair.current + 1 : null; + + /** @private {shaka.util.FailoverUri.NetworkCallback} */ + this.networkCallback_ = networkCallback; }; goog.inherits(shaka.dash.DynamicLiveSegmentIndex, shaka.dash.LiveSegmentIndex); @@ -295,6 +299,7 @@ shaka.dash.DynamicLiveSegmentIndex.computeAvailableSegmentRange_ = */ shaka.dash.DynamicLiveSegmentIndex.prototype.destroy = function() { this.representation_ = null; + this.networkCallback_ = null; shaka.dash.LiveSegmentIndex.prototype.destroy.call(this); }; @@ -410,7 +415,8 @@ shaka.dash.DynamicLiveSegmentIndex.prototype.generateSegmentReferences_ = // Generate and correct the new SegmentReferences. var newReferences = shaka.dash.MpdUtils.generateSegmentReferences( - this.representation_, this.nextSegmentNumber_, numNewSegments); + this.networkCallback_, this.representation_, + this.nextSegmentNumber_, numNewSegments); // |newReferences| should never be null since generateSegmentReferences() // should have been called at least once successfully with |representation_|. diff --git a/lib/dash/list_segment_index_source.js b/lib/dash/list_segment_index_source.js index 529eccbf08..826a8a1400 100644 --- a/lib/dash/list_segment_index_source.js +++ b/lib/dash/list_segment_index_source.js @@ -37,12 +37,13 @@ goog.require('shaka.util.TypedBind'); * @param {!shaka.dash.mpd.Representation} representation * @param {number} manifestCreationTime The time, in seconds, when the manifest * was created. + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @constructor * @struct * @implements {shaka.media.ISegmentIndexSource} */ shaka.dash.ListSegmentIndexSource = function( - mpd, period, representation, manifestCreationTime) { + mpd, period, representation, manifestCreationTime, networkCallback) { shaka.asserts.assert(representation.segmentList); // Alias @@ -67,6 +68,9 @@ shaka.dash.ListSegmentIndexSource = function( /** @private {number} */ this.manifestCreationTime_ = manifestCreationTime; + + /** @private {shaka.util.FailoverUri.NetworkCallback} */ + this.networkCallback_ = networkCallback; }; @@ -78,6 +82,7 @@ shaka.dash.ListSegmentIndexSource.prototype.destroy = function() { this.mpd_ = null; this.period_ = null; this.representation_ = null; + this.networkCallback_ = null; if (this.segmentIndex_) { this.segmentIndex_.destroy(); @@ -164,7 +169,8 @@ shaka.dash.ListSegmentIndexSource.prototype.create = function() { scaledStartTime, scaledEndTime, new shaka.util.FailoverUri( - segmentUrl.mediaUrl, startByte, endByte))); + this.networkCallback_, segmentUrl.mediaUrl, + startByte, endByte))); } this.segmentIndex_ = this.mpd_.type == 'dynamic' ? diff --git a/lib/dash/mpd_processor.js b/lib/dash/mpd_processor.js index ed5d3f0c1c..2bbfee697a 100644 --- a/lib/dash/mpd_processor.js +++ b/lib/dash/mpd_processor.js @@ -66,9 +66,10 @@ shaka.dash.MpdProcessor.DEFAULT_MIN_BUFFER_TIME = 5.0; * This function modifies |mpd| but does not take ownership of it. * * @param {!shaka.dash.mpd.Mpd} mpd + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @return {!shaka.media.ManifestInfo} */ -shaka.dash.MpdProcessor.prototype.process = function(mpd) { +shaka.dash.MpdProcessor.prototype.process = function(mpd, networkCallback) { var manifestCreationTime = shaka.util.Clock.now() / 1000.0; this.validateSegmentInfo_(mpd); @@ -83,7 +84,7 @@ shaka.dash.MpdProcessor.prototype.process = function(mpd) { mpd.availabilityStartTime = manifestCreationTime; } - return this.createManifestInfo_(mpd, manifestCreationTime); + return this.createManifestInfo_(mpd, manifestCreationTime, networkCallback); }; @@ -315,11 +316,12 @@ shaka.dash.MpdProcessor.prototype.filterAdaptationSet_ = function( * @param {!shaka.dash.mpd.Mpd} mpd * @param {number} manifestCreationTime The time, in seconds, when the manifest * was created. + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @return {!shaka.media.ManifestInfo} * @private */ shaka.dash.MpdProcessor.prototype.createManifestInfo_ = function( - mpd, manifestCreationTime) { + mpd, manifestCreationTime, networkCallback) { var manifestInfo = new shaka.media.ManifestInfo(); if (mpd.type == 'dynamic') { @@ -328,7 +330,7 @@ shaka.dash.MpdProcessor.prototype.createManifestInfo_ = function( // Prefer the URL specified by the Location element. manifestInfo.updateUrl = new shaka.util.FailoverUri( - mpd.updateLocation || mpd.url); + null, mpd.updateLocation || mpd.url); } manifestInfo.minBufferTime = mpd.minBufferTime || @@ -379,7 +381,7 @@ shaka.dash.MpdProcessor.prototype.createManifestInfo_ = function( } var streamInfo = this.createStreamInfo_( - mpd, period, representation, manifestCreationTime); + mpd, period, representation, manifestCreationTime, networkCallback); if (!streamInfo) { // An error has already been logged. continue; @@ -477,12 +479,13 @@ shaka.dash.MpdProcessor.prototype.getDrmSchemeInfos_ = * @param {!shaka.dash.mpd.Representation} representation * @param {number} manifestCreationTime The time, in seconds, when the manifest * was created. + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @return {shaka.media.StreamInfo} The new StreamInfo on success; otherwise, * return null. * @private */ shaka.dash.MpdProcessor.prototype.createStreamInfo_ = function( - mpd, period, representation, manifestCreationTime) { + mpd, period, representation, manifestCreationTime, networkCallback) { if (!representation.baseUrl || representation.baseUrl.length === 0) { shaka.log.warning( 'Representation does not contain sufficient segment information:', @@ -497,24 +500,24 @@ shaka.dash.MpdProcessor.prototype.createStreamInfo_ = function( if (representation.segmentBase) { streamInfo = this.createStreamInfoFromSegmentBase_( - mpd, period, representation, manifestCreationTime); + mpd, period, representation, manifestCreationTime, networkCallback); timescale = representation.segmentBase.timescale; presentationTimeOffset = representation.segmentBase.presentationTimeOffset; } else if (representation.segmentList) { streamInfo = this.createStreamInfoFromSegmentList_( - mpd, period, representation, manifestCreationTime); + mpd, period, representation, manifestCreationTime, networkCallback); timescale = representation.segmentList.timescale; presentationTimeOffset = representation.segmentList.presentationTimeOffset; } else if (representation.segmentTemplate) { streamInfo = this.createStreamInfoFromSegmentTemplate_( - mpd, period, representation, manifestCreationTime); + mpd, period, representation, manifestCreationTime, networkCallback); timescale = representation.segmentTemplate.timescale; presentationTimeOffset = representation.segmentTemplate.presentationTimeOffset; } else if (representation.mimeType.split('/')[0] == 'text') { streamInfo = new shaka.media.StreamInfo(); streamInfo.segmentIndexSource = new shaka.media.TextSegmentIndexSource( - new shaka.util.FailoverUri(representation.baseUrl)); + new shaka.util.FailoverUri(networkCallback, representation.baseUrl)); } else { shaka.asserts.unreachable(); } @@ -553,12 +556,13 @@ shaka.dash.MpdProcessor.prototype.createStreamInfo_ = function( * @param {!shaka.dash.mpd.Representation} representation * @param {number} manifestCreationTime The time, in seconds, when the manifest * was created. + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @return {shaka.media.StreamInfo} A streamInfo on success; otherwise, * return null. * @private */ shaka.dash.MpdProcessor.prototype.createStreamInfoFromSegmentBase_ = function( - mpd, period, representation, manifestCreationTime) { + mpd, period, representation, manifestCreationTime, networkCallback) { shaka.asserts.assert(representation.segmentBase); shaka.asserts.assert(representation.segmentBase.timescale > 0); @@ -605,10 +609,11 @@ shaka.dash.MpdProcessor.prototype.createStreamInfoFromSegmentBase_ = function( null; } - var indexMetadata = this.createSegmentMetadata_(representationIndex); + var indexMetadata = this.createSegmentMetadata_( + representationIndex, networkCallback); var initMetadata = segmentBase.initialization ? - this.createSegmentMetadata_(segmentBase.initialization) : + this.createSegmentMetadata_(segmentBase.initialization, networkCallback) : null; var segmentIndexSource = @@ -618,7 +623,8 @@ shaka.dash.MpdProcessor.prototype.createStreamInfoFromSegmentBase_ = function( containerType, indexMetadata, initMetadata, - manifestCreationTime); + manifestCreationTime, + networkCallback); var segmentInitSource = new shaka.media.SegmentInitSource(initMetadata); var streamInfo = new shaka.media.StreamInfo(); @@ -637,12 +643,14 @@ shaka.dash.MpdProcessor.prototype.createStreamInfoFromSegmentBase_ = function( * @param {!shaka.dash.mpd.Representation} representation * @param {number} manifestCreationTime The time, in seconds, when the manifest * was created. + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @return {shaka.media.StreamInfo} A StreamInfo on success; otherwise, * return null. * @private */ shaka.dash.MpdProcessor.prototype.createStreamInfoFromSegmentList_ = - function(mpd, period, representation, manifestCreationTime) { + function(mpd, period, representation, manifestCreationTime, + networkCallback) { shaka.asserts.assert(representation.segmentList); var segmentList = representation.segmentList; @@ -678,12 +686,12 @@ shaka.dash.MpdProcessor.prototype.createStreamInfoFromSegmentList_ = var initMetadata = segmentList.initialization ? - this.createSegmentMetadata_(segmentList.initialization) : + this.createSegmentMetadata_(segmentList.initialization, networkCallback) : null; var segmentIndexSource = new shaka.dash.ListSegmentIndexSource( - mpd, period, representation, manifestCreationTime); + mpd, period, representation, manifestCreationTime, networkCallback); var segmentInitSource = new shaka.media.SegmentInitSource(initMetadata); var streamInfo = new shaka.media.StreamInfo(); @@ -702,12 +710,14 @@ shaka.dash.MpdProcessor.prototype.createStreamInfoFromSegmentList_ = * @param {!shaka.dash.mpd.Representation} representation * @param {number} manifestCreationTime The time, in seconds, when the manifest * was created. + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @return {shaka.media.StreamInfo} A StreamInfo on success; otherwise, * return null. * @private */ shaka.dash.MpdProcessor.prototype.createStreamInfoFromSegmentTemplate_ = - function(mpd, period, representation, manifestCreationTime) { + function(mpd, period, representation, manifestCreationTime, + networkCallback) { shaka.asserts.assert(representation.segmentTemplate); var segmentTemplate = /** @type {!shaka.dash.mpd.SegmentTemplate} */ ( @@ -729,10 +739,12 @@ shaka.dash.MpdProcessor.prototype.createStreamInfoFromSegmentTemplate_ = } var initMetadata = - initialization ? this.createSegmentMetadata_(initialization) : null; + initialization ? this.createSegmentMetadata_( + initialization, networkCallback) : null; var segmentIndexSource = this.makeSegmentIndexSourceViaSegmentTemplate_( - mpd, period, representation, manifestCreationTime, initMetadata); + mpd, period, representation, manifestCreationTime, initMetadata, + networkCallback); if (!segmentIndexSource) { // An error has already been logged. return null; @@ -801,19 +813,22 @@ shaka.dash.MpdProcessor.prototype.validateSegmentTemplate_ = function( * @param {number} manifestCreationTime The time, in seconds, when the manifest * was created. * @param {shaka.util.FailoverUri} initMetadata + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @return {shaka.media.ISegmentIndexSource} A SegmentIndexSource on success; * otherwise, return null. * @private */ shaka.dash.MpdProcessor.prototype.makeSegmentIndexSourceViaSegmentTemplate_ = - function(mpd, period, representation, manifestCreationTime, initMetadata) { + function(mpd, period, representation, manifestCreationTime, initMetadata, + networkCallback) { shaka.asserts.assert(representation.segmentTemplate); var segmentTemplate = representation.segmentTemplate; if (segmentTemplate.indexUrlTemplate) { return this.makeSegmentIndexSourceViaIndexUrlTemplate_( - mpd, period, representation, manifestCreationTime, initMetadata); + mpd, period, representation, manifestCreationTime, initMetadata, + networkCallback); } if (!segmentTemplate.mediaUrlTemplate) { @@ -826,7 +841,7 @@ shaka.dash.MpdProcessor.prototype.makeSegmentIndexSourceViaSegmentTemplate_ = if (segmentTemplate.timeline) { return new shaka.dash.TimelineSegmentIndexSource( - mpd, period, representation, manifestCreationTime); + mpd, period, representation, manifestCreationTime, networkCallback); } else if (segmentTemplate.segmentDuration) { if ((mpd.type != 'dynamic') && (period.duration == null)) { shaka.log.warning( @@ -836,7 +851,7 @@ shaka.dash.MpdProcessor.prototype.makeSegmentIndexSourceViaSegmentTemplate_ = return null; } return new shaka.dash.DurationSegmentIndexSource( - mpd, period, representation, manifestCreationTime); + mpd, period, representation, manifestCreationTime, networkCallback); } shaka.asserts.unreachable(); @@ -853,12 +868,14 @@ shaka.dash.MpdProcessor.prototype.makeSegmentIndexSourceViaSegmentTemplate_ = * @param {number} manifestCreationTime The time, in seconds, when the manifest * was created. * @param {shaka.util.FailoverUri} initMetadata + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @return {shaka.media.ISegmentIndexSource} A SegmentIndexSource on success; * otherwise, return null. * @private */ shaka.dash.MpdProcessor.prototype.makeSegmentIndexSourceViaIndexUrlTemplate_ = - function(mpd, period, representation, manifestCreationTime, initMetadata) { + function(mpd, period, representation, manifestCreationTime, initMetadata, + networkCallback) { shaka.asserts.assert(representation.segmentTemplate); shaka.asserts.assert(representation.segmentTemplate.indexUrlTemplate); @@ -884,7 +901,7 @@ shaka.dash.MpdProcessor.prototype.makeSegmentIndexSourceViaIndexUrlTemplate_ = // Generate the media URL. var mediaUrl = shaka.dash.MpdUtils.createFromTemplate( - representation, 1, 0, 0, null); + networkCallback, representation, 1, 0, 0, null); if (!mediaUrl) { // An error has already been logged. return null; @@ -897,7 +914,8 @@ shaka.dash.MpdProcessor.prototype.makeSegmentIndexSourceViaIndexUrlTemplate_ = return null; } - var indexMetadata = this.createSegmentMetadata_(representationIndex); + var indexMetadata = this.createSegmentMetadata_( + representationIndex, networkCallback); var segmentIndexSource = new shaka.dash.ContainerSegmentIndexSource( @@ -906,7 +924,8 @@ shaka.dash.MpdProcessor.prototype.makeSegmentIndexSourceViaIndexUrlTemplate_ = containerType, indexMetadata, initMetadata, - manifestCreationTime); + manifestCreationTime, + networkCallback); return segmentIndexSource; }; @@ -995,11 +1014,12 @@ shaka.dash.MpdProcessor.prototype.generateUrlTypeObject_ = function( * * @param {!shaka.dash.mpd.RepresentationIndex| * !shaka.dash.mpd.Initialization} urlTypeObject + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @return {!shaka.util.FailoverUri} * @private */ shaka.dash.MpdProcessor.prototype.createSegmentMetadata_ = function( - urlTypeObject) { + urlTypeObject, networkCallback) { var url = urlTypeObject.url; var startByte = 0; @@ -1009,6 +1029,6 @@ shaka.dash.MpdProcessor.prototype.createSegmentMetadata_ = function( endByte = urlTypeObject.range.end; } - return new shaka.util.FailoverUri(url, startByte, endByte); + return new shaka.util.FailoverUri(networkCallback, url, startByte, endByte); }; diff --git a/lib/dash/mpd_utils.js b/lib/dash/mpd_utils.js index 285ff0c166..a4deeae3a5 100644 --- a/lib/dash/mpd_utils.js +++ b/lib/dash/mpd_utils.js @@ -42,6 +42,7 @@ shaka.dash.MpdUtils.GAP_OVERLAP_WARN_THRESHOLD = 1.0 / 32.0; * Generates a set of SegmentReferences from a SegmentTemplate with a 'duration' * attribute. * + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @param {!shaka.dash.mpd.Representation} representation * @param {number} firstSegmentNumber The segment number (one-based) of the * first SegmentReference to generate, relative to the start of the @@ -51,7 +52,7 @@ shaka.dash.MpdUtils.GAP_OVERLAP_WARN_THRESHOLD = 1.0 / 32.0; * success; otherwise, null. */ shaka.dash.MpdUtils.generateSegmentReferences = function( - representation, firstSegmentNumber, numSegments) { + networkCallback, representation, firstSegmentNumber, numSegments) { shaka.asserts.assert(representation.segmentTemplate); shaka.asserts.assert(representation.segmentTemplate.timescale > 0); shaka.asserts.assert(representation.segmentTemplate.segmentDuration); @@ -80,7 +81,7 @@ shaka.dash.MpdUtils.generateSegmentReferences = function( // Generate the media URL. var mediaUrl = shaka.dash.MpdUtils.createFromTemplate( - representation, segmentReplacement, timeReplacement, + networkCallback, representation, segmentReplacement, timeReplacement, 0 /* startByte */, null /* endByte */); if (!mediaUrl) { // An error has already been logged. @@ -101,6 +102,7 @@ shaka.dash.MpdUtils.generateSegmentReferences = function( /** * Creates a FailoverUri from a relative template URL. * + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @param {!shaka.dash.mpd.Representation} representation * @param {number} number * @param {number} time @@ -109,7 +111,7 @@ shaka.dash.MpdUtils.generateSegmentReferences = function( * @return {shaka.util.FailoverUri} */ shaka.dash.MpdUtils.createFromTemplate = function( - representation, number, time, startByte, endByte) { + networkCallback, representation, number, time, startByte, endByte) { shaka.asserts.assert(representation.segmentTemplate); if (!representation.segmentTemplate) return null; @@ -120,7 +122,8 @@ shaka.dash.MpdUtils.createFromTemplate = function( 'using the Representation\'s BaseURL instead.', representation); return representation.baseUrl ? - new shaka.util.FailoverUri(representation.baseUrl, startByte, endByte) : + new shaka.util.FailoverUri( + networkCallback, representation.baseUrl, startByte, endByte) : null; } @@ -138,7 +141,8 @@ shaka.dash.MpdUtils.createFromTemplate = function( var mediaUrl = shaka.util.FailoverUri.resolve( representation.baseUrl, filledUrlTemplate); - return new shaka.util.FailoverUri(mediaUrl, startByte, endByte); + return new shaka.util.FailoverUri( + networkCallback, mediaUrl, startByte, endByte); }; diff --git a/lib/dash/timeline_segment_index_source.js b/lib/dash/timeline_segment_index_source.js index fe1659e8a8..017cd305aa 100644 --- a/lib/dash/timeline_segment_index_source.js +++ b/lib/dash/timeline_segment_index_source.js @@ -37,12 +37,13 @@ goog.require('shaka.util.TypedBind'); * @param {!shaka.dash.mpd.Representation} representation * @param {number} manifestCreationTime The time, in seconds, when the manifest * was created. + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @constructor * @struct * @implements {shaka.media.ISegmentIndexSource} */ shaka.dash.TimelineSegmentIndexSource = function( - mpd, period, representation, manifestCreationTime) { + mpd, period, representation, manifestCreationTime, networkCallback) { shaka.asserts.assert(period.start != null); shaka.asserts.assert(representation.segmentTemplate); shaka.asserts.assert(representation.segmentTemplate.mediaUrlTemplate); @@ -63,6 +64,9 @@ shaka.dash.TimelineSegmentIndexSource = function( /** @private {shaka.media.SegmentIndex} */ this.segmentIndex_ = null; + + /** @private {shaka.util.FailoverUri.NetworkCallback} */ + this.networkCallback_ = networkCallback; }; @@ -74,6 +78,7 @@ shaka.dash.TimelineSegmentIndexSource.prototype.destroy = function() { this.mpd_ = null; this.period_ = null; this.representation_ = null; + this.networkCallback_ = null; if (this.segmentIndex_) { this.segmentIndex_.destroy(); @@ -116,7 +121,8 @@ shaka.dash.TimelineSegmentIndexSource.prototype.create = function() { // Generate the media URL. var mediaUrl = shaka.dash.MpdUtils.createFromTemplate( - this.representation_, segmentReplacement, timeReplacement, 0, null); + this.networkCallback_, this.representation_, segmentReplacement, + timeReplacement, 0, null); if (!mediaUrl) { var error = new Error('Failed to generate media URL.'); error.type = 'dash'; diff --git a/lib/media/mp4_segment_index_parser.js b/lib/media/mp4_segment_index_parser.js index ace04eeaa7..89a338b39e 100644 --- a/lib/media/mp4_segment_index_parser.js +++ b/lib/media/mp4_segment_index_parser.js @@ -42,15 +42,17 @@ shaka.media.Mp4SegmentIndexParser = function() {}; * @param {number} sidxOffset The SIDX's offset, in bytes, from the start of * the MP4 container. * @param {!Array.} url The location of each SegmentReference. + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @return {Array.} SegmentReferences on success; * otherwise, return null. */ shaka.media.Mp4SegmentIndexParser.prototype.parse = function( - sidxData, sidxOffset, url) { + sidxData, sidxOffset, url, networkCallback) { var references = null; try { - references = this.parseInternal_(sidxData, sidxOffset, url); + references = this.parseInternal_( + sidxData, sidxOffset, url, networkCallback); } catch (exception) { if (!(exception instanceof RangeError)) { throw exception; @@ -74,6 +76,7 @@ shaka.media.Mp4SegmentIndexParser.SIDX_INDICATOR = 0x73696478; * @param {!DataView} sidxData * @param {number} sidxOffset * @param {!Array.} url + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @return {Array.} SegmentReferences on success; * otherwise, return null. * @throws {RangeError} @@ -81,7 +84,7 @@ shaka.media.Mp4SegmentIndexParser.SIDX_INDICATOR = 0x73696478; * @see ISO/IEC 14496-12:2012 section 4.2 and 8.16.3 */ shaka.media.Mp4SegmentIndexParser.prototype.parseInternal_ = function( - sidxData, sidxOffset, url) { + sidxData, sidxOffset, url, networkCallback) { var references = []; var reader = new shaka.util.DataViewReader( @@ -164,7 +167,7 @@ shaka.media.Mp4SegmentIndexParser.prototype.parseInternal_ = function( } var failover = new shaka.util.FailoverUri( - url, startByte, startByte + referenceSize - 1); + networkCallback, url, startByte, startByte + referenceSize - 1); references.push( new shaka.media.SegmentReference( unscaledStartTime / timescale, diff --git a/lib/media/offline_segment_index_source.js b/lib/media/offline_segment_index_source.js index 69150ac045..61da20bfa0 100644 --- a/lib/media/offline_segment_index_source.js +++ b/lib/media/offline_segment_index_source.js @@ -74,6 +74,7 @@ shaka.media.OfflineSegmentIndexSource.prototype.create = function() { info['start_time'], info['end_time'], new shaka.util.FailoverUri( + null, [new goog.Uri(info['url'])], info['start_byte'], null /* endByte */))); diff --git a/lib/media/webm_segment_index_parser.js b/lib/media/webm_segment_index_parser.js index e7941d88fb..6c448fb565 100644 --- a/lib/media/webm_segment_index_parser.js +++ b/lib/media/webm_segment_index_parser.js @@ -43,15 +43,16 @@ shaka.media.WebmSegmentIndexParser = function() {}; * @param {!DataView} cuesData The WebM container's "Cueing Data" section. * @param {!DataView} initData The WebM container's headers. * @param {!Array.} url The location of each SegmentReference. + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @return {Array.} SegmentReferences on success; * otherwise, return null. */ shaka.media.WebmSegmentIndexParser.prototype.parse = function( - cuesData, initData, url) { + cuesData, initData, url, networkCallback) { var references = null; try { - references = this.parseInternal_(cuesData, initData, url); + references = this.parseInternal_(cuesData, initData, url, networkCallback); } catch (exception) { if (!(exception instanceof RangeError)) { throw exception; @@ -105,13 +106,14 @@ shaka.media.WebmSegmentIndexParser.CUE_CLUSTER_POSITION = 0xf1; * @return {Array.} SegmentReferences on success; * otherwise, return null. * @param {!Array.} url The location of each SegmentReference. + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @throws {RangeError} * @see http://www.matroska.org/technical/specs/index.html * @see http://www.webmproject.org/docs/container/ * @private */ shaka.media.WebmSegmentIndexParser.prototype.parseInternal_ = function( - cuesData, initData, url) { + cuesData, initData, url, networkCallback) { var tuple = this.parseWebmContainer_(initData); if (!tuple) { return null; @@ -125,7 +127,8 @@ shaka.media.WebmSegmentIndexParser.prototype.parseInternal_ = function( } return this.parseCues_( - cuesElement, tuple.segmentOffset, tuple.timecodeScale, url); + cuesElement, tuple.segmentOffset, tuple.timecodeScale, url, + networkCallback); }; @@ -239,11 +242,12 @@ shaka.media.WebmSegmentIndexParser.prototype.parseInfo_ = function( * @param {number} segmentOffset * @param {number} timecodeScale * @param {!Array.} url + * @param {shaka.util.FailoverUri.NetworkCallback} networkCallback * @return {!Array.} * @private */ shaka.media.WebmSegmentIndexParser.prototype.parseCues_ = function( - cuesElement, segmentOffset, timecodeScale, url) { + cuesElement, segmentOffset, timecodeScale, url, networkCallback) { var references = []; var parser = cuesElement.createParser(); @@ -269,7 +273,7 @@ shaka.media.WebmSegmentIndexParser.prototype.parseCues_ = function( shaka.asserts.assert(lastOffset >= 0); var failover = new shaka.util.FailoverUri( - url, lastOffset, currentOffset - 1); + networkCallback, url, lastOffset, currentOffset - 1); references.push( new shaka.media.SegmentReference(lastTime, currentTime, failover)); } @@ -281,7 +285,8 @@ shaka.media.WebmSegmentIndexParser.prototype.parseCues_ = function( if (lastTime >= 0) { shaka.asserts.assert(lastOffset >= 0); - var failover = new shaka.util.FailoverUri(url, lastOffset, null); + var failover = new shaka.util.FailoverUri(networkCallback, url, + lastOffset, null); references.push( new shaka.media.SegmentReference(lastTime, null, failover)); } diff --git a/lib/player/dash_video_source.js b/lib/player/dash_video_source.js index 8e75f629b2..ac620738b8 100644 --- a/lib/player/dash_video_source.js +++ b/lib/player/dash_video_source.js @@ -62,7 +62,7 @@ shaka.player.DashVideoSource = shaka.player.StreamVideoSource.call(this, null, estimator, abrManager); /** @private {!shaka.util.FailoverUri} */ - this.mpdUrl_ = new shaka.util.FailoverUri([new goog.Uri(mpdUrl)]); + this.mpdUrl_ = new shaka.util.FailoverUri(null, [new goog.Uri(mpdUrl)]); /** @private {?shaka.player.DashVideoSource.ContentProtectionCallback} */ this.interpretContentProtection_ = interpretContentProtection; @@ -75,6 +75,9 @@ shaka.player.DashVideoSource = /** @private {!Array.} */ this.captionsMime_ = []; + + /** @private {shaka.util.FailoverUri.NetworkCallback} */ + this.networkCallback_ = null; }; goog.inherits(shaka.player.DashVideoSource, shaka.player.StreamVideoSource); if (shaka.features.Dash) { @@ -118,9 +121,21 @@ shaka.player.DashVideoSource.prototype.addExternalCaptions = }; +/** + * Sets the callback used to modify each segment request's URL and headers. + * + * @param {!shaka.util.FailoverUri.NetworkCallback} callback + * @export + */ +shaka.player.DashVideoSource.prototype.setNetworkCallback = function(callback) { + this.networkCallback_ = callback; +}; + + /** @override */ shaka.player.DashVideoSource.prototype.destroy = function() { this.interpretContentProtection_ = null; + this.networkCallback_ = null; shaka.player.StreamVideoSource.prototype.destroy.call(this); }; @@ -139,7 +154,7 @@ shaka.player.DashVideoSource.prototype.load = function() { var mpdProcessor = new shaka.dash.MpdProcessor(this.interpretContentProtection_); - this.manifestInfo = mpdProcessor.process(mpd); + this.manifestInfo = mpdProcessor.process(mpd, this.networkCallback_); var baseClassLoad = shaka.player.StreamVideoSource.prototype.load; var p = baseClassLoad.call(this); @@ -159,7 +174,7 @@ shaka.player.DashVideoSource.prototype.onUpdateManifest = function(url) { function(mpd) { var mpdProcessor = new shaka.dash.MpdProcessor(this.interpretContentProtection_); - var newManifestInfo = mpdProcessor.process(mpd); + var newManifestInfo = mpdProcessor.process(mpd, this.networkCallback_); return Promise.resolve(newManifestInfo); }) ); diff --git a/lib/player/offline_video_source.js b/lib/player/offline_video_source.js index f52b7aeac9..498ef4bd24 100644 --- a/lib/player/offline_video_source.js +++ b/lib/player/offline_video_source.js @@ -81,6 +81,9 @@ shaka.player.OfflineVideoSource = function(groupId, estimator, abrManager) { /** @private {!Object.} */ this.config_ = {}; + + /** @private {shaka.util.FailoverUri.NetworkCallback} */ + this.networkCallback_ = null; }; goog.inherits(shaka.player.OfflineVideoSource, shaka.player.StreamVideoSource); if (shaka.features.Offline) { @@ -182,7 +185,8 @@ shaka.player.OfflineVideoSource.prototype.store = function( /** @type {!Array.} */ var selectedStreams = []; - var failover = new shaka.util.FailoverUri([new goog.Uri(mpdUrl)]); + var failover = new shaka.util.FailoverUri(this.networkCallback_, + [new goog.Uri(mpdUrl)]); var mpdRequest = new shaka.dash.MpdRequest(failover, this.mpdRequestTimeout); return mpdRequest.send().then(shaka.util.TypedBind(this, @@ -190,7 +194,7 @@ shaka.player.OfflineVideoSource.prototype.store = function( function(mpd) { var mpdProcessor = new shaka.dash.MpdProcessor(interpretContentProtection); - this.manifestInfo = mpdProcessor.process(mpd); + this.manifestInfo = mpdProcessor.process(mpd, this.networkCallback_); if (this.manifestInfo.live) { var error = new Error('Unable to store live streams offline.'); @@ -294,6 +298,18 @@ if (shaka.features.Dash) { } +/** + * Sets the callback used to intercept the URL in network requests. + * + * @param {!shaka.util.FailoverUri.NetworkCallback} callback + * @export + */ +shaka.player.OfflineVideoSource.prototype.setNetworkCallback = + function(callback) { + this.networkCallback_ = callback; +}; + + /** * Creates sourceBuffers and appends init data for each of the given streams. * This should trigger encrypted events for any encrypted streams. @@ -465,7 +481,8 @@ shaka.player.OfflineVideoSource.prototype.reconstructManifestInfo_ = new goog.Uri('data:application/octet-stream;base64,' + shaka.util.Uint8ArrayUtils.toBase64(initData)); var segmentInitSource = new shaka.media.SegmentInitSource( - new shaka.util.FailoverUri([segmentInitUrl], 0, null)); + new shaka.util.FailoverUri(this.networkCallback_, [segmentInitUrl], + 0, null)); streamInfo.segmentIndexSource = segmentIndexSource; streamInfo.segmentInitSource = segmentInitSource; diff --git a/lib/util/failover_uri.js b/lib/util/failover_uri.js index 0bfa826922..e503a8d528 100644 --- a/lib/util/failover_uri.js +++ b/lib/util/failover_uri.js @@ -29,13 +29,14 @@ goog.require('shaka.util.AjaxRequest'); /** * Creates a FailoverUri. * + * @param {shaka.util.FailoverUri.NetworkCallback} callback * @param {Array.} urls * @param {number=} opt_startByte The start byte of the data, defaults to 0. * @param {?number=} opt_endByte The end byte of the data, null means the end; * defaults to null. * @constructor */ -shaka.util.FailoverUri = function(urls, opt_startByte, opt_endByte) { +shaka.util.FailoverUri = function(callback, urls, opt_startByte, opt_endByte) { shaka.asserts.assert(urls); shaka.asserts.assert(urls.length > 0); @@ -53,9 +54,24 @@ shaka.util.FailoverUri = function(urls, opt_startByte, opt_endByte) { /** @private {shaka.util.AjaxRequest} */ this.request_ = null; + + /** @private {shaka.util.FailoverUri.NetworkCallback} */ + this.callback_ = callback; }; +/** + * A callback to the application called prior to media network events. The + * first parameter is the URL for the request. The second parameter is the + * headers for the request. These can be modified. The callback should return + * a modified URL for the request, or null to use the original. + * + * @typedef {?function(string,!Object.):(string?)} + * @exportDoc + */ +shaka.util.FailoverUri.NetworkCallback; + + /** * Resolves a relative url to the given |baseUrl|. * @@ -118,15 +134,19 @@ shaka.util.FailoverUri.prototype.abortFetch = function() { * * @private * @param {number} i - * @param {shaka.util.AjaxRequest.Parameters} parameters + * @param {!shaka.util.AjaxRequest.Parameters} parameters * @param {shaka.util.IBandwidthEstimator=} opt_estimator * @return {!Promise.} */ shaka.util.FailoverUri.prototype.createRequest_ = function(i, parameters, opt_estimator) { shaka.asserts.assert(i < this.urls.length); - this.request_ = new shaka.util.AjaxRequest( - this.urls[i].toString(), parameters); + + var url = this.urls[i].toString(); + if (this.callback_) { + url = this.callback_(url, parameters.requestHeaders) || url; + } + this.request_ = new shaka.util.AjaxRequest(url, parameters); if (opt_estimator) { this.request_.estimator = opt_estimator; } @@ -164,6 +184,7 @@ shaka.util.FailoverUri.prototype.createRequest_ = */ shaka.util.FailoverUri.prototype.clone = function() { return new shaka.util.FailoverUri( + this.callback_, this.urls.map(function(a) { return a.clone(); }), this.startByte, this.endByte diff --git a/lib/util/license_request.js b/lib/util/license_request.js index a0d75002d2..ede11c73ae 100644 --- a/lib/util/license_request.js +++ b/lib/util/license_request.js @@ -53,7 +53,7 @@ shaka.util.LicenseRequest = function( shaka.asserts.assert((method == 'GET') || (method == 'POST')); /** @private {!shaka.util.FailoverUri} */ - this.url_ = new shaka.util.FailoverUri([new goog.Uri(url)]); + this.url_ = new shaka.util.FailoverUri(null, [new goog.Uri(url)]); /** @private {!shaka.util.AjaxRequest.Parameters} */ this.parameters_ = new shaka.util.AjaxRequest.Parameters(); diff --git a/spec/ajax_request_spec.js b/spec/ajax_request_spec.js index b37ae3f436..44b33055f0 100644 --- a/spec/ajax_request_spec.js +++ b/spec/ajax_request_spec.js @@ -35,7 +35,7 @@ describe('AjaxRequest', function() { // Set up mock FailoverUri. The mock FailoverUri is used to insert a // stream into the database. originalFailoverUri = shaka.util.FailoverUri; - var mockFailoverUri = function(url, startByte, endByte) { + var mockFailoverUri = function(callback, url, startByte, endByte) { return { fetch: function() { return Promise.resolve(new ArrayBuffer(bufferSize)); diff --git a/spec/content_database_spec.js b/spec/content_database_spec.js index aeb8b4e1ed..8f6a0709b5 100644 --- a/spec/content_database_spec.js +++ b/spec/content_database_spec.js @@ -69,7 +69,7 @@ describe('ContentDatabase', function() { // Set up mock FailoverUri. originalFailoverUri = shaka.util.FailoverUri; - var mockFailoverUri = function(url, startByte, endByte) { + var mockFailoverUri = function(callback, url, startByte, endByte) { return { fetch: function() { return Promise.resolve(new ArrayBuffer(768 * 1024)); diff --git a/spec/player_integration.js b/spec/player_integration.js index c54c65b6f7..3ac30bdd62 100644 --- a/spec/player_integration.js +++ b/spec/player_integration.js @@ -525,6 +525,63 @@ describe('Player', function() { }); }); + describe('setNetworkCallback', function() { + it('intercepts network calls', function(done) { + var callback = jasmine.createSpy('network').and.callFake( + function(url, parameters) { + expect(url).toBeTruthy(); + expect(parameters).toBeTruthy(); + return null; + }); + var source = newSource(plainManifest); + source.setNetworkCallback(callback); + + player.load(source).then(function() { + video.play(); + return waitForMovement(video, eventManager); + }).then(function() { + expect(callback.calls.any()).toBe(true); + done(); + }).catch(function(error) { + fail(error); + done(); + }); + }); + + it('changes urls', function(done) { + var callback = jasmine.createSpy('network').and.callFake( + function(url, parameters) { + expect(url).toBeTruthy(); + expect(parameters).toBeTruthy(); + return url.replace('example', 'appspot'); + }); + + // Load the angel manifest and edit the urls to point to an + // invalid location. + var url = new shaka.util.FailoverUri(null, + [new goog.Uri(languagesManifest)]); + var params = new shaka.util.AjaxRequest.Parameters(); + params.responseType = 'text'; + url.fetch(params).then(function(data) { + var dummy = data.replace('appspot', 'example'); + dummy = 'data:text/plain,' + window.encodeURIComponent(dummy); + + var source = newSource(dummy); + source.setNetworkCallback(callback); + return player.load(source); + }).then(function() { + video.play(); + return waitForMovement(video, eventManager); + }).then(function() { + expect(callback.calls.any()).toBe(true); + done(); + }).catch(function(error) { + fail(error); + done(); + }); + }); + }); + it('supports failover', function(done) { player.load(newSource(failoverManifest)).then(function() { video.play(); diff --git a/spec/util.js b/spec/util.js index 2ab6554018..7ac7725b7b 100644 --- a/spec/util.js +++ b/spec/util.js @@ -331,7 +331,7 @@ function checkReference(reference, url, startTime, endTime) { */ function createFailover(url, opt_start, opt_end) { return new shaka.util.FailoverUri( - [new goog.Uri(url)], opt_start || 0, opt_end || null); + null, [new goog.Uri(url)], opt_start || 0, opt_end || null); } diff --git a/third_party/closure/goog/uri/uri.js b/third_party/closure/goog/uri/uri.js index 9d445100c5..2bd616215a 100644 --- a/third_party/closure/goog/uri/uri.js +++ b/third_party/closure/goog/uri/uri.js @@ -222,6 +222,10 @@ goog.Uri.prototype.toString = function() { goog.Uri.prototype.resolve = function(relativeUri) { var absoluteUri = this.clone(); + if (absoluteUri.scheme_ === 'data') { + // Cannot have a relative URI to a data URI. + absoluteUri = new goog.Uri(); + } // we satisfy these conditions by looking for the first part of relativeUri // that is not blank and applying defaults to the rest