UNPKG

shaka-player

Version:
363 lines (320 loc) 10 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.util.ContentSteeringManager'); goog.require('goog.Uri'); goog.require('shaka.media.ManifestParser'); goog.require('shaka.net.NetworkingEngine'); goog.require('shaka.util.Error'); goog.require('shaka.util.IDestroyable'); goog.require('shaka.util.ManifestParserUtils'); goog.require('shaka.util.OperationManager'); goog.require('shaka.util.StringUtils'); goog.require('shaka.util.Timer'); /** * Create a Content Steering manager. * * @implements {shaka.util.IDestroyable} */ shaka.util.ContentSteeringManager = class { /** * @param {shaka.extern.ManifestParser.PlayerInterface} playerInterface */ constructor(playerInterface) { /** @private {?shaka.extern.ManifestConfiguration} */ this.config_ = null; /** @private {?shaka.extern.ManifestParser.PlayerInterface} */ this.playerInterface_ = playerInterface; /** @private {!shaka.util.OperationManager} */ this.operationManager_ = new shaka.util.OperationManager(); /** @private {!Array<string>} */ this.baseUris_ = []; /** @private {?string} */ this.defaultPathwayId_ = null; /** @private {!Array<string>} */ this.pathwayPriority_ = []; /** @private {?string} */ this.lastPathwayUsed_ = null; /** @private {!Array<shaka.util.ContentSteeringManager.PathwayClone>} */ this.pathwayClones_ = []; /** * Default to 5 minutes. Value in seconds. * * @private {number} */ this.lastTTL_ = 300; /** @private {!Map<(string | number), !Map<string, string>>} */ this.locations_ = new Map(); /** @private {!Map<string, number>} */ this.bannedLocations_ = new Map(); /** @private {?shaka.util.Timer} */ this.updateTimer_ = null; /** @private {string} */ this.manifestType_ = shaka.media.ManifestParser.UNKNOWN; } /** * @param {shaka.extern.ManifestConfiguration} config */ configure(config) { this.config_ = config; } /** @override */ destroy() { this.config_ = null; this.playerInterface_ = null; this.baseUris_ = []; this.defaultPathwayId_ = null; this.pathwayPriority_ = []; this.pathwayClones_ = []; this.locations_.clear(); if (this.updateTimer_ != null) { this.updateTimer_.stop(); this.updateTimer_ = null; } return this.operationManager_.destroy(); } /** * @param {string} manifestType */ setManifestType(manifestType) { this.manifestType_ = manifestType; } /** * @param {!Array<string>} baseUris */ setBaseUris(baseUris) { this.baseUris_ = baseUris; } /** * @param {?string} defaultPathwayId */ setDefaultPathwayId(defaultPathwayId) { this.defaultPathwayId_ = defaultPathwayId; } /** * Request the Content Steering info. * * @param {string} uri * @return {!Promise} */ async requestInfo(uri) { const uris = shaka.util.ManifestParserUtils.resolveUris( this.baseUris_, [this.addQueryParams_(uri)]); const type = shaka.net.NetworkingEngine.RequestType.CONTENT_STEERING; const request = shaka.net.NetworkingEngine.makeRequest( uris, this.config_.retryParameters); const op = this.playerInterface_.networkingEngine.request(type, request); this.operationManager_.manage(op); try { const response = await op.promise; const str = shaka.util.StringUtils.fromUTF8(response.data); const steeringManifest = /** @type {shaka.util.ContentSteeringManager.SteeringManifest} */ (JSON.parse(str)); if (steeringManifest.VERSION == 1) { this.processManifest_(steeringManifest, response.uri); } } catch (e) { if (e && e.code == shaka.util.Error.Code.OPERATION_ABORTED) { return; } if (this.updateTimer_ != null) { this.updateTimer_.stop(); this.updateTimer_ = null; } this.updateTimer_ = new shaka.util.Timer(() => { this.requestInfo(uri); }); this.updateTimer_.tickAfter(this.lastTTL_); } } /** @private */ addQueryParams_(uri) { if (!this.pathwayPriority_.length) { return uri; } const finalUri = new goog.Uri(uri); const currentPathwayID = this.lastPathwayUsed_ || this.pathwayPriority_[0]; const currentBandwidth = Math.round(this.playerInterface_.getBandwidthEstimate()); const queryData = finalUri.getQueryData(); if (this.manifestType_ == shaka.media.ManifestParser.DASH) { queryData.add('_DASH_pathway', currentPathwayID); queryData.add('_DASH_throughput', String(currentBandwidth)); } else if (this.manifestType_ == shaka.media.ManifestParser.HLS) { queryData.add('_HLS_pathway', currentPathwayID); queryData.add('_HLS_throughput', String(currentBandwidth)); } if (queryData.getCount()) { finalUri.setQueryData(queryData); } return finalUri.toString(); } /** * @param {shaka.util.ContentSteeringManager.SteeringManifest} manifest * @param {string} finalManifestUri * @private */ processManifest_(manifest, finalManifestUri) { if (this.updateTimer_ != null) { this.updateTimer_.stop(); this.updateTimer_ = null; } const uri = manifest['RELOAD-URI'] || finalManifestUri; this.updateTimer_ = new shaka.util.Timer(() => { this.requestInfo(uri); }); const newTTL = manifest['TTL']; if (newTTL) { this.lastTTL_ = newTTL; } this.updateTimer_.tickAfter(this.lastTTL_); this.pathwayPriority_ = manifest['PATHWAY-PRIORITY'] || []; this.pathwayClones_ = manifest['PATHWAY-CLONES'] || []; } /** * Clear the previous locations added. */ clearPreviousLocations() { this.locations_.clear(); } /** * @param {string|number} streamId * @param {string} pathwayId * @param {string} uri */ addLocation(streamId, pathwayId, uri) { let streamLocations = this.locations_.get(streamId); if (!streamLocations) { streamLocations = new Map(); } streamLocations.set(pathwayId, uri); this.locations_.set(streamId, streamLocations); } /** * @param {string} uri */ banLocation(uri) { const bannedUntil = Date.now() + 60000; this.bannedLocations_.set(uri, bannedUntil); } /** * Get the base locations ordered according the priority. * * @param {string|number} streamId * @param {boolean=} ignoreBaseUrls * @return {!Array<string>} */ getLocations(streamId, ignoreBaseUrls = false) { const streamLocations = this.locations_.get(streamId) || new Map(); /** @type {!Array<!{pathwayId: string, location: string}>} */ let locationsPathwayIdMap = []; for (const pathwayId of this.pathwayPriority_) { const location = streamLocations.get(pathwayId); if (location) { locationsPathwayIdMap.push({pathwayId, location}); } else { const clone = this.pathwayClones_.find((c) => c.ID == pathwayId); if (clone) { const cloneLocation = streamLocations.get(clone['BASE-ID']); if (cloneLocation) { if (clone['URI-REPLACEMENT'].HOST) { const uri = new goog.Uri(cloneLocation); uri.setDomain(clone['URI-REPLACEMENT'].HOST); locationsPathwayIdMap.push({ pathwayId: pathwayId, location: uri.toString(), }); } else { locationsPathwayIdMap.push({ pathwayId: pathwayId, location: cloneLocation, }); } } } } } const now = Date.now(); for (const uri of this.bannedLocations_.keys()) { const bannedUntil = this.bannedLocations_.get(uri); if (now > bannedUntil) { this.bannedLocations_.delete(uri); } } locationsPathwayIdMap = locationsPathwayIdMap.filter((l) => { for (const uri of this.bannedLocations_.keys()) { if (uri.includes(new goog.Uri(l.location).getDomain())) { return false; } } return true; }); if (locationsPathwayIdMap.length) { this.lastPathwayUsed_ = locationsPathwayIdMap[0].pathwayId; } const locations = locationsPathwayIdMap.map((l) => l.location); if (!locations.length && this.defaultPathwayId_) { for (const pathwayId of this.defaultPathwayId_.split(',')) { const location = streamLocations.get(pathwayId); if (location) { this.lastPathwayUsed_ = this.defaultPathwayId_; locations.push(location); } } } if (!locations.length) { for (const location of streamLocations.values()) { locations.push(location); } } if (ignoreBaseUrls) { return locations; } return shaka.util.ManifestParserUtils.resolveUris( this.baseUris_, locations); } }; /** * @typedef {{ * VERSION: number, * TTL: number, * RELOAD-URI: string, * PATHWAY-PRIORITY: !Array<string>, * PATHWAY-CLONES: !Array<shaka.util.ContentSteeringManager.PathwayClone> * }} * * @description * Contains information about the Steering Manifest * * @property {string} VERSION * @property {number} TTL * @property {string} RELOAD-URI * @property {!Array<string>} PATHWAY-PRIORITY * @property {!Array< * shaka.util.ContentSteeringManager.PathwayClone>} PATHWAY-CLONES */ shaka.util.ContentSteeringManager.SteeringManifest; /** * @typedef {{ * BASE-ID: string, * ID: string, * URI-REPLACEMENT: !Array<shaka.util.ContentSteeringManager.UriReplacement> * }} * * @property {string} BASE-ID * @property {string} ID * @property {!Array< * shaka.util.ContentSteeringManager.UriReplacement>} URI-REPLACEMENT */ shaka.util.ContentSteeringManager.PathwayClone; /** * @typedef {{ * HOST: string * }} * * @property {string} HOST */ shaka.util.ContentSteeringManager.UriReplacement;