Source: lib/transmuxer/mpeg_ts_transmuxer.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.transmuxer.MpegTsTransmuxer');
  7. goog.require('shaka.media.Capabilities');
  8. goog.require('shaka.transmuxer.MpegAudio');
  9. goog.require('shaka.transmuxer.TransmuxerEngine');
  10. goog.require('shaka.util.BufferUtils');
  11. goog.require('shaka.util.Error');
  12. goog.require('shaka.util.ManifestParserUtils');
  13. goog.require('shaka.util.MimeUtils');
  14. goog.require('shaka.util.TsParser');
  15. goog.require('shaka.util.Uint8ArrayUtils');
  16. /**
  17. * @fileoverview
  18. *
  19. * This transmuxer takes an audio-only TS with MP3, and converts it to
  20. * raw MP3(audio/mpeg). We don't do it in ts_transmuxer.js because the
  21. * output of it is always MP4. This transmuxer is necessary because the only
  22. * browser that supports MP3 in MP4 is Firefox(audio/mp4; codecs="mp3"),
  23. * other browsers don't support it.
  24. */
  25. /**
  26. * @implements {shaka.extern.Transmuxer}
  27. * @export
  28. */
  29. shaka.transmuxer.MpegTsTransmuxer = class {
  30. /**
  31. * @param {string} mimeType
  32. */
  33. constructor(mimeType) {
  34. /** @private {string} */
  35. this.originalMimeType_ = mimeType;
  36. /** @private {?shaka.util.TsParser} */
  37. this.tsParser_ = null;
  38. }
  39. /**
  40. * @override
  41. * @export
  42. */
  43. destroy() {
  44. // Nothing
  45. }
  46. /**
  47. * Check if the mime type and the content type is supported.
  48. * @param {string} mimeType
  49. * @param {string=} contentType
  50. * @return {boolean}
  51. * @override
  52. * @export
  53. */
  54. isSupported(mimeType, contentType) {
  55. const Capabilities = shaka.media.Capabilities;
  56. if (!this.isTsContainer_(mimeType)) {
  57. return false;
  58. }
  59. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  60. const MimeUtils = shaka.util.MimeUtils;
  61. const codecs = MimeUtils.getCodecs(mimeType);
  62. const allCodecs = MimeUtils.splitCodecs(codecs);
  63. const audioCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
  64. ContentType.AUDIO, allCodecs);
  65. const videoCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
  66. ContentType.VIDEO, allCodecs);
  67. if (!audioCodec || videoCodec) {
  68. return false;
  69. }
  70. const normalizedCodec = MimeUtils.getNormalizedCodec(audioCodec);
  71. if (normalizedCodec != 'mp3') {
  72. return false;
  73. }
  74. return Capabilities.isTypeSupported(
  75. this.convertCodecs(ContentType.AUDIO, mimeType));
  76. }
  77. /**
  78. * Check if the mimetype is 'video/mp2t'.
  79. * @param {string} mimeType
  80. * @return {boolean}
  81. * @private
  82. */
  83. isTsContainer_(mimeType) {
  84. return mimeType.toLowerCase().split(';')[0] == 'video/mp2t';
  85. }
  86. /**
  87. * @override
  88. * @export
  89. */
  90. convertCodecs(contentType, mimeType) {
  91. if (this.isTsContainer_(mimeType)) {
  92. return 'audio/mpeg';
  93. }
  94. return mimeType;
  95. }
  96. /**
  97. * @override
  98. * @export
  99. */
  100. getOriginalMimeType() {
  101. return this.originalMimeType_;
  102. }
  103. /**
  104. * @override
  105. * @export
  106. */
  107. transmux(data, stream, reference, duration, contentType) {
  108. const MpegAudio = shaka.transmuxer.MpegAudio;
  109. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  110. const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
  111. if (!this.tsParser_) {
  112. this.tsParser_ = new shaka.util.TsParser();
  113. } else {
  114. this.tsParser_.clearData();
  115. }
  116. const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
  117. const tsParser = this.tsParser_.parse(uint8ArrayData);
  118. const codecs = tsParser.getCodecs();
  119. if (codecs.audio != 'mp3' || contentType != ContentType.AUDIO) {
  120. return Promise.reject(new shaka.util.Error(
  121. shaka.util.Error.Severity.CRITICAL,
  122. shaka.util.Error.Category.MEDIA,
  123. shaka.util.Error.Code.TRANSMUXING_FAILED,
  124. reference ? reference.getUris()[0] : null));
  125. }
  126. let transmuxData = new Uint8Array([]);
  127. for (const audioData of tsParser.getAudioData()) {
  128. const data = audioData.data;
  129. if (!data) {
  130. continue;
  131. }
  132. let offset = 0;
  133. while (offset < data.length) {
  134. const header = MpegAudio.parseHeader(data, offset);
  135. if (!header) {
  136. offset++;
  137. continue;
  138. }
  139. if (offset + header.frameLength <= data.length) {
  140. transmuxData = Uint8ArrayUtils.concat(transmuxData,
  141. data.subarray(offset, offset + header.frameLength));
  142. }
  143. offset += header.frameLength;
  144. }
  145. }
  146. return Promise.resolve(transmuxData);
  147. }
  148. };
  149. shaka.transmuxer.TransmuxerEngine.registerTransmuxer(
  150. 'video/mp2t',
  151. () => new shaka.transmuxer.MpegTsTransmuxer('video/mp2t'),
  152. shaka.transmuxer.TransmuxerEngine.PluginPriority.PREFERRED_SECONDARY);