diff --git a/build/types/complete b/build/types/complete index c549a23680..99092a3eb3 100644 --- a/build/types/complete +++ b/build/types/complete @@ -7,5 +7,6 @@ +@manifests +@polyfill +@text ++@transmuxer +@ui +@lcevc diff --git a/build/types/core b/build/types/core index f9f4554860..d3146906d1 100644 --- a/build/types/core +++ b/build/types/core @@ -39,7 +39,6 @@ +../../lib/media/stall_detector.js +../../lib/media/streaming_engine.js +../../lib/media/time_ranges_utils.js -+../../lib/media/transmuxer.js +../../lib/media/video_wrapper.js +../../lib/media/webm_segment_index_parser.js @@ -60,6 +59,8 @@ +../../lib/text/ui_text_displayer.js +../../lib/text/web_vtt_generator.js ++../../lib/transmuxer/transmuxer_engine.js + +../../lib/util/abortable_operation.js +../../lib/util/array_utils.js +../../lib/util/buffer_utils.js diff --git a/build/types/transmuxer b/build/types/transmuxer new file mode 100644 index 0000000000..f899f05f1a --- /dev/null +++ b/build/types/transmuxer @@ -0,0 +1,3 @@ +# Optional plugins related to transmuxer. + ++../../lib/transmuxer/muxjs_transmuxer.js diff --git a/externs/shaka/transmuxer.js b/externs/shaka/transmuxer.js new file mode 100644 index 0000000000..09a3401a33 --- /dev/null +++ b/externs/shaka/transmuxer.js @@ -0,0 +1,48 @@ +/** + * An interface for transmuxer plugins. + * + * @interface + * @exportDoc + */ +shaka.extern.Transmuxer = class { + /** + * Destroy + */ + destroy() {} + + /** + * Check if the mime type and the content type is supported. + * @param {string} mimeType + * @param {string=} contentType + * @return {boolean} + */ + isSupported(mimeType, contentType) {} + + /** + * For any stream, convert its codecs to MP4 codecs. + * @param {string} contentType + * @param {string} mimeType + * @return {string} + */ + convertCodecs(contentType, mimeType) {} + + /** + * Returns the original mimetype of the transmuxer. + * @return {string} + */ + getOrginalMimeType() {} + + /** + * Transmux a input data to MP4. + * @param {BufferSource} data + * @return {!Promise.} + */ + transmux(data) {} +}; + + +/** + * @typedef {function():!shaka.extern.Transmuxer} + * @exportDoc + */ +shaka.extern.TransmuxerPlugin; diff --git a/lib/media/drm_engine.js b/lib/media/drm_engine.js index 6417fa5e83..6c21be5942 100644 --- a/lib/media/drm_engine.js +++ b/lib/media/drm_engine.js @@ -8,8 +8,8 @@ goog.provide('shaka.media.DrmEngine'); goog.require('goog.asserts'); goog.require('shaka.log'); -goog.require('shaka.media.Transmuxer'); goog.require('shaka.net.NetworkingEngine'); +goog.require('shaka.transmuxer.TransmuxerEngine'); goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.Destroyer'); goog.require('shaka.util.Error'); @@ -842,10 +842,11 @@ shaka.media.DrmEngine = class { static computeMimeType_(stream, codecOverride) { const realMimeType = shaka.util.MimeUtils.getFullType(stream.mimeType, codecOverride || stream.codecs); - if (shaka.media.Transmuxer.isSupported(realMimeType)) { + const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine; + if (TransmuxerEngine.isSupported(realMimeType, stream.type)) { // This will be handled by the Transmuxer, so use the MIME type that the // Transmuxer will produce. - return shaka.media.Transmuxer.convertCodecs(stream.type, realMimeType); + return TransmuxerEngine.convertCodecs(stream.type, realMimeType); } return realMimeType; } diff --git a/lib/media/media_source_engine.js b/lib/media/media_source_engine.js index 466a142b7d..e8ce3aebed 100644 --- a/lib/media/media_source_engine.js +++ b/lib/media/media_source_engine.js @@ -14,8 +14,8 @@ goog.require('shaka.media.ClosedCaptionParser'); goog.require('shaka.media.IClosedCaptionParser'); goog.require('shaka.media.SegmentReference'); goog.require('shaka.media.TimeRangesUtils'); -goog.require('shaka.media.Transmuxer'); goog.require('shaka.text.TextEngine'); +goog.require('shaka.transmuxer.TransmuxerEngine'); goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.Destroyer'); goog.require('shaka.util.Error'); @@ -100,7 +100,7 @@ shaka.media.MediaSourceEngine = class { /** @private {shaka.util.EventManager} */ this.eventManager_ = new shaka.util.EventManager(); - /** @private {!Object.} */ + /** @private {!Object.} */ this.transmuxers_ = {}; /** @private {?shaka.media.IClosedCaptionParser} */ @@ -175,9 +175,10 @@ shaka.media.MediaSourceEngine = class { const fullMimeType = shaka.util.MimeUtils.getFullType( stream.mimeType, stream.codecs); const extendedMimeType = shaka.util.MimeUtils.getExtendedType(stream); + const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine; return shaka.text.TextEngine.isTypeSupported(fullMimeType) || shaka.media.Capabilities.isTypeSupported(extendedMimeType) || - shaka.media.Transmuxer.isSupported(fullMimeType, stream.type); + TransmuxerEngine.isSupported(fullMimeType, stream.type); } /** @@ -233,7 +234,7 @@ shaka.media.MediaSourceEngine = class { support[type] = true; } else { support[type] = shaka.media.Capabilities.isTypeSupported(type) || - shaka.media.Transmuxer.isSupported(type); + shaka.transmuxer.TransmuxerEngine.isSupported(type); } } else { support[type] = shaka.util.Platform.supportsMediaType(type); @@ -364,13 +365,16 @@ shaka.media.MediaSourceEngine = class { this.reinitText(mimeType, sequenceMode); } else { const forceTransmux = this.config_.forceTransmux; + const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine; if ((forceTransmux || !shaka.media.Capabilities.isTypeSupported(mimeType)) && - shaka.media.Transmuxer.isSupported(mimeType, contentType)) { - this.transmuxers_[contentType] = - new shaka.media.Transmuxer(mimeType); - mimeType = - shaka.media.Transmuxer.convertCodecs(contentType, mimeType); + TransmuxerEngine.isSupported(mimeType, contentType)) { + const transmuxerPlugin = TransmuxerEngine.findTransmuxer(mimeType); + if (transmuxerPlugin) { + const transmuxer = transmuxerPlugin(); + this.transmuxers_[contentType] = transmuxer; + mimeType = transmuxer.convertCodecs(contentType, mimeType); + } } const type = mimeType + this.config_.sourceBufferExtraFeatures; const sourceBuffer = this.mediaSource_.addSourceBuffer(type); diff --git a/lib/media/transmuxer.js b/lib/transmuxer/muxjs_transmuxer.js similarity index 77% rename from lib/media/transmuxer.js rename to lib/transmuxer/muxjs_transmuxer.js index 18b07ca853..57684cebe1 100644 --- a/lib/media/transmuxer.js +++ b/lib/transmuxer/muxjs_transmuxer.js @@ -4,13 +4,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -goog.provide('shaka.media.Transmuxer'); +goog.provide('shaka.transmuxer.MuxjsTransmuxer'); goog.require('goog.asserts'); goog.require('shaka.media.Capabilities'); +goog.require('shaka.transmuxer.TransmuxerEngine'); goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.Error'); -goog.require('shaka.util.IDestroyable'); goog.require('shaka.util.ManifestParserUtils'); goog.require('shaka.util.PublicPromise'); goog.require('shaka.util.Uint8ArrayUtils'); @@ -18,12 +18,10 @@ goog.require('shaka.dependencies'); /** - * Transmuxer provides all operations for transmuxing from Transport - * Stream or AAC to MP4. - * - * @implements {shaka.util.IDestroyable} + * @implements {shaka.extern.Transmuxer} + * @export */ -shaka.media.Transmuxer = class { +shaka.transmuxer.MuxjsTransmuxer = class { /** * @param {string} mimeType */ @@ -53,47 +51,48 @@ shaka.media.Transmuxer = class { this.muxTransmuxer_.on('done', () => this.onTransmuxDone_()); } + /** * @override + * @export */ destroy() { this.muxTransmuxer_.dispose(); this.muxTransmuxer_ = null; - return Promise.resolve(); } /** - * Check if the content type is Transport Stream or AAC, and if muxjs is - * loaded. + * Check if the mime type and the content type is supported. * @param {string} mimeType * @param {string=} contentType * @return {boolean} + * @override + * @export */ - static isSupported(mimeType, contentType) { - const Transmuxer = shaka.media.Transmuxer; + isSupported(mimeType, contentType) { const Capabilities = shaka.media.Capabilities; - const isTs = Transmuxer.isTsContainer_(mimeType); - const isAac = Transmuxer.isAacContainer_(mimeType); + const isTs = this.isTsContainer_(mimeType); + const isAac = this.isAacContainer_(mimeType); if (!shaka.dependencies.muxjs() || (!isTs && !isAac)) { return false; } if (isAac) { - return Capabilities.isTypeSupported(Transmuxer.convertAacCodecs_()); + return Capabilities.isTypeSupported(this.convertAacCodecs_()); } if (contentType) { return Capabilities.isTypeSupported( - Transmuxer.convertTsCodecs_(contentType, mimeType)); + this.convertTsCodecs_(contentType, mimeType)); } const ContentType = shaka.util.ManifestParserUtils.ContentType; - const audioMime = Transmuxer.convertTsCodecs_(ContentType.AUDIO, mimeType); - const videoMime = Transmuxer.convertTsCodecs_(ContentType.VIDEO, mimeType); + const audioMime = this.convertTsCodecs_(ContentType.AUDIO, mimeType); + const videoMime = this.convertTsCodecs_(ContentType.VIDEO, mimeType); return Capabilities.isTypeSupported(audioMime) || Capabilities.isTypeSupported(videoMime); } @@ -105,7 +104,7 @@ shaka.media.Transmuxer = class { * @return {boolean} * @private */ - static isAacContainer_(mimeType) { + isAacContainer_(mimeType) { return mimeType.toLowerCase().split(';')[0] == 'audio/aac'; } @@ -116,23 +115,20 @@ shaka.media.Transmuxer = class { * @return {boolean} * @private */ - static isTsContainer_(mimeType) { + isTsContainer_(mimeType) { return mimeType.toLowerCase().split(';')[0].split('/')[1] == 'mp2t'; } /** - * For any stream, convert its codecs to MP4 codecs. - * @param {string} contentType - * @param {string} mimeType - * @return {string} + * @override + * @export */ - static convertCodecs(contentType, mimeType) { - const Transmuxer = shaka.media.Transmuxer; - if (Transmuxer.isAacContainer_(mimeType)) { - return Transmuxer.convertAacCodecs_(); - } else if (Transmuxer.isTsContainer_(mimeType)) { - return Transmuxer.convertTsCodecs_(contentType, mimeType); + convertCodecs(contentType, mimeType) { + if (this.isAacContainer_(mimeType)) { + return this.convertAacCodecs_(); + } else if (this.isTsContainer_(mimeType)) { + return this.convertTsCodecs_(contentType, mimeType); } return mimeType; } @@ -143,7 +139,7 @@ shaka.media.Transmuxer = class { * @return {string} * @private */ - static convertAacCodecs_() { + convertAacCodecs_() { return 'audio/mp4; codecs="mp4a.40.2"'; } @@ -155,7 +151,7 @@ shaka.media.Transmuxer = class { * @return {string} * @private */ - static convertTsCodecs_(contentType, tsMimeType) { + convertTsCodecs_(contentType, tsMimeType) { const ContentType = shaka.util.ManifestParserUtils.ContentType; let mp4MimeType = tsMimeType.replace(/mp2t/i, 'mp4'); if (contentType == ContentType.AUDIO) { @@ -198,8 +194,8 @@ shaka.media.Transmuxer = class { /** - * Returns the original mimetype of the transmuxer. - * @return {string} + * @override + * @export */ getOrginalMimeType() { return this.originalMimeType_; @@ -207,9 +203,8 @@ shaka.media.Transmuxer = class { /** - * Transmux from Transport stream to MP4, using the mux.js library. - * @param {BufferSource} data - * @return {!Promise.} + * @override + * @export */ transmux(data) { goog.asserts.assert(!this.isTransmuxing_, @@ -263,3 +258,11 @@ shaka.media.Transmuxer = class { } }; +shaka.transmuxer.TransmuxerEngine.registerTransmuxer( + 'audio/aac', + () => new shaka.transmuxer.MuxjsTransmuxer('audio/aac'), + shaka.transmuxer.TransmuxerEngine.PluginPriority.FALLBACK); +shaka.transmuxer.TransmuxerEngine.registerTransmuxer( + 'video/mp2t', + () => new shaka.transmuxer.MuxjsTransmuxer('video/mp2t'), + shaka.transmuxer.TransmuxerEngine.PluginPriority.FALLBACK); diff --git a/lib/transmuxer/transmuxer_engine.js b/lib/transmuxer/transmuxer_engine.js new file mode 100644 index 0000000000..876043e44b --- /dev/null +++ b/lib/transmuxer/transmuxer_engine.js @@ -0,0 +1,146 @@ +/*! @license + * Shaka Player + * Copyright 2016 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.provide('shaka.transmuxer.TransmuxerEngine'); + +goog.require('goog.asserts'); +goog.require('shaka.util.IDestroyable'); + + +// TODO: revisit this when Closure Compiler supports partially-exported classes. +/** + * @summary Manages transmuxer plugins. + * @implements {shaka.util.IDestroyable} + * @export + */ +shaka.transmuxer.TransmuxerEngine = class { + // TODO: revisit this when the compiler supports partially-exported classes. + /** + * @override + * @export + */ + destroy() {} + + /** + * @param {string} mimeType + * @param {!shaka.extern.TransmuxerPlugin} plugin + * @export + */ + static registerTransmuxer(mimeType, plugin, priority) { + const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine; + goog.asserts.assert(priority == undefined || priority > 0, + 'explicit priority must be > 0'); + const mimeTypeNormalizated = TransmuxerEngine.normalizeMimeType_(mimeType); + const existing = TransmuxerEngine.transmuxerMap_[mimeTypeNormalizated]; + if (!existing || priority >= existing.priority) { + TransmuxerEngine.transmuxerMap_[mimeTypeNormalizated] = { + priority: priority, + plugin: plugin, + }; + } + } + + /** + * @param {string} mimeType + * @export + */ + static unregisterTransmuxer(mimeType) { + const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine; + const mimeTypeNormalizated = TransmuxerEngine.normalizeMimeType_(mimeType); + delete TransmuxerEngine.transmuxerMap_[mimeTypeNormalizated]; + } + + /** + * @return {?shaka.extern.TransmuxerPlugin} + * @export + */ + static findTransmuxer(mimeType) { + const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine; + const mimeTypeNormalizated = TransmuxerEngine.normalizeMimeType_(mimeType); + const object = TransmuxerEngine.transmuxerMap_[mimeTypeNormalizated]; + return object ? object.plugin : null; + } + + /** + * @param {string} mimeType + * @return {string} + * @private + */ + static normalizeMimeType_(mimeType) { + return mimeType.toLowerCase().split(';')[0]; + } + + /** + * Check if the mime type and the content type is supported. + * @param {string} mimeType + * @param {string=} contentType + * @return {boolean} + */ + static isSupported(mimeType, contentType) { + const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine; + const transmuxerPlugin = TransmuxerEngine.findTransmuxer(mimeType); + if (!transmuxerPlugin) { + return false; + } + const transmuxer = transmuxerPlugin(); + const isSupported = transmuxer.isSupported(mimeType, contentType); + transmuxer.destroy(); + return isSupported; + } + + /** + * For any stream, convert its codecs to MP4 codecs. + * @param {string} contentType + * @param {string} mimeType + * @return {string} + */ + static convertCodecs(contentType, mimeType) { + const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine; + const transmuxerPlugin = TransmuxerEngine.findTransmuxer(mimeType); + if (!transmuxerPlugin) { + return mimeType; + } + const transmuxer = transmuxerPlugin(); + const codecs = transmuxer.convertCodecs(contentType, mimeType); + transmuxer.destroy(); + return codecs; + } +}; + + +/** + * @typedef {{ + * plugin: shaka.extern.TransmuxerPlugin, + * priority: number + * }} + * @property {shaka.extern.TransmuxerPlugin} plugin + * The associated plugin. + * @property {number} priority + * The plugin's priority. + */ +shaka.transmuxer.TransmuxerEngine.PluginObject; + + +/** + * @private {!Object.} + */ +shaka.transmuxer.TransmuxerEngine.transmuxerMap_ = {}; + + +/** + * Priority level for transmuxer plugins. + * If multiple plugins are provided for the same mime type, only the + * highest-priority one is used. + * + * @enum {number} + * @export + */ +shaka.transmuxer.TransmuxerEngine.PluginPriority = { + 'FALLBACK': 1, + 'PREFERRED': 2, + 'APPLICATION': 3, +}; + diff --git a/lib/util/mime_utils.js b/lib/util/mime_utils.js index e1f934bab6..0f3dcd2a06 100644 --- a/lib/util/mime_utils.js +++ b/lib/util/mime_utils.js @@ -6,7 +6,7 @@ goog.provide('shaka.util.MimeUtils'); -goog.require('shaka.media.Transmuxer'); +goog.require('shaka.transmuxer.TransmuxerEngine'); goog.require('shaka.util.ManifestParserUtils'); /** @@ -45,10 +45,10 @@ shaka.util.MimeUtils = class { static getFullOrConvertedType(mimeType, codecs, contentType) { const fullMimeType = shaka.util.MimeUtils.getFullType(mimeType, codecs); const ContentType = shaka.util.ManifestParserUtils.ContentType; - const Transmuxer = shaka.media.Transmuxer; + const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine; - if (Transmuxer.isSupported(fullMimeType, contentType)) { - return shaka.media.Transmuxer.convertCodecs(contentType, fullMimeType); + if (TransmuxerEngine.isSupported(fullMimeType, contentType)) { + return TransmuxerEngine.convertCodecs(contentType, fullMimeType); } else if (contentType == ContentType.AUDIO) { // video/mp2t is the correct mime type for TS audio, so only replace the // word "video" with "audio" for non-TS audio content. diff --git a/shaka-player.uncompiled.js b/shaka-player.uncompiled.js index 880f0c4d79..2cbd8398b5 100644 --- a/shaka-player.uncompiled.js +++ b/shaka-player.uncompiled.js @@ -61,6 +61,8 @@ goog.require('shaka.text.SsaTextParser'); goog.require('shaka.text.TtmlTextParser'); goog.require('shaka.text.VttTextParser'); goog.require('shaka.text.WebVttGenerator'); +goog.require('shaka.transmuxer.TransmuxerEngine'); +goog.require('shaka.transmuxer.MuxjsTransmuxer'); goog.require('shaka.ui.Controls'); goog.require('shaka.ui.PlayButton'); goog.require('shaka.ui.SettingsMenu'); diff --git a/test/media/drm_engine_unit.js b/test/media/drm_engine_unit.js index a93df2a643..68a7d22950 100644 --- a/test/media/drm_engine_unit.js +++ b/test/media/drm_engine_unit.js @@ -713,15 +713,17 @@ describe('DrmEngine', () => { }); it('maps TS MIME types through the transmuxer', async () => { - const originalIsSupported = shaka.media.Transmuxer.isSupported; + const originalIsSupported = + shaka.transmuxer.TransmuxerEngine.isSupported; try { // Mock out isSupported on Transmuxer so that we don't have to care // about what MediaSource supports under that. All we really care about // is the translation of MIME types. - shaka.media.Transmuxer.isSupported = (mimeType, contentType) => { - return mimeType.startsWith('video/mp2t'); - }; + shaka.transmuxer.TransmuxerEngine.isSupported = + (mimeType, contentType) => { + return mimeType.startsWith('video/mp2t'); + }; // The default mock for this is so unrealistic, some of our test // conditions would always fail. Make it realistic enough for this @@ -758,7 +760,7 @@ describe('DrmEngine', () => { expect(drmEngine.supportsVariant(variants[0])).toBeTruthy(); } finally { // Restore the mock. - shaka.media.Transmuxer.isSupported = originalIsSupported; + shaka.transmuxer.TransmuxerEngine.isSupported = originalIsSupported; } }); }); // describe('init') diff --git a/test/media/media_source_engine_unit.js b/test/media/media_source_engine_unit.js index f1d98f8387..3082cd5370 100644 --- a/test/media/media_source_engine_unit.js +++ b/test/media/media_source_engine_unit.js @@ -42,7 +42,13 @@ describe('MediaSourceEngine', () => { const originalCreateMediaSource = // eslint-disable-next-line no-restricted-syntax shaka.media.MediaSourceEngine.prototype.createMediaSource; - const originalTransmuxer = shaka.media.Transmuxer; + + const originalFindTransmuxer = + shaka.transmuxer.TransmuxerEngine.findTransmuxer; + const originalConvertCodecs = + shaka.transmuxer.TransmuxerEngine.convertCodecs; + const originalIsSupported = + shaka.transmuxer.TransmuxerEngine.isSupported; // Jasmine Spies don't handle toHaveBeenCalledWith well with objects, so use // some numbers instead. @@ -89,7 +95,12 @@ describe('MediaSourceEngine', () => { afterAll(() => { window.MediaSource.isTypeSupported = originalIsTypeSupported; - shaka.media.Transmuxer = originalTransmuxer; + shaka.transmuxer.TransmuxerEngine.findTransmuxer = + originalFindTransmuxer; + shaka.transmuxer.TransmuxerEngine.convertCodecs = + originalConvertCodecs; + shaka.transmuxer.TransmuxerEngine.isSupported = + originalIsSupported; }); beforeEach(/** @suppress {invalidCasts} */ () => { @@ -101,17 +112,18 @@ describe('MediaSourceEngine', () => { return type == 'audio' ? audioSourceBuffer : videoSourceBuffer; }); mockTransmuxer = new shaka.test.FakeTransmuxer(); - - // eslint-disable-next-line no-restricted-syntax - shaka.media.Transmuxer = /** @type {?} */ (function() { - return /** @type {?} */ (mockTransmuxer); - }); - shaka.media.Transmuxer.convertCodecs = (mimeType, contentType) => { - return 'video/mp4; codecs="avc1.42E01E"'; - }; - shaka.media.Transmuxer.isSupported = (mimeType, contentType) => { - return mimeType == 'tsMimetype'; - }; + shaka.transmuxer.TransmuxerEngine.findTransmuxer = + (mimeType) => { + return () => mockTransmuxer; + }; + shaka.transmuxer.TransmuxerEngine.convertCodecs = + (mimeType, contentType) => { + return 'video/mp4; codecs="avc1.42E01E"'; + }; + shaka.transmuxer.TransmuxerEngine.isSupported = + (mimeType, contentType) => { + return mimeType == 'tsMimetype'; + }; shaka.text.TextEngine = createMockTextEngineCtor(); diff --git a/test/test/util/simple_fakes.js b/test/test/util/simple_fakes.js index 9ff9d6d278..9a1908ba61 100644 --- a/test/test/util/simple_fakes.js +++ b/test/test/util/simple_fakes.js @@ -497,17 +497,26 @@ shaka.test.FakeSegmentIndex = class { } }; -/** @extends {shaka.media.Transmuxer} */ +/** @implements {shaka.extern.Transmuxer} */ shaka.test.FakeTransmuxer = class { constructor() { + const mp4MimeType = 'video/mp4; codecs="avc1.42E01E"'; + const output = { data: new Uint8Array(), captions: [], }; /** @type {!jasmine.Spy} */ - this.destroy = - jasmine.createSpy('destroy').and.returnValue(Promise.resolve()); + this.destroy = jasmine.createSpy('destroy'); + + /** @type {!jasmine.Spy} */ + this.isSupported = + jasmine.createSpy('isSupported').and.returnValue(true); + + /** @type {!jasmine.Spy} */ + this.convertCodecs = + jasmine.createSpy('convertCodecs').and.returnValue(mp4MimeType); /** @type {!jasmine.Spy} */ this.transmux = diff --git a/test/media/transmuxer_integration.js b/test/transmuxer/muxjs_transmuxer_integration.js similarity index 84% rename from test/media/transmuxer_integration.js rename to test/transmuxer/muxjs_transmuxer_integration.js index 63df2ed105..b4c7b66876 100644 --- a/test/media/transmuxer_integration.js +++ b/test/transmuxer/muxjs_transmuxer_integration.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -describe('Transmuxer', () => { +describe('MuxjsTransmuxer', () => { const ContentType = shaka.util.ManifestParserUtils.ContentType; const videoSegmentUri = '/base/test/test/assets/video.ts'; @@ -14,8 +14,6 @@ describe('Transmuxer', () => { const transportStreamAudioMimeType = 'video/mp2t; codecs="mp4a.40.2"'; const aacAudioMimeType = 'audio/aac'; - const transmuxerMimeType = 'fake/mimeType'; - /** @type {!ArrayBuffer} */ let videoSegment; /** @type {!ArrayBuffer} */ @@ -23,9 +21,16 @@ describe('Transmuxer', () => { /** @type {!ArrayBuffer} */ let emptySegment; - /** @type {!shaka.media.Transmuxer} */ + /** @type {?shaka.transmuxer.MuxjsTransmuxer} */ let transmuxer; + function useTsTransmuxer() { + transmuxer = new shaka.transmuxer.MuxjsTransmuxer('video/mp2t'); + } + + function useAacTransmuxer() { + transmuxer = new shaka.transmuxer.MuxjsTransmuxer('audio/aac'); + } beforeAll(async () => { const responses = await Promise.all([ @@ -38,7 +43,7 @@ describe('Transmuxer', () => { }); beforeEach(() => { - transmuxer = new shaka.media.Transmuxer(transmuxerMimeType); + transmuxer = null; }); afterEach(async () => { @@ -46,42 +51,49 @@ describe('Transmuxer', () => { }); describe('isSupported', () => { - const Transmuxer = shaka.media.Transmuxer; - it('returns whether the content type is supported', () => { - expect(Transmuxer.isSupported( + useTsTransmuxer(); + expect(transmuxer.isSupported( mp4MimeType, ContentType.VIDEO)).toBe(false); - expect(Transmuxer.isSupported( + expect(transmuxer.isSupported( transportStreamVideoMimeType, ContentType.VIDEO)).toBe(true); }); // Issue #1991 it('handles upper-case MIME types', () => { + useTsTransmuxer(); const mimeType = transportStreamVideoMimeType.replace('mp2t', 'MP2T'); - expect(Transmuxer.isSupported(mimeType, ContentType.VIDEO)).toBe(true); + expect(transmuxer.isSupported( + mimeType, ContentType.VIDEO)).toBe(true); }); }); describe('convertCodecs', () => { const convertCodecs = - (type, codecs) => shaka.media.Transmuxer.convertCodecs(type, codecs); + (type, codecs) => transmuxer.convertCodecs(type, codecs); - it('returns converted codecs', () => { + it('returns converted codecs for TS', () => { + useTsTransmuxer(); const convertedVideoCodecs = convertCodecs(ContentType.VIDEO, transportStreamVideoMimeType); const convertedAudioCodecs = convertCodecs(ContentType.AUDIO, transportStreamAudioMimeType); - const convertedAacCodecs = - convertCodecs(ContentType.AUDIO, aacAudioMimeType); const expectedVideoCodecs = 'video/mp4; codecs="avc1.42E01E"'; const expectedAudioCodecs = 'audio/mp4; codecs="mp4a.40.2"'; - const expectedAacCodecs = 'audio/mp4; codecs="mp4a.40.2"'; expect(convertedVideoCodecs).toBe(expectedVideoCodecs); expect(convertedAudioCodecs).toBe(expectedAudioCodecs); + }); + + it('returns converted codecs for AAC', () => { + useAacTransmuxer(); + const convertedAacCodecs = + convertCodecs(ContentType.AUDIO, aacAudioMimeType); + const expectedAacCodecs = 'audio/mp4; codecs="mp4a.40.2"'; expect(convertedAacCodecs).toBe(expectedAacCodecs); }); it('converts legacy avc1 codec strings', () => { + useTsTransmuxer(); expect( convertCodecs( ContentType.VIDEO, 'video/mp2t; codecs="avc1.100.42"')) @@ -96,6 +108,7 @@ describe('Transmuxer', () => { // Issue #1991 it('handles upper-case MIME types', () => { + useTsTransmuxer(); expect(convertCodecs( ContentType.VIDEO, 'video/MP2T; codecs="avc1.420001"')) .toBe('video/mp4; codecs="avc1.420001"'); @@ -103,11 +116,13 @@ describe('Transmuxer', () => { }); it('getOrginalMimeType returns the correct mimeType', () => { - expect(transmuxer.getOrginalMimeType()).toBe(transmuxerMimeType); + useAacTransmuxer(); + expect(transmuxer.getOrginalMimeType()).toBe(aacAudioMimeType); }); describe('transmuxing', () => { it('transmux video from TS to MP4', async () => { + useTsTransmuxer(); let sawMDAT = false; const transmuxedData = await transmuxer.transmux(videoSegment); @@ -123,6 +138,7 @@ describe('Transmuxer', () => { }); it('transmux audio from TS to MP4', async () => { + useTsTransmuxer(); let sawMDAT = false; const transmuxedData = await transmuxer.transmux(audioSegment); expect(transmuxedData).toEqual(jasmine.any(Uint8Array)); @@ -137,6 +153,7 @@ describe('Transmuxer', () => { }); it('transmux empty video from TS to MP4', async () => { + useTsTransmuxer(); let sawMDAT = false; const transmuxedData = await transmuxer.transmux(emptySegment); expect(transmuxedData).toEqual(jasmine.any(Uint8Array)); @@ -149,6 +166,7 @@ describe('Transmuxer', () => { }); it('passes through true timestamps', async () => { + useTsTransmuxer(); let parsed = false; const expectedMp4Timestamp = 5166000; // in timescale units let mp4Timestamp; diff --git a/test/transmuxer/transmuxer_engine_integration.js b/test/transmuxer/transmuxer_engine_integration.js new file mode 100644 index 0000000000..bc0c519cc1 --- /dev/null +++ b/test/transmuxer/transmuxer_engine_integration.js @@ -0,0 +1,73 @@ +/*! @license + * Shaka Player + * Copyright 2016 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +describe('TransmuxerEngine', () => { + const ContentType = shaka.util.ManifestParserUtils.ContentType; + + const mp4MimeType = 'video/mp4; codecs="avc1.42E01E"'; + const transportStreamVideoMimeType = 'video/mp2t; codecs="avc1.42E01E"'; + const transportStreamAudioMimeType = 'video/mp2t; codecs="mp4a.40.2"'; + const aacAudioMimeType = 'audio/aac'; + + describe('isSupported', () => { + const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine; + + it('returns whether the content type is supported', () => { + expect(TransmuxerEngine.isSupported( + mp4MimeType, ContentType.VIDEO)).toBe(false); + expect(TransmuxerEngine.isSupported( + transportStreamVideoMimeType, ContentType.VIDEO)).toBe(true); + }); + + // Issue #1991 + it('handles upper-case MIME types', () => { + const mimeType = transportStreamVideoMimeType.replace('mp2t', 'MP2T'); + expect(TransmuxerEngine.isSupported( + mimeType, ContentType.VIDEO)).toBe(true); + }); + }); + + describe('convertCodecs', () => { + const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine; + const convertCodecs = + (type, codecs) => TransmuxerEngine.convertCodecs(type, codecs); + + it('returns converted codecs', () => { + const convertedVideoCodecs = + convertCodecs(ContentType.VIDEO, transportStreamVideoMimeType); + const convertedAudioCodecs = + convertCodecs(ContentType.AUDIO, transportStreamAudioMimeType); + const convertedAacCodecs = + convertCodecs(ContentType.AUDIO, aacAudioMimeType); + const expectedVideoCodecs = 'video/mp4; codecs="avc1.42E01E"'; + const expectedAudioCodecs = 'audio/mp4; codecs="mp4a.40.2"'; + const expectedAacCodecs = 'audio/mp4; codecs="mp4a.40.2"'; + expect(convertedVideoCodecs).toBe(expectedVideoCodecs); + expect(convertedAudioCodecs).toBe(expectedAudioCodecs); + expect(convertedAacCodecs).toBe(expectedAacCodecs); + }); + + it('converts legacy avc1 codec strings', () => { + expect( + convertCodecs( + ContentType.VIDEO, 'video/mp2t; codecs="avc1.100.42"')) + .toBe('video/mp4; codecs="avc1.64002a"'); + expect( + convertCodecs(ContentType.VIDEO, 'video/mp2t; codecs="avc1.77.80"')) + .toBe('video/mp4; codecs="avc1.4d0050"'); + expect( + convertCodecs(ContentType.VIDEO, 'video/mp2t; codecs="avc1.66.1"')) + .toBe('video/mp4; codecs="avc1.420001"'); + }); + + // Issue #1991 + it('handles upper-case MIME types', () => { + expect(convertCodecs( + ContentType.VIDEO, 'video/MP2T; codecs="avc1.420001"')) + .toBe('video/mp4; codecs="avc1.420001"'); + }); + }); +});