shaka-player 4.2.3 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/.github/workflows/sync-labels.yaml +1 -1
  2. package/.github/workflows/update-issues.yaml +1 -1
  3. package/AUTHORS +5 -0
  4. package/CHANGELOG.md +85 -39
  5. package/CONTRIBUTORS +5 -0
  6. package/README.md +14 -0
  7. package/demo/common/assets.js +58 -0
  8. package/demo/common/message_ids.js +22 -1
  9. package/demo/config.js +89 -5
  10. package/demo/demo.less +7 -1
  11. package/demo/index.html +8 -1
  12. package/demo/locales/en.json +21 -1
  13. package/demo/locales/source.json +82 -2
  14. package/demo/main.js +57 -1
  15. package/demo/search.js +2 -0
  16. package/demo/visualizer.js +337 -0
  17. package/demo/visualizer_button.js +103 -0
  18. package/dist/controls.css +1 -1
  19. package/dist/controls.css.map +1 -1
  20. package/dist/demo.compiled.debug.js +83 -58
  21. package/dist/demo.compiled.debug.map +4 -4
  22. package/dist/demo.compiled.js +83 -58
  23. package/dist/demo.compiled.map +4 -4
  24. package/dist/demo.css +1 -1
  25. package/dist/demo.css.map +1 -1
  26. package/dist/deps.js +18 -11
  27. package/dist/locales.js +32 -32
  28. package/dist/receiver.compiled.debug.js +23 -19
  29. package/dist/receiver.compiled.debug.map +3 -3
  30. package/dist/receiver.compiled.js +23 -19
  31. package/dist/receiver.compiled.map +3 -3
  32. package/dist/shaka-player.compiled.d.ts +178 -27
  33. package/dist/shaka-player.compiled.debug.d.ts +178 -27
  34. package/dist/shaka-player.compiled.debug.externs.js +163 -32
  35. package/dist/shaka-player.compiled.debug.js +488 -422
  36. package/dist/shaka-player.compiled.debug.map +4 -4
  37. package/dist/shaka-player.compiled.externs.js +163 -32
  38. package/dist/shaka-player.compiled.js +895 -857
  39. package/dist/shaka-player.compiled.map +4 -4
  40. package/dist/shaka-player.ui.d.ts +178 -27
  41. package/dist/shaka-player.ui.debug.d.ts +178 -27
  42. package/dist/shaka-player.ui.debug.externs.js +163 -32
  43. package/dist/shaka-player.ui.debug.js +519 -450
  44. package/dist/shaka-player.ui.debug.map +4 -4
  45. package/dist/shaka-player.ui.externs.js +163 -32
  46. package/dist/shaka-player.ui.js +1053 -1015
  47. package/dist/shaka-player.ui.map +4 -4
  48. package/docs/design/lcevc-architecture.png +0 -0
  49. package/docs/design/lcevc-demo.png +0 -0
  50. package/docs/design/lcevc-integration.md +72 -0
  51. package/docs/tutorials/fairplay.md +7 -0
  52. package/docs/tutorials/faq.md +1 -1
  53. package/docs/tutorials/ui.md +7 -7
  54. package/docs/tutorials/upgrade.md +6 -0
  55. package/externs/ima.js +13 -1
  56. package/externs/lcevc.js +81 -0
  57. package/externs/mux.js +4 -42
  58. package/externs/shaka/ads.js +11 -1
  59. package/externs/shaka/manifest_parser.js +5 -1
  60. package/externs/shaka/player.js +178 -15
  61. package/lib/abr/simple_abr_manager.js +7 -0
  62. package/lib/ads/ad_manager.js +0 -8
  63. package/lib/ads/client_side_ad.js +15 -0
  64. package/lib/ads/client_side_ad_manager.js +21 -8
  65. package/lib/ads/server_side_ad.js +15 -0
  66. package/lib/ads/server_side_ad_manager.js +3 -3
  67. package/lib/cast/cast_proxy.js +2 -1
  68. package/lib/cast/cast_receiver.js +19 -5
  69. package/lib/cast/cast_sender.js +2 -1
  70. package/lib/cast/cast_utils.js +18 -6
  71. package/lib/cea/dummy_cea_parser.js +28 -0
  72. package/lib/cea/i_cea_parser.js +14 -2
  73. package/lib/cea/mp4_cea_parser.js +87 -5
  74. package/lib/config/auto_show_text.js +33 -0
  75. package/lib/dash/dash_parser.js +1 -1
  76. package/lib/hls/hls_parser.js +799 -376
  77. package/lib/lcevc/lcevc_dil.js +152 -0
  78. package/lib/media/closed_caption_parser.js +9 -6
  79. package/lib/media/drm_engine.js +14 -6
  80. package/lib/media/media_source_engine.js +112 -26
  81. package/lib/media/playhead.js +23 -1
  82. package/lib/media/presentation_timeline.js +26 -11
  83. package/lib/media/segment_reference.js +49 -1
  84. package/lib/media/streaming_engine.js +145 -24
  85. package/lib/media/transmuxer.js +79 -18
  86. package/lib/offline/storage.js +1 -0
  87. package/lib/player.js +477 -85
  88. package/lib/polyfill/patchedmediakeys_apple.js +55 -1
  89. package/lib/text/ui_text_displayer.js +5 -5
  90. package/lib/text/vtt_text_parser.js +35 -1
  91. package/lib/util/dom_utils.js +0 -1
  92. package/lib/util/error.js +17 -0
  93. package/lib/util/fake_event.js +2 -0
  94. package/lib/util/id3_utils.js +300 -0
  95. package/lib/util/manifest_parser_utils.js +4 -0
  96. package/lib/util/mime_utils.js +3 -6
  97. package/lib/util/mp4_box_parsers.js +15 -5
  98. package/lib/util/platform.js +9 -1
  99. package/lib/util/player_configuration.js +23 -1
  100. package/lib/util/ts_parser.js +499 -0
  101. package/maintained-branches.md +2 -2
  102. package/package.json +4 -1
  103. package/roadmap.md +27 -7
  104. package/test/ads/ad_manager_unit.js +180 -29
  105. package/test/cast/cast_receiver_integration.js +97 -16
  106. package/test/cast/cast_receiver_unit.js +17 -12
  107. package/test/cast/cast_utils_unit.js +4 -1
  108. package/test/dash/dash_parser_content_protection_unit.js +1 -0
  109. package/test/dash/dash_parser_live_unit.js +1 -0
  110. package/test/dash/dash_parser_manifest_unit.js +93 -1
  111. package/test/dash/dash_parser_segment_base_unit.js +1 -0
  112. package/test/dash/dash_parser_segment_list_unit.js +1 -0
  113. package/test/dash/dash_parser_segment_template_unit.js +1 -0
  114. package/test/demo/demo_unit.js +2 -1
  115. package/test/hls/hls_live_unit.js +253 -19
  116. package/test/hls/hls_parser_unit.js +500 -12
  117. package/test/media/closed_caption_parser_unit.js +2 -1
  118. package/test/media/drm_engine_integration.js +3 -1
  119. package/test/media/media_source_engine_integration.js +102 -6
  120. package/test/media/media_source_engine_unit.js +10 -5
  121. package/test/media/playhead_unit.js +41 -0
  122. package/test/media/streaming_engine_integration.js +17 -1
  123. package/test/media/streaming_engine_unit.js +180 -24
  124. package/test/media/transmuxer_integration.js +21 -10
  125. package/test/player_integration.js +224 -82
  126. package/test/player_unit.js +92 -2
  127. package/test/polyfill/patchedmediakeys_apple_unit.js +124 -0
  128. package/test/test/assets/id3-metadata.aac +0 -0
  129. package/test/test/assets/id3-metadata.ts +0 -0
  130. package/test/test/assets/thumbnails-sprites.vtt +10 -0
  131. package/test/test/assets/thumbnails.vtt +10 -0
  132. package/test/test/util/dash_parser_util.js +2 -0
  133. package/test/test/util/fake_ad.js +21 -0
  134. package/test/test/util/fake_media_source_engine.js +7 -0
  135. package/test/test/util/id3_generator.js +100 -0
  136. package/test/test/util/manifest_generator.js +3 -6
  137. package/test/test/util/simple_fakes.js +7 -0
  138. package/test/test/util/stream_generator.js +38 -0
  139. package/test/test/util/test_scheme.js +43 -14
  140. package/test/text/vtt_text_parser_unit.js +11 -0
  141. package/test/ui/text_displayer_layout_unit.js +14 -0
  142. package/test/util/id3_utils_unit.js +116 -0
  143. package/test/util/ts_parser_unit.js +77 -0
  144. package/ui/controls.js +4 -1
  145. package/ui/less/containers.less +7 -0
  146. package/ui/settings_menu.js +11 -2
  147. package/ui/ui.js +43 -4
@@ -23,6 +23,8 @@ describe('HlsParser', () => {
23
23
  let parser;
24
24
  /** @type {!jasmine.Spy} */
25
25
  let onEventSpy;
26
+ /** @type {!jasmine.Spy} */
27
+ let newDrmInfoSpy;
26
28
  /** @type {shaka.extern.ManifestParser.PlayerInterface} */
27
29
  let playerInterface;
28
30
  /** @type {shaka.extern.ManifestConfiguration} */
@@ -86,6 +88,7 @@ describe('HlsParser', () => {
86
88
 
87
89
  config = shaka.util.PlayerConfiguration.createDefault().manifest;
88
90
  onEventSpy = jasmine.createSpy('onEvent');
91
+ newDrmInfoSpy = jasmine.createSpy('newDrmInfo');
89
92
  playerInterface = {
90
93
  modifyManifestRequest: (request, manifestInfo) => {},
91
94
  modifySegmentRequest: (request, segmentInfo) => {},
@@ -99,6 +102,7 @@ describe('HlsParser', () => {
99
102
  isAutoLowLatencyMode: () => false,
100
103
  enableLowLatencyMode: () => {},
101
104
  updateDuration: () => {},
105
+ newDrmInfo: shaka.test.Util.spyFunc(newDrmInfoSpy),
102
106
  };
103
107
 
104
108
  parser = new shaka.hls.HlsParser();
@@ -109,17 +113,18 @@ describe('HlsParser', () => {
109
113
  * @param {string} master
110
114
  * @param {string} media
111
115
  * @param {shaka.extern.Manifest} manifest
116
+ * @param {string=} media2
112
117
  * @return {!Promise.<shaka.extern.Manifest>}
113
118
  */
114
- async function testHlsParser(master, media, manifest) {
119
+ async function testHlsParser(master, media, manifest, media2) {
115
120
  fakeNetEngine
116
121
  .setResponseText('test:/master', master)
117
122
  .setResponseText('test:/audio', media)
118
- .setResponseText('test:/audio2', media)
123
+ .setResponseText('test:/audio2', media2 || media)
119
124
  .setResponseText('test:/video', media)
120
- .setResponseText('test:/video2', media)
125
+ .setResponseText('test:/video2', media2 || media)
121
126
  .setResponseText('test:/text', media)
122
- .setResponseText('test:/text2', media)
127
+ .setResponseText('test:/text2', media2 || media)
123
128
  .setResponseText('test:/main.vtt', vttText)
124
129
  .setResponseValue('test:/init.mp4', initSegmentData)
125
130
  .setResponseValue('test:/init2.mp4', initSegmentData)
@@ -129,10 +134,30 @@ describe('HlsParser', () => {
129
134
  .setResponseValue('test:/selfInit.mp4', selfInitializingSegmentData);
130
135
 
131
136
  const actual = await parser.start('test:/master', playerInterface);
137
+ await loadAllStreamsFor(actual);
132
138
  expect(actual).toEqual(manifest);
133
139
  return actual;
134
140
  }
135
141
 
142
+ /** @param {!shaka.extern.Manifest} manifest */
143
+ async function loadAllStreamsFor(manifest) {
144
+ const promises = [];
145
+ for (const variant of manifest.variants) {
146
+ for (const stream of [variant.video, variant.audio]) {
147
+ if (stream) {
148
+ promises.push(stream.createSegmentIndex());
149
+ }
150
+ }
151
+ }
152
+ for (const text of manifest.textStreams) {
153
+ promises.push(text.createSegmentIndex());
154
+ }
155
+ for (const image of manifest.imageStreams) {
156
+ promises.push(image.createSegmentIndex());
157
+ }
158
+ await Promise.all(promises);
159
+ }
160
+
136
161
  it('parses manifest attributes', async () => {
137
162
  const master = [
138
163
  '#EXTM3U\n',
@@ -205,6 +230,7 @@ describe('HlsParser', () => {
205
230
  .setResponseValue('test:/main.mp4', segmentData);
206
231
 
207
232
  const actual = await parser.start('test:/master', playerInterface);
233
+ await loadAllStreamsFor(actual);
208
234
  expect(actual).toEqual(manifest);
209
235
  });
210
236
 
@@ -280,6 +306,7 @@ describe('HlsParser', () => {
280
306
  .setResponseValue('test:/main.mp4', segmentData);
281
307
 
282
308
  const actual = await parser.start('test:/master', playerInterface);
309
+ await loadAllStreamsFor(actual);
283
310
  expect(actual).toEqual(manifest);
284
311
  });
285
312
 
@@ -321,6 +348,7 @@ describe('HlsParser', () => {
321
348
  .setResponseValue('test:/main.mp4', segmentData);
322
349
 
323
350
  const actual = await parser.start('test:/master', playerInterface);
351
+ await loadAllStreamsFor(actual);
324
352
  expect(actual).toEqual(manifest);
325
353
  });
326
354
 
@@ -572,6 +600,33 @@ describe('HlsParser', () => {
572
600
  await testHlsParser(master, media, manifest);
573
601
  });
574
602
 
603
+ it('accepts mp4a.40.34 codec as audio/mpeg', async () => {
604
+ const master = [
605
+ '#EXTM3U\n',
606
+ '#EXT-X-STREAM-INF:BANDWIDTH=63701,CODECS="mp4a.40.34"\n',
607
+ 'audio\n',
608
+ ].join('');
609
+
610
+ const media = [
611
+ '#EXTM3U\n',
612
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
613
+ '#EXTINF:5,\n',
614
+ 'main.mp3',
615
+ ].join('');
616
+
617
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
618
+ manifest.anyTimeline();
619
+ manifest.addPartialVariant((variant) => {
620
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
621
+ stream.mime('audio/mpeg', '');
622
+ });
623
+ });
624
+ manifest.sequenceMode = true;
625
+ });
626
+
627
+ await testHlsParser(master, media, manifest);
628
+ });
629
+
575
630
  it('parses audio+video variant with closed captions', async () => {
576
631
  const master = [
577
632
  '#EXTM3U\n',
@@ -1129,6 +1184,7 @@ describe('HlsParser', () => {
1129
1184
  .setResponseValue('test:/main.mp4', segmentData);
1130
1185
 
1131
1186
  const actual = await parser.start('test:/master', playerInterface);
1187
+ await loadAllStreamsFor(actual);
1132
1188
  expect(actual).toEqual(manifest);
1133
1189
  });
1134
1190
 
@@ -1200,6 +1256,7 @@ describe('HlsParser', () => {
1200
1256
  .setResponseValue('test:/main.mp4', segmentData);
1201
1257
 
1202
1258
  const actual = await parser.start('test:/master', playerInterface);
1259
+ await loadAllStreamsFor(actual);
1203
1260
  expect(actual).toEqual(manifest);
1204
1261
  });
1205
1262
 
@@ -1263,6 +1320,7 @@ describe('HlsParser', () => {
1263
1320
  .setResponseValue('test:/main.mp4', segmentData);
1264
1321
 
1265
1322
  const actual = await parser.start('test:/master', playerInterface);
1323
+ await loadAllStreamsFor(actual);
1266
1324
  expect(actual).toEqual(manifest);
1267
1325
  });
1268
1326
 
@@ -1324,6 +1382,7 @@ describe('HlsParser', () => {
1324
1382
  .setResponseValue('test:/main.mp4', segmentData);
1325
1383
 
1326
1384
  const actual = await parser.start('test:/master', playerInterface);
1385
+ await loadAllStreamsFor(actual);
1327
1386
  expect(actual).toEqual(manifest);
1328
1387
  });
1329
1388
 
@@ -1387,6 +1446,7 @@ describe('HlsParser', () => {
1387
1446
  .setResponseValue('test:/main.mp4', segmentData);
1388
1447
 
1389
1448
  const actual = await parser.start('test:/master', playerInterface);
1449
+ await loadAllStreamsFor(actual);
1390
1450
  expect(actual).toEqual(manifest);
1391
1451
  });
1392
1452
 
@@ -1442,6 +1502,7 @@ describe('HlsParser', () => {
1442
1502
  .setResponseValue('test:/main.mp4', segmentData);
1443
1503
 
1444
1504
  const actual = await parser.start('test:/master', playerInterface);
1505
+ await loadAllStreamsFor(actual);
1445
1506
  // Duration should be the minimum of the streams, but ignore the text
1446
1507
  // stream.
1447
1508
  const timeline = actual.presentationTimeline;
@@ -1524,6 +1585,7 @@ describe('HlsParser', () => {
1524
1585
  .setResponseValue('test:/main.mp4', segmentData);
1525
1586
 
1526
1587
  const actual = await parser.start('test:/master', playerInterface);
1588
+ await loadAllStreamsFor(actual);
1527
1589
 
1528
1590
  expect(actual.imageStreams.length).toBe(1);
1529
1591
  expect(actual.textStreams.length).toBe(1);
@@ -1596,6 +1658,7 @@ describe('HlsParser', () => {
1596
1658
  .setResponseValue('test:/main.mp4', segmentData);
1597
1659
 
1598
1660
  const actual = await parser.start('test:/master', playerInterface);
1661
+ await loadAllStreamsFor(actual);
1599
1662
 
1600
1663
  expect(actual.variants.length).toBe(1);
1601
1664
 
@@ -1721,6 +1784,7 @@ describe('HlsParser', () => {
1721
1784
  parser.configure(config);
1722
1785
 
1723
1786
  const actual = await parser.start('test:/master', playerInterface);
1787
+ await loadAllStreamsFor(actual);
1724
1788
  const variant = actual.variants[0];
1725
1789
  expect(variant.audio).toBe(null);
1726
1790
  expect(variant.video).toBeTruthy();
@@ -1796,6 +1860,7 @@ describe('HlsParser', () => {
1796
1860
  parser.configure(config);
1797
1861
 
1798
1862
  const actual = await parser.start('test:/master', playerInterface);
1863
+ await loadAllStreamsFor(actual);
1799
1864
  const variant = actual.variants[0];
1800
1865
  expect(variant.audio).toBeTruthy();
1801
1866
  expect(variant.video).toBe(null);
@@ -1871,6 +1936,7 @@ describe('HlsParser', () => {
1871
1936
  parser.configure(config);
1872
1937
 
1873
1938
  const actual = await parser.start('test:/master', playerInterface);
1939
+ await loadAllStreamsFor(actual);
1874
1940
  const stream = actual.textStreams[0];
1875
1941
  expect(stream).toBeUndefined();
1876
1942
  });
@@ -1945,6 +2011,7 @@ describe('HlsParser', () => {
1945
2011
  parser.configure(config);
1946
2012
 
1947
2013
  const actual = await parser.start('test:/master', playerInterface);
2014
+ await loadAllStreamsFor(actual);
1948
2015
  const stream = actual.imageStreams[0];
1949
2016
  expect(stream).toBeUndefined();
1950
2017
  });
@@ -1989,6 +2056,7 @@ describe('HlsParser', () => {
1989
2056
  .setResponseValue('test:/main.mp4', segmentData);
1990
2057
 
1991
2058
  const actual = await parser.start('test:/master', playerInterface);
2059
+ await loadAllStreamsFor(actual);
1992
2060
  expect(actual).toEqual(manifest);
1993
2061
  });
1994
2062
 
@@ -2040,6 +2108,7 @@ describe('HlsParser', () => {
2040
2108
  .setResponseValue('test:/main.mp4', segmentData);
2041
2109
 
2042
2110
  const actual = await parser.start('test:/master', playerInterface);
2111
+ await loadAllStreamsFor(actual);
2043
2112
  expect(actual).toEqual(manifest);
2044
2113
  });
2045
2114
 
@@ -2082,6 +2151,7 @@ describe('HlsParser', () => {
2082
2151
  .setResponseValue('test:/main.mp4', segmentData);
2083
2152
 
2084
2153
  const actual = await parser.start('test:/master', playerInterface);
2154
+ await loadAllStreamsFor(actual);
2085
2155
  expect(actual).toEqual(manifest);
2086
2156
  });
2087
2157
 
@@ -2148,6 +2218,7 @@ describe('HlsParser', () => {
2148
2218
  .setResponseValue('test:/main.mp4', segmentData);
2149
2219
 
2150
2220
  const actual = await parser.start('test:/master', playerInterface);
2221
+ await loadAllStreamsFor(actual);
2151
2222
  expect(actual).toEqual(manifest);
2152
2223
  }
2153
2224
 
@@ -2293,6 +2364,7 @@ describe('HlsParser', () => {
2293
2364
 
2294
2365
  config.hls.ignoreTextStreamFailures = true;
2295
2366
  const actual = await parser.start('test:/master', playerInterface);
2367
+ await loadAllStreamsFor(actual);
2296
2368
  expect(actual).toEqual(manifest);
2297
2369
  });
2298
2370
 
@@ -2331,6 +2403,7 @@ describe('HlsParser', () => {
2331
2403
 
2332
2404
  config.hls.ignoreImageStreamFailures = true;
2333
2405
  const actual = await parser.start('test:/master', playerInterface);
2406
+ await loadAllStreamsFor(actual);
2334
2407
  expect(actual).toEqual(manifest);
2335
2408
  });
2336
2409
 
@@ -2396,6 +2469,7 @@ describe('HlsParser', () => {
2396
2469
 
2397
2470
  const actual =
2398
2471
  await parser.start('test:/host/master.m3u8', playerInterface);
2472
+ await loadAllStreamsFor(actual);
2399
2473
  const video = actual.variants[0].video;
2400
2474
  const audio = actual.variants[0].audio;
2401
2475
 
@@ -2594,6 +2668,7 @@ describe('HlsParser', () => {
2594
2668
  .setResponseValue('test:/selfInit.mp4', selfInitializingSegmentData);
2595
2669
 
2596
2670
  const actual = await parser.start('test:/master', playerInterface);
2671
+ await loadAllStreamsFor(actual);
2597
2672
  expect(actual).toEqual(manifest);
2598
2673
  });
2599
2674
 
@@ -2638,7 +2713,8 @@ describe('HlsParser', () => {
2638
2713
  shaka.util.Error.Severity.CRITICAL,
2639
2714
  shaka.util.Error.Category.MANIFEST,
2640
2715
  shaka.util.Error.Code.NO_WEB_CRYPTO_API));
2641
- await expectAsync(parser.start('test:/master', playerInterface))
2716
+ const actual = await parser.start('test:/master', playerInterface);
2717
+ await expectAsync(loadAllStreamsFor(actual))
2642
2718
  .toBeRejectedWith(expectedError);
2643
2719
  } finally {
2644
2720
  Object.defineProperty(window, 'crypto', {
@@ -2648,7 +2724,6 @@ describe('HlsParser', () => {
2648
2724
  }
2649
2725
  });
2650
2726
 
2651
-
2652
2727
  it('constructs DrmInfo for Widevine', async () => {
2653
2728
  const master = [
2654
2729
  '#EXTM3U\n',
@@ -2692,6 +2767,7 @@ describe('HlsParser', () => {
2692
2767
  });
2693
2768
 
2694
2769
  await testHlsParser(master, media, manifest);
2770
+ expect(newDrmInfoSpy).toHaveBeenCalled();
2695
2771
  });
2696
2772
 
2697
2773
  it('constructs DrmInfo for PlayReady', async () => {
@@ -2732,6 +2808,7 @@ describe('HlsParser', () => {
2732
2808
  });
2733
2809
 
2734
2810
  await testHlsParser(master, media, manifest);
2811
+ expect(newDrmInfoSpy).toHaveBeenCalled();
2735
2812
  });
2736
2813
 
2737
2814
  it('constructs DrmInfo for FairPlay', async () => {
@@ -2769,6 +2846,229 @@ describe('HlsParser', () => {
2769
2846
  });
2770
2847
 
2771
2848
  await testHlsParser(master, media, manifest);
2849
+ expect(newDrmInfoSpy).toHaveBeenCalled();
2850
+ });
2851
+
2852
+ it('constructs DrmInfo for ClearKey with explicit KEYFORMAT', async () => {
2853
+ const master = [
2854
+ '#EXTM3U\n',
2855
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
2856
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
2857
+ 'video\n',
2858
+ ].join('');
2859
+
2860
+ const media = [
2861
+ '#EXTM3U\n',
2862
+ '#EXT-X-TARGETDURATION:6\n',
2863
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2864
+ '#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
2865
+ 'KEYFORMAT="identity",',
2866
+ 'URI="key.bin",\n',
2867
+ '#EXT-X-MAP:URI="init.mp4"\n',
2868
+ '#EXTINF:5,\n',
2869
+ 'main.mp4',
2870
+ ].join('');
2871
+
2872
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
2873
+ manifest.anyTimeline();
2874
+ manifest.addPartialVariant((variant) => {
2875
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
2876
+ stream.encrypted = true;
2877
+ stream.addDrmInfo('org.w3.clearkey');
2878
+ });
2879
+ });
2880
+ manifest.sequenceMode = true;
2881
+ });
2882
+
2883
+ await testHlsParser(master, media, manifest);
2884
+ expect(newDrmInfoSpy).toHaveBeenCalled();
2885
+ });
2886
+
2887
+ it('constructs DrmInfo for ClearKey without explicit KEYFORMAT', async () => {
2888
+ const master = [
2889
+ '#EXTM3U\n',
2890
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
2891
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
2892
+ 'video\n',
2893
+ ].join('');
2894
+
2895
+ const media = [
2896
+ '#EXTM3U\n',
2897
+ '#EXT-X-TARGETDURATION:6\n',
2898
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2899
+ '#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
2900
+ 'URI="key.bin",\n',
2901
+ '#EXT-X-MAP:URI="init.mp4"\n',
2902
+ '#EXTINF:5,\n',
2903
+ 'main.mp4',
2904
+ ].join('');
2905
+
2906
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
2907
+ manifest.anyTimeline();
2908
+ manifest.addPartialVariant((variant) => {
2909
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
2910
+ stream.encrypted = true;
2911
+ stream.addDrmInfo('org.w3.clearkey');
2912
+ });
2913
+ });
2914
+ manifest.sequenceMode = true;
2915
+ });
2916
+
2917
+ await testHlsParser(master, media, manifest);
2918
+ expect(newDrmInfoSpy).toHaveBeenCalled();
2919
+ });
2920
+
2921
+ describe('constructs DrmInfo with EXT-X-SESSION-KEY', () => {
2922
+ it('for Widevine', async () => {
2923
+ const initDataBase64 =
2924
+ 'dGhpcyBpbml0IGRhdGEgY29udGFpbnMgaGlkZGVuIHNlY3JldHMhISE=';
2925
+
2926
+ const keyId = 'abc123';
2927
+
2928
+ const master = [
2929
+ '#EXTM3U\n',
2930
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
2931
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
2932
+ 'video\n',
2933
+ '#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
2934
+ 'KEYID=0X' + keyId + ',',
2935
+ 'KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",',
2936
+ 'URI="data:text/plain;base64,',
2937
+ initDataBase64, '",\n',
2938
+ ].join('');
2939
+
2940
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
2941
+ manifest.anyTimeline();
2942
+ manifest.addPartialVariant((variant) => {
2943
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
2944
+ stream.addDrmInfo('com.widevine.alpha', (drmInfo) => {
2945
+ drmInfo.addCencInitData(initDataBase64);
2946
+ drmInfo.keyIds.add(keyId);
2947
+ });
2948
+ });
2949
+ });
2950
+ manifest.sequenceMode = true;
2951
+ });
2952
+
2953
+ fakeNetEngine.setResponseText('test:/master', master);
2954
+
2955
+ const actual = await parser.start('test:/master', playerInterface);
2956
+ expect(actual).toEqual(manifest);
2957
+ });
2958
+
2959
+ it('for PlayReady', async () => {
2960
+ const initDataBase64 =
2961
+ 'AAAAKXBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAAlQbGF5cmVhZHk=';
2962
+
2963
+ const master = [
2964
+ '#EXTM3U\n',
2965
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
2966
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
2967
+ 'video\n',
2968
+ '#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
2969
+ 'KEYFORMAT="com.microsoft.playready",',
2970
+ 'URI="data:text/plain;base64,UGxheXJlYWR5",\n',
2971
+ ].join('');
2972
+
2973
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
2974
+ manifest.anyTimeline();
2975
+ manifest.addPartialVariant((variant) => {
2976
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
2977
+ stream.addDrmInfo('com.microsoft.playready', (drmInfo) => {
2978
+ drmInfo.addCencInitData(initDataBase64);
2979
+ });
2980
+ });
2981
+ });
2982
+ manifest.sequenceMode = true;
2983
+ });
2984
+
2985
+ fakeNetEngine.setResponseText('test:/master', master);
2986
+
2987
+ const actual = await parser.start('test:/master', playerInterface);
2988
+ expect(actual).toEqual(manifest);
2989
+ });
2990
+
2991
+ it('for FairPlay', async () => {
2992
+ const master = [
2993
+ '#EXTM3U\n',
2994
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
2995
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
2996
+ 'video\n',
2997
+ '#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
2998
+ 'KEYFORMAT="com.apple.streamingkeydelivery",',
2999
+ 'URI="skd://f93d4e700d7ddde90529a27735d9e7cb",\n',
3000
+ ].join('');
3001
+
3002
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3003
+ manifest.anyTimeline();
3004
+ manifest.addPartialVariant((variant) => {
3005
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
3006
+ stream.addDrmInfo('com.apple.fps', (drmInfo) => {
3007
+ drmInfo.addInitData('sinf', new Uint8Array(0));
3008
+ });
3009
+ });
3010
+ });
3011
+ manifest.sequenceMode = true;
3012
+ });
3013
+
3014
+ fakeNetEngine.setResponseText('test:/master', master);
3015
+
3016
+ const actual = await parser.start('test:/master', playerInterface);
3017
+ expect(actual).toEqual(manifest);
3018
+ });
3019
+
3020
+ it('for ClearKey with explicit KEYFORMAT', async () => {
3021
+ const master = [
3022
+ '#EXTM3U\n',
3023
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
3024
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
3025
+ 'video\n',
3026
+ '#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
3027
+ 'KEYFORMAT="identity",',
3028
+ 'URI="key.bin",\n',
3029
+ ].join('');
3030
+
3031
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3032
+ manifest.anyTimeline();
3033
+ manifest.addPartialVariant((variant) => {
3034
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
3035
+ stream.addDrmInfo('org.w3.clearkey');
3036
+ });
3037
+ });
3038
+ manifest.sequenceMode = true;
3039
+ });
3040
+
3041
+ fakeNetEngine.setResponseText('test:/master', master);
3042
+
3043
+ const actual = await parser.start('test:/master', playerInterface);
3044
+ expect(actual).toEqual(manifest);
3045
+ });
3046
+
3047
+ it('for ClearKey without explicit KEYFORMAT', async () => {
3048
+ const master = [
3049
+ '#EXTM3U\n',
3050
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
3051
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
3052
+ 'video\n',
3053
+ '#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
3054
+ 'URI="key.bin",\n',
3055
+ ].join('');
3056
+
3057
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3058
+ manifest.anyTimeline();
3059
+ manifest.addPartialVariant((variant) => {
3060
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
3061
+ stream.addDrmInfo('org.w3.clearkey');
3062
+ });
3063
+ });
3064
+ manifest.sequenceMode = true;
3065
+ });
3066
+
3067
+ fakeNetEngine.setResponseText('test:/master', master);
3068
+
3069
+ const actual = await parser.start('test:/master', playerInterface);
3070
+ expect(actual).toEqual(manifest);
3071
+ });
2772
3072
  });
2773
3073
 
2774
3074
  it('falls back to mp4 if HEAD request fails', async () => {
@@ -2817,8 +3117,9 @@ describe('HlsParser', () => {
2817
3117
  * @param {string} master
2818
3118
  * @param {string} media
2819
3119
  * @param {!shaka.util.Error} error
3120
+ * @param {boolean=} onCreateSegmentIndex
2820
3121
  */
2821
- async function verifyError(master, media, error) {
3122
+ async function verifyError(master, media, error, onCreateSegmentIndex) {
2822
3123
  fakeNetEngine
2823
3124
  .setResponseText('test:/master', master)
2824
3125
  .setResponseText('test:/audio', media)
@@ -2829,8 +3130,14 @@ describe('HlsParser', () => {
2829
3130
  .setResponseValue('data:text/plain;base64,AAECAwQFBgcICQoLDA0ODw==',
2830
3131
  aes128Key);
2831
3132
 
2832
- await expectAsync(parser.start('test:/master', playerInterface))
2833
- .toBeRejectedWith(Util.jasmineError(error));
3133
+ if (onCreateSegmentIndex) {
3134
+ const actual = await parser.start('test:/master', playerInterface);
3135
+ await expectAsync(loadAllStreamsFor(actual))
3136
+ .toBeRejectedWith(Util.jasmineError(error));
3137
+ } else {
3138
+ await expectAsync(parser.start('test:/master', playerInterface))
3139
+ .toBeRejectedWith(Util.jasmineError(error));
3140
+ }
2834
3141
  }
2835
3142
 
2836
3143
  it('if unable to guess codecs', async () => {
@@ -2967,7 +3274,7 @@ describe('HlsParser', () => {
2967
3274
  shaka.util.Error.Category.MANIFEST,
2968
3275
  Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
2969
3276
 
2970
- await verifyError(master, media, error);
3277
+ await verifyError(master, media, error, /* onCreateSegmentIndex= */ true);
2971
3278
  });
2972
3279
 
2973
3280
  describe('if required tags are missing', () => {
@@ -2983,7 +3290,8 @@ describe('HlsParser', () => {
2983
3290
  Code.HLS_REQUIRED_TAG_MISSING,
2984
3291
  tagName);
2985
3292
 
2986
- await verifyError(master, media, error);
3293
+ await verifyError(
3294
+ master, media, error, /* onCreateSegmentIndex= */ true);
2987
3295
  }
2988
3296
 
2989
3297
  it('EXTINF', async () => {
@@ -3270,6 +3578,67 @@ describe('HlsParser', () => {
3270
3578
  await testHlsParser(master, media, manifest);
3271
3579
  });
3272
3580
 
3581
+ // https://github.com/shaka-project/shaka-player/issues/4308
3582
+ it('handles unaligned streams', async () => {
3583
+ const master = [
3584
+ '#EXTM3U\n',
3585
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
3586
+ 'RESOLUTION=1280x720,FRAME-RATE=30\n',
3587
+ 'video\n',
3588
+ '#EXT-X-STREAM-INF:BANDWIDTH=400,CODECS="avc1",',
3589
+ 'RESOLUTION=1920x1080,FRAME-RATE=30\n',
3590
+ 'video2\n',
3591
+ ].join('');
3592
+
3593
+ const media = [
3594
+ '#EXTM3U\n',
3595
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
3596
+ '#EXTINF:5\n',
3597
+ 'main.mp4\n',
3598
+ '#EXTINF:5\n',
3599
+ 'main.mp4',
3600
+ ].join('');
3601
+
3602
+ const media2 = [
3603
+ '#EXTM3U\n',
3604
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
3605
+ '#EXTINF:4\n',
3606
+ 'main.mp4\n',
3607
+ '#EXTINF:5\n',
3608
+ 'main.mp4\n',
3609
+ '#EXTINF:1\n',
3610
+ 'main.mp4',
3611
+ ].join('');
3612
+
3613
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3614
+ manifest.anyTimeline();
3615
+ manifest.addPartialVariant((variant) => {
3616
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
3617
+ stream.size(1280, 720);
3618
+ });
3619
+ });
3620
+ manifest.addPartialVariant((variant) => {
3621
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
3622
+ stream.size(1920, 1080);
3623
+ });
3624
+ });
3625
+ manifest.sequenceMode = true;
3626
+ });
3627
+
3628
+ const actual = await testHlsParser(master, media, manifest, media2);
3629
+
3630
+ const videoSegments =
3631
+ Array.from(actual.variants[0].video.segmentIndex || '');
3632
+ expect(videoSegments[0].endTime).toBe(5);
3633
+ expect(videoSegments[1].endTime).toBe(10);
3634
+
3635
+ const videoSegments2 =
3636
+ Array.from(actual.variants[1].video.segmentIndex || '');
3637
+ expect(videoSegments2[0].endTime).toBe(4);
3638
+ expect(videoSegments2[1].endTime).toBe(9);
3639
+ expect(videoSegments2[2].endTime).toBe(10);
3640
+ });
3641
+
3273
3642
  // Issue #1875
3274
3643
  it('ignores audio groups on audio-only content', async () => {
3275
3644
  // NOTE: To reproduce the original issue accurately, the two audio playlist
@@ -3317,6 +3686,7 @@ describe('HlsParser', () => {
3317
3686
  .setResponseValue('test:/main.mp4', segmentData);
3318
3687
 
3319
3688
  const actual = await parser.start('test:/master', playerInterface);
3689
+ await loadAllStreamsFor(actual);
3320
3690
  expect(actual.variants.length).toBe(1);
3321
3691
  expect(actual).toEqual(manifest);
3322
3692
  });
@@ -3349,6 +3719,7 @@ describe('HlsParser', () => {
3349
3719
 
3350
3720
  const actual =
3351
3721
  await parser.start('test:/host/master.m3u8', playerInterface);
3722
+ await loadAllStreamsFor(actual);
3352
3723
  const video = actual.variants[0].video;
3353
3724
  const audio = actual.variants[0].audio;
3354
3725
 
@@ -3396,6 +3767,7 @@ describe('HlsParser', () => {
3396
3767
 
3397
3768
  const actual =
3398
3769
  await parser.start('test:/host/master.m3u8', playerInterface);
3770
+ await loadAllStreamsFor(actual);
3399
3771
  const video = actual.variants[0].video;
3400
3772
  const audio = actual.variants[0].audio;
3401
3773
 
@@ -3443,6 +3815,7 @@ describe('HlsParser', () => {
3443
3815
 
3444
3816
  const actual =
3445
3817
  await parser.start('test:/host/master.m3u8', playerInterface);
3818
+ await loadAllStreamsFor(actual);
3446
3819
  const video = actual.variants[0].video;
3447
3820
  const audio = actual.variants[0].audio;
3448
3821
 
@@ -3657,7 +4030,9 @@ describe('HlsParser', () => {
3657
4030
  });
3658
4031
  });
3659
4032
 
3660
- await testHlsParser(media, '', manifest);
4033
+ const actualManifest = await testHlsParser(media, '', manifest);
4034
+
4035
+ expect(actualManifest.presentationTimeline.getDuration()).toBe(5);
3661
4036
  });
3662
4037
 
3663
4038
  it('honors hls.mediaPlaylistFullMimeType', async () => {
@@ -3687,6 +4062,58 @@ describe('HlsParser', () => {
3687
4062
  await testHlsParser(media, '', manifest);
3688
4063
  });
3689
4064
 
4065
+ it('honors hls.mediaPlaylistFullMimeType but detects AAC', async () => {
4066
+ const media = [
4067
+ '#EXTM3U\n',
4068
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
4069
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
4070
+ '#EXTINF:5,\n',
4071
+ '#EXT-X-BYTERANGE:121090@616\n',
4072
+ 'main.aac',
4073
+ ].join('');
4074
+
4075
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
4076
+ parser.configure(config);
4077
+
4078
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
4079
+ manifest.sequenceMode = true;
4080
+ manifest.anyTimeline();
4081
+ manifest.addPartialVariant((variant) => {
4082
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
4083
+ stream.mime('audio/aac');
4084
+ });
4085
+ });
4086
+ });
4087
+
4088
+ await testHlsParser(media, '', manifest);
4089
+ });
4090
+
4091
+ it('honors hls.mediaPlaylistFullMimeType but detects MPEG', async () => {
4092
+ const media = [
4093
+ '#EXTM3U\n',
4094
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
4095
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
4096
+ '#EXTINF:5,\n',
4097
+ '#EXT-X-BYTERANGE:121090@616\n',
4098
+ 'main.mp3',
4099
+ ].join('');
4100
+
4101
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
4102
+ parser.configure(config);
4103
+
4104
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
4105
+ manifest.sequenceMode = true;
4106
+ manifest.anyTimeline();
4107
+ manifest.addPartialVariant((variant) => {
4108
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
4109
+ stream.mime('audio/mpeg');
4110
+ });
4111
+ });
4112
+ });
4113
+
4114
+ await testHlsParser(media, '', manifest);
4115
+ });
4116
+
3690
4117
  it('syncs on sequence with ignoreManifestProgramDateTime', async () => {
3691
4118
  const config = shaka.util.PlayerConfiguration.createDefault().manifest;
3692
4119
  config.hls.ignoreManifestProgramDateTime = true;
@@ -3774,4 +4201,65 @@ describe('HlsParser', () => {
3774
4201
  'test:/audio4.mp4',
3775
4202
  ]);
3776
4203
  });
4204
+
4205
+ it('lazy-loads TS content without filtering it out', async () => {
4206
+ const master = [
4207
+ '#EXTM3U\n',
4208
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",URI="audio"\n',
4209
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
4210
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
4211
+ 'video\n',
4212
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
4213
+ 'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud1"\n',
4214
+ 'video2\n',
4215
+ ].join('');
4216
+
4217
+ const video = [
4218
+ '#EXTM3U\n',
4219
+ '#EXT-X-TARGETDURATION:5\n',
4220
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
4221
+ '#EXT-X-MEDIA-SEQUENCE:1\n',
4222
+ '#EXTINF:5,\n',
4223
+ 'video1.ts\n',
4224
+ '#EXTINF:5,\n',
4225
+ 'video2.ts\n',
4226
+ ].join('');
4227
+
4228
+ const audio = [
4229
+ '#EXTM3U\n',
4230
+ '#EXT-X-TARGETDURATION:5\n',
4231
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
4232
+ '#EXT-X-MEDIA-SEQUENCE:3\n',
4233
+ '#EXTINF:5,\n',
4234
+ 'audio1.ts\n',
4235
+ '#EXTINF:5,\n',
4236
+ 'audio2.ts\n',
4237
+ ].join('');
4238
+
4239
+ fakeNetEngine
4240
+ .setResponseText('test:/master', master)
4241
+ .setResponseText('test:/audio', audio)
4242
+ .setResponseText('test:/video', video)
4243
+ .setResponseText('test:/video2', video)
4244
+ .setResponseValue('test:/init.mp4', initSegmentData);
4245
+
4246
+ const actualManifest = await parser.start('test:/master', playerInterface);
4247
+ expect(actualManifest.variants.length).toBe(2);
4248
+
4249
+ const actualVideo0 = actualManifest.variants[0].video;
4250
+ const actualVideo1 = actualManifest.variants[1].video;
4251
+
4252
+ // Before loading, all MIME types agree, and are defaulted to video/mp4.
4253
+ expect(actualVideo0.mimeType).toBe('video/mp4');
4254
+ expect(actualVideo1.mimeType).toBe('video/mp4');
4255
+
4256
+ await actualVideo0.createSegmentIndex();
4257
+
4258
+ // After loading just ONE stream, all MIME types agree again, and have been
4259
+ // updated to reflect the TS content found inside the loaded playlist.
4260
+ // This is how we avoid having the unloaded tracks filtered out during
4261
+ // startup.
4262
+ expect(actualVideo0.mimeType).toBe('video/mp2t');
4263
+ expect(actualVideo1.mimeType).toBe('video/mp2t');
4264
+ });
3777
4265
  });