mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-17 16:26:39 +03:00
f539147d48
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
215 lines
6.1 KiB
JavaScript
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;
|
|
}
|
|
};
|
|
|