UNPKG

shaka-player

Version:
406 lines (351 loc) 14.1 kB
/** * @license * Copyright 2015 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ goog.provide('shaka.media.ManifestUpdater'); goog.require('shaka.media.ManifestInfo'); goog.require('shaka.media.PeriodInfo'); goog.require('shaka.media.StreamInfo'); goog.require('shaka.media.StreamInfoProcessor'); goog.require('shaka.util.TypedBind'); /** * Creates a ManifestUpdater. * This function takes ownership of |newManifestInfo|. * * @param {!shaka.media.ManifestInfo} newManifestInfo * @struct * @constructor */ shaka.media.ManifestUpdater = function(newManifestInfo) { /** @private {!shaka.media.ManifestInfo} */ this.newManifestInfo_ = newManifestInfo; }; /** * Destroys this ManifestUpdater. * @suppress {checkTypes} to set otherwise non-nullable types to null. */ shaka.media.ManifestUpdater.prototype.destroy = function() { this.newManifestInfo_.destroy(); this.newManifestInfo_ = null; }; /** * Updates |currentManifestInfo|. * * Although this function is asynchronous, all modifications to * |currentManifestInfo| occur synchronously immediately before the returned * promise resolves. * * @param {!shaka.media.ManifestInfo} currentManifestInfo * @return {!Promise.<!Array.<!shaka.media.StreamInfo>>} A promise to a list of * StreamInfos that were removed from |currentManifestInfo|. */ shaka.media.ManifestUpdater.prototype.update = function(currentManifestInfo) { // Alias. var ManifestUpdater = shaka.media.ManifestUpdater; // Pre-create all SegmentIndexes from both manifests. This enables us to // update synchronously and in-place safely, i.e., by modifying // |currentManifestInfo| directly. var promise1 = ManifestUpdater.createSegmentIndexes_(currentManifestInfo); var promise2 = ManifestUpdater.createSegmentIndexes_(this.newManifestInfo_); return Promise.all([promise1, promise2]).then(shaka.util.TypedBind(this, /** @param {!Array} results */ function(results) { /** @type {!Object.<number, !shaka.media.SegmentIndex>} */ var currentSegmentIndexesByUid = results[0]; /** @type {!Object.<number, !shaka.media.SegmentIndex>} */ var newSegmentIndexesByUid = results[1]; var processor = new shaka.media.StreamInfoProcessor(); processor.process(this.newManifestInfo_.periodInfos); currentManifestInfo.updatePeriod = this.newManifestInfo_.updatePeriod; currentManifestInfo.updateUrl = this.newManifestInfo_.updateUrl ? this.newManifestInfo_.updateUrl.clone() : null; currentManifestInfo.minBufferTime = this.newManifestInfo_.minBufferTime; /** @type {!Array.<!shaka.media.StreamInfo>} */ var removedStreamInfos = []; ManifestUpdater.mergePeriodInfos_( currentManifestInfo, this.newManifestInfo_, currentSegmentIndexesByUid, newSegmentIndexesByUid, removedStreamInfos); // Process |currentManifestInfo| to ensure that the StreamInfos // are still sorted by bandwidth. processor.process(currentManifestInfo.periodInfos); return Promise.resolve(removedStreamInfos); }) ); }; /** * Creates every SegmentIndex contained in |manifestInfo|. * * @param {!shaka.media.ManifestInfo} manifestInfo * @return {!Promise.<!Object.<number, !shaka.media.SegmentIndex>>} * @private */ shaka.media.ManifestUpdater.createSegmentIndexes_ = function( manifestInfo) { var gather = function(all, part) { return all.concat(part); }; var streamInfos = manifestInfo.periodInfos .map(function(periodInfo) { return periodInfo.streamSetInfos; }) .reduce(gather, []) .map(function(streamSetInfo) { return streamSetInfo.streamInfos; }) .reduce(gather, []); var async = streamInfos.map( function(streamInfo) { return streamInfo.segmentIndexSource.create(); }); return Promise.all(async).then( /** @param {!Array.<!shaka.media.SegmentIndex>} segmentIndexes */ function(segmentIndexes) { shaka.asserts.assert(streamInfos.length == segmentIndexes.length); /** @type {!Object.<number, !shaka.media.SegmentIndex>} */ var segmentIndexesByUid = {}; for (var i = 0; i < streamInfos.length; ++i) { segmentIndexesByUid[streamInfos[i].uniqueId] = segmentIndexes[i]; } return Promise.resolve(segmentIndexesByUid); } ); }; /** * Merges PeriodInfos from |newManifestInfo_| into |currentManifestInfo|. * Populates |removedStreamInfos| with any StreamInfos from * |currentManifestInfo| that have been removed. * * @param {!shaka.media.ManifestInfo} currentManifestInfo * @param {!shaka.media.ManifestInfo} newManifestInfo * @param {!Object.<number, !shaka.media.SegmentIndex>} * currentSegmentIndexesByUid * @param {!Object.<number, !shaka.media.SegmentIndex>} * newSegmentIndexesByUid * @param {!Array.<!shaka.media.StreamInfo>} removedStreamInfos * @private */ shaka.media.ManifestUpdater.mergePeriodInfos_ = function( currentManifestInfo, newManifestInfo, currentSegmentIndexesByUid, newSegmentIndexesByUid, removedStreamInfos) { /** @type {!shaka.util.MultiMap.<!shaka.media.PeriodInfo>} */ var currentPeriodInfoMap = new shaka.util.MultiMap(); currentManifestInfo.periodInfos.forEach(function(info, index) { var id = info.id || ('' + index); currentPeriodInfoMap.push(id, info); }); /** @type {!shaka.util.MultiMap.<!shaka.media.PeriodInfo>} */ var newPeriodInfoMap = new shaka.util.MultiMap(); newManifestInfo.periodInfos.forEach(function(info, index) { var id = info.id || ('' + index); newPeriodInfoMap.push(id, info); }); var keys = currentPeriodInfoMap.keys(); for (var i = 0; i < keys.length; ++i) { var id = keys[i]; var currentPeriodInfos = currentPeriodInfoMap.get(id); shaka.asserts.assert(currentPeriodInfos && currentPeriodInfos.length != 0); if (currentPeriodInfos.length > 1) { shaka.log.warning('Cannot update Period ' + id + ' because more ' + 'than one existing Period has the same ID.'); continue; } var newPeriodInfos = newPeriodInfoMap.get(id); if (!newPeriodInfos || newPeriodInfos.length == 0) { continue; } else if (newPeriodInfos.length == 1) { shaka.media.ManifestUpdater.mergeStreamSetInfos_( currentPeriodInfos[0], newPeriodInfos[0], currentSegmentIndexesByUid, newSegmentIndexesByUid, removedStreamInfos); currentPeriodInfos[0].duration = newPeriodInfos[0].duration; } else { shaka.log.warning('Cannot update Period ' + id + ' because more ' + 'than one new Period has the same ID.'); } } }; /** * Merges StreamSetInfos from |newPeriodInfo| into |currentPeriodInfo|. * * @param {!shaka.media.PeriodInfo} currentPeriodInfo * @param {!shaka.media.PeriodInfo} newPeriodInfo * @param {!Object.<number, !shaka.media.SegmentIndex>} * currentSegmentIndexesByUid * @param {!Object.<number, !shaka.media.SegmentIndex>} * newSegmentIndexesByUid * @param {!Array.<!shaka.media.StreamInfo>} removedStreamInfos * @private */ shaka.media.ManifestUpdater.mergeStreamSetInfos_ = function( currentPeriodInfo, newPeriodInfo, currentSegmentIndexesByUid, newSegmentIndexesByUid, removedStreamInfos) { /** @type {!shaka.util.MultiMap.<!shaka.media.StreamSetInfo>} */ var currentStreamSetInfoMap = new shaka.util.MultiMap(); currentPeriodInfo.streamSetInfos.forEach(function(info, index) { var id = info.id || ('' + index); currentStreamSetInfoMap.push(id, info); }); /** @type {!shaka.util.MultiMap.<!shaka.media.StreamSetInfo>} */ var newStreamSetInfoMap = new shaka.util.MultiMap(); newPeriodInfo.streamSetInfos.forEach(function(info, index) { var id = info.id || ('' + index); newStreamSetInfoMap.push(id, info); }); var keys = currentStreamSetInfoMap.keys(); for (var i = 0; i < keys.length; ++i) { var id = keys[i]; var currentStreamSetInfos = currentStreamSetInfoMap.get(id); shaka.asserts.assert(currentStreamSetInfos && currentStreamSetInfos.length != 0); if (currentStreamSetInfos.length > 1) { shaka.log.warning('Cannot update StreamSet ' + id + ' because more ' + 'than one existing StreamSet has the same ID.'); continue; } var newStreamSetInfos = newStreamSetInfoMap.get(id); if (!newStreamSetInfos || newStreamSetInfos.length == 0) { continue; } else if (newStreamSetInfos.length == 1) { shaka.media.ManifestUpdater.mergeStreamInfos_( currentStreamSetInfos[0], newStreamSetInfos[0], currentSegmentIndexesByUid, newSegmentIndexesByUid, removedStreamInfos); } else { shaka.log.warning('Cannot update StreamSet ' + id + ' because more ' + 'than one new StreamSet has the same ID.'); } } }; /** * Merges StreamInfos from |newStreamSetInfo| into |currentStreamSetInfo|. * * @param {!shaka.media.StreamSetInfo} currentStreamSetInfo * @param {!shaka.media.StreamSetInfo} newStreamSetInfo * @param {!Object.<number, !shaka.media.SegmentIndex>} * currentSegmentIndexesByUid * @param {!Object.<number, !shaka.media.SegmentIndex>} * newSegmentIndexesByUid * @param {!Array.<!shaka.media.StreamInfo>} removedStreamInfos * @private */ shaka.media.ManifestUpdater.mergeStreamInfos_ = function( currentStreamSetInfo, newStreamSetInfo, currentSegmentIndexesByUid, newSegmentIndexesByUid, removedStreamInfos) { /** @type {!shaka.util.MultiMap.<!shaka.media.StreamInfo>} */ var currentStreamInfoMap = new shaka.util.MultiMap(); currentStreamSetInfo.streamInfos.forEach(function(info, index) { var id = info.id || ('' + index); currentStreamInfoMap.push(id, info); }); /** @type {!shaka.util.MultiMap.<!shaka.media.StreamInfo>} */ var newStreamInfoMap = new shaka.util.MultiMap(); newStreamSetInfo.streamInfos.forEach(function(info, index) { var id = info.id || ('' + index); newStreamInfoMap.push(id, info); }); /** @type {!Object.<string, string>} */ var visitedStreamInfoSet = {}; var keys = currentStreamInfoMap.keys(); for (var i = 0; i < keys.length; ++i) { var id = keys[i]; visitedStreamInfoSet[id] = id; var currentStreamInfos = currentStreamInfoMap.get(id); shaka.asserts.assert(currentStreamInfos && currentStreamInfos.length != 0); if (currentStreamInfos.length > 1) { shaka.log.warning('Cannot update Stream ' + id + ' because more ' + 'than one existing Stream has the same ID.'); continue; } var newStreamInfos = newStreamInfoMap.get(id); if (!newStreamInfos || newStreamInfos.length == 0) { removedStreamInfos.push(currentStreamInfos[0]); currentStreamSetInfo.streamInfos.splice( currentStreamSetInfo.streamInfos.indexOf(currentStreamInfos[0]), 1); } else if (newStreamInfos.length == 1) { shaka.media.ManifestUpdater.integrateSegmentIndexes_( currentStreamInfos[0], newStreamInfos[0], currentSegmentIndexesByUid, newSegmentIndexesByUid); // Transfer ownership of the SegmentInitSource. currentStreamInfos[0].segmentInitSource = newStreamInfos[0].segmentInitSource; newStreamInfos[0].segmentInitSource = null; currentStreamInfos[0].timestampOffset = newStreamInfos[0].timestampOffset; } else { shaka.log.warning('Cannot update Stream ' + id + ' because more ' + 'than one new Stream has the same ID.'); } } keys = newStreamInfoMap.keys(); for (var i = 0; i < keys.length; ++i) { var id = keys[i]; if (visitedStreamInfoSet[id]) continue; visitedStreamInfoSet[id] = id; var newStreamInfos = newStreamInfoMap.get(id); shaka.asserts.assert(newStreamInfos && newStreamInfos.length != 0); if (newStreamInfos.length > 1) { shaka.log.warning('Cannot add Stream ' + id + ' because more ' + 'than one new Stream has the same ID.'); } currentStreamSetInfo.streamInfos.push(newStreamInfos[0]); shaka.log.info('Added Stream ' + id + '.'); } }; /** * Integrates |newStreamInfo|'s SegmentIndex into |currentStreamSet|'s * SegmentIndex. * * @param {!shaka.media.StreamInfo} currentStreamInfo * @param {!shaka.media.StreamInfo} newStreamInfo * @param {!Object.<number, !shaka.media.SegmentIndex>} * currentSegmentIndexesByUid * @param {!Object.<number, !shaka.media.SegmentIndex>} * newSegmentIndexesByUid * @private */ shaka.media.ManifestUpdater.integrateSegmentIndexes_ = function( currentStreamInfo, newStreamInfo, currentSegmentIndexesByUid, newSegmentIndexesByUid) { var currentSegmentIndex = currentSegmentIndexesByUid[currentStreamInfo.uniqueId]; shaka.asserts.assert(currentSegmentIndex); var newSegmentIndex = newSegmentIndexesByUid[newStreamInfo.uniqueId]; shaka.asserts.assert(newSegmentIndex); var originalLength = currentSegmentIndex.length(); if (currentSegmentIndex.integrate(newSegmentIndex)) { shaka.log.info('Updated SegmentIndex', currentStreamInfo.uniqueId + ':', originalLength, '->', currentSegmentIndex.length(), 'SegmentReference(s).'); } else { shaka.log.warning('Failed to update SegmentIndex', newStreamInfo.uniqueId); } };