You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

741 lines
16 KiB

  1. import QUnit from 'qunit';
  2. import TransmuxWorker from 'worker!../src/transmuxer-worker.worker.js';
  3. import {
  4. mp4Captions as mp4CaptionsSegment,
  5. muxed as muxedSegment,
  6. oneSecond as oneSecondSegment,
  7. caption as captionSegment
  8. } from 'create-test-data!segments';
  9. // needed for plugin registration
  10. import '../src/videojs-http-streaming';
  11. const createTransmuxer = (isPartial) => {
  12. const transmuxer = new TransmuxWorker();
  13. transmuxer.postMessage({
  14. action: 'init',
  15. options: {
  16. remux: false,
  17. keepOriginalTimestamps: true,
  18. handlePartialData: isPartial
  19. }
  20. });
  21. return transmuxer;
  22. };
  23. // The final done message from the Transmux worker
  24. // will have type `transmuxed`
  25. const isFinalDone = (event) => {
  26. return event.data.action === 'done' &&
  27. event.data.type === 'transmuxed';
  28. };
  29. QUnit.module('Transmuxer Worker: Full Transmuxer', {
  30. beforeEach(assert) {
  31. assert.timeout(5000);
  32. },
  33. afterEach(assert) {
  34. if (this.transmuxer) {
  35. this.transmuxer.terminate();
  36. }
  37. }
  38. });
  39. // Missing tests as these are not accessible to unit testing
  40. // - setTimestampOffset
  41. // - setAudioAppendStart
  42. // - alignGopsWith
  43. QUnit.test('push should result in a trackinfo event', function(assert) {
  44. const done = assert.async();
  45. this.transmuxer = createTransmuxer(false);
  46. this.transmuxer.onmessage = (e) => {
  47. assert.equal(
  48. e.data.action,
  49. 'trackinfo',
  50. 'pushing data should get trackinfo as the first event'
  51. );
  52. assert.deepEqual(
  53. e.data.trackInfo,
  54. {
  55. hasVideo: true,
  56. hasAudio: true
  57. },
  58. 'should have video and audio'
  59. );
  60. done();
  61. };
  62. this.transmuxer.postMessage({
  63. action: 'push',
  64. data: muxedSegment()
  65. });
  66. });
  67. QUnit.test('flush should return data from transmuxer', function(assert) {
  68. const testDone = assert.async();
  69. const messages = [];
  70. const handleMessages = (e) => {
  71. messages.push(e.data);
  72. if (!isFinalDone(e)) {
  73. return;
  74. }
  75. assert.deepEqual(
  76. messages.map((x) => x.action),
  77. [
  78. 'trackinfo',
  79. 'gopInfo',
  80. 'videoSegmentTimingInfo',
  81. 'videoTimingInfo',
  82. 'data',
  83. 'audioTimingInfo',
  84. 'data',
  85. 'done',
  86. 'done'
  87. ],
  88. 'the events are received in the expected order'
  89. );
  90. assert.ok(
  91. messages.shift().trackInfo,
  92. 'returns trackInfo with trackinfo event'
  93. );
  94. assert.ok(
  95. messages.shift().gopInfo,
  96. 'returns gopInfo with gopInfo event'
  97. );
  98. assert.ok(
  99. messages.shift().videoSegmentTimingInfo,
  100. 'returns timing information with videoSegmentTimingInfo event'
  101. );
  102. assert.ok(
  103. messages.shift().videoTimingInfo,
  104. 'returns timing information with videoTimingInfo event'
  105. );
  106. const data1 = messages.shift();
  107. assert.ok(
  108. data1.segment.data.byteLength > 0,
  109. 'returns data with the 1st data event'
  110. );
  111. assert.ok(
  112. data1.segment.type,
  113. 'video',
  114. 'returns video data with the 1st data event'
  115. );
  116. assert.ok(
  117. messages.shift().audioTimingInfo,
  118. 'returns timing information with audioTimingInfo event'
  119. );
  120. const data2 = messages.shift();
  121. assert.ok(
  122. data2.segment.data.byteLength > 0,
  123. 'returns data with the 2nd data event'
  124. );
  125. assert.ok(
  126. data2.segment.type,
  127. 'returns audio bytes with the 2nd data event'
  128. );
  129. testDone();
  130. };
  131. this.transmuxer = createTransmuxer(false);
  132. this.transmuxer.onmessage = handleMessages;
  133. this.transmuxer.postMessage({
  134. action: 'push',
  135. data: muxedSegment()
  136. });
  137. this.transmuxer.postMessage({
  138. action: 'flush'
  139. });
  140. });
  141. QUnit.test('reset will clear transmuxer', function(assert) {
  142. const done = assert.async();
  143. const messages = [];
  144. this.transmuxer = createTransmuxer(false);
  145. this.transmuxer.onmessage = (e) => {
  146. messages.push(e.data);
  147. if (!isFinalDone(e)) {
  148. return;
  149. }
  150. assert.deepEqual(
  151. messages.map((x) => x.action),
  152. [
  153. 'trackinfo',
  154. 'done',
  155. 'done'
  156. ],
  157. 'flush after a reset does not return data events'
  158. );
  159. done();
  160. };
  161. this.transmuxer.postMessage({
  162. action: 'push',
  163. data: muxedSegment()
  164. });
  165. this.transmuxer.postMessage({
  166. action: 'reset'
  167. });
  168. this.transmuxer.postMessage({
  169. action: 'flush'
  170. });
  171. });
  172. QUnit.test('endTimeline will return unflushed data', function(assert) {
  173. const done = assert.async();
  174. const messages = [];
  175. this.transmuxer = createTransmuxer(false);
  176. this.transmuxer.onmessage = (e) => {
  177. messages.push(e.data);
  178. if (e.data.action !== 'endedtimeline') {
  179. return;
  180. }
  181. assert.deepEqual(
  182. e.data,
  183. {
  184. action: 'endedtimeline',
  185. type: 'transmuxed'
  186. },
  187. 'endedtimeline event is received from worker'
  188. );
  189. assert.ok(
  190. messages.filter((x) => x.action === 'data'),
  191. 'data event was returned on endedtimeline'
  192. );
  193. done();
  194. };
  195. this.transmuxer.postMessage({
  196. action: 'push',
  197. data: muxedSegment()
  198. });
  199. this.transmuxer.postMessage({
  200. action: 'endTimeline'
  201. });
  202. });
  203. QUnit.test('caption events are returned', function(assert) {
  204. const done = assert.async();
  205. const messages = [];
  206. this.transmuxer = createTransmuxer(false);
  207. this.transmuxer.onmessage = (e) => {
  208. messages.push(e.data);
  209. if (!isFinalDone(e)) {
  210. return;
  211. }
  212. assert.deepEqual(
  213. messages
  214. .map((x) => x.action)
  215. .filter((y) => y === 'trackinfo')
  216. .length,
  217. 25,
  218. 'expected amount of trackinfo events returned'
  219. );
  220. assert.deepEqual(
  221. messages
  222. .map((x) => x.action)
  223. .filter((y) => y !== 'trackinfo'),
  224. [
  225. 'gopInfo',
  226. 'videoSegmentTimingInfo',
  227. 'videoTimingInfo',
  228. 'data',
  229. 'caption',
  230. 'done',
  231. 'done'
  232. ],
  233. 'events are returned in expected order'
  234. );
  235. assert.deepEqual(
  236. messages.shift().trackInfo,
  237. {
  238. hasVideo: true,
  239. hasAudio: false
  240. },
  241. 'trackinfo should have video only'
  242. );
  243. assert.ok(
  244. messages[24].gopInfo,
  245. 'gopInfo event has gopInfo'
  246. );
  247. assert.ok(
  248. messages[25].videoSegmentTimingInfo,
  249. 'videoSegmentTimingInfo event has timing info'
  250. );
  251. assert.ok(
  252. messages[26].videoTimingInfo,
  253. 'videoTimingInfo event has timing info'
  254. );
  255. assert.ok(
  256. messages[27].segment.data.byteLength > 0,
  257. 'data event returns data'
  258. );
  259. assert.deepEqual(
  260. messages[28].caption,
  261. {
  262. text: 'Bip!',
  263. stream: 'CC1',
  264. startPts: 157500,
  265. endPts: 175500,
  266. startTime: 1.75,
  267. endTime: 1.95
  268. },
  269. 'caption event returns expected caption'
  270. );
  271. done();
  272. };
  273. this.transmuxer.postMessage({
  274. action: 'push',
  275. data: captionSegment()
  276. });
  277. this.transmuxer.postMessage({
  278. action: 'flush'
  279. });
  280. });
  281. QUnit.test('can parse mp4 captions', function(assert) {
  282. const done = assert.async();
  283. const data = mp4CaptionsSegment();
  284. this.transmuxer = createTransmuxer(false);
  285. this.transmuxer.onmessage = (e) => {
  286. const message = e.data;
  287. assert.equal(message.action, 'mp4Captions', 'returned mp4Captions event');
  288. assert.deepEqual(message.captions.length, 2, 'two captions');
  289. assert.deepEqual(
  290. new Uint8Array(message.data),
  291. data,
  292. 'data returned to main thread'
  293. );
  294. done();
  295. };
  296. this.transmuxer.postMessage({
  297. action: 'pushMp4Captions',
  298. data,
  299. timescales: 30000,
  300. trackIds: [1],
  301. byteLength: data.byteLength,
  302. byteOffset: 0
  303. });
  304. });
  305. QUnit.test('returns empty array without mp4 captions', function(assert) {
  306. const done = assert.async();
  307. const data = muxedSegment();
  308. this.transmuxer = createTransmuxer(false);
  309. this.transmuxer.onmessage = (e) => {
  310. const message = e.data;
  311. assert.equal(message.action, 'mp4Captions', 'returned mp4Captions event');
  312. assert.deepEqual(message.captions, [], 'no captions');
  313. assert.deepEqual(
  314. new Uint8Array(message.data),
  315. data,
  316. 'data returned to main thread'
  317. );
  318. done();
  319. };
  320. this.transmuxer.postMessage({
  321. action: 'pushMp4Captions',
  322. data,
  323. timescales: 30000,
  324. trackIds: [1],
  325. byteLength: data.byteLength,
  326. byteOffset: 0
  327. });
  328. });
  329. QUnit.module('Transmuxer Worker: Partial Transmuxer', {
  330. beforeEach(assert) {
  331. assert.timeout(5000);
  332. },
  333. afterEach(assert) {
  334. if (this.transmuxer) {
  335. this.transmuxer.terminate();
  336. delete this.transmuxer;
  337. }
  338. }
  339. });
  340. // Missing tests as these are not accessible to unit testing
  341. // - setTimestampOffset
  342. // - setAudioAppendStart
  343. // - alignGopsWith
  344. QUnit.test('push should result in a trackinfo event', function(assert) {
  345. const done = assert.async();
  346. this.transmuxer = createTransmuxer(true);
  347. this.transmuxer.onmessage = (e) => {
  348. assert.equal(
  349. e.data.action,
  350. 'trackinfo',
  351. 'pushing data should get trackinfo as the first event'
  352. );
  353. assert.deepEqual(
  354. e.data.trackInfo,
  355. {
  356. hasVideo: true,
  357. hasAudio: true
  358. },
  359. 'should have video and audio'
  360. );
  361. done();
  362. };
  363. this.transmuxer.postMessage({
  364. action: 'push',
  365. data: muxedSegment()
  366. });
  367. });
  368. QUnit.test('flush should return data from transmuxer', function(assert) {
  369. const testDone = assert.async();
  370. const messages = [];
  371. const handleMessages = (e) => {
  372. messages.push(e.data);
  373. if (!isFinalDone(e)) {
  374. return;
  375. }
  376. assert.deepEqual(
  377. messages.map((x) => x.action),
  378. [
  379. 'trackinfo',
  380. 'videoTimingInfo',
  381. 'data',
  382. 'data',
  383. 'done',
  384. 'audioTimingInfo',
  385. 'data',
  386. 'audioTimingInfo',
  387. 'done',
  388. 'done'
  389. ],
  390. 'the events are received in the expected order'
  391. );
  392. const trackInfoEvent = messages.shift();
  393. const videoTimingInfoEvent = messages.shift();
  394. const data1 = messages.shift();
  395. const data2 = messages.shift();
  396. const done1 = messages.shift();
  397. const audioTimingInfoEvent = messages.shift();
  398. const data3 = messages.shift();
  399. const audioTimingInfoEvent2 = messages.shift();
  400. const done2 = messages.shift();
  401. assert.ok(
  402. trackInfoEvent.trackInfo,
  403. 'returns trackInfo with trackinfo event'
  404. );
  405. assert.ok(
  406. videoTimingInfoEvent.videoTimingInfo,
  407. 'returns timing information with videoTimingInfo event'
  408. );
  409. assert.ok(
  410. data1.segment.boxes.byteLength > 0,
  411. 'returns data with the 1st data event'
  412. );
  413. assert.deepEqual(
  414. data1.segment.type,
  415. 'video',
  416. 'returns video data with the 1st data event'
  417. );
  418. assert.ok(
  419. data2.segment.boxes.byteLength > 0,
  420. 'returns data with the 2nd data event'
  421. );
  422. assert.deepEqual(
  423. data2.segment.type,
  424. 'video',
  425. 'returns video bytes with the 2nd data event'
  426. );
  427. assert.deepEqual(
  428. done1,
  429. {
  430. action: 'done',
  431. type: 'video'
  432. },
  433. 'got done event for video data only'
  434. );
  435. assert.ok(
  436. audioTimingInfoEvent.audioTimingInfo,
  437. 'returns timing information with audioTimingInfo event'
  438. );
  439. assert.deepEqual(
  440. Object.keys(audioTimingInfoEvent.audioTimingInfo),
  441. ['start'],
  442. '1st audioTimingInfo only has startTime'
  443. );
  444. assert.ok(
  445. data3.segment.boxes.byteLength > 0,
  446. 'returns data with audio data event'
  447. );
  448. assert.deepEqual(
  449. data3.segment.type,
  450. 'audio',
  451. 'returns audio bytes with the audio data event'
  452. );
  453. assert.ok(
  454. audioTimingInfoEvent2.audioTimingInfo,
  455. 'returns timing information with 2nd audioTimingInfo event'
  456. );
  457. assert.deepEqual(
  458. Object.keys(audioTimingInfoEvent2.audioTimingInfo),
  459. ['start', 'end'],
  460. '2nd audioTimingInfo has startTime and endTime'
  461. );
  462. assert.deepEqual(
  463. done2,
  464. {
  465. action: 'done',
  466. type: 'audio'
  467. },
  468. 'got done event for audio data only'
  469. );
  470. testDone();
  471. };
  472. this.transmuxer = createTransmuxer(true);
  473. this.transmuxer.onmessage = handleMessages;
  474. this.transmuxer.postMessage({
  475. action: 'push',
  476. data: muxedSegment()
  477. });
  478. this.transmuxer.postMessage({
  479. action: 'flush'
  480. });
  481. });
  482. QUnit.test('reset will clear transmuxer', function(assert) {
  483. const done = assert.async();
  484. const messages = [];
  485. this.transmuxer = createTransmuxer(true);
  486. this.transmuxer.onmessage = (e) => {
  487. messages.push(e.data);
  488. if (!isFinalDone(e)) {
  489. return;
  490. }
  491. assert.deepEqual(
  492. messages.map((x) => x.action),
  493. [
  494. 'trackinfo',
  495. 'done',
  496. // Note: the partial transmuxer differs in behavior
  497. // with the full transmuxer and will trigger this
  498. // event even without audio data
  499. 'audioTimingInfo',
  500. 'done',
  501. 'done'
  502. ],
  503. 'flush after a reset does not return data events'
  504. );
  505. assert.deepEqual(
  506. messages.filter((x) => x.action === 'audioTimingInfo')[0],
  507. {
  508. action: 'audioTimingInfo',
  509. audioTimingInfo: {
  510. start: null,
  511. end: null
  512. }
  513. },
  514. 'gets invalid/reset data for audioTimingInfo after reset'
  515. );
  516. assert.deepEqual(
  517. messages.filter((x) => x.action === 'done'),
  518. [
  519. {
  520. action: 'done',
  521. type: 'video'
  522. },
  523. {
  524. action: 'done',
  525. type: 'audio'
  526. },
  527. {
  528. action: 'done',
  529. type: 'transmuxed'
  530. }
  531. ],
  532. 'gets audio, video and transmuxed done events separately'
  533. );
  534. done();
  535. };
  536. this.transmuxer.postMessage({
  537. action: 'push',
  538. data: muxedSegment()
  539. });
  540. this.transmuxer.postMessage({
  541. action: 'reset'
  542. });
  543. this.transmuxer.postMessage({
  544. action: 'flush'
  545. });
  546. });
  547. QUnit.test('endTimeline will return unflushed data', function(assert) {
  548. const done = assert.async();
  549. const messages = [];
  550. this.transmuxer = createTransmuxer(true);
  551. this.transmuxer.onmessage = (e) => {
  552. messages.push(e.data);
  553. if (e.data.action !== 'endedtimeline') {
  554. return;
  555. }
  556. assert.deepEqual(
  557. e.data,
  558. {
  559. action: 'endedtimeline',
  560. type: 'transmuxed'
  561. },
  562. 'endedtimeline event is received from worker'
  563. );
  564. assert.ok(
  565. messages.filter((x) => x.action === 'data'),
  566. 'data event was returned on endedtimeline'
  567. );
  568. done();
  569. };
  570. this.transmuxer.postMessage({
  571. action: 'push',
  572. data: muxedSegment()
  573. });
  574. this.transmuxer.postMessage({
  575. action: 'endTimeline'
  576. });
  577. });
  578. QUnit.test('partialFlush', function(assert) {
  579. const done = assert.async();
  580. const messages = [];
  581. const isFinalPartialDone = (e) => {
  582. return e.data.action === 'partialdone' &&
  583. e.data.type === 'transmuxed';
  584. };
  585. this.transmuxer = createTransmuxer(true);
  586. this.transmuxer.onmessage = (e) => {
  587. messages.push(e.data);
  588. if (!isFinalPartialDone(e)) {
  589. return;
  590. }
  591. assert.deepEqual(
  592. messages.map((x) => x.action),
  593. [
  594. 'trackinfo', 'trackinfo', 'trackinfo',
  595. 'trackinfo', 'trackinfo', 'trackinfo',
  596. 'videoTimingInfo',
  597. 'data', 'data', 'data', 'data', 'data', 'data',
  598. 'data', 'data', 'data', 'data', 'data', 'data',
  599. 'data', 'data', 'data', 'data', 'data', 'data',
  600. 'data', 'data', 'data', 'data', 'data', 'data',
  601. 'data', 'data', 'data', 'data',
  602. 'partialdone',
  603. 'audioTimingInfo',
  604. 'data',
  605. 'partialdone',
  606. 'partialdone'
  607. ],
  608. 'the events are received in the expected order'
  609. );
  610. const partialdones = [];
  611. messages.forEach(function(m) {
  612. const expected = {action: m.action};
  613. if (m.action === 'trackinfo') {
  614. expected.trackInfo = {hasAudio: true, hasVideo: true};
  615. } else if (m.action === 'videoTimingInfo') {
  616. expected.videoTimingInfo = {end: 2.300911111111111, start: 1.4};
  617. } else if (m.action === 'audioTimingInfo') {
  618. expected.audioTimingInfo = {start: 1.4};
  619. } else if (m.action === 'data') {
  620. assert.ok(m.segment.boxes.byteLength > 0, 'box has bytes');
  621. assert.ok(m.segment.initSegment.byteLength > 0, 'init segment has bytes');
  622. if (m.segment.type === 'video') {
  623. assert.ok(m.segment.videoFrameDtsTime > 0, 'has video frame dts time');
  624. }
  625. return;
  626. } else if (m.action === 'partialdone') {
  627. partialdones.push(m);
  628. return;
  629. }
  630. assert.deepEqual(m, expected, `${m.action} as expected`);
  631. });
  632. assert.deepEqual(partialdones, [
  633. {action: 'partialdone', type: 'video'},
  634. {action: 'partialdone', type: 'audio'},
  635. {action: 'partialdone', type: 'transmuxed'}
  636. ]);
  637. done();
  638. };
  639. this.transmuxer.postMessage({
  640. action: 'push',
  641. data: oneSecondSegment()
  642. });
  643. this.transmuxer.postMessage({
  644. action: 'partialFlush'
  645. });
  646. });