toM3u8.test.js 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393
  1. import {
  2. toM3u8,
  3. generateSidxKey,
  4. addMediaSequenceValues,
  5. flattenMediaGroupPlaylists
  6. } from '../src/toM3u8';
  7. import QUnit from 'qunit';
  8. QUnit.module('toM3u8');
  9. QUnit.test('playlists', function(assert) {
  10. const dashPlaylists = [{
  11. attributes: {
  12. id: '1',
  13. codecs: 'foo;bar',
  14. sourceDuration: 100,
  15. duration: 0,
  16. bandwidth: 20000,
  17. periodStart: 0,
  18. mimeType: 'audio/mp4',
  19. type: 'static'
  20. },
  21. segments: []
  22. }, {
  23. attributes: {
  24. id: '2',
  25. codecs: 'foo;bar',
  26. sourceDuration: 100,
  27. duration: 0,
  28. bandwidth: 10000,
  29. periodStart: 0,
  30. mimeType: 'audio/mp4',
  31. type: 'static'
  32. },
  33. segments: []
  34. }, {
  35. attributes: {
  36. sourceDuration: 100,
  37. id: '1',
  38. width: 800,
  39. height: 600,
  40. codecs: 'foo;bar',
  41. duration: 0,
  42. bandwidth: 10000,
  43. frameRate: 30,
  44. periodStart: 0,
  45. mimeType: 'video/mp4',
  46. type: 'static'
  47. },
  48. segments: []
  49. }, {
  50. attributes: {
  51. sourceDuration: 100,
  52. id: '1',
  53. bandwidth: 20000,
  54. periodStart: 0,
  55. mimeType: 'text/vtt',
  56. type: 'static',
  57. baseUrl: 'https://www.example.com/vtt'
  58. }
  59. }, {
  60. attributes: {
  61. sourceDuration: 100,
  62. id: '2',
  63. bandwidth: 10000,
  64. periodStart: 0,
  65. mimeType: 'text/vtt',
  66. type: 'static',
  67. baseUrl: 'https://www.example.com/vtt'
  68. }
  69. }];
  70. const expected = {
  71. allowCache: true,
  72. discontinuityStarts: [],
  73. timelineStarts: [{ start: 0, timeline: 0 }],
  74. duration: 100,
  75. endList: true,
  76. mediaGroups: {
  77. AUDIO: {
  78. audio: {
  79. main: {
  80. autoselect: true,
  81. default: true,
  82. language: '',
  83. playlists: [{
  84. attributes: {
  85. BANDWIDTH: 20000,
  86. CODECS: 'foo;bar',
  87. NAME: '1',
  88. ['PROGRAM-ID']: 1
  89. },
  90. mediaSequence: 0,
  91. discontinuitySequence: 0,
  92. discontinuityStarts: [],
  93. timelineStarts: [{ start: 0, timeline: 0 }],
  94. endList: true,
  95. resolvedUri: '',
  96. segments: [],
  97. timeline: 0,
  98. uri: '',
  99. targetDuration: 0
  100. }, {
  101. attributes: {
  102. BANDWIDTH: 10000,
  103. CODECS: 'foo;bar',
  104. NAME: '2',
  105. ['PROGRAM-ID']: 1
  106. },
  107. mediaSequence: 0,
  108. discontinuitySequence: 0,
  109. discontinuityStarts: [],
  110. timelineStarts: [{ start: 0, timeline: 0 }],
  111. endList: true,
  112. resolvedUri: '',
  113. segments: [],
  114. timeline: 0,
  115. uri: '',
  116. targetDuration: 0
  117. }],
  118. uri: ''
  119. }
  120. }
  121. },
  122. ['CLOSED-CAPTIONS']: {},
  123. SUBTITLES: {
  124. subs: {
  125. text: {
  126. autoselect: false,
  127. default: false,
  128. language: 'text',
  129. playlists: [{
  130. attributes: {
  131. BANDWIDTH: 20000,
  132. NAME: '1',
  133. ['PROGRAM-ID']: 1
  134. },
  135. mediaSequence: 0,
  136. discontinuitySequence: 0,
  137. discontinuityStarts: [],
  138. timelineStarts: [{ start: 0, timeline: 0 }],
  139. targetDuration: 100,
  140. endList: true,
  141. resolvedUri: 'https://www.example.com/vtt',
  142. segments: [{
  143. duration: 100,
  144. resolvedUri: 'https://www.example.com/vtt',
  145. timeline: 0,
  146. uri: 'https://www.example.com/vtt',
  147. number: 0
  148. }],
  149. timeline: 0,
  150. uri: ''
  151. }, {
  152. attributes: {
  153. BANDWIDTH: 10000,
  154. NAME: '2',
  155. ['PROGRAM-ID']: 1
  156. },
  157. mediaSequence: 0,
  158. discontinuitySequence: 0,
  159. discontinuityStarts: [],
  160. timelineStarts: [{ start: 0, timeline: 0 }],
  161. targetDuration: 100,
  162. endList: true,
  163. resolvedUri: 'https://www.example.com/vtt',
  164. segments: [{
  165. duration: 100,
  166. resolvedUri: 'https://www.example.com/vtt',
  167. timeline: 0,
  168. uri: 'https://www.example.com/vtt',
  169. number: 0
  170. }],
  171. timeline: 0,
  172. uri: ''
  173. }],
  174. uri: ''
  175. }
  176. }
  177. },
  178. VIDEO: {}
  179. },
  180. playlists: [{
  181. attributes: {
  182. AUDIO: 'audio',
  183. SUBTITLES: 'subs',
  184. BANDWIDTH: 10000,
  185. CODECS: 'foo;bar',
  186. NAME: '1',
  187. ['FRAME-RATE']: 30,
  188. ['PROGRAM-ID']: 1,
  189. RESOLUTION: {
  190. height: 600,
  191. width: 800
  192. }
  193. },
  194. endList: true,
  195. mediaSequence: 0,
  196. discontinuitySequence: 0,
  197. discontinuityStarts: [],
  198. timelineStarts: [{ start: 0, timeline: 0 }],
  199. targetDuration: 0,
  200. resolvedUri: '',
  201. segments: [],
  202. timeline: 0,
  203. uri: ''
  204. }],
  205. segments: [],
  206. uri: ''
  207. };
  208. assert.deepEqual(toM3u8({ dashPlaylists }), expected);
  209. });
  210. QUnit.test('playlists with segments', function(assert) {
  211. const dashPlaylists = [{
  212. attributes: {
  213. id: '1',
  214. codecs: 'foo;bar',
  215. duration: 2,
  216. sourceDuration: 100,
  217. bandwidth: 20000,
  218. periodStart: 0,
  219. mimeType: 'audio/mp4',
  220. type: 'static'
  221. },
  222. segments: [{
  223. uri: '',
  224. timeline: 0,
  225. duration: 2,
  226. resolvedUri: '',
  227. map: {
  228. uri: '',
  229. resolvedUri: ''
  230. },
  231. number: 0
  232. }, {
  233. uri: '',
  234. timeline: 0,
  235. duration: 2,
  236. resolvedUri: '',
  237. map: {
  238. uri: '',
  239. resolvedUri: ''
  240. },
  241. number: 1
  242. }]
  243. }, {
  244. attributes: {
  245. id: '2',
  246. codecs: 'foo;bar',
  247. sourceDuration: 100,
  248. duration: 2,
  249. bandwidth: 10000,
  250. periodStart: 0,
  251. mimeType: 'audio/mp4',
  252. type: 'static'
  253. },
  254. segments: [{
  255. uri: '',
  256. timeline: 0,
  257. duration: 2,
  258. resolvedUri: '',
  259. map: {
  260. uri: '',
  261. resolvedUri: ''
  262. },
  263. number: 0
  264. }, {
  265. uri: '',
  266. timeline: 0,
  267. duration: 2,
  268. resolvedUri: '',
  269. map: {
  270. uri: '',
  271. resolvedUri: ''
  272. },
  273. number: 1
  274. }]
  275. }, {
  276. attributes: {
  277. sourceDuration: 100,
  278. id: '1',
  279. width: 800,
  280. duration: 2,
  281. height: 600,
  282. codecs: 'foo;bar',
  283. bandwidth: 10000,
  284. periodStart: 0,
  285. mimeType: 'video/mp4',
  286. type: 'static'
  287. },
  288. segments: [{
  289. uri: '',
  290. timeline: 0,
  291. duration: 2,
  292. resolvedUri: '',
  293. map: {
  294. uri: '',
  295. resolvedUri: ''
  296. },
  297. number: 0
  298. }, {
  299. uri: '',
  300. timeline: 0,
  301. duration: 2,
  302. resolvedUri: '',
  303. map: {
  304. uri: '',
  305. resolvedUri: ''
  306. },
  307. number: 1
  308. }]
  309. }, {
  310. attributes: {
  311. sourceDuration: 100,
  312. id: '1',
  313. duration: 2,
  314. bandwidth: 20000,
  315. periodStart: 0,
  316. mimeType: 'text/vtt',
  317. type: 'static',
  318. baseUrl: 'https://www.example.com/vtt'
  319. },
  320. segments: [{
  321. uri: '',
  322. timeline: 0,
  323. duration: 2,
  324. resolvedUri: '',
  325. map: {
  326. uri: '',
  327. resolvedUri: ''
  328. },
  329. number: 0
  330. }, {
  331. uri: '',
  332. timeline: 0,
  333. duration: 2,
  334. resolvedUri: '',
  335. map: {
  336. uri: '',
  337. resolvedUri: ''
  338. },
  339. number: 1
  340. }]
  341. }, {
  342. attributes: {
  343. sourceDuration: 100,
  344. duration: 2,
  345. id: '2',
  346. bandwidth: 10000,
  347. periodStart: 0,
  348. mimeType: 'text/vtt',
  349. type: 'static',
  350. baseUrl: 'https://www.example.com/vtt'
  351. },
  352. segments: [{
  353. uri: '',
  354. timeline: 0,
  355. duration: 2,
  356. resolvedUri: '',
  357. map: {
  358. uri: '',
  359. resolvedUri: ''
  360. },
  361. number: 0
  362. }, {
  363. uri: '',
  364. timeline: 0,
  365. duration: 2,
  366. resolvedUri: '',
  367. map: {
  368. uri: '',
  369. resolvedUri: ''
  370. },
  371. number: 1
  372. }]
  373. }];
  374. const expected = {
  375. allowCache: true,
  376. discontinuityStarts: [],
  377. duration: 100,
  378. endList: true,
  379. mediaGroups: {
  380. AUDIO: {
  381. audio: {
  382. main: {
  383. autoselect: true,
  384. default: true,
  385. language: '',
  386. playlists: [{
  387. attributes: {
  388. BANDWIDTH: 20000,
  389. CODECS: 'foo;bar',
  390. NAME: '1',
  391. ['PROGRAM-ID']: 1
  392. },
  393. targetDuration: 2,
  394. mediaSequence: 0,
  395. discontinuitySequence: 0,
  396. discontinuityStarts: [],
  397. endList: true,
  398. resolvedUri: '',
  399. segments: [{
  400. uri: '',
  401. timeline: 0,
  402. duration: 2,
  403. resolvedUri: '',
  404. map: {
  405. uri: '',
  406. resolvedUri: ''
  407. },
  408. number: 0
  409. }, {
  410. uri: '',
  411. timeline: 0,
  412. duration: 2,
  413. resolvedUri: '',
  414. map: {
  415. uri: '',
  416. resolvedUri: ''
  417. },
  418. number: 1
  419. }],
  420. timeline: 0,
  421. timelineStarts: [{ start: 0, timeline: 0 }],
  422. uri: ''
  423. }, {
  424. attributes: {
  425. BANDWIDTH: 10000,
  426. CODECS: 'foo;bar',
  427. NAME: '2',
  428. ['PROGRAM-ID']: 1
  429. },
  430. targetDuration: 2,
  431. mediaSequence: 0,
  432. discontinuitySequence: 0,
  433. discontinuityStarts: [],
  434. endList: true,
  435. resolvedUri: '',
  436. segments: [{
  437. uri: '',
  438. timeline: 0,
  439. duration: 2,
  440. resolvedUri: '',
  441. map: {
  442. uri: '',
  443. resolvedUri: ''
  444. },
  445. number: 0
  446. }, {
  447. uri: '',
  448. timeline: 0,
  449. duration: 2,
  450. resolvedUri: '',
  451. map: {
  452. uri: '',
  453. resolvedUri: ''
  454. },
  455. number: 1
  456. }],
  457. timeline: 0,
  458. timelineStarts: [{ start: 0, timeline: 0 }],
  459. uri: ''
  460. }],
  461. uri: ''
  462. }
  463. }
  464. },
  465. ['CLOSED-CAPTIONS']: {},
  466. SUBTITLES: {
  467. subs: {
  468. text: {
  469. autoselect: false,
  470. default: false,
  471. language: 'text',
  472. playlists: [{
  473. attributes: {
  474. BANDWIDTH: 20000,
  475. NAME: '1',
  476. ['PROGRAM-ID']: 1
  477. },
  478. endList: true,
  479. targetDuration: 2,
  480. mediaSequence: 0,
  481. discontinuitySequence: 0,
  482. discontinuityStarts: [],
  483. resolvedUri: 'https://www.example.com/vtt',
  484. segments: [{
  485. uri: '',
  486. timeline: 0,
  487. duration: 2,
  488. resolvedUri: '',
  489. map: {
  490. uri: '',
  491. resolvedUri: ''
  492. },
  493. number: 0
  494. }, {
  495. uri: '',
  496. timeline: 0,
  497. duration: 2,
  498. resolvedUri: '',
  499. map: {
  500. uri: '',
  501. resolvedUri: ''
  502. },
  503. number: 1
  504. }],
  505. timeline: 0,
  506. timelineStarts: [{ start: 0, timeline: 0 }],
  507. uri: ''
  508. }, {
  509. attributes: {
  510. BANDWIDTH: 10000,
  511. NAME: '2',
  512. ['PROGRAM-ID']: 1
  513. },
  514. endList: true,
  515. targetDuration: 2,
  516. mediaSequence: 0,
  517. discontinuitySequence: 0,
  518. discontinuityStarts: [],
  519. resolvedUri: 'https://www.example.com/vtt',
  520. segments: [{
  521. uri: '',
  522. timeline: 0,
  523. duration: 2,
  524. resolvedUri: '',
  525. map: {
  526. uri: '',
  527. resolvedUri: ''
  528. },
  529. number: 0
  530. }, {
  531. uri: '',
  532. timeline: 0,
  533. duration: 2,
  534. resolvedUri: '',
  535. map: {
  536. uri: '',
  537. resolvedUri: ''
  538. },
  539. number: 1
  540. }],
  541. timeline: 0,
  542. timelineStarts: [{ start: 0, timeline: 0 }],
  543. uri: ''
  544. }],
  545. uri: ''
  546. }
  547. }
  548. },
  549. VIDEO: {}
  550. },
  551. playlists: [{
  552. attributes: {
  553. AUDIO: 'audio',
  554. SUBTITLES: 'subs',
  555. BANDWIDTH: 10000,
  556. CODECS: 'foo;bar',
  557. NAME: '1',
  558. ['PROGRAM-ID']: 1,
  559. RESOLUTION: {
  560. height: 600,
  561. width: 800
  562. }
  563. },
  564. endList: true,
  565. resolvedUri: '',
  566. mediaSequence: 0,
  567. discontinuitySequence: 0,
  568. discontinuityStarts: [],
  569. targetDuration: 2,
  570. segments: [{
  571. uri: '',
  572. timeline: 0,
  573. duration: 2,
  574. resolvedUri: '',
  575. map: {
  576. uri: '',
  577. resolvedUri: ''
  578. },
  579. number: 0
  580. }, {
  581. uri: '',
  582. timeline: 0,
  583. duration: 2,
  584. resolvedUri: '',
  585. map: {
  586. uri: '',
  587. resolvedUri: ''
  588. },
  589. number: 1
  590. }],
  591. timeline: 0,
  592. timelineStarts: [{ start: 0, timeline: 0 }],
  593. uri: ''
  594. }],
  595. segments: [],
  596. timelineStarts: [{ start: 0, timeline: 0 }],
  597. uri: ''
  598. };
  599. assert.deepEqual(toM3u8({ dashPlaylists }), expected);
  600. });
  601. QUnit.test('playlists with sidx and sidxMapping', function(assert) {
  602. const dashPlaylists = [{
  603. attributes: {
  604. sourceDuration: 100,
  605. id: '1',
  606. width: 800,
  607. height: 600,
  608. codecs: 'foo;bar',
  609. duration: 0,
  610. bandwidth: 10000,
  611. periodStart: 0,
  612. mimeType: 'video/mp4',
  613. type: 'static'
  614. },
  615. segments: [],
  616. sidx: {
  617. byterange: {
  618. offset: 10,
  619. length: 10
  620. },
  621. uri: 'sidx.mp4',
  622. resolvedUri: 'http://example.com/sidx.mp4',
  623. timeline: 0,
  624. duration: 10
  625. },
  626. uri: 'http://example.com/fmp4.mp4'
  627. }];
  628. const sidxMapping = {
  629. 'sidx.mp4-10-19': {
  630. sidx: {
  631. timescale: 1,
  632. firstOffset: 0,
  633. references: [{
  634. referenceType: 0,
  635. referencedSize: 5,
  636. subsegmentDuration: 2
  637. }]
  638. }
  639. }
  640. };
  641. const expected = [{
  642. attributes: {
  643. AUDIO: 'audio',
  644. SUBTITLES: 'subs',
  645. BANDWIDTH: 10000,
  646. CODECS: 'foo;bar',
  647. NAME: '1',
  648. ['PROGRAM-ID']: 1,
  649. RESOLUTION: {
  650. height: 600,
  651. width: 800
  652. }
  653. },
  654. sidx: {
  655. byterange: {
  656. offset: 10,
  657. length: 10
  658. },
  659. uri: 'sidx.mp4',
  660. resolvedUri: 'http://example.com/sidx.mp4',
  661. timeline: 0,
  662. duration: 10
  663. },
  664. targetDuration: 0,
  665. timeline: 0,
  666. timelineStarts: [{ start: 0, timeline: 0 }],
  667. uri: '',
  668. segments: [{
  669. map: {
  670. resolvedUri: 'http://example.com/sidx.mp4',
  671. uri: ''
  672. },
  673. byterange: {
  674. offset: 20,
  675. length: 5
  676. },
  677. uri: 'http://example.com/sidx.mp4',
  678. resolvedUri: 'http://example.com/sidx.mp4',
  679. duration: 2,
  680. number: 0,
  681. presentationTime: 0,
  682. timeline: 0
  683. }],
  684. endList: true,
  685. mediaSequence: 0,
  686. discontinuitySequence: 0,
  687. discontinuityStarts: [],
  688. resolvedUri: ''
  689. }];
  690. assert.deepEqual(toM3u8({ dashPlaylists, sidxMapping }).playlists, expected);
  691. });
  692. QUnit.test('playlists without minimumUpdatePeriod dont assign default value', function(assert) {
  693. const dashPlaylists = [{
  694. attributes: {
  695. sourceDuration: 100,
  696. id: '1',
  697. width: 800,
  698. height: 600,
  699. codecs: 'foo;bar',
  700. duration: 0,
  701. bandwidth: 10000,
  702. periodStart: 0,
  703. mimeType: 'video/mp4',
  704. type: 'static'
  705. },
  706. segments: [],
  707. sidx: {
  708. byterange: {
  709. offset: 10,
  710. length: 10
  711. },
  712. uri: 'sidx.mp4',
  713. resolvedUri: 'http://example.com/sidx.mp4',
  714. duration: 10
  715. },
  716. uri: 'http://example.com/fmp4.mp4'
  717. }];
  718. assert.equal(toM3u8({ dashPlaylists }).minimumUpdatePeriod, undefined);
  719. });
  720. QUnit.test('playlists with minimumUpdatePeriod = 0', function(assert) {
  721. const dashPlaylists = [{
  722. attributes: {
  723. sourceDuration: 100,
  724. id: '1',
  725. width: 800,
  726. height: 600,
  727. codecs: 'foo;bar',
  728. duration: 0,
  729. bandwidth: 10000,
  730. periodStart: 0,
  731. mimeType: 'video/mp4',
  732. type: 'static',
  733. minimumUpdatePeriod: 0
  734. },
  735. segments: [],
  736. sidx: {
  737. byterange: {
  738. offset: 10,
  739. length: 10
  740. },
  741. uri: 'sidx.mp4',
  742. resolvedUri: 'http://example.com/sidx.mp4',
  743. duration: 10
  744. },
  745. uri: 'http://example.com/fmp4.mp4'
  746. }];
  747. assert.equal(toM3u8({ dashPlaylists }).minimumUpdatePeriod, 0);
  748. });
  749. QUnit.test('playlists with integer value for minimumUpdatePeriod', function(assert) {
  750. const dashPlaylists = [{
  751. attributes: {
  752. sourceDuration: 100,
  753. id: '1',
  754. width: 800,
  755. height: 600,
  756. codecs: 'foo;bar',
  757. duration: 0,
  758. bandwidth: 10000,
  759. periodStart: 0,
  760. mimeType: 'video/mp4',
  761. type: 'static',
  762. minimumUpdatePeriod: 2
  763. },
  764. segments: [],
  765. sidx: {
  766. byterange: {
  767. offset: 10,
  768. length: 10
  769. },
  770. uri: 'sidx.mp4',
  771. resolvedUri: 'http://example.com/sidx.mp4',
  772. duration: 10
  773. },
  774. uri: 'http://example.com/fmp4.mp4'
  775. }];
  776. assert.equal(
  777. toM3u8({ dashPlaylists }).minimumUpdatePeriod,
  778. 2000,
  779. 'converts update period to ms'
  780. );
  781. });
  782. QUnit.test('no playlists', function(assert) {
  783. assert.deepEqual(toM3u8({ dashPlaylists: [] }), {});
  784. });
  785. QUnit.test('dynamic playlists with suggestedPresentationDelay', function(assert) {
  786. const dashPlaylists = [{
  787. attributes: {
  788. id: '1',
  789. codecs: 'foo;bar',
  790. sourceDuration: 100,
  791. duration: 0,
  792. bandwidth: 20000,
  793. periodStart: 0,
  794. mimeType: 'audio/mp4',
  795. type: 'dynamic',
  796. suggestedPresentationDelay: 18
  797. },
  798. segments: []
  799. }, {
  800. attributes: {
  801. id: '2',
  802. codecs: 'foo;bar',
  803. sourceDuration: 100,
  804. duration: 0,
  805. bandwidth: 10000,
  806. periodStart: 0,
  807. mimeType: 'audio/mp4',
  808. type: 'static'
  809. },
  810. segments: []
  811. }, {
  812. attributes: {
  813. sourceDuration: 100,
  814. id: '1',
  815. width: 800,
  816. height: 600,
  817. codecs: 'foo;bar',
  818. duration: 0,
  819. bandwidth: 10000,
  820. periodStart: 0,
  821. mimeType: 'video/mp4',
  822. type: 'static'
  823. },
  824. segments: []
  825. }, {
  826. attributes: {
  827. sourceDuration: 100,
  828. id: '1',
  829. bandwidth: 20000,
  830. periodStart: 0,
  831. mimeType: 'text/vtt',
  832. type: 'static',
  833. baseUrl: 'https://www.example.com/vtt'
  834. }
  835. }, {
  836. attributes: {
  837. sourceDuration: 100,
  838. id: '1',
  839. bandwidth: 10000,
  840. periodStart: 0,
  841. mimeType: 'text/vtt',
  842. type: 'static',
  843. baseUrl: 'https://www.example.com/vtt'
  844. }
  845. }];
  846. const output = toM3u8({ dashPlaylists });
  847. assert.ok('suggestedPresentationDelay' in output);
  848. assert.deepEqual(output.suggestedPresentationDelay, 18);
  849. });
  850. QUnit.test('playlists with label', function(assert) {
  851. const label = 'English with commentary';
  852. const dashPlaylists = [{
  853. attributes: {
  854. id: '1',
  855. codecs: 'foo;bar',
  856. sourceDuration: 100,
  857. duration: 0,
  858. bandwidth: 20000,
  859. periodStart: 0,
  860. mimeType: 'audio/mp4',
  861. type: 'dynamic',
  862. label
  863. },
  864. segments: []
  865. }, {
  866. attributes: {
  867. id: '2',
  868. codecs: 'foo;bar',
  869. sourceDuration: 100,
  870. duration: 0,
  871. bandwidth: 10000,
  872. periodStart: 0,
  873. mimeType: 'audio/mp4',
  874. type: 'static'
  875. },
  876. segments: []
  877. }, {
  878. attributes: {
  879. sourceDuration: 100,
  880. id: '1',
  881. width: 800,
  882. height: 600,
  883. codecs: 'foo;bar',
  884. duration: 0,
  885. bandwidth: 10000,
  886. periodStart: 0,
  887. mimeType: 'video/mp4',
  888. type: 'static'
  889. },
  890. segments: []
  891. }, {
  892. attributes: {
  893. sourceDuration: 100,
  894. id: '1',
  895. width: 800,
  896. height: 600,
  897. codecs: 'foo;bar',
  898. duration: 0,
  899. bandwidth: 10000,
  900. periodStart: 0,
  901. mimeType: 'text/vtt',
  902. type: 'static',
  903. label
  904. },
  905. segments: []
  906. }];
  907. const output = toM3u8({ dashPlaylists });
  908. assert.ok(label in output.mediaGroups.AUDIO.audio, 'label exists');
  909. assert.ok(label in output.mediaGroups.SUBTITLES.subs, 'label exists');
  910. });
  911. QUnit.test('608 captions', function(assert) {
  912. const dashPlaylists = [{
  913. attributes: {
  914. captionServices: [{
  915. channel: 'CC1',
  916. language: 'CC1'
  917. }, {
  918. channel: 'CC2',
  919. language: 'CC2'
  920. }, {
  921. channel: undefined,
  922. language: 'English'
  923. }, {
  924. channel: 'CC4',
  925. language: 'eng'
  926. }],
  927. id: '1',
  928. codecs: 'foo;bar',
  929. sourceDuration: 100,
  930. duration: 0,
  931. bandwidth: 20000,
  932. periodStart: 0,
  933. mimeType: 'audio/mp4',
  934. type: 'dynamic'
  935. },
  936. segments: []
  937. }, {
  938. attributes: {
  939. id: '2',
  940. codecs: 'foo;bar',
  941. sourceDuration: 100,
  942. duration: 0,
  943. bandwidth: 10000,
  944. periodStart: 0,
  945. mimeType: 'audio/mp4',
  946. type: 'static'
  947. },
  948. segments: []
  949. }, {
  950. attributes: {
  951. sourceDuration: 100,
  952. id: '1',
  953. width: 800,
  954. height: 600,
  955. codecs: 'foo;bar',
  956. duration: 0,
  957. bandwidth: 10000,
  958. periodStart: 0,
  959. mimeType: 'video/mp4',
  960. type: 'static'
  961. },
  962. segments: []
  963. }];
  964. const output = toM3u8({ dashPlaylists });
  965. const cc = output.mediaGroups['CLOSED-CAPTIONS'].cc;
  966. Object.keys(cc).forEach((key) => {
  967. assert.notOk(cc[key].autoselect, 'no autoselect');
  968. assert.notOk(cc[key].default, 'no default');
  969. });
  970. assert.deepEqual(Object.keys(cc), ['CC1', 'CC2', 'English', 'eng'], 'we have 4 channels');
  971. assert.equal(cc.CC1.instreamId, 'CC1', 'CC1 has an instreamId of CC1');
  972. assert.equal(cc.CC2.instreamId, 'CC2', 'CC2 has an instreamId of CC1');
  973. assert.equal(cc.English.instreamId, undefined, 'English captions dont have an instreamId');
  974. assert.equal(cc.eng.instreamId, 'CC4', 'eng captions have an instreamId of CC4');
  975. });
  976. QUnit.module('generateSidxKey');
  977. QUnit.test('generates correct key', function(assert) {
  978. const sidxInfo = {
  979. byterange: {
  980. offset: 1,
  981. length: 5
  982. },
  983. uri: 'uri'
  984. };
  985. assert.strictEqual(
  986. generateSidxKey(sidxInfo),
  987. 'uri-1-5',
  988. 'the key byterange should have a inclusive end'
  989. );
  990. });
  991. QUnit.module('addMediaSequenceValues');
  992. QUnit.test('resets media sequence values', function(assert) {
  993. const playlists = [{
  994. timeline: 17,
  995. mediaSequence: 2,
  996. discontinuitySequence: 3,
  997. segments: [{
  998. number: 5,
  999. presentationTime: 17,
  1000. timeline: 17
  1001. }, {
  1002. number: 6,
  1003. presentationTime: 19,
  1004. timeline: 17
  1005. }, {
  1006. number: 7,
  1007. presentationTime: 21,
  1008. timeline: 21
  1009. }]
  1010. }, {
  1011. timeline: 21,
  1012. mediaSequence: 2,
  1013. discontinuitySequence: 3,
  1014. segments: [{
  1015. number: 1,
  1016. presentationTime: 21,
  1017. timeline: 21
  1018. }]
  1019. }, {
  1020. timeline: 17,
  1021. mediaSequence: 2,
  1022. discontinuitySequence: 2,
  1023. segments: []
  1024. }];
  1025. const timelineStarts = [{
  1026. timeline: 17,
  1027. start: 17
  1028. }, {
  1029. timeline: 21,
  1030. start: 21
  1031. }];
  1032. addMediaSequenceValues(playlists, timelineStarts);
  1033. assert.deepEqual(
  1034. playlists,
  1035. [{
  1036. timeline: 17,
  1037. mediaSequence: 0,
  1038. discontinuitySequence: 0,
  1039. segments: [{
  1040. number: 0,
  1041. presentationTime: 17,
  1042. timeline: 17
  1043. }, {
  1044. number: 1,
  1045. presentationTime: 19,
  1046. timeline: 17
  1047. }, {
  1048. number: 2,
  1049. presentationTime: 21,
  1050. timeline: 21
  1051. }]
  1052. }, {
  1053. timeline: 21,
  1054. mediaSequence: 0,
  1055. discontinuitySequence: 1,
  1056. segments: [{
  1057. number: 0,
  1058. presentationTime: 21,
  1059. timeline: 21
  1060. }]
  1061. }, {
  1062. timeline: 17,
  1063. mediaSequence: 0,
  1064. discontinuitySequence: 0,
  1065. segments: []
  1066. }],
  1067. 'updated media sequence values'
  1068. );
  1069. });
  1070. QUnit.module('flattenMediaGroupPlaylists');
  1071. QUnit.test('includes all media group playlists', function(assert) {
  1072. assert.deepEqual(
  1073. flattenMediaGroupPlaylists({
  1074. en: {
  1075. playlists: [
  1076. { attributes: { NAME: 'A' } },
  1077. { attributes: { NAME: 'B' } }
  1078. ]
  1079. },
  1080. es: {
  1081. playlists: [
  1082. { attributes: { NAME: 'C' } },
  1083. { attributes: { NAME: 'D' } }
  1084. ]
  1085. }
  1086. }),
  1087. [
  1088. { attributes: { NAME: 'A' } },
  1089. { attributes: { NAME: 'B' } },
  1090. { attributes: { NAME: 'C' } },
  1091. { attributes: { NAME: 'D' } }
  1092. ],
  1093. 'included all media group playlists'
  1094. );
  1095. });
  1096. QUnit.module('eventStream');
  1097. QUnit.test('eventStreams with playlists', function(assert) {
  1098. const dashPlaylists = [{
  1099. attributes: {
  1100. id: '1',
  1101. codecs: 'foo;bar',
  1102. sourceDuration: 100,
  1103. duration: 0,
  1104. bandwidth: 20000,
  1105. periodStart: 0,
  1106. mimeType: 'audio/mp4',
  1107. type: 'static'
  1108. },
  1109. segments: []
  1110. }, {
  1111. attributes: {
  1112. id: '2',
  1113. codecs: 'foo;bar',
  1114. sourceDuration: 100,
  1115. duration: 0,
  1116. bandwidth: 10000,
  1117. periodStart: 0,
  1118. mimeType: 'audio/mp4',
  1119. type: 'static'
  1120. },
  1121. segments: []
  1122. }, {
  1123. attributes: {
  1124. sourceDuration: 100,
  1125. id: '1',
  1126. width: 800,
  1127. height: 600,
  1128. codecs: 'foo;bar',
  1129. duration: 0,
  1130. bandwidth: 10000,
  1131. frameRate: 30,
  1132. periodStart: 0,
  1133. mimeType: 'video/mp4',
  1134. type: 'static'
  1135. },
  1136. segments: []
  1137. }, {
  1138. attributes: {
  1139. sourceDuration: 100,
  1140. id: '1',
  1141. bandwidth: 20000,
  1142. periodStart: 0,
  1143. mimeType: 'text/vtt',
  1144. type: 'static',
  1145. baseUrl: 'https://www.example.com/vtt'
  1146. }
  1147. }, {
  1148. attributes: {
  1149. sourceDuration: 100,
  1150. id: '2',
  1151. bandwidth: 10000,
  1152. periodStart: 0,
  1153. mimeType: 'text/vtt',
  1154. type: 'static',
  1155. baseUrl: 'https://www.example.com/vtt'
  1156. }
  1157. }];
  1158. const eventStream = [
  1159. {
  1160. end: 1,
  1161. id: 'one',
  1162. messageData: 'foo',
  1163. schemeIdUri: 'urn:foo.bar.2023',
  1164. start: 1,
  1165. value: 'bar'
  1166. },
  1167. {
  1168. end: 2,
  1169. id: 'two',
  1170. messageData: 'bar',
  1171. schemeIdUri: 'urn:foo.bar.2023',
  1172. start: 2,
  1173. value: 'foo'
  1174. },
  1175. {
  1176. end: 3,
  1177. id: 'three',
  1178. messageData: 'foo_bar',
  1179. schemeIdUri: 'urn:foo.bar.2023',
  1180. start: 3,
  1181. value: 'bar_foo'
  1182. }
  1183. ];
  1184. const expected = {
  1185. allowCache: true,
  1186. discontinuityStarts: [],
  1187. timelineStarts: [{ start: 0, timeline: 0 }],
  1188. duration: 100,
  1189. endList: true,
  1190. eventStream: [
  1191. {
  1192. end: 1,
  1193. id: 'one',
  1194. messageData: 'foo',
  1195. schemeIdUri: 'urn:foo.bar.2023',
  1196. start: 1,
  1197. value: 'bar'
  1198. },
  1199. {
  1200. end: 2,
  1201. id: 'two',
  1202. messageData: 'bar',
  1203. schemeIdUri: 'urn:foo.bar.2023',
  1204. start: 2,
  1205. value: 'foo'
  1206. },
  1207. {
  1208. end: 3,
  1209. id: 'three',
  1210. messageData: 'foo_bar',
  1211. schemeIdUri: 'urn:foo.bar.2023',
  1212. start: 3,
  1213. value: 'bar_foo'
  1214. }
  1215. ],
  1216. mediaGroups: {
  1217. AUDIO: {
  1218. audio: {
  1219. main: {
  1220. autoselect: true,
  1221. default: true,
  1222. language: '',
  1223. playlists: [{
  1224. attributes: {
  1225. BANDWIDTH: 20000,
  1226. CODECS: 'foo;bar',
  1227. NAME: '1',
  1228. ['PROGRAM-ID']: 1
  1229. },
  1230. mediaSequence: 0,
  1231. discontinuitySequence: 0,
  1232. discontinuityStarts: [],
  1233. timelineStarts: [{ start: 0, timeline: 0 }],
  1234. endList: true,
  1235. resolvedUri: '',
  1236. segments: [],
  1237. timeline: 0,
  1238. uri: '',
  1239. targetDuration: 0
  1240. }, {
  1241. attributes: {
  1242. BANDWIDTH: 10000,
  1243. CODECS: 'foo;bar',
  1244. NAME: '2',
  1245. ['PROGRAM-ID']: 1
  1246. },
  1247. mediaSequence: 0,
  1248. discontinuitySequence: 0,
  1249. discontinuityStarts: [],
  1250. timelineStarts: [{ start: 0, timeline: 0 }],
  1251. endList: true,
  1252. resolvedUri: '',
  1253. segments: [],
  1254. timeline: 0,
  1255. uri: '',
  1256. targetDuration: 0
  1257. }],
  1258. uri: ''
  1259. }
  1260. }
  1261. },
  1262. ['CLOSED-CAPTIONS']: {},
  1263. SUBTITLES: {
  1264. subs: {
  1265. text: {
  1266. autoselect: false,
  1267. default: false,
  1268. language: 'text',
  1269. playlists: [{
  1270. attributes: {
  1271. BANDWIDTH: 20000,
  1272. NAME: '1',
  1273. ['PROGRAM-ID']: 1
  1274. },
  1275. mediaSequence: 0,
  1276. discontinuitySequence: 0,
  1277. discontinuityStarts: [],
  1278. timelineStarts: [{ start: 0, timeline: 0 }],
  1279. targetDuration: 100,
  1280. endList: true,
  1281. resolvedUri: 'https://www.example.com/vtt',
  1282. segments: [{
  1283. duration: 100,
  1284. resolvedUri: 'https://www.example.com/vtt',
  1285. timeline: 0,
  1286. uri: 'https://www.example.com/vtt',
  1287. number: 0
  1288. }],
  1289. timeline: 0,
  1290. uri: ''
  1291. }, {
  1292. attributes: {
  1293. BANDWIDTH: 10000,
  1294. NAME: '2',
  1295. ['PROGRAM-ID']: 1
  1296. },
  1297. mediaSequence: 0,
  1298. discontinuitySequence: 0,
  1299. discontinuityStarts: [],
  1300. timelineStarts: [{ start: 0, timeline: 0 }],
  1301. targetDuration: 100,
  1302. endList: true,
  1303. resolvedUri: 'https://www.example.com/vtt',
  1304. segments: [{
  1305. duration: 100,
  1306. resolvedUri: 'https://www.example.com/vtt',
  1307. timeline: 0,
  1308. uri: 'https://www.example.com/vtt',
  1309. number: 0
  1310. }],
  1311. timeline: 0,
  1312. uri: ''
  1313. }],
  1314. uri: ''
  1315. }
  1316. }
  1317. },
  1318. VIDEO: {}
  1319. },
  1320. playlists: [{
  1321. attributes: {
  1322. AUDIO: 'audio',
  1323. SUBTITLES: 'subs',
  1324. BANDWIDTH: 10000,
  1325. CODECS: 'foo;bar',
  1326. NAME: '1',
  1327. ['FRAME-RATE']: 30,
  1328. ['PROGRAM-ID']: 1,
  1329. RESOLUTION: {
  1330. height: 600,
  1331. width: 800
  1332. }
  1333. },
  1334. endList: true,
  1335. mediaSequence: 0,
  1336. discontinuitySequence: 0,
  1337. discontinuityStarts: [],
  1338. timelineStarts: [{ start: 0, timeline: 0 }],
  1339. targetDuration: 0,
  1340. resolvedUri: '',
  1341. segments: [],
  1342. timeline: 0,
  1343. uri: ''
  1344. }],
  1345. segments: [],
  1346. uri: ''
  1347. };
  1348. assert.deepEqual(toM3u8({ dashPlaylists, eventStream }), expected);
  1349. });