mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-15 16:06:41 +03:00
933a4f2f7a
Player errors out for Dolby contents in adaptive streaming because correct mimetype is not identified. This change adds ac3, ac-3, eac3, ec-3, and ac-4 mimetype support. --------- Co-authored-by: Srikanth Peddibhotla <peddisri@amazon.com>
343 lines
9.0 KiB
JavaScript
343 lines
9.0 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
goog.provide('shaka.util.ManifestParserUtils');
|
|
|
|
goog.require('goog.Uri');
|
|
goog.require('shaka.util.BufferUtils');
|
|
goog.require('shaka.util.Error');
|
|
goog.require('shaka.util.StringUtils');
|
|
goog.require('shaka.util.Uint8ArrayUtils');
|
|
|
|
|
|
/**
|
|
* @summary Utility functions for manifest parsing.
|
|
*/
|
|
shaka.util.ManifestParserUtils = class {
|
|
/**
|
|
* Resolves an array of relative URIs to the given base URIs. This will result
|
|
* in M*N number of URIs.
|
|
*
|
|
* Note: This method is slow in SmartTVs and Consoles. It should only be
|
|
* called when necessary.
|
|
*
|
|
* @param {!Array<string>} baseUris
|
|
* @param {!Array<string>} relativeUris
|
|
* @param {string=} extraQueryParams
|
|
* @return {!Array<string>}
|
|
*/
|
|
static resolveUris(baseUris, relativeUris, extraQueryParams = '') {
|
|
if (relativeUris.length == 0) {
|
|
return baseUris;
|
|
}
|
|
|
|
if (baseUris.length == 1 && relativeUris.length == 1) {
|
|
const baseUri = new goog.Uri(baseUris[0]);
|
|
const relativeUri = new goog.Uri(relativeUris[0]);
|
|
const resolvedUri = baseUri.resolve(relativeUri);
|
|
if (extraQueryParams) {
|
|
resolvedUri.setQueryData(extraQueryParams);
|
|
}
|
|
return [resolvedUri.toString()];
|
|
}
|
|
|
|
const relativeAsGoog = relativeUris.map((uri) => new goog.Uri(uri));
|
|
|
|
// For each base URI, this code resolves it with every relative URI.
|
|
// The result is a single array containing all the resolved URIs.
|
|
const resolvedUris = [];
|
|
for (const baseStr of baseUris) {
|
|
const base = new goog.Uri(baseStr);
|
|
for (const relative of relativeAsGoog) {
|
|
const resolvedUri = base.resolve(relative);
|
|
if (extraQueryParams) {
|
|
resolvedUri.setQueryData(extraQueryParams);
|
|
}
|
|
resolvedUris.push(resolvedUri.toString());
|
|
}
|
|
}
|
|
|
|
return resolvedUris;
|
|
}
|
|
|
|
|
|
/**
|
|
* Creates a DrmInfo object from the given info.
|
|
*
|
|
* @param {string} keySystem
|
|
* @param {string} encryptionScheme
|
|
* @param {Array<shaka.extern.InitDataOverride>} initData
|
|
* @param {string} [keySystemUri]
|
|
* @return {shaka.extern.DrmInfo}
|
|
*/
|
|
static createDrmInfo(keySystem, encryptionScheme, initData, keySystemUri) {
|
|
const drmInfo = {
|
|
keySystem,
|
|
encryptionScheme,
|
|
licenseServerUri: '',
|
|
distinctiveIdentifierRequired: false,
|
|
persistentStateRequired: false,
|
|
audioRobustness: '',
|
|
videoRobustness: '',
|
|
serverCertificate: null,
|
|
serverCertificateUri: '',
|
|
sessionType: '',
|
|
initData: initData || [],
|
|
keyIds: new Set(),
|
|
};
|
|
|
|
if (keySystemUri) {
|
|
drmInfo.keySystemUris = new Set([keySystemUri]);
|
|
}
|
|
|
|
return drmInfo;
|
|
}
|
|
|
|
/**
|
|
* Creates a DrmInfo object from ClearKeys.
|
|
*
|
|
* @param {!Map<string, string>} clearKeys
|
|
* @param {string=} encryptionScheme
|
|
* @return {shaka.extern.DrmInfo}
|
|
*/
|
|
static createDrmInfoFromClearKeys(clearKeys, encryptionScheme = 'cenc') {
|
|
const StringUtils = shaka.util.StringUtils;
|
|
const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
|
|
const keys = [];
|
|
const keyIds = [];
|
|
const originalKeyIds = [];
|
|
|
|
clearKeys.forEach((key, keyId) => {
|
|
let kid = keyId;
|
|
if (kid.length != 22) {
|
|
kid = Uint8ArrayUtils.toBase64(
|
|
Uint8ArrayUtils.fromHex(keyId), false);
|
|
}
|
|
let k = key;
|
|
if (k.length != 22) {
|
|
k = Uint8ArrayUtils.toBase64(
|
|
Uint8ArrayUtils.fromHex(key), false);
|
|
}
|
|
const keyObj = {
|
|
kty: 'oct',
|
|
kid: kid,
|
|
k: k,
|
|
};
|
|
|
|
keys.push(keyObj);
|
|
keyIds.push(keyObj.kid);
|
|
originalKeyIds.push(keyId);
|
|
});
|
|
|
|
const jwkSet = {keys: keys};
|
|
const license = JSON.stringify(jwkSet);
|
|
|
|
// Use the keyids init data since is suggested by EME.
|
|
// Suggestion: https://bit.ly/2JYcNTu
|
|
// Format: https://www.w3.org/TR/eme-initdata-keyids/
|
|
const initDataStr = JSON.stringify({'kids': keyIds});
|
|
const initData =
|
|
shaka.util.BufferUtils.toUint8(StringUtils.toUTF8(initDataStr));
|
|
const initDatas = [{initData: initData, initDataType: 'keyids'}];
|
|
|
|
return {
|
|
keySystem: 'org.w3.clearkey',
|
|
encryptionScheme,
|
|
licenseServerUri: 'data:application/json;base64,' + window.btoa(license),
|
|
distinctiveIdentifierRequired: false,
|
|
persistentStateRequired: false,
|
|
audioRobustness: '',
|
|
videoRobustness: '',
|
|
serverCertificate: null,
|
|
serverCertificateUri: '',
|
|
sessionType: '',
|
|
initData: initDatas,
|
|
keyIds: new Set(originalKeyIds),
|
|
};
|
|
}
|
|
|
|
|
|
/**
|
|
* Attempts to guess which codecs from the codecs list belong to a given
|
|
* content type.
|
|
* Assumes that at least one codec is correct, and throws if none are.
|
|
*
|
|
* @param {string} contentType
|
|
* @param {!Array<string>} codecs
|
|
* @return {string}
|
|
*/
|
|
static guessCodecs(contentType, codecs) {
|
|
if (codecs.length == 1) {
|
|
return codecs[0];
|
|
}
|
|
|
|
const match = shaka.util.ManifestParserUtils.guessCodecsSafe(
|
|
contentType, codecs);
|
|
// A failure is specifically denoted by null; an empty string represents a
|
|
// valid match of no codec.
|
|
if (match != null) {
|
|
return match;
|
|
}
|
|
|
|
// Unable to guess codecs.
|
|
throw new shaka.util.Error(
|
|
shaka.util.Error.Severity.CRITICAL,
|
|
shaka.util.Error.Category.MANIFEST,
|
|
shaka.util.Error.Code.HLS_COULD_NOT_GUESS_CODECS,
|
|
codecs);
|
|
}
|
|
|
|
|
|
/**
|
|
* Attempts to guess which codecs from the codecs list belong to a given
|
|
* content type. Does not assume a single codec is anything special, and does
|
|
* not throw if it fails to match.
|
|
*
|
|
* @param {string} contentType
|
|
* @param {!Array<string>} codecs
|
|
* @return {?string} or null if no match is found
|
|
*/
|
|
static guessCodecsSafe(contentType, codecs) {
|
|
const formats = shaka.util.ManifestParserUtils
|
|
.CODEC_REGEXPS_BY_CONTENT_TYPE_.get(contentType);
|
|
for (const format of formats) {
|
|
for (const codec of codecs) {
|
|
if (format.test(codec.trim())) {
|
|
return codec.trim();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Text does not require a codec string.
|
|
if (contentType == shaka.util.ManifestParserUtils.ContentType.TEXT) {
|
|
return '';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
/**
|
|
* Attempts to guess which codecs from the codecs list belong to a given
|
|
* content.
|
|
*
|
|
* @param {string} contentType
|
|
* @param {!Array<string>} codecs
|
|
* @return {!Array<string>}
|
|
*/
|
|
static guessAllCodecsSafe(contentType, codecs) {
|
|
const allCodecs = [];
|
|
const formats = shaka.util.ManifestParserUtils
|
|
.CODEC_REGEXPS_BY_CONTENT_TYPE_.get(contentType);
|
|
for (const format of formats) {
|
|
for (const codec of codecs) {
|
|
if (format.test(codec.trim())) {
|
|
allCodecs.push(codec.trim());
|
|
}
|
|
}
|
|
}
|
|
|
|
return allCodecs;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @enum {string}
|
|
*/
|
|
shaka.util.ManifestParserUtils.ContentType = {
|
|
VIDEO: 'video',
|
|
AUDIO: 'audio',
|
|
TEXT: 'text',
|
|
IMAGE: 'image',
|
|
APPLICATION: 'application',
|
|
};
|
|
|
|
|
|
/**
|
|
* @enum {string}
|
|
*/
|
|
shaka.util.ManifestParserUtils.TextStreamKind = {
|
|
SUBTITLE: 'subtitle',
|
|
CLOSED_CAPTION: 'caption',
|
|
};
|
|
|
|
|
|
/**
|
|
* Specifies how tolerant the player is of inaccurate segment start times and
|
|
* end times within a manifest. For example, gaps or overlaps between segments
|
|
* in a SegmentTimeline which are greater than or equal to this value will
|
|
* result in a warning message.
|
|
*
|
|
* @const {number}
|
|
*/
|
|
shaka.util.ManifestParserUtils.GAP_OVERLAP_TOLERANCE_SECONDS = 1 / 15;
|
|
|
|
|
|
/**
|
|
* A list of regexps to detect well-known video codecs.
|
|
*
|
|
* @const {!Array<!RegExp>}
|
|
* @private
|
|
*/
|
|
shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_ = [
|
|
/^avc/,
|
|
/^hev/,
|
|
/^hvc/,
|
|
/^vvc/,
|
|
/^vvi/,
|
|
/^vp0?[89]/,
|
|
/^av01/,
|
|
/^dvh/, // Dolby Vision based in HEVC
|
|
/^dva/, // Dolby Vision based in AVC
|
|
/^dav/, // Dolby Vision based in AV1
|
|
];
|
|
|
|
|
|
/**
|
|
* A list of regexps to detect well-known audio codecs.
|
|
*
|
|
* @const {!Array<!RegExp>}
|
|
* @private
|
|
*/
|
|
shaka.util.ManifestParserUtils.AUDIO_CODEC_REGEXPS_ = [
|
|
/^vorbis$/,
|
|
/^Opus$/, // correct codec string according to RFC 6381 section 3.3
|
|
/^opus$/, // some manifests wrongfully use this
|
|
/^fLaC$/, // correct codec string according to RFC 6381 section 3.3
|
|
/^flac$/, // some manifests wrongfully use this
|
|
/^mp4a/,
|
|
/^[ae]c-3$/,
|
|
/^ac-4/,
|
|
/^dts[cex]$/, // DTS Digital Surround (dtsc), DTS Express (dtse), DTS:X (dtsx)
|
|
/^iamf/,
|
|
/^mhm[12]/, // MPEG-H Audio LC
|
|
/^ac3$/, // sometimes ac3 is used instead of ac-3
|
|
/^eac3$/, // sometimes "eac3" is used instead of "ec-3"
|
|
];
|
|
|
|
|
|
/**
|
|
* A list of regexps to detect well-known text codecs.
|
|
*
|
|
* @const {!Array<!RegExp>}
|
|
* @private
|
|
*/
|
|
shaka.util.ManifestParserUtils.TEXT_CODEC_REGEXPS_ = [
|
|
/^vtt$/,
|
|
/^wvtt/,
|
|
/^stpp/,
|
|
];
|
|
|
|
|
|
/**
|
|
* @const {!Map<string, !Array<!RegExp>>}
|
|
*/
|
|
shaka.util.ManifestParserUtils.CODEC_REGEXPS_BY_CONTENT_TYPE_ = new Map()
|
|
.set('audio', shaka.util.ManifestParserUtils.AUDIO_CODEC_REGEXPS_)
|
|
.set('video', shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_)
|
|
.set('text', shaka.util.ManifestParserUtils.TEXT_CODEC_REGEXPS_);
|