toM3u8.test.js 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978
  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 content steering and resolvable URLs', function(assert) {
  211. const contentSteering = {
  212. defaultServiceLocation: 'beta',
  213. proxyServerURL: 'http://127.0.0.1:3455/steer',
  214. queryBeforeStart: false,
  215. serverURL: 'https://example.com/app/url'
  216. };
  217. const dashPlaylists = [
  218. {
  219. attributes: {
  220. bandwidth: 5000000,
  221. baseUrl: 'https://cdn1.example.com/video',
  222. clientOffset: 0,
  223. codecs: 'avc1.64001e',
  224. duration: 0,
  225. height: 404,
  226. id: 'test',
  227. mimeType: 'video/mp4',
  228. periodStart: 0,
  229. role: {
  230. value: 'main'
  231. },
  232. serviceLocation: 'alpha',
  233. sourceDuration: 0,
  234. type: 'dyanmic',
  235. width: 720
  236. },
  237. segments: [
  238. {
  239. duration: 0,
  240. map: {
  241. resolvedUri: 'https://cdn1.example.com/video',
  242. uri: ''
  243. },
  244. number: 1,
  245. presentationTime: 0,
  246. resolvedUri: 'https://cdn1.example.com/video',
  247. timeline: 0,
  248. uri: ''
  249. }
  250. ]
  251. },
  252. {
  253. attributes: {
  254. bandwidth: 5000000,
  255. baseUrl: 'https://cdn2.example.com/video',
  256. clientOffset: 0,
  257. codecs: 'avc1.64001e',
  258. duration: 0,
  259. height: 404,
  260. id: 'test',
  261. mimeType: 'video/mp4',
  262. periodStart: 0,
  263. role: {
  264. value: 'main'
  265. },
  266. serviceLocation: 'beta',
  267. sourceDuration: 0,
  268. type: 'dyanmic',
  269. width: 720
  270. },
  271. segments: [
  272. {
  273. duration: 0,
  274. map: {
  275. resolvedUri: 'https://cdn2.example.com/video',
  276. uri: ''
  277. },
  278. number: 1,
  279. presentationTime: 0,
  280. resolvedUri: 'https://cdn2.example.com/video',
  281. timeline: 0,
  282. uri: ''
  283. }
  284. ]
  285. },
  286. {
  287. attributes: {
  288. bandwidth: 256,
  289. baseUrl: 'https://cdn1.example.com/vtt',
  290. clientOffset: 0,
  291. duration: 0,
  292. id: 'en',
  293. lang: 'en',
  294. mimeType: 'text/vtt',
  295. periodStart: 0,
  296. role: {},
  297. serviceLocation: 'alpha',
  298. sourceDuration: 0,
  299. type: 'dyanmic'
  300. },
  301. segments: [
  302. {
  303. duration: 0,
  304. map: {
  305. resolvedUri: 'https://cdn1.example.com/vtt',
  306. uri: ''
  307. },
  308. number: 1,
  309. presentationTime: 0,
  310. resolvedUri: 'https://cdn1.example.com/vtt',
  311. timeline: 0,
  312. uri: ''
  313. }
  314. ]
  315. },
  316. {
  317. attributes: {
  318. bandwidth: 256,
  319. baseUrl: 'https://cdn2.example.com/vtt',
  320. clientOffset: 0,
  321. id: 'en',
  322. lang: 'en',
  323. mimeType: 'text/vtt',
  324. periodStart: 0,
  325. role: {},
  326. serviceLocation: 'beta',
  327. sourceDuration: 0,
  328. type: 'dyanmic'
  329. }
  330. }
  331. ];
  332. const expected = {
  333. allowCache: true,
  334. contentSteering: {
  335. defaultServiceLocation: 'beta',
  336. proxyServerURL: 'http://127.0.0.1:3455/steer',
  337. queryBeforeStart: false,
  338. serverURL: 'https://example.com/app/url'
  339. },
  340. discontinuityStarts: [],
  341. duration: 0,
  342. endList: true,
  343. mediaGroups: {
  344. AUDIO: {},
  345. ['CLOSED-CAPTIONS']: {},
  346. SUBTITLES: {
  347. subs: {
  348. en: {
  349. autoselect: false,
  350. default: false,
  351. language: 'en',
  352. playlists: [
  353. {
  354. attributes: {
  355. BANDWIDTH: 256,
  356. NAME: 'en',
  357. ['PROGRAM-ID']: 1,
  358. serviceLocation: 'alpha'
  359. },
  360. discontinuitySequence: 0,
  361. discontinuityStarts: [],
  362. endList: false,
  363. mediaSequence: 0,
  364. resolvedUri: 'https://cdn1.example.com/vtt',
  365. segments: [
  366. {
  367. duration: 0,
  368. map: {
  369. resolvedUri: 'https://cdn1.example.com/vtt',
  370. uri: ''
  371. },
  372. number: 0,
  373. presentationTime: 0,
  374. resolvedUri: 'https://cdn1.example.com/vtt',
  375. timeline: 0,
  376. uri: ''
  377. }
  378. ],
  379. targetDuration: 0,
  380. timeline: 0,
  381. timelineStarts: [
  382. {
  383. start: 0,
  384. timeline: 0
  385. }
  386. ],
  387. uri: ''
  388. },
  389. {
  390. attributes: {
  391. BANDWIDTH: 256,
  392. NAME: 'en',
  393. ['PROGRAM-ID']: 1,
  394. serviceLocation: 'beta'
  395. },
  396. discontinuitySequence: 0,
  397. discontinuityStarts: [],
  398. endList: false,
  399. mediaSequence: 0,
  400. resolvedUri: 'https://cdn2.example.com/vtt',
  401. segments: [
  402. {
  403. duration: 0,
  404. number: 0,
  405. resolvedUri: 'https://cdn2.example.com/vtt',
  406. timeline: 0,
  407. uri: 'https://cdn2.example.com/vtt'
  408. }
  409. ],
  410. targetDuration: 0,
  411. timeline: 0,
  412. timelineStarts: [
  413. {
  414. start: 0,
  415. timeline: 0
  416. }
  417. ],
  418. uri: ''
  419. }
  420. ],
  421. uri: ''
  422. }
  423. }
  424. },
  425. VIDEO: {}
  426. },
  427. playlists: [
  428. {
  429. attributes: {
  430. AUDIO: 'audio',
  431. BANDWIDTH: 5000000,
  432. CODECS: 'avc1.64001e',
  433. NAME: 'test',
  434. ['PROGRAM-ID']: 1,
  435. RESOLUTION: {
  436. height: 404,
  437. width: 720
  438. },
  439. SUBTITLES: 'subs',
  440. serviceLocation: 'alpha'
  441. },
  442. discontinuitySequence: 0,
  443. discontinuityStarts: [],
  444. endList: false,
  445. mediaSequence: 0,
  446. resolvedUri: 'https://cdn1.example.com/video',
  447. segments: [
  448. {
  449. duration: 0,
  450. map: {
  451. resolvedUri: 'https://cdn1.example.com/video',
  452. uri: ''
  453. },
  454. number: 0,
  455. presentationTime: 0,
  456. resolvedUri: 'https://cdn1.example.com/video',
  457. timeline: 0,
  458. uri: ''
  459. }
  460. ],
  461. targetDuration: 0,
  462. timeline: 0,
  463. timelineStarts: [
  464. {
  465. start: 0,
  466. timeline: 0
  467. }
  468. ],
  469. uri: ''
  470. },
  471. {
  472. attributes: {
  473. AUDIO: 'audio',
  474. BANDWIDTH: 5000000,
  475. CODECS: 'avc1.64001e',
  476. NAME: 'test',
  477. ['PROGRAM-ID']: 1,
  478. RESOLUTION: {
  479. height: 404,
  480. width: 720
  481. },
  482. SUBTITLES: 'subs',
  483. serviceLocation: 'beta'
  484. },
  485. discontinuitySequence: 0,
  486. discontinuityStarts: [],
  487. endList: false,
  488. mediaSequence: 0,
  489. resolvedUri: 'https://cdn2.example.com/video',
  490. segments: [
  491. {
  492. duration: 0,
  493. map: {
  494. resolvedUri: 'https://cdn2.example.com/video',
  495. uri: ''
  496. },
  497. number: 0,
  498. presentationTime: 0,
  499. resolvedUri: 'https://cdn2.example.com/video',
  500. timeline: 0,
  501. uri: ''
  502. }
  503. ],
  504. targetDuration: 0,
  505. timeline: 0,
  506. timelineStarts: [
  507. {
  508. start: 0,
  509. timeline: 0
  510. }
  511. ],
  512. uri: ''
  513. }
  514. ],
  515. segments: [],
  516. timelineStarts: [
  517. {
  518. start: 0,
  519. timeline: 0
  520. }
  521. ],
  522. uri: ''
  523. };
  524. assert.deepEqual(toM3u8({ dashPlaylists, contentSteering }), expected);
  525. });
  526. QUnit.test('playlists with content steering', function(assert) {
  527. const contentSteering = {
  528. defaultServiceLocation: 'beta',
  529. proxyServerURL: 'http://127.0.0.1:3455/steer',
  530. queryBeforeStart: false,
  531. serverURL: 'https://example.com/app/url'
  532. };
  533. const dashPlaylists = [{
  534. attributes: {
  535. bandwidth: 5000000,
  536. baseUrl: 'https://cdn1.example.com/',
  537. clientOffset: 0,
  538. codecs: 'avc1.64001e',
  539. duration: 0,
  540. height: 404,
  541. id: 'test',
  542. mimeType: 'video/mp4',
  543. periodStart: 0,
  544. role: {
  545. value: 'main'
  546. },
  547. serviceLocation: 'alpha',
  548. sourceDuration: 0,
  549. type: 'dyanmic',
  550. width: 720
  551. },
  552. segments: [
  553. {
  554. duration: 0,
  555. map: {
  556. resolvedUri: 'https://cdn1.example.com/',
  557. uri: ''
  558. },
  559. number: 1,
  560. presentationTime: 0,
  561. resolvedUri: 'https://cdn1.example.com/',
  562. timeline: 0,
  563. uri: ''
  564. }
  565. ]
  566. }, {
  567. attributes: {
  568. bandwidth: 5000000,
  569. baseUrl: 'https://cdn2.example.com/',
  570. clientOffset: 0,
  571. codecs: 'avc1.64001e',
  572. duration: 0,
  573. height: 404,
  574. id: 'test',
  575. mimeType: 'video/mp4',
  576. periodStart: 0,
  577. role: {
  578. value: 'main'
  579. },
  580. serviceLocation: 'beta',
  581. sourceDuration: 0,
  582. type: 'dyanmic',
  583. width: 720
  584. },
  585. segments: [
  586. {
  587. duration: 0,
  588. map: {
  589. resolvedUri: 'https://cdn2.example.com/',
  590. uri: ''
  591. },
  592. number: 1,
  593. presentationTime: 0,
  594. resolvedUri: 'https://cdn2.example.com/',
  595. timeline: 0,
  596. uri: ''
  597. }
  598. ]
  599. }, {
  600. attributes: {
  601. bandwidth: 256,
  602. baseUrl: 'https://example.com/en.vtt',
  603. clientOffset: 0,
  604. id: 'en',
  605. lang: 'en',
  606. mimeType: 'text/vtt',
  607. periodStart: 0,
  608. role: {},
  609. sourceDuration: 0,
  610. type: 'dyanmic'
  611. }
  612. }, {
  613. attributes: {
  614. bandwidth: 256,
  615. baseUrl: 'https://example.com/en.vtt',
  616. clientOffset: 0,
  617. id: 'en',
  618. lang: 'en',
  619. mimeType: 'text/vtt',
  620. periodStart: 0,
  621. role: {},
  622. sourceDuration: 0,
  623. type: 'dyanmic'
  624. }
  625. }];
  626. const expected = {
  627. allowCache: true,
  628. contentSteering: {
  629. defaultServiceLocation: 'beta',
  630. proxyServerURL: 'http://127.0.0.1:3455/steer',
  631. queryBeforeStart: false,
  632. serverURL: 'https://example.com/app/url'
  633. },
  634. discontinuityStarts: [],
  635. duration: 0,
  636. endList: true,
  637. mediaGroups: {
  638. AUDIO: {},
  639. ['CLOSED-CAPTIONS']: {},
  640. SUBTITLES: {
  641. subs: {
  642. en: {
  643. autoselect: false,
  644. default: false,
  645. language: 'en',
  646. playlists: [
  647. {
  648. attributes: {
  649. BANDWIDTH: 256,
  650. NAME: 'en',
  651. ['PROGRAM-ID']: 1
  652. },
  653. discontinuitySequence: 0,
  654. discontinuityStarts: [],
  655. endList: false,
  656. mediaSequence: 0,
  657. resolvedUri: 'https://example.com/en.vtt',
  658. segments: [
  659. {
  660. duration: 0,
  661. number: 0,
  662. resolvedUri: 'https://example.com/en.vtt',
  663. timeline: 0,
  664. uri: 'https://example.com/en.vtt'
  665. }
  666. ],
  667. targetDuration: 0,
  668. timeline: 0,
  669. timelineStarts: [
  670. {
  671. start: 0,
  672. timeline: 0
  673. },
  674. {
  675. start: 0,
  676. timeline: 0
  677. }
  678. ],
  679. uri: ''
  680. }
  681. ],
  682. uri: ''
  683. }
  684. }
  685. },
  686. VIDEO: {}
  687. },
  688. playlists: [
  689. {
  690. attributes: {
  691. AUDIO: 'audio',
  692. BANDWIDTH: 5000000,
  693. CODECS: 'avc1.64001e',
  694. NAME: 'test',
  695. ['PROGRAM-ID']: 1,
  696. RESOLUTION: {
  697. height: 404,
  698. width: 720
  699. },
  700. SUBTITLES: 'subs',
  701. serviceLocation: 'alpha'
  702. },
  703. discontinuitySequence: 0,
  704. discontinuityStarts: [],
  705. endList: false,
  706. mediaSequence: 0,
  707. resolvedUri: 'https://cdn1.example.com/',
  708. segments: [
  709. {
  710. duration: 0,
  711. map: {
  712. resolvedUri: 'https://cdn1.example.com/',
  713. uri: ''
  714. },
  715. number: 0,
  716. presentationTime: 0,
  717. resolvedUri: 'https://cdn1.example.com/',
  718. timeline: 0,
  719. uri: ''
  720. }
  721. ],
  722. targetDuration: 0,
  723. timeline: 0,
  724. timelineStarts: [
  725. {
  726. start: 0,
  727. timeline: 0
  728. }
  729. ],
  730. uri: ''
  731. },
  732. {
  733. attributes: {
  734. AUDIO: 'audio',
  735. BANDWIDTH: 5000000,
  736. CODECS: 'avc1.64001e',
  737. NAME: 'test',
  738. ['PROGRAM-ID']: 1,
  739. RESOLUTION: {
  740. height: 404,
  741. width: 720
  742. },
  743. SUBTITLES: 'subs',
  744. serviceLocation: 'beta'
  745. },
  746. discontinuitySequence: 0,
  747. discontinuityStarts: [],
  748. endList: false,
  749. mediaSequence: 0,
  750. resolvedUri: 'https://cdn2.example.com/',
  751. segments: [
  752. {
  753. duration: 0,
  754. map: {
  755. resolvedUri: 'https://cdn2.example.com/',
  756. uri: ''
  757. },
  758. number: 0,
  759. presentationTime: 0,
  760. resolvedUri: 'https://cdn2.example.com/',
  761. timeline: 0,
  762. uri: ''
  763. }
  764. ],
  765. targetDuration: 0,
  766. timeline: 0,
  767. timelineStarts: [
  768. {
  769. start: 0,
  770. timeline: 0
  771. }
  772. ],
  773. uri: ''
  774. }
  775. ],
  776. segments: [],
  777. timelineStarts: [
  778. {
  779. start: 0,
  780. timeline: 0
  781. }
  782. ],
  783. uri: ''
  784. };
  785. assert.deepEqual(toM3u8({ dashPlaylists, contentSteering }), expected);
  786. });
  787. QUnit.test('playlists with segments', function(assert) {
  788. const dashPlaylists = [{
  789. attributes: {
  790. id: '1',
  791. codecs: 'foo;bar',
  792. duration: 2,
  793. sourceDuration: 100,
  794. bandwidth: 20000,
  795. periodStart: 0,
  796. mimeType: 'audio/mp4',
  797. type: 'static'
  798. },
  799. segments: [{
  800. uri: '',
  801. timeline: 0,
  802. duration: 2,
  803. resolvedUri: '',
  804. map: {
  805. uri: '',
  806. resolvedUri: ''
  807. },
  808. number: 0
  809. }, {
  810. uri: '',
  811. timeline: 0,
  812. duration: 2,
  813. resolvedUri: '',
  814. map: {
  815. uri: '',
  816. resolvedUri: ''
  817. },
  818. number: 1
  819. }]
  820. }, {
  821. attributes: {
  822. id: '2',
  823. codecs: 'foo;bar',
  824. sourceDuration: 100,
  825. duration: 2,
  826. bandwidth: 10000,
  827. periodStart: 0,
  828. mimeType: 'audio/mp4',
  829. type: 'static'
  830. },
  831. segments: [{
  832. uri: '',
  833. timeline: 0,
  834. duration: 2,
  835. resolvedUri: '',
  836. map: {
  837. uri: '',
  838. resolvedUri: ''
  839. },
  840. number: 0
  841. }, {
  842. uri: '',
  843. timeline: 0,
  844. duration: 2,
  845. resolvedUri: '',
  846. map: {
  847. uri: '',
  848. resolvedUri: ''
  849. },
  850. number: 1
  851. }]
  852. }, {
  853. attributes: {
  854. sourceDuration: 100,
  855. id: '1',
  856. width: 800,
  857. duration: 2,
  858. height: 600,
  859. codecs: 'foo;bar',
  860. bandwidth: 10000,
  861. periodStart: 0,
  862. mimeType: 'video/mp4',
  863. type: 'static'
  864. },
  865. segments: [{
  866. uri: '',
  867. timeline: 0,
  868. duration: 2,
  869. resolvedUri: '',
  870. map: {
  871. uri: '',
  872. resolvedUri: ''
  873. },
  874. number: 0
  875. }, {
  876. uri: '',
  877. timeline: 0,
  878. duration: 2,
  879. resolvedUri: '',
  880. map: {
  881. uri: '',
  882. resolvedUri: ''
  883. },
  884. number: 1
  885. }]
  886. }, {
  887. attributes: {
  888. sourceDuration: 100,
  889. id: '1',
  890. duration: 2,
  891. bandwidth: 20000,
  892. periodStart: 0,
  893. mimeType: 'text/vtt',
  894. type: 'static',
  895. baseUrl: 'https://www.example.com/vtt'
  896. },
  897. segments: [{
  898. uri: '',
  899. timeline: 0,
  900. duration: 2,
  901. resolvedUri: '',
  902. map: {
  903. uri: '',
  904. resolvedUri: ''
  905. },
  906. number: 0
  907. }, {
  908. uri: '',
  909. timeline: 0,
  910. duration: 2,
  911. resolvedUri: '',
  912. map: {
  913. uri: '',
  914. resolvedUri: ''
  915. },
  916. number: 1
  917. }]
  918. }, {
  919. attributes: {
  920. sourceDuration: 100,
  921. duration: 2,
  922. id: '2',
  923. bandwidth: 10000,
  924. periodStart: 0,
  925. mimeType: 'text/vtt',
  926. type: 'static',
  927. baseUrl: 'https://www.example.com/vtt'
  928. },
  929. segments: [{
  930. uri: '',
  931. timeline: 0,
  932. duration: 2,
  933. resolvedUri: '',
  934. map: {
  935. uri: '',
  936. resolvedUri: ''
  937. },
  938. number: 0
  939. }, {
  940. uri: '',
  941. timeline: 0,
  942. duration: 2,
  943. resolvedUri: '',
  944. map: {
  945. uri: '',
  946. resolvedUri: ''
  947. },
  948. number: 1
  949. }]
  950. }];
  951. const expected = {
  952. allowCache: true,
  953. discontinuityStarts: [],
  954. duration: 100,
  955. endList: true,
  956. mediaGroups: {
  957. AUDIO: {
  958. audio: {
  959. main: {
  960. autoselect: true,
  961. default: true,
  962. language: '',
  963. playlists: [{
  964. attributes: {
  965. BANDWIDTH: 20000,
  966. CODECS: 'foo;bar',
  967. NAME: '1',
  968. ['PROGRAM-ID']: 1
  969. },
  970. targetDuration: 2,
  971. mediaSequence: 0,
  972. discontinuitySequence: 0,
  973. discontinuityStarts: [],
  974. endList: true,
  975. resolvedUri: '',
  976. segments: [{
  977. uri: '',
  978. timeline: 0,
  979. duration: 2,
  980. resolvedUri: '',
  981. map: {
  982. uri: '',
  983. resolvedUri: ''
  984. },
  985. number: 0
  986. }, {
  987. uri: '',
  988. timeline: 0,
  989. duration: 2,
  990. resolvedUri: '',
  991. map: {
  992. uri: '',
  993. resolvedUri: ''
  994. },
  995. number: 1
  996. }],
  997. timeline: 0,
  998. timelineStarts: [{ start: 0, timeline: 0 }],
  999. uri: ''
  1000. }, {
  1001. attributes: {
  1002. BANDWIDTH: 10000,
  1003. CODECS: 'foo;bar',
  1004. NAME: '2',
  1005. ['PROGRAM-ID']: 1
  1006. },
  1007. targetDuration: 2,
  1008. mediaSequence: 0,
  1009. discontinuitySequence: 0,
  1010. discontinuityStarts: [],
  1011. endList: true,
  1012. resolvedUri: '',
  1013. segments: [{
  1014. uri: '',
  1015. timeline: 0,
  1016. duration: 2,
  1017. resolvedUri: '',
  1018. map: {
  1019. uri: '',
  1020. resolvedUri: ''
  1021. },
  1022. number: 0
  1023. }, {
  1024. uri: '',
  1025. timeline: 0,
  1026. duration: 2,
  1027. resolvedUri: '',
  1028. map: {
  1029. uri: '',
  1030. resolvedUri: ''
  1031. },
  1032. number: 1
  1033. }],
  1034. timeline: 0,
  1035. timelineStarts: [{ start: 0, timeline: 0 }],
  1036. uri: ''
  1037. }],
  1038. uri: ''
  1039. }
  1040. }
  1041. },
  1042. ['CLOSED-CAPTIONS']: {},
  1043. SUBTITLES: {
  1044. subs: {
  1045. text: {
  1046. autoselect: false,
  1047. default: false,
  1048. language: 'text',
  1049. playlists: [{
  1050. attributes: {
  1051. BANDWIDTH: 20000,
  1052. NAME: '1',
  1053. ['PROGRAM-ID']: 1
  1054. },
  1055. endList: true,
  1056. targetDuration: 2,
  1057. mediaSequence: 0,
  1058. discontinuitySequence: 0,
  1059. discontinuityStarts: [],
  1060. resolvedUri: 'https://www.example.com/vtt',
  1061. segments: [{
  1062. uri: '',
  1063. timeline: 0,
  1064. duration: 2,
  1065. resolvedUri: '',
  1066. map: {
  1067. uri: '',
  1068. resolvedUri: ''
  1069. },
  1070. number: 0
  1071. }, {
  1072. uri: '',
  1073. timeline: 0,
  1074. duration: 2,
  1075. resolvedUri: '',
  1076. map: {
  1077. uri: '',
  1078. resolvedUri: ''
  1079. },
  1080. number: 1
  1081. }],
  1082. timeline: 0,
  1083. timelineStarts: [{ start: 0, timeline: 0 }],
  1084. uri: ''
  1085. }, {
  1086. attributes: {
  1087. BANDWIDTH: 10000,
  1088. NAME: '2',
  1089. ['PROGRAM-ID']: 1
  1090. },
  1091. endList: true,
  1092. targetDuration: 2,
  1093. mediaSequence: 0,
  1094. discontinuitySequence: 0,
  1095. discontinuityStarts: [],
  1096. resolvedUri: 'https://www.example.com/vtt',
  1097. segments: [{
  1098. uri: '',
  1099. timeline: 0,
  1100. duration: 2,
  1101. resolvedUri: '',
  1102. map: {
  1103. uri: '',
  1104. resolvedUri: ''
  1105. },
  1106. number: 0
  1107. }, {
  1108. uri: '',
  1109. timeline: 0,
  1110. duration: 2,
  1111. resolvedUri: '',
  1112. map: {
  1113. uri: '',
  1114. resolvedUri: ''
  1115. },
  1116. number: 1
  1117. }],
  1118. timeline: 0,
  1119. timelineStarts: [{ start: 0, timeline: 0 }],
  1120. uri: ''
  1121. }],
  1122. uri: ''
  1123. }
  1124. }
  1125. },
  1126. VIDEO: {}
  1127. },
  1128. playlists: [{
  1129. attributes: {
  1130. AUDIO: 'audio',
  1131. SUBTITLES: 'subs',
  1132. BANDWIDTH: 10000,
  1133. CODECS: 'foo;bar',
  1134. NAME: '1',
  1135. ['PROGRAM-ID']: 1,
  1136. RESOLUTION: {
  1137. height: 600,
  1138. width: 800
  1139. }
  1140. },
  1141. endList: true,
  1142. resolvedUri: '',
  1143. mediaSequence: 0,
  1144. discontinuitySequence: 0,
  1145. discontinuityStarts: [],
  1146. targetDuration: 2,
  1147. segments: [{
  1148. uri: '',
  1149. timeline: 0,
  1150. duration: 2,
  1151. resolvedUri: '',
  1152. map: {
  1153. uri: '',
  1154. resolvedUri: ''
  1155. },
  1156. number: 0
  1157. }, {
  1158. uri: '',
  1159. timeline: 0,
  1160. duration: 2,
  1161. resolvedUri: '',
  1162. map: {
  1163. uri: '',
  1164. resolvedUri: ''
  1165. },
  1166. number: 1
  1167. }],
  1168. timeline: 0,
  1169. timelineStarts: [{ start: 0, timeline: 0 }],
  1170. uri: ''
  1171. }],
  1172. segments: [],
  1173. timelineStarts: [{ start: 0, timeline: 0 }],
  1174. uri: ''
  1175. };
  1176. assert.deepEqual(toM3u8({ dashPlaylists }), expected);
  1177. });
  1178. QUnit.test('playlists with sidx and sidxMapping', function(assert) {
  1179. const dashPlaylists = [{
  1180. attributes: {
  1181. sourceDuration: 100,
  1182. id: '1',
  1183. width: 800,
  1184. height: 600,
  1185. codecs: 'foo;bar',
  1186. duration: 0,
  1187. bandwidth: 10000,
  1188. periodStart: 0,
  1189. mimeType: 'video/mp4',
  1190. type: 'static'
  1191. },
  1192. segments: [],
  1193. sidx: {
  1194. byterange: {
  1195. offset: 10,
  1196. length: 10
  1197. },
  1198. uri: 'sidx.mp4',
  1199. resolvedUri: 'http://example.com/sidx.mp4',
  1200. timeline: 0,
  1201. duration: 10
  1202. },
  1203. uri: 'http://example.com/fmp4.mp4'
  1204. }];
  1205. const sidxMapping = {
  1206. 'sidx.mp4-10-19': {
  1207. sidx: {
  1208. timescale: 1,
  1209. firstOffset: 0,
  1210. references: [{
  1211. referenceType: 0,
  1212. referencedSize: 5,
  1213. subsegmentDuration: 2
  1214. }]
  1215. }
  1216. }
  1217. };
  1218. const expected = [{
  1219. attributes: {
  1220. AUDIO: 'audio',
  1221. SUBTITLES: 'subs',
  1222. BANDWIDTH: 10000,
  1223. CODECS: 'foo;bar',
  1224. NAME: '1',
  1225. ['PROGRAM-ID']: 1,
  1226. RESOLUTION: {
  1227. height: 600,
  1228. width: 800
  1229. }
  1230. },
  1231. sidx: {
  1232. byterange: {
  1233. offset: 10,
  1234. length: 10
  1235. },
  1236. uri: 'sidx.mp4',
  1237. resolvedUri: 'http://example.com/sidx.mp4',
  1238. timeline: 0,
  1239. duration: 10
  1240. },
  1241. targetDuration: 0,
  1242. timeline: 0,
  1243. timelineStarts: [{ start: 0, timeline: 0 }],
  1244. uri: '',
  1245. segments: [{
  1246. map: {
  1247. resolvedUri: 'http://example.com/sidx.mp4',
  1248. uri: ''
  1249. },
  1250. byterange: {
  1251. offset: 20,
  1252. length: 5
  1253. },
  1254. uri: 'http://example.com/sidx.mp4',
  1255. resolvedUri: 'http://example.com/sidx.mp4',
  1256. duration: 2,
  1257. number: 0,
  1258. presentationTime: 0,
  1259. timeline: 0
  1260. }],
  1261. endList: true,
  1262. mediaSequence: 0,
  1263. discontinuitySequence: 0,
  1264. discontinuityStarts: [],
  1265. resolvedUri: ''
  1266. }];
  1267. assert.deepEqual(toM3u8({ dashPlaylists, sidxMapping }).playlists, expected);
  1268. });
  1269. QUnit.test('playlists without minimumUpdatePeriod dont assign default value', function(assert) {
  1270. const dashPlaylists = [{
  1271. attributes: {
  1272. sourceDuration: 100,
  1273. id: '1',
  1274. width: 800,
  1275. height: 600,
  1276. codecs: 'foo;bar',
  1277. duration: 0,
  1278. bandwidth: 10000,
  1279. periodStart: 0,
  1280. mimeType: 'video/mp4',
  1281. type: 'static'
  1282. },
  1283. segments: [],
  1284. sidx: {
  1285. byterange: {
  1286. offset: 10,
  1287. length: 10
  1288. },
  1289. uri: 'sidx.mp4',
  1290. resolvedUri: 'http://example.com/sidx.mp4',
  1291. duration: 10
  1292. },
  1293. uri: 'http://example.com/fmp4.mp4'
  1294. }];
  1295. assert.equal(toM3u8({ dashPlaylists }).minimumUpdatePeriod, undefined);
  1296. });
  1297. QUnit.test('playlists with minimumUpdatePeriod = 0', function(assert) {
  1298. const dashPlaylists = [{
  1299. attributes: {
  1300. sourceDuration: 100,
  1301. id: '1',
  1302. width: 800,
  1303. height: 600,
  1304. codecs: 'foo;bar',
  1305. duration: 0,
  1306. bandwidth: 10000,
  1307. periodStart: 0,
  1308. mimeType: 'video/mp4',
  1309. type: 'static',
  1310. minimumUpdatePeriod: 0
  1311. },
  1312. segments: [],
  1313. sidx: {
  1314. byterange: {
  1315. offset: 10,
  1316. length: 10
  1317. },
  1318. uri: 'sidx.mp4',
  1319. resolvedUri: 'http://example.com/sidx.mp4',
  1320. duration: 10
  1321. },
  1322. uri: 'http://example.com/fmp4.mp4'
  1323. }];
  1324. assert.equal(toM3u8({ dashPlaylists }).minimumUpdatePeriod, 0);
  1325. });
  1326. QUnit.test('playlists with integer value for minimumUpdatePeriod', function(assert) {
  1327. const dashPlaylists = [{
  1328. attributes: {
  1329. sourceDuration: 100,
  1330. id: '1',
  1331. width: 800,
  1332. height: 600,
  1333. codecs: 'foo;bar',
  1334. duration: 0,
  1335. bandwidth: 10000,
  1336. periodStart: 0,
  1337. mimeType: 'video/mp4',
  1338. type: 'static',
  1339. minimumUpdatePeriod: 2
  1340. },
  1341. segments: [],
  1342. sidx: {
  1343. byterange: {
  1344. offset: 10,
  1345. length: 10
  1346. },
  1347. uri: 'sidx.mp4',
  1348. resolvedUri: 'http://example.com/sidx.mp4',
  1349. duration: 10
  1350. },
  1351. uri: 'http://example.com/fmp4.mp4'
  1352. }];
  1353. assert.equal(
  1354. toM3u8({ dashPlaylists }).minimumUpdatePeriod,
  1355. 2000,
  1356. 'converts update period to ms'
  1357. );
  1358. });
  1359. QUnit.test('no playlists', function(assert) {
  1360. assert.deepEqual(toM3u8({ dashPlaylists: [] }), {});
  1361. });
  1362. QUnit.test('dynamic playlists with suggestedPresentationDelay', function(assert) {
  1363. const dashPlaylists = [{
  1364. attributes: {
  1365. id: '1',
  1366. codecs: 'foo;bar',
  1367. sourceDuration: 100,
  1368. duration: 0,
  1369. bandwidth: 20000,
  1370. periodStart: 0,
  1371. mimeType: 'audio/mp4',
  1372. type: 'dynamic',
  1373. suggestedPresentationDelay: 18
  1374. },
  1375. segments: []
  1376. }, {
  1377. attributes: {
  1378. id: '2',
  1379. codecs: 'foo;bar',
  1380. sourceDuration: 100,
  1381. duration: 0,
  1382. bandwidth: 10000,
  1383. periodStart: 0,
  1384. mimeType: 'audio/mp4',
  1385. type: 'static'
  1386. },
  1387. segments: []
  1388. }, {
  1389. attributes: {
  1390. sourceDuration: 100,
  1391. id: '1',
  1392. width: 800,
  1393. height: 600,
  1394. codecs: 'foo;bar',
  1395. duration: 0,
  1396. bandwidth: 10000,
  1397. periodStart: 0,
  1398. mimeType: 'video/mp4',
  1399. type: 'static'
  1400. },
  1401. segments: []
  1402. }, {
  1403. attributes: {
  1404. sourceDuration: 100,
  1405. id: '1',
  1406. bandwidth: 20000,
  1407. periodStart: 0,
  1408. mimeType: 'text/vtt',
  1409. type: 'static',
  1410. baseUrl: 'https://www.example.com/vtt'
  1411. }
  1412. }, {
  1413. attributes: {
  1414. sourceDuration: 100,
  1415. id: '1',
  1416. bandwidth: 10000,
  1417. periodStart: 0,
  1418. mimeType: 'text/vtt',
  1419. type: 'static',
  1420. baseUrl: 'https://www.example.com/vtt'
  1421. }
  1422. }];
  1423. const output = toM3u8({ dashPlaylists });
  1424. assert.ok('suggestedPresentationDelay' in output);
  1425. assert.deepEqual(output.suggestedPresentationDelay, 18);
  1426. });
  1427. QUnit.test('playlists with label', function(assert) {
  1428. const label = 'English with commentary';
  1429. const dashPlaylists = [{
  1430. attributes: {
  1431. id: '1',
  1432. codecs: 'foo;bar',
  1433. sourceDuration: 100,
  1434. duration: 0,
  1435. bandwidth: 20000,
  1436. periodStart: 0,
  1437. mimeType: 'audio/mp4',
  1438. type: 'dynamic',
  1439. label
  1440. },
  1441. segments: []
  1442. }, {
  1443. attributes: {
  1444. id: '2',
  1445. codecs: 'foo;bar',
  1446. sourceDuration: 100,
  1447. duration: 0,
  1448. bandwidth: 10000,
  1449. periodStart: 0,
  1450. mimeType: 'audio/mp4',
  1451. type: 'static'
  1452. },
  1453. segments: []
  1454. }, {
  1455. attributes: {
  1456. sourceDuration: 100,
  1457. id: '1',
  1458. width: 800,
  1459. height: 600,
  1460. codecs: 'foo;bar',
  1461. duration: 0,
  1462. bandwidth: 10000,
  1463. periodStart: 0,
  1464. mimeType: 'video/mp4',
  1465. type: 'static'
  1466. },
  1467. segments: []
  1468. }, {
  1469. attributes: {
  1470. sourceDuration: 100,
  1471. id: '1',
  1472. width: 800,
  1473. height: 600,
  1474. codecs: 'foo;bar',
  1475. duration: 0,
  1476. bandwidth: 10000,
  1477. periodStart: 0,
  1478. mimeType: 'text/vtt',
  1479. type: 'static',
  1480. label
  1481. },
  1482. segments: []
  1483. }];
  1484. const output = toM3u8({ dashPlaylists });
  1485. assert.ok(label in output.mediaGroups.AUDIO.audio, 'label exists');
  1486. assert.ok(label in output.mediaGroups.SUBTITLES.subs, 'label exists');
  1487. });
  1488. QUnit.test('608 captions', function(assert) {
  1489. const dashPlaylists = [{
  1490. attributes: {
  1491. captionServices: [{
  1492. channel: 'CC1',
  1493. language: 'CC1'
  1494. }, {
  1495. channel: 'CC2',
  1496. language: 'CC2'
  1497. }, {
  1498. channel: undefined,
  1499. language: 'English'
  1500. }, {
  1501. channel: 'CC4',
  1502. language: 'eng'
  1503. }],
  1504. id: '1',
  1505. codecs: 'foo;bar',
  1506. sourceDuration: 100,
  1507. duration: 0,
  1508. bandwidth: 20000,
  1509. periodStart: 0,
  1510. mimeType: 'audio/mp4',
  1511. type: 'dynamic'
  1512. },
  1513. segments: []
  1514. }, {
  1515. attributes: {
  1516. id: '2',
  1517. codecs: 'foo;bar',
  1518. sourceDuration: 100,
  1519. duration: 0,
  1520. bandwidth: 10000,
  1521. periodStart: 0,
  1522. mimeType: 'audio/mp4',
  1523. type: 'static'
  1524. },
  1525. segments: []
  1526. }, {
  1527. attributes: {
  1528. sourceDuration: 100,
  1529. id: '1',
  1530. width: 800,
  1531. height: 600,
  1532. codecs: 'foo;bar',
  1533. duration: 0,
  1534. bandwidth: 10000,
  1535. periodStart: 0,
  1536. mimeType: 'video/mp4',
  1537. type: 'static'
  1538. },
  1539. segments: []
  1540. }];
  1541. const output = toM3u8({ dashPlaylists });
  1542. const cc = output.mediaGroups['CLOSED-CAPTIONS'].cc;
  1543. Object.keys(cc).forEach((key) => {
  1544. assert.notOk(cc[key].autoselect, 'no autoselect');
  1545. assert.notOk(cc[key].default, 'no default');
  1546. });
  1547. assert.deepEqual(Object.keys(cc), ['CC1', 'CC2', 'English', 'eng'], 'we have 4 channels');
  1548. assert.equal(cc.CC1.instreamId, 'CC1', 'CC1 has an instreamId of CC1');
  1549. assert.equal(cc.CC2.instreamId, 'CC2', 'CC2 has an instreamId of CC1');
  1550. assert.equal(cc.English.instreamId, undefined, 'English captions dont have an instreamId');
  1551. assert.equal(cc.eng.instreamId, 'CC4', 'eng captions have an instreamId of CC4');
  1552. });
  1553. QUnit.module('generateSidxKey');
  1554. QUnit.test('generates correct key', function(assert) {
  1555. const sidxInfo = {
  1556. byterange: {
  1557. offset: 1,
  1558. length: 5
  1559. },
  1560. uri: 'uri'
  1561. };
  1562. assert.strictEqual(
  1563. generateSidxKey(sidxInfo),
  1564. 'uri-1-5',
  1565. 'the key byterange should have a inclusive end'
  1566. );
  1567. });
  1568. QUnit.module('addMediaSequenceValues');
  1569. QUnit.test('resets media sequence values', function(assert) {
  1570. const playlists = [{
  1571. timeline: 17,
  1572. mediaSequence: 2,
  1573. discontinuitySequence: 3,
  1574. segments: [{
  1575. number: 5,
  1576. presentationTime: 17,
  1577. timeline: 17
  1578. }, {
  1579. number: 6,
  1580. presentationTime: 19,
  1581. timeline: 17
  1582. }, {
  1583. number: 7,
  1584. presentationTime: 21,
  1585. timeline: 21
  1586. }]
  1587. }, {
  1588. timeline: 21,
  1589. mediaSequence: 2,
  1590. discontinuitySequence: 3,
  1591. segments: [{
  1592. number: 1,
  1593. presentationTime: 21,
  1594. timeline: 21
  1595. }]
  1596. }, {
  1597. timeline: 17,
  1598. mediaSequence: 2,
  1599. discontinuitySequence: 2,
  1600. segments: []
  1601. }];
  1602. const timelineStarts = [{
  1603. timeline: 17,
  1604. start: 17
  1605. }, {
  1606. timeline: 21,
  1607. start: 21
  1608. }];
  1609. addMediaSequenceValues(playlists, timelineStarts);
  1610. assert.deepEqual(
  1611. playlists,
  1612. [{
  1613. timeline: 17,
  1614. mediaSequence: 0,
  1615. discontinuitySequence: 0,
  1616. segments: [{
  1617. number: 0,
  1618. presentationTime: 17,
  1619. timeline: 17
  1620. }, {
  1621. number: 1,
  1622. presentationTime: 19,
  1623. timeline: 17
  1624. }, {
  1625. number: 2,
  1626. presentationTime: 21,
  1627. timeline: 21
  1628. }]
  1629. }, {
  1630. timeline: 21,
  1631. mediaSequence: 0,
  1632. discontinuitySequence: 1,
  1633. segments: [{
  1634. number: 0,
  1635. presentationTime: 21,
  1636. timeline: 21
  1637. }]
  1638. }, {
  1639. timeline: 17,
  1640. mediaSequence: 0,
  1641. discontinuitySequence: 0,
  1642. segments: []
  1643. }],
  1644. 'updated media sequence values'
  1645. );
  1646. });
  1647. QUnit.module('flattenMediaGroupPlaylists');
  1648. QUnit.test('includes all media group playlists', function(assert) {
  1649. assert.deepEqual(
  1650. flattenMediaGroupPlaylists({
  1651. en: {
  1652. playlists: [
  1653. { attributes: { NAME: 'A' } },
  1654. { attributes: { NAME: 'B' } }
  1655. ]
  1656. },
  1657. es: {
  1658. playlists: [
  1659. { attributes: { NAME: 'C' } },
  1660. { attributes: { NAME: 'D' } }
  1661. ]
  1662. }
  1663. }),
  1664. [
  1665. { attributes: { NAME: 'A' } },
  1666. { attributes: { NAME: 'B' } },
  1667. { attributes: { NAME: 'C' } },
  1668. { attributes: { NAME: 'D' } }
  1669. ],
  1670. 'included all media group playlists'
  1671. );
  1672. });
  1673. QUnit.module('eventStream');
  1674. QUnit.test('eventStreams with playlists', function(assert) {
  1675. const dashPlaylists = [{
  1676. attributes: {
  1677. id: '1',
  1678. codecs: 'foo;bar',
  1679. sourceDuration: 100,
  1680. duration: 0,
  1681. bandwidth: 20000,
  1682. periodStart: 0,
  1683. mimeType: 'audio/mp4',
  1684. type: 'static'
  1685. },
  1686. segments: []
  1687. }, {
  1688. attributes: {
  1689. id: '2',
  1690. codecs: 'foo;bar',
  1691. sourceDuration: 100,
  1692. duration: 0,
  1693. bandwidth: 10000,
  1694. periodStart: 0,
  1695. mimeType: 'audio/mp4',
  1696. type: 'static'
  1697. },
  1698. segments: []
  1699. }, {
  1700. attributes: {
  1701. sourceDuration: 100,
  1702. id: '1',
  1703. width: 800,
  1704. height: 600,
  1705. codecs: 'foo;bar',
  1706. duration: 0,
  1707. bandwidth: 10000,
  1708. frameRate: 30,
  1709. periodStart: 0,
  1710. mimeType: 'video/mp4',
  1711. type: 'static'
  1712. },
  1713. segments: []
  1714. }, {
  1715. attributes: {
  1716. sourceDuration: 100,
  1717. id: '1',
  1718. bandwidth: 20000,
  1719. periodStart: 0,
  1720. mimeType: 'text/vtt',
  1721. type: 'static',
  1722. baseUrl: 'https://www.example.com/vtt'
  1723. }
  1724. }, {
  1725. attributes: {
  1726. sourceDuration: 100,
  1727. id: '2',
  1728. bandwidth: 10000,
  1729. periodStart: 0,
  1730. mimeType: 'text/vtt',
  1731. type: 'static',
  1732. baseUrl: 'https://www.example.com/vtt'
  1733. }
  1734. }];
  1735. const eventStream = [
  1736. {
  1737. end: 1,
  1738. id: 'one',
  1739. messageData: 'foo',
  1740. schemeIdUri: 'urn:foo.bar.2023',
  1741. start: 1,
  1742. value: 'bar'
  1743. },
  1744. {
  1745. end: 2,
  1746. id: 'two',
  1747. messageData: 'bar',
  1748. schemeIdUri: 'urn:foo.bar.2023',
  1749. start: 2,
  1750. value: 'foo'
  1751. },
  1752. {
  1753. end: 3,
  1754. id: 'three',
  1755. messageData: 'foo_bar',
  1756. schemeIdUri: 'urn:foo.bar.2023',
  1757. start: 3,
  1758. value: 'bar_foo'
  1759. }
  1760. ];
  1761. const expected = {
  1762. allowCache: true,
  1763. discontinuityStarts: [],
  1764. timelineStarts: [{ start: 0, timeline: 0 }],
  1765. duration: 100,
  1766. endList: true,
  1767. eventStream: [
  1768. {
  1769. end: 1,
  1770. id: 'one',
  1771. messageData: 'foo',
  1772. schemeIdUri: 'urn:foo.bar.2023',
  1773. start: 1,
  1774. value: 'bar'
  1775. },
  1776. {
  1777. end: 2,
  1778. id: 'two',
  1779. messageData: 'bar',
  1780. schemeIdUri: 'urn:foo.bar.2023',
  1781. start: 2,
  1782. value: 'foo'
  1783. },
  1784. {
  1785. end: 3,
  1786. id: 'three',
  1787. messageData: 'foo_bar',
  1788. schemeIdUri: 'urn:foo.bar.2023',
  1789. start: 3,
  1790. value: 'bar_foo'
  1791. }
  1792. ],
  1793. mediaGroups: {
  1794. AUDIO: {
  1795. audio: {
  1796. main: {
  1797. autoselect: true,
  1798. default: true,
  1799. language: '',
  1800. playlists: [{
  1801. attributes: {
  1802. BANDWIDTH: 20000,
  1803. CODECS: 'foo;bar',
  1804. NAME: '1',
  1805. ['PROGRAM-ID']: 1
  1806. },
  1807. mediaSequence: 0,
  1808. discontinuitySequence: 0,
  1809. discontinuityStarts: [],
  1810. timelineStarts: [{ start: 0, timeline: 0 }],
  1811. endList: true,
  1812. resolvedUri: '',
  1813. segments: [],
  1814. timeline: 0,
  1815. uri: '',
  1816. targetDuration: 0
  1817. }, {
  1818. attributes: {
  1819. BANDWIDTH: 10000,
  1820. CODECS: 'foo;bar',
  1821. NAME: '2',
  1822. ['PROGRAM-ID']: 1
  1823. },
  1824. mediaSequence: 0,
  1825. discontinuitySequence: 0,
  1826. discontinuityStarts: [],
  1827. timelineStarts: [{ start: 0, timeline: 0 }],
  1828. endList: true,
  1829. resolvedUri: '',
  1830. segments: [],
  1831. timeline: 0,
  1832. uri: '',
  1833. targetDuration: 0
  1834. }],
  1835. uri: ''
  1836. }
  1837. }
  1838. },
  1839. ['CLOSED-CAPTIONS']: {},
  1840. SUBTITLES: {
  1841. subs: {
  1842. text: {
  1843. autoselect: false,
  1844. default: false,
  1845. language: 'text',
  1846. playlists: [{
  1847. attributes: {
  1848. BANDWIDTH: 20000,
  1849. NAME: '1',
  1850. ['PROGRAM-ID']: 1
  1851. },
  1852. mediaSequence: 0,
  1853. discontinuitySequence: 0,
  1854. discontinuityStarts: [],
  1855. timelineStarts: [{ start: 0, timeline: 0 }],
  1856. targetDuration: 100,
  1857. endList: true,
  1858. resolvedUri: 'https://www.example.com/vtt',
  1859. segments: [{
  1860. duration: 100,
  1861. resolvedUri: 'https://www.example.com/vtt',
  1862. timeline: 0,
  1863. uri: 'https://www.example.com/vtt',
  1864. number: 0
  1865. }],
  1866. timeline: 0,
  1867. uri: ''
  1868. }, {
  1869. attributes: {
  1870. BANDWIDTH: 10000,
  1871. NAME: '2',
  1872. ['PROGRAM-ID']: 1
  1873. },
  1874. mediaSequence: 0,
  1875. discontinuitySequence: 0,
  1876. discontinuityStarts: [],
  1877. timelineStarts: [{ start: 0, timeline: 0 }],
  1878. targetDuration: 100,
  1879. endList: true,
  1880. resolvedUri: 'https://www.example.com/vtt',
  1881. segments: [{
  1882. duration: 100,
  1883. resolvedUri: 'https://www.example.com/vtt',
  1884. timeline: 0,
  1885. uri: 'https://www.example.com/vtt',
  1886. number: 0
  1887. }],
  1888. timeline: 0,
  1889. uri: ''
  1890. }],
  1891. uri: ''
  1892. }
  1893. }
  1894. },
  1895. VIDEO: {}
  1896. },
  1897. playlists: [{
  1898. attributes: {
  1899. AUDIO: 'audio',
  1900. SUBTITLES: 'subs',
  1901. BANDWIDTH: 10000,
  1902. CODECS: 'foo;bar',
  1903. NAME: '1',
  1904. ['FRAME-RATE']: 30,
  1905. ['PROGRAM-ID']: 1,
  1906. RESOLUTION: {
  1907. height: 600,
  1908. width: 800
  1909. }
  1910. },
  1911. endList: true,
  1912. mediaSequence: 0,
  1913. discontinuitySequence: 0,
  1914. discontinuityStarts: [],
  1915. timelineStarts: [{ start: 0, timeline: 0 }],
  1916. targetDuration: 0,
  1917. resolvedUri: '',
  1918. segments: [],
  1919. timeline: 0,
  1920. uri: ''
  1921. }],
  1922. segments: [],
  1923. uri: ''
  1924. };
  1925. assert.deepEqual(toM3u8({ dashPlaylists, eventStream }), expected);
  1926. });