diff --git a/build/types/polyfill b/build/types/polyfill index 182906ca8..ccd1ad665 100644 --- a/build/types/polyfill +++ b/build/types/polyfill @@ -3,6 +3,7 @@ +../../lib/polyfill/eme_encryption_scheme.js +../../lib/polyfill/encryption_scheme_media_key_system_access.js +../../lib/polyfill/encryption_scheme_utils.js ++../../lib/polyfill/map.js +../../lib/polyfill/mcap_encryption_scheme.js +../../lib/polyfill/mediasource.js +../../lib/polyfill/media_capabilities.js diff --git a/externs/map.js b/externs/map.js new file mode 100644 index 000000000..0be83d332 --- /dev/null +++ b/externs/map.js @@ -0,0 +1,59 @@ +/*! @license + * Shaka Player + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Externs for Map getOrInsert/getOrInsertComputed methods + * + * @externs + */ + +/** + * Returns the value for the given key if present; otherwise inserts + * the default value, and returns that. + * @param {K} key + * @param {V} defaultValue + * @return {V} + * @this {Map} + * @template K, V + */ +// eslint-disable-next-line no-extend-native +Map.prototype.getOrInsert = function(key, defaultValue) {}; + +/** + * Returns the value for the given key if present; otherwise calls + * the callback with the key, inserts the returned value, and returns that. + * @param {K} key + * @param {function(K): V} callbackFunction + * @return {V} + * @this {Map} + * @template K, V + */ +// eslint-disable-next-line no-extend-native +Map.prototype.getOrInsertComputed = function(key, callbackFunction) {}; + +/** + * Returns the value for the given key if present; otherwise inserts + * the default value, and returns that. + * @param {K} key + * @param {V} defaultValue + * @return {V} + * @this {WeakMap} + * @template K, V + */ +// eslint-disable-next-line no-extend-native +WeakMap.prototype.getOrInsert = function(key, defaultValue) {}; + +/** + * Returns the value for the given key if present; otherwise calls + * the callback with the key, inserts the returned value, and returns that. + * @param {K} key + * @param {function(K): V} callbackFunction + * @return {V} + * @this {WeakMap} + * @template K, V + */ +// eslint-disable-next-line no-extend-native +WeakMap.prototype.getOrInsertComputed = function(key, callbackFunction) {}; diff --git a/lib/ads/svta_ad_manager.js b/lib/ads/svta_ad_manager.js index 6514148c6..2346f7729 100644 --- a/lib/ads/svta_ad_manager.js +++ b/lib/ads/svta_ad_manager.js @@ -258,8 +258,7 @@ shaka.ads.SvtaAdManager = class { let cuepointsChanged = false; for (const tracking of trackings) { const id = JSON.stringify(tracking); - if (!this.trackings_.has(id)) { - this.trackings_.set(id, tracking); + if (this.trackings_.getOrInsert(id, tracking) === tracking) { cuepointsChanged = true; } } diff --git a/lib/cea/cea_decoder.js b/lib/cea/cea_decoder.js index 1f6fb2f67..c3843e8bd 100644 --- a/lib/cea/cea_decoder.js +++ b/lib/cea/cea_decoder.js @@ -359,11 +359,9 @@ shaka.cea.CeaDecoder = class { if (serviceNumber != 0) { this.streams_.add('svc'+ serviceNumber); // If the service doesn't already exist, create it. - if (!this.serviceNumberToService_.has(serviceNumber)) { - const service = new shaka.cea.Cea708Service(serviceNumber); - this.serviceNumberToService_.set(serviceNumber, service); - } - const service = this.serviceNumberToService_.get(serviceNumber); + const service = this.serviceNumberToService_.getOrInsertComputed( + serviceNumber, + (num) => new shaka.cea.Cea708Service(num)); // Process all control codes. const startPos = dtvccPacket.getPosition(); diff --git a/lib/dash/content_protection.js b/lib/dash/content_protection.js index 83612d61b..f4bb202a2 100644 --- a/lib/dash/content_protection.js +++ b/lib/dash/content_protection.js @@ -543,13 +543,12 @@ shaka.dash.ContentProtection = class { try { // Try parsing PSSH data. init = psshs.map((pssh) => { - if (!this.psshToInitData_.has(pssh)) { - const initData = shaka.util.Uint8ArrayUtils.fromBase64(pssh); - this.psshToInitData_.set(pssh, initData); - } + const initData = this.psshToInitData_.getOrInsertComputed(pssh, () => { + return shaka.util.Uint8ArrayUtils.fromBase64(pssh); + }); return { initDataType: 'cenc', - initData: this.psshToInitData_.get(pssh), + initData, keyId: null, }; }); diff --git a/lib/dash/dash_parser.js b/lib/dash/dash_parser.js index 0a2751892..350908e3e 100644 --- a/lib/dash/dash_parser.js +++ b/lib/dash/dash_parser.js @@ -2555,11 +2555,9 @@ shaka.dash.DashParser = class { if (contextId && context.dynamic && !this.streamMap_.has(contextId)) { const periodId = context.period.id || ''; - if (!this.indexStreamMap_.has(periodId)) { - this.indexStreamMap_.set(periodId, []); - } this.streamMap_.set(contextId, stream); - this.indexStreamMap_.get(periodId).push(contextId); + this.indexStreamMap_.getOrInsertComputed(periodId, () => []) + .push(contextId); } return stream; diff --git a/lib/drm/drm_engine.js b/lib/drm/drm_engine.js index 25cf31172..2b30713e4 100644 --- a/lib/drm/drm_engine.js +++ b/lib/drm/drm_engine.js @@ -1099,10 +1099,8 @@ shaka.drm.DrmEngine = class { // Get all the key systems in the variant that shouldHaveLicenseServer. const drmInfos = this.getVariantDrmInfos_(variant); for (const info of drmInfos) { - if (!drmInfosByKeySystem.has(info.keySystem)) { - drmInfosByKeySystem.set(info.keySystem, []); - } - drmInfosByKeySystem.get(info.keySystem).push(info); + drmInfosByKeySystem.getOrInsertComputed(info.keySystem, () => []) + .push(info); } } diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index 3f403ebe3..91532089d 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -1311,9 +1311,7 @@ shaka.hls.HlsParser = class { const value = variableTag.getAttributeValue('VALUE'); const queryParam = variableTag.getAttributeValue('QUERYPARAM'); if (name && value) { - if (!this.globalVariables_.has(name)) { - this.globalVariables_.set(name, value); - } + this.globalVariables_.getOrInsert(name, value); } if (queryParam) { const queryParamValue = queryParams.get(queryParam)[0]; @@ -1340,9 +1338,7 @@ shaka.hls.HlsParser = class { const queryParam = variableTag.getAttributeValue('QUERYPARAM'); const mediaImport = variableTag.getAttributeValue('IMPORT'); if (name && value) { - if (!mediaVariables.has(name)) { - mediaVariables.set(name, value); - } + mediaVariables.getOrInsert(name, value); } if (queryParam) { const queryParamValue = queryParams.get(queryParam)[0]; @@ -1652,10 +1648,8 @@ shaka.hls.HlsParser = class { } else if ((i + 1) < chapterList.length) { item.endTime = chapterList[i + 1]['start-time']; } - if (!chapterByLanguage.has(title.language)) { - chapterByLanguage.set(title.language, []); - } - chapterByLanguage.get(title.language).push(item); + chapterByLanguage.getOrInsertComputed(title.language, () => []) + .push(item); } } @@ -1801,14 +1795,12 @@ shaka.hls.HlsParser = class { const keyUris = shaka.hls.Utils.constructSegmentUris( getUris(), drmTag.getRequiredAttrValue('URI'), variables); const keyMapKey = keyUris.sort().join(''); - if (!this.aesKeyMap_.has(keyMapKey)) { + this.aesKeyMap_.getOrInsertComputed(keyMapKey, () => { const requestType = shaka.net.NetworkingEngine.RequestType.KEY; const request = shaka.net.NetworkingEngine.makeRequest( keyUris, this.config_.retryParameters); - const keyResponse = this.makeNetworkRequest_(request, requestType) - .promise; - this.aesKeyMap_.set(keyMapKey, keyResponse); - } + return this.makeNetworkRequest_(request, requestType).promise; + }); continue; } else if (keyFormat == 'identity') { // eslint-disable-next-line no-await-in-loop @@ -3581,7 +3573,7 @@ shaka.hls.HlsParser = class { const keyMapKey = keyUris.sort().join(''); const aesKeyInfoKey = `${drmTag.toString()}-${firstMediaSequenceNumber}-${keyMapKey}`; - if (!this.aesKeyInfoMap_.has(aesKeyInfoKey)) { + return this.aesKeyInfoMap_.getOrInsertComputed(aesKeyInfoKey, () => { // Default AES-128 const keyInfo = { bitsKey: 128, @@ -3604,15 +3596,13 @@ shaka.hls.HlsParser = class { // Don't download the key object until the segment is parsed, to avoid a // startup delay for long manifests with lots of keys. keyInfo.fetchKey = async () => { - if (!this.aesKeyMap_.has(keyMapKey)) { - const requestType = shaka.net.NetworkingEngine.RequestType.KEY; - const request = shaka.net.NetworkingEngine.makeRequest( - keyUris, this.config_.retryParameters); - const keyResponse = this.makeNetworkRequest_(request, requestType) - .promise; - this.aesKeyMap_.set(keyMapKey, keyResponse); - } - const keyResponse = await this.aesKeyMap_.get(keyMapKey); + const keyResponse = await this.aesKeyMap_.getOrInsertComputed( + keyMapKey, () => { + const requestType = shaka.net.NetworkingEngine.RequestType.KEY; + const request = shaka.net.NetworkingEngine.makeRequest( + keyUris, this.config_.retryParameters); + return this.makeNetworkRequest_(request, requestType).promise; + }); // keyResponse.status is undefined when URI is "data:text/plain;base64," if (!keyResponse.data || @@ -3631,9 +3621,8 @@ shaka.hls.HlsParser = class { 'raw', keyResponse.data, algorithm, true, ['decrypt']); keyInfo.fetchKey = undefined; // No longer needed. }; - this.aesKeyInfoMap_.set(aesKeyInfoKey, keyInfo); - } - return this.aesKeyInfoMap_.get(aesKeyInfoKey); + return keyInfo; + }); } @@ -3843,30 +3832,31 @@ shaka.hls.HlsParser = class { absoluteInitSegmentUris.toString(), mapTag.getAttributeValue('BYTERANGE', ''), ].join('-'); - if (!this.mapTagToInitSegmentRefMap_.has(mapTagKey)) { - /** @type {shaka.extern.aesKey|undefined} */ - let aesKey = undefined; - let byteRangeTag = null; - let encrypted = false; - for (const tag of tags) { - if (tag.name == 'EXT-X-KEY') { - const method = tag.getRequiredAttrValue('METHOD'); - if (this.isAesMethod_(method) && tag.id < mapTag.id) { - encrypted = false; - aesKey = - this.parseAESDrmTag_(tag, playlist, getUris, variables); - } else { - encrypted = method != 'NONE'; + return this.mapTagToInitSegmentRefMap_.getOrInsertComputed( + mapTagKey, () => { + goog.asserts.assert(mapTag, 'mapTag should be non-null'); + /** @type {shaka.extern.aesKey|undefined} */ + let aesKey = undefined; + /** @type {shaka.hls.Tag|undefined} */ + let byteRangeTag = undefined; + let encrypted = false; + for (const tag of tags) { + if (tag.name == 'EXT-X-KEY') { + const method = tag.getRequiredAttrValue('METHOD'); + if (this.isAesMethod_(method) && tag.id < mapTag.id) { + encrypted = false; + aesKey = + this.parseAESDrmTag_(tag, playlist, getUris, variables); + } else { + encrypted = method != 'NONE'; + } + } else if (tag.name == 'EXT-X-BYTERANGE' && tag.id < mapTag.id) { + byteRangeTag = tag; + } } - } else if (tag.name == 'EXT-X-BYTERANGE' && tag.id < mapTag.id) { - byteRangeTag = tag; - } - } - const initSegmentRef = this.createInitSegmentReference_( - absoluteInitSegmentUris, mapTag, byteRangeTag, aesKey, encrypted); - this.mapTagToInitSegmentRefMap_.set(mapTagKey, initSegmentRef); - } - return this.mapTagToInitSegmentRefMap_.get(mapTagKey); + return this.createInitSegmentReference_( + absoluteInitSegmentUris, mapTag, byteRangeTag, aesKey, encrypted); + }); } /** @@ -5224,16 +5214,15 @@ shaka.hls.HlsParser = class { const parsedData = shaka.net.DataUriPlugin.parseRaw(uri.split('?')[0]); const psshKey = uri.split('?')[0]; - if (!this.psshToInitData_.has(psshKey)) { + const initData = this.psshToInitData_.getOrInsertComputed(psshKey, () => { // The data encoded in the URI is a PSSH box to be used as init data. - const initData = shaka.util.BufferUtils.toUint8(parsedData.data); - this.psshToInitData_.set(psshKey, initData); - } + return shaka.util.BufferUtils.toUint8(parsedData.data); + }); const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo( 'com.widevine.alpha', encryptionScheme, /* initData= */ [ - {initDataType: 'cenc', initData: this.psshToInitData_.get(psshKey)}, + {initDataType: 'cenc', initData}, ], /* keySystemUri= */ undefined, /* mediaTypeList= */ parsedData.typeInfoList); @@ -5333,16 +5322,15 @@ shaka.hls.HlsParser = class { const parsedData = shaka.net.DataUriPlugin.parseRaw(uri.split('?')[0]); const psshKey = uri.split('?')[0]; - if (!this.psshToInitData_.has(psshKey)) { + const initData = this.psshToInitData_.getOrInsertComputed(psshKey, () => { // The data encoded in the URI is a PSSH box to be used as init data. - const initData = shaka.util.BufferUtils.toUint8(parsedData.data); - this.psshToInitData_.set(psshKey, initData); - } + return shaka.util.BufferUtils.toUint8(parsedData.data); + }); const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo( /* keySystem= */ 'com.huawei.wiseplay', encryptionScheme, /* initData= */ [ - {initDataType: 'cenc', initData: this.psshToInitData_.get(psshKey)}, + {initDataType: 'cenc', initData}, ], /* keySystemUri= */ undefined, /* mediaTypeList= */ parsedData.typeInfoList); @@ -5405,15 +5393,13 @@ shaka.hls.HlsParser = class { keyUris[0].split('data:text/plain;base64,').pop())); } else { const keyMapKey = keyUris.sort().join(''); - if (!this.identityKeyMap_.has(keyMapKey)) { - const requestType = shaka.net.NetworkingEngine.RequestType.KEY; - const request = shaka.net.NetworkingEngine.makeRequest( - keyUris, this.config_.retryParameters); - const keyResponse = this.makeNetworkRequest_(request, requestType) - .promise; - this.identityKeyMap_.set(keyMapKey, keyResponse); - } - const keyResponse = await this.identityKeyMap_.get(keyMapKey); + const keyResponse = await this.identityKeyMap_.getOrInsertComputed( + keyMapKey, () => { + const requestType = shaka.net.NetworkingEngine.RequestType.KEY; + const request = shaka.net.NetworkingEngine.makeRequest( + keyUris, this.config_.retryParameters); + return this.makeNetworkRequest_(request, requestType).promise; + }); key = shaka.util.Uint8ArrayUtils.toHex(keyResponse.data); } diff --git a/lib/media/media_source_capabilities.js b/lib/media/media_source_capabilities.js index c93aa2790..d52e158d2 100644 --- a/lib/media/media_source_capabilities.js +++ b/lib/media/media_source_capabilities.js @@ -20,16 +20,10 @@ shaka.media.Capabilities = class { */ static isTypeSupported(type) { const supportMap = shaka.media.Capabilities.MediaSourceTypeSupportMap; - if (supportMap.has(type)) { - return supportMap.get(type); - } - const mediaSource = window.ManagedMediaSource || window.MediaSource; - if (mediaSource) { - const currentSupport = mediaSource.isTypeSupported(type); - supportMap.set(type, currentSupport); - return currentSupport; - } - return false; + return supportMap.getOrInsertComputed(type, () => { + const mediaSource = window.ManagedMediaSource || window.MediaSource; + return mediaSource?.isTypeSupported(type) ?? false; + }); } /** diff --git a/lib/media/media_source_engine.js b/lib/media/media_source_engine.js index b3e0d3a81..5073e5a55 100644 --- a/lib/media/media_source_engine.js +++ b/lib/media/media_source_engine.js @@ -941,10 +941,8 @@ shaka.media.MediaSourceEngine = class { } } else if (!mimeType.includes('/mp4') && !mimeType.includes('/webm') && shaka.util.TsParser.probe(uint8ArrayData)) { - if (!this.tsParsers_.has(contentType)) { - this.tsParsers_.set(contentType, new shaka.util.TsParser()); - } - const tsParser = this.tsParsers_.get(contentType); + const tsParser = this.tsParsers_.getOrInsertComputed( + contentType, () => new shaka.util.TsParser()); tsParser.clearData(); tsParser.setDiscontinuitySequence(reference.discontinuitySequence); tsParser.parse(uint8ArrayData); diff --git a/lib/media/streaming_engine.js b/lib/media/streaming_engine.js index ee8fb5917..df146d88f 100644 --- a/lib/media/streaming_engine.js +++ b/lib/media/streaming_engine.js @@ -685,17 +685,15 @@ shaka.media.StreamingEngine = class { if (mediaState.performingUpdate) { const oldStreamTag = shaka.media.StreamingEngine.logPrefix_(mediaState); - if (!this.deferredCloseSegmentIndex_.has(oldStreamTag)) { - // The ongoing update is still using the old stream's segment - // reference information. - // If we close the old stream now, the update will not complete - // correctly. - // The next onUpdate_() for this content type will resume the - // closeSegmentIndex() operation for the old stream once the ongoing - // update has finished, then immediately create a new segment index. - this.deferredCloseSegmentIndex_.set( - oldStreamTag, mediaState.stream.closeSegmentIndex); - } + // The ongoing update is still using the old stream's segment + // reference information. + // If we close the old stream now, the update will not complete + // correctly. + // The next onUpdate_() for this content type will resume the + // closeSegmentIndex() operation for the old stream once the ongoing + // update has finished, then immediately create a new segment index. + this.deferredCloseSegmentIndex_.getOrInsert( + oldStreamTag, mediaState.stream.closeSegmentIndex); } else { mediaState.stream.closeSegmentIndex(); } @@ -1117,8 +1115,8 @@ shaka.media.StreamingEngine = class { const stream = streamsByType.get(type); if (!this.mediaStates_.has(type)) { const mediaState = this.createMediaState_(stream); - if (segmentPrefetchById.has(stream.id)) { - const segmentPrefetch = segmentPrefetchById.get(stream.id); + const segmentPrefetch = segmentPrefetchById.get(stream.id); + if (segmentPrefetch) { segmentPrefetch.replaceFetchDispatcher( (reference, stream, streamDataCallback) => { return this.dispatchFetch_( diff --git a/lib/polyfill/map.js b/lib/polyfill/map.js new file mode 100644 index 000000000..84507e6a4 --- /dev/null +++ b/lib/polyfill/map.js @@ -0,0 +1,95 @@ +/*! @license + * Shaka Player + * Copyright 2016 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.provide('shaka.polyfill.Map'); + +goog.require('shaka.log'); +goog.require('shaka.polyfill'); + +/** + * @summary A polyfill to provide Map.prototype.getOrInsert, + * Map.prototype.getOrInsertComputed, WeakMap.prototype.getOrInsert, + * and WeakMap.prototype.getOrInsertComputed methods. + * @see https://github.com/tc39/proposal-upsert + * @export + */ +shaka.polyfill.Map = class { + /** + * Install the polyfill if needed. + * @export + */ + static install() { + shaka.log.debug('Map.install'); + + // eslint-disable-next-line no-restricted-syntax + if (!('getOrInsert' in Map.prototype)) { + shaka.log.debug('Map: Installing getOrInsert polyfill.'); + // eslint-disable-next-line no-extend-native, no-restricted-syntax + Map.prototype.getOrInsert = shaka.polyfill.Map.mapGetOrInsert_; + } + + // eslint-disable-next-line no-restricted-syntax + if (!('getOrInsertComputed' in Map.prototype)) { + shaka.log.debug('Map: Installing getOrInsertComputed polyfill.'); + // eslint-disable-next-line no-extend-native, no-restricted-syntax + Map.prototype.getOrInsertComputed = + shaka.polyfill.Map.mapGetOrInsertComputed_; + } + + shaka.log.debug('WeakMap.install'); + + // eslint-disable-next-line no-restricted-syntax + if (!('getOrInsert' in WeakMap.prototype)) { + shaka.log.debug('WeakMap: Installing getOrInsert polyfill.'); + // eslint-disable-next-line no-extend-native, no-restricted-syntax + WeakMap.prototype.getOrInsert = shaka.polyfill.Map.mapGetOrInsert_; + } + + // eslint-disable-next-line no-restricted-syntax + if (!('getOrInsertComputed' in WeakMap.prototype)) { + shaka.log.debug('WeakMap: Installing getOrInsertComputed polyfill.'); + // eslint-disable-next-line no-extend-native, no-restricted-syntax + WeakMap.prototype.getOrInsertComputed = + shaka.polyfill.Map.mapGetOrInsertComputed_; + } + } + + /** + * Returns the value for the given key if present; otherwise inserts + * the default value, and returns that. + * @param {K} key + * @param {V} defaultValue + * @return {V} + * @this {Map|WeakMap} + * @template K, V + * @private + */ + static mapGetOrInsert_(key, defaultValue) { + if (!this.has(key)) { + this.set(key, defaultValue); + } + return this.get(key); + } + + /** + * Returns the value for the given key if present; otherwise calls + * the callback with the key, inserts the returned value, and returns that. + * @param {K} key + * @param {function(K): V} callbackFunction + * @return {V} + * @this {Map|WeakMap} + * @template K, V + * @private + */ + static mapGetOrInsertComputed_(key, callbackFunction) { + if (!this.has(key)) { + this.set(key, callbackFunction(key)); + } + return this.get(key); + } +}; + +shaka.polyfill.register(shaka.polyfill.Map.install); diff --git a/lib/text/speech_to_text.js b/lib/text/speech_to_text.js index 24bd99c17..2216708f0 100644 --- a/lib/text/speech_to_text.js +++ b/lib/text/speech_to_text.js @@ -473,23 +473,28 @@ shaka.text.SpeechToText = class { if (!mediaElement) { return null; } - if (!shaka.text.SpeechToText.audioObjectMap_.has(mediaElement)) { - const AudioContext = window.AudioContext || window.webkitAudioContext; - const audioContext = new AudioContext(); - const sourceNode = audioContext.createMediaElementSource(mediaElement); - const destinationNode = audioContext.createMediaStreamDestination(); - sourceNode.connect(destinationNode); - sourceNode.connect(audioContext.destination); - const audioTrack = destinationNode.stream.getAudioTracks()[0]; - shaka.text.SpeechToText.audioObjectMap_.set(mediaElement, { - audioContext, - sourceNode, - destinationNode, - audioTrack, - }); - } const audioObject = - shaka.text.SpeechToText.audioObjectMap_.get(mediaElement); + shaka.text.SpeechToText.audioObjectMap_.getOrInsertComputed( + mediaElement, + () => { + const AudioContext = + window.AudioContext || window.webkitAudioContext; + const audioContext = new AudioContext(); + goog.asserts.assert(mediaElement, 'MediaElement should be null'); + const sourceNode = + audioContext.createMediaElementSource(mediaElement); + const destinationNode = + audioContext.createMediaStreamDestination(); + sourceNode.connect(destinationNode); + sourceNode.connect(audioContext.destination); + const audioTrack = destinationNode.stream.getAudioTracks()[0]; + return { + audioContext, + sourceNode, + destinationNode, + audioTrack, + }; + }); return audioObject.audioTrack; } diff --git a/lib/text/text_engine.js b/lib/text/text_engine.js index 7378d226d..c295c45b0 100644 --- a/lib/text/text_engine.js +++ b/lib/text/text_engine.js @@ -392,9 +392,7 @@ shaka.text.TextEngine = class { for (const caption of closedCaptions) { const id = caption.stream; const cue = caption.cue; - if (!captionsMap.has(id)) { - captionsMap.set(id, []); - } + captionsMap.getOrInsert(id, []); // Adjust CEA captions with respect to the timestamp offset of the video // stream in which they were embedded. @@ -414,11 +412,10 @@ shaka.text.TextEngine = class { } for (const id of captionsMap.keys()) { - if (!this.closedCaptionsMap_.has(id)) { - this.closedCaptionsMap_.set(id, []); - } + const closedCaptions = this.closedCaptionsMap_.getOrInsertComputed( + id, () => []); for (const cue of captionsMap.get(id)) { - this.closedCaptionsMap_.get(id).push(cue); + closedCaptions.push(cue); } } diff --git a/lib/transmuxer/aac_transmuxer.js b/lib/transmuxer/aac_transmuxer.js index 900705749..b3e56e6ba 100644 --- a/lib/transmuxer/aac_transmuxer.js +++ b/lib/transmuxer/aac_transmuxer.js @@ -207,14 +207,9 @@ shaka.transmuxer.AacTransmuxer = class { stream: stream, }; const mp4Generator = new shaka.util.Mp4Generator([streamInfo]); - let initSegment; const initSegmentKey = stream.id + '_' + reference.discontinuitySequence; - if (!this.initSegments.has(initSegmentKey)) { - initSegment = mp4Generator.initSegment(); - this.initSegments.set(initSegmentKey, initSegment); - } else { - initSegment = this.initSegments.get(initSegmentKey); - } + const initSegment = this.initSegments.getOrInsertComputed( + initSegmentKey, () => mp4Generator.initSegment()); const appendInitSegment = this.lastInitSegment_ !== initSegment; const segmentData = mp4Generator.segmentData(); this.lastInitSegment_ = initSegment; diff --git a/lib/transmuxer/ac3_transmuxer.js b/lib/transmuxer/ac3_transmuxer.js index 548fd46e9..85eab4b5a 100644 --- a/lib/transmuxer/ac3_transmuxer.js +++ b/lib/transmuxer/ac3_transmuxer.js @@ -201,14 +201,9 @@ shaka.transmuxer.Ac3Transmuxer = class { stream: stream, }; const mp4Generator = new shaka.util.Mp4Generator([streamInfo]); - let initSegment; const initSegmentKey = stream.id + '_' + reference.discontinuitySequence; - if (!this.initSegments.has(initSegmentKey)) { - initSegment = mp4Generator.initSegment(); - this.initSegments.set(initSegmentKey, initSegment); - } else { - initSegment = this.initSegments.get(initSegmentKey); - } + const initSegment = this.initSegments.getOrInsertComputed( + initSegmentKey, () => mp4Generator.initSegment()); const appendInitSegment = this.lastInitSegment_ !== initSegment; const segmentData = mp4Generator.segmentData(); this.lastInitSegment_ = initSegment; diff --git a/lib/transmuxer/ec3_transmuxer.js b/lib/transmuxer/ec3_transmuxer.js index afd7ee721..2648881d3 100644 --- a/lib/transmuxer/ec3_transmuxer.js +++ b/lib/transmuxer/ec3_transmuxer.js @@ -195,14 +195,9 @@ shaka.transmuxer.Ec3Transmuxer = class { stream: stream, }; const mp4Generator = new shaka.util.Mp4Generator([streamInfo]); - let initSegment; const initSegmentKey = stream.id + '_' + reference.discontinuitySequence; - if (!this.initSegments.has(initSegmentKey)) { - initSegment = mp4Generator.initSegment(); - this.initSegments.set(initSegmentKey, initSegment); - } else { - initSegment = this.initSegments.get(initSegmentKey); - } + const initSegment = this.initSegments.getOrInsertComputed( + initSegmentKey, () => mp4Generator.initSegment()); const appendInitSegment = this.lastInitSegment_ !== initSegment; const segmentData = mp4Generator.segmentData(); this.lastInitSegment_ = initSegment; diff --git a/lib/transmuxer/mp3_transmuxer.js b/lib/transmuxer/mp3_transmuxer.js index 630317720..6f397d153 100644 --- a/lib/transmuxer/mp3_transmuxer.js +++ b/lib/transmuxer/mp3_transmuxer.js @@ -188,14 +188,9 @@ shaka.transmuxer.Mp3Transmuxer = class { stream: stream, }; const mp4Generator = new shaka.util.Mp4Generator([streamInfo]); - let initSegment; const initSegmentKey = stream.id + '_' + reference.discontinuitySequence; - if (!this.initSegments.has(initSegmentKey)) { - initSegment = mp4Generator.initSegment(); - this.initSegments.set(initSegmentKey, initSegment); - } else { - initSegment = this.initSegments.get(initSegmentKey); - } + const initSegment = this.initSegments.getOrInsertComputed( + initSegmentKey, () => mp4Generator.initSegment()); const appendInitSegment = this.lastInitSegment_ !== initSegment; const segmentData = mp4Generator.segmentData(); this.lastInitSegment_ = initSegment; diff --git a/lib/transmuxer/ts_transmuxer.js b/lib/transmuxer/ts_transmuxer.js index 446e36afa..ce838e929 100644 --- a/lib/transmuxer/ts_transmuxer.js +++ b/lib/transmuxer/ts_transmuxer.js @@ -288,14 +288,9 @@ shaka.transmuxer.TsTransmuxer = class { } const mp4Generator = new shaka.util.Mp4Generator(streamInfos); - let initSegment; const initSegmentKey = stream.id + '_' + reference.discontinuitySequence; - if (!this.initSegments.has(initSegmentKey)) { - initSegment = mp4Generator.initSegment(); - this.initSegments.set(initSegmentKey, initSegment); - } else { - initSegment = this.initSegments.get(initSegmentKey); - } + const initSegment = this.initSegments.getOrInsertComputed( + initSegmentKey, () => mp4Generator.initSegment()); const appendInitSegment = this.lastInitSegment_ !== initSegment; const segmentData = mp4Generator.segmentData(); this.lastInitSegment_ = initSegment; diff --git a/lib/util/multi_map.js b/lib/util/multi_map.js index eb83f604c..ffdc9a648 100644 --- a/lib/util/multi_map.js +++ b/lib/util/multi_map.js @@ -24,11 +24,7 @@ shaka.util.MultiMap = class { * @param {T} value */ push(key, value) { - if (this.map_.has(key)) { - this.map_.get(key).push(value); - } else { - this.map_.set(key, [value]); - } + this.map_.getOrInsertComputed(key, () => []).push(value); } diff --git a/lib/util/periods.js b/lib/util/periods.js index f251cca87..011b95cdb 100644 --- a/lib/util/periods.js +++ b/lib/util/periods.js @@ -1947,11 +1947,8 @@ shaka.util.PeriodCombiner = class { * @private */ static getCodec_(codecs) { - if (!shaka.util.PeriodCombiner.memoizedCodecs.has(codecs)) { - const normalizedCodec = shaka.util.MimeUtils.getNormalizedCodec(codecs); - shaka.util.PeriodCombiner.memoizedCodecs.set(codecs, normalizedCodec); - } - return shaka.util.PeriodCombiner.memoizedCodecs.get(codecs); + return shaka.util.PeriodCombiner.memoizedCodecs.getOrInsertComputed( + codecs, () => shaka.util.MimeUtils.getNormalizedCodec(codecs)); } /** diff --git a/shaka-player.uncompiled.js b/shaka-player.uncompiled.js index af605ca1a..c6e4c575b 100644 --- a/shaka-player.uncompiled.js +++ b/shaka-player.uncompiled.js @@ -46,6 +46,7 @@ goog.require('shaka.offline.indexeddb.StorageMechanism'); goog.require('shaka.polyfill.Aria'); goog.require('shaka.polyfill.EmeEncryptionScheme'); goog.require('shaka.polyfill.Fullscreen'); +goog.require('shaka.polyfill.Map'); goog.require('shaka.polyfill.MCapEncryptionScheme'); goog.require('shaka.polyfill.MediaSource'); goog.require('shaka.polyfill.MediaCapabilities'); diff --git a/ui/language_utils.js b/ui/language_utils.js index 7fd68288c..9270470d8 100644 --- a/ui/language_utils.js +++ b/ui/language_utils.js @@ -80,10 +80,7 @@ shaka.ui.LanguageUtils = class { if (!track.codecs) { continue; } - if (!codecsByLanguage.has(track.language)) { - codecsByLanguage.set(track.language, new Set()); - } - codecsByLanguage.get(track.language).add( + codecsByLanguage.getOrInsertComputed(track.language, () => new Set()).add( shaka.util.MimeUtils.getNormalizedCodec(track.codecs)); } const hasDifferentAudioCodecs = (language) => diff --git a/ui/localization.js b/ui/localization.js index 50938a4fd..705bd44be 100644 --- a/ui/localization.js +++ b/ui/localization.js @@ -155,7 +155,7 @@ shaka.ui.Localization = class extends shaka.util.FakeEventTarget { // Make sure we have an entry for the locale because we are about to // write to it. - const table = this.localizations_.get(locale) || new Map(); + const table = this.localizations_.getOrInsert(locale, new Map()); localizations.forEach((value, id) => { // Set the value if we don't have an old value or if we are to replace // the old value with the new value. @@ -163,7 +163,6 @@ shaka.ui.Localization = class extends shaka.util.FakeEventTarget { table.set(id, value); } }); - this.localizations_.set(locale, table); // The data we use to make our map may have changed, update the map we pull // data from.