diff --git a/lib/media/media_source_capabilities.js b/lib/media/media_source_capabilities.js index 8c0e0f2f64..fa6c90cc58 100644 --- a/lib/media/media_source_capabilities.js +++ b/lib/media/media_source_capabilities.js @@ -27,6 +27,16 @@ shaka.media.Capabilities = class { supportMap.set(type, currentSupport); return currentSupport; } + + /** + * Determine support for SourceBuffer.changeType + * @return {boolean} + */ + static isChangeTypeSupported() { + return !!window.SourceBuffer && + // eslint-disable-next-line no-restricted-syntax + !!SourceBuffer.prototype && !!SourceBuffer.prototype.changeType; + } }; /** diff --git a/lib/media/media_source_engine.js b/lib/media/media_source_engine.js index 97d8087dd2..24dfadfda1 100644 --- a/lib/media/media_source_engine.js +++ b/lib/media/media_source_engine.js @@ -118,6 +118,9 @@ shaka.media.MediaSourceEngine = class { /** @private {MediaSource} */ this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_); + /** @private {boolean} */ + this.reloadingMediaSource_ = false; + /** @type {!shaka.util.Destroyer} */ this.destroyer_ = new shaka.util.Destroyer(() => this.doDestroy_()); @@ -372,8 +375,6 @@ shaka.media.MediaSourceEngine = class { async init(streamsByType, sequenceMode=false, manifestType=shaka.media.ManifestParser.UNKNOWN, ignoreManifestTimestampsInSegmentsMode=false) { - const ContentType = shaka.util.ManifestParserUtils.ContentType; - await this.mediaSourceOpen_; this.sequenceMode_ = sequenceMode; @@ -383,65 +384,78 @@ shaka.media.MediaSourceEngine = class { for (const contentType of streamsByType.keys()) { const stream = streamsByType.get(contentType); - goog.asserts.assert( - shaka.media.MediaSourceEngine.isStreamSupported(stream), - 'Type negotiation should happen before MediaSourceEngine.init!'); - - let mimeType = shaka.util.MimeUtils.getFullType( - stream.mimeType, stream.codecs); - if (contentType == ContentType.TEXT) { - this.reinitText(mimeType, sequenceMode); - } else { - let needTransmux = this.config_.forceTransmux; - if (!shaka.media.Capabilities.isTypeSupported(mimeType) || - (!sequenceMode && - shaka.util.MimeUtils.RAW_FORMATS.includes(mimeType))) { - needTransmux = true; - } - const mimeTypeWithAllCodecs = - shaka.util.MimeUtils.getFullTypeWithAllCodecs( - stream.mimeType, stream.codecs); - const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine; - if (needTransmux && - TransmuxerEngine.isSupported(mimeTypeWithAllCodecs, contentType)) { - const transmuxerPlugin = - TransmuxerEngine.findTransmuxer(mimeTypeWithAllCodecs); - if (transmuxerPlugin) { - const transmuxer = transmuxerPlugin(); - this.transmuxers_[contentType] = transmuxer; - mimeType = - transmuxer.convertCodecs(contentType, mimeTypeWithAllCodecs); - } - } - const type = mimeType + this.config_.sourceBufferExtraFeatures; + this.initSourceBuffer_(contentType, stream); + this.queues_[contentType] = []; + } + } - this.destroyer_.ensureNotDestroyed(); + /** + * Initialize a specific SourceBuffer. + * + * @param {shaka.util.ManifestParserUtils.ContentType} contentType + * @param {shaka.extern.Stream} stream + * @private + */ + initSourceBuffer_(contentType, stream) { + const ContentType = shaka.util.ManifestParserUtils.ContentType; - let sourceBuffer; + goog.asserts.assert( + shaka.media.MediaSourceEngine.isStreamSupported(stream), + 'Type negotiation should happen before MediaSourceEngine.init!'); - try { - sourceBuffer = this.mediaSource_.addSourceBuffer(type); - } catch (exception) { - throw new shaka.util.Error( - shaka.util.Error.Severity.CRITICAL, - shaka.util.Error.Category.MEDIA, - shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_THREW, - exception, - 'The mediaSource_ status was' + this.mediaSource_.readyState + - 'expected \'open\''); + let mimeType = shaka.util.MimeUtils.getFullType( + stream.mimeType, stream.codecs); + if (contentType == ContentType.TEXT) { + this.reinitText(mimeType, this.sequenceMode_); + } else { + let needTransmux = this.config_.forceTransmux; + if (!shaka.media.Capabilities.isTypeSupported(mimeType) || + (!this.sequenceMode_ && + shaka.util.MimeUtils.RAW_FORMATS.includes(mimeType))) { + needTransmux = true; + } + const mimeTypeWithAllCodecs = + shaka.util.MimeUtils.getFullTypeWithAllCodecs( + stream.mimeType, stream.codecs); + const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine; + if (needTransmux && + TransmuxerEngine.isSupported(mimeTypeWithAllCodecs, contentType)) { + const transmuxerPlugin = + TransmuxerEngine.findTransmuxer(mimeTypeWithAllCodecs); + if (transmuxerPlugin) { + const transmuxer = transmuxerPlugin(); + this.transmuxers_[contentType] = transmuxer; + mimeType = + transmuxer.convertCodecs(contentType, mimeTypeWithAllCodecs); } + } + const type = mimeType + this.config_.sourceBufferExtraFeatures; + + this.destroyer_.ensureNotDestroyed(); + + let sourceBuffer; - this.eventManager_.listen( - sourceBuffer, 'error', - () => this.onError_(contentType)); - this.eventManager_.listen( - sourceBuffer, 'updateend', - () => this.onUpdateEnd_(contentType)); - this.sourceBuffers_[contentType] = sourceBuffer; - this.sourceBufferTypes_[contentType] = mimeType; - this.queues_[contentType] = []; - this.expectedEncryption_[contentType] = !!stream.drmInfos.length; + try { + sourceBuffer = this.mediaSource_.addSourceBuffer(type); + } catch (exception) { + throw new shaka.util.Error( + shaka.util.Error.Severity.CRITICAL, + shaka.util.Error.Category.MEDIA, + shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_THREW, + exception, + 'The mediaSource_ status was' + this.mediaSource_.readyState + + 'expected \'open\''); } + + this.eventManager_.listen( + sourceBuffer, 'error', + () => this.onError_(contentType)); + this.eventManager_.listen( + sourceBuffer, 'updateend', + () => this.onUpdateEnd_(contentType)); + this.sourceBuffers_[contentType] = sourceBuffer; + this.sourceBufferTypes_[contentType] = mimeType; + this.expectedEncryption_[contentType] = !!stream.drmInfos.length; } } @@ -473,6 +487,9 @@ shaka.media.MediaSourceEngine = class { * object has been destroyed. */ ended() { + if (this.reloadingMediaSource_) { + return false; + } return this.mediaSource_ ? this.mediaSource_.readyState == 'ended' : true; } @@ -483,6 +500,9 @@ shaka.media.MediaSourceEngine = class { * @return {?number} The timestamp in seconds, or null if nothing is buffered. */ bufferStart(contentType) { + if (this.reloadingMediaSource_) { + return null; + } const ContentType = shaka.util.ManifestParserUtils.ContentType; if (contentType == ContentType.TEXT) { return this.textEngine_.bufferStart(); @@ -498,6 +518,9 @@ shaka.media.MediaSourceEngine = class { * @return {?number} The timestamp in seconds, or null if nothing is buffered. */ bufferEnd(contentType) { + if (this.reloadingMediaSource_) { + return null; + } const ContentType = shaka.util.ManifestParserUtils.ContentType; if (contentType == ContentType.TEXT) { return this.textEngine_.bufferEnd(); @@ -515,6 +538,9 @@ shaka.media.MediaSourceEngine = class { * @return {boolean} */ isBuffered(contentType, time) { + if (this.reloadingMediaSource_) { + return false; + } const ContentType = shaka.util.ManifestParserUtils.ContentType; if (contentType == ContentType.TEXT) { return this.textEngine_.isBuffered(time); @@ -533,6 +559,9 @@ shaka.media.MediaSourceEngine = class { * @return {number} The amount of time buffered ahead in seconds. */ bufferedAheadOf(contentType, time) { + if (this.reloadingMediaSource_) { + return 0; + } const ContentType = shaka.util.ManifestParserUtils.ContentType; if (contentType == ContentType.TEXT) { return this.textEngine_.bufferedAheadOf(time); @@ -551,11 +580,12 @@ shaka.media.MediaSourceEngine = class { const TimeRangesUtils = shaka.media.TimeRangesUtils; const info = { - total: TimeRangesUtils.getBufferedInfo(this.video_.buffered), - audio: TimeRangesUtils.getBufferedInfo( - this.getBuffered_(ContentType.AUDIO)), - video: TimeRangesUtils.getBufferedInfo( - this.getBuffered_(ContentType.VIDEO)), + total: this.reloadingMediaSource_ ? [] : + TimeRangesUtils.getBufferedInfo(this.video_.buffered), + audio: this.reloadingMediaSource_ ? [] : + TimeRangesUtils.getBufferedInfo(this.getBuffered_(ContentType.AUDIO)), + video: this.reloadingMediaSource_ ? [] : + TimeRangesUtils.getBufferedInfo(this.getBuffered_(ContentType.VIDEO)), text: [], }; @@ -1002,11 +1032,17 @@ shaka.media.MediaSourceEngine = class { * value will be dropped. * @param {boolean} sequenceMode If true, the timestampOffset will not be * applied in this step. + * @param {shaka.extern.Stream} stream The current stream. + * @param {!Map.} streamsByType + * A map of content types to streams. All streams must be supported + * according to MediaSourceEngine.isStreamSupported. + * * @return {!Promise} */ async setStreamProperties( contentType, timestampOffset, appendWindowStart, appendWindowEnd, - sequenceMode) { + sequenceMode, stream, streamsByType) { const ContentType = shaka.util.ManifestParserUtils.ContentType; if (contentType == ContentType.TEXT) { if (!sequenceMode) { @@ -1015,6 +1051,8 @@ shaka.media.MediaSourceEngine = class { this.textEngine_.setAppendWindow(appendWindowStart, appendWindowEnd); return; } + const hasChangedCodecs = + await this.codecSwitchIfNecessary_(contentType, stream, streamsByType); await Promise.all([ // Queue an abort() to help MSE splice together overlapping segments. @@ -1025,7 +1063,7 @@ shaka.media.MediaSourceEngine = class { // always enter a PARSING_MEDIA_SEGMENT state and we can't change the // timestamp offset. By calling abort(), we reset the state so we can // set it. - this.enqueueOperation_( + hasChangedCodecs ? Promise.resolve() : this.enqueueOperation_( contentType, () => this.abort_(contentType)), // Don't set the timestampOffset here when in sequenceMode, since we @@ -1281,6 +1319,9 @@ shaka.media.MediaSourceEngine = class { * @private */ onUpdateEnd_(contentType) { + if (this.reloadingMediaSource_) { + return; + } const operation = this.queues_[contentType][0]; goog.asserts.assert(operation, 'Spurious updateend event!'); if (!operation) { @@ -1319,7 +1360,7 @@ shaka.media.MediaSourceEngine = class { * Enqueue an operation which must block all other operations on all * SourceBuffers. * - * @param {function()} run + * @param {function():(Promise|undefined)} run * @return {!Promise} * @private */ @@ -1385,9 +1426,9 @@ shaka.media.MediaSourceEngine = class { } } - // Run the real operation, which is synchronous. + // Run the real operation, which can be asynchronous. try { - run(); + await run(); } catch (exception) { throw new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, @@ -1513,6 +1554,183 @@ shaka.media.MediaSourceEngine = class { return segment; } + /** + * Prepare the SourceBuffer to parse a potentially new type or codec. + * + * @param {shaka.util.ManifestParserUtils.ContentType} contentType + * @param {string} mimeType + * @param {?shaka.extern.Transmuxer} transmuxer + * @private + */ + change_(contentType, mimeType, transmuxer) { + const ContentType = shaka.util.ManifestParserUtils.ContentType; + if (contentType === ContentType.TEXT) { + shaka.log.debug(`Change not supported for ${contentType}`); + return; + } + shaka.log.debug( + `Change Type: ${this.sourceBufferTypes_[contentType]} -> ${mimeType}`); + if (shaka.media.Capabilities.isChangeTypeSupported()) { + if (this.transmuxers_[contentType]) { + this.transmuxers_[contentType].destroy(); + } + if (transmuxer) { + this.transmuxers_[contentType] = transmuxer; + } + const type = mimeType + this.config_.sourceBufferExtraFeatures; + this.sourceBuffers_[contentType].changeType(type); + this.sourceBufferTypes_[contentType] = mimeType; + } else { + shaka.log.debug('Change Type not supported'); + } + + // Fake an 'updateend' event to resolve the operation. + this.onUpdateEnd_(contentType); + } + + /** + * Enqueue an operation to prepare the SourceBuffer to parse a potentially new + * type or codec. + * + * @param {shaka.util.ManifestParserUtils.ContentType} contentType + * @param {string} mimeType + * @param {?shaka.extern.Transmuxer} transmuxer + * @return {!Promise} + */ + changeType(contentType, mimeType, transmuxer) { + return this.enqueueOperation_( + contentType, + () => this.change_(contentType, mimeType, transmuxer)); + } + + /** + * Resets the MediaSource and re-adds source buffers due to codec mismatch + * + * @param {!Map.} streamsByType + * @private + */ + async reset_(streamsByType) { + this.reloadingMediaSource_ = true; + const currentTime = this.video_.currentTime; + try { + this.eventManager_.removeAll(); + this.sourceBuffers_ = {}; + + const cleanup = []; + for (const contentType in this.transmuxers_) { + cleanup.push(this.transmuxers_[contentType].destroy()); + } + await Promise.all(cleanup); + this.transmuxers_ = {}; + + const previousDuration = this.mediaSource_.duration; + this.mediaSourceOpen_ = new shaka.util.PublicPromise(); + this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_); + await this.mediaSourceOpen_; + this.mediaSource_.duration = previousDuration; + + const sourceBufferAdded = new shaka.util.PublicPromise(); + const sourceBuffers = + /** @type {EventTarget} */(this.mediaSource_.sourceBuffers); + + const totalOfBuffers = streamsByType.size; + let numberOfSourceBufferAdded = 0; + this.eventManager_.listen(sourceBuffers, 'addsourcebuffer', (event) => { + numberOfSourceBufferAdded++; + if (numberOfSourceBufferAdded === totalOfBuffers) { + sourceBufferAdded.resolve(); + } + }); + + for (const contentType of streamsByType.keys()) { + const stream = streamsByType.get(contentType); + this.initSourceBuffer_(contentType, stream); + } + + // Fake a seek to catchup the playhead. + this.video_.currentTime = currentTime; + + await sourceBufferAdded; + } finally { + this.reloadingMediaSource_ = false; + } + } + + /** + * Resets the Media Source + * @param {!Map.} streamsByType + * @return {!Promise} + */ + reset(streamsByType) { + return this.enqueueBlockingOperation_( + () => this.reset_(streamsByType)); + } + + /** + * Codec switch if necessary, this will not resolve until the codec + * switch is over. + * @param {shaka.util.ManifestParserUtils.ContentType} contentType + * @param {shaka.extern.Stream} stream + * @param {!Map.} streamsByType + * @return {!Promise.} true if there was a codec switch, + * false otherwise. + * @private + */ + async codecSwitchIfNecessary_(contentType, stream, streamsByType) { + const MimeUtils = shaka.util.MimeUtils; + const currentCodec = MimeUtils.getCodecBase( + MimeUtils.getCodecs(this.sourceBufferTypes_[contentType])); + const currentBasicType = MimeUtils.getBasicType( + this.sourceBufferTypes_[contentType]); + + /** @type {?shaka.extern.Transmuxer} */ + let transmuxer; + let newMimeType = shaka.util.MimeUtils.getFullType( + stream.mimeType, stream.codecs); + let needTransmux = this.config_.forceTransmux; + if (!shaka.media.Capabilities.isTypeSupported(newMimeType) || + (!this.sequenceMode_ && + shaka.util.MimeUtils.RAW_FORMATS.includes(newMimeType))) { + needTransmux = true; + } + const newMimeTypeWithAllCodecs = + shaka.util.MimeUtils.getFullTypeWithAllCodecs( + stream.mimeType, stream.codecs); + const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine; + if (needTransmux && + TransmuxerEngine.isSupported(newMimeTypeWithAllCodecs, contentType)) { + const transmuxerPlugin = + TransmuxerEngine.findTransmuxer(newMimeTypeWithAllCodecs); + if (transmuxerPlugin) { + transmuxer = transmuxerPlugin(); + newMimeType = + transmuxer.convertCodecs(contentType, newMimeTypeWithAllCodecs); + } + } + + const newCodec = MimeUtils.getCodecBase( + MimeUtils.getCodecs(newMimeType)); + const newBasicType = MimeUtils.getBasicType(newMimeType); + + // Current/new codecs base and basic type match then no need to switch + if (currentCodec === newCodec && currentBasicType === newBasicType) { + return false; + } + + if (shaka.media.Capabilities.isChangeTypeSupported()) { + await this.changeType(contentType, newMimeType, transmuxer); + } else { + if (transmuxer) { + transmuxer.destroy(); + } + await this.reset(streamsByType); + } + return true; + } + /** * Update LCEVC DIL object when ready for LCEVC Decodes * @param {?shaka.lcevc.Dil} lcevcDil diff --git a/lib/media/streaming_engine.js b/lib/media/streaming_engine.js index 832ab9c3b2..822481a704 100644 --- a/lib/media/streaming_engine.js +++ b/lib/media/streaming_engine.js @@ -1645,6 +1645,8 @@ shaka.media.StreamingEngine = class { * @private */ async initSourceBuffer_(mediaState, reference) { + const ContentType = shaka.util.ManifestParserUtils.ContentType; + const MimeUtils = shaka.util.MimeUtils; const StreamingEngine = shaka.media.StreamingEngine; const logPrefix = StreamingEngine.logPrefix_(mediaState); @@ -1663,28 +1665,66 @@ shaka.media.StreamingEngine = class { reference.startTime <= appendWindowEnd, logPrefix + ' segment should start before append window end'); + const codecs = MimeUtils.getCodecBase(mediaState.stream.codecs); + const mimeType = MimeUtils.getBasicType(mediaState.stream.mimeType); const timestampOffset = reference.timestampOffset; if (timestampOffset != mediaState.lastTimestampOffset || appendWindowStart != mediaState.lastAppendWindowStart || - appendWindowEnd != mediaState.lastAppendWindowEnd) { + appendWindowEnd != mediaState.lastAppendWindowEnd || + codecs != mediaState.lastCodecs || + mimeType != mediaState.lastMimeType) { shaka.log.v1(logPrefix, 'setting timestamp offset to ' + timestampOffset); shaka.log.v1(logPrefix, 'setting append window start to ' + appendWindowStart); shaka.log.v1(logPrefix, 'setting append window end to ' + appendWindowEnd); + if ((mediaState.lastCodecs && codecs != mediaState.lastCodecs) || + (mediaState.lastMimeType && mimeType != mediaState.lastMimeType)) { + if (mediaState.type === ContentType.VIDEO) { + /** @type {shaka.media.StreamingEngine.MediaState_} */ + const audioState = this.mediaStates_.get(ContentType.AUDIO); + if (audioState) { + audioState.lastInitSegmentReference = null; + this.abortOperations_(audioState); + } + } else if (mediaState.type === ContentType.AUDIO) { + /** @type {shaka.media.StreamingEngine.MediaState_} */ + const videoState = this.mediaStates_.get(ContentType.VIDEO); + if (videoState) { + videoState.lastInitSegmentReference = null; + this.abortOperations_(videoState); + } + } + } + const setProperties = async () => { + /** + * @type {!Map.} + */ + const streamsByType = new Map(); + if (this.currentVariant_.audio) { + streamsByType.set(ContentType.AUDIO, this.currentVariant_.audio); + } + if (this.currentVariant_.video) { + streamsByType.set(ContentType.VIDEO, this.currentVariant_.video); + } try { mediaState.lastAppendWindowStart = appendWindowStart; mediaState.lastAppendWindowEnd = appendWindowEnd; + mediaState.lastCodecs = codecs; + mediaState.lastMimeType = mimeType; mediaState.lastTimestampOffset = timestampOffset; await this.playerInterface_.mediaSourceEngine.setStreamProperties( mediaState.type, timestampOffset, appendWindowStart, - appendWindowEnd, this.manifest_.sequenceMode); + appendWindowEnd, this.manifest_.sequenceMode, + mediaState.stream, streamsByType); } catch (error) { mediaState.lastAppendWindowStart = null; mediaState.lastAppendWindowEnd = null; + mediaState.lastCodecs = null; mediaState.lastTimestampOffset = null; throw error; @@ -2421,6 +2461,8 @@ shaka.media.StreamingEngine.PlayerInterface; * lastTimestampOffset: ?number, * lastAppendWindowStart: ?number, * lastAppendWindowEnd: ?number, + * lastCodecs: ?string, + * lastMimeType: ?string, * restoreStreamAfterTrickPlay: ?shaka.extern.Stream, * endOfStream: boolean, * performingUpdate: boolean, @@ -2459,6 +2501,10 @@ shaka.media.StreamingEngine.PlayerInterface; * The last append window start given to MediaSourceEngine for this type. * @property {?number} lastAppendWindowEnd * The last append window end given to MediaSourceEngine for this type. + * @property {?string} lastCodecs + * The last append codecs given to MediaSourceEngine for this type. + * @property {?string} lastMimeType + * The last append mime type given to MediaSourceEngine for this type. * @property {?shaka.extern.Stream} restoreStreamAfterTrickPlay * The Stream to restore after trick play mode is turned off. * @property {boolean} endOfStream diff --git a/test/media/media_source_engine_integration.js b/test/media/media_source_engine_integration.js index c6788eb568..75290c3fe5 100644 --- a/test/media/media_source_engine_integration.js +++ b/test/media/media_source_engine_integration.js @@ -447,7 +447,9 @@ describe('MediaSourceEngine', () => { /* timestampOffset= */ 0, /* appendWindowStart= */ 5, /* appendWindowEnd= */ 18, - /* sequenceMode= */ false); + /* sequenceMode= */ false, + fakeStream, + /* streamsByType= */ new Map()); expect(buffered(ContentType.VIDEO, 0)).toBe(0); await append(ContentType.VIDEO, 0); expect(bufferStart(ContentType.VIDEO)).toBeCloseTo(5, 1); @@ -466,7 +468,9 @@ describe('MediaSourceEngine', () => { /* timestampOffset= */ 100, /* appendWindowStart= */ 5, /* appendWindowEnd= */ 18, - /* sequenceMode= */ true); + /* sequenceMode= */ true, + fakeStream, + /* streamsByType= */ new Map()); expect(buffered(ContentType.VIDEO, 0)).toBe(0); await append(ContentType.VIDEO, 0); expect(bufferStart(ContentType.VIDEO)).toBeCloseTo(5, 1); @@ -486,7 +490,9 @@ describe('MediaSourceEngine', () => { /* timestampOffset= */ 0, /* appendWindowStart= */ 0, /* appendWindowEnd= */ 20, - /* sequenceMode= */ false); + /* sequenceMode= */ false, + fakeStream, + /* streamsByType= */ new Map()); await append(ContentType.VIDEO, 0); await append(ContentType.VIDEO, 1); expect(bufferStart(ContentType.VIDEO)).toBeCloseTo(0, 1); @@ -499,7 +505,9 @@ describe('MediaSourceEngine', () => { /* timestampOffset= */ 15, /* appendWindowStart= */ 20, /* appendWindowEnd= */ 35, - /* sequenceMode= */ false); + /* sequenceMode= */ false, + fakeStream, + /* streamsByType= */ new Map()); await append(ContentType.VIDEO, 0); await append(ContentType.VIDEO, 1); expect(bufferStart(ContentType.VIDEO)).toBeCloseTo(0, 1); @@ -573,7 +581,9 @@ describe('MediaSourceEngine', () => { /* timestampOffset= */ 0, /* appendWindowStart= */ 0, /* appendWindowEnd= */ Infinity, - /* sequenceMode= */ true); + /* sequenceMode= */ true, + fakeStream, + /* streamsByType= */ new Map()); const segment = generators[videoType].getSegment(0, Date.now() / 1000); const partialSegmentLength = Math.floor(segment.byteLength / 3); diff --git a/test/media/media_source_engine_unit.js b/test/media/media_source_engine_unit.js index 890a3a776d..3190430e6c 100644 --- a/test/media/media_source_engine_unit.js +++ b/test/media/media_source_engine_unit.js @@ -916,7 +916,9 @@ describe('MediaSourceEngine', () => { /* timestampOffset= */ 10, /* appendWindowStart= */ 0, /* appendWindowEnd= */ 20, - /* sequenceMode= */ false); + /* sequenceMode= */ false, + fakeStream, + /* streamsByType= */ new Map()); expect(mockTextEngine.setTimestampOffset).toHaveBeenCalledWith(10); expect(mockTextEngine.setAppendWindow).toHaveBeenCalledWith(0, 20); }); diff --git a/test/media/streaming_engine_unit.js b/test/media/streaming_engine_unit.js index 9ea8eacce6..352a314784 100644 --- a/test/media/streaming_engine_unit.js +++ b/test/media/streaming_engine_unit.js @@ -841,8 +841,13 @@ describe('StreamingEngine', () => { const gt40 = { asymmetricMatch: (val) => val > 40 && val <= 40.1, }; + const streamsByType = new Map(); + streamsByType.set(ContentType.AUDIO, audioStream); + streamsByType.set(ContentType.VIDEO, videoStream); + expect(mediaSourceEngine.setStreamProperties) - .toHaveBeenCalledWith('video', 0, lt20, gt40, false); + .toHaveBeenCalledWith('video', 0, lt20, gt40, false, + videoStream, streamsByType); }); // Regression test for https://github.com/shaka-project/shaka-player/issues/3717