Skip to content

Commit

Permalink
feat: Allow custom plugins for transmuxing (shaka-project#4854)
Browse files Browse the repository at this point in the history
  • Loading branch information
avelad committed Jan 5, 2023
1 parent 15232dd commit fac721d
Show file tree
Hide file tree
Showing 15 changed files with 414 additions and 91 deletions.
1 change: 1 addition & 0 deletions build/types/complete
Expand Up @@ -7,5 +7,6 @@
+@manifests
+@polyfill
+@text
+@transmuxer
+@ui
+@lcevc
3 changes: 2 additions & 1 deletion build/types/core
Expand Up @@ -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

Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions build/types/transmuxer
@@ -0,0 +1,3 @@
# Optional plugins related to transmuxer.

+../../lib/transmuxer/muxjs_transmuxer.js
48 changes: 48 additions & 0 deletions 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.<!Uint8Array>}
*/
transmux(data) {}
};


/**
* @typedef {function():!shaka.extern.Transmuxer}
* @exportDoc
*/
shaka.extern.TransmuxerPlugin;
7 changes: 4 additions & 3 deletions lib/media/drm_engine.js
Expand Up @@ -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');
Expand Down Expand Up @@ -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;
}
Expand Down
22 changes: 13 additions & 9 deletions lib/media/media_source_engine.js
Expand Up @@ -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');
Expand Down Expand Up @@ -100,7 +100,7 @@ shaka.media.MediaSourceEngine = class {
/** @private {shaka.util.EventManager} */
this.eventManager_ = new shaka.util.EventManager();

/** @private {!Object.<string, !shaka.media.Transmuxer>} */
/** @private {!Object.<string, !shaka.extern.Transmuxer>} */
this.transmuxers_ = {};

/** @private {?shaka.media.IClosedCaptionParser} */
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
77 changes: 40 additions & 37 deletions lib/media/transmuxer.js → lib/transmuxer/muxjs_transmuxer.js
Expand Up @@ -4,26 +4,24 @@
* 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');
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
*/
Expand Down Expand Up @@ -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);
}
Expand All @@ -105,7 +104,7 @@ shaka.media.Transmuxer = class {
* @return {boolean}
* @private
*/
static isAacContainer_(mimeType) {
isAacContainer_(mimeType) {
return mimeType.toLowerCase().split(';')[0] == 'audio/aac';
}

Expand All @@ -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;
}
Expand All @@ -143,7 +139,7 @@ shaka.media.Transmuxer = class {
* @return {string}
* @private
*/
static convertAacCodecs_() {
convertAacCodecs_() {
return 'audio/mp4; codecs="mp4a.40.2"';
}

Expand All @@ -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) {
Expand Down Expand Up @@ -198,18 +194,17 @@ shaka.media.Transmuxer = class {


/**
* Returns the original mimetype of the transmuxer.
* @return {string}
* @override
* @export
*/
getOrginalMimeType() {
return this.originalMimeType_;
}


/**
* Transmux from Transport stream to MP4, using the mux.js library.
* @param {BufferSource} data
* @return {!Promise.<!Uint8Array>}
* @override
* @export
*/
transmux(data) {
goog.asserts.assert(!this.isTransmuxing_,
Expand Down Expand Up @@ -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);

0 comments on commit fac721d

Please sign in to comment.