Files
shaka-player/lib/media/transmuxer.js
T
Joey Parrish f539147d48 fix: Correct license headers in compiled output
This fixes all the license headers in the main library, which corrects
the appearance of the main license in the compiled output.

It seems that the `!` in the header forces the compiler to keep it in
the output.  I believe older compiler releases did this purely based
on `@license`.

Issue #2638

Change-Id: I7f0e918caad10c9af689c9d07672b7fe9be7b2f3
2020-06-09 16:05:09 -07:00

215 lines
6.1 KiB
JavaScript

/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.media.Transmuxer');
goog.require('goog.asserts');
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');
/**
* Transmuxer provides all operations for transmuxing from Transport
* Stream to MP4.
*
* @implements {shaka.util.IDestroyable}
*/
shaka.media.Transmuxer = class {
constructor() {
/** @private {muxjs.mp4.Transmuxer} */
this.muxTransmuxer_ = new muxjs.mp4.Transmuxer({
'keepOriginalTimestamps': true,
});
/** @private {shaka.util.PublicPromise} */
this.transmuxPromise_ = null;
/** @private {!Array.<!Uint8Array>} */
this.transmuxedData_ = [];
/** @private {!Array.<muxjs.mp4.ClosedCaption>} */
this.captions_ = [];
/** @private {!Array.<muxjs.mp4.Metadata>} */
this.metadata_ = [];
/** @private {boolean} */
this.isTransmuxing_ = false;
this.muxTransmuxer_.on('data', (segment) => this.onTransmuxed_(segment));
this.muxTransmuxer_.on('done', () => this.onTransmuxDone_());
}
/**
* @override
*/
destroy() {
this.muxTransmuxer_.dispose();
this.muxTransmuxer_ = null;
return Promise.resolve();
}
/**
* Check if the content type is Transport Stream, and if muxjs is loaded.
* @param {string} mimeType
* @param {string=} contentType
* @return {boolean}
*/
static isSupported(mimeType, contentType) {
const Transmuxer = shaka.media.Transmuxer;
if (!window.muxjs || !Transmuxer.isTsContainer(mimeType)) {
return false;
}
if (contentType) {
return MediaSource.isTypeSupported(
Transmuxer.convertTsCodecs(contentType, mimeType));
}
const ContentType = shaka.util.ManifestParserUtils.ContentType;
const audioMime = Transmuxer.convertTsCodecs(ContentType.AUDIO, mimeType);
const videoMime = Transmuxer.convertTsCodecs(ContentType.VIDEO, mimeType);
return MediaSource.isTypeSupported(audioMime) ||
MediaSource.isTypeSupported(videoMime);
}
/**
* Check if the mimetype contains 'mp2t'.
* @param {string} mimeType
* @return {boolean}
*/
static isTsContainer(mimeType) {
return mimeType.toLowerCase().split(';')[0].split('/')[1] == 'mp2t';
}
/**
* For transport stream, convert its codecs to MP4 codecs.
* @param {string} contentType
* @param {string} tsMimeType
* @return {string}
*/
static convertTsCodecs(contentType, tsMimeType) {
const ContentType = shaka.util.ManifestParserUtils.ContentType;
let mp4MimeType = tsMimeType.replace(/mp2t/i, 'mp4');
if (contentType == ContentType.AUDIO) {
mp4MimeType = mp4MimeType.replace('video', 'audio');
}
// Handle legacy AVC1 codec strings (pre-RFC 6381).
// Look for "avc1.<profile>.<level>", where profile is:
// 66 (baseline => 0x42)
// 77 (main => 0x4d)
// 100 (high => 0x64)
// Reference: https://bit.ly/2K9JI3x
const match = /avc1\.(66|77|100)\.(\d+)/.exec(mp4MimeType);
if (match) {
let newCodecString = 'avc1.';
const profile = match[1];
if (profile == '66') {
newCodecString += '4200';
} else if (profile == '77') {
newCodecString += '4d00';
} else {
goog.asserts.assert(profile == '100',
'Legacy avc1 parsing code out of sync with regex!');
newCodecString += '6400';
}
// Convert the level to hex and append to the codec string.
const level = Number(match[2]);
goog.asserts.assert(level < 256,
'Invalid legacy avc1 level number!');
newCodecString += (level >> 4).toString(16);
newCodecString += (level & 0xf).toString(16);
mp4MimeType = mp4MimeType.replace(match[0], newCodecString);
}
return mp4MimeType;
}
/**
* Transmux from Transport stream to MP4, using the mux.js library.
* @param {BufferSource} data
* @return {!Promise.<{data: !Uint8Array,
* captions: !Array.<!muxjs.mp4.ClosedCaption>,
* metadata: !Array.<!Object>}>}
*/
transmux(data) {
goog.asserts.assert(!this.isTransmuxing_,
'No transmuxing should be in progress.');
this.isTransmuxing_ = true;
this.transmuxPromise_ = new shaka.util.PublicPromise();
this.transmuxedData_ = [];
this.captions_ = [];
this.metadata_ = [];
const dataArray = shaka.util.BufferUtils.toUint8(data);
this.muxTransmuxer_.push(dataArray);
this.muxTransmuxer_.flush();
// Workaround for https://bit.ly/Shaka1449 mux.js not
// emitting 'data' and 'done' events.
// mux.js code is synchronous, so if onTransmuxDone_ has
// not been called by now, it's not going to be.
// Treat it as a transmuxing failure and reject the promise.
if (this.isTransmuxing_) {
this.transmuxPromise_.reject(new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.MEDIA,
shaka.util.Error.Code.TRANSMUXING_FAILED));
}
return this.transmuxPromise_;
}
/**
* Handles the 'data' event of the transmuxer.
* Extracts the cues from the transmuxed segment, and adds them to an array.
* Stores the transmuxed data in another array, to pass it back to
* MediaSourceEngine, and append to the source buffer.
*
* @param {muxjs.mp4.Transmuxer.Segment} segment
* @private
*/
onTransmuxed_(segment) {
this.captions_ = segment.captions;
this.metadata_ = segment.metadata;
this.transmuxedData_.push(
shaka.util.Uint8ArrayUtils.concat(segment.initSegment, segment.data));
}
/**
* Handles the 'done' event of the transmuxer.
* Resolves the transmux Promise, and returns the transmuxed data.
* @private
*/
onTransmuxDone_() {
const output = {
data: shaka.util.Uint8ArrayUtils.concat(...this.transmuxedData_),
captions: this.captions_,
metadata: this.metadata_,
};
this.transmuxPromise_.resolve(output);
this.isTransmuxing_ = false;
}
};