Source: lib/media/manifest_parser.js

/**
 * @license
 * Copyright 2016 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

goog.provide('shaka.media.ManifestParser');

goog.require('goog.Uri');
goog.require('goog.asserts');
goog.require('shaka.log');
goog.require('shaka.net.NetworkingEngine');
goog.require('shaka.util.Error');


/**
 * @namespace shaka.media.ManifestParser
 * @summary An interface to register manifest parsers.
 * @exportDoc
 */


/**
 * Contains the parser factory functions indexed by MIME type.
 *
 * @type {!Object.<string, shakaExtern.ManifestParser.Factory>}
 */
shaka.media.ManifestParser.parsersByMime = {};


/**
 * Contains the parser factory functions indexed by file extension.
 *
 * @type {!Object.<string, shakaExtern.ManifestParser.Factory>}
 */
shaka.media.ManifestParser.parsersByExtension = {};


/**
 * Registers a manifest parser by file extension.
 *
 * @param {string} extension The file extension of the manifest.
 * @param {shakaExtern.ManifestParser.Factory} parserFactory The factory
 *   used to create parser instances.
 * @export
 */
shaka.media.ManifestParser.registerParserByExtension = function(
    extension, parserFactory) {
  shaka.media.ManifestParser.parsersByExtension[extension] = parserFactory;
};


/**
 * Registers a manifest parser by MIME type.
 *
 * @param {string} mimeType The MIME type of the manifest.
 * @param {shakaExtern.ManifestParser.Factory} parserFactory The factory
 *   used to create parser instances.
 * @export
 */
shaka.media.ManifestParser.registerParserByMime = function(
    mimeType, parserFactory) {
  shaka.media.ManifestParser.parsersByMime[mimeType] = parserFactory;
};


/**
 * Returns a map of manifest support for well-known types.
 *
 * @return {!Object.<string, boolean>}
 */
shaka.media.ManifestParser.probeSupport = function() {
  // Make sure all registered parsers are shown.
  let support = {};
  for (let type in shaka.media.ManifestParser.parsersByMime) {
    support[type] = true;
  }
  for (let type in shaka.media.ManifestParser.parsersByExtension) {
    support[type] = true;
  }

  // Make sure all well-known types are tested as well, just to show an explicit
  // false for things people might be expecting.
  let testMimeTypes = [
    // DASH
    'application/dash+xml',
    // HLS
    'application/x-mpegurl',
    'application/vnd.apple.mpegurl',
    // SmoothStreaming
    'application/vnd.ms-sstr+xml'
  ];
  let testExtensions = [
    // DASH
    'mpd',
    // HLS
    'm3u8',
    // SmoothStreaming
    'ism'
  ];

  testMimeTypes.forEach(function(type) {
    support[type] = !!shaka.media.ManifestParser.parsersByMime[type];
  });
  testExtensions.forEach(function(type) {
    support[type] = !!shaka.media.ManifestParser.parsersByExtension[type];
  });

  return support;
};


/**
 * Finds a manifest parser factory to parse the given manifest.
 *
 * @param {string} manifestUri
 * @param {!shaka.net.NetworkingEngine} netEngine
 * @param {shakaExtern.RetryParameters} retryParams
 * @param {shakaExtern.ManifestParser.Factory=} opt_manifestParserFactory
 * @return {!Promise.<shakaExtern.ManifestParser.Factory>}
 */
shaka.media.ManifestParser.getFactory = function(
    manifestUri, netEngine, retryParams, opt_manifestParserFactory) {
  let factory = opt_manifestParserFactory;
  let extension;

  if (!factory) {
    // Try to choose a manifest parser by file extension.
    let uriObj = new goog.Uri(manifestUri);
    let uriPieces = uriObj.getPath().split('/');
    let uriFilename = uriPieces.pop();
    let filenamePieces = uriFilename.split('.');
    // Only one piece means there is no extension.
    if (filenamePieces.length > 1) {
      extension = filenamePieces.pop().toLowerCase();
      factory = shaka.media.ManifestParser.parsersByExtension[extension];
    }
  }

  if (factory) {
    return Promise.resolve(factory);
  }

  // Try to choose a manifest parser by MIME type.
  let headRequest =
      shaka.net.NetworkingEngine.makeRequest([manifestUri], retryParams);
  headRequest.method = 'HEAD';
  const type = shaka.net.NetworkingEngine.RequestType.MANIFEST;

  return netEngine.request(type, headRequest).promise.then(
      function(response) {
        let mimeType = response.headers['content-type'];
        // https://goo.gl/yzKDRx says this header should always be available,
        // but just to be safe:
        if (mimeType) {
          mimeType = mimeType.toLowerCase();
        }
        factory = shaka.media.ManifestParser.parsersByMime[mimeType];
        if (!factory) {
          shaka.log.error(
              'Unable to guess manifest type by file extension ' +
              'or by MIME type.', extension, mimeType);
          return Promise.reject(new shaka.util.Error(
              shaka.util.Error.Severity.CRITICAL,
              shaka.util.Error.Category.MANIFEST,
              shaka.util.Error.Code.UNABLE_TO_GUESS_MANIFEST_TYPE,
              manifestUri));
        }
        return factory;
      }, function(error) {
        goog.asserts.assert(error instanceof shaka.util.Error,
                            'Incorrect error type');
        shaka.log.error('HEAD request to guess manifest type failed!', error);
        error.severity = shaka.util.Error.Severity.CRITICAL;
        return Promise.reject(error);
      });
};