vhs-utils.js 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494
  1. /*! @name @videojs/vhs-utils @version 4.0.0 @license MIT */
  2. (function (global, factory) {
  3. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  4. typeof define === 'function' && define.amd ? define(factory) :
  5. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.vhsUtils = factory());
  6. })(this, (function () { 'use strict';
  7. const regexs = {
  8. // to determine mime types
  9. mp4: /^(av0?1|avc0?[1234]|vp0?9|flac|opus|mp3|mp4a|mp4v|stpp.ttml.im1t)/,
  10. webm: /^(vp0?[89]|av0?1|opus|vorbis)/,
  11. ogg: /^(vp0?[89]|theora|flac|opus|vorbis)/,
  12. // to determine if a codec is audio or video
  13. video: /^(av0?1|avc0?[1234]|vp0?[89]|hvc1|hev1|theora|mp4v)/,
  14. audio: /^(mp4a|flac|vorbis|opus|ac-[34]|ec-3|alac|mp3|speex|aac)/,
  15. text: /^(stpp.ttml.im1t)/,
  16. // mux.js support regex
  17. muxerVideo: /^(avc0?1)/,
  18. muxerAudio: /^(mp4a)/,
  19. // match nothing as muxer does not support text right now.
  20. // there cannot never be a character before the start of a string
  21. // so this matches nothing.
  22. muxerText: /a^/
  23. };
  24. const mediaTypes = ['video', 'audio', 'text'];
  25. const upperMediaTypes = ['Video', 'Audio', 'Text'];
  26. /**
  27. * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard
  28. * `avc1.<hhhhhh>`
  29. *
  30. * @param {string} codec
  31. * Codec string to translate
  32. * @return {string}
  33. * The translated codec string
  34. */
  35. const translateLegacyCodec = function (codec) {
  36. if (!codec) {
  37. return codec;
  38. }
  39. return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
  40. const profileHex = ('00' + Number(profile).toString(16)).slice(-2);
  41. const avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
  42. return 'avc1.' + profileHex + '00' + avcLevelHex;
  43. });
  44. };
  45. /**
  46. * Replace the old apple-style `avc1.<dd>.<dd>` codec strings with the standard
  47. * `avc1.<hhhhhh>`
  48. *
  49. * @param {string[]} codecs
  50. * An array of codec strings to translate
  51. * @return {string[]}
  52. * The translated array of codec strings
  53. */
  54. const translateLegacyCodecs = function (codecs) {
  55. return codecs.map(translateLegacyCodec);
  56. };
  57. /**
  58. * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
  59. * standard `avc1.<hhhhhh>`.
  60. *
  61. * @param {string} codecString
  62. * The codec string
  63. * @return {string}
  64. * The codec string with old apple-style codecs replaced
  65. *
  66. * @private
  67. */
  68. const mapLegacyAvcCodecs = function (codecString) {
  69. return codecString.replace(/avc1\.(\d+)\.(\d+)/i, match => {
  70. return translateLegacyCodecs([match])[0];
  71. });
  72. };
  73. /**
  74. * @typedef {Object} ParsedCodecInfo
  75. * @property {number} codecCount
  76. * Number of codecs parsed
  77. * @property {string} [videoCodec]
  78. * Parsed video codec (if found)
  79. * @property {string} [videoObjectTypeIndicator]
  80. * Video object type indicator (if found)
  81. * @property {string|null} audioProfile
  82. * Audio profile
  83. */
  84. /**
  85. * Parses a codec string to retrieve the number of codecs specified, the video codec and
  86. * object type indicator, and the audio profile.
  87. *
  88. * @param {string} [codecString]
  89. * The codec string to parse
  90. * @return {ParsedCodecInfo}
  91. * Parsed codec info
  92. */
  93. const parseCodecs = function (codecString = '') {
  94. const codecs = codecString.split(',');
  95. const result = [];
  96. codecs.forEach(function (codec) {
  97. codec = codec.trim();
  98. let codecType;
  99. mediaTypes.forEach(function (name) {
  100. const match = regexs[name].exec(codec.toLowerCase());
  101. if (!match || match.length <= 1) {
  102. return;
  103. }
  104. codecType = name; // maintain codec case
  105. const type = codec.substring(0, match[1].length);
  106. const details = codec.replace(type, '');
  107. result.push({
  108. type,
  109. details,
  110. mediaType: name
  111. });
  112. });
  113. if (!codecType) {
  114. result.push({
  115. type: codec,
  116. details: '',
  117. mediaType: 'unknown'
  118. });
  119. }
  120. });
  121. return result;
  122. };
  123. /**
  124. * Returns a ParsedCodecInfo object for the default alternate audio playlist if there is
  125. * a default alternate audio playlist for the provided audio group.
  126. *
  127. * @param {Object} master
  128. * The master playlist
  129. * @param {string} audioGroupId
  130. * ID of the audio group for which to find the default codec info
  131. * @return {ParsedCodecInfo}
  132. * Parsed codec info
  133. */
  134. const codecsFromDefault = (master, audioGroupId) => {
  135. if (!master.mediaGroups.AUDIO || !audioGroupId) {
  136. return null;
  137. }
  138. const audioGroup = master.mediaGroups.AUDIO[audioGroupId];
  139. if (!audioGroup) {
  140. return null;
  141. }
  142. for (const name in audioGroup) {
  143. const audioType = audioGroup[name];
  144. if (audioType.default && audioType.playlists) {
  145. // codec should be the same for all playlists within the audio type
  146. return parseCodecs(audioType.playlists[0].attributes.CODECS);
  147. }
  148. }
  149. return null;
  150. };
  151. const isVideoCodec = (codec = '') => regexs.video.test(codec.trim().toLowerCase());
  152. const isAudioCodec = (codec = '') => regexs.audio.test(codec.trim().toLowerCase());
  153. const isTextCodec = (codec = '') => regexs.text.test(codec.trim().toLowerCase());
  154. const getMimeForCodec = codecString => {
  155. if (!codecString || typeof codecString !== 'string') {
  156. return;
  157. }
  158. const codecs = codecString.toLowerCase().split(',').map(c => translateLegacyCodec(c.trim())); // default to video type
  159. let type = 'video'; // only change to audio type if the only codec we have is
  160. // audio
  161. if (codecs.length === 1 && isAudioCodec(codecs[0])) {
  162. type = 'audio';
  163. } else if (codecs.length === 1 && isTextCodec(codecs[0])) {
  164. // text uses application/<container> for now
  165. type = 'application';
  166. } // default the container to mp4
  167. let container = 'mp4'; // every codec must be able to go into the container
  168. // for that container to be the correct one
  169. if (codecs.every(c => regexs.mp4.test(c))) {
  170. container = 'mp4';
  171. } else if (codecs.every(c => regexs.webm.test(c))) {
  172. container = 'webm';
  173. } else if (codecs.every(c => regexs.ogg.test(c))) {
  174. container = 'ogg';
  175. }
  176. return `${type}/${container};codecs="${codecString}"`;
  177. };
  178. const browserSupportsCodec = (codecString = '') => window.MediaSource && window.MediaSource.isTypeSupported && window.MediaSource.isTypeSupported(getMimeForCodec(codecString)) || false;
  179. const muxerSupportsCodec = (codecString = '') => codecString.toLowerCase().split(',').every(codec => {
  180. codec = codec.trim(); // any match is supported.
  181. for (let i = 0; i < upperMediaTypes.length; i++) {
  182. const type = upperMediaTypes[i];
  183. if (regexs[`muxer${type}`].test(codec)) {
  184. return true;
  185. }
  186. }
  187. return false;
  188. });
  189. const DEFAULT_AUDIO_CODEC = 'mp4a.40.2';
  190. const DEFAULT_VIDEO_CODEC = 'avc1.4d400d';
  191. var codecs = /*#__PURE__*/Object.freeze({
  192. __proto__: null,
  193. translateLegacyCodec: translateLegacyCodec,
  194. translateLegacyCodecs: translateLegacyCodecs,
  195. mapLegacyAvcCodecs: mapLegacyAvcCodecs,
  196. parseCodecs: parseCodecs,
  197. codecsFromDefault: codecsFromDefault,
  198. isVideoCodec: isVideoCodec,
  199. isAudioCodec: isAudioCodec,
  200. isTextCodec: isTextCodec,
  201. getMimeForCodec: getMimeForCodec,
  202. browserSupportsCodec: browserSupportsCodec,
  203. muxerSupportsCodec: muxerSupportsCodec,
  204. DEFAULT_AUDIO_CODEC: DEFAULT_AUDIO_CODEC,
  205. DEFAULT_VIDEO_CODEC: DEFAULT_VIDEO_CODEC
  206. });
  207. // const log2 = Math.log2 ? Math.log2 : (x) => (Math.log(x) / Math.log(2));
  208. const repeat = function (str, len) {
  209. let acc = '';
  210. while (len--) {
  211. acc += str;
  212. }
  213. return acc;
  214. }; // count the number of bits it would take to represent a number
  215. // we used to do this with log2 but BigInt does not support builtin math
  216. // Math.ceil(log2(x));
  217. const countBits = x => x.toString(2).length; // count the number of whole bytes it would take to represent a number
  218. const countBytes = x => Math.ceil(countBits(x) / 8);
  219. const padStart = (b, len, str = ' ') => (repeat(str, len) + b.toString()).slice(-len);
  220. const isArrayBufferView = obj => {
  221. if (ArrayBuffer.isView === 'function') {
  222. return ArrayBuffer.isView(obj);
  223. }
  224. return obj && obj.buffer instanceof ArrayBuffer;
  225. };
  226. const isTypedArray = obj => isArrayBufferView(obj);
  227. const toUint8 = function (bytes) {
  228. if (bytes instanceof Uint8Array) {
  229. return bytes;
  230. }
  231. if (!Array.isArray(bytes) && !isTypedArray(bytes) && !(bytes instanceof ArrayBuffer)) {
  232. // any non-number or NaN leads to empty uint8array
  233. // eslint-disable-next-line
  234. if (typeof bytes !== 'number' || typeof bytes === 'number' && bytes !== bytes) {
  235. bytes = 0;
  236. } else {
  237. bytes = [bytes];
  238. }
  239. }
  240. return new Uint8Array(bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0);
  241. };
  242. const toHexString = function (bytes) {
  243. bytes = toUint8(bytes);
  244. let str = '';
  245. for (let i = 0; i < bytes.length; i++) {
  246. str += padStart(bytes[i].toString(16), 2, '0');
  247. }
  248. return str;
  249. };
  250. const toBinaryString = function (bytes) {
  251. bytes = toUint8(bytes);
  252. let str = '';
  253. for (let i = 0; i < bytes.length; i++) {
  254. str += padStart(bytes[i].toString(2), 8, '0');
  255. }
  256. return str;
  257. };
  258. const BigInt = window.BigInt || Number;
  259. const BYTE_TABLE = [BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')];
  260. const ENDIANNESS = function () {
  261. const a = new Uint16Array([0xFFCC]);
  262. const b = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
  263. if (b[0] === 0xFF) {
  264. return 'big';
  265. }
  266. if (b[0] === 0xCC) {
  267. return 'little';
  268. }
  269. return 'unknown';
  270. }();
  271. const IS_BIG_ENDIAN = ENDIANNESS === 'big';
  272. const IS_LITTLE_ENDIAN = ENDIANNESS === 'little';
  273. const bytesToNumber = function (bytes, {
  274. signed = false,
  275. le = false
  276. } = {}) {
  277. bytes = toUint8(bytes);
  278. const fn = le ? 'reduce' : 'reduceRight';
  279. const obj = bytes[fn] ? bytes[fn] : Array.prototype[fn];
  280. let number = obj.call(bytes, function (total, byte, i) {
  281. const exponent = le ? i : Math.abs(i + 1 - bytes.length);
  282. return total + BigInt(byte) * BYTE_TABLE[exponent];
  283. }, BigInt(0));
  284. if (signed) {
  285. const max = BYTE_TABLE[bytes.length] / BigInt(2) - BigInt(1);
  286. number = BigInt(number);
  287. if (number > max) {
  288. number -= max;
  289. number -= max;
  290. number -= BigInt(2);
  291. }
  292. }
  293. return Number(number);
  294. };
  295. const numberToBytes = function (number, {
  296. le = false
  297. } = {}) {
  298. // eslint-disable-next-line
  299. if (typeof number !== 'bigint' && typeof number !== 'number' || typeof number === 'number' && number !== number) {
  300. number = 0;
  301. }
  302. number = BigInt(number);
  303. const byteCount = countBytes(number);
  304. const bytes = new Uint8Array(new ArrayBuffer(byteCount));
  305. for (let i = 0; i < byteCount; i++) {
  306. const byteIndex = le ? i : Math.abs(i + 1 - bytes.length);
  307. bytes[byteIndex] = Number(number / BYTE_TABLE[i] & BigInt(0xFF));
  308. if (number < 0) {
  309. bytes[byteIndex] = Math.abs(~bytes[byteIndex]);
  310. bytes[byteIndex] -= i === 0 ? 1 : 2;
  311. }
  312. }
  313. return bytes;
  314. };
  315. const bytesToString = bytes => {
  316. if (!bytes) {
  317. return '';
  318. } // TODO: should toUint8 handle cases where we only have 8 bytes
  319. // but report more since this is a Uint16+ Array?
  320. bytes = Array.prototype.slice.call(bytes);
  321. const string = String.fromCharCode.apply(null, toUint8(bytes));
  322. try {
  323. return decodeURIComponent(escape(string));
  324. } catch (e) {// if decodeURIComponent/escape fails, we are dealing with partial
  325. // or full non string data. Just return the potentially garbled string.
  326. }
  327. return string;
  328. };
  329. const stringToBytes = (string, stringIsBytes) => {
  330. if (typeof string !== 'string' && string && typeof string.toString === 'function') {
  331. string = string.toString();
  332. }
  333. if (typeof string !== 'string') {
  334. return new Uint8Array();
  335. } // If the string already is bytes, we don't have to do this
  336. // otherwise we do this so that we split multi length characters
  337. // into individual bytes
  338. if (!stringIsBytes) {
  339. string = unescape(encodeURIComponent(string));
  340. }
  341. const view = new Uint8Array(string.length);
  342. for (let i = 0; i < string.length; i++) {
  343. view[i] = string.charCodeAt(i);
  344. }
  345. return view;
  346. };
  347. const concatTypedArrays = (...buffers) => {
  348. buffers = buffers.filter(b => b && (b.byteLength || b.length) && typeof b !== 'string');
  349. if (buffers.length <= 1) {
  350. // for 0 length we will return empty uint8
  351. // for 1 length we return the first uint8
  352. return toUint8(buffers[0]);
  353. }
  354. const totalLen = buffers.reduce((total, buf, i) => total + (buf.byteLength || buf.length), 0);
  355. const tempBuffer = new Uint8Array(totalLen);
  356. let offset = 0;
  357. buffers.forEach(function (buf) {
  358. buf = toUint8(buf);
  359. tempBuffer.set(buf, offset);
  360. offset += buf.byteLength;
  361. });
  362. return tempBuffer;
  363. };
  364. /**
  365. * Check if the bytes "b" are contained within bytes "a".
  366. *
  367. * @param {Uint8Array|Array} a
  368. * Bytes to check in
  369. *
  370. * @param {Uint8Array|Array} b
  371. * Bytes to check for
  372. *
  373. * @param {Object} options
  374. * options
  375. *
  376. * @param {Array|Uint8Array} [offset=0]
  377. * offset to use when looking at bytes in a
  378. *
  379. * @param {Array|Uint8Array} [mask=[]]
  380. * mask to use on bytes before comparison.
  381. *
  382. * @return {boolean}
  383. * If all bytes in b are inside of a, taking into account
  384. * bit masks.
  385. */
  386. const bytesMatch = (a, b, {
  387. offset = 0,
  388. mask = []
  389. } = {}) => {
  390. a = toUint8(a);
  391. b = toUint8(b); // ie 11 does not support uint8 every
  392. const fn = b.every ? b.every : Array.prototype.every;
  393. return b.length && a.length - offset >= b.length && // ie 11 doesn't support every on uin8
  394. fn.call(b, (bByte, i) => {
  395. const aByte = mask[i] ? mask[i] & a[offset + i] : a[offset + i];
  396. return bByte === aByte;
  397. });
  398. };
  399. const sliceBytes = function (src, start, end) {
  400. if (Uint8Array.prototype.slice) {
  401. return Uint8Array.prototype.slice.call(src, start, end);
  402. }
  403. return new Uint8Array(Array.prototype.slice.call(src, start, end));
  404. };
  405. const reverseBytes = function (src) {
  406. if (src.reverse) {
  407. return src.reverse();
  408. }
  409. return Array.prototype.reverse.call(src);
  410. };
  411. var byteHelpers = /*#__PURE__*/Object.freeze({
  412. __proto__: null,
  413. countBits: countBits,
  414. countBytes: countBytes,
  415. padStart: padStart,
  416. isArrayBufferView: isArrayBufferView,
  417. isTypedArray: isTypedArray,
  418. toUint8: toUint8,
  419. toHexString: toHexString,
  420. toBinaryString: toBinaryString,
  421. ENDIANNESS: ENDIANNESS,
  422. IS_BIG_ENDIAN: IS_BIG_ENDIAN,
  423. IS_LITTLE_ENDIAN: IS_LITTLE_ENDIAN,
  424. bytesToNumber: bytesToNumber,
  425. numberToBytes: numberToBytes,
  426. bytesToString: bytesToString,
  427. stringToBytes: stringToBytes,
  428. concatTypedArrays: concatTypedArrays,
  429. bytesMatch: bytesMatch,
  430. sliceBytes: sliceBytes,
  431. reverseBytes: reverseBytes
  432. });
  433. const normalizePath$1 = function (path) {
  434. if (typeof path === 'string') {
  435. return stringToBytes(path);
  436. }
  437. if (typeof path === 'number') {
  438. return path;
  439. }
  440. return path;
  441. };
  442. const normalizePaths$1 = function (paths) {
  443. if (!Array.isArray(paths)) {
  444. return [normalizePath$1(paths)];
  445. }
  446. return paths.map(p => normalizePath$1(p));
  447. };
  448. /**
  449. * find any number of boxes by name given a path to it in an iso bmff
  450. * such as mp4.
  451. *
  452. * @param {TypedArray} bytes
  453. * bytes for the iso bmff to search for boxes in
  454. *
  455. * @param {Uint8Array[]|string[]|string|Uint8Array} name
  456. * An array of paths or a single path representing the name
  457. * of boxes to search through in bytes. Paths may be
  458. * uint8 (character codes) or strings.
  459. *
  460. * @param {boolean} [complete=false]
  461. * Should we search only for complete boxes on the final path.
  462. * This is very useful when you do not want to get back partial boxes
  463. * in the case of streaming files.
  464. *
  465. * @return {Uint8Array[]}
  466. * An array of the end paths that we found.
  467. */
  468. const findBox = function (bytes, paths, complete = false) {
  469. paths = normalizePaths$1(paths);
  470. bytes = toUint8(bytes);
  471. const results = [];
  472. if (!paths.length) {
  473. // short-circuit the search for empty paths
  474. return results;
  475. }
  476. let i = 0;
  477. while (i < bytes.length) {
  478. const size = (bytes[i] << 24 | bytes[i + 1] << 16 | bytes[i + 2] << 8 | bytes[i + 3]) >>> 0;
  479. const type = bytes.subarray(i + 4, i + 8); // invalid box format.
  480. if (size === 0) {
  481. break;
  482. }
  483. let end = i + size;
  484. if (end > bytes.length) {
  485. // this box is bigger than the number of bytes we have
  486. // and complete is set, we cannot find any more boxes.
  487. if (complete) {
  488. break;
  489. }
  490. end = bytes.length;
  491. }
  492. const data = bytes.subarray(i + 8, end);
  493. if (bytesMatch(type, paths[0])) {
  494. if (paths.length === 1) {
  495. // this is the end of the path and we've found the box we were
  496. // looking for
  497. results.push(data);
  498. } else {
  499. // recursively search for the next box along the path
  500. results.push.apply(results, findBox(data, paths.slice(1), complete));
  501. }
  502. }
  503. i = end;
  504. } // we've finished searching all of bytes
  505. return results;
  506. };
  507. // https://matroska-org.github.io/libebml/specs.html
  508. // https://www.matroska.org/technical/elements.html
  509. // https://www.webmproject.org/docs/container/
  510. const EBML_TAGS = {
  511. EBML: toUint8([0x1A, 0x45, 0xDF, 0xA3]),
  512. DocType: toUint8([0x42, 0x82]),
  513. Segment: toUint8([0x18, 0x53, 0x80, 0x67]),
  514. SegmentInfo: toUint8([0x15, 0x49, 0xA9, 0x66]),
  515. Tracks: toUint8([0x16, 0x54, 0xAE, 0x6B]),
  516. Track: toUint8([0xAE]),
  517. TrackNumber: toUint8([0xd7]),
  518. DefaultDuration: toUint8([0x23, 0xe3, 0x83]),
  519. TrackEntry: toUint8([0xAE]),
  520. TrackType: toUint8([0x83]),
  521. FlagDefault: toUint8([0x88]),
  522. CodecID: toUint8([0x86]),
  523. CodecPrivate: toUint8([0x63, 0xA2]),
  524. VideoTrack: toUint8([0xe0]),
  525. AudioTrack: toUint8([0xe1]),
  526. // Not used yet, but will be used for live webm/mkv
  527. // see https://www.matroska.org/technical/basics.html#block-structure
  528. // see https://www.matroska.org/technical/basics.html#simpleblock-structure
  529. Cluster: toUint8([0x1F, 0x43, 0xB6, 0x75]),
  530. Timestamp: toUint8([0xE7]),
  531. TimestampScale: toUint8([0x2A, 0xD7, 0xB1]),
  532. BlockGroup: toUint8([0xA0]),
  533. BlockDuration: toUint8([0x9B]),
  534. Block: toUint8([0xA1]),
  535. SimpleBlock: toUint8([0xA3])
  536. };
  537. /**
  538. * This is a simple table to determine the length
  539. * of things in ebml. The length is one based (starts at 1,
  540. * rather than zero) and for every zero bit before a one bit
  541. * we add one to length. We also need this table because in some
  542. * case we have to xor all the length bits from another value.
  543. */
  544. const LENGTH_TABLE = [0b10000000, 0b01000000, 0b00100000, 0b00010000, 0b00001000, 0b00000100, 0b00000010, 0b00000001];
  545. const getLength = function (byte) {
  546. let len = 1;
  547. for (let i = 0; i < LENGTH_TABLE.length; i++) {
  548. if (byte & LENGTH_TABLE[i]) {
  549. break;
  550. }
  551. len++;
  552. }
  553. return len;
  554. }; // length in ebml is stored in the first 4 to 8 bits
  555. // of the first byte. 4 for the id length and 8 for the
  556. // data size length. Length is measured by converting the number to binary
  557. // then 1 + the number of zeros before a 1 is encountered starting
  558. // from the left.
  559. const getvint = function (bytes, offset, removeLength = true, signed = false) {
  560. const length = getLength(bytes[offset]);
  561. let valueBytes = bytes.subarray(offset, offset + length); // NOTE that we do **not** subarray here because we need to copy these bytes
  562. // as they will be modified below to remove the dataSizeLen bits and we do not
  563. // want to modify the original data. normally we could just call slice on
  564. // uint8array but ie 11 does not support that...
  565. if (removeLength) {
  566. valueBytes = Array.prototype.slice.call(bytes, offset, offset + length);
  567. valueBytes[0] ^= LENGTH_TABLE[length - 1];
  568. }
  569. return {
  570. length,
  571. value: bytesToNumber(valueBytes, {
  572. signed
  573. }),
  574. bytes: valueBytes
  575. };
  576. };
  577. const normalizePath = function (path) {
  578. if (typeof path === 'string') {
  579. return path.match(/.{1,2}/g).map(p => normalizePath(p));
  580. }
  581. if (typeof path === 'number') {
  582. return numberToBytes(path);
  583. }
  584. return path;
  585. };
  586. const normalizePaths = function (paths) {
  587. if (!Array.isArray(paths)) {
  588. return [normalizePath(paths)];
  589. }
  590. return paths.map(p => normalizePath(p));
  591. };
  592. const getInfinityDataSize = (id, bytes, offset) => {
  593. if (offset >= bytes.length) {
  594. return bytes.length;
  595. }
  596. const innerid = getvint(bytes, offset, false);
  597. if (bytesMatch(id.bytes, innerid.bytes)) {
  598. return offset;
  599. }
  600. const dataHeader = getvint(bytes, offset + innerid.length);
  601. return getInfinityDataSize(id, bytes, offset + dataHeader.length + dataHeader.value + innerid.length);
  602. };
  603. /**
  604. * Notes on the EBLM format.
  605. *
  606. * EBLM uses "vints" tags. Every vint tag contains
  607. * two parts
  608. *
  609. * 1. The length from the first byte. You get this by
  610. * converting the byte to binary and counting the zeros
  611. * before a 1. Then you add 1 to that. Examples
  612. * 00011111 = length 4 because there are 3 zeros before a 1.
  613. * 00100000 = length 3 because there are 2 zeros before a 1.
  614. * 00000011 = length 7 because there are 6 zeros before a 1.
  615. *
  616. * 2. The bits used for length are removed from the first byte
  617. * Then all the bytes are merged into a value. NOTE: this
  618. * is not the case for id ebml tags as there id includes
  619. * length bits.
  620. *
  621. */
  622. const findEbml = function (bytes, paths) {
  623. paths = normalizePaths(paths);
  624. bytes = toUint8(bytes);
  625. let results = [];
  626. if (!paths.length) {
  627. return results;
  628. }
  629. let i = 0;
  630. while (i < bytes.length) {
  631. const id = getvint(bytes, i, false);
  632. const dataHeader = getvint(bytes, i + id.length);
  633. const dataStart = i + id.length + dataHeader.length; // dataSize is unknown or this is a live stream
  634. if (dataHeader.value === 0x7f) {
  635. dataHeader.value = getInfinityDataSize(id, bytes, dataStart);
  636. if (dataHeader.value !== bytes.length) {
  637. dataHeader.value -= dataStart;
  638. }
  639. }
  640. const dataEnd = dataStart + dataHeader.value > bytes.length ? bytes.length : dataStart + dataHeader.value;
  641. const data = bytes.subarray(dataStart, dataEnd);
  642. if (bytesMatch(paths[0], id.bytes)) {
  643. if (paths.length === 1) {
  644. // this is the end of the paths and we've found the tag we were
  645. // looking for
  646. results.push(data);
  647. } else {
  648. // recursively search for the next tag inside of the data
  649. // of this one
  650. results = results.concat(findEbml(data, paths.slice(1)));
  651. }
  652. }
  653. const totalLength = id.length + dataHeader.length + data.length; // move past this tag entirely, we are not looking for it
  654. i += totalLength;
  655. }
  656. return results;
  657. }; // see https://www.matroska.org/technical/basics.html#block-structure
  658. const ID3 = toUint8([0x49, 0x44, 0x33]);
  659. const getId3Size = function (bytes, offset = 0) {
  660. bytes = toUint8(bytes);
  661. const flags = bytes[offset + 5];
  662. const returnSize = bytes[offset + 6] << 21 | bytes[offset + 7] << 14 | bytes[offset + 8] << 7 | bytes[offset + 9];
  663. const footerPresent = (flags & 16) >> 4;
  664. if (footerPresent) {
  665. return returnSize + 20;
  666. }
  667. return returnSize + 10;
  668. };
  669. const getId3Offset = function (bytes, offset = 0) {
  670. bytes = toUint8(bytes);
  671. if (bytes.length - offset < 10 || !bytesMatch(bytes, ID3, {
  672. offset
  673. })) {
  674. return offset;
  675. }
  676. offset += getId3Size(bytes, offset); // recursive check for id3 tags as some files
  677. // have multiple ID3 tag sections even though
  678. // they should not.
  679. return getId3Offset(bytes, offset);
  680. };
  681. const NAL_TYPE_ONE = toUint8([0x00, 0x00, 0x00, 0x01]);
  682. const NAL_TYPE_TWO = toUint8([0x00, 0x00, 0x01]);
  683. const EMULATION_PREVENTION = toUint8([0x00, 0x00, 0x03]);
  684. /**
  685. * Expunge any "Emulation Prevention" bytes from a "Raw Byte
  686. * Sequence Payload"
  687. *
  688. * @param data {Uint8Array} the bytes of a RBSP from a NAL
  689. * unit
  690. * @return {Uint8Array} the RBSP without any Emulation
  691. * Prevention Bytes
  692. */
  693. const discardEmulationPreventionBytes = function (bytes) {
  694. const positions = [];
  695. let i = 1; // Find all `Emulation Prevention Bytes`
  696. while (i < bytes.length - 2) {
  697. if (bytesMatch(bytes.subarray(i, i + 3), EMULATION_PREVENTION)) {
  698. positions.push(i + 2);
  699. i++;
  700. }
  701. i++;
  702. } // If no Emulation Prevention Bytes were found just return the original
  703. // array
  704. if (positions.length === 0) {
  705. return bytes;
  706. } // Create a new array to hold the NAL unit data
  707. const newLength = bytes.length - positions.length;
  708. const newData = new Uint8Array(newLength);
  709. let sourceIndex = 0;
  710. for (i = 0; i < newLength; sourceIndex++, i++) {
  711. if (sourceIndex === positions[0]) {
  712. // Skip this byte
  713. sourceIndex++; // Remove this position index
  714. positions.shift();
  715. }
  716. newData[i] = bytes[sourceIndex];
  717. }
  718. return newData;
  719. };
  720. const findNal = function (bytes, dataType, types, nalLimit = Infinity) {
  721. bytes = toUint8(bytes);
  722. types = [].concat(types);
  723. let i = 0;
  724. let nalStart;
  725. let nalsFound = 0; // keep searching until:
  726. // we reach the end of bytes
  727. // we reach the maximum number of nals they want to seach
  728. // NOTE: that we disregard nalLimit when we have found the start
  729. // of the nal we want so that we can find the end of the nal we want.
  730. while (i < bytes.length && (nalsFound < nalLimit || nalStart)) {
  731. let nalOffset;
  732. if (bytesMatch(bytes.subarray(i), NAL_TYPE_ONE)) {
  733. nalOffset = 4;
  734. } else if (bytesMatch(bytes.subarray(i), NAL_TYPE_TWO)) {
  735. nalOffset = 3;
  736. } // we are unsynced,
  737. // find the next nal unit
  738. if (!nalOffset) {
  739. i++;
  740. continue;
  741. }
  742. nalsFound++;
  743. if (nalStart) {
  744. return discardEmulationPreventionBytes(bytes.subarray(nalStart, i));
  745. }
  746. let nalType;
  747. if (dataType === 'h264') {
  748. nalType = bytes[i + nalOffset] & 0x1f;
  749. } else if (dataType === 'h265') {
  750. nalType = bytes[i + nalOffset] >> 1 & 0x3f;
  751. }
  752. if (types.indexOf(nalType) !== -1) {
  753. nalStart = i + nalOffset;
  754. } // nal header is 1 length for h264, and 2 for h265
  755. i += nalOffset + (dataType === 'h264' ? 1 : 2);
  756. }
  757. return bytes.subarray(0, 0);
  758. };
  759. const findH264Nal = (bytes, type, nalLimit) => findNal(bytes, 'h264', type, nalLimit);
  760. const findH265Nal = (bytes, type, nalLimit) => findNal(bytes, 'h265', type, nalLimit);
  761. const CONSTANTS = {
  762. // "webm" string literal in hex
  763. 'webm': toUint8([0x77, 0x65, 0x62, 0x6d]),
  764. // "matroska" string literal in hex
  765. 'matroska': toUint8([0x6d, 0x61, 0x74, 0x72, 0x6f, 0x73, 0x6b, 0x61]),
  766. // "fLaC" string literal in hex
  767. 'flac': toUint8([0x66, 0x4c, 0x61, 0x43]),
  768. // "OggS" string literal in hex
  769. 'ogg': toUint8([0x4f, 0x67, 0x67, 0x53]),
  770. // ac-3 sync byte, also works for ec-3 as that is simply a codec
  771. // of ac-3
  772. 'ac3': toUint8([0x0b, 0x77]),
  773. // "RIFF" string literal in hex used for wav and avi
  774. 'riff': toUint8([0x52, 0x49, 0x46, 0x46]),
  775. // "AVI" string literal in hex
  776. 'avi': toUint8([0x41, 0x56, 0x49]),
  777. // "WAVE" string literal in hex
  778. 'wav': toUint8([0x57, 0x41, 0x56, 0x45]),
  779. // "ftyp3g" string literal in hex
  780. '3gp': toUint8([0x66, 0x74, 0x79, 0x70, 0x33, 0x67]),
  781. // "ftyp" string literal in hex
  782. 'mp4': toUint8([0x66, 0x74, 0x79, 0x70]),
  783. // "styp" string literal in hex
  784. 'fmp4': toUint8([0x73, 0x74, 0x79, 0x70]),
  785. // "ftypqt" string literal in hex
  786. 'mov': toUint8([0x66, 0x74, 0x79, 0x70, 0x71, 0x74]),
  787. // moov string literal in hex
  788. 'moov': toUint8([0x6D, 0x6F, 0x6F, 0x76]),
  789. // moof string literal in hex
  790. 'moof': toUint8([0x6D, 0x6F, 0x6F, 0x66])
  791. };
  792. const _isLikely = {
  793. aac(bytes) {
  794. const offset = getId3Offset(bytes);
  795. return bytesMatch(bytes, [0xFF, 0x10], {
  796. offset,
  797. mask: [0xFF, 0x16]
  798. });
  799. },
  800. mp3(bytes) {
  801. const offset = getId3Offset(bytes);
  802. return bytesMatch(bytes, [0xFF, 0x02], {
  803. offset,
  804. mask: [0xFF, 0x06]
  805. });
  806. },
  807. webm(bytes) {
  808. const docType = findEbml(bytes, [EBML_TAGS.EBML, EBML_TAGS.DocType])[0]; // check if DocType EBML tag is webm
  809. return bytesMatch(docType, CONSTANTS.webm);
  810. },
  811. mkv(bytes) {
  812. const docType = findEbml(bytes, [EBML_TAGS.EBML, EBML_TAGS.DocType])[0]; // check if DocType EBML tag is matroska
  813. return bytesMatch(docType, CONSTANTS.matroska);
  814. },
  815. mp4(bytes) {
  816. // if this file is another base media file format, it is not mp4
  817. if (_isLikely['3gp'](bytes) || _isLikely.mov(bytes)) {
  818. return false;
  819. } // if this file starts with a ftyp or styp box its mp4
  820. if (bytesMatch(bytes, CONSTANTS.mp4, {
  821. offset: 4
  822. }) || bytesMatch(bytes, CONSTANTS.fmp4, {
  823. offset: 4
  824. })) {
  825. return true;
  826. } // if this file starts with a moof/moov box its mp4
  827. if (bytesMatch(bytes, CONSTANTS.moof, {
  828. offset: 4
  829. }) || bytesMatch(bytes, CONSTANTS.moov, {
  830. offset: 4
  831. })) {
  832. return true;
  833. }
  834. },
  835. mov(bytes) {
  836. return bytesMatch(bytes, CONSTANTS.mov, {
  837. offset: 4
  838. });
  839. },
  840. '3gp'(bytes) {
  841. return bytesMatch(bytes, CONSTANTS['3gp'], {
  842. offset: 4
  843. });
  844. },
  845. ac3(bytes) {
  846. const offset = getId3Offset(bytes);
  847. return bytesMatch(bytes, CONSTANTS.ac3, {
  848. offset
  849. });
  850. },
  851. ts(bytes) {
  852. if (bytes.length < 189 && bytes.length >= 1) {
  853. return bytes[0] === 0x47;
  854. }
  855. let i = 0; // check the first 376 bytes for two matching sync bytes
  856. while (i + 188 < bytes.length && i < 188) {
  857. if (bytes[i] === 0x47 && bytes[i + 188] === 0x47) {
  858. return true;
  859. }
  860. i += 1;
  861. }
  862. return false;
  863. },
  864. flac(bytes) {
  865. const offset = getId3Offset(bytes);
  866. return bytesMatch(bytes, CONSTANTS.flac, {
  867. offset
  868. });
  869. },
  870. ogg(bytes) {
  871. return bytesMatch(bytes, CONSTANTS.ogg);
  872. },
  873. avi(bytes) {
  874. return bytesMatch(bytes, CONSTANTS.riff) && bytesMatch(bytes, CONSTANTS.avi, {
  875. offset: 8
  876. });
  877. },
  878. wav(bytes) {
  879. return bytesMatch(bytes, CONSTANTS.riff) && bytesMatch(bytes, CONSTANTS.wav, {
  880. offset: 8
  881. });
  882. },
  883. 'h264'(bytes) {
  884. // find seq_parameter_set_rbsp
  885. return findH264Nal(bytes, 7, 3).length;
  886. },
  887. 'h265'(bytes) {
  888. // find video_parameter_set_rbsp or seq_parameter_set_rbsp
  889. return findH265Nal(bytes, [32, 33], 3).length;
  890. }
  891. }; // get all the isLikely functions
  892. // but make sure 'ts' is above h264 and h265
  893. // but below everything else as it is the least specific
  894. const isLikelyTypes = Object.keys(_isLikely) // remove ts, h264, h265
  895. .filter(t => t !== 'ts' && t !== 'h264' && t !== 'h265') // add it back to the bottom
  896. .concat(['ts', 'h264', 'h265']); // make sure we are dealing with uint8 data.
  897. isLikelyTypes.forEach(function (type) {
  898. const isLikelyFn = _isLikely[type];
  899. _isLikely[type] = bytes => isLikelyFn(toUint8(bytes));
  900. }); // export after wrapping
  901. const isLikely = _isLikely; // A useful list of file signatures can be found here
  902. // https://en.wikipedia.org/wiki/List_of_file_signatures
  903. const detectContainerForBytes = bytes => {
  904. bytes = toUint8(bytes);
  905. for (let i = 0; i < isLikelyTypes.length; i++) {
  906. const type = isLikelyTypes[i];
  907. if (isLikely[type](bytes)) {
  908. return type;
  909. }
  910. }
  911. return '';
  912. }; // fmp4 is not a container
  913. const isLikelyFmp4MediaSegment = bytes => {
  914. return findBox(bytes, ['moof']).length > 0;
  915. };
  916. var containers = /*#__PURE__*/Object.freeze({
  917. __proto__: null,
  918. isLikely: isLikely,
  919. detectContainerForBytes: detectContainerForBytes,
  920. isLikelyFmp4MediaSegment: isLikelyFmp4MediaSegment
  921. });
  922. const atob = s => window.atob ? window.atob(s) : Buffer.from(s, 'base64').toString('binary');
  923. function decodeB64ToUint8Array(b64Text) {
  924. const decodedString = atob(b64Text);
  925. const array = new Uint8Array(decodedString.length);
  926. for (let i = 0; i < decodedString.length; i++) {
  927. array[i] = decodedString.charCodeAt(i);
  928. }
  929. return array;
  930. }
  931. /**
  932. * Loops through all supported media groups in master and calls the provided
  933. * callback for each group
  934. *
  935. * @param {Object} master
  936. * The parsed master manifest object
  937. * @param {string[]} groups
  938. * The media groups to call the callback for
  939. * @param {Function} callback
  940. * Callback to call for each media group
  941. */
  942. const forEachMediaGroup = (master, groups, callback) => {
  943. groups.forEach(mediaType => {
  944. for (const groupKey in master.mediaGroups[mediaType]) {
  945. for (const labelKey in master.mediaGroups[mediaType][groupKey]) {
  946. const mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
  947. callback(mediaProperties, mediaType, groupKey, labelKey);
  948. }
  949. }
  950. });
  951. };
  952. var mediaGroups = /*#__PURE__*/Object.freeze({
  953. __proto__: null,
  954. forEachMediaGroup: forEachMediaGroup
  955. });
  956. var urlToolkit = {exports: {}};
  957. (function (module, exports) {
  958. // see https://tools.ietf.org/html/rfc1808
  959. (function (root) {
  960. var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/?#]*\/)*[^;?#]*)?(;[^?#]*)?(\?[^#]*)?(#.*)?$/;
  961. var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/;
  962. var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
  963. var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/)[^\/]*(?=\/)/g;
  964. var URLToolkit = {
  965. // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
  966. // E.g
  967. // With opts.alwaysNormalize = false (default, spec compliant)
  968. // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
  969. // With opts.alwaysNormalize = true (not spec compliant)
  970. // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
  971. buildAbsoluteURL: function (baseURL, relativeURL, opts) {
  972. opts = opts || {}; // remove any remaining space and CRLF
  973. baseURL = baseURL.trim();
  974. relativeURL = relativeURL.trim();
  975. if (!relativeURL) {
  976. // 2a) If the embedded URL is entirely empty, it inherits the
  977. // entire base URL (i.e., is set equal to the base URL)
  978. // and we are done.
  979. if (!opts.alwaysNormalize) {
  980. return baseURL;
  981. }
  982. var basePartsForNormalise = URLToolkit.parseURL(baseURL);
  983. if (!basePartsForNormalise) {
  984. throw new Error('Error trying to parse base URL.');
  985. }
  986. basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
  987. return URLToolkit.buildURLFromParts(basePartsForNormalise);
  988. }
  989. var relativeParts = URLToolkit.parseURL(relativeURL);
  990. if (!relativeParts) {
  991. throw new Error('Error trying to parse relative URL.');
  992. }
  993. if (relativeParts.scheme) {
  994. // 2b) If the embedded URL starts with a scheme name, it is
  995. // interpreted as an absolute URL and we are done.
  996. if (!opts.alwaysNormalize) {
  997. return relativeURL;
  998. }
  999. relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
  1000. return URLToolkit.buildURLFromParts(relativeParts);
  1001. }
  1002. var baseParts = URLToolkit.parseURL(baseURL);
  1003. if (!baseParts) {
  1004. throw new Error('Error trying to parse base URL.');
  1005. }
  1006. if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
  1007. // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
  1008. // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
  1009. var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
  1010. baseParts.netLoc = pathParts[1];
  1011. baseParts.path = pathParts[2];
  1012. }
  1013. if (baseParts.netLoc && !baseParts.path) {
  1014. baseParts.path = '/';
  1015. }
  1016. var builtParts = {
  1017. // 2c) Otherwise, the embedded URL inherits the scheme of
  1018. // the base URL.
  1019. scheme: baseParts.scheme,
  1020. netLoc: relativeParts.netLoc,
  1021. path: null,
  1022. params: relativeParts.params,
  1023. query: relativeParts.query,
  1024. fragment: relativeParts.fragment
  1025. };
  1026. if (!relativeParts.netLoc) {
  1027. // 3) If the embedded URL's <net_loc> is non-empty, we skip to
  1028. // Step 7. Otherwise, the embedded URL inherits the <net_loc>
  1029. // (if any) of the base URL.
  1030. builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the
  1031. // path is not relative and we skip to Step 7.
  1032. if (relativeParts.path[0] !== '/') {
  1033. if (!relativeParts.path) {
  1034. // 5) If the embedded URL path is empty (and not preceded by a
  1035. // slash), then the embedded URL inherits the base URL path
  1036. builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to
  1037. // step 7; otherwise, it inherits the <params> of the base
  1038. // URL (if any) and
  1039. if (!relativeParts.params) {
  1040. builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to
  1041. // step 7; otherwise, it inherits the <query> of the base
  1042. // URL (if any) and we skip to step 7.
  1043. if (!relativeParts.query) {
  1044. builtParts.query = baseParts.query;
  1045. }
  1046. }
  1047. } else {
  1048. // 6) The last segment of the base URL's path (anything
  1049. // following the rightmost slash "/", or the entire path if no
  1050. // slash is present) is removed and the embedded URL's path is
  1051. // appended in its place.
  1052. var baseURLPath = baseParts.path;
  1053. var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
  1054. builtParts.path = URLToolkit.normalizePath(newPath);
  1055. }
  1056. }
  1057. }
  1058. if (builtParts.path === null) {
  1059. builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
  1060. }
  1061. return URLToolkit.buildURLFromParts(builtParts);
  1062. },
  1063. parseURL: function (url) {
  1064. var parts = URL_REGEX.exec(url);
  1065. if (!parts) {
  1066. return null;
  1067. }
  1068. return {
  1069. scheme: parts[1] || '',
  1070. netLoc: parts[2] || '',
  1071. path: parts[3] || '',
  1072. params: parts[4] || '',
  1073. query: parts[5] || '',
  1074. fragment: parts[6] || ''
  1075. };
  1076. },
  1077. normalizePath: function (path) {
  1078. // The following operations are
  1079. // then applied, in order, to the new path:
  1080. // 6a) All occurrences of "./", where "." is a complete path
  1081. // segment, are removed.
  1082. // 6b) If the path ends with "." as a complete path segment,
  1083. // that "." is removed.
  1084. path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a
  1085. // complete path segment not equal to "..", are removed.
  1086. // Removal of these path segments is performed iteratively,
  1087. // removing the leftmost matching pattern on each iteration,
  1088. // until no matching pattern remains.
  1089. // 6d) If the path ends with "<segment>/..", where <segment> is a
  1090. // complete path segment not equal to "..", that
  1091. // "<segment>/.." is removed.
  1092. while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {}
  1093. return path.split('').reverse().join('');
  1094. },
  1095. buildURLFromParts: function (parts) {
  1096. return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
  1097. }
  1098. };
  1099. module.exports = URLToolkit;
  1100. })();
  1101. })(urlToolkit);
  1102. var URLToolkit = urlToolkit.exports;
  1103. const DEFAULT_LOCATION = 'http://example.com';
  1104. const resolveUrl = (baseUrl, relativeUrl) => {
  1105. // return early if we don't need to resolve
  1106. if (/^[a-z]+:/i.test(relativeUrl)) {
  1107. return relativeUrl;
  1108. } // if baseUrl is a data URI, ignore it and resolve everything relative to window.location
  1109. if (/^data:/.test(baseUrl)) {
  1110. baseUrl = window.location && window.location.href || '';
  1111. } // IE11 supports URL but not the URL constructor
  1112. // feature detect the behavior we want
  1113. const nativeURL = typeof window.URL === 'function';
  1114. const protocolLess = /^\/\//.test(baseUrl); // remove location if window.location isn't available (i.e. we're in node)
  1115. // and if baseUrl isn't an absolute url
  1116. const removeLocation = !window.location && !/\/\//i.test(baseUrl); // if the base URL is relative then combine with the current location
  1117. if (nativeURL) {
  1118. baseUrl = new window.URL(baseUrl, window.location || DEFAULT_LOCATION);
  1119. } else if (!/\/\//i.test(baseUrl)) {
  1120. baseUrl = URLToolkit.buildAbsoluteURL(window.location && window.location.href || '', baseUrl);
  1121. }
  1122. if (nativeURL) {
  1123. const newUrl = new URL(relativeUrl, baseUrl); // if we're a protocol-less url, remove the protocol
  1124. // and if we're location-less, remove the location
  1125. // otherwise, return the url unmodified
  1126. if (removeLocation) {
  1127. return newUrl.href.slice(DEFAULT_LOCATION.length);
  1128. } else if (protocolLess) {
  1129. return newUrl.href.slice(newUrl.protocol.length);
  1130. }
  1131. return newUrl.href;
  1132. }
  1133. return URLToolkit.buildAbsoluteURL(baseUrl, relativeUrl);
  1134. };
  1135. /**
  1136. * @file stream.js
  1137. */
  1138. /**
  1139. * A lightweight readable stream implemention that handles event dispatching.
  1140. *
  1141. * @class Stream
  1142. */
  1143. class Stream {
  1144. constructor() {
  1145. this.listeners = {};
  1146. }
  1147. /**
  1148. * Add a listener for a specified event type.
  1149. *
  1150. * @param {string} type the event name
  1151. * @param {Function} listener the callback to be invoked when an event of
  1152. * the specified type occurs
  1153. */
  1154. on(type, listener) {
  1155. if (!this.listeners[type]) {
  1156. this.listeners[type] = [];
  1157. }
  1158. this.listeners[type].push(listener);
  1159. }
  1160. /**
  1161. * Remove a listener for a specified event type.
  1162. *
  1163. * @param {string} type the event name
  1164. * @param {Function} listener a function previously registered for this
  1165. * type of event through `on`
  1166. * @return {boolean} if we could turn it off or not
  1167. */
  1168. off(type, listener) {
  1169. if (!this.listeners[type]) {
  1170. return false;
  1171. }
  1172. const index = this.listeners[type].indexOf(listener); // TODO: which is better?
  1173. // In Video.js we slice listener functions
  1174. // on trigger so that it does not mess up the order
  1175. // while we loop through.
  1176. //
  1177. // Here we slice on off so that the loop in trigger
  1178. // can continue using it's old reference to loop without
  1179. // messing up the order.
  1180. this.listeners[type] = this.listeners[type].slice(0);
  1181. this.listeners[type].splice(index, 1);
  1182. return index > -1;
  1183. }
  1184. /**
  1185. * Trigger an event of the specified type on this stream. Any additional
  1186. * arguments to this function are passed as parameters to event listeners.
  1187. *
  1188. * @param {string} type the event name
  1189. */
  1190. trigger(type) {
  1191. const callbacks = this.listeners[type];
  1192. if (!callbacks) {
  1193. return;
  1194. } // Slicing the arguments on every invocation of this method
  1195. // can add a significant amount of overhead. Avoid the
  1196. // intermediate object creation for the common case of a
  1197. // single callback argument
  1198. if (arguments.length === 2) {
  1199. const length = callbacks.length;
  1200. for (let i = 0; i < length; ++i) {
  1201. callbacks[i].call(this, arguments[1]);
  1202. }
  1203. } else {
  1204. const args = Array.prototype.slice.call(arguments, 1);
  1205. const length = callbacks.length;
  1206. for (let i = 0; i < length; ++i) {
  1207. callbacks[i].apply(this, args);
  1208. }
  1209. }
  1210. }
  1211. /**
  1212. * Destroys the stream and cleans up.
  1213. */
  1214. dispose() {
  1215. this.listeners = {};
  1216. }
  1217. /**
  1218. * Forwards all `data` events on this stream to the destination stream. The
  1219. * destination stream should provide a method `push` to receive the data
  1220. * events as they arrive.
  1221. *
  1222. * @param {Stream} destination the stream that will receive all `data` events
  1223. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  1224. */
  1225. pipe(destination) {
  1226. this.on('data', function (data) {
  1227. destination.push(data);
  1228. });
  1229. }
  1230. }
  1231. var index = {
  1232. codecs,
  1233. byteHelpers,
  1234. containers,
  1235. decodeB64ToUint8Array,
  1236. mediaGroups,
  1237. resolveUrl,
  1238. Stream
  1239. };
  1240. return index;
  1241. }));