Source: lib/media/manifest_parser.js

  1. /**
  2. * @license
  3. * Copyright 2016 Google Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. goog.provide('shaka.media.ManifestParser');
  18. goog.require('goog.Uri');
  19. goog.require('goog.asserts');
  20. goog.require('shaka.log');
  21. goog.require('shaka.net.NetworkingEngine');
  22. goog.require('shaka.util.Error');
  23. goog.require('shaka.util.Platform');
  24. /**
  25. * @namespace shaka.media.ManifestParser
  26. * @summary An interface to register manifest parsers.
  27. * @exportDoc
  28. */
  29. /**
  30. * Contains the parser factory functions indexed by MIME type.
  31. *
  32. * @type {!Object.<string, shaka.extern.ManifestParser.Factory>}
  33. */
  34. shaka.media.ManifestParser.parsersByMime = {};
  35. /**
  36. * Contains the parser factory functions indexed by file extension.
  37. *
  38. * @type {!Object.<string, shaka.extern.ManifestParser.Factory>}
  39. */
  40. shaka.media.ManifestParser.parsersByExtension = {};
  41. /**
  42. * Registers a manifest parser by file extension.
  43. *
  44. * @param {string} extension The file extension of the manifest.
  45. * @param {shaka.extern.ManifestParser.Factory} parserFactory The factory
  46. * used to create parser instances.
  47. * @export
  48. */
  49. shaka.media.ManifestParser.registerParserByExtension = function(
  50. extension, parserFactory) {
  51. shaka.media.ManifestParser.parsersByExtension[extension] = parserFactory;
  52. };
  53. /**
  54. * Registers a manifest parser by MIME type.
  55. *
  56. * @param {string} mimeType The MIME type of the manifest.
  57. * @param {shaka.extern.ManifestParser.Factory} parserFactory The factory
  58. * used to create parser instances.
  59. * @export
  60. */
  61. shaka.media.ManifestParser.registerParserByMime = function(
  62. mimeType, parserFactory) {
  63. shaka.media.ManifestParser.parsersByMime[mimeType] = parserFactory;
  64. };
  65. /**
  66. * Returns a map of manifest support for well-known types.
  67. *
  68. * @return {!Object.<string, boolean>}
  69. */
  70. shaka.media.ManifestParser.probeSupport = function() {
  71. const ManifestParser = shaka.media.ManifestParser;
  72. let support = {};
  73. // Make sure all registered parsers are shown, but only for MSE-enabled
  74. // platforms where our parsers matter.
  75. if (shaka.util.Platform.supportsMediaSource()) {
  76. for (let type in ManifestParser.parsersByMime) {
  77. support[type] = true;
  78. }
  79. for (let type in ManifestParser.parsersByExtension) {
  80. support[type] = true;
  81. }
  82. }
  83. // Make sure all well-known types are tested as well, just to show an explicit
  84. // false for things people might be expecting.
  85. const testMimeTypes = [
  86. // DASH
  87. 'application/dash+xml',
  88. // HLS
  89. 'application/x-mpegurl',
  90. 'application/vnd.apple.mpegurl',
  91. // SmoothStreaming
  92. 'application/vnd.ms-sstr+xml',
  93. ];
  94. const testExtensions = {
  95. // DASH
  96. 'mpd': 'application/dash+xml',
  97. // HLS
  98. 'm3u8': 'application/x-mpegurl',
  99. // SmoothStreaming
  100. 'ism': 'application/vnd.ms-sstr+xml',
  101. };
  102. for (let type of testMimeTypes) {
  103. // Only query our parsers for MSE-enabled platforms. Otherwise, query a
  104. // temporary media element for native support for these types.
  105. if (shaka.util.Platform.supportsMediaSource()) {
  106. support[type] = !!ManifestParser.parsersByMime[type];
  107. } else {
  108. support[type] = shaka.util.Platform.supportsMediaType(type);
  109. }
  110. }
  111. for (let extension in testExtensions) {
  112. // Only query our parsers for MSE-enabled platforms. Otherwise, query a
  113. // temporary media element for native support for these MIME type for the
  114. // extension.
  115. if (shaka.util.Platform.supportsMediaSource()) {
  116. support[extension] = !!ManifestParser.parsersByExtension[extension];
  117. } else {
  118. const type = testExtensions[extension];
  119. support[extension] = shaka.util.Platform.supportsMediaType(type);
  120. }
  121. }
  122. return support;
  123. };
  124. /**
  125. * Create a manifest parser to parse the manifest at |uri|.
  126. *
  127. * @param {string} uri
  128. * @param {!shaka.net.NetworkingEngine} netEngine
  129. * @param {shaka.extern.RetryParameters} retryParams
  130. * @param {?string} mimeType
  131. * @return {!Promise.<!shaka.extern.ManifestParser>}
  132. */
  133. shaka.media.ManifestParser.create = async function(
  134. uri, netEngine, retryParams, mimeType) {
  135. try {
  136. const Factory = await shaka.media.ManifestParser.getFactory_(
  137. uri, netEngine, retryParams, mimeType);
  138. return new Factory();
  139. } catch (error) {
  140. goog.asserts.assert(
  141. error instanceof shaka.util.Error, 'Incorrect error type');
  142. // Regardless of what the error was, we need to upgrade it to a critical
  143. // error. We can't do anything if we can't create a manifest parser.
  144. error.severity = shaka.util.Error.Severity.CRITICAL;
  145. throw error;
  146. }
  147. };
  148. /**
  149. * Get a factory that can create a manifest parser that should be able to parse
  150. * the manifest at |uri|.
  151. *
  152. * @param {string} uri
  153. * @param {!shaka.net.NetworkingEngine} netEngine
  154. * @param {shaka.extern.RetryParameters} retryParams
  155. * @param {?string} mimeType
  156. * @return {!Promise.<shaka.extern.ManifestParser.Factory>}
  157. * @private
  158. */
  159. shaka.media.ManifestParser.getFactory_ = async function(
  160. uri, netEngine, retryParams, mimeType) {
  161. const ManifestParser = shaka.media.ManifestParser;
  162. // Try using the MIME type we were given.
  163. if (mimeType) {
  164. const factory = ManifestParser.parsersByMime[mimeType.toLowerCase()];
  165. if (factory) {
  166. return factory;
  167. }
  168. shaka.log.warning(
  169. 'Could not determine manifest type using MIME type ', mimeType);
  170. }
  171. const extension = ManifestParser.getExtension(uri);
  172. if (extension) {
  173. const factory = ManifestParser.parsersByExtension[extension];
  174. if (factory) {
  175. return factory;
  176. }
  177. shaka.log.warning(
  178. 'Could not determine manifest type for extension ', extension);
  179. } else {
  180. shaka.log.warning('Could not find extension for ', uri);
  181. }
  182. if (!mimeType) {
  183. mimeType = await ManifestParser.getMimeType(uri, netEngine, retryParams);
  184. if (mimeType) {
  185. const factory = shaka.media.ManifestParser.parsersByMime[mimeType];
  186. if (factory) {
  187. return factory;
  188. }
  189. shaka.log.warning('Could not determine manifest type using MIME type',
  190. mimeType);
  191. }
  192. }
  193. throw new shaka.util.Error(
  194. shaka.util.Error.Severity.CRITICAL,
  195. shaka.util.Error.Category.MANIFEST,
  196. shaka.util.Error.Code.UNABLE_TO_GUESS_MANIFEST_TYPE,
  197. uri);
  198. };
  199. /**
  200. * @param {string} uri
  201. * @param {!shaka.net.NetworkingEngine} netEngine
  202. * @param {shaka.extern.RetryParameters} retryParams
  203. * @return {!Promise.<string>}
  204. */
  205. shaka.media.ManifestParser.getMimeType = async function(
  206. uri, netEngine, retryParams) {
  207. const type = shaka.net.NetworkingEngine.RequestType.MANIFEST;
  208. let request = shaka.net.NetworkingEngine.makeRequest([uri], retryParams);
  209. request.method = 'HEAD';
  210. let response = await netEngine.request(type, request).promise;
  211. // https://bit.ly/2K9s9kf says this header should always be available,
  212. // but just to be safe:
  213. const mimeType = response.headers['content-type'];
  214. return mimeType ? mimeType.toLowerCase().split(';').shift() : '';
  215. };
  216. /**
  217. * @param {string} uri
  218. * @return {string}
  219. */
  220. shaka.media.ManifestParser.getExtension = function(uri) {
  221. const uriObj = new goog.Uri(uri);
  222. const uriPieces = uriObj.getPath().split('/');
  223. const uriFilename = uriPieces.pop();
  224. const filenamePieces = uriFilename.split('.');
  225. // Only one piece means there is no extension.
  226. if (filenamePieces.length == 1) {
  227. return '';
  228. }
  229. return filenamePieces.pop().toLowerCase();
  230. };
  231. /**
  232. * Determines whether or not this URI and MIME type are supported by our own
  233. * manifest parsers on this platform. This takes into account whether or not
  234. * MediaSource is available, as well as which parsers are registered to the
  235. * system.
  236. *
  237. * @param {string} uri
  238. * @param {string} mimeType
  239. * @return {boolean}
  240. */
  241. shaka.media.ManifestParser.isSupported = function(uri, mimeType) {
  242. // Without MediaSource, our own parsers are useless.
  243. if (!shaka.util.Platform.supportsMediaSource()) {
  244. return false;
  245. }
  246. if (mimeType in shaka.media.ManifestParser.parsersByMime) {
  247. return true;
  248. }
  249. const extension = shaka.media.ManifestParser.getExtension(uri);
  250. if (extension in shaka.media.ManifestParser.parsersByExtension) {
  251. return true;
  252. }
  253. return false;
  254. };