parse-stream.test.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. import {LineStream, ParseStream} from '../src';
  2. import QUnit from 'qunit';
  3. import sinon from 'sinon';
  4. QUnit.module('ParseStream', {
  5. beforeEach() {
  6. this.lineStream = new LineStream();
  7. this.parseStream = new ParseStream();
  8. this.lineStream.pipe(this.parseStream);
  9. }
  10. });
  11. QUnit.test('parses custom tags', function(assert) {
  12. const manifest = '#VOD-STARTTIMESTAMP:1501533337573\n';
  13. let element;
  14. this.parseStream.addParser({
  15. expression: /^#VOD-STARTTIMESTAMP/,
  16. customType: 'startTimestamp'
  17. });
  18. this.parseStream.on('data', function(elem) {
  19. element = elem;
  20. });
  21. this.lineStream.push(manifest);
  22. assert.ok(element, 'element');
  23. assert.strictEqual(element.type, 'custom', 'the type of the data is custom');
  24. assert.strictEqual(
  25. element.customType,
  26. 'startTimestamp',
  27. 'the customType is startTimestamp'
  28. );
  29. });
  30. QUnit.test('mapper does not conflict with parser', function(assert) {
  31. const manifest = '#EXAMPLE\n';
  32. const commentMapper = sinon.spy(line => '#NEW-COMMENT');
  33. const commentMapper2 = sinon.spy(line => '#SOMETHING-ELSE');
  34. const dataCallback = sinon.spy();
  35. this.parseStream.addTagMapper({
  36. expression: /^#EXAMPLE/,
  37. map: commentMapper
  38. });
  39. this.parseStream.addTagMapper({
  40. expression: /^#EXAMPLE/,
  41. map: commentMapper2
  42. });
  43. this.parseStream.addParser({
  44. expression: /^#EXAMPLE/,
  45. customType: 'test'
  46. });
  47. this.parseStream.addParser({
  48. expression: /^#NEW-COMMENT/,
  49. customType: 'test2'
  50. });
  51. this.parseStream.on('data', dataCallback);
  52. this.lineStream.push(manifest);
  53. assert.ok(commentMapper.called);
  54. assert.ok(commentMapper2.called);
  55. assert.strictEqual(dataCallback.callCount, 3);
  56. assert.deepEqual(dataCallback.getCall(0).args[0], {
  57. data: '#EXAMPLE',
  58. type: 'custom',
  59. customType: 'test',
  60. segment: undefined
  61. });
  62. assert.deepEqual(dataCallback.getCall(1).args[0], {
  63. data: '#NEW-COMMENT',
  64. type: 'custom',
  65. customType: 'test2',
  66. segment: undefined
  67. });
  68. assert.deepEqual(dataCallback.getCall(2).args[0], {
  69. text: 'SOMETHING-ELSE',
  70. type: 'comment'
  71. });
  72. });
  73. QUnit.test('maps custom tags', function(assert) {
  74. const manifest = '#EXAMPLE\n';
  75. const commentMapper = sinon.spy(line => '#NEW-COMMENT');
  76. const dataCallback = sinon.spy();
  77. this.parseStream.addTagMapper({
  78. expression: /^#EXAMPLE/,
  79. map: commentMapper
  80. });
  81. this.parseStream.on('data', dataCallback);
  82. this.lineStream.push(manifest);
  83. assert.ok(commentMapper.called);
  84. assert.strictEqual(dataCallback.callCount, 2);
  85. assert.deepEqual(dataCallback.getCall(0).args[0], {
  86. text: 'EXAMPLE',
  87. type: 'comment'
  88. });
  89. assert.deepEqual(dataCallback.getCall(1).args[0], {
  90. text: 'NEW-COMMENT',
  91. type: 'comment'
  92. });
  93. });
  94. QUnit.test('maps multiple custom tags', function(assert) {
  95. const manifest = '#VOD-STARTTIMESTAMP:1501533337573\n';
  96. const commentMapper = sinon.spy(line => '#NEW-COMMENT');
  97. const pdtMapper = sinon.spy((line) => {
  98. const match = /#VOD-STARTTIMESTAMP:(\d+)/g.exec(line)[1];
  99. const ISOdate = new Date(Number(match)).toISOString();
  100. return `#EXT-X-PROGRAM-DATE-TIME:${ISOdate}`;
  101. });
  102. const dataCallback = sinon.spy();
  103. this.parseStream.addTagMapper({
  104. expression: /^#VOD-STARTTIMESTAMP/,
  105. map: commentMapper
  106. });
  107. this.parseStream.addTagMapper({
  108. expression: /^#VOD-STARTTIMESTAMP/,
  109. map: pdtMapper
  110. });
  111. this.parseStream.on('data', dataCallback);
  112. this.lineStream.push(manifest);
  113. assert.ok(commentMapper.called);
  114. assert.ok(pdtMapper.called);
  115. assert.strictEqual(dataCallback.callCount, 3);
  116. assert.deepEqual(dataCallback.getCall(0).args[0], {
  117. text: 'VOD-STARTTIMESTAMP:1501533337573',
  118. type: 'comment'
  119. });
  120. assert.deepEqual(dataCallback.getCall(1).args[0], {
  121. text: 'NEW-COMMENT',
  122. type: 'comment'
  123. });
  124. const dateTag = dataCallback.getCall(2).args[0];
  125. assert.strictEqual(dateTag.dateTimeString, '2017-07-31T20:35:37.573Z');
  126. assert.strictEqual(dateTag.tagType, 'program-date-time');
  127. assert.strictEqual(dateTag.type, 'tag');
  128. });
  129. QUnit.test('mapper ignores tags', function(assert) {
  130. const manifest = '#TAG\n';
  131. const dataCallback = sinon.spy();
  132. this.parseStream.addTagMapper({
  133. expression: /^#NO-MATCH/,
  134. map(line) {
  135. return '#MAPPED';
  136. }
  137. });
  138. this.parseStream.on('data', dataCallback);
  139. this.lineStream.push(manifest);
  140. assert.strictEqual(dataCallback.callCount, 1);
  141. assert.deepEqual(dataCallback.getCall(0).args[0], {
  142. text: 'TAG',
  143. type: 'comment'
  144. });
  145. });
  146. QUnit.test('parses comment lines', function(assert) {
  147. const manifest = '# a line that starts with a hash mark without "EXT" is a comment\n';
  148. let element;
  149. this.parseStream.on('data', function(elem) {
  150. element = elem;
  151. });
  152. this.lineStream.push(manifest);
  153. assert.ok(element, 'an event was triggered');
  154. assert.strictEqual(element.type, 'comment', 'the type is comment');
  155. assert.strictEqual(
  156. element.text,
  157. manifest.slice(1, manifest.length - 1),
  158. 'the comment text is parsed'
  159. );
  160. });
  161. QUnit.test('parses uri lines', function(assert) {
  162. const manifest = 'any non-blank line that does not start with a hash-mark is a URI\n';
  163. let element;
  164. this.parseStream.on('data', function(elem) {
  165. element = elem;
  166. });
  167. this.lineStream.push(manifest);
  168. assert.ok(element, 'an event was triggered');
  169. assert.strictEqual(element.type, 'uri', 'the type is uri');
  170. assert.strictEqual(
  171. element.uri,
  172. manifest.substring(0, manifest.length - 1),
  173. 'the uri text is parsed'
  174. );
  175. });
  176. QUnit.test('parses unknown tag types', function(assert) {
  177. const manifest = '#EXT-X-EXAMPLE-TAG:some,additional,stuff\n';
  178. let element;
  179. this.parseStream.on('data', function(elem) {
  180. element = elem;
  181. });
  182. this.lineStream.push(manifest);
  183. assert.ok(element, 'an event was triggered');
  184. assert.strictEqual(element.type, 'tag', 'the type is tag');
  185. assert.strictEqual(
  186. element.data,
  187. manifest.slice(4, manifest.length - 1),
  188. 'unknown tag data is preserved'
  189. );
  190. });
  191. // #EXTM3U
  192. QUnit.test('parses #EXTM3U tags', function(assert) {
  193. const manifest = '#EXTM3U\n';
  194. let element;
  195. this.parseStream.on('data', function(elem) {
  196. element = elem;
  197. });
  198. this.lineStream.push(manifest);
  199. assert.ok(element, 'an event was triggered');
  200. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  201. assert.strictEqual(element.tagType, 'm3u', 'the tag type is m3u');
  202. });
  203. // #EXTINF
  204. QUnit.test('parses minimal #EXTINF tags', function(assert) {
  205. const manifest = '#EXTINF:\n';
  206. let element;
  207. this.parseStream.on('data', function(elem) {
  208. element = elem;
  209. });
  210. this.lineStream.push(manifest);
  211. assert.ok(element, 'an event was triggered');
  212. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  213. assert.strictEqual(element.tagType, 'inf', 'the tag type is inf');
  214. });
  215. QUnit.test('parses #EXTINF tags with durations', function(assert) {
  216. let manifest = '#EXTINF:15\n';
  217. let element;
  218. this.parseStream.on('data', function(elem) {
  219. element = elem;
  220. });
  221. this.lineStream.push(manifest);
  222. assert.ok(element, 'an event was triggered');
  223. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  224. assert.strictEqual(element.tagType, 'inf', 'the tag type is inf');
  225. assert.strictEqual(element.duration, 15, 'the duration is parsed');
  226. assert.ok(!('title' in element), 'no title is parsed');
  227. manifest = '#EXTINF:21,\n';
  228. this.lineStream.push(manifest);
  229. assert.ok(element, 'an event was triggered');
  230. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  231. assert.strictEqual(element.tagType, 'inf', 'the tag type is inf');
  232. assert.strictEqual(element.duration, 21, 'the duration is parsed');
  233. assert.ok(!('title' in element), 'no title is parsed');
  234. });
  235. QUnit.test('parses #EXTINF tags with a duration and title', function(assert) {
  236. const manifest = '#EXTINF:13,Does anyone really use the title attribute?\n';
  237. let element;
  238. this.parseStream.on('data', function(elem) {
  239. element = elem;
  240. });
  241. this.lineStream.push(manifest);
  242. assert.ok(element, 'an event was triggered');
  243. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  244. assert.strictEqual(element.tagType, 'inf', 'the tag type is inf');
  245. assert.strictEqual(element.duration, 13, 'the duration is parsed');
  246. assert.strictEqual(
  247. element.title,
  248. manifest.substring(manifest.indexOf(',') + 1, manifest.length - 1),
  249. 'the title is parsed'
  250. );
  251. });
  252. QUnit.test('parses #EXTINF tags with carriage returns', function(assert) {
  253. const manifest = '#EXTINF:13,Does anyone really use the title attribute?\r\n';
  254. let element;
  255. this.parseStream.on('data', function(elem) {
  256. element = elem;
  257. });
  258. this.lineStream.push(manifest);
  259. assert.ok(element, 'an event was triggered');
  260. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  261. assert.strictEqual(element.tagType, 'inf', 'the tag type is inf');
  262. assert.strictEqual(element.duration, 13, 'the duration is parsed');
  263. assert.strictEqual(
  264. element.title,
  265. manifest.substring(manifest.indexOf(',') + 1, manifest.length - 2),
  266. 'the title is parsed'
  267. );
  268. });
  269. // #EXT-X-TARGETDURATION
  270. QUnit.test('parses minimal #EXT-X-TARGETDURATION tags', function(assert) {
  271. const manifest = '#EXT-X-TARGETDURATION:\n';
  272. let element;
  273. this.parseStream.on('data', function(elem) {
  274. element = elem;
  275. });
  276. this.lineStream.push(manifest);
  277. assert.ok(element, 'an event was triggered');
  278. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  279. assert.strictEqual(element.tagType, 'targetduration', 'the tag type is targetduration');
  280. assert.ok(!('duration' in element), 'no duration is parsed');
  281. });
  282. QUnit.test('parses #EXT-X-TARGETDURATION with duration', function(assert) {
  283. const manifest = '#EXT-X-TARGETDURATION:47\n';
  284. let element;
  285. this.parseStream.on('data', function(elem) {
  286. element = elem;
  287. });
  288. this.lineStream.push(manifest);
  289. assert.ok(element, 'an event was triggered');
  290. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  291. assert.strictEqual(element.tagType, 'targetduration', 'the tag type is targetduration');
  292. assert.strictEqual(element.duration, 47, 'the duration is parsed');
  293. });
  294. // #EXT-X-VERSION
  295. QUnit.test('parses minimal #EXT-X-VERSION tags', function(assert) {
  296. const manifest = '#EXT-X-VERSION:\n';
  297. let element;
  298. this.parseStream.on('data', function(elem) {
  299. element = elem;
  300. });
  301. this.lineStream.push(manifest);
  302. assert.ok(element, 'an event was triggered');
  303. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  304. assert.strictEqual(element.tagType, 'version', 'the tag type is version');
  305. assert.ok(!('version' in element), 'no version is present');
  306. });
  307. QUnit.test('parses #EXT-X-VERSION with a version', function(assert) {
  308. const manifest = '#EXT-X-VERSION:99\n';
  309. let element;
  310. this.parseStream.on('data', function(elem) {
  311. element = elem;
  312. });
  313. this.lineStream.push(manifest);
  314. assert.ok(element, 'an event was triggered');
  315. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  316. assert.strictEqual(element.tagType, 'version', 'the tag type is version');
  317. assert.strictEqual(element.version, 99, 'the version is parsed');
  318. });
  319. // #EXT-X-MEDIA-SEQUENCE
  320. QUnit.test('parses minimal #EXT-X-MEDIA-SEQUENCE tags', function(assert) {
  321. const manifest = '#EXT-X-MEDIA-SEQUENCE:\n';
  322. let element;
  323. this.parseStream.on('data', function(elem) {
  324. element = elem;
  325. });
  326. this.lineStream.push(manifest);
  327. assert.ok(element, 'an event was triggered');
  328. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  329. assert.strictEqual(element.tagType, 'media-sequence', 'the tag type is media-sequence');
  330. assert.ok(!('number' in element), 'no number is present');
  331. });
  332. QUnit.test('parses #EXT-X-MEDIA-SEQUENCE with sequence numbers', function(assert) {
  333. const manifest = '#EXT-X-MEDIA-SEQUENCE:109\n';
  334. let element;
  335. this.parseStream.on('data', function(elem) {
  336. element = elem;
  337. });
  338. this.lineStream.push(manifest);
  339. assert.ok(element, 'an event was triggered');
  340. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  341. assert.strictEqual(element.tagType, 'media-sequence', 'the tag type is media-sequence');
  342. assert.ok(element.number, 109, 'the number is parsed');
  343. });
  344. // #EXT-X-PLAYLIST-TYPE
  345. QUnit.test('parses minimal #EXT-X-PLAYLIST-TYPE tags', function(assert) {
  346. const manifest = '#EXT-X-PLAYLIST-TYPE:\n';
  347. let element;
  348. this.parseStream.on('data', function(elem) {
  349. element = elem;
  350. });
  351. this.lineStream.push(manifest);
  352. assert.ok(element, 'an event was triggered');
  353. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  354. assert.strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type');
  355. assert.ok(!('playlistType' in element), 'no playlist type is present');
  356. });
  357. QUnit.test('parses #EXT-X-PLAYLIST-TYPE with mutability info', function(assert) {
  358. let manifest = '#EXT-X-PLAYLIST-TYPE:EVENT\n';
  359. let element;
  360. this.parseStream.on('data', function(elem) {
  361. element = elem;
  362. });
  363. this.lineStream.push(manifest);
  364. assert.ok(element, 'an event was triggered');
  365. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  366. assert.strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type');
  367. assert.strictEqual(element.playlistType, 'EVENT', 'the playlist type is EVENT');
  368. manifest = '#EXT-X-PLAYLIST-TYPE:VOD\n';
  369. this.lineStream.push(manifest);
  370. assert.ok(element, 'an event was triggered');
  371. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  372. assert.strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type');
  373. assert.strictEqual(element.playlistType, 'VOD', 'the playlist type is VOD');
  374. manifest = '#EXT-X-PLAYLIST-TYPE:nonsense\n';
  375. this.lineStream.push(manifest);
  376. assert.ok(element, 'an event was triggered');
  377. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  378. assert.strictEqual(element.tagType, 'playlist-type', 'the tag type is playlist-type');
  379. assert.strictEqual(element.playlistType, 'nonsense', 'the playlist type is parsed');
  380. });
  381. // #EXT-X-BYTERANGE
  382. QUnit.test('parses minimal #EXT-X-BYTERANGE tags', function(assert) {
  383. const manifest = '#EXT-X-BYTERANGE:\n';
  384. let element;
  385. this.parseStream.on('data', function(elem) {
  386. element = elem;
  387. });
  388. this.lineStream.push(manifest);
  389. assert.ok(element, 'an event was triggered');
  390. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  391. assert.strictEqual(element.tagType, 'byterange', 'the tag type is byterange');
  392. assert.ok(!('length' in element), 'no length is present');
  393. assert.ok(!('offset' in element), 'no offset is present');
  394. });
  395. QUnit.test('parses #EXT-X-BYTERANGE with length and offset', function(assert) {
  396. let manifest = '#EXT-X-BYTERANGE:45\n';
  397. let element;
  398. this.parseStream.on('data', function(elem) {
  399. element = elem;
  400. });
  401. this.lineStream.push(manifest);
  402. assert.ok(element, 'an event was triggered');
  403. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  404. assert.strictEqual(element.tagType, 'byterange', 'the tag type is byterange');
  405. assert.strictEqual(element.length, 45, 'length is parsed');
  406. assert.ok(!('offset' in element), 'no offset is present');
  407. manifest = '#EXT-X-BYTERANGE:108@16\n';
  408. this.lineStream.push(manifest);
  409. assert.ok(element, 'an event was triggered');
  410. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  411. assert.strictEqual(element.tagType, 'byterange', 'the tag type is byterange');
  412. assert.strictEqual(element.length, 108, 'length is parsed');
  413. assert.strictEqual(element.offset, 16, 'offset is parsed');
  414. });
  415. // #EXT-X-ALLOW-CACHE
  416. QUnit.test('parses minimal #EXT-X-ALLOW-CACHE tags', function(assert) {
  417. const manifest = '#EXT-X-ALLOW-CACHE:\n';
  418. let element;
  419. this.parseStream.on('data', function(elem) {
  420. element = elem;
  421. });
  422. this.lineStream.push(manifest);
  423. assert.ok(element, 'an event was triggered');
  424. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  425. assert.strictEqual(element.tagType, 'allow-cache', 'the tag type is allow-cache');
  426. assert.ok(!('allowed' in element), 'no allowed is present');
  427. });
  428. QUnit.test('parses valid #EXT-X-ALLOW-CACHE tags', function(assert) {
  429. let manifest = '#EXT-X-ALLOW-CACHE:YES\n';
  430. let element;
  431. this.parseStream.on('data', function(elem) {
  432. element = elem;
  433. });
  434. this.lineStream.push(manifest);
  435. assert.ok(element, 'an event was triggered');
  436. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  437. assert.strictEqual(element.tagType, 'allow-cache', 'the tag type is allow-cache');
  438. assert.ok(element.allowed, 'allowed is parsed');
  439. manifest = '#EXT-X-ALLOW-CACHE:NO\n';
  440. this.lineStream.push(manifest);
  441. assert.ok(element, 'an event was triggered');
  442. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  443. assert.strictEqual(element.tagType, 'allow-cache', 'the tag type is allow-cache');
  444. assert.ok(!element.allowed, 'allowed is parsed');
  445. });
  446. // #EXT-X-MAP
  447. QUnit.test('parses minimal #EXT-X-MAP tags', function(assert) {
  448. const manifest = '#EXT-X-MAP:URI="init.m4s"\n';
  449. let element;
  450. this.parseStream.on('data', function(elem) {
  451. element = elem;
  452. });
  453. this.lineStream.push(manifest);
  454. assert.ok(element, 'an event was triggered');
  455. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  456. assert.strictEqual(element.tagType, 'map', 'the tag type is map');
  457. assert.strictEqual(element.uri, 'init.m4s', 'parsed the uri');
  458. });
  459. QUnit.test('parses #EXT-X-MAP tags with a byterange', function(assert) {
  460. const manifest = '#EXT-X-MAP:URI="0.m4s", BYTERANGE="1000@23"\n';
  461. let element;
  462. this.parseStream.on('data', function(elem) {
  463. element = elem;
  464. });
  465. this.lineStream.push(manifest);
  466. assert.ok(element, 'an event was triggered');
  467. assert.strictEqual(element.uri, '0.m4s', 'parsed the uri');
  468. assert.strictEqual(
  469. element.byterange.length,
  470. 1000,
  471. 'parsed the byterange length'
  472. );
  473. assert.strictEqual(
  474. element.byterange.offset,
  475. 23,
  476. 'parsed the byterange offset'
  477. );
  478. });
  479. QUnit.test('parses #EXT-X-MAP tags with arbitrary attributes', function(assert) {
  480. const manifest = '#EXT-X-MAP:URI="init.mp4", SOMETHING=YES,BYTERANGE="720@0"\n';
  481. let element;
  482. this.parseStream.on('data', function(elem) {
  483. element = elem;
  484. });
  485. this.lineStream.push(manifest);
  486. assert.ok(element, 'an event was triggered');
  487. assert.strictEqual(element.uri, 'init.mp4', 'parsed the uri');
  488. assert.strictEqual(
  489. element.byterange.length,
  490. 720,
  491. 'parsed the byterange length'
  492. );
  493. assert.strictEqual(
  494. element.byterange.offset,
  495. 0,
  496. 'parsed the byterange offset'
  497. );
  498. });
  499. // #EXT-X-STREAM-INF
  500. QUnit.test('parses minimal #EXT-X-STREAM-INF tags', function(assert) {
  501. const manifest = '#EXT-X-STREAM-INF:\n';
  502. let element;
  503. this.parseStream.on('data', function(elem) {
  504. element = elem;
  505. });
  506. this.lineStream.push(manifest);
  507. assert.ok(element, 'an event was triggered');
  508. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  509. assert.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
  510. assert.ok(!('attributes' in element), 'no attributes are present');
  511. });
  512. // #EXT-X-PROGRAM-DATE-TIME
  513. QUnit.test('parses minimal EXT-X-PROGRAM-DATE-TIME tags', function(assert) {
  514. const manifest = '#EXT-X-PROGRAM-DATE-TIME:\n';
  515. let element;
  516. this.parseStream.on('data', function(elem) {
  517. element = elem;
  518. });
  519. this.lineStream.push(manifest);
  520. assert.ok(element, 'an event was triggered');
  521. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  522. assert.strictEqual(element.tagType, 'program-date-time', 'the tag type is date-time');
  523. assert.ok(!('dateTimeString' in element), 'no dateTime is present');
  524. });
  525. QUnit.test(
  526. 'parses EXT-X-PROGRAM-DATE-TIME tags with valid date-time formats',
  527. function(assert) {
  528. let manifest = '#EXT-X-PROGRAM-DATE-TIME:2016-06-22T09:20:16.166-04:00\n';
  529. let element;
  530. this.parseStream.on('data', function(elem) {
  531. element = elem;
  532. });
  533. this.lineStream.push(manifest);
  534. assert.ok(element, 'an event was triggered');
  535. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  536. assert.strictEqual(element.tagType, 'program-date-time', 'the tag type is date-time');
  537. assert.strictEqual(
  538. element.dateTimeString, '2016-06-22T09:20:16.166-04:00',
  539. 'dateTimeString is parsed'
  540. );
  541. assert.deepEqual(
  542. element.dateTimeObject, new Date('2016-06-22T09:20:16.166-04:00'),
  543. 'dateTimeObject is parsed'
  544. );
  545. manifest = '#EXT-X-PROGRAM-DATE-TIME:2016-06-22T09:20:16.16389Z\n';
  546. this.lineStream.push(manifest);
  547. assert.ok(element, 'an event was triggered');
  548. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  549. assert.strictEqual(element.tagType, 'program-date-time', 'the tag type is date-time');
  550. assert.strictEqual(
  551. element.dateTimeString, '2016-06-22T09:20:16.16389Z',
  552. 'dateTimeString is parsed'
  553. );
  554. assert.deepEqual(
  555. element.dateTimeObject, new Date('2016-06-22T09:20:16.16389Z'),
  556. 'dateTimeObject is parsed'
  557. );
  558. }
  559. );
  560. QUnit.test('parses #EXT-X-STREAM-INF with common attributes', function(assert) {
  561. let manifest = '#EXT-X-STREAM-INF:BANDWIDTH=14400\n';
  562. let element;
  563. this.parseStream.on('data', function(elem) {
  564. element = elem;
  565. });
  566. this.lineStream.push(manifest);
  567. assert.ok(element, 'an event was triggered');
  568. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  569. assert.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
  570. assert.strictEqual(element.attributes.BANDWIDTH, 14400, 'bandwidth is parsed');
  571. manifest = '#EXT-X-STREAM-INF:PROGRAM-ID=7\n';
  572. this.lineStream.push(manifest);
  573. assert.ok(element, 'an event was triggered');
  574. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  575. assert.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
  576. assert.strictEqual(element.attributes['PROGRAM-ID'], 7, 'program-id is parsed');
  577. manifest = '#EXT-X-STREAM-INF:RESOLUTION=396x224\n';
  578. this.lineStream.push(manifest);
  579. assert.ok(element, 'an event was triggered');
  580. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  581. assert.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
  582. assert.strictEqual(element.attributes.RESOLUTION.width, 396, 'width is parsed');
  583. assert.strictEqual(element.attributes.RESOLUTION.height, 224, 'heigth is parsed');
  584. manifest = '#EXT-X-STREAM-INF:CODECS="avc1.4d400d, mp4a.40.2"\n';
  585. this.lineStream.push(manifest);
  586. assert.ok(element, 'an event was triggered');
  587. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  588. assert.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
  589. assert.strictEqual(
  590. element.attributes.CODECS,
  591. 'avc1.4d400d, mp4a.40.2',
  592. 'codecs are parsed'
  593. );
  594. });
  595. QUnit.test('parses #EXT-X-STREAM-INF with arbitrary attributes', function(assert) {
  596. const manifest = '#EXT-X-STREAM-INF:NUMERIC=24,ALPHA=Value,MIXED=123abc\n';
  597. let element;
  598. this.parseStream.on('data', function(elem) {
  599. element = elem;
  600. });
  601. this.lineStream.push(manifest);
  602. assert.ok(element, 'an event was triggered');
  603. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  604. assert.strictEqual(element.tagType, 'stream-inf', 'the tag type is stream-inf');
  605. assert.strictEqual(element.attributes.NUMERIC, '24', 'numeric attributes are parsed');
  606. assert.strictEqual(
  607. element.attributes.ALPHA,
  608. 'Value',
  609. 'alphabetic attributes are parsed'
  610. );
  611. assert.strictEqual(element.attributes.MIXED, '123abc', 'mixed attributes are parsed');
  612. });
  613. // #EXT-X-ENDLIST
  614. QUnit.test('parses #EXT-X-ENDLIST tags', function(assert) {
  615. const manifest = '#EXT-X-ENDLIST\n';
  616. let element;
  617. this.parseStream.on('data', function(elem) {
  618. element = elem;
  619. });
  620. this.lineStream.push(manifest);
  621. assert.ok(element, 'an event was triggered');
  622. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  623. assert.strictEqual(element.tagType, 'endlist', 'the tag type is stream-inf');
  624. });
  625. // #EXT-X-KEY
  626. QUnit.test('parses valid #EXT-X-KEY tags', function(assert) {
  627. let manifest =
  628. '#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52"\n';
  629. let element;
  630. this.parseStream.on('data', function(elem) {
  631. element = elem;
  632. });
  633. this.lineStream.push(manifest);
  634. assert.ok(element, 'an event was triggered');
  635. assert.deepEqual(element, {
  636. type: 'tag',
  637. tagType: 'key',
  638. attributes: {
  639. METHOD: 'AES-128',
  640. URI: 'https://priv.example.com/key.php?r=52'
  641. }
  642. }, 'parsed a valid key');
  643. manifest = '#EXT-X-KEY:URI="https://example.com/key#1",METHOD=FutureType-1024\n';
  644. this.lineStream.push(manifest);
  645. assert.ok(element, 'an event was triggered');
  646. assert.deepEqual(element, {
  647. type: 'tag',
  648. tagType: 'key',
  649. attributes: {
  650. METHOD: 'FutureType-1024',
  651. URI: 'https://example.com/key#1'
  652. }
  653. }, 'parsed the attribute list independent of order');
  654. manifest = '#EXT-X-KEY:IV=1234567890abcdef1234567890abcdef\n';
  655. this.lineStream.push(manifest);
  656. assert.ok(element.attributes.IV, 'detected an IV attribute');
  657. assert.deepEqual(element.attributes.IV, new Uint32Array([
  658. 0x12345678,
  659. 0x90abcdef,
  660. 0x12345678,
  661. 0x90abcdef
  662. ]), 'parsed an IV value');
  663. });
  664. QUnit.test('parses minimal #EXT-X-KEY tags', function(assert) {
  665. const manifest = '#EXT-X-KEY:\n';
  666. let element;
  667. this.parseStream.on('data', function(elem) {
  668. element = elem;
  669. });
  670. this.lineStream.push(manifest);
  671. assert.ok(element, 'an event was triggered');
  672. assert.deepEqual(element, {
  673. type: 'tag',
  674. tagType: 'key'
  675. }, 'parsed a minimal key tag');
  676. });
  677. QUnit.test('parses lightly-broken #EXT-X-KEY tags', function(assert) {
  678. let manifest = '#EXT-X-KEY:URI=\'https://example.com/single-quote\',METHOD=AES-128\n';
  679. let element;
  680. this.parseStream.on('data', function(elem) {
  681. element = elem;
  682. });
  683. this.lineStream.push(manifest);
  684. assert.strictEqual(
  685. element.attributes.URI,
  686. 'https://example.com/single-quote',
  687. 'parsed a single-quoted uri'
  688. );
  689. element = null;
  690. manifest = '#EXT-X-KEY: URI = "https://example.com/key",METHOD=AES-128\n';
  691. this.lineStream.push(manifest);
  692. assert.strictEqual(
  693. element.attributes.URI,
  694. 'https://example.com/key',
  695. 'trims and removes quotes around the URI'
  696. );
  697. });
  698. QUnit.test('parses prefixed with 0x or 0X #EXT-X-KEY:IV tags', function(assert) {
  699. let manifest;
  700. let element;
  701. this.parseStream.on('data', function(elem) {
  702. element = elem;
  703. });
  704. manifest = '#EXT-X-KEY:IV=0x1234567890abcdef1234567890abcdef\n';
  705. this.lineStream.push(manifest);
  706. assert.ok(element.attributes.IV, 'detected an IV attribute');
  707. assert.deepEqual(element.attributes.IV, new Uint32Array([
  708. 0x12345678,
  709. 0x90abcdef,
  710. 0x12345678,
  711. 0x90abcdef
  712. ]), 'parsed an IV value with 0x');
  713. manifest = '#EXT-X-KEY:IV=0X1234567890abcdef1234567890abcdef\n';
  714. this.lineStream.push(manifest);
  715. assert.ok(element.attributes.IV, 'detected an IV attribute');
  716. assert.deepEqual(element.attributes.IV, new Uint32Array([
  717. 0x12345678,
  718. 0x90abcdef,
  719. 0x12345678,
  720. 0x90abcdef
  721. ]), 'parsed an IV value with 0X');
  722. });
  723. // #EXT-X-START
  724. QUnit.test('parses EXT-X-START tags', function(assert) {
  725. const manifest = '#EXT-X-START:TIME-OFFSET=1.1\n';
  726. let element;
  727. this.parseStream.on('data', function(elem) {
  728. element = elem;
  729. });
  730. this.lineStream.push(manifest);
  731. assert.ok(element, 'an event was triggered');
  732. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  733. assert.strictEqual(element.tagType, 'start', 'the tag type is start');
  734. assert.strictEqual(element.attributes['TIME-OFFSET'], 1.1, 'parses time offset');
  735. assert.strictEqual(element.attributes.PRECISE, false, 'precise defaults to false');
  736. });
  737. QUnit.test('parses EXT-X-START PRECISE attribute', function(assert) {
  738. const manifest = '#EXT-X-START:TIME-OFFSET=1.4,PRECISE=YES\n';
  739. let element;
  740. this.parseStream.on('data', function(elem) {
  741. element = elem;
  742. });
  743. this.lineStream.push(manifest);
  744. assert.ok(element, 'an event was triggered');
  745. assert.strictEqual(element.type, 'tag', 'the line type is tag');
  746. assert.strictEqual(element.tagType, 'start', 'the tag type is start');
  747. assert.strictEqual(element.attributes['TIME-OFFSET'], 1.4, 'parses time offset');
  748. assert.strictEqual(element.attributes.PRECISE, true, 'parses precise attribute');
  749. });
  750. QUnit.test('ignores empty lines', function(assert) {
  751. const manifest = '\n';
  752. let event = false;
  753. this.parseStream.on('data', function() {
  754. event = true;
  755. });
  756. this.lineStream.push(manifest);
  757. assert.ok(!event, 'no event is triggered');
  758. });