codecs.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import window from 'global/window';
  2. var regexs = {
  3. // to determine mime types
  4. mp4: /^(av0?1|avc0?[1234]|vp0?9|flac|opus|mp3|mp4a|mp4v|stpp.ttml.im1t)/,
  5. webm: /^(vp0?[89]|av0?1|opus|vorbis)/,
  6. ogg: /^(vp0?[89]|theora|flac|opus|vorbis)/,
  7. // to determine if a codec is audio or video
  8. video: /^(av0?1|avc0?[1234]|vp0?[89]|hvc1|hev1|theora|mp4v)/,
  9. audio: /^(mp4a|flac|vorbis|opus|ac-[34]|ec-3|alac|mp3|speex|aac)/,
  10. text: /^(stpp.ttml.im1t)/,
  11. // mux.js support regex
  12. muxerVideo: /^(avc0?1)/,
  13. muxerAudio: /^(mp4a)/,
  14. // match nothing as muxer does not support text right now.
  15. // there cannot never be a character before the start of a string
  16. // so this matches nothing.
  17. muxerText: /a^/
  18. };
  19. var mediaTypes = ['video', 'audio', 'text'];
  20. var upperMediaTypes = ['Video', 'Audio', 'Text'];
  21. /**
  22. * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
  23. * `avc1.<hhhhhh>`
  24. *
  25. * @param {string} codec
  26. * Codec string to translate
  27. * @return {string}
  28. * The translated codec string
  29. */
  30. export var translateLegacyCodec = function translateLegacyCodec(codec) {
  31. if (!codec) {
  32. return codec;
  33. }
  34. return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
  35. var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
  36. var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
  37. return 'avc1.' + profileHex + '00' + avcLevelHex;
  38. });
  39. };
  40. /**
  41. * Replace the old apple-style `avc1.<dd>.<dd>` codec strings with the standard
  42. * `avc1.<hhhhhh>`
  43. *
  44. * @param {string[]} codecs
  45. * An array of codec strings to translate
  46. * @return {string[]}
  47. * The translated array of codec strings
  48. */
  49. export var translateLegacyCodecs = function translateLegacyCodecs(codecs) {
  50. return codecs.map(translateLegacyCodec);
  51. };
  52. /**
  53. * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
  54. * standard `avc1.<hhhhhh>`.
  55. *
  56. * @param {string} codecString
  57. * The codec string
  58. * @return {string}
  59. * The codec string with old apple-style codecs replaced
  60. *
  61. * @private
  62. */
  63. export var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) {
  64. return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) {
  65. return translateLegacyCodecs([match])[0];
  66. });
  67. };
  68. /**
  69. * @typedef {Object} ParsedCodecInfo
  70. * @property {number} codecCount
  71. * Number of codecs parsed
  72. * @property {string} [videoCodec]
  73. * Parsed video codec (if found)
  74. * @property {string} [videoObjectTypeIndicator]
  75. * Video object type indicator (if found)
  76. * @property {string|null} audioProfile
  77. * Audio profile
  78. */
  79. /**
  80. * Parses a codec string to retrieve the number of codecs specified, the video codec and
  81. * object type indicator, and the audio profile.
  82. *
  83. * @param {string} [codecString]
  84. * The codec string to parse
  85. * @return {ParsedCodecInfo}
  86. * Parsed codec info
  87. */
  88. export var parseCodecs = function parseCodecs(codecString) {
  89. if (codecString === void 0) {
  90. codecString = '';
  91. }
  92. var codecs = codecString.split(',');
  93. var result = [];
  94. codecs.forEach(function (codec) {
  95. codec = codec.trim();
  96. var codecType;
  97. mediaTypes.forEach(function (name) {
  98. var match = regexs[name].exec(codec.toLowerCase());
  99. if (!match || match.length <= 1) {
  100. return;
  101. }
  102. codecType = name; // maintain codec case
  103. var type = codec.substring(0, match[1].length);
  104. var details = codec.replace(type, '');
  105. result.push({
  106. type: type,
  107. details: details,
  108. mediaType: name
  109. });
  110. });
  111. if (!codecType) {
  112. result.push({
  113. type: codec,
  114. details: '',
  115. mediaType: 'unknown'
  116. });
  117. }
  118. });
  119. return result;
  120. };
  121. /**
  122. * Returns a ParsedCodecInfo object for the default alternate audio playlist if there is
  123. * a default alternate audio playlist for the provided audio group.
  124. *
  125. * @param {Object} master
  126. * The master playlist
  127. * @param {string} audioGroupId
  128. * ID of the audio group for which to find the default codec info
  129. * @return {ParsedCodecInfo}
  130. * Parsed codec info
  131. */
  132. export var codecsFromDefault = function codecsFromDefault(master, audioGroupId) {
  133. if (!master.mediaGroups.AUDIO || !audioGroupId) {
  134. return null;
  135. }
  136. var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
  137. if (!audioGroup) {
  138. return null;
  139. }
  140. for (var name in audioGroup) {
  141. var audioType = audioGroup[name];
  142. if (audioType.default && audioType.playlists) {
  143. // codec should be the same for all playlists within the audio type
  144. return parseCodecs(audioType.playlists[0].attributes.CODECS);
  145. }
  146. }
  147. return null;
  148. };
  149. export var isVideoCodec = function isVideoCodec(codec) {
  150. if (codec === void 0) {
  151. codec = '';
  152. }
  153. return regexs.video.test(codec.trim().toLowerCase());
  154. };
  155. export var isAudioCodec = function isAudioCodec(codec) {
  156. if (codec === void 0) {
  157. codec = '';
  158. }
  159. return regexs.audio.test(codec.trim().toLowerCase());
  160. };
  161. export var isTextCodec = function isTextCodec(codec) {
  162. if (codec === void 0) {
  163. codec = '';
  164. }
  165. return regexs.text.test(codec.trim().toLowerCase());
  166. };
  167. export var getMimeForCodec = function getMimeForCodec(codecString) {
  168. if (!codecString || typeof codecString !== 'string') {
  169. return;
  170. }
  171. var codecs = codecString.toLowerCase().split(',').map(function (c) {
  172. return translateLegacyCodec(c.trim());
  173. }); // default to video type
  174. var type = 'video'; // only change to audio type if the only codec we have is
  175. // audio
  176. if (codecs.length === 1 && isAudioCodec(codecs[0])) {
  177. type = 'audio';
  178. } else if (codecs.length === 1 && isTextCodec(codecs[0])) {
  179. // text uses application/<container> for now
  180. type = 'application';
  181. } // default the container to mp4
  182. var container = 'mp4'; // every codec must be able to go into the container
  183. // for that container to be the correct one
  184. if (codecs.every(function (c) {
  185. return regexs.mp4.test(c);
  186. })) {
  187. container = 'mp4';
  188. } else if (codecs.every(function (c) {
  189. return regexs.webm.test(c);
  190. })) {
  191. container = 'webm';
  192. } else if (codecs.every(function (c) {
  193. return regexs.ogg.test(c);
  194. })) {
  195. container = 'ogg';
  196. }
  197. return type + "/" + container + ";codecs=\"" + codecString + "\"";
  198. };
  199. export var browserSupportsCodec = function browserSupportsCodec(codecString) {
  200. if (codecString === void 0) {
  201. codecString = '';
  202. }
  203. return window.MediaSource && window.MediaSource.isTypeSupported && window.MediaSource.isTypeSupported(getMimeForCodec(codecString)) || false;
  204. };
  205. export var muxerSupportsCodec = function muxerSupportsCodec(codecString) {
  206. if (codecString === void 0) {
  207. codecString = '';
  208. }
  209. return codecString.toLowerCase().split(',').every(function (codec) {
  210. codec = codec.trim(); // any match is supported.
  211. for (var i = 0; i < upperMediaTypes.length; i++) {
  212. var type = upperMediaTypes[i];
  213. if (regexs["muxer" + type].test(codec)) {
  214. return true;
  215. }
  216. }
  217. return false;
  218. });
  219. };
  220. export var DEFAULT_AUDIO_CODEC = 'mp4a.40.2';
  221. export var DEFAULT_VIDEO_CODEC = 'avc1.4d400d';