Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow custom plugins for transmuxing #4854

Merged
merged 1 commit into from
Jan 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build/types/complete
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
+@manifests
+@polyfill
+@text
+@transmuxer
+@ui
+@lcevc
3 changes: 2 additions & 1 deletion build/types/core
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Optional plugins related to transmuxer.

+../../lib/transmuxer/muxjs_transmuxer.js
48 changes: 48 additions & 0 deletions externs/shaka/transmuxer.js
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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);
Loading