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.transmuxer.TransmuxerEngine');
  8. goog.require('shaka.util.ManifestParserUtils');
  9. /**
  10. * @summary A set of utility functions for dealing with MIME types.
  11. * @export
  12. */
  13. shaka.util.MimeUtils = class {
  14. /**
  15. * Takes a MIME type and optional codecs string and produces the full MIME
  16. * type. Also remove the codecs for raw formats.
  17. *
  18. * @param {string} mimeType
  19. * @param {string=} codecs
  20. * @return {string}
  21. * @export
  22. */
  23. static getFullType(mimeType, codecs) {
  24. let fullMimeType = mimeType;
  25. if (codecs && !shaka.util.MimeUtils.RAW_FORMATS.includes(mimeType)) {
  26. fullMimeType += '; codecs="' + codecs + '"';
  27. }
  28. return fullMimeType;
  29. }
  30. /**
  31. * Takes a MIME type and optional codecs string and produces the full MIME
  32. * type.
  33. *
  34. * @param {string} mimeType
  35. * @param {string=} codecs
  36. * @return {string}
  37. * @export
  38. */
  39. static getFullTypeWithAllCodecs(mimeType, codecs) {
  40. let fullMimeType = mimeType;
  41. if (codecs) {
  42. fullMimeType += '; codecs="' + codecs + '"';
  43. }
  44. return fullMimeType;
  45. }
  46. /**
  47. * Takes a MIME type and a codecs string and produces the full MIME
  48. * type. If it's a transport stream, convert its codecs to MP4 codecs.
  49. * Otherwise for multiplexed content, convert the video MIME types to
  50. * their audio equivalents if the content type is audio.
  51. *
  52. * @param {string} mimeType
  53. * @param {string} codecs
  54. * @param {string} contentType
  55. * @return {string}
  56. */
  57. static getFullOrConvertedType(mimeType, codecs, contentType) {
  58. const MimeUtils = shaka.util.MimeUtils;
  59. const fullMimeType = MimeUtils.getFullType(mimeType, codecs);
  60. const fullMimeTypeWithAllCodecs = MimeUtils.getFullTypeWithAllCodecs(
  61. mimeType, codecs);
  62. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  63. const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
  64. if (TransmuxerEngine.isSupported(fullMimeTypeWithAllCodecs, contentType)) {
  65. return TransmuxerEngine.convertCodecs(
  66. contentType, fullMimeTypeWithAllCodecs);
  67. } else if (mimeType != 'video/mp2t' && contentType == ContentType.AUDIO) {
  68. // video/mp2t is the correct mime type for TS audio, so only replace the
  69. // word "video" with "audio" for non-TS audio content.
  70. return fullMimeType.replace('video', 'audio');
  71. }
  72. return fullMimeType;
  73. }
  74. /**
  75. * Takes a Stream object and produces an extended MIME type with information
  76. * beyond the container and codec type, when available.
  77. *
  78. * @param {shaka.extern.Stream} stream
  79. * @param {string} mimeType
  80. * @param {string} codecs
  81. * @return {string}
  82. */
  83. static getExtendedType(stream, mimeType, codecs) {
  84. const components = [mimeType];
  85. const extendedMimeParams = shaka.util.MimeUtils.EXTENDED_MIME_PARAMETERS_;
  86. extendedMimeParams.forEach((mimeKey, streamKey) => {
  87. const value = stream[streamKey];
  88. if (streamKey == 'codecs') {
  89. if (shaka.util.MimeUtils.RAW_FORMATS.includes(stream.mimeType)) {
  90. // Skip codecs for raw formats
  91. } else {
  92. components.push('codecs="' + codecs + '"');
  93. }
  94. } else if (value) {
  95. components.push(mimeKey + '="' + value + '"');
  96. }
  97. });
  98. if (stream.hdr == 'PQ') {
  99. components.push('eotf="smpte2084"');
  100. }
  101. return components.join(';');
  102. }
  103. /**
  104. * Takes a full MIME type (with codecs) or basic MIME type (without codecs)
  105. * and returns a container type string ("mp2t", "mp4", "webm", etc.)
  106. *
  107. * @param {string} mimeType
  108. * @return {string}
  109. */
  110. static getContainerType(mimeType) {
  111. return mimeType.split(';')[0].split('/')[1];
  112. }
  113. /**
  114. * Split a list of codecs encoded in a string into a list of codecs.
  115. * @param {string} codecs
  116. * @return {!Array<string>}
  117. */
  118. static splitCodecs(codecs) {
  119. return codecs.split(',');
  120. }
  121. /**
  122. * Get the normalized codec from a codec string,
  123. * independently of their container.
  124. *
  125. * @param {string} codecString
  126. * @return {string}
  127. */
  128. static getNormalizedCodec(codecString) {
  129. const parts =
  130. shaka.util.MimeUtils.getCodecParts_(codecString);
  131. const base = parts[0].toLowerCase();
  132. const profile = parts[1].toLowerCase();
  133. switch (true) {
  134. case base === 'mp4a' && profile === '69':
  135. case base === 'mp4a' && profile === '6b':
  136. case base === 'mp4a' && profile === '40.34':
  137. return 'mp3';
  138. case base === 'mp4a' && profile === '66':
  139. case base === 'mp4a' && profile === '67':
  140. case base === 'mp4a' && profile === '68':
  141. case base === 'mp4a' && profile === '40.2':
  142. case base === 'mp4a' && profile === '40.02':
  143. case base === 'mp4a' && profile === '40.5':
  144. case base === 'mp4a' && profile === '40.05':
  145. case base === 'mp4a' && profile === '40.29':
  146. case base === 'mp4a' && profile === '40.42': // Extended HE-AAC
  147. return 'aac';
  148. case base === 'mp4a' && profile === 'a5':
  149. case base === 'ac3':
  150. case base === 'ac-3':
  151. return 'ac-3'; // Dolby Digital
  152. case base === 'mp4a' && profile === 'a6':
  153. case base === 'eac3':
  154. case base === 'ec-3':
  155. return 'ec-3'; // Dolby Digital Plus
  156. case base === 'ac-4':
  157. return 'ac-4'; // Dolby AC-4
  158. case base === 'mp4a' && profile === 'b2':
  159. return 'dtsx'; // DTS:X
  160. case base === 'mp4a' && profile === 'a9':
  161. return 'dtsc'; // DTS Digital Surround
  162. case base === 'vp09':
  163. case base === 'vp9':
  164. return 'vp9';
  165. case base === 'avc1':
  166. case base === 'avc3':
  167. return 'avc'; // H264
  168. case base === 'hvc1':
  169. case base === 'hev1':
  170. return 'hevc'; // H265
  171. case base === 'vvc1':
  172. case base === 'vvi1':
  173. return 'vvc'; // H266
  174. case base === 'dvh1':
  175. case base === 'dvhe':
  176. if (profile && profile.startsWith('05')) {
  177. return 'dovi-p5'; // Dolby Vision profile 5
  178. }
  179. return 'dovi-hevc'; // Dolby Vision based in HEVC
  180. case base === 'dvav':
  181. case base === 'dva1':
  182. return 'dovi-avc'; // Dolby Vision based in AVC
  183. case base === 'dav1':
  184. return 'dovi-av1'; // Dolby Vision based in AV1
  185. case base === 'dvc1':
  186. case base === 'dvi1':
  187. return 'dovi-vvc'; // Dolby Vision based in VVC
  188. }
  189. return base;
  190. }
  191. /**
  192. * Get the base codec from a codec string.
  193. *
  194. * @param {string} codecString
  195. * @return {string}
  196. */
  197. static getCodecBase(codecString) {
  198. const codecsBase = [];
  199. for (const codec of codecString.split(',')) {
  200. const parts = shaka.util.MimeUtils.getCodecParts_(codec);
  201. codecsBase.push(parts[0]);
  202. }
  203. return codecsBase.sort().join(',');
  204. }
  205. /**
  206. * Takes a full MIME type (with codecs) or basic MIME type (without codecs)
  207. * and returns a basic MIME type (without codecs or other parameters).
  208. *
  209. * @param {string} mimeType
  210. * @return {string}
  211. */
  212. static getBasicType(mimeType) {
  213. return mimeType.split(';')[0];
  214. }
  215. /**
  216. * Takes a MIME type and returns the codecs parameter, or an empty string if
  217. * there is no codecs parameter.
  218. *
  219. * @param {string} mimeType
  220. * @return {string}
  221. */
  222. static getCodecs(mimeType) {
  223. // Parse the basic MIME type from its parameters.
  224. const pieces = mimeType.split(/ *; */);
  225. pieces.shift(); // Remove basic MIME type from pieces.
  226. const codecs = pieces.find((piece) => piece.startsWith('codecs='));
  227. if (!codecs) {
  228. return '';
  229. }
  230. // The value may be quoted, so remove quotes at the beginning or end.
  231. const value = codecs.split('=')[1].replace(/^"|"$/g, '');
  232. return value;
  233. }
  234. /**
  235. * Checks if the given MIME type is HLS MIME type.
  236. *
  237. * @param {string} mimeType
  238. * @return {boolean}
  239. */
  240. static isHlsType(mimeType) {
  241. return mimeType === 'application/x-mpegurl' ||
  242. mimeType === 'application/vnd.apple.mpegurl';
  243. }
  244. /**
  245. * Checks if the given MIME type is DASH MIME type.
  246. *
  247. * @param {string} mimeType
  248. * @return {boolean}
  249. */
  250. static isDashType(mimeType) {
  251. return mimeType === 'application/dash+xml' ||
  252. mimeType === 'video/vnd.mpeg.dash.mpd';
  253. }
  254. /**
  255. * Get the base and profile of a codec string. Where [0] will be the codec
  256. * base and [1] will be the profile.
  257. * @param {string} codecString
  258. * @return {!Array<string>}
  259. * @private
  260. */
  261. static getCodecParts_(codecString) {
  262. const parts = codecString.split('.');
  263. const base = parts[0];
  264. parts.shift();
  265. const profile = parts.join('.');
  266. // Make sure that we always return a "base" and "profile".
  267. return [base, profile];
  268. }
  269. };
  270. /**
  271. * A map from Stream object keys to MIME type parameters. These should be
  272. * ignored by platforms that do not recognize them.
  273. *
  274. * This initial set of parameters are all recognized by Chromecast.
  275. *
  276. * @const {!Map<string, string>}
  277. * @private
  278. */
  279. shaka.util.MimeUtils.EXTENDED_MIME_PARAMETERS_ = new Map()
  280. .set('codecs', 'codecs')
  281. .set('frameRate', 'framerate') // Ours is camelCase, theirs is lowercase.
  282. .set('bandwidth', 'bitrate') // They are in the same units: bits/sec.
  283. .set('width', 'width')
  284. .set('height', 'height')
  285. .set('channelsCount', 'channels');
  286. /**
  287. * A mimetype created for CEA-608 closed captions.
  288. * @const {string}
  289. */
  290. shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE = 'application/cea-608';
  291. /**
  292. * A mimetype created for CEA-708 closed captions.
  293. * @const {string}
  294. */
  295. shaka.util.MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE = 'application/cea-708';
  296. /**
  297. * MIME types of raw formats.
  298. *
  299. * @const {!Array<string>}
  300. */
  301. shaka.util.MimeUtils.RAW_FORMATS = [
  302. 'audio/aac',
  303. 'audio/ac3',
  304. 'audio/ec3',
  305. 'audio/mpeg',
  306. ];