Source: lib/util/mime_utils.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.util.MimeUtils');
  7. goog.require('shaka.dependencies');
  8. goog.require('shaka.media.Transmuxer');
  9. goog.require('shaka.util.ManifestParserUtils');
  10. /**
  11. * @summary A set of utility functions for dealing with MIME types.
  12. * @export
  13. */
  14. shaka.util.MimeUtils = class {
  15. /**
  16. * Takes a MIME type and optional codecs string and produces the full MIME
  17. * type.
  18. *
  19. * @param {string} mimeType
  20. * @param {string=} codecs
  21. * @return {string}
  22. * @export
  23. */
  24. static getFullType(mimeType, codecs) {
  25. let fullMimeType = mimeType;
  26. if (codecs) {
  27. fullMimeType += '; codecs="' + codecs + '"';
  28. }
  29. return fullMimeType;
  30. }
  31. /**
  32. * Takes a MIME type and a codecs string and produces the full MIME
  33. * type. If it's a transport stream, convert its codecs to MP4 codecs.
  34. * Otherwise for multiplexed content, convert the video MIME types to
  35. * their audio equivalents if the content type is audio.
  36. *
  37. * @param {string} mimeType
  38. * @param {string} codecs
  39. * @param {string} contentType
  40. * @return {string}
  41. */
  42. static getFullOrConvertedType(mimeType, codecs, contentType) {
  43. const fullMimeType = shaka.util.MimeUtils.getFullType(mimeType, codecs);
  44. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  45. if (shaka.media.Transmuxer.isTsContainer(fullMimeType)) {
  46. if (shaka.dependencies.muxjs()) {
  47. return shaka.media.Transmuxer.convertTsCodecs(
  48. contentType, fullMimeType);
  49. }
  50. } else if (contentType == ContentType.AUDIO) {
  51. // video/mp2t is the correct mime type for TS audio, so only replace the
  52. // word "video" with "audio" for non-TS audio content.
  53. return fullMimeType.replace('video', 'audio');
  54. }
  55. return fullMimeType;
  56. }
  57. /**
  58. * Takes a Stream object and produces an extended MIME type with information
  59. * beyond the container and codec type, when available.
  60. *
  61. * @param {shaka.extern.Stream} stream
  62. * @return {string}
  63. */
  64. static getExtendedType(stream) {
  65. const components = [stream.mimeType];
  66. const extendedMimeParams = shaka.util.MimeUtils.EXTENDED_MIME_PARAMETERS_;
  67. extendedMimeParams.forEach((mimeKey, streamKey) => {
  68. const value = stream[streamKey];
  69. if (value) {
  70. components.push(mimeKey + '="' + value + '"');
  71. }
  72. });
  73. if (stream.hdr == 'PQ') {
  74. components.push('eotf="smpte2084"');
  75. }
  76. return components.join(';');
  77. }
  78. /**
  79. * Takes a full MIME type (with codecs) or basic MIME type (without codecs)
  80. * and returns a container type string ("mp2t", "mp4", "webm", etc.)
  81. *
  82. * @param {string} mimeType
  83. * @return {string}
  84. */
  85. static getContainerType(mimeType) {
  86. return mimeType.split(';')[0].split('/')[1];
  87. }
  88. /**
  89. * Split a list of codecs encoded in a string into a list of codecs.
  90. * @param {string} codecs
  91. * @return {!Array.<string>}
  92. */
  93. static splitCodecs(codecs) {
  94. return codecs.split(',');
  95. }
  96. /**
  97. * Get the normalized codec from a codec string,
  98. * independently of their container.
  99. *
  100. * @param {string} codecString
  101. * @return {string}
  102. */
  103. static getNormalizedCodec(codecString) {
  104. const parts =
  105. shaka.util.MimeUtils.getCodecParts_(codecString);
  106. const base = parts[0];
  107. const profile = parts[1].toLowerCase();
  108. switch (true) {
  109. case base === 'mp4a' && profile === '69':
  110. case base === 'mp4a' && profile === '6b':
  111. return 'mp3';
  112. case base === 'mp4a' && profile === '66':
  113. case base === 'mp4a' && profile === '67':
  114. case base === 'mp4a' && profile === '68':
  115. case base === 'mp4a' && profile === '40.2':
  116. case base === 'mp4a' && profile === '40.02':
  117. case base === 'mp4a' && profile === '40.5':
  118. case base === 'mp4a' && profile === '40.05':
  119. case base === 'mp4a' && profile === '40.29':
  120. case base === 'mp4a' && profile === '40.42': // Extended HE-AAC
  121. return 'aac';
  122. case base === 'mp4a' && profile === 'a5':
  123. return 'ac-3'; // Dolby Digital
  124. case base === 'mp4a' && profile === 'a6':
  125. return 'ec-3'; // Dolby Digital Plus
  126. case base === 'mp4a' && profile === 'b2':
  127. return 'dtsx'; // DTS:X
  128. case base === 'mp4a' && profile === 'a9':
  129. return 'dtsc'; // DTS Digital Surround
  130. case base === 'avc1':
  131. case base === 'avc3':
  132. return 'avc'; // H264
  133. case base === 'hvc1':
  134. case base === 'hev1':
  135. return 'hevc'; // H265
  136. case base === 'dvh1':
  137. case base === 'dvhe':
  138. return 'dovi'; // Dolby Vision
  139. }
  140. return base;
  141. }
  142. /**
  143. * Get the base codec from a codec string.
  144. *
  145. * @param {string} codecString
  146. * @return {string}
  147. */
  148. static getCodecBase(codecString) {
  149. const parts = shaka.util.MimeUtils.getCodecParts_(codecString);
  150. return parts[0];
  151. }
  152. /**
  153. * Takes a full MIME type (with codecs) or basic MIME type (without codecs)
  154. * and returns a basic MIME type (without codecs or other parameters).
  155. *
  156. * @param {string} mimeType
  157. * @return {string}
  158. */
  159. static getBasicType(mimeType) {
  160. return mimeType.split(';')[0];
  161. }
  162. /**
  163. * Takes a MIME type and returns the codecs parameter, or an empty string if
  164. * there is no codecs parameter.
  165. *
  166. * @param {string} mimeType
  167. * @return {string}
  168. */
  169. static getCodecs(mimeType) {
  170. // Parse the basic MIME type from its parameters.
  171. const pieces = mimeType.split(/ *; */);
  172. pieces.shift(); // Remove basic MIME type from pieces.
  173. const codecs = pieces.find((piece) => piece.startsWith('codecs='));
  174. if (!codecs) {
  175. return '';
  176. }
  177. // The value may be quoted, so remove quotes at the beginning or end.
  178. const value = codecs.split('=')[1].replace(/^"|"$/g, '');
  179. return value;
  180. }
  181. /**
  182. * Get the base and profile of a codec string. Where [0] will be the codec
  183. * base and [1] will be the profile.
  184. * @param {string} codecString
  185. * @return {!Array.<string>}
  186. * @private
  187. */
  188. static getCodecParts_(codecString) {
  189. const parts = codecString.split('.');
  190. const base = parts[0];
  191. parts.shift();
  192. const profile = parts.join('.');
  193. // Make sure that we always return a "base" and "profile".
  194. return [base, profile];
  195. }
  196. };
  197. /**
  198. * A map from Stream object keys to MIME type parameters. These should be
  199. * ignored by platforms that do not recognize them.
  200. *
  201. * This initial set of parameters are all recognized by Chromecast.
  202. *
  203. * @const {!Map.<string, string>}
  204. * @private
  205. */
  206. shaka.util.MimeUtils.EXTENDED_MIME_PARAMETERS_ = new Map()
  207. .set('codecs', 'codecs')
  208. .set('frameRate', 'framerate') // Ours is camelCase, theirs is lowercase.
  209. .set('bandwidth', 'bitrate') // They are in the same units: bits/sec.
  210. .set('width', 'width')
  211. .set('height', 'height')
  212. .set('channelsCount', 'channels');
  213. /**
  214. * A mimetype created for CEA-608 closed captions.
  215. * @const {string}
  216. */
  217. shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE = 'application/cea-608';
  218. /**
  219. * A mimetype created for CEA-708 closed captions.
  220. * @const {string}
  221. */
  222. shaka.util.MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE = 'application/cea-708';