Source: lib/media/manifest_parser.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.media.ManifestParser');
  7. goog.require('goog.Uri');
  8. goog.require('shaka.log');
  9. goog.require('shaka.net.NetworkingEngine');
  10. goog.require('shaka.util.Error');
  11. goog.require('shaka.util.Platform');
  12. // TODO: revisit this when Closure Compiler supports partially-exported classes.
  13. /**
  14. * @summary An interface to register manifest parsers.
  15. * @export
  16. */
  17. shaka.media.ManifestParser = class {
  18. /**
  19. * Registers a manifest parser by file extension.
  20. *
  21. * @param {string} extension The file extension of the manifest.
  22. * @param {shaka.extern.ManifestParser.Factory} parserFactory The factory
  23. * used to create parser instances.
  24. * @export
  25. */
  26. static registerParserByExtension(extension, parserFactory) {
  27. shaka.media.ManifestParser.parsersByExtension[extension] = parserFactory;
  28. }
  29. /**
  30. * Registers a manifest parser by MIME type.
  31. *
  32. * @param {string} mimeType The MIME type of the manifest.
  33. * @param {shaka.extern.ManifestParser.Factory} parserFactory The factory
  34. * used to create parser instances.
  35. * @export
  36. */
  37. static registerParserByMime(mimeType, parserFactory) {
  38. shaka.media.ManifestParser.parsersByMime[mimeType] = parserFactory;
  39. }
  40. /**
  41. * Unregisters a manifest parser by MIME type.
  42. *
  43. * @param {string} mimeType The MIME type of the manifest.
  44. * @export
  45. */
  46. static unregisterParserByMime(mimeType) {
  47. delete shaka.media.ManifestParser.parsersByMime[mimeType];
  48. }
  49. /**
  50. * Returns a map of manifest support for well-known types.
  51. *
  52. * @return {!Object.<string, boolean>}
  53. */
  54. static probeSupport() {
  55. const ManifestParser = shaka.media.ManifestParser;
  56. const support = {};
  57. // Make sure all registered parsers are shown, but only for MSE-enabled
  58. // platforms where our parsers matter.
  59. if (shaka.util.Platform.supportsMediaSource()) {
  60. for (const type in ManifestParser.parsersByMime) {
  61. support[type] = true;
  62. }
  63. for (const type in ManifestParser.parsersByExtension) {
  64. support[type] = true;
  65. }
  66. }
  67. // Make sure all well-known types are tested as well, just to show an
  68. // explicit false for things people might be expecting.
  69. const testMimeTypes = [
  70. // DASH
  71. 'application/dash+xml',
  72. // HLS
  73. 'application/x-mpegurl',
  74. 'application/vnd.apple.mpegurl',
  75. // SmoothStreaming
  76. 'application/vnd.ms-sstr+xml',
  77. ];
  78. const testExtensions = {
  79. // DASH
  80. 'mpd': 'application/dash+xml',
  81. // HLS
  82. 'm3u8': 'application/x-mpegurl',
  83. // SmoothStreaming
  84. 'ism': 'application/vnd.ms-sstr+xml',
  85. };
  86. for (const type of testMimeTypes) {
  87. // Only query our parsers for MSE-enabled platforms. Otherwise, query a
  88. // temporary media element for native support for these types.
  89. if (shaka.util.Platform.supportsMediaSource()) {
  90. support[type] = !!ManifestParser.parsersByMime[type];
  91. } else {
  92. support[type] = shaka.util.Platform.supportsMediaType(type);
  93. }
  94. }
  95. for (const extension in testExtensions) {
  96. // Only query our parsers for MSE-enabled platforms. Otherwise, query a
  97. // temporary media element for native support for these MIME type for the
  98. // extension.
  99. if (shaka.util.Platform.supportsMediaSource()) {
  100. support[extension] = !!ManifestParser.parsersByExtension[extension];
  101. } else {
  102. const type = testExtensions[extension];
  103. support[extension] = shaka.util.Platform.supportsMediaType(type);
  104. }
  105. }
  106. return support;
  107. }
  108. /**
  109. * Get a factory that can create a manifest parser that should be able to
  110. * parse the manifest at |uri|.
  111. *
  112. * @param {string} uri
  113. * @param {!shaka.net.NetworkingEngine} netEngine
  114. * @param {shaka.extern.RetryParameters} retryParams
  115. * @param {?string} mimeType
  116. * @return {!Promise.<shaka.extern.ManifestParser.Factory>}
  117. */
  118. static async getFactory(uri, netEngine, retryParams, mimeType) {
  119. const ManifestParser = shaka.media.ManifestParser;
  120. // Try using the MIME type we were given.
  121. if (mimeType) {
  122. const factory = ManifestParser.parsersByMime[mimeType.toLowerCase()];
  123. if (factory) {
  124. return factory;
  125. }
  126. shaka.log.warning(
  127. 'Could not determine manifest type using MIME type ', mimeType);
  128. }
  129. const extension = ManifestParser.getExtension(uri);
  130. if (extension) {
  131. const factory = ManifestParser.parsersByExtension[extension];
  132. if (factory) {
  133. return factory;
  134. }
  135. shaka.log.warning(
  136. 'Could not determine manifest type for extension ', extension);
  137. } else {
  138. shaka.log.warning('Could not find extension for ', uri);
  139. }
  140. if (!mimeType) {
  141. mimeType = await ManifestParser.getMimeType(uri, netEngine, retryParams);
  142. if (mimeType) {
  143. const factory = shaka.media.ManifestParser.parsersByMime[mimeType];
  144. if (factory) {
  145. return factory;
  146. }
  147. shaka.log.warning('Could not determine manifest type using MIME type',
  148. mimeType);
  149. }
  150. }
  151. throw new shaka.util.Error(
  152. shaka.util.Error.Severity.CRITICAL,
  153. shaka.util.Error.Category.MANIFEST,
  154. shaka.util.Error.Code.UNABLE_TO_GUESS_MANIFEST_TYPE,
  155. uri,
  156. mimeType);
  157. }
  158. /**
  159. * @param {string} uri
  160. * @param {!shaka.net.NetworkingEngine} netEngine
  161. * @param {shaka.extern.RetryParameters} retryParams
  162. * @return {!Promise.<string>}
  163. */
  164. static async getMimeType(uri, netEngine, retryParams) {
  165. const type = shaka.net.NetworkingEngine.RequestType.MANIFEST;
  166. const request = shaka.net.NetworkingEngine.makeRequest([uri], retryParams);
  167. request.method = 'HEAD';
  168. const response = await netEngine.request(type, request).promise;
  169. // https://bit.ly/2K9s9kf says this header should always be available,
  170. // but just to be safe:
  171. const mimeType = response.headers['content-type'];
  172. return mimeType ? mimeType.toLowerCase().split(';').shift() : '';
  173. }
  174. /**
  175. * @param {string} uri
  176. * @return {string}
  177. */
  178. static getExtension(uri) {
  179. const uriObj = new goog.Uri(uri);
  180. const uriPieces = uriObj.getPath().split('/');
  181. const uriFilename = uriPieces.pop();
  182. const filenamePieces = uriFilename.split('.');
  183. // Only one piece means there is no extension.
  184. if (filenamePieces.length == 1) {
  185. return '';
  186. }
  187. return filenamePieces.pop().toLowerCase();
  188. }
  189. /**
  190. * Determines whether or not this URI and MIME type are supported by our own
  191. * manifest parsers on this platform. This takes into account whether or not
  192. * MediaSource is available, as well as which parsers are registered to the
  193. * system.
  194. *
  195. * @param {string} uri
  196. * @param {string} mimeType
  197. * @return {boolean}
  198. */
  199. static isSupported(uri, mimeType) {
  200. // Without MediaSource, our own parsers are useless.
  201. if (!shaka.util.Platform.supportsMediaSource()) {
  202. return false;
  203. }
  204. if (mimeType in shaka.media.ManifestParser.parsersByMime) {
  205. return true;
  206. }
  207. const extension = shaka.media.ManifestParser.getExtension(uri);
  208. if (extension in shaka.media.ManifestParser.parsersByExtension) {
  209. return true;
  210. }
  211. return false;
  212. }
  213. };
  214. /**
  215. * Contains the parser factory functions indexed by MIME type.
  216. *
  217. * @type {!Object.<string, shaka.extern.ManifestParser.Factory>}
  218. */
  219. shaka.media.ManifestParser.parsersByMime = {};
  220. /**
  221. * Contains the parser factory functions indexed by file extension.
  222. *
  223. * @type {!Object.<string, shaka.extern.ManifestParser.Factory>}
  224. */
  225. shaka.media.ManifestParser.parsersByExtension = {};