text: /**
   * @license
   * Copyright 2016 Google Inc.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  goog.provide('shaka.media.Transmuxer');
  
  goog.require('goog.asserts');
  goog.require('shaka.text.Cue');
  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.
   *
   * @struct
   * @constructor
   * @implements {shaka.util.IDestroyable}
   */
  shaka.media.Transmuxer = function() {
    /** @private {muxjs.mp4.Transmuxer} */
    this.muxTransmuxer_ = new muxjs.mp4.Transmuxer({
      'keepOriginalTimestamps': true
    });
  
    /** @private {shaka.util.PublicPromise} */
    this.transmuxPromise_ = null;
  
    /** @private {!Array} */
    this.transmuxedData_ = [];
  
    /** @private {!Array.<shaka.text.Cue>} */
    this.cues_ = [];
  
    /** @private {boolean} */
    this.isTransmuxing_ = false;
  
    this.muxTransmuxer_.on('data', this.onTransmuxed_.bind(this));
  
    this.muxTransmuxer_.on('done', this.onTransmuxDone_.bind(this));
  };
  
  
  /**
   * @override
   */
  shaka.media.Transmuxer.prototype.destroy = function() {
    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}
   */
  shaka.media.Transmuxer.isSupported = function(mimeType, contentType) {
    if (!window.muxjs || !shaka.media.Transmuxer.isTsContainer(mimeType)) {
      return false;
    }
    let convertTsCodecs = shaka.media.Transmuxer.convertTsCodecs;
    if (contentType) {
      return MediaSource.isTypeSupported(convertTsCodecs(contentType, mimeType));
    }
    const ContentType = shaka.util.ManifestParserUtils.ContentType;
    return MediaSource.isTypeSupported(
               convertTsCodecs(ContentType.AUDIO, mimeType)) ||
        MediaSource.isTypeSupported(convertTsCodecs(ContentType.VIDEO, mimeType));
  };
  
  
  /**
   * Check if the mimetype contains 'mp2t'.
   * @param {string} mimeType
   * @return {boolean}
   */
  shaka.media.Transmuxer.isTsContainer = function(mimeType) {
    return mimeType.split(';')[0].split('/')[1] == 'mp2t';
  };
  
  
  /**
   * For transport stream, convert its codecs to MP4 codecs.
   * @param {string} contentType
   * @param {string} tsMimeType
   * @return {string}
   */
  shaka.media.Transmuxer.convertTsCodecs = function(contentType, tsMimeType) {
    const ContentType = shaka.util.ManifestParserUtils.ContentType;
    let mp4MimeType = tsMimeType.replace('mp2t', '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
    let match = /avc1\.(66|77|100)\.(\d+)/.exec(mp4MimeType);
    if (match) {
      let newCodecString = 'avc1.';
  
      let 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.
      let 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 {!ArrayBuffer} data
   * @return {!Promise.<{data: !Uint8Array, cues: !Array.<!shaka.text.Cue>}>}
   */
  shaka.media.Transmuxer.prototype.transmux = function(data) {
    goog.asserts.assert(!this.isTransmuxing_,
        'No transmuxing should be in progress.');
    this.isTransmuxing_ = true;
    this.transmuxPromise_ = new shaka.util.PublicPromise();
    this.transmuxedData_ = [];
    this.cues_ = [];
  
    let dataArray = new Uint8Array(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
   */
  shaka.media.Transmuxer.prototype.onTransmuxed_ = function(segment) {
    for (let i = 0; i < segment.captions.length; i++) {
      let cue = segment.captions[i];
      this.cues_.push(
          new shaka.text.Cue(cue.startTime, cue.endTime, cue.text));
    }
  
    let segmentWithInit = new Uint8Array(segment.data.byteLength +
        segment.initSegment.byteLength);
    segmentWithInit.set(segment.initSegment, 0);
    segmentWithInit.set(segment.data, segment.initSegment.byteLength);
    this.transmuxedData_.push(segmentWithInit);
  };
  
  
  /**
   * Handles the 'done' event of the transmuxer.
   * Resolves the transmux Promise, and returns the transmuxed data.
   * @private
   */
  shaka.media.Transmuxer.prototype.onTransmuxDone_ = function() {
    let output = {
      data: shaka.util.Uint8ArrayUtils.concat.apply(null, this.transmuxedData_),
      cues: this.cues_
    };
  
    this.transmuxPromise_.resolve(output);
    this.isTransmuxing_ = false;
  };
  