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. }
  157. /**
  158. * @param {string} uri
  159. * @param {!shaka.net.NetworkingEngine} netEngine
  160. * @param {shaka.extern.RetryParameters} retryParams
  161. * @return {!Promise.<string>}
  162. */
  163. static async getMimeType(uri, netEngine, retryParams) {
  164. const type = shaka.net.NetworkingEngine.RequestType.MANIFEST;
  165. const request = shaka.net.NetworkingEngine.makeRequest([uri], retryParams);
  166. request.method = 'HEAD';
  167. const response = await netEngine.request(type, request).promise;
  168. // https://bit.ly/2K9s9kf says this header should always be available,
  169. // but just to be safe:
  170. const mimeType = response.headers['content-type'];
  171. return mimeType ? mimeType.toLowerCase().split(';').shift() : '';
  172. }
  173. /**
  174. * @param {string} uri
  175. * @return {string}
  176. */
  177. static getExtension(uri) {
  178. const uriObj = new goog.Uri(uri);
  179. const uriPieces = uriObj.getPath().split('/');
  180. const uriFilename = uriPieces.pop();
  181. const filenamePieces = uriFilename.split('.');
  182. // Only one piece means there is no extension.
  183. if (filenamePieces.length == 1) {
  184. return '';
  185. }
  186. return filenamePieces.pop().toLowerCase();
  187. }
  188. /**
  189. * Determines whether or not this URI and MIME type are supported by our own
  190. * manifest parsers on this platform. This takes into account whether or not
  191. * MediaSource is available, as well as which parsers are registered to the
  192. * system.
  193. *
  194. * @param {string} uri
  195. * @param {string} mimeType
  196. * @return {boolean}
  197. */
  198. static isSupported(uri, mimeType) {
  199. // Without MediaSource, our own parsers are useless.
  200. if (!shaka.util.Platform.supportsMediaSource()) {
  201. return false;
  202. }
  203. if (mimeType in shaka.media.ManifestParser.parsersByMime) {
  204. return true;
  205. }
  206. const extension = shaka.media.ManifestParser.getExtension(uri);
  207. if (extension in shaka.media.ManifestParser.parsersByExtension) {
  208. return true;
  209. }
  210. return false;
  211. }
  212. };
  213. /**
  214. * Contains the parser factory functions indexed by MIME type.
  215. *
  216. * @type {!Object.<string, shaka.extern.ManifestParser.Factory>}
  217. */
  218. shaka.media.ManifestParser.parsersByMime = {};
  219. /**
  220. * Contains the parser factory functions indexed by file extension.
  221. *
  222. * @type {!Object.<string, shaka.extern.ManifestParser.Factory>}
  223. */
  224. shaka.media.ManifestParser.parsersByExtension = {};