Source: lib/util/manifest_parser_utils.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.util.ManifestParserUtils');
  7. goog.require('goog.Uri');
  8. goog.require('shaka.util.BufferUtils');
  9. goog.require('shaka.util.Error');
  10. goog.require('shaka.util.StringUtils');
  11. goog.require('shaka.util.Uint8ArrayUtils');
  12. /**
  13. * @summary Utility functions for manifest parsing.
  14. */
  15. shaka.util.ManifestParserUtils = class {
  16. /**
  17. * Resolves an array of relative URIs to the given base URIs. This will result
  18. * in M*N number of URIs.
  19. *
  20. * Note: This method is slow in SmartTVs and Consoles. It should only be
  21. * called when necessary.
  22. *
  23. * @param {!Array.<string>} baseUris
  24. * @param {!Array.<string>} relativeUris
  25. * @return {!Array.<string>}
  26. */
  27. static resolveUris(baseUris, relativeUris) {
  28. if (relativeUris.length == 0) {
  29. return baseUris;
  30. }
  31. if (baseUris.length == 1 && relativeUris.length == 1) {
  32. const baseUri = new goog.Uri(baseUris[0]);
  33. const relativeUri = new goog.Uri(relativeUris[0]);
  34. return [baseUri.resolve(relativeUri).toString()];
  35. }
  36. const relativeAsGoog = relativeUris.map((uri) => new goog.Uri(uri));
  37. // For each base URI, this code resolves it with every relative URI.
  38. // The result is a single array containing all the resolved URIs.
  39. const resolvedUris = [];
  40. for (const baseStr of baseUris) {
  41. const base = new goog.Uri(baseStr);
  42. for (const relative of relativeAsGoog) {
  43. resolvedUris.push(base.resolve(relative).toString());
  44. }
  45. }
  46. return resolvedUris;
  47. }
  48. /**
  49. * Creates a DrmInfo object from the given info.
  50. *
  51. * @param {string} keySystem
  52. * @param {string} encryptionScheme
  53. * @param {Array.<shaka.extern.InitDataOverride>} initData
  54. * @param {string} [keySystemUri]
  55. * @return {shaka.extern.DrmInfo}
  56. */
  57. static createDrmInfo(keySystem, encryptionScheme, initData, keySystemUri) {
  58. const drmInfo = {
  59. keySystem,
  60. encryptionScheme,
  61. licenseServerUri: '',
  62. distinctiveIdentifierRequired: false,
  63. persistentStateRequired: false,
  64. audioRobustness: '',
  65. videoRobustness: '',
  66. serverCertificate: null,
  67. serverCertificateUri: '',
  68. sessionType: '',
  69. initData: initData || [],
  70. keyIds: new Set(),
  71. };
  72. if (keySystemUri) {
  73. drmInfo.keySystemUris = new Set([keySystemUri]);
  74. }
  75. return drmInfo;
  76. }
  77. /**
  78. * Creates a DrmInfo object from ClearKeys.
  79. *
  80. * @param {!Map.<string, string>} clearKeys
  81. * @param {string=} encryptionScheme
  82. * @return {shaka.extern.DrmInfo}
  83. */
  84. static createDrmInfoFromClearKeys(clearKeys, encryptionScheme = 'cenc') {
  85. const StringUtils = shaka.util.StringUtils;
  86. const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
  87. const keys = [];
  88. const keyIds = [];
  89. const originalKeyIds = [];
  90. clearKeys.forEach((key, keyId) => {
  91. let kid = keyId;
  92. if (kid.length != 22) {
  93. kid = Uint8ArrayUtils.toBase64(
  94. Uint8ArrayUtils.fromHex(keyId), false);
  95. }
  96. let k = key;
  97. if (k.length != 22) {
  98. k = Uint8ArrayUtils.toBase64(
  99. Uint8ArrayUtils.fromHex(key), false);
  100. }
  101. const keyObj = {
  102. kty: 'oct',
  103. kid: kid,
  104. k: k,
  105. };
  106. keys.push(keyObj);
  107. keyIds.push(keyObj.kid);
  108. originalKeyIds.push(keyId);
  109. });
  110. const jwkSet = {keys: keys};
  111. const license = JSON.stringify(jwkSet);
  112. // Use the keyids init data since is suggested by EME.
  113. // Suggestion: https://bit.ly/2JYcNTu
  114. // Format: https://www.w3.org/TR/eme-initdata-keyids/
  115. const initDataStr = JSON.stringify({'kids': keyIds});
  116. const initData =
  117. shaka.util.BufferUtils.toUint8(StringUtils.toUTF8(initDataStr));
  118. const initDatas = [{initData: initData, initDataType: 'keyids'}];
  119. return {
  120. keySystem: 'org.w3.clearkey',
  121. encryptionScheme,
  122. licenseServerUri: 'data:application/json;base64,' + window.btoa(license),
  123. distinctiveIdentifierRequired: false,
  124. persistentStateRequired: false,
  125. audioRobustness: '',
  126. videoRobustness: '',
  127. serverCertificate: null,
  128. serverCertificateUri: '',
  129. sessionType: '',
  130. initData: initDatas,
  131. keyIds: new Set(originalKeyIds),
  132. };
  133. }
  134. /**
  135. * Attempts to guess which codecs from the codecs list belong to a given
  136. * content type.
  137. * Assumes that at least one codec is correct, and throws if none are.
  138. *
  139. * @param {string} contentType
  140. * @param {!Array.<string>} codecs
  141. * @return {string}
  142. */
  143. static guessCodecs(contentType, codecs) {
  144. if (codecs.length == 1) {
  145. return codecs[0];
  146. }
  147. const match = shaka.util.ManifestParserUtils.guessCodecsSafe(
  148. contentType, codecs);
  149. // A failure is specifically denoted by null; an empty string represents a
  150. // valid match of no codec.
  151. if (match != null) {
  152. return match;
  153. }
  154. // Unable to guess codecs.
  155. throw new shaka.util.Error(
  156. shaka.util.Error.Severity.CRITICAL,
  157. shaka.util.Error.Category.MANIFEST,
  158. shaka.util.Error.Code.HLS_COULD_NOT_GUESS_CODECS,
  159. codecs);
  160. }
  161. /**
  162. * Attempts to guess which codecs from the codecs list belong to a given
  163. * content type. Does not assume a single codec is anything special, and does
  164. * not throw if it fails to match.
  165. *
  166. * @param {string} contentType
  167. * @param {!Array.<string>} codecs
  168. * @return {?string} or null if no match is found
  169. */
  170. static guessCodecsSafe(contentType, codecs) {
  171. const formats = shaka.util.ManifestParserUtils
  172. .CODEC_REGEXPS_BY_CONTENT_TYPE_[contentType];
  173. for (const format of formats) {
  174. for (const codec of codecs) {
  175. if (format.test(codec.trim())) {
  176. return codec.trim();
  177. }
  178. }
  179. }
  180. // Text does not require a codec string.
  181. if (contentType == shaka.util.ManifestParserUtils.ContentType.TEXT) {
  182. return '';
  183. }
  184. return null;
  185. }
  186. /**
  187. * Attempts to guess which codecs from the codecs list belong to a given
  188. * content.
  189. *
  190. * @param {string} contentType
  191. * @param {!Array.<string>} codecs
  192. * @return {!Array.<string>}
  193. */
  194. static guessAllCodecsSafe(contentType, codecs) {
  195. const allCodecs = [];
  196. const formats = shaka.util.ManifestParserUtils
  197. .CODEC_REGEXPS_BY_CONTENT_TYPE_[contentType];
  198. for (const format of formats) {
  199. for (const codec of codecs) {
  200. if (format.test(codec.trim())) {
  201. allCodecs.push(codec.trim());
  202. }
  203. }
  204. }
  205. return allCodecs;
  206. }
  207. };
  208. /**
  209. * @enum {string}
  210. */
  211. shaka.util.ManifestParserUtils.ContentType = {
  212. VIDEO: 'video',
  213. AUDIO: 'audio',
  214. TEXT: 'text',
  215. IMAGE: 'image',
  216. APPLICATION: 'application',
  217. };
  218. /**
  219. * @enum {string}
  220. */
  221. shaka.util.ManifestParserUtils.TextStreamKind = {
  222. SUBTITLE: 'subtitle',
  223. CLOSED_CAPTION: 'caption',
  224. };
  225. /**
  226. * Specifies how tolerant the player is of inaccurate segment start times and
  227. * end times within a manifest. For example, gaps or overlaps between segments
  228. * in a SegmentTimeline which are greater than or equal to this value will
  229. * result in a warning message.
  230. *
  231. * @const {number}
  232. */
  233. shaka.util.ManifestParserUtils.GAP_OVERLAP_TOLERANCE_SECONDS = 1 / 15;
  234. /**
  235. * A list of regexps to detect well-known video codecs.
  236. *
  237. * @const {!Array.<!RegExp>}
  238. * @private
  239. */
  240. shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_ = [
  241. /^avc/,
  242. /^hev/,
  243. /^hvc/,
  244. /^vvc/,
  245. /^vvi/,
  246. /^vp0?[89]/,
  247. /^av01/,
  248. /^dvh/, // Dolby Vision based in HEVC
  249. /^dva/, // Dolby Vision based in AVC
  250. /^dav/, // Dolby Vision based in AV1
  251. ];
  252. /**
  253. * A list of regexps to detect well-known audio codecs.
  254. *
  255. * @const {!Array.<!RegExp>}
  256. * @private
  257. */
  258. shaka.util.ManifestParserUtils.AUDIO_CODEC_REGEXPS_ = [
  259. /^vorbis$/,
  260. /^Opus$/, // correct codec string according to RFC 6381 section 3.3
  261. /^opus$/, // some manifests wrongfully use this
  262. /^fLaC$/, // correct codec string according to RFC 6381 section 3.3
  263. /^flac$/, // some manifests wrongfully use this
  264. /^mp4a/,
  265. /^[ae]c-3$/,
  266. /^ac-4/,
  267. /^dts[cex]$/, // DTS Digital Surround (dtsc), DTS Express (dtse), DTS:X (dtsx)
  268. /^iamf/,
  269. /^mhm[12]/, // MPEG-H Audio LC
  270. ];
  271. /**
  272. * A list of regexps to detect well-known text codecs.
  273. *
  274. * @const {!Array.<!RegExp>}
  275. * @private
  276. */
  277. shaka.util.ManifestParserUtils.TEXT_CODEC_REGEXPS_ = [
  278. /^vtt$/,
  279. /^wvtt/,
  280. /^stpp/,
  281. ];
  282. /**
  283. * @const {!Object.<string, !Array.<!RegExp>>}
  284. */
  285. shaka.util.ManifestParserUtils.CODEC_REGEXPS_BY_CONTENT_TYPE_ = {
  286. 'audio': shaka.util.ManifestParserUtils.AUDIO_CODEC_REGEXPS_,
  287. 'video': shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_,
  288. 'text': shaka.util.ManifestParserUtils.TEXT_CODEC_REGEXPS_,
  289. };