m3u8-parser.js 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084
  1. /**
  2. * m3u8-parser
  3. * @version 2.1.0
  4. * @copyright 2017 Brightcove, Inc
  5. * @license Apache-2.0
  6. */
  7. (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.m3u8Parser = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
  8. 'use strict';
  9. Object.defineProperty(exports, "__esModule", {
  10. value: true
  11. });
  12. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  13. var _stream = require('./stream');
  14. var _stream2 = _interopRequireDefault(_stream);
  15. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  16. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  17. function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
  18. function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
  19. * @file m3u8/line-stream.js
  20. */
  21. /**
  22. * A stream that buffers string input and generates a `data` event for each
  23. * line.
  24. *
  25. * @class LineStream
  26. * @extends Stream
  27. */
  28. var LineStream = function (_Stream) {
  29. _inherits(LineStream, _Stream);
  30. function LineStream() {
  31. _classCallCheck(this, LineStream);
  32. var _this = _possibleConstructorReturn(this, (LineStream.__proto__ || Object.getPrototypeOf(LineStream)).call(this));
  33. _this.buffer = '';
  34. return _this;
  35. }
  36. /**
  37. * Add new data to be parsed.
  38. *
  39. * @param {String} data the text to process
  40. */
  41. _createClass(LineStream, [{
  42. key: 'push',
  43. value: function push(data) {
  44. var nextNewline = void 0;
  45. this.buffer += data;
  46. nextNewline = this.buffer.indexOf('\n');
  47. for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
  48. this.trigger('data', this.buffer.substring(0, nextNewline));
  49. this.buffer = this.buffer.substring(nextNewline + 1);
  50. }
  51. }
  52. }]);
  53. return LineStream;
  54. }(_stream2['default']);
  55. exports['default'] = LineStream;
  56. },{"./stream":4}],2:[function(require,module,exports){
  57. 'use strict';
  58. Object.defineProperty(exports, "__esModule", {
  59. value: true
  60. });
  61. var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
  62. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  63. var _stream = require('./stream');
  64. var _stream2 = _interopRequireDefault(_stream);
  65. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  66. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  67. function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
  68. function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
  69. * @file m3u8/parse-stream.js
  70. */
  71. /**
  72. * "forgiving" attribute list psuedo-grammar:
  73. * attributes -> keyvalue (',' keyvalue)*
  74. * keyvalue -> key '=' value
  75. * key -> [^=]*
  76. * value -> '"' [^"]* '"' | [^,]*
  77. */
  78. var attributeSeparator = function attributeSeparator() {
  79. var key = '[^=]*';
  80. var value = '"[^"]*"|[^,]*';
  81. var keyvalue = '(?:' + key + ')=(?:' + value + ')';
  82. return new RegExp('(?:^|,)(' + keyvalue + ')');
  83. };
  84. /**
  85. * Parse attributes from a line given the seperator
  86. *
  87. * @param {String} attributes the attibute line to parse
  88. */
  89. var parseAttributes = function parseAttributes(attributes) {
  90. // split the string using attributes as the separator
  91. var attrs = attributes.split(attributeSeparator());
  92. var result = {};
  93. var i = attrs.length;
  94. var attr = void 0;
  95. while (i--) {
  96. // filter out unmatched portions of the string
  97. if (attrs[i] === '') {
  98. continue;
  99. }
  100. // split the key and value
  101. attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1);
  102. // trim whitespace and remove optional quotes around the value
  103. attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
  104. attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
  105. attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
  106. result[attr[0]] = attr[1];
  107. }
  108. return result;
  109. };
  110. /**
  111. * A line-level M3U8 parser event stream. It expects to receive input one
  112. * line at a time and performs a context-free parse of its contents. A stream
  113. * interpretation of a manifest can be useful if the manifest is expected to
  114. * be too large to fit comfortably into memory or the entirety of the input
  115. * is not immediately available. Otherwise, it's probably much easier to work
  116. * with a regular `Parser` object.
  117. *
  118. * Produces `data` events with an object that captures the parser's
  119. * interpretation of the input. That object has a property `tag` that is one
  120. * of `uri`, `comment`, or `tag`. URIs only have a single additional
  121. * property, `line`, which captures the entirety of the input without
  122. * interpretation. Comments similarly have a single additional property
  123. * `text` which is the input without the leading `#`.
  124. *
  125. * Tags always have a property `tagType` which is the lower-cased version of
  126. * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
  127. * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
  128. * tags are given the tag type `unknown` and a single additional property
  129. * `data` with the remainder of the input.
  130. *
  131. * @class ParseStream
  132. * @extends Stream
  133. */
  134. var ParseStream = function (_Stream) {
  135. _inherits(ParseStream, _Stream);
  136. function ParseStream() {
  137. _classCallCheck(this, ParseStream);
  138. return _possibleConstructorReturn(this, (ParseStream.__proto__ || Object.getPrototypeOf(ParseStream)).call(this));
  139. }
  140. /**
  141. * Parses an additional line of input.
  142. *
  143. * @param {String} line a single line of an M3U8 file to parse
  144. */
  145. _createClass(ParseStream, [{
  146. key: 'push',
  147. value: function push(line) {
  148. var match = void 0;
  149. var event = void 0;
  150. // strip whitespace
  151. line = line.replace(/^[\u0000\s]+|[\u0000\s]+$/g, '');
  152. if (line.length === 0) {
  153. // ignore empty lines
  154. return;
  155. }
  156. // URIs
  157. if (line[0] !== '#') {
  158. this.trigger('data', {
  159. type: 'uri',
  160. uri: line
  161. });
  162. return;
  163. }
  164. // Comments
  165. if (line.indexOf('#EXT') !== 0) {
  166. this.trigger('data', {
  167. type: 'comment',
  168. text: line.slice(1)
  169. });
  170. return;
  171. }
  172. // strip off any carriage returns here so the regex matching
  173. // doesn't have to account for them.
  174. line = line.replace('\r', '');
  175. // Tags
  176. match = /^#EXTM3U/.exec(line);
  177. if (match) {
  178. this.trigger('data', {
  179. type: 'tag',
  180. tagType: 'm3u'
  181. });
  182. return;
  183. }
  184. match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(line);
  185. if (match) {
  186. event = {
  187. type: 'tag',
  188. tagType: 'inf'
  189. };
  190. if (match[1]) {
  191. event.duration = parseFloat(match[1]);
  192. }
  193. if (match[2]) {
  194. event.title = match[2];
  195. }
  196. this.trigger('data', event);
  197. return;
  198. }
  199. match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(line);
  200. if (match) {
  201. event = {
  202. type: 'tag',
  203. tagType: 'targetduration'
  204. };
  205. if (match[1]) {
  206. event.duration = parseInt(match[1], 10);
  207. }
  208. this.trigger('data', event);
  209. return;
  210. }
  211. match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(line);
  212. if (match) {
  213. event = {
  214. type: 'tag',
  215. tagType: 'totalduration'
  216. };
  217. if (match[1]) {
  218. event.duration = parseInt(match[1], 10);
  219. }
  220. this.trigger('data', event);
  221. return;
  222. }
  223. match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(line);
  224. if (match) {
  225. event = {
  226. type: 'tag',
  227. tagType: 'version'
  228. };
  229. if (match[1]) {
  230. event.version = parseInt(match[1], 10);
  231. }
  232. this.trigger('data', event);
  233. return;
  234. }
  235. match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(line);
  236. if (match) {
  237. event = {
  238. type: 'tag',
  239. tagType: 'media-sequence'
  240. };
  241. if (match[1]) {
  242. event.number = parseInt(match[1], 10);
  243. }
  244. this.trigger('data', event);
  245. return;
  246. }
  247. match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(line);
  248. if (match) {
  249. event = {
  250. type: 'tag',
  251. tagType: 'discontinuity-sequence'
  252. };
  253. if (match[1]) {
  254. event.number = parseInt(match[1], 10);
  255. }
  256. this.trigger('data', event);
  257. return;
  258. }
  259. match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(line);
  260. if (match) {
  261. event = {
  262. type: 'tag',
  263. tagType: 'playlist-type'
  264. };
  265. if (match[1]) {
  266. event.playlistType = match[1];
  267. }
  268. this.trigger('data', event);
  269. return;
  270. }
  271. match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(line);
  272. if (match) {
  273. event = {
  274. type: 'tag',
  275. tagType: 'byterange'
  276. };
  277. if (match[1]) {
  278. event.length = parseInt(match[1], 10);
  279. }
  280. if (match[2]) {
  281. event.offset = parseInt(match[2], 10);
  282. }
  283. this.trigger('data', event);
  284. return;
  285. }
  286. match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(line);
  287. if (match) {
  288. event = {
  289. type: 'tag',
  290. tagType: 'allow-cache'
  291. };
  292. if (match[1]) {
  293. event.allowed = !/NO/.test(match[1]);
  294. }
  295. this.trigger('data', event);
  296. return;
  297. }
  298. match = /^#EXT-X-MAP:?(.*)$/.exec(line);
  299. if (match) {
  300. event = {
  301. type: 'tag',
  302. tagType: 'map'
  303. };
  304. if (match[1]) {
  305. var attributes = parseAttributes(match[1]);
  306. if (attributes.URI) {
  307. event.uri = attributes.URI;
  308. }
  309. if (attributes.BYTERANGE) {
  310. var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'),
  311. _attributes$BYTERANGE2 = _slicedToArray(_attributes$BYTERANGE, 2),
  312. length = _attributes$BYTERANGE2[0],
  313. offset = _attributes$BYTERANGE2[1];
  314. event.byterange = {};
  315. if (length) {
  316. event.byterange.length = parseInt(length, 10);
  317. }
  318. if (offset) {
  319. event.byterange.offset = parseInt(offset, 10);
  320. }
  321. }
  322. }
  323. this.trigger('data', event);
  324. return;
  325. }
  326. match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(line);
  327. if (match) {
  328. event = {
  329. type: 'tag',
  330. tagType: 'stream-inf'
  331. };
  332. if (match[1]) {
  333. event.attributes = parseAttributes(match[1]);
  334. if (event.attributes.RESOLUTION) {
  335. var split = event.attributes.RESOLUTION.split('x');
  336. var resolution = {};
  337. if (split[0]) {
  338. resolution.width = parseInt(split[0], 10);
  339. }
  340. if (split[1]) {
  341. resolution.height = parseInt(split[1], 10);
  342. }
  343. event.attributes.RESOLUTION = resolution;
  344. }
  345. if (event.attributes.BANDWIDTH) {
  346. event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
  347. }
  348. if (event.attributes['PROGRAM-ID']) {
  349. event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
  350. }
  351. }
  352. this.trigger('data', event);
  353. return;
  354. }
  355. match = /^#EXT-X-MEDIA:?(.*)$/.exec(line);
  356. if (match) {
  357. event = {
  358. type: 'tag',
  359. tagType: 'media'
  360. };
  361. if (match[1]) {
  362. event.attributes = parseAttributes(match[1]);
  363. }
  364. this.trigger('data', event);
  365. return;
  366. }
  367. match = /^#EXT-X-ENDLIST/.exec(line);
  368. if (match) {
  369. this.trigger('data', {
  370. type: 'tag',
  371. tagType: 'endlist'
  372. });
  373. return;
  374. }
  375. match = /^#EXT-X-DISCONTINUITY/.exec(line);
  376. if (match) {
  377. this.trigger('data', {
  378. type: 'tag',
  379. tagType: 'discontinuity'
  380. });
  381. return;
  382. }
  383. match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(line);
  384. if (match) {
  385. event = {
  386. type: 'tag',
  387. tagType: 'program-date-time'
  388. };
  389. if (match[1]) {
  390. event.dateTimeString = match[1];
  391. event.dateTimeObject = new Date(match[1]);
  392. }
  393. this.trigger('data', event);
  394. return;
  395. }
  396. match = /^#EXT-X-KEY:?(.*)$/.exec(line);
  397. if (match) {
  398. event = {
  399. type: 'tag',
  400. tagType: 'key'
  401. };
  402. if (match[1]) {
  403. event.attributes = parseAttributes(match[1]);
  404. // parse the IV string into a Uint32Array
  405. if (event.attributes.IV) {
  406. if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
  407. event.attributes.IV = event.attributes.IV.substring(2);
  408. }
  409. event.attributes.IV = event.attributes.IV.match(/.{8}/g);
  410. event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
  411. event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
  412. event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
  413. event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
  414. event.attributes.IV = new Uint32Array(event.attributes.IV);
  415. }
  416. }
  417. this.trigger('data', event);
  418. return;
  419. }
  420. match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(line);
  421. if (match) {
  422. event = {
  423. type: 'tag',
  424. tagType: 'cue-out-cont'
  425. };
  426. if (match[1]) {
  427. event.data = match[1];
  428. } else {
  429. event.data = '';
  430. }
  431. this.trigger('data', event);
  432. return;
  433. }
  434. match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(line);
  435. if (match) {
  436. event = {
  437. type: 'tag',
  438. tagType: 'cue-out'
  439. };
  440. if (match[1]) {
  441. event.data = match[1];
  442. } else {
  443. event.data = '';
  444. }
  445. this.trigger('data', event);
  446. return;
  447. }
  448. match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(line);
  449. if (match) {
  450. event = {
  451. type: 'tag',
  452. tagType: 'cue-in'
  453. };
  454. if (match[1]) {
  455. event.data = match[1];
  456. } else {
  457. event.data = '';
  458. }
  459. this.trigger('data', event);
  460. return;
  461. }
  462. // unknown tag type
  463. this.trigger('data', {
  464. type: 'tag',
  465. data: line.slice(4)
  466. });
  467. }
  468. }]);
  469. return ParseStream;
  470. }(_stream2['default']);
  471. exports['default'] = ParseStream;
  472. },{"./stream":4}],3:[function(require,module,exports){
  473. 'use strict';
  474. Object.defineProperty(exports, "__esModule", {
  475. value: true
  476. });
  477. var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
  478. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  479. var _stream = require('./stream');
  480. var _stream2 = _interopRequireDefault(_stream);
  481. var _lineStream = require('./line-stream');
  482. var _lineStream2 = _interopRequireDefault(_lineStream);
  483. var _parseStream = require('./parse-stream');
  484. var _parseStream2 = _interopRequireDefault(_parseStream);
  485. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  486. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  487. function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
  488. function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
  489. * @file m3u8/parser.js
  490. */
  491. /**
  492. * A parser for M3U8 files. The current interpretation of the input is
  493. * exposed as a property `manifest` on parser objects. It's just two lines to
  494. * create and parse a manifest once you have the contents available as a string:
  495. *
  496. * ```js
  497. * var parser = new m3u8.Parser();
  498. * parser.push(xhr.responseText);
  499. * ```
  500. *
  501. * New input can later be applied to update the manifest object by calling
  502. * `push` again.
  503. *
  504. * The parser attempts to create a usable manifest object even if the
  505. * underlying input is somewhat nonsensical. It emits `info` and `warning`
  506. * events during the parse if it encounters input that seems invalid or
  507. * requires some property of the manifest object to be defaulted.
  508. *
  509. * @class Parser
  510. * @extends Stream
  511. */
  512. var Parser = function (_Stream) {
  513. _inherits(Parser, _Stream);
  514. function Parser() {
  515. _classCallCheck(this, Parser);
  516. var _this = _possibleConstructorReturn(this, (Parser.__proto__ || Object.getPrototypeOf(Parser)).call(this));
  517. _this.lineStream = new _lineStream2['default']();
  518. _this.parseStream = new _parseStream2['default']();
  519. _this.lineStream.pipe(_this.parseStream);
  520. /* eslint-disable consistent-this */
  521. var self = _this;
  522. /* eslint-enable consistent-this */
  523. var uris = [];
  524. var currentUri = {};
  525. // if specified, the active EXT-X-MAP definition
  526. var currentMap = void 0;
  527. // if specified, the active decryption key
  528. var _key = void 0;
  529. var noop = function noop() {};
  530. var defaultMediaGroups = {
  531. 'AUDIO': {},
  532. 'VIDEO': {},
  533. 'CLOSED-CAPTIONS': {},
  534. 'SUBTITLES': {}
  535. };
  536. // group segments into numbered timelines delineated by discontinuities
  537. var currentTimeline = 0;
  538. // the manifest is empty until the parse stream begins delivering data
  539. _this.manifest = {
  540. allowCache: true,
  541. discontinuityStarts: [],
  542. segments: []
  543. };
  544. // update the manifest with the m3u8 entry from the parse stream
  545. _this.parseStream.on('data', function (entry) {
  546. var mediaGroup = void 0;
  547. var rendition = void 0;
  548. ({
  549. tag: function tag() {
  550. // switch based on the tag type
  551. (({
  552. 'allow-cache': function allowCache() {
  553. this.manifest.allowCache = entry.allowed;
  554. if (!('allowed' in entry)) {
  555. this.trigger('info', {
  556. message: 'defaulting allowCache to YES'
  557. });
  558. this.manifest.allowCache = true;
  559. }
  560. },
  561. byterange: function byterange() {
  562. var byterange = {};
  563. if ('length' in entry) {
  564. currentUri.byterange = byterange;
  565. byterange.length = entry.length;
  566. if (!('offset' in entry)) {
  567. this.trigger('info', {
  568. message: 'defaulting offset to zero'
  569. });
  570. entry.offset = 0;
  571. }
  572. }
  573. if ('offset' in entry) {
  574. currentUri.byterange = byterange;
  575. byterange.offset = entry.offset;
  576. }
  577. },
  578. endlist: function endlist() {
  579. this.manifest.endList = true;
  580. },
  581. inf: function inf() {
  582. if (!('mediaSequence' in this.manifest)) {
  583. this.manifest.mediaSequence = 0;
  584. this.trigger('info', {
  585. message: 'defaulting media sequence to zero'
  586. });
  587. }
  588. if (!('discontinuitySequence' in this.manifest)) {
  589. this.manifest.discontinuitySequence = 0;
  590. this.trigger('info', {
  591. message: 'defaulting discontinuity sequence to zero'
  592. });
  593. }
  594. if (entry.duration > 0) {
  595. currentUri.duration = entry.duration;
  596. }
  597. if (entry.duration === 0) {
  598. currentUri.duration = 0.01;
  599. this.trigger('info', {
  600. message: 'updating zero segment duration to a small value'
  601. });
  602. }
  603. this.manifest.segments = uris;
  604. },
  605. key: function key() {
  606. if (!entry.attributes) {
  607. this.trigger('warn', {
  608. message: 'ignoring key declaration without attribute list'
  609. });
  610. return;
  611. }
  612. // clear the active encryption key
  613. if (entry.attributes.METHOD === 'NONE') {
  614. _key = null;
  615. return;
  616. }
  617. if (!entry.attributes.URI) {
  618. this.trigger('warn', {
  619. message: 'ignoring key declaration without URI'
  620. });
  621. return;
  622. }
  623. if (!entry.attributes.METHOD) {
  624. this.trigger('warn', {
  625. message: 'defaulting key method to AES-128'
  626. });
  627. }
  628. // setup an encryption key for upcoming segments
  629. _key = {
  630. method: entry.attributes.METHOD || 'AES-128',
  631. uri: entry.attributes.URI
  632. };
  633. if (typeof entry.attributes.IV !== 'undefined') {
  634. _key.iv = entry.attributes.IV;
  635. }
  636. },
  637. 'media-sequence': function mediaSequence() {
  638. if (!isFinite(entry.number)) {
  639. this.trigger('warn', {
  640. message: 'ignoring invalid media sequence: ' + entry.number
  641. });
  642. return;
  643. }
  644. this.manifest.mediaSequence = entry.number;
  645. },
  646. 'discontinuity-sequence': function discontinuitySequence() {
  647. if (!isFinite(entry.number)) {
  648. this.trigger('warn', {
  649. message: 'ignoring invalid discontinuity sequence: ' + entry.number
  650. });
  651. return;
  652. }
  653. this.manifest.discontinuitySequence = entry.number;
  654. currentTimeline = entry.number;
  655. },
  656. 'playlist-type': function playlistType() {
  657. if (!/VOD|EVENT/.test(entry.playlistType)) {
  658. this.trigger('warn', {
  659. message: 'ignoring unknown playlist type: ' + entry.playlist
  660. });
  661. return;
  662. }
  663. this.manifest.playlistType = entry.playlistType;
  664. },
  665. map: function map() {
  666. currentMap = {};
  667. if (entry.uri) {
  668. currentMap.uri = entry.uri;
  669. }
  670. if (entry.byterange) {
  671. currentMap.byterange = entry.byterange;
  672. }
  673. },
  674. 'stream-inf': function streamInf() {
  675. this.manifest.playlists = uris;
  676. this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
  677. if (!entry.attributes) {
  678. this.trigger('warn', {
  679. message: 'ignoring empty stream-inf attributes'
  680. });
  681. return;
  682. }
  683. if (!currentUri.attributes) {
  684. currentUri.attributes = {};
  685. }
  686. _extends(currentUri.attributes, entry.attributes);
  687. },
  688. media: function media() {
  689. this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
  690. if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
  691. this.trigger('warn', {
  692. message: 'ignoring incomplete or missing media group'
  693. });
  694. return;
  695. }
  696. // find the media group, creating defaults as necessary
  697. var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
  698. mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
  699. mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']];
  700. // collect the rendition metadata
  701. rendition = {
  702. 'default': /yes/i.test(entry.attributes.DEFAULT)
  703. };
  704. if (rendition['default']) {
  705. rendition.autoselect = true;
  706. } else {
  707. rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
  708. }
  709. if (entry.attributes.LANGUAGE) {
  710. rendition.language = entry.attributes.LANGUAGE;
  711. }
  712. if (entry.attributes.URI) {
  713. rendition.uri = entry.attributes.URI;
  714. }
  715. if (entry.attributes['INSTREAM-ID']) {
  716. rendition.instreamId = entry.attributes['INSTREAM-ID'];
  717. }
  718. if (entry.attributes.CHARACTERISTICS) {
  719. rendition.characteristics = entry.attributes.CHARACTERISTICS;
  720. }
  721. if (entry.attributes.FORCED) {
  722. rendition.forced = /yes/i.test(entry.attributes.FORCED);
  723. }
  724. // insert the new rendition
  725. mediaGroup[entry.attributes.NAME] = rendition;
  726. },
  727. discontinuity: function discontinuity() {
  728. currentTimeline += 1;
  729. currentUri.discontinuity = true;
  730. this.manifest.discontinuityStarts.push(uris.length);
  731. },
  732. 'program-date-time': function programDateTime() {
  733. this.manifest.dateTimeString = entry.dateTimeString;
  734. this.manifest.dateTimeObject = entry.dateTimeObject;
  735. },
  736. targetduration: function targetduration() {
  737. if (!isFinite(entry.duration) || entry.duration < 0) {
  738. this.trigger('warn', {
  739. message: 'ignoring invalid target duration: ' + entry.duration
  740. });
  741. return;
  742. }
  743. this.manifest.targetDuration = entry.duration;
  744. },
  745. totalduration: function totalduration() {
  746. if (!isFinite(entry.duration) || entry.duration < 0) {
  747. this.trigger('warn', {
  748. message: 'ignoring invalid total duration: ' + entry.duration
  749. });
  750. return;
  751. }
  752. this.manifest.totalDuration = entry.duration;
  753. },
  754. 'cue-out': function cueOut() {
  755. currentUri.cueOut = entry.data;
  756. },
  757. 'cue-out-cont': function cueOutCont() {
  758. currentUri.cueOutCont = entry.data;
  759. },
  760. 'cue-in': function cueIn() {
  761. currentUri.cueIn = entry.data;
  762. }
  763. })[entry.tagType] || noop).call(self);
  764. },
  765. uri: function uri() {
  766. currentUri.uri = entry.uri;
  767. uris.push(currentUri);
  768. // if no explicit duration was declared, use the target duration
  769. if (this.manifest.targetDuration && !('duration' in currentUri)) {
  770. this.trigger('warn', {
  771. message: 'defaulting segment duration to the target duration'
  772. });
  773. currentUri.duration = this.manifest.targetDuration;
  774. }
  775. // annotate with encryption information, if necessary
  776. if (_key) {
  777. currentUri.key = _key;
  778. }
  779. currentUri.timeline = currentTimeline;
  780. // annotate with initialization segment information, if necessary
  781. if (currentMap) {
  782. currentUri.map = currentMap;
  783. }
  784. // prepare for the next URI
  785. currentUri = {};
  786. },
  787. comment: function comment() {
  788. // comments are not important for playback
  789. }
  790. })[entry.type].call(self);
  791. });
  792. return _this;
  793. }
  794. /**
  795. * Parse the input string and update the manifest object.
  796. *
  797. * @param {String} chunk a potentially incomplete portion of the manifest
  798. */
  799. _createClass(Parser, [{
  800. key: 'push',
  801. value: function push(chunk) {
  802. this.lineStream.push(chunk);
  803. }
  804. /**
  805. * Flush any remaining input. This can be handy if the last line of an M3U8
  806. * manifest did not contain a trailing newline but the file has been
  807. * completely received.
  808. */
  809. }, {
  810. key: 'end',
  811. value: function end() {
  812. // flush any buffered input
  813. this.lineStream.push('\n');
  814. }
  815. }]);
  816. return Parser;
  817. }(_stream2['default']);
  818. exports['default'] = Parser;
  819. },{"./line-stream":1,"./parse-stream":2,"./stream":4}],4:[function(require,module,exports){
  820. 'use strict';
  821. Object.defineProperty(exports, "__esModule", {
  822. value: true
  823. });
  824. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  825. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  826. /**
  827. * @file stream.js
  828. */
  829. /**
  830. * A lightweight readable stream implemention that handles event dispatching.
  831. *
  832. * @class Stream
  833. */
  834. var Stream = function () {
  835. function Stream() {
  836. _classCallCheck(this, Stream);
  837. this.listeners = {};
  838. }
  839. /**
  840. * Add a listener for a specified event type.
  841. *
  842. * @param {String} type the event name
  843. * @param {Function} listener the callback to be invoked when an event of
  844. * the specified type occurs
  845. */
  846. _createClass(Stream, [{
  847. key: 'on',
  848. value: function on(type, listener) {
  849. if (!this.listeners[type]) {
  850. this.listeners[type] = [];
  851. }
  852. this.listeners[type].push(listener);
  853. }
  854. /**
  855. * Remove a listener for a specified event type.
  856. *
  857. * @param {String} type the event name
  858. * @param {Function} listener a function previously registered for this
  859. * type of event through `on`
  860. * @return {Boolean} if we could turn it off or not
  861. */
  862. }, {
  863. key: 'off',
  864. value: function off(type, listener) {
  865. if (!this.listeners[type]) {
  866. return false;
  867. }
  868. var index = this.listeners[type].indexOf(listener);
  869. this.listeners[type].splice(index, 1);
  870. return index > -1;
  871. }
  872. /**
  873. * Trigger an event of the specified type on this stream. Any additional
  874. * arguments to this function are passed as parameters to event listeners.
  875. *
  876. * @param {String} type the event name
  877. */
  878. }, {
  879. key: 'trigger',
  880. value: function trigger(type) {
  881. var callbacks = this.listeners[type];
  882. var i = void 0;
  883. var length = void 0;
  884. var args = void 0;
  885. if (!callbacks) {
  886. return;
  887. }
  888. // Slicing the arguments on every invocation of this method
  889. // can add a significant amount of overhead. Avoid the
  890. // intermediate object creation for the common case of a
  891. // single callback argument
  892. if (arguments.length === 2) {
  893. length = callbacks.length;
  894. for (i = 0; i < length; ++i) {
  895. callbacks[i].call(this, arguments[1]);
  896. }
  897. } else {
  898. args = Array.prototype.slice.call(arguments, 1);
  899. length = callbacks.length;
  900. for (i = 0; i < length; ++i) {
  901. callbacks[i].apply(this, args);
  902. }
  903. }
  904. }
  905. /**
  906. * Destroys the stream and cleans up.
  907. */
  908. }, {
  909. key: 'dispose',
  910. value: function dispose() {
  911. this.listeners = {};
  912. }
  913. /**
  914. * Forwards all `data` events on this stream to the destination stream. The
  915. * destination stream should provide a method `push` to receive the data
  916. * events as they arrive.
  917. *
  918. * @param {Stream} destination the stream that will receive all `data` events
  919. * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  920. */
  921. }, {
  922. key: 'pipe',
  923. value: function pipe(destination) {
  924. this.on('data', function (data) {
  925. destination.push(data);
  926. });
  927. }
  928. }]);
  929. return Stream;
  930. }();
  931. exports['default'] = Stream;
  932. },{}],5:[function(require,module,exports){
  933. 'use strict';
  934. var _lineStream = require('./line-stream');
  935. var _lineStream2 = _interopRequireDefault(_lineStream);
  936. var _parseStream = require('./parse-stream');
  937. var _parseStream2 = _interopRequireDefault(_parseStream);
  938. var _parser = require('./parser');
  939. var _parser2 = _interopRequireDefault(_parser);
  940. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  941. module.exports = {
  942. LineStream: _lineStream2['default'],
  943. ParseStream: _parseStream2['default'],
  944. Parser: _parser2['default']
  945. }; /**
  946. * @file m3u8/index.js
  947. *
  948. * Utilities for parsing M3U8 files. If the entire manifest is available,
  949. * `Parser` will create an object representation with enough detail for managing
  950. * playback. `ParseStream` and `LineStream` are lower-level parsing primitives
  951. * that do not assume the entirety of the manifest is ready and expose a
  952. * ReadableStream-like interface.
  953. */
  954. },{"./line-stream":1,"./parse-stream":2,"./parser":3}]},{},[5])(5)
  955. });