diff --git a/build/types/core b/build/types/core index 4b16b4812d..b9669ef6ab 100644 --- a/build/types/core +++ b/build/types/core @@ -35,8 +35,10 @@ +../../lib/util/event_manager.js +../../lib/util/fake_event.js +../../lib/util/fake_event_target.js ++../../lib/util/functional.js +../../lib/util/i_destroyable.js +../../lib/util/language_utils.js ++../../lib/util/map_utils.js +../../lib/util/multi_map.js +../../lib/util/pssh.js +../../lib/util/public_promise.js diff --git a/lib/dash/content_protection.js b/lib/dash/content_protection.js index 10df0119a2..b7f047e976 100644 --- a/lib/dash/content_protection.js +++ b/lib/dash/content_protection.js @@ -19,6 +19,8 @@ goog.provide('shaka.dash.ContentProtection'); goog.require('goog.asserts'); goog.require('shaka.util.Error'); +goog.require('shaka.util.Functional'); +goog.require('shaka.util.MapUtils'); goog.require('shaka.util.XmlUtils'); @@ -107,6 +109,8 @@ shaka.dash.ContentProtection.MP4Protection_ = shaka.dash.ContentProtection.parseFromAdaptationSet = function( elems, callback) { var ContentProtection = shaka.dash.ContentProtection; + var Functional = shaka.util.Functional; + var MapUtils = shaka.util.MapUtils; var parsed = ContentProtection.parseElements_(elems); // Find the default key ID and init data. Create a new array of all the @@ -126,12 +130,12 @@ shaka.dash.ContentProtection.parseFromAdaptationSet = function( // Get the default key ID; if there are multiple, they must all match. var keyIds = parsed.map(function(elem) { return elem.keyId; }) - .filter(function(keyId) { return keyId != null; }); + .filter(Functional.isNotNull); /** @type {?string} */ var defaultKeyId = null; if (keyIds.length > 0) { defaultKeyId = keyIds[0]; - if (keyIds.some(function(keyId) { return keyId != defaultKeyId; })) { + if (keyIds.some(Functional.isNotEqualFunc(defaultKeyId))) { throw new shaka.util.Error( shaka.util.Error.Category.MANIFEST, shaka.util.Error.Code.DASH_CONFLICTING_KEY_IDS); @@ -154,8 +158,7 @@ shaka.dash.ContentProtection.parseFromAdaptationSet = function( // supported. var keySystems = ContentProtection.defaultKeySystems_; drmInfos = - Object.keys(keySystems) - .map(function(uri) { return keySystems[uri]; }) + MapUtils.values(keySystems) .map(function(keySystem) { return ContentProtection.createDrmInfo_(keySystem, defaultInit); }); @@ -251,6 +254,7 @@ shaka.dash.ContentProtection.createDrmInfo_ = function(keySystem, initData) { */ shaka.dash.ContentProtection.convertElements_ = function( defaultInit, callback, elements) { + var Functional = shaka.util.Functional; return elements.map( /** * @param {shaka.dash.ContentProtection.Element} element @@ -269,7 +273,7 @@ shaka.dash.ContentProtection.convertElements_ = function( callback, 'ContentProtection callback is required'); return callback(element.node) || []; } - }).reduce(function(all, part) { return all.concat(part); }, []); + }).reduce(Functional.collapseArrays, []); }; @@ -282,6 +286,7 @@ shaka.dash.ContentProtection.convertElements_ = function( * @private */ shaka.dash.ContentProtection.parseElements_ = function(elems) { + var Functional = shaka.util.Functional; return elems.map( /** * @param {!Element} elem @@ -338,6 +343,6 @@ shaka.dash.ContentProtection.parseElements_ = function(elems) { init: (init.length > 0 ? init : null) }; return element; - }).filter(function(e) { return e != null; }); + }).filter(Functional.isNotNull); }; diff --git a/lib/dash/dash_parser.js b/lib/dash/dash_parser.js index 738c467409..75a4c696ca 100644 --- a/lib/dash/dash_parser.js +++ b/lib/dash/dash_parser.js @@ -29,6 +29,7 @@ goog.require('shaka.media.PresentationTimeline'); goog.require('shaka.media.SegmentReference'); goog.require('shaka.net.NetworkingEngine'); goog.require('shaka.util.Error'); +goog.require('shaka.util.Functional'); goog.require('shaka.util.LanguageUtils'); goog.require('shaka.util.MultiMap'); goog.require('shaka.util.StringUtils'); @@ -361,8 +362,9 @@ shaka.dash.DashParser.prototype.requestManifest_ = function() { */ shaka.dash.DashParser.prototype.parseManifest_ = function(data, finalManifestUri) { - var XmlUtils = shaka.util.XmlUtils; var Error = shaka.util.Error; + var Functional = shaka.util.Functional; + var XmlUtils = shaka.util.XmlUtils; var string = shaka.util.StringUtils.fromBytesAutoDetect(data); var parser = new DOMParser(); @@ -391,7 +393,7 @@ shaka.dash.DashParser.prototype.parseManifest_ = /** @type {!Array.} */ var locations = XmlUtils.findChildren(mpd, 'Location') .map(XmlUtils.getContents) - .filter(function(l) { return l != null; }); + .filter(Functional.isNotNull); if (locations.length > 0) { this.manifestUris_ = locations; manifestBaseUris = locations; @@ -491,6 +493,7 @@ shaka.dash.DashParser.prototype.parseManifest_ = */ shaka.dash.DashParser.prototype.parsePeriods_ = function( context, baseUris, mpd) { + var Functional = shaka.util.Functional; var XmlUtils = shaka.util.XmlUtils; var presentationDuration = XmlUtils.parseAttr( mpd, 'mediaPresentationDuration', XmlUtils.parseDuration); @@ -531,7 +534,7 @@ shaka.dash.DashParser.prototype.parsePeriods_ = function( // If there are any new periods, call the callback and add them to the // manifest. If this is the first parse, it will see all of them as new. var periodId = context.period.id; - if (this.periodIds_.every(function(id) { return id != periodId; })) { + if (this.periodIds_.every(Functional.isNotEqualFunc(periodId))) { this.filterPeriod_(period); this.periodIds_.push(periodId); if (this.manifest_) @@ -887,6 +890,7 @@ shaka.dash.DashParser.prototype.createFrame_ = function( * @private */ shaka.dash.DashParser.prototype.createStreamSets_ = function(adaptationSets) { + var Functional = shaka.util.Functional; /** * A map of ID to the group it belongs to. Multiple IDs can map to the same * group. Each entry in the group will map back to the same array. @@ -920,9 +924,7 @@ shaka.dash.DashParser.prototype.createStreamSets_ = function(adaptationSets) { /** @type {!Array.>} */ var seenGroups = []; - // TODO: Use MapUtils. - Object.keys(groupMap).map(function(groupId) { - var group = groupMap[groupId]; + shaka.util.MapUtils.values(groupMap).forEach(function(group) { if (seenGroups.indexOf(group) >= 0) return; @@ -954,10 +956,10 @@ shaka.dash.DashParser.prototype.createStreamSets_ = function(adaptationSets) { primary: sets.some(function(s) { return s.main; }), drmInfos: sets.map(function(s) { return s.drmInfos; }) - .reduce(function(all, part) { return all.concat(part); }, []), + .reduce(Functional.collapseArrays, []), streams: sets.map(function(s) { return s.streams; }) - .reduce(function(all, part) { return all.concat(part); }, []) + .reduce(Functional.collapseArrays, []) }; ret.push(streamSet); }); // forEach lang @@ -1043,45 +1045,44 @@ shaka.dash.DashParser.prototype.requestForTiming_ = function(uri, method) { /** * Parses an array of UTCTiming elements. * - * @param {Array.} elems + * @param {!Array.} elems * @return {!Promise.} * @private */ shaka.dash.DashParser.prototype.parseUtcTiming_ = function(elems) { - var promise = elems.reduce(function(parent, elem) { - return parent.catch(function() { - var scheme = elem.getAttribute('schemeIdUri'); - var value = elem.getAttribute('value'); - switch (scheme) { - // See DASH IOP Guidelines Section 4.7 - // http://goo.gl/CQFNJT - case 'urn:mpeg:dash:utc:http-head:2014': - // Some old ISO23009-1 drafts used 2012. - case 'urn:mpeg:dash:utc:http-head:2012': - return this.requestForTiming_(value, 'HEAD'); - case 'urn:mpeg:dash:utc:http-xsdate:2014': - case 'urn:mpeg:dash:utc:http-iso:2014': - case 'urn:mpeg:dash:utc:http-xsdate:2012': - case 'urn:mpeg:dash:utc:http-iso:2012': - return this.requestForTiming_(value, 'GET'); - case 'urn:mpeg:dash:utc:direct:2014': - case 'urn:mpeg:dash:utc:direct:2012': - var date = Date.parse(value); - return isNaN(date) ? 0 : (date - Date.now()); - - case 'urn:mpeg:dash:utc:http-ntp:2014': - case 'urn:mpeg:dash:utc:ntp:2014': - case 'urn:mpeg:dash:utc:sntp:2014': - shaka.log.warning('NTP UTCTiming scheme is not supported'); - return Promise.reject(); - default: - shaka.log.warning('Unrecognized scheme in UTCTiming element', scheme); - return Promise.reject(); - } - }.bind(this)); - }.bind(this), Promise.reject()); - - return promise.catch(function() { return 0; }); + var Functional = shaka.util.Functional; + return Functional.createFallbackPromiseChain(elems, function(elem) { + var scheme = elem.getAttribute('schemeIdUri'); + var value = elem.getAttribute('value'); + switch (scheme) { + // See DASH IOP Guidelines Section 4.7 + // http://goo.gl/CQFNJT + case 'urn:mpeg:dash:utc:http-head:2014': + // Some old ISO23009-1 drafts used 2012. + case 'urn:mpeg:dash:utc:http-head:2012': + return this.requestForTiming_(value, 'HEAD'); + case 'urn:mpeg:dash:utc:http-xsdate:2014': + case 'urn:mpeg:dash:utc:http-iso:2014': + case 'urn:mpeg:dash:utc:http-xsdate:2012': + case 'urn:mpeg:dash:utc:http-iso:2012': + return this.requestForTiming_(value, 'GET'); + case 'urn:mpeg:dash:utc:direct:2014': + case 'urn:mpeg:dash:utc:direct:2012': + var date = Date.parse(value); + return isNaN(date) ? 0 : (date - Date.now()); + + case 'urn:mpeg:dash:utc:http-ntp:2014': + case 'urn:mpeg:dash:utc:ntp:2014': + case 'urn:mpeg:dash:utc:sntp:2014': + shaka.log.warning('NTP UTCTiming scheme is not supported'); + return Promise.reject(); + default: + shaka.log.warning( + 'Unrecognized scheme in UTCTiming element', scheme); + return Promise.reject(); + } + }.bind(this)) + .catch(function() { return 0; }); }; diff --git a/lib/dash/mpd_utils.js b/lib/dash/mpd_utils.js index c3b47f2858..ea9bd4b2b3 100644 --- a/lib/dash/mpd_utils.js +++ b/lib/dash/mpd_utils.js @@ -21,6 +21,7 @@ goog.require('goog.Uri'); goog.require('goog.asserts'); goog.require('shaka.log'); goog.require('shaka.media.SegmentReference'); +goog.require('shaka.util.Functional'); goog.require('shaka.util.XmlUtils'); @@ -352,6 +353,7 @@ shaka.dash.MpdUtils.fitSegmentReferences = function( * @return {!Array.} */ shaka.dash.MpdUtils.resolveUris = function(baseUris, relativeUris) { + var Functional = shaka.util.Functional; if (relativeUris.length == 0) return baseUris; @@ -361,7 +363,7 @@ shaka.dash.MpdUtils.resolveUris = function(baseUris, relativeUris) { // Then flatten the Arrays into a single Array. return baseUris.map(function(uri) { return new goog.Uri(uri); }) .map(function(base) { return relativeAsGoog.map(base.resolve.bind(base)); }) - .reduce(function(all, part) { return all.concat(part); }, []) + .reduce(Functional.collapseArrays, []) .map(function(uri) { return uri.toString(); }); }; @@ -432,6 +434,7 @@ shaka.dash.MpdUtils.parseSegmentInfo = function(context, callback) { * @return {?string} */ shaka.dash.MpdUtils.inheritAttribute = function(context, callback, attribute) { + var Functional = shaka.util.Functional; goog.asserts.assert( callback(context.representation), 'There must be at least one element of the given type'); @@ -441,7 +444,7 @@ shaka.dash.MpdUtils.inheritAttribute = function(context, callback, attribute) { callback(context.representation), callback(context.adaptationSet), callback(context.period) - ].filter(function(s) { return s != null; }); + ].filter(Functional.isNotNull); return nodes .map(function(s) { return s.getAttribute(attribute); }) @@ -459,6 +462,7 @@ shaka.dash.MpdUtils.inheritAttribute = function(context, callback, attribute) { * @return {Element} */ shaka.dash.MpdUtils.inheritChild = function(context, callback, child) { + var Functional = shaka.util.Functional; goog.asserts.assert( callback(context.representation), 'There must be at least one element of the given type'); @@ -468,7 +472,7 @@ shaka.dash.MpdUtils.inheritChild = function(context, callback, child) { callback(context.representation), callback(context.adaptationSet), callback(context.period) - ].filter(function(s) { return s != null; }); + ].filter(Functional.isNotNull); var XmlUtils = shaka.util.XmlUtils; return nodes diff --git a/lib/dash/segment_list.js b/lib/dash/segment_list.js index 832e492c26..88a6b7e13f 100644 --- a/lib/dash/segment_list.js +++ b/lib/dash/segment_list.js @@ -23,6 +23,7 @@ goog.require('shaka.dash.SegmentBase'); goog.require('shaka.media.SegmentIndex'); goog.require('shaka.media.SegmentReference'); goog.require('shaka.util.Error'); +goog.require('shaka.util.Functional'); goog.require('shaka.util.XmlUtils'); @@ -295,12 +296,13 @@ shaka.dash.SegmentList.createSegmentReferences_ = function( * @private */ shaka.dash.SegmentList.parseMediaSegments_ = function(context) { + var Functional = shaka.util.Functional; /** @type {!Array.} */ var segmentLists = [ context.representation.segmentList, context.adaptationSet.segmentList, context.period.segmentList - ].filter(function(s) { return s != null; }); + ].filter(Functional.isNotNull); var XmlUtils = shaka.util.XmlUtils; // Search each SegmentList for one with at least one SegmentURL element, diff --git a/lib/media/drm_engine.js b/lib/media/drm_engine.js index 8c9b56e717..05de9fcd1d 100644 --- a/lib/media/drm_engine.js +++ b/lib/media/drm_engine.js @@ -22,6 +22,7 @@ goog.require('shaka.log'); goog.require('shaka.util.ArrayUtils'); goog.require('shaka.util.Error'); goog.require('shaka.util.EventManager'); +goog.require('shaka.util.Functional'); goog.require('shaka.util.IDestroyable'); goog.require('shaka.util.PublicPromise'); goog.require('shaka.util.Uint8ArrayUtils'); @@ -92,13 +93,14 @@ shaka.media.DrmEngine.ActiveSession; /** @override */ shaka.media.DrmEngine.prototype.destroy = function() { + var Functional = shaka.util.Functional; this.destroyed_ = true; this.activeSessions_.forEach(function(activeSession) { // Ignore any errors when closing the sessions. One such error would be // an invalid state error triggered by closing a session which has not // generated any key requests. - activeSession.session.close().catch(function() {}); + activeSession.session.close().catch(Functional.noop); }); var async = []; @@ -107,7 +109,7 @@ shaka.media.DrmEngine.prototype.destroy = function() { if (this.video_) { goog.asserts.assert(!this.video_.src, 'video src must be removed first!'); - async.push(this.video_.setMediaKeys(null).catch(function() {})); + async.push(this.video_.setMediaKeys(null).catch(Functional.noop)); } this.drmInfos_ = []; @@ -428,6 +430,7 @@ shaka.media.DrmEngine.prototype.queryMediaKeys_ = * @private */ shaka.media.DrmEngine.prototype.fillInDrmInfoDefaults_ = function(drmInfo) { + var MapUtils = shaka.util.MapUtils; var keySystem = drmInfo.keySystem; if (!keySystem) { @@ -441,7 +444,7 @@ shaka.media.DrmEngine.prototype.fillInDrmInfoDefaults_ = function(drmInfo) { if (server) { drmInfo.licenseServerUri = server; } else if (keySystem == 'org.w3.clearkey') { - var hasClearKeys = Object.keys(this.config_.clearKeys).length != 0; + var hasClearKeys = !MapUtils.empty(this.config_.clearKeys); if (hasClearKeys) { this.configureClearKey_(drmInfo); } else { diff --git a/lib/media/media_source_engine.js b/lib/media/media_source_engine.js index 4c46194aa3..544899235b 100644 --- a/lib/media/media_source_engine.js +++ b/lib/media/media_source_engine.js @@ -22,6 +22,7 @@ goog.require('shaka.media.TextEngine'); goog.require('shaka.media.TimeRangesUtils'); goog.require('shaka.util.Error'); goog.require('shaka.util.EventManager'); +goog.require('shaka.util.Functional'); goog.require('shaka.util.IDestroyable'); goog.require('shaka.util.PublicPromise'); @@ -162,6 +163,7 @@ shaka.media.MediaSourceEngine.support = function() { * @override */ shaka.media.MediaSourceEngine.prototype.destroy = function() { + var Functional = shaka.util.Functional; this.destroyed_ = true; var cleanup = []; @@ -176,12 +178,12 @@ shaka.media.MediaSourceEngine.prototype.destroy = function() { // We will wait for this item to complete/fail. if (inProgress) { - cleanup.push(inProgress.p.catch(function() {})); + cleanup.push(inProgress.p.catch(Functional.noop)); } // The rest will be rejected silently if possible. for (var i = 1; i < q.length; ++i) { - q[i].p.catch(function() {}); + q[i].p.catch(Functional.noop); q[i].p.reject(); } } diff --git a/lib/media/streaming_engine.js b/lib/media/streaming_engine.js index e55fcba0b6..d0fa439569 100644 --- a/lib/media/streaming_engine.js +++ b/lib/media/streaming_engine.js @@ -22,7 +22,9 @@ goog.require('shaka.media.MediaSourceEngine'); goog.require('shaka.media.Playhead'); goog.require('shaka.net.NetworkingEngine'); goog.require('shaka.util.Error'); +goog.require('shaka.util.Functional'); goog.require('shaka.util.IDestroyable'); +goog.require('shaka.util.MapUtils'); goog.require('shaka.util.PublicPromise'); @@ -311,6 +313,7 @@ shaka.media.StreamingEngine.prototype.configure = function(config) { * @return {!Promise} */ shaka.media.StreamingEngine.prototype.init = function() { + var MapUtils = shaka.util.MapUtils; goog.asserts.assert(this.config_, 'StreamingEngine configure() must be called before init()!'); @@ -321,7 +324,7 @@ shaka.media.StreamingEngine.prototype.init = function() { // Get the initial set of Streams. var streamsByType = this.onChooseStreams_(this.manifest_.periods[needPeriodIndex]); - if (Object.keys(streamsByType).length == 0) { + if (MapUtils.empty(streamsByType)) { shaka.log.error('init: no Streams chosen'); return Promise.reject(new shaka.util.Error( shaka.util.Error.Category.STREAMING, @@ -361,11 +364,9 @@ shaka.media.StreamingEngine.prototype.getCurrentPeriod = function() { */ shaka.media.StreamingEngine.prototype.getActiveStreams = function() { goog.asserts.assert(this.mediaStates_, 'Must be initialized'); - var activeStreams = {}; - Object.keys(this.mediaStates_).map(function(type) { - activeStreams[type] = this.mediaStates_[type].stream; - }.bind(this)); - return activeStreams; + var MapUtils = shaka.util.MapUtils; + return MapUtils.map( + this.mediaStates_, function(state) { return state.stream; }); }; @@ -525,6 +526,7 @@ shaka.media.StreamingEngine.prototype.seeked = function() { * @private */ shaka.media.StreamingEngine.prototype.initStreams_ = function(streamsByType) { + var MapUtils = shaka.util.MapUtils; goog.asserts.assert(this.config_, 'StreamingEngine configure() must be called before init()!'); @@ -533,23 +535,17 @@ shaka.media.StreamingEngine.prototype.initStreams_ = function(streamsByType) { var needPeriodIndex = this.findPeriodContainingTime_(playheadTime); // Init MediaSourceEngine. - var typeConfig = {}; - - for (var type in streamsByType) { - var stream = streamsByType[type]; - typeConfig[type] = - stream.mimeType + + var typeConfig = MapUtils.map(streamsByType, function(stream) { + return stream.mimeType + (stream.codecs ? '; codecs="' + stream.codecs + '"' : ''); - } + }); this.mediaSourceEngine_.init(typeConfig); this.setDuration_(); // Setup the initial set of Streams and then begin each update cycle. After // startup completes onUpdate_() will set up the remaining Periods. - // TODO: Use MapUtils. - var streams = Object.keys(streamsByType) - .map(function(type) { return streamsByType[type]; }); + var streams = MapUtils.values(streamsByType); return this.setupStreams_(streams).then(function() { for (var type in streamsByType) { var stream = streamsByType[type]; @@ -586,6 +582,7 @@ shaka.media.StreamingEngine.prototype.initStreams_ = function(streamsByType) { * @private */ shaka.media.StreamingEngine.prototype.setupPeriod_ = function(periodIndex) { + var Functional = shaka.util.Functional; var canSwitchRecord = this.canSwitchPeriod_[periodIndex]; if (canSwitchRecord) { shaka.log.debug( @@ -603,7 +600,7 @@ shaka.media.StreamingEngine.prototype.setupPeriod_ = function(periodIndex) { var streams = this.manifest_.periods[periodIndex].streamSets .map(function(streamSet) { return streamSet.streams; }) - .reduce(function(all, part) { return all.concat(part); }, []); + .reduce(Functional.collapseArrays, []); // Serialize Period set up. this.setupPeriodPromise_ = this.setupPeriodPromise_.then(function() { @@ -700,6 +697,7 @@ shaka.media.StreamingEngine.prototype.setDuration_ = function() { * @private */ shaka.media.StreamingEngine.prototype.onUpdate_ = function(mediaState) { + var MapUtils = shaka.util.MapUtils; if (this.destroyed_) return; var logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState); @@ -736,10 +734,8 @@ shaka.media.StreamingEngine.prototype.onUpdate_ = function(mediaState) { return; } - // TODO: Use MapUtils. goog.asserts.assert(this.mediaStates_, 'must not be destroyed'); - var mediaStates = Object.keys(this.mediaStates_) - .map(function(type) { return this.mediaStates_[type]; }.bind(this)); + var mediaStates = MapUtils.values(this.mediaStates_); // Handle startup and re- buffering. this.playhead_.setBuffering( @@ -1240,16 +1236,18 @@ shaka.media.StreamingEngine.prototype.append_ = function( */ shaka.media.StreamingEngine.prototype.evict_ = function( mediaState, playheadTime) { + var MapUtils = shaka.util.MapUtils; var logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState); shaka.log.v2(logPrefix, 'checking buffer length'); // Get the earliest start time. - var startTime = Number.POSITIVE_INFINITY; - for (var type in this.mediaStates_) { - var ms = this.mediaStates_[type]; - startTime = Math.min(this.mediaSourceEngine_.bufferStart(ms.type), - startTime); - } + goog.asserts.assert(this.mediaStates_, 'Must be initialized'); + var mediaStates = MapUtils.values(this.mediaStates_); + var times = mediaStates.map(function(state) { + return this.mediaSourceEngine_.bufferStart(state.type); + }.bind(this)); + var startTime = Math.min.apply(Math, times); + goog.asserts.assert(startTime != Number.POSITIVE_INFINITY, 'startTime should not be infinity'); var bufferedBehind = playheadTime - startTime; @@ -1272,12 +1270,10 @@ shaka.media.StreamingEngine.prototype.evict_ = function( 'bufferBehind=' + this.config_.bufferBehind, 'overflow=' + overflow); - var async = []; - for (var type in this.mediaStates_) { - var ms = this.mediaStates_[type]; - async.push(this.mediaSourceEngine_.remove( - ms.type, startTime, startTime + overflow)); - } + var async = mediaStates.map(function(state) { + return this.mediaSourceEngine_.remove( + state.type, startTime, startTime + overflow); + }.bind(this)); return Promise.all(async).then(function() { if (this.destroyed_) return; @@ -1342,15 +1338,15 @@ shaka.media.StreamingEngine.prototype.handleDrift_ = function( * @private */ shaka.media.StreamingEngine.prototype.handleStartup_ = function(mediaState) { + var Functional = shaka.util.Functional; + var MapUtils = shaka.util.MapUtils; if (this.startupComplete_) return; var logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState); - // TODO: Use MapUtils. goog.asserts.assert(this.mediaStates_, 'must not be destroyed'); - var mediaStates = Object.keys(this.mediaStates_) - .map(function(type) { return this.mediaStates_[type]; }.bind(this)); + var mediaStates = MapUtils.values(this.mediaStates_); this.startupComplete_ = mediaStates.every(function(ms) { // Startup completes once we have buffered at least one segment from each // MediaState, have handled positive or large negative drift, and are not @@ -1386,12 +1382,12 @@ shaka.media.StreamingEngine.prototype.handleStartup_ = function(mediaState) { this.setupPeriod_(currentPeriodIndex).then(function() { shaka.log.v1(logPrefix, 'calling onCanSwitch_()...'); this.onCanSwitch_(); - }.bind(this)).catch(function() {}); + }.bind(this)).catch(Functional.noop); } // Now setup all known Periods. for (var i = 0; i < this.manifest_.periods.length; ++i) { - this.setupPeriod_(i).catch(function() {}); + this.setupPeriod_(i).catch(Functional.noop); } if (this.onStartupComplete_) { @@ -1410,6 +1406,8 @@ shaka.media.StreamingEngine.prototype.handleStartup_ = function(mediaState) { */ shaka.media.StreamingEngine.prototype.handlePeriodTransition_ = function( mediaState) { + var Functional = shaka.util.Functional; + var MapUtils = shaka.util.MapUtils; var logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState); var currentPeriodIndex = this.findPeriodContainingStream_(mediaState.stream); @@ -1418,10 +1416,8 @@ shaka.media.StreamingEngine.prototype.handlePeriodTransition_ = function( var needPeriodIndex = mediaState.needPeriodIndex; - // TODO: Use MapUtils. goog.asserts.assert(this.mediaStates_, 'must not be destroyed'); - var mediaStates = Object.keys(this.mediaStates_) - .map(function(type) { return this.mediaStates_[type]; }.bind(this)); + var mediaStates = MapUtils.values(this.mediaStates_); // Only call onChooseStreams_() when all MediaStates need the same Period. var needSamePeriod = mediaStates.every(function(ms) { @@ -1434,9 +1430,7 @@ shaka.media.StreamingEngine.prototype.handlePeriodTransition_ = function( } // Only call onChooseStreams_() once per Period transition. - var allAreIdle = mediaStates.every(function(ms) { - return shaka.media.StreamingEngine.isIdle_(ms); - }); + var allAreIdle = mediaStates.every(shaka.media.StreamingEngine.isIdle_); if (!allAreIdle) { shaka.log.debug( logPrefix, @@ -1497,7 +1491,7 @@ shaka.media.StreamingEngine.prototype.handlePeriodTransition_ = function( // We've already set up the Period so call onCanSwitch_() right now. shaka.log.v1(logPrefix, 'calling onCanSwitch_()...'); this.onCanSwitch_(); - }.bind(this)).catch(function() {}); + }.bind(this)).catch(Functional.noop); }; diff --git a/lib/net/networking_engine.js b/lib/net/networking_engine.js index f3a8d4220f..62dacb7e16 100644 --- a/lib/net/networking_engine.js +++ b/lib/net/networking_engine.js @@ -20,6 +20,7 @@ goog.provide('shaka.net.NetworkingEngine'); goog.require('goog.Uri'); goog.require('goog.asserts'); goog.require('shaka.util.Error'); +goog.require('shaka.util.Functional'); goog.require('shaka.util.IDestroyable'); goog.require('shaka.util.PublicPromise'); @@ -230,13 +231,14 @@ shaka.net.NetworkingEngine.makeRequest = function( /** @override */ shaka.net.NetworkingEngine.prototype.destroy = function() { + var Functional = shaka.util.Functional; this.destroyed_ = true; this.requestFilters_ = []; this.responseFilters_ = []; var cleanup = []; for (var i = 0; i < this.requests_.length; ++i) { - cleanup.push(this.requests_[i].catch(function() {})); + cleanup.push(this.requests_[i].catch(Functional.noop)); } return Promise.all(cleanup); }; diff --git a/lib/player.js b/lib/player.js index 8fbb03c9ca..acbcd12756 100644 --- a/lib/player.js +++ b/lib/player.js @@ -32,6 +32,7 @@ goog.require('shaka.util.Error'); goog.require('shaka.util.EventManager'); goog.require('shaka.util.FakeEvent'); goog.require('shaka.util.FakeEventTarget'); +goog.require('shaka.util.Functional'); goog.require('shaka.util.IDestroyable'); goog.require('shaka.util.LanguageUtils'); goog.require('shaka.util.PublicPromise'); @@ -562,6 +563,7 @@ shaka.Player.prototype.cancelTrickPlay = function() { * @export */ shaka.Player.prototype.getTracks = function() { + var Functional = shaka.util.Functional; if (!this.streamingEngine_) return []; @@ -584,7 +586,7 @@ shaka.Player.prototype.getTracks = function() { }; }); }) - .reduce(function(all, part) { return all.concat(part); }, []); + .reduce(Functional.collapseArrays, []); }; diff --git a/lib/util/functional.js b/lib/util/functional.js new file mode 100644 index 0000000000..038dd7ff99 --- /dev/null +++ b/lib/util/functional.js @@ -0,0 +1,102 @@ +/** + * @license + * Copyright 2016 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.util.Functional'); + + +/** + * @namespace shaka.util.Functional + * @summary A set of functional utility functions. + */ + + +/** + * Creates a promise chain that calls the given callback for each element in + * the array in a catch of a promise. + * + * e.g.: + * Promise.reject().catch(callback(array[0])).catch(callback(array[1])); + * + * @param {!Array.} array + * @param {function(ELEM):!Promise.} callback + * @return {!Promise.} + * @template ELEM,RESULT + */ +shaka.util.Functional.createFallbackPromiseChain = function(array, callback) { + return array.reduce(function(callback, promise, elem) { + return promise.catch(callback.bind(null, elem)); + }.bind(null, callback), Promise.reject()); +}; + + +/** + * Returns the first array concatenated to the second; used to collapse an + * array of arrays into a single array. + * + * @param {!Array.} all + * @param {!Array.} part + * @return {!Array.} + * @template T + */ +shaka.util.Functional.collapseArrays = function(all, part) { + return all.concat(part); +}; + + +/** + * A no-op function. Useful in promise chains. + */ +shaka.util.Functional.noop = function() {}; + + +/** + * Returns if the given value is not null; useful for filtering out null values. + * + * @param {T} value + * @return {boolean} + * @template T + */ +shaka.util.Functional.isNotNull = function(value) { + return value != null; +}; + + +/** + * Creates a function that returns whether the given value is equal to the given + * value. + * + * @param {T} compare + * @return {function(T):boolean} + * @template T + */ +shaka.util.Functional.isEqualFunc = function(compare) { + return function(a) { return a == compare; }; +}; + + +/** + * Creates a function that returns whether the given value is not equal to the + * given value. + * + * @param {T} compare + * @return {function(T):boolean} + * @template T + */ +shaka.util.Functional.isNotEqualFunc = function(compare) { + return function(a) { return a != compare; }; +}; + diff --git a/lib/util/map_utils.js b/lib/util/map_utils.js new file mode 100644 index 0000000000..553d0f4983 --- /dev/null +++ b/lib/util/map_utils.js @@ -0,0 +1,65 @@ +/** + * @license + * Copyright 2016 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.util.MapUtils'); + + +/** + * @namespace shaka.util.MapUtils + * @summary A set of map/object utility functions. + */ + + +/** + * Returns true if the map is empty; otherwise, returns false. + * + * @param {Object.} object + * @return {boolean} + * @template KEY,VALUE + */ +shaka.util.MapUtils.empty = function(object) { + return !object || Object.keys(object).length == 0; +}; + + +/** + * Gets the map's values. + * + * @param {!Object.} object + * @return {!Array.} + * @template KEY,VALUE + */ +shaka.util.MapUtils.values = function(object) { + return Object.keys(object).map(function(key) { return object[key]; }); +}; + + +/** + * Converts the values in the given Map to a different value. + * + * @param {!Object.} object + * @param {function(VALUE, KEY=):OUTPUT} callback + * @return {!Object.} + * @template KEY,VALUE,OUTPUT + */ +shaka.util.MapUtils.map = function(object, callback) { + return Object.keys(object).reduce(function(ret, key) { + var value = object[key]; + ret[key] = callback(value, key); + return ret; + }, {}); +}; diff --git a/lib/util/xml_utils.js b/lib/util/xml_utils.js index 8ee39d8bdf..abe04df039 100644 --- a/lib/util/xml_utils.js +++ b/lib/util/xml_utils.js @@ -30,19 +30,10 @@ goog.require('shaka.log'); * child XML element with the given tag name. */ shaka.util.XmlUtils.findChild = function(elem, name) { - var childElement = null; - - for (var i = 0; i < elem.childNodes.length; i++) { - if (elem.childNodes[i].tagName != name) - continue; - if (childElement) - return null; - childElement = elem.childNodes[i]; - } - - goog.asserts.assert(!childElement || childElement instanceof Element, - 'child element should be an Element'); - return childElement; + var children = shaka.util.XmlUtils.findChildren(elem, name); + if (children.length != 1) + return null; + return children[0]; }; @@ -53,14 +44,12 @@ shaka.util.XmlUtils.findChild = function(elem, name) { * @return {!Array.} The child XML elements. */ shaka.util.XmlUtils.findChildren = function(elem, name) { - var childElements = []; - - for (var i = 0; i < elem.childNodes.length; i++) { - if (elem.childNodes[i].tagName == name) - childElements.push(elem.childNodes[i]); - } - - return childElements; + return Array.prototype.filter.call(elem.childNodes, function(child) { + goog.asserts.assert( + child.tagName != name || child instanceof Element, + 'child element should be an Element'); + return child.tagName == name; + }); };