diff --git a/demo/common/asset.js b/demo/common/asset.js index bbd57e5532..201e9e0a84 100644 --- a/demo/common/asset.js +++ b/demo/common/asset.js @@ -373,7 +373,7 @@ const ShakaDemoAssetInfo = class { if (this.licenseRequestHeaders.size) { /** @type {!shaka.extern.RequestFilter} */ - const filter = (requestType, request, advType) => { + const filter = (requestType, request, context) => { return this.addLicenseRequestHeaders_(this.licenseRequestHeaders, requestType, request); diff --git a/docs/tutorials/ad_monetization.md b/docs/tutorials/ad_monetization.md index cccb21cd0b..024adc54cb 100644 --- a/docs/tutorials/ad_monetization.md +++ b/docs/tutorials/ad_monetization.md @@ -203,7 +203,7 @@ default, so you should only need to set this if you've enabled it in other parts your code. ```js - player.getNetworkingEngine().registerRequestFilter(function(type, request, advType) { + player.getNetworkingEngine().registerRequestFilter(function(type, request, context) { if (type == shaka.net.NetworkingEngine.RequestType.MANIFEST || type == shaka.net.NetworkingEngine.RequestType.SEGMENT) { request.withCredentials = false; diff --git a/docs/tutorials/application-level-redirects.md b/docs/tutorials/application-level-redirects.md index 58c602b03f..f45a68537e 100644 --- a/docs/tutorials/application-level-redirects.md +++ b/docs/tutorials/application-level-redirects.md @@ -36,7 +36,7 @@ const HTTP_IN_HEX = 0x68747470; const RequestType = shaka.net.NetworkingEngine.RequestType; -player.getNetworkingEngine().registerResponseFilter(async (type, response, advType) => { +player.getNetworkingEngine().registerResponseFilter(async (type, response, context) => { // NOTE: If the system requires an ALR for both manifests and segments, // remove this RequestType check. if (type != RequestType.MANIFEST) { diff --git a/docs/tutorials/fairplay.md b/docs/tutorials/fairplay.md index 0bad7064b3..d00a10d1b2 100644 --- a/docs/tutorials/fairplay.md +++ b/docs/tutorials/fairplay.md @@ -90,7 +90,7 @@ or give the response in a different format. For more info, see the general {@tutorial license-wrapping} tutorial: ```js -player.getNetworkingEngine().registerRequestFilter((type, request, advType) => { +player.getNetworkingEngine().registerRequestFilter((type, request, context) => { if (type != shaka.net.NetworkingEngine.RequestType.LICENSE) { return; } @@ -103,7 +103,7 @@ player.getNetworkingEngine().registerRequestFilter((type, request, advType) => { request.body = shaka.util.StringUtils.toUTF8(encodeURIComponent(params)); }); -player.getNetworkingEngine().registerResponseFilter((type, response, advType) => { +player.getNetworkingEngine().registerResponseFilter((type, response, context) => { if (type != shaka.net.NetworkingEngine.RequestType.LICENSE) { return; } @@ -143,7 +143,7 @@ Note: If the url of the license server has to undergo any transformation (eg: add the contentId), you would have to create your filter manually. ```js -player.getNetworkingEngine().registerRequestFilter((type, request, advType) => { +player.getNetworkingEngine().registerRequestFilter((type, request, context) => { if (type != shaka.net.NetworkingEngine.RequestType.LICENSE) { return; } @@ -175,7 +175,7 @@ Note: If the url of the license server has to undergo any transformation (eg: add the contentId), you would have to create your filter manually. ```js -player.getNetworkingEngine().registerRequestFilter((type, request, advType) => { +player.getNetworkingEngine().registerRequestFilter((type, request, context) => { if (type != shaka.net.NetworkingEngine.RequestType.LICENSE) { return; } diff --git a/docs/tutorials/license-server-auth.md b/docs/tutorials/license-server-auth.md index 801d46ad9b..e51b0d8065 100644 --- a/docs/tutorials/license-server-auth.md +++ b/docs/tutorials/license-server-auth.md @@ -62,7 +62,7 @@ arbitrary headers to Shaka's requests through a request filter callback. Register the filter before calling `player.load()`: ```js - player.getNetworkingEngine().registerRequestFilter(function(type, request, advType) { + player.getNetworkingEngine().registerRequestFilter(function(type, request, context) { // Only add headers to license requests: if (type == shaka.net.NetworkingEngine.RequestType.LICENSE) { // This is the specific header name and value the server wants: @@ -92,7 +92,7 @@ try to use it without setting the parameter, you will see `Error code 6007` We can use a request filter to modify the URL and add the required parameter: ```js - player.getNetworkingEngine().registerRequestFilter(function(type, request, advType) { + player.getNetworkingEngine().registerRequestFilter(function(type, request, context) { // Only add headers to license requests: if (type == shaka.net.NetworkingEngine.RequestType.LICENSE) { // This is the specific parameter name and value the server wants: @@ -140,7 +140,7 @@ Our `cookie_auth` endpoint sends back headers that allow credentialed requests, so we set a flag in our request filter to send credentials cross-site: ```js - player.getNetworkingEngine().registerRequestFilter(function(type, request, advType) { + player.getNetworkingEngine().registerRequestFilter(function(type, request, context) { if (type == shaka.net.NetworkingEngine.RequestType.LICENSE) { request.allowCrossSiteCredentials = true; } @@ -205,7 +205,7 @@ const authToken = null; Now change the request filter: ```js - player.getNetworkingEngine().registerRequestFilter(function(type, request, advType) { + player.getNetworkingEngine().registerRequestFilter(function(type, request, context) { // Only add headers to license requests: if (type != shaka.net.NetworkingEngine.RequestType.LICENSE) return; diff --git a/docs/tutorials/license-wrapping.md b/docs/tutorials/license-wrapping.md index fa580b717a..791196cc8e 100644 --- a/docs/tutorials/license-wrapping.md +++ b/docs/tutorials/license-wrapping.md @@ -65,7 +65,7 @@ will see `Error code 6007`, which means `LICENSE_REQUEST_FAILED`. To wrap the license request, we must register a request filter: ```js - player.getNetworkingEngine().registerRequestFilter(function(type, request, advType) { + player.getNetworkingEngine().registerRequestFilter(function(type, request, context) { // Alias some utilities provided by the library. const StringUtils = shaka.util.StringUtils; const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils; @@ -136,7 +136,7 @@ Widevine CDM does not understand this wrapped format, so we must unwrap it first using a request filter: ```js - player.getNetworkingEngine().registerResponseFilter(function(type, response, advType) { + player.getNetworkingEngine().registerResponseFilter(function(type, response, context) { // Alias some utilities provided by the library. const StringUtils = shaka.util.StringUtils; const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils; diff --git a/externs/shaka/manifest_parser.js b/externs/shaka/manifest_parser.js index 6b297ec8eb..c3f8b6e592 100644 --- a/externs/shaka/manifest_parser.js +++ b/externs/shaka/manifest_parser.js @@ -100,10 +100,6 @@ shaka.extern.ManifestParser = class { /** * @typedef {{ * networkingEngine: !shaka.net.NetworkingEngine, - * modifyManifestRequest: function(!shaka.extern.Request, - * shaka.util.CmcdManager.ManifestInfo), - * modifySegmentRequest: function(!shaka.extern.Request, - * shaka.util.CmcdManager.SegmentInfo), * filter: function(shaka.extern.Manifest):!Promise, * makeTextStreamsForClosedCaptions: function(shaka.extern.Manifest), * onTimelineRegionAdded: function(shaka.extern.TimelineRegionInfo), @@ -124,12 +120,6 @@ shaka.extern.ManifestParser = class { * * @property {!shaka.net.NetworkingEngine} networkingEngine * The networking engine to use for network requests. - * @property {function(!shaka.extern.Request, - * shaka.util.CmcdManager.ManifestInfo)} modifyManifestRequest - * Modify a manifest request - * @property {function(!shaka.extern.Request, - * shaka.util.CmcdManager.SegmentInfo)} modifySegmentRequest - * Modify a segment request * @property {function(shaka.extern.Manifest):!Promise} filter * Should be called when new variants or text streams are added to the * Manifest. Note that this operation is asynchronous. diff --git a/externs/shaka/net.js b/externs/shaka/net.js index 4435ad8bc1..2851f76a54 100644 --- a/externs/shaka/net.js +++ b/externs/shaka/net.js @@ -203,14 +203,14 @@ shaka.extern.HeadersReceived; * Defines a filter for requests. This filter takes the request and modifies * it before it is sent to the scheme plugin. * The RequestType describes the basic type of the request (manifest, segment, - * etc). The optional AdvancedRequestType will be provided in the case of a - * sub-type of the basic type (playlist manifest, init segment, etc). - * A request filter can run asynchronously by returning a promise; in this case, - * the request will not be sent until the promise is resolved. + * etc). The optional RequestContext will be provided where applicable to + * provide additional infomation about the request. A request filter can run + * asynchronously by returning a promise; in this case, the request will not be + * sent until the promise is resolved. * * @typedef {!function(shaka.net.NetworkingEngine.RequestType, * shaka.extern.Request, - * shaka.net.NetworkingEngine.AdvancedRequestType=): + * shaka.net.NetworkingEngine.RequestContext=): * (Promise|undefined)} * @exportDoc */ @@ -221,13 +221,13 @@ shaka.extern.RequestFilter; * Defines a filter for responses. This filter takes the response and modifies * it before it is returned. * The RequestType describes the basic type of the request (manifest, segment, - * etc). The optional AdvancedRequestType will be provided in the case of a - * sub-type of the basic type (playlist manifest, init segment, etc). - * A response filter can run asynchronously by returning a promise. + * etc). The optional RequestContext will be provided where applicable to + * provide additional infomation about the request. A response filter can run + * asynchronously by returning a promise. * * @typedef {!function(shaka.net.NetworkingEngine.RequestType, * shaka.extern.Response, - * shaka.net.NetworkingEngine.AdvancedRequestType=): + * shaka.net.NetworkingEngine.RequestContext=): * (Promise|undefined)} * @exportDoc */ diff --git a/lib/dash/dash_parser.js b/lib/dash/dash_parser.js index 617fc94655..1b6e0c3eb1 100644 --- a/lib/dash/dash_parser.js +++ b/lib/dash/dash_parser.js @@ -19,7 +19,6 @@ goog.require('shaka.media.PresentationTimeline'); goog.require('shaka.media.SegmentIndex'); goog.require('shaka.net.NetworkingEngine'); goog.require('shaka.text.TextEngine'); -goog.require('shaka.util.CmcdManager'); goog.require('shaka.util.Error'); goog.require('shaka.util.Functional'); goog.require('shaka.util.LanguageUtils'); @@ -222,16 +221,12 @@ shaka.dash.DashParser = class { */ async requestManifest_() { const requestType = shaka.net.NetworkingEngine.RequestType.MANIFEST; - const advType = shaka.net.NetworkingEngine.AdvancedRequestType.MPD; + const type = shaka.net.NetworkingEngine.AdvancedRequestType.MPD; const request = shaka.net.NetworkingEngine.makeRequest( this.manifestUris_, this.config_.retryParameters); const networkingEngine = this.playerInterface_.networkingEngine; - - const format = shaka.util.CmcdManager.StreamingFormat.DASH; - this.playerInterface_.modifyManifestRequest(request, {format: format}); - const startTime = Date.now(); - const operation = networkingEngine.request(requestType, request, advType); + const operation = networkingEngine.request(requestType, request, {type}); this.operationManager_.manage(operation); const response = await operation.promise; @@ -1837,7 +1832,7 @@ shaka.dash.DashParser = class { */ async requestSegment_(uris, startByte, endByte, isInit) { const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT; - const advType = isInit ? + const type = isInit ? shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT : shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT; @@ -1848,7 +1843,7 @@ shaka.dash.DashParser = class { this.config_.retryParameters); const networkingEngine = this.playerInterface_.networkingEngine; - const operation = networkingEngine.request(requestType, request, advType); + const operation = networkingEngine.request(requestType, request, {type}); this.operationManager_.manage(operation); const response = await operation.promise; return response.data; diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index 837a0b9f9c..e9f1b83027 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -26,7 +26,6 @@ goog.require('shaka.net.DataUriPlugin'); goog.require('shaka.net.NetworkingEngine'); goog.require('shaka.util.ArrayUtils'); goog.require('shaka.util.BufferUtils'); -goog.require('shaka.util.CmcdManager'); goog.require('shaka.util.Error'); goog.require('shaka.util.FakeEvent'); goog.require('shaka.util.Functional'); @@ -3273,13 +3272,10 @@ shaka.hls.HlsParser = class { const request = shaka.net.NetworkingEngine.makeRequest( [absoluteUri], this.config_.retryParameters); - const format = shaka.util.CmcdManager.StreamingFormat.HLS; - this.playerInterface_.modifyManifestRequest(request, {format: format}); - - const advType = isPlaylist ? + const type = isPlaylist ? shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_PLAYLIST : shaka.net.NetworkingEngine.AdvancedRequestType.MASTER_PLAYLIST; - return this.makeNetworkRequest_(request, requestType, advType); + return this.makeNetworkRequest_(request, requestType, {type}); } /** @@ -3379,11 +3375,11 @@ shaka.hls.HlsParser = class { * * @param {shaka.extern.Request} request * @param {shaka.net.NetworkingEngine.RequestType} type - * @param {shaka.net.NetworkingEngine.AdvancedRequestType=} advType + * @param {shaka.net.NetworkingEngine.RequestContext=} context * @return {!Promise.} * @private */ - makeNetworkRequest_(request, type, advType) { + makeNetworkRequest_(request, type, context) { if (!this.operationManager_) { throw new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, @@ -3392,7 +3388,7 @@ shaka.hls.HlsParser = class { } const op = this.playerInterface_.networkingEngine.request( - type, request, advType); + type, request, context); this.operationManager_.manage(op); return op.promise; diff --git a/lib/media/streaming_engine.js b/lib/media/streaming_engine.js index 4632bf18d8..1dec6be22b 100644 --- a/lib/media/streaming_engine.js +++ b/lib/media/streaming_engine.js @@ -2109,10 +2109,11 @@ shaka.media.StreamingEngine = class { */ dispatchFetch_(reference, stream, streamDataCallback, isInit) { const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT; - const advType = isInit ? + const type = isInit ? shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT : shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT; - + const segment = reference instanceof shaka.media.SegmentReference ? + reference : undefined; const request = shaka.util.Networking.createSegmentRequest( reference.getUris(), reference.startByte, @@ -2121,24 +2122,13 @@ shaka.media.StreamingEngine = class { streamDataCallback); shaka.log.v2('fetching: reference=', reference); - let duration = 0; - if (reference instanceof shaka.media.SegmentReference) { - // start and endTime are not defined in InitSegmentReference - duration = reference.endTime - reference.startTime; - } - this.playerInterface_.modifySegmentRequest( - request, - { - type: stream.type, - init: reference instanceof shaka.media.InitSegmentReference, - duration: duration, - mimeType: stream.mimeType, - codecs: stream.codecs, - bandwidth: stream.bandwidth, - }, - ); + return this.playerInterface_.netEngine.request( - requestType, request, advType); + requestType, request, { + type: type, + stream: stream, + segment: segment, + }); } /** @@ -2304,8 +2294,6 @@ shaka.media.StreamingEngine = class { * @typedef {{ * getPresentationTime: function():number, * getBandwidthEstimate: function():number, - * modifySegmentRequest: function(shaka.extern.Request, - * shaka.util.CmcdManager.SegmentInfo), * mediaSourceEngine: !shaka.media.MediaSourceEngine, * netEngine: shaka.net.NetworkingEngine, * onError: function(!shaka.util.Error), @@ -2324,9 +2312,6 @@ shaka.media.StreamingEngine = class { * viewer is seeing on screen right now. * @property {function():number} getBandwidthEstimate * Get the estimated bandwidth in bits per second. - * @property {function(shaka.extern.Request, - * shaka.extern.Cmcd.SegmentInfo)} modifySegmentRequest - * The request modifier * @property {!shaka.media.MediaSourceEngine} mediaSourceEngine * The MediaSourceEngine. The caller retains ownership. * @property {shaka.net.NetworkingEngine} netEngine diff --git a/lib/net/networking_engine.js b/lib/net/networking_engine.js index 5354f454e3..9074449f0c 100644 --- a/lib/net/networking_engine.js +++ b/lib/net/networking_engine.js @@ -10,6 +10,7 @@ goog.provide('shaka.net.NetworkingEngine.PendingRequest'); goog.require('goog.Uri'); goog.require('goog.asserts'); +goog.require('shaka.media.SegmentReference'); goog.require('shaka.net.Backoff'); goog.require('shaka.util.AbortableOperation'); goog.require('shaka.util.BufferUtils'); @@ -52,8 +53,11 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget { * Called when the headers are received for a download. * @param {shaka.net.NetworkingEngine.OnDownloadFailed=} onDownloadFailed * Called when a download fails, for any reason. + * @param {shaka.net.NetworkingEngine.OnRequest=} onRequest + * Called when a request is made */ - constructor(onProgressUpdated, onHeadersReceived, onDownloadFailed) { + constructor(onProgressUpdated, onHeadersReceived, onDownloadFailed, + onRequest) { super(); /** @private {boolean} */ @@ -77,6 +81,9 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget { /** @private {?shaka.net.NetworkingEngine.OnDownloadFailed} */ this.onDownloadFailed_ = onDownloadFailed || null; + /** @private {?shaka.net.NetworkingEngine.OnRequest} */ + this.onRequest_ = onRequest || null; + /** @private {boolean} */ this.forceHTTPS_ = false; } @@ -247,11 +254,11 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget { * * @param {shaka.net.NetworkingEngine.RequestType} type * @param {shaka.extern.Request} request - * @param {shaka.net.NetworkingEngine.AdvancedRequestType=} advType + * @param {shaka.net.NetworkingEngine.RequestContext=} context * @return {!shaka.net.NetworkingEngine.PendingRequest} * @export */ - request(type, request, advType) { + request(type, request, context) { const ObjectUtils = shaka.util.ObjectUtils; const numBytesRemainingObj = new shaka.net.NetworkingEngine.NumBytesRemainingClass(); @@ -286,12 +293,12 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget { request.uris = ObjectUtils.cloneObject(request.uris); // Apply the registered filters to the request. - const requestFilterOperation = this.filterRequest_(type, request, advType); + const requestFilterOperation = this.filterRequest_(type, request, context); const requestOperation = requestFilterOperation.chain( () => this.makeRequestWithRetry_(type, request, numBytesRemainingObj)); const responseFilterOperation = requestOperation.chain( (responseAndGotProgress) => - this.filterResponse_(type, responseAndGotProgress, advType)); + this.filterResponse_(type, responseAndGotProgress, context)); // Keep track of time spent in filters. const requestFilterStartTime = Date.now(); @@ -342,15 +349,14 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget { /** * @param {shaka.net.NetworkingEngine.RequestType} type * @param {shaka.extern.Request} request - * @param {shaka.net.NetworkingEngine.AdvancedRequestType=} advType + * @param {shaka.net.NetworkingEngine.RequestContext=} context * @return {!shaka.util.AbortableOperation.} * @private */ - filterRequest_(type, request, advType) { + filterRequest_(type, request, context) { let filterOperation = shaka.util.AbortableOperation.completed(undefined); - for (const requestFilter of this.requestFilters_) { - // Request filters are run sequentially. + const applyFilter = (requestFilter) => { filterOperation = filterOperation.chain(() => { if (request.body) { // TODO: For v4.0 we should remove this or change to always pass a @@ -360,8 +366,17 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget { // copying buffers if this is a partial view. request.body = shaka.util.BufferUtils.toArrayBuffer(request.body); } - return requestFilter(type, request, advType); + return requestFilter(type, request, context); }); + }; + + if (this.onRequest_) { + applyFilter(this.onRequest_); + } + + for (const requestFilter of this.requestFilters_) { + // Request filters are run sequentially. + applyFilter(requestFilter); } // Catch any errors thrown by request filters, and substitute @@ -609,12 +624,12 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget { * @param {shaka.net.NetworkingEngine.RequestType} type * @param {shaka.net.NetworkingEngine.ResponseAndGotProgress} * responseAndGotProgress - * @param {shaka.net.NetworkingEngine.AdvancedRequestType=} advType + * @param {shaka.net.NetworkingEngine.RequestContext=} context * @return {!shaka.extern.IAbortableOperation.< * shaka.net.NetworkingEngine.ResponseAndGotProgress>} * @private */ - filterResponse_(type, responseAndGotProgress, advType) { + filterResponse_(type, responseAndGotProgress, context) { let filterOperation = shaka.util.AbortableOperation.completed(undefined); for (const responseFilter of this.responseFilters_) { // Response filters are run sequentially. @@ -624,7 +639,7 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget { // TODO: See TODO in filterRequest_. resp.data = shaka.util.BufferUtils.toArrayBuffer(resp.data); } - return responseFilter(type, resp, advType); + return responseFilter(type, resp, context); }); } // If successful, return the filtered response with whether it got @@ -771,6 +786,7 @@ shaka.net.NetworkingEngine.AdvancedRequestType = { 'MEDIA_PLAYLIST': 2, 'MASTER_PLAYLIST': 3, 'MPD': 4, + 'MSS': 5, }; @@ -829,6 +845,27 @@ shaka.net.NetworkingEngine.schemes_ = {}; shaka.net.NetworkingEngine.ResponseAndGotProgress; +/** + * @typedef {{ + * type: (shaka.net.NetworkingEngine.AdvancedRequestType|undefined), + * stream: (shaka.extern.Stream|undefined), + * segment: (shaka.media.SegmentReference|undefined) + * }} + * + * @description + * Defines contextual data about a request + * + * @property {shaka.net.NetworkingEngine.AdvancedRequestType=} type + * The advanced type + * @property {shaka.extern.Stream=} stream + * The duration of the segment in seconds + * @property {shaka.media.SegmentReference=} segment + * The request's segment reference + * @export + */ +shaka.net.NetworkingEngine.RequestContext; + + /** * @typedef {function( * !Object., @@ -856,3 +893,16 @@ shaka.net.NetworkingEngine.OnHeadersReceived; * @export */ shaka.net.NetworkingEngine.OnDownloadFailed; + + +/** + * @typedef {function( + * !shaka.net.NetworkingEngine.RequestType, + * !shaka.extern.Request, + * (shaka.net.NetworkingEngine.RequestContext|undefined))} + * + * @description + * A callback function called on every request + * @export + */ +shaka.net.NetworkingEngine.OnRequest; diff --git a/lib/offline/storage.js b/lib/offline/storage.js index c7620b7e04..10c4346be5 100644 --- a/lib/offline/storage.js +++ b/lib/offline/storage.js @@ -1147,10 +1147,6 @@ shaka.offline.Storage = class { const playerInterface = { networkingEngine: networkingEngine, - // No need to apply CMCD data for offline requests - modifyManifestRequest: (request, manifestInfo) => {}, - modifySegmentRequest: (request, segmentInfo) => {}, - // Don't bother filtering now. We will do that later when we have all the // information we need to filter. filter: () => Promise.resolve(), diff --git a/lib/player.js b/lib/player.js index 4a4a0fb201..78c327c950 100644 --- a/lib/player.js +++ b/lib/player.js @@ -615,6 +615,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget { dependencyInjector(this); } + + // Create the CMCD manager so client data can be attached to all requests + this.cmcdManager_ = this.createCmcd_(); + this.networkingEngine_ = this.createNetworkingEngine(); this.networkingEngine_.setForceHTTPS(this.config_.streaming.forceHTTPS); @@ -1304,9 +1308,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget { // before we allow calls to |updateStateHistory|. this.stats_ = new shaka.util.Stats(); - // Create the CMCD manager so client data can be attached to all requests - this.cmcdManager_ = this.createCmcd_(); - // Load's request is a little different, so we can't use our normal // listeners-to-promise method. It is the only request where we may skip the // request, so we need to set the on skip callback to reject with a specific @@ -1862,12 +1863,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget { const playerInterface = { networkingEngine: networkingEngine, - modifyManifestRequest: (request, manifestInfo) => { - this.cmcdManager_.applyManifestData(request, manifestInfo); - }, - modifySegmentRequest: (request, segmentInfo) => { - this.cmcdManager_.applySegmentData(request, segmentInfo); - }, filter: (manifest) => this.filterManifest_(manifest), makeTextStreamsForClosedCaptions: (manifest) => { return this.makeTextStreamsForClosedCaptions_(manifest); @@ -2885,9 +2880,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget { .set('aborted', aborted); this.dispatchEvent(this.makeEvent_(name, data)); }; + /** @type {shaka.net.NetworkingEngine.OnRequest} */ + const onRequest_ = (type, request, context) => { + this.cmcdManager_.applyData(type, request, context); + }; return new shaka.net.NetworkingEngine( - onProgressUpdated_, onHeadersReceived_, onDownloadFailed_); + onProgressUpdated_, onHeadersReceived_, onDownloadFailed_, + this.config_.cmcd.enabled ? onRequest_ : undefined); } /** @@ -3089,8 +3089,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget { this.abrManager_.getBandwidthEstimate() : NaN, getBufferedInfo: () => this.getBufferedInfo(), getCurrentTime: () => this.video_ ? this.video_.currentTime : 0, - getVariantTracks: () => this.getVariantTracks(), getPlaybackRate: () => this.getPlaybackRate(), + getNetworkingEngine: () => this.getNetworkingEngine(), + getVariantTracks: () => this.getVariantTracks(), isLive: () => this.isLive(), }; @@ -3105,17 +3106,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget { */ createStreamingEngine() { goog.asserts.assert( - this.abrManager_ && this.mediaSourceEngine_ && - this.cmcdManager_ && this.manifest_, + this.abrManager_ && this.mediaSourceEngine_ && this.manifest_, 'Must not be destroyed'); /** @type {shaka.media.StreamingEngine.PlayerInterface} */ const playerInterface = { getPresentationTime: () => this.playhead_ ? this.playhead_.getTime() : 0, getBandwidthEstimate: () => this.abrManager_.getBandwidthEstimate(), - modifySegmentRequest: (request, segmentInfo) => { - this.cmcdManager_.applySegmentData(request, segmentInfo); - }, mediaSourceEngine: this.mediaSourceEngine_, netEngine: this.networkingEngine_, onError: (error) => this.onError_(error), diff --git a/lib/util/cmcd_manager.js b/lib/util/cmcd_manager.js index 32d092437b..2d26728b20 100644 --- a/lib/util/cmcd_manager.js +++ b/lib/util/cmcd_manager.js @@ -8,7 +8,7 @@ goog.provide('shaka.util.CmcdManager'); goog.require('goog.Uri'); goog.require('shaka.log'); - +goog.require('shaka.net.NetworkingEngine'); /** * @summary @@ -74,21 +74,67 @@ shaka.util.CmcdManager = class { this.buffering_ = buffering; } + /** + * Apply CMCD data to a request. + * + * @param {!shaka.net.NetworkingEngine.RequestType} type + * The request type + * @param {!shaka.extern.Request} request + * The request to apply CMCD data to + * @param {shaka.net.NetworkingEngine.RequestContext=} context + * The request context + */ + applyData(type, request, context = {}) { + if (!this.config_.enabled) { + return; + } + + if (request.method === 'HEAD') { + this.apply_(request); + return; + } + + const RequestType = shaka.net.NetworkingEngine.RequestType; + const ObjectType = shaka.util.CmcdManager.ObjectType; + + switch (type) { + case RequestType.MANIFEST: + this.applyManifestData(request, context); + break; + + case RequestType.SEGMENT: + this.applySegmentData(request, context); + break; + + case RequestType.LICENSE: + case RequestType.SERVER_CERTIFICATE: + case RequestType.KEY: + this.apply_(request, {ot: ObjectType.KEY}); + break; + + case RequestType.TIMING: + this.apply_(request, {ot: ObjectType.OTHER}); + break; + } + } + /** * Apply CMCD data to a manifest request. * * @param {!shaka.extern.Request} request * The request to apply CMCD data to - * @param {shaka.util.CmcdManager.ManifestInfo} manifestInfo - * The manifest format + * @param {shaka.net.NetworkingEngine.RequestContext} context + * The request context */ - applyManifestData(request, manifestInfo) { + applyManifestData(request, context) { try { if (!this.config_.enabled) { return; } - this.sf_ = manifestInfo.format; + if (context.type) { + this.sf_ = this.getStreamFormat_(context.type); + } this.apply_(request, { ot: shaka.util.CmcdManager.ObjectType.MANIFEST, @@ -104,33 +150,44 @@ shaka.util.CmcdManager = class { * Apply CMCD data to a segment request * * @param {!shaka.extern.Request} request - * @param {shaka.util.CmcdManager.SegmentInfo} segmentInfo + * @param {shaka.net.NetworkingEngine.RequestContext} context + * The request context */ - applySegmentData(request, segmentInfo) { + applySegmentData(request, context) { try { if (!this.config_.enabled) { return; } + const segment = context.segment; + + let duration = 0; + if (segment) { + duration = segment.endTime - segment.startTime; + } + const data = { - d: segmentInfo.duration * 1000, + d: duration * 1000, st: this.getStreamType_(), }; - data.ot = this.getObjectType_(segmentInfo); + data.ot = this.getObjectType_(context); const ObjectType = shaka.util.CmcdManager.ObjectType; const isMedia = data.ot === ObjectType.VIDEO || - data.ot === ObjectType.AUDIO || - data.ot === ObjectType.MUXED || - data.ot === ObjectType.TIMED_TEXT; - - if (isMedia) { - data.bl = this.getBufferLength_(segmentInfo.type); - } - - if (segmentInfo.bandwidth) { - data.br = segmentInfo.bandwidth / 1000; + data.ot === ObjectType.AUDIO || + data.ot === ObjectType.MUXED || + data.ot === ObjectType.TIMED_TEXT; + + const stream = context.stream; + if (stream) { + if (isMedia) { + data.bl = this.getBufferLength_(stream.type); + } + + if (stream.bandwidth) { + data.br = stream.bandwidth / 1000; + } } if (isMedia && data.ot !== ObjectType.TIMED_TEXT) { @@ -256,7 +313,7 @@ shaka.util.CmcdManager = class { data.pr = this.playerInterface_.getPlaybackRate(); const isVideo = data.ot === shaka.util.CmcdManager.ObjectType.VIDEO || - data.ot === shaka.util.CmcdManager.ObjectType.MUXED; + data.ot === shaka.util.CmcdManager.ObjectType.MUXED; if (this.starved_ && isVideo) { data.bs = true; @@ -292,18 +349,26 @@ shaka.util.CmcdManager = class { /** * The CMCD object type. * - * @param {shaka.util.CmcdManager.SegmentInfo} segmentInfo + * @param {shaka.net.NetworkingEngine.RequestContext} context + * The request context * @private */ - getObjectType_(segmentInfo) { - const type = segmentInfo.type; - - if (segmentInfo.init) { + getObjectType_(context) { + if (context.type === + shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT) { return shaka.util.CmcdManager.ObjectType.INIT; } + const stream = context.stream; + + if (!stream) { + return undefined; + } + + const type = stream.type; + if (type == 'video') { - if (segmentInfo.codecs.includes(',')) { + if (stream.codecs && stream.codecs.includes(',')) { return shaka.util.CmcdManager.ObjectType.MUXED; } return shaka.util.CmcdManager.ObjectType.VIDEO; @@ -314,7 +379,7 @@ shaka.util.CmcdManager = class { } if (type == 'text') { - if (segmentInfo.mimeType === 'application/mp4') { + if (stream.mimeType === 'application/mp4') { return shaka.util.CmcdManager.ObjectType.TIMED_TEXT; } return shaka.util.CmcdManager.ObjectType.CAPTION; @@ -368,6 +433,32 @@ shaka.util.CmcdManager = class { return (range.end - start) * 1000; } + /** + * Get the stream format + * + * @param {shaka.net.NetworkingEngine.AdvancedRequestType} type + * The request's advanced type + * @return {(shaka.util.CmcdManager.StreamingFormat|undefined)} + * @private + */ + getStreamFormat_(type) { + const AdvancedRequestType = shaka.net.NetworkingEngine.AdvancedRequestType; + + switch (type) { + case AdvancedRequestType.MPD: + return shaka.util.CmcdManager.StreamingFormat.DASH; + + case AdvancedRequestType.MASTER_PLAYLIST: + case AdvancedRequestType.MEDIA_PLAYLIST: + return shaka.util.CmcdManager.StreamingFormat.HLS; + + case AdvancedRequestType.MSS: + return shaka.util.CmcdManager.StreamingFormat.SMOOTH; + } + + return undefined; + } + /** * Get the stream type * @@ -566,8 +657,8 @@ shaka.util.CmcdManager = class { * getBandwidthEstimate: function():number, * getBufferedInfo: function():shaka.extern.BufferedInfo, * getCurrentTime: function():number, - * getVariantTracks: function():Array., * getPlaybackRate: function():number, + * getVariantTracks: function():Array., * isLive: function():boolean * }} * @@ -577,57 +668,16 @@ shaka.util.CmcdManager = class { * Get information about what the player has buffered. * @property {function():number} getCurrentTime * Get the current time - * @property {function():Array.} getVariantTracks - * Get the variant tracks * @property {function():number} getPlaybackRate * Get the playback rate + * @property {function():Array.} getVariantTracks + * Get the variant tracks * @property {function():boolean} isLive * Get if the player is playing live content. */ shaka.util.CmcdManager.PlayerInterface; -/** - * @typedef {{ - * type: string, - * init: boolean, - * duration: number, - * mimeType: string, - * codecs: string, - * bandwidth: (number|undefined) - * }} - * - * @property {string} type - * The media type - * @property {boolean} init - * Flag indicating whether the segment is an init segment - * @property {number} duration - * The duration of the segment in seconds - * @property {string} mimeType - * The segment's mime type - * @property {string} codecs - * The segment's codecs - * @property {(number|undefined)} bandwidth - * The segment's variation bandwidth - * - * @export - */ -shaka.util.CmcdManager.SegmentInfo; - - -/** - * @typedef {{ - * format: shaka.util.CmcdManager.StreamingFormat - * }} - * - * @property {shaka.util.CmcdManager.StreamingFormat} format - * The manifest's stream format - * - * @export - */ -shaka.util.CmcdManager.ManifestInfo; - - /** * @enum {string} */ diff --git a/lib/util/fairplay_utils.js b/lib/util/fairplay_utils.js index 22661f2b5b..e83b4e3443 100644 --- a/lib/util/fairplay_utils.js +++ b/lib/util/fairplay_utils.js @@ -233,10 +233,10 @@ shaka.util.FairPlayUtils = class { * * @param {shaka.net.NetworkingEngine.RequestType} type * @param {shaka.extern.Request} request - * @param {shaka.net.NetworkingEngine.AdvancedRequestType=} advType + * @param {shaka.net.NetworkingEngine.RequestContext=} context * @export */ - static verimatrixFairPlayRequest(type, request, advType) { + static verimatrixFairPlayRequest(type, request, context) { if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) { return; } @@ -252,10 +252,10 @@ shaka.util.FairPlayUtils = class { * * @param {shaka.net.NetworkingEngine.RequestType} type * @param {shaka.extern.Request} request - * @param {shaka.net.NetworkingEngine.AdvancedRequestType=} advType + * @param {shaka.net.NetworkingEngine.RequestContext=} context * @private */ - static octetStreamFairPlayRequest_(type, request, advType) { + static octetStreamFairPlayRequest_(type, request, context) { if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) { return; } @@ -267,10 +267,10 @@ shaka.util.FairPlayUtils = class { * * @param {shaka.net.NetworkingEngine.RequestType} type * @param {shaka.extern.Request} request - * @param {shaka.net.NetworkingEngine.AdvancedRequestType=} advType + * @param {shaka.net.NetworkingEngine.RequestContext=} context * @export */ - static ezdrmFairPlayRequest(type, request, advType) { + static ezdrmFairPlayRequest(type, request, context) { shaka.util.FairPlayUtils.octetStreamFairPlayRequest_(type, request); } @@ -279,10 +279,10 @@ shaka.util.FairPlayUtils = class { * * @param {shaka.net.NetworkingEngine.RequestType} type * @param {shaka.extern.Request} request - * @param {shaka.net.NetworkingEngine.AdvancedRequestType=} advType + * @param {shaka.net.NetworkingEngine.RequestContext=} context * @export */ - static conaxFairPlayRequest(type, request, advType) { + static conaxFairPlayRequest(type, request, context) { shaka.util.FairPlayUtils.octetStreamFairPlayRequest_(type, request); } @@ -291,10 +291,10 @@ shaka.util.FairPlayUtils = class { * * @param {shaka.net.NetworkingEngine.RequestType} type * @param {shaka.extern.Request} request - * @param {shaka.net.NetworkingEngine.AdvancedRequestType=} advType + * @param {shaka.net.NetworkingEngine.RequestContext=} context * @export */ - static expressplayFairPlayRequest(type, request, advType) { + static expressplayFairPlayRequest(type, request, context) { shaka.util.FairPlayUtils.octetStreamFairPlayRequest_(type, request); } @@ -303,10 +303,10 @@ shaka.util.FairPlayUtils = class { * * @param {shaka.net.NetworkingEngine.RequestType} type * @param {shaka.extern.Response} response - * @param {shaka.net.NetworkingEngine.AdvancedRequestType=} advType + * @param {shaka.net.NetworkingEngine.RequestContext=} context * @export */ - static commonFairPlayResponse(type, response, advType) { + static commonFairPlayResponse(type, response, context) { if (type !== shaka.net.NetworkingEngine.RequestType.LICENSE) { return; } diff --git a/test/dash/dash_parser_live_unit.js b/test/dash/dash_parser_live_unit.js index 1fe4ac86e8..26ca68b9d7 100644 --- a/test/dash/dash_parser_live_unit.js +++ b/test/dash/dash_parser_live_unit.js @@ -26,8 +26,6 @@ describe('DashParser Live', () => { parser.configure(shaka.util.PlayerConfiguration.createDefault().manifest); playerInterface = { networkingEngine: fakeNetEngine, - modifyManifestRequest: (request, manifestInfo) => {}, - modifySegmentRequest: (request, segmentInfo) => {}, filter: (manifest) => Promise.resolve(), makeTextStreamsForClosedCaptions: (manifest) => {}, onTimelineRegionAdded: fail, // Should not have any EventStream elements. @@ -708,19 +706,21 @@ describe('DashParser Live', () => { fakeNetEngine.setResponseText('dummy://foo', manifestText); const manifestRequest = shaka.net.NetworkingEngine.RequestType.MANIFEST; - const manifestAdv = shaka.net.NetworkingEngine.AdvancedRequestType.MPD; + const manifestContext = { + type: shaka.net.NetworkingEngine.AdvancedRequestType.MPD, + }; await parser.start('dummy://foo', playerInterface); expect(fakeNetEngine.request).toHaveBeenCalledTimes(1); - fakeNetEngine.expectRequest('dummy://foo', manifestRequest, manifestAdv); + fakeNetEngine.expectRequest('dummy://foo', manifestRequest, manifestContext); fakeNetEngine.request.calls.reset(); // Create a mock so we can verify it gives two URIs. // The third location is a relative url, and should be resolved as an // absolute url. - fakeNetEngine.request.and.callFake((type, request, advType) => { + fakeNetEngine.request.and.callFake((type, request, context) => { expect(type).toBe(manifestRequest); - expect(advType).toBe(manifestAdv); + expect(context).toEqual(manifestContext); expect(request.uris).toEqual( ['http://foobar', 'http://foobar2', 'dummy://foo/foobar3']); const data = shaka.util.StringUtils.toUTF8(manifestText); @@ -953,7 +953,9 @@ describe('DashParser Live', () => { describe('stop', () => { const manifestRequestType = shaka.net.NetworkingEngine.RequestType.MANIFEST; - const manifestAdvType = shaka.net.NetworkingEngine.AdvancedRequestType.MPD; + const manifestContext = { + type: shaka.net.NetworkingEngine.AdvancedRequestType.MPD, + }; const dateRequestType = shaka.net.NetworkingEngine.RequestType.TIMING; const manifestUri = 'dummy://foo'; const dateUri = 'http://foo.bar/date'; @@ -986,7 +988,7 @@ describe('DashParser Live', () => { await parser.start(manifestUri, playerInterface); fakeNetEngine.expectRequest( - manifestUri, manifestRequestType, manifestAdvType); + manifestUri, manifestRequestType, manifestContext); fakeNetEngine.request.calls.reset(); parser.stop(); @@ -1005,7 +1007,7 @@ describe('DashParser Live', () => { await expectation; fakeNetEngine.expectRequest( - manifestUri, manifestRequestType, manifestAdvType); + manifestUri, manifestRequestType, manifestContext); fakeNetEngine.request.calls.reset(); await updateManifest(); // An update should not occur. @@ -1017,7 +1019,7 @@ describe('DashParser Live', () => { expect(manifest).toBeTruthy(); fakeNetEngine.expectRequest( - manifestUri, manifestRequestType, manifestAdvType); + manifestUri, manifestRequestType, manifestContext); fakeNetEngine.request.calls.reset(); /** @type {!shaka.util.PublicPromise} */ const delay = fakeNetEngine.delayNextRequest(); @@ -1026,7 +1028,7 @@ describe('DashParser Live', () => { // The request was made but should not be resolved yet. expect(fakeNetEngine.request).toHaveBeenCalledTimes(1); fakeNetEngine.expectRequest( - manifestUri, manifestRequestType, manifestAdvType); + manifestUri, manifestRequestType, manifestContext); fakeNetEngine.request.calls.reset(); parser.stop(); delay.resolve(); @@ -1048,7 +1050,7 @@ describe('DashParser Live', () => { // This is the initial manifest request. expect(fakeNetEngine.request).toHaveBeenCalledTimes(1); fakeNetEngine.expectRequest( - manifestUri, manifestRequestType, manifestAdvType); + manifestUri, manifestRequestType, manifestContext); fakeNetEngine.request.calls.reset(); // Resolve the manifest request and wait on the UTCTiming request. delay.resolve(); diff --git a/test/dash/dash_parser_manifest_unit.js b/test/dash/dash_parser_manifest_unit.js index 7c2b63a2f5..b261e2bdc2 100644 --- a/test/dash/dash_parser_manifest_unit.js +++ b/test/dash/dash_parser_manifest_unit.js @@ -727,7 +727,7 @@ describe('DashParser Manifest', () => { ' value="http://foo.bar/date" />', ]); - fakeNetEngine.request.and.callFake((type, request, advType) => { + fakeNetEngine.request.and.callFake((type, request, context) => { if (request.uris[0] == 'http://foo.bar/manifest') { const data = shaka.util.StringUtils.toUTF8(source); return shaka.util.AbortableOperation.completed({ diff --git a/test/hls/hls_live_unit.js b/test/hls/hls_live_unit.js index ae51076335..a1bbb63460 100644 --- a/test/hls/hls_live_unit.js +++ b/test/hls/hls_live_unit.js @@ -66,8 +66,6 @@ describe('HlsParser live', () => { config = shaka.util.PlayerConfiguration.createDefault().manifest; playerInterface = { - modifyManifestRequest: (request, manifestInfo) => {}, - modifySegmentRequest: (request, segmentInfo) => {}, filter: () => Promise.resolve(), makeTextStreamsForClosedCaptions: (manifest) => {}, networkingEngine: fakeNetEngine, @@ -373,10 +371,13 @@ describe('HlsParser live', () => { manifest, mediaWithAdditionalSegment + '#EXT-X-ENDLIST\n'); // We saw one request for the video playlist, which signalled "ENDLIST". + const type = + shaka.net.NetworkingEngine.AdvancedRequestType.MASTER_PLAYLIST; + fakeNetEngine.expectRequest( 'test:/video', shaka.net.NetworkingEngine.RequestType.MANIFEST, - shaka.net.NetworkingEngine.AdvancedRequestType.MASTER_PLAYLIST); + {type}); expect(manifest.presentationTimeline.isLive()).toBe(false); fakeNetEngine.request.calls.reset(); @@ -842,10 +843,12 @@ describe('HlsParser live', () => { // Only one request was made, and it was for the playlist. // No segment requests were needed to get the start time. expect(fakeNetEngine.request).toHaveBeenCalledTimes(1); + const type = + shaka.net.NetworkingEngine.AdvancedRequestType.MASTER_PLAYLIST; fakeNetEngine.expectRequest( 'test:/video', shaka.net.NetworkingEngine.RequestType.MANIFEST, - shaka.net.NetworkingEngine.AdvancedRequestType.MASTER_PLAYLIST); + {type}); }); it('request playlist delta updates to skip segments', async () => { @@ -888,7 +891,8 @@ describe('HlsParser live', () => { fakeNetEngine.expectRequest( 'test:/video?_HLS_skip=YES', shaka.net.NetworkingEngine.RequestType.MANIFEST, - shaka.net.NetworkingEngine.AdvancedRequestType.MASTER_PLAYLIST); + {type: + shaka.net.NetworkingEngine.AdvancedRequestType.MASTER_PLAYLIST}); }); it('skips older segments', async () => { diff --git a/test/media/drm_engine_unit.js b/test/media/drm_engine_unit.js index 729ac68484..f34c70deb1 100644 --- a/test/media/drm_engine_unit.js +++ b/test/media/drm_engine_unit.js @@ -1514,7 +1514,7 @@ describe('DrmEngine', () => { // Not mocked. Run data through real data URI parser to ensure that it is // correctly formatted. - fakeNetEngine.request.and.callFake((type, request, advType) => { + fakeNetEngine.request.and.callFake((type, request, context) => { const requestType = shaka.net.NetworkingEngine.RequestType.LICENSE; // A dummy progress callback. diff --git a/test/media/streaming_engine_integration.js b/test/media/streaming_engine_integration.js index beae0db6eb..fcfe98c8ed 100644 --- a/test/media/streaming_engine_integration.js +++ b/test/media/streaming_engine_integration.js @@ -252,7 +252,6 @@ describe('StreamingEngine', () => { function createStreamingEngine() { const playerInterface = { - modifySegmentRequest: (request, segmentInfo) => {}, getPresentationTime: () => playhead.getTime(), getBandwidthEstimate: () => 1e6, mediaSourceEngine: mediaSourceEngine, @@ -357,12 +356,13 @@ describe('StreamingEngine', () => { await waiter.timeoutAfter(60).waitUntilPlayheadReaches(video, 305); const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT; - const segmentAdvType = - shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT; + const segmentContext = { + type: shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT, + }; // firstSegmentNumber = // [(segmentAvailabilityEnd - rebufferingGoal) / segmentDuration] + 1 - netEngine.expectRequest('0_video_29', segmentType, segmentAdvType); - netEngine.expectRequest('0_audio_29', segmentType, segmentAdvType); + netEngine.expectRequest('0_video_29', segmentType, segmentContext); + netEngine.expectRequest('0_audio_29', segmentType, segmentContext); }); it('can handle seeks ahead of availability window', async () => { diff --git a/test/media/streaming_engine_unit.js b/test/media/streaming_engine_unit.js index a9f4d68741..e6100ffaff 100644 --- a/test/media/streaming_engine_unit.js +++ b/test/media/streaming_engine_unit.js @@ -453,7 +453,6 @@ describe('StreamingEngine', () => { presentationTimeInSeconds != undefined, 'All tests should have defined an initial presentation time by now!'); const playerInterface = { - modifySegmentRequest: (request, segmentInfo) => {}, getPresentationTime: () => presentationTimeInSeconds, getBandwidthEstimate: Util.spyFunc(getBandwidthEstimate), mediaSourceEngine: mediaSourceEngine, @@ -568,24 +567,25 @@ describe('StreamingEngine', () => { /* isInit= */ true); const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT; - const segmentAdvType = - shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT; + const segmentContext = { + type: shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT, + }; - netEngine.expectRequest('0_audio_0', segmentType, segmentAdvType); - netEngine.expectRequest('0_video_0', segmentType, segmentAdvType); - netEngine.expectRequest('0_text_0', segmentType, segmentAdvType); + netEngine.expectRequest('0_audio_0', segmentType, segmentContext); + netEngine.expectRequest('0_video_0', segmentType, segmentContext); + netEngine.expectRequest('0_text_0', segmentType, segmentContext); - netEngine.expectRequest('0_audio_1', segmentType, segmentAdvType); - netEngine.expectRequest('0_video_1', segmentType, segmentAdvType); - netEngine.expectRequest('0_text_1', segmentType, segmentAdvType); + netEngine.expectRequest('0_audio_1', segmentType, segmentContext); + netEngine.expectRequest('0_video_1', segmentType, segmentContext); + netEngine.expectRequest('0_text_1', segmentType, segmentContext); - netEngine.expectRequest('1_audio_2', segmentType, segmentAdvType); - netEngine.expectRequest('1_video_2', segmentType, segmentAdvType); - netEngine.expectRequest('1_text_2', segmentType, segmentAdvType); + netEngine.expectRequest('1_audio_2', segmentType, segmentContext); + netEngine.expectRequest('1_video_2', segmentType, segmentContext); + netEngine.expectRequest('1_text_2', segmentType, segmentContext); - netEngine.expectRequest('1_audio_3', segmentType, segmentAdvType); - netEngine.expectRequest('1_video_3', segmentType, segmentAdvType); - netEngine.expectRequest('1_text_3', segmentType, segmentAdvType); + netEngine.expectRequest('1_audio_3', segmentType, segmentContext); + netEngine.expectRequest('1_video_3', segmentType, segmentContext); + netEngine.expectRequest('1_text_3', segmentType, segmentContext); }); describe('unloadTextStream', () => { @@ -603,18 +603,19 @@ describe('StreamingEngine', () => { // is sent. await runTest(() => { const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT; - const segmentAdvType = - shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT; + const segmentContext = { + type: shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT, + }; if (presentationTimeInSeconds == 1) { - netEngine.expectRequest('0_text_0', segmentType, segmentAdvType); + netEngine.expectRequest('0_text_0', segmentType, segmentContext); netEngine.request.calls.reset(); streamingEngine.unloadTextStream(); } else if (presentationTimeInSeconds == 35) { - netEngine.expectNoRequest('0_text_0', segmentType, segmentAdvType); - netEngine.expectNoRequest('0_text_1', segmentType, segmentAdvType); - netEngine.expectNoRequest('1_text_2', segmentType, segmentAdvType); - netEngine.expectNoRequest('1_text_3', segmentType, segmentAdvType); + netEngine.expectNoRequest('0_text_0', segmentType, segmentContext); + netEngine.expectNoRequest('0_text_1', segmentType, segmentContext); + netEngine.expectNoRequest('1_text_2', segmentType, segmentContext); + netEngine.expectNoRequest('1_text_3', segmentType, segmentContext); } }); }); @@ -866,24 +867,24 @@ describe('StreamingEngine', () => { expect(mediaSourceEngine.endOfStream).toHaveBeenCalled(); const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT; - const segmentAdvType = - shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT; - - netEngine.expectRequest('0_audio_0', segmentType, segmentAdvType); - netEngine.expectRequest('0_video_0', segmentType, segmentAdvType); - netEngine.expectRequest('0_text_0', segmentType, segmentAdvType); + const segmentContext = { + type: shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT, + }; + netEngine.expectRequest('0_audio_0', segmentType, segmentContext); + netEngine.expectRequest('0_video_0', segmentType, segmentContext); + netEngine.expectRequest('0_text_0', segmentType, segmentContext); - netEngine.expectRequest('0_audio_1', segmentType, segmentAdvType); - netEngine.expectRequest('0_video_1', segmentType, segmentAdvType); - netEngine.expectRequest('0_text_1', segmentType, segmentAdvType); + netEngine.expectRequest('0_audio_1', segmentType, segmentContext); + netEngine.expectRequest('0_video_1', segmentType, segmentContext); + netEngine.expectRequest('0_text_1', segmentType, segmentContext); - netEngine.expectRequest('1_audio_2', segmentType, segmentAdvType); - netEngine.expectRequest('1_video_2', segmentType, segmentAdvType); - netEngine.expectRequest('1_text_2', segmentType, segmentAdvType); + netEngine.expectRequest('1_audio_2', segmentType, segmentContext); + netEngine.expectRequest('1_video_2', segmentType, segmentContext); + netEngine.expectRequest('1_text_2', segmentType, segmentContext); - netEngine.expectNoRequest('1_audio_3', segmentType, segmentAdvType); - netEngine.expectNoRequest('1_video_3', segmentType, segmentAdvType); - netEngine.expectNoRequest('1_text_3', segmentType, segmentAdvType); + netEngine.expectNoRequest('1_audio_3', segmentType, segmentContext); + netEngine.expectNoRequest('1_video_3', segmentType, segmentContext); + netEngine.expectNoRequest('1_text_3', segmentType, segmentContext); }); it('does not buffer one media type ahead of another', async () => { @@ -1158,12 +1159,14 @@ describe('StreamingEngine', () => { await Util.fakeEventLoop(5); const segmentType = shaka.net.NetworkingEngine.RequestType.SEGMENT; - const segmentAdvType = - shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT; + const segmentContext = { + type: shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT, + }; + // Quickly switching back to text1, and text init segment should be // fetched again. - netEngine.expectRequest('text-20-init', segmentType, segmentAdvType); - netEngine.expectNoRequest('text-21-init', segmentType, segmentAdvType); + netEngine.expectRequest('text-20-init', segmentType, segmentContext); + netEngine.expectNoRequest('text-21-init', segmentType, segmentContext); // TODO: huh? }); }); @@ -3100,7 +3103,7 @@ describe('StreamingEngine', () => { // For these tests, we don't care about specific data appended. // Just return any old ArrayBuffer for any requested segment. netEngine = new shaka.test.FakeNetworkingEngine(); - netEngine.request.and.callFake((requestType, request, advType) => { + netEngine.request.and.callFake((requestType, request, context) => { const buffer = new ArrayBuffer(0); const response = {uri: request.uris[0], data: buffer, headers: {}}; const bytes = new shaka.net.NetworkingEngine.NumBytesRemainingClass(); @@ -3422,7 +3425,7 @@ describe('StreamingEngine', () => { // For these tests, we don't care about specific data appended. // Just return any old ArrayBuffer for any requested segment. - netEngine.request.and.callFake((requestType, request, advType) => { + netEngine.request.and.callFake((requestType, request, context) => { const buffer = new ArrayBuffer(0); /** @type {shaka.extern.Response} */ const response = {uri: request.uris[0], data: buffer, headers: {}}; @@ -3662,7 +3665,7 @@ describe('StreamingEngine', () => { let isRequested = false; let isAborted = false; - netEngine.request.and.callFake((requestType, request, advType) => { + netEngine.request.and.callFake((requestType, request, context) => { isRequested = true; const abortOp = () => { @@ -3838,14 +3841,15 @@ describe('StreamingEngine', () => { '1_audio_3', '1_video_3', ]; - const segmentAdvType = - shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT; + const context = { + type: shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT, + }; for (const request of requests) { if (hasRequest) { - netEngine.expectRequest(request, segmentType, segmentAdvType); + netEngine.expectRequest(request, segmentType, context); } else { - netEngine.expectNoRequest(request, segmentType, segmentAdvType); + netEngine.expectNoRequest(request, segmentType, context); } } } diff --git a/test/test/util/fake_networking_engine.js b/test/test/util/fake_networking_engine.js index 370bc64762..632687ef71 100644 --- a/test/test/util/fake_networking_engine.js +++ b/test/test/util/fake_networking_engine.js @@ -174,11 +174,11 @@ shaka.test.FakeNetworkingEngine = class { * * @param {string} uri * @param {shaka.net.NetworkingEngine.RequestType} type - * @param {shaka.net.NetworkingEngine.AdvancedRequestType=} advType + * @param {shaka.net.NetworkingEngine.RequestContext=} context */ - expectRequest(uri, type, advType) { + expectRequest(uri, type, context) { shaka.test.FakeNetworkingEngine.expectRequest( - this.request, uri, type, advType); + this.request, uri, type, context); } /** @@ -186,11 +186,11 @@ shaka.test.FakeNetworkingEngine = class { * * @param {string} uri * @param {shaka.net.NetworkingEngine.RequestType} type - * @param {shaka.net.NetworkingEngine.AdvancedRequestType=} advType + * @param {shaka.net.NetworkingEngine.RequestContext=} context */ - expectNoRequest(uri, type, advType) { + expectNoRequest(uri, type, context) { shaka.test.FakeNetworkingEngine.expectNoRequest( - this.request, uri, type, advType); + this.request, uri, type, context); } /** @@ -294,13 +294,14 @@ shaka.test.FakeNetworkingEngine = class { * @param {!Object} requestSpy * @param {string} uri * @param {shaka.net.NetworkingEngine.RequestType} type - * @param {shaka.net.NetworkingEngine.AdvancedRequestType=} advType + * @param {shaka.net.NetworkingEngine.RequestContext=} context */ - static expectRequest(requestSpy, uri, type, advType) { + static expectRequest(requestSpy, uri, type, context) { // Jasmine "toHaveBeenCalledWith" doesn't handle optional parameters well. - if (advType != undefined) { + if (context != undefined) { expect(requestSpy).toHaveBeenCalledWith( - type, jasmine.objectContaining({uris: [uri]}), advType); + type, jasmine.objectContaining({uris: [uri]}), + jasmine.objectContaining({type: context.type})); } else { expect(requestSpy).toHaveBeenCalledWith( type, jasmine.objectContaining({uris: [uri]})); @@ -313,13 +314,14 @@ shaka.test.FakeNetworkingEngine = class { * @param {!Object} requestSpy * @param {string} uri * @param {shaka.net.NetworkingEngine.RequestType} type - * @param {shaka.net.NetworkingEngine.AdvancedRequestType=} advType + * @param {shaka.net.NetworkingEngine.RequestContext=} context */ - static expectNoRequest(requestSpy, uri, type, advType) { + static expectNoRequest(requestSpy, uri, type, context) { // Jasmine "toHaveBeenCalledWith" doesn't handle optional parameters well. - if (advType != undefined) { + if (context != undefined) { expect(requestSpy).not.toHaveBeenCalledWith( - type, jasmine.objectContaining({uris: [uri]}), advType); + type, jasmine.objectContaining({uris: [uri]}), + jasmine.objectContaining({type: context.type})); } else { expect(requestSpy).not.toHaveBeenCalledWith( type, jasmine.objectContaining({uris: [uri]})); @@ -347,7 +349,7 @@ shaka.test.FakeNetworkingEngine = class { headers['Range'] = range; } - const advType = isInit ? + const type = isInit ? shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT : shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT; @@ -357,7 +359,7 @@ shaka.test.FakeNetworkingEngine = class { uris: [uri], headers: headers, }), - advType); + jasmine.objectContaining({type})); } }; diff --git a/test/test/util/streaming_engine_util.js b/test/test/util/streaming_engine_util.js index d288e02b9c..4c95ccf627 100644 --- a/test/test/util/streaming_engine_util.js +++ b/test/test/util/streaming_engine_util.js @@ -28,7 +28,7 @@ shaka.test.StreamingEngineUtil = class { static createFakeNetworkingEngine(getInitSegment, getSegment, delays) { const netEngine = new shaka.test.FakeNetworkingEngine(); - netEngine.request.and.callFake((requestType, request, advType) => { + netEngine.request.and.callFake((requestType, request, context) => { expect(requestType).toBeTruthy(); expect(request.uris.length).toBe(1); diff --git a/test/util/cmcd_manager_unit.js b/test/util/cmcd_manager_unit.js index a338f7d877..704355a57d 100644 --- a/test/util/cmcd_manager_unit.js +++ b/test/util/cmcd_manager_unit.js @@ -67,9 +67,31 @@ describe('CmcdManager', () => { }); }); + const NetworkingEngine = shaka.net.NetworkingEngine; + const RequestType = NetworkingEngine.RequestType; + + function createNetworkingEngine(cmcd) { + const resolveScheme = jasmine.createSpy('cmcd').and.callFake( + () => shaka.util.AbortableOperation.completed( + {uri: '', data: new ArrayBuffer(5), headers: {}}, + )); + + NetworkingEngine.registerScheme( + 'cmcd', shaka.test.Util.spyFunc(resolveScheme), + NetworkingEngine.PluginPriority.FALLBACK); + + + /** @type {shaka.net.NetworkingEngine.OnRequest} */ + function onRequest(type, request, context) { + cmcd.applyData(type, request, context); + } + + return new NetworkingEngine(undefined, undefined, undefined, + onRequest); + } + describe('CmcdManager instance', () => { const ObjectUtils = shaka.util.ObjectUtils; - const playerInterface = { isLive: () => false, getBandwidthEstimate: () => 10000000, @@ -80,6 +102,8 @@ describe('CmcdManager', () => { {start: 35, end: 40}, ], }), + getCurrentTime: () => 10, + getPlaybackRate: () => 1, getVariantTracks: () => /** @type {Array.} */([ { type: 'variant', @@ -94,8 +118,6 @@ describe('CmcdManager', () => { audioBandWidth: 1000000, }, ]), - getPlaybackRate: () => 1, - getCurrentTime: () => 10, }; const sid = '2ed2d1cd-970b-48f2-bfb3-50a79e87cfa3'; @@ -126,18 +148,25 @@ describe('CmcdManager', () => { streamDataCallback: null, }; - const manifestInfo = { - format: shaka.util.CmcdManager.StreamingFormat.DASH, + const createContext = (type) => { + return { + type: type, + stream: /** @type {shaka.extern.Stream} */ ({ + bandwidth: 5234167, + codecs: 'avc1.42001e', + mimeType: 'application/mp4', + type: 'video', + }), + segment: /** @type {shaka.media.SegmentReference} */ ({ + startTime: 0, + endTime: 3.33, + }), + }; }; - const segmentInfo = { - type: 'video', - init: false, - duration: 3.33, - mimeType: 'application/mp4', - codecs: 'avc1.42001e', - bandwidth: 5234167, - }; + const AdvancedRequestType = NetworkingEngine.AdvancedRequestType; + const manifestInfo = createContext(AdvancedRequestType.MPD); + const segmentInfo = createContext(AdvancedRequestType.MEDIA_SEGMENT); describe('configuration', () => { it('does not modify requests when disabled', () => { @@ -286,6 +315,126 @@ describe('CmcdManager', () => { cmcdManager.applySegmentData(r, segmentInfo); expect(r.headers['CMCD-Request']).not.toContain(',su'); }); + + describe('applies core CMCD params to networking engine requests', () => { + let networkingEngine; + const uri = 'cmcd://foo'; + const retry = NetworkingEngine.defaultRetryParameters(); + + beforeAll(() => { + config.useHeaders = false; + cmcdManager = new CmcdManager(playerInterface, config); + networkingEngine = createNetworkingEngine(cmcdManager); + }); + + it('HEAD requests', async () => { + const request = NetworkingEngine.makeRequest([uri], retry); + request.method = 'HEAD'; + await networkingEngine.request(RequestType.MANIFEST, request); + + const result = request.uris[0]; + expect(result).toContain('?CMCD='); + expect(result).toContain(encodeURIComponent('sid="')); + expect(result).toContain(encodeURIComponent('cid="testing"')); + expect(result).not.toContain(encodeURIComponent('sf=')); + }); + + it('dash manifest requests', async () => { + const request = NetworkingEngine.makeRequest([uri], retry); + await networkingEngine.request(RequestType.MANIFEST, request, + {type: AdvancedRequestType.MPD}); + + const result = request.uris[0]; + expect(result).toContain(encodeURIComponent('ot=m')); + expect(result).toContain(encodeURIComponent('sf=d')); + }); + + it('hls manifest requests', async () => { + const request = NetworkingEngine.makeRequest([uri], retry); + await networkingEngine.request(RequestType.MANIFEST, request, + {type: AdvancedRequestType.MASTER_PLAYLIST}); + + const result = request.uris[0]; + expect(result).toContain(encodeURIComponent('ot=m')); + expect(result).toContain(encodeURIComponent('sf=h')); + }); + + it('hls playlist requests', async () => { + const request = NetworkingEngine.makeRequest([uri], retry); + await networkingEngine.request(RequestType.MANIFEST, request, + {type: AdvancedRequestType.MEDIA_PLAYLIST}); + + const result = request.uris[0]; + expect(result).toContain(encodeURIComponent('ot=m')); + expect(result).toContain(encodeURIComponent('sf=h')); + }); + + it('init segment requests', async () => { + const request = NetworkingEngine.makeRequest([uri], retry); + await networkingEngine.request(RequestType.SEGMENT, request, + {type: AdvancedRequestType.INIT_SEGMENT}); + + const result = request.uris[0]; + expect(result).toContain(encodeURIComponent('ot=i')); + }); + + it('media segment requests', async () => { + const request = NetworkingEngine.makeRequest([uri], retry); + await networkingEngine.request(RequestType.SEGMENT, request, + { + type: AdvancedRequestType.MEDIA_SEGMENT, + stream: { + type: 'video', + }, + }); + + const result = request.uris[0]; + expect(result).toContain(encodeURIComponent('ot=v')); + }); + + it('key requests', async () => { + const request = NetworkingEngine.makeRequest([uri], retry); + await networkingEngine.request(RequestType.KEY, request); + + const result = request.uris[0]; + expect(result).toContain(encodeURIComponent('ot=k')); + }); + + it('license requests', async () => { + const request = NetworkingEngine.makeRequest([uri], retry); + await networkingEngine.request(RequestType.LICENSE, request); + + const result = request.uris[0]; + expect(result).toContain(encodeURIComponent('ot=k')); + }); + + it('cert requests', async () => { + const request = NetworkingEngine.makeRequest([uri], retry); + await networkingEngine.request(RequestType.SERVER_CERTIFICATE, + request); + + const result = request.uris[0]; + expect(result).toContain(encodeURIComponent('ot=k')); + }); + + it('timing requests', async () => { + const request = NetworkingEngine.makeRequest([uri], retry); + await networkingEngine.request(RequestType.TIMING, request); + + const result = request.uris[0]; + expect(result).toContain(encodeURIComponent('ot=o')); + }); + + it('not when enabled is false', async () => { + config.enabled = false; + cmcdManager = new CmcdManager(playerInterface, config); + const request = NetworkingEngine.makeRequest([uri], retry); + await networkingEngine.request(RequestType.TIMING, request); + + const result = request.uris[0]; + expect(result).not.toContain('?CMCD='); + }); + }); }); }); });