shaka-player 4.2.5 → 4.3.1

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 (143) hide show
  1. package/.github/workflows/build-and-test.yaml +7 -0
  2. package/.github/workflows/sync-labels.yaml +1 -1
  3. package/.github/workflows/update-issues.yaml +1 -1
  4. package/AUTHORS +4 -0
  5. package/CHANGELOG.md +114 -74
  6. package/CONTRIBUTORS +4 -0
  7. package/README.md +14 -0
  8. package/conditional/dummy_cast_proxy.js +120 -0
  9. package/demo/common/assets.js +58 -0
  10. package/demo/common/message_ids.js +22 -1
  11. package/demo/config.js +89 -5
  12. package/demo/demo.less +7 -1
  13. package/demo/index.html +8 -1
  14. package/demo/locales/en.json +21 -1
  15. package/demo/locales/source.json +82 -2
  16. package/demo/main.js +57 -1
  17. package/demo/search.js +2 -0
  18. package/demo/visualizer.js +337 -0
  19. package/demo/visualizer_button.js +103 -0
  20. package/dist/controls.css +1 -1
  21. package/dist/controls.css.map +1 -1
  22. package/dist/demo.compiled.debug.js +107 -88
  23. package/dist/demo.compiled.debug.map +4 -4
  24. package/dist/demo.compiled.js +107 -88
  25. package/dist/demo.compiled.map +4 -4
  26. package/dist/demo.css +1 -1
  27. package/dist/demo.css.map +1 -1
  28. package/dist/deps.js +21 -13
  29. package/dist/locales.js +31 -31
  30. package/dist/receiver.compiled.debug.js +75 -77
  31. package/dist/receiver.compiled.debug.map +3 -3
  32. package/dist/receiver.compiled.js +75 -77
  33. package/dist/receiver.compiled.map +3 -3
  34. package/dist/shaka-player.compiled.d.ts +181 -27
  35. package/dist/shaka-player.compiled.debug.d.ts +181 -27
  36. package/dist/shaka-player.compiled.debug.externs.js +169 -31
  37. package/dist/shaka-player.compiled.debug.js +510 -450
  38. package/dist/shaka-player.compiled.debug.map +4 -4
  39. package/dist/shaka-player.compiled.externs.js +169 -31
  40. package/dist/shaka-player.compiled.js +912 -874
  41. package/dist/shaka-player.compiled.map +4 -4
  42. package/dist/shaka-player.ui.d.ts +181 -27
  43. package/dist/shaka-player.ui.debug.d.ts +181 -27
  44. package/dist/shaka-player.ui.debug.externs.js +169 -31
  45. package/dist/shaka-player.ui.debug.js +533 -471
  46. package/dist/shaka-player.ui.debug.map +4 -4
  47. package/dist/shaka-player.ui.externs.js +169 -31
  48. package/dist/shaka-player.ui.js +1073 -1036
  49. package/dist/shaka-player.ui.map +4 -4
  50. package/docs/design/lcevc-architecture.png +0 -0
  51. package/docs/design/lcevc-demo.png +0 -0
  52. package/docs/design/lcevc-integration.md +72 -0
  53. package/docs/tutorials/fairplay.md +7 -0
  54. package/docs/tutorials/faq.md +1 -1
  55. package/docs/tutorials/ui.md +7 -7
  56. package/docs/tutorials/upgrade.md +6 -0
  57. package/externs/ima.js +13 -1
  58. package/externs/lcevc.js +81 -0
  59. package/externs/mux.js +1 -42
  60. package/externs/shaka/ads.js +11 -1
  61. package/externs/shaka/manifest_parser.js +5 -1
  62. package/externs/shaka/mp4_parser.js +3 -0
  63. package/externs/shaka/player.js +178 -15
  64. package/lib/abr/simple_abr_manager.js +7 -0
  65. package/lib/ads/ad_manager.js +0 -8
  66. package/lib/ads/client_side_ad.js +15 -0
  67. package/lib/ads/client_side_ad_manager.js +21 -8
  68. package/lib/ads/server_side_ad.js +15 -0
  69. package/lib/ads/server_side_ad_manager.js +3 -3
  70. package/lib/cast/cast_utils.js +2 -0
  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 +108 -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 +808 -384
  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_capabilities.js +36 -0
  81. package/lib/media/media_source_engine.js +113 -28
  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 +142 -12
  85. package/lib/media/transmuxer.js +78 -21
  86. package/lib/offline/storage.js +1 -0
  87. package/lib/player.js +456 -77
  88. package/lib/polyfill/media_capabilities.js +9 -5
  89. package/lib/polyfill/patchedmediakeys_apple.js +63 -4
  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/mime_utils.js +3 -6
  96. package/lib/util/mp4_box_parsers.js +39 -5
  97. package/lib/util/mp4_parser.js +35 -0
  98. package/lib/util/player_configuration.js +23 -1
  99. package/lib/util/stream_utils.js +5 -3
  100. package/lib/util/ts_parser.js +499 -0
  101. package/maintained-branches.md +2 -2
  102. package/package.json +7 -4
  103. package/roadmap.md +5 -3
  104. package/test/ads/ad_manager_unit.js +182 -29
  105. package/test/cast/cast_utils_unit.js +3 -1
  106. package/test/dash/dash_parser_content_protection_unit.js +1 -0
  107. package/test/dash/dash_parser_live_unit.js +1 -0
  108. package/test/dash/dash_parser_manifest_unit.js +93 -1
  109. package/test/dash/dash_parser_segment_base_unit.js +1 -0
  110. package/test/dash/dash_parser_segment_list_unit.js +1 -0
  111. package/test/dash/dash_parser_segment_template_unit.js +1 -0
  112. package/test/demo/demo_unit.js +2 -1
  113. package/test/hls/hls_live_unit.js +253 -19
  114. package/test/hls/hls_parser_unit.js +631 -12
  115. package/test/media/closed_caption_parser_unit.js +2 -1
  116. package/test/media/drm_engine_integration.js +3 -1
  117. package/test/media/media_source_engine_integration.js +71 -6
  118. package/test/media/media_source_engine_unit.js +10 -5
  119. package/test/media/streaming_engine_integration.js +4 -1
  120. package/test/media/streaming_engine_unit.js +144 -1
  121. package/test/media/transmuxer_integration.js +21 -10
  122. package/test/player_integration.js +224 -82
  123. package/test/player_unit.js +5 -2
  124. package/test/polyfill/media_capabilities_unit.js +21 -23
  125. package/test/polyfill/patchedmediakeys_apple_unit.js +130 -0
  126. package/test/test/assets/id3-metadata.aac +0 -0
  127. package/test/test/assets/id3-metadata.ts +0 -0
  128. package/test/test/assets/thumbnails-sprites.vtt +10 -0
  129. package/test/test/assets/thumbnails.vtt +10 -0
  130. package/test/test/util/dash_parser_util.js +2 -0
  131. package/test/test/util/fake_ad.js +21 -0
  132. package/test/test/util/fake_media_source_engine.js +7 -0
  133. package/test/test/util/id3_generator.js +100 -0
  134. package/test/test/util/manifest_generator.js +3 -6
  135. package/test/test/util/simple_fakes.js +4 -0
  136. package/test/test/util/stream_generator.js +38 -0
  137. package/test/test/util/test_scheme.js +43 -14
  138. package/test/text/vtt_text_parser_unit.js +11 -0
  139. package/test/util/id3_utils_unit.js +116 -0
  140. package/test/util/ts_parser_unit.js +77 -0
  141. package/third_party/closure-uri/uri.js +1 -1
  142. package/ui/less/containers.less +7 -0
  143. 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} */
@@ -87,6 +89,7 @@ describe('HlsParser', () => {
87
89
 
88
90
  config = shaka.util.PlayerConfiguration.createDefault().manifest;
89
91
  onEventSpy = jasmine.createSpy('onEvent');
92
+ newDrmInfoSpy = jasmine.createSpy('newDrmInfo');
90
93
  playerInterface = {
91
94
  modifyManifestRequest: (request, manifestInfo) => {},
92
95
  modifySegmentRequest: (request, segmentInfo) => {},
@@ -100,6 +103,7 @@ describe('HlsParser', () => {
100
103
  isAutoLowLatencyMode: () => false,
101
104
  enableLowLatencyMode: () => {},
102
105
  updateDuration: () => {},
106
+ newDrmInfo: shaka.test.Util.spyFunc(newDrmInfoSpy),
103
107
  };
104
108
 
105
109
  parser = new shaka.hls.HlsParser();
@@ -110,17 +114,18 @@ describe('HlsParser', () => {
110
114
  * @param {string} master
111
115
  * @param {string} media
112
116
  * @param {shaka.extern.Manifest} manifest
117
+ * @param {string=} media2
113
118
  * @return {!Promise.<shaka.extern.Manifest>}
114
119
  */
115
- async function testHlsParser(master, media, manifest) {
120
+ async function testHlsParser(master, media, manifest, media2) {
116
121
  fakeNetEngine
117
122
  .setResponseText('test:/master', master)
118
123
  .setResponseText('test:/audio', media)
119
- .setResponseText('test:/audio2', media)
124
+ .setResponseText('test:/audio2', media2 || media)
120
125
  .setResponseText('test:/video', media)
121
- .setResponseText('test:/video2', media)
126
+ .setResponseText('test:/video2', media2 || media)
122
127
  .setResponseText('test:/text', media)
123
- .setResponseText('test:/text2', media)
128
+ .setResponseText('test:/text2', media2 || media)
124
129
  .setResponseText('test:/main.vtt', vttText)
125
130
  .setResponseValue('test:/init.mp4', initSegmentData)
126
131
  .setResponseValue('test:/init2.mp4', initSegmentData)
@@ -130,10 +135,30 @@ describe('HlsParser', () => {
130
135
  .setResponseValue('test:/selfInit.mp4', selfInitializingSegmentData);
131
136
 
132
137
  const actual = await parser.start('test:/master', playerInterface);
138
+ await loadAllStreamsFor(actual);
133
139
  expect(actual).toEqual(manifest);
134
140
  return actual;
135
141
  }
136
142
 
143
+ /** @param {!shaka.extern.Manifest} manifest */
144
+ async function loadAllStreamsFor(manifest) {
145
+ const promises = [];
146
+ for (const variant of manifest.variants) {
147
+ for (const stream of [variant.video, variant.audio]) {
148
+ if (stream) {
149
+ promises.push(stream.createSegmentIndex());
150
+ }
151
+ }
152
+ }
153
+ for (const text of manifest.textStreams) {
154
+ promises.push(text.createSegmentIndex());
155
+ }
156
+ for (const image of manifest.imageStreams) {
157
+ promises.push(image.createSegmentIndex());
158
+ }
159
+ await Promise.all(promises);
160
+ }
161
+
137
162
  it('parses manifest attributes', async () => {
138
163
  const master = [
139
164
  '#EXTM3U\n',
@@ -206,6 +231,7 @@ describe('HlsParser', () => {
206
231
  .setResponseValue('test:/main.mp4', segmentData);
207
232
 
208
233
  const actual = await parser.start('test:/master', playerInterface);
234
+ await loadAllStreamsFor(actual);
209
235
  expect(actual).toEqual(manifest);
210
236
  });
211
237
 
@@ -281,6 +307,7 @@ describe('HlsParser', () => {
281
307
  .setResponseValue('test:/main.mp4', segmentData);
282
308
 
283
309
  const actual = await parser.start('test:/master', playerInterface);
310
+ await loadAllStreamsFor(actual);
284
311
  expect(actual).toEqual(manifest);
285
312
  });
286
313
 
@@ -322,6 +349,7 @@ describe('HlsParser', () => {
322
349
  .setResponseValue('test:/main.mp4', segmentData);
323
350
 
324
351
  const actual = await parser.start('test:/master', playerInterface);
352
+ await loadAllStreamsFor(actual);
325
353
  expect(actual).toEqual(manifest);
326
354
  });
327
355
 
@@ -573,6 +601,33 @@ describe('HlsParser', () => {
573
601
  await testHlsParser(master, media, manifest);
574
602
  });
575
603
 
604
+ it('accepts mp4a.40.34 codec as audio/mpeg', async () => {
605
+ const master = [
606
+ '#EXTM3U\n',
607
+ '#EXT-X-STREAM-INF:BANDWIDTH=63701,CODECS="mp4a.40.34"\n',
608
+ 'audio\n',
609
+ ].join('');
610
+
611
+ const media = [
612
+ '#EXTM3U\n',
613
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
614
+ '#EXTINF:5,\n',
615
+ 'main.mp3',
616
+ ].join('');
617
+
618
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
619
+ manifest.anyTimeline();
620
+ manifest.addPartialVariant((variant) => {
621
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
622
+ stream.mime('audio/mpeg', '');
623
+ });
624
+ });
625
+ manifest.sequenceMode = true;
626
+ });
627
+
628
+ await testHlsParser(master, media, manifest);
629
+ });
630
+
576
631
  it('parses audio+video variant with closed captions', async () => {
577
632
  const master = [
578
633
  '#EXTM3U\n',
@@ -1035,6 +1090,77 @@ describe('HlsParser', () => {
1035
1090
  await testHlsParser(master, media, manifest);
1036
1091
  });
1037
1092
 
1093
+ // https://github.com/shaka-project/shaka-player/issues/4759
1094
+ it('makes roles available without loading tracks', async () => {
1095
+ const master = [
1096
+ '#EXTM3U\n',
1097
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
1098
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
1099
+ 'video\n',
1100
+
1101
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
1102
+ 'NAME="English",URI="audio"\n',
1103
+
1104
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",',
1105
+ 'CHARACTERISTICS="public.accessibility.describes-video,',
1106
+ 'public.accessibility.describes-music-and-sound",',
1107
+ 'NAME="English (describes-video)",URI="audio2"\n',
1108
+
1109
+ '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",',
1110
+ 'NAME="English (caption)",DEFAULT=YES,AUTOSELECT=YES,',
1111
+ 'URI="text"\n',
1112
+
1113
+ '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="en",',
1114
+ 'NAME="English (caption)",DEFAULT=YES,AUTOSELECT=YES,',
1115
+ 'CHARACTERISTICS="public.accessibility.describes-spoken-dialog,',
1116
+ 'public.accessibility.describes-music-and-sound",',
1117
+ 'URI="text2"\n',
1118
+ ].join('');
1119
+
1120
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
1121
+ manifest.anyTimeline();
1122
+ manifest.addPartialVariant((variant) => {
1123
+ variant.language = 'en';
1124
+ variant.addPartialStream(ContentType.VIDEO);
1125
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
1126
+ stream.language = 'en';
1127
+ });
1128
+ });
1129
+ manifest.addPartialVariant((variant) => {
1130
+ variant.language = 'en';
1131
+ variant.addPartialStream(ContentType.VIDEO);
1132
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
1133
+ stream.language = 'en';
1134
+ stream.roles = [
1135
+ 'public.accessibility.describes-video',
1136
+ 'public.accessibility.describes-music-and-sound',
1137
+ ];
1138
+ });
1139
+ });
1140
+ manifest.addPartialTextStream((stream) => {
1141
+ stream.language = 'en';
1142
+ stream.kind = TextStreamKind.SUBTITLE;
1143
+ });
1144
+ manifest.addPartialTextStream((stream) => {
1145
+ stream.language = 'en';
1146
+ stream.kind = TextStreamKind.SUBTITLE;
1147
+ stream.roles = [
1148
+ 'public.accessibility.describes-spoken-dialog',
1149
+ 'public.accessibility.describes-music-and-sound',
1150
+ ];
1151
+ });
1152
+ manifest.sequenceMode = true;
1153
+ });
1154
+
1155
+ fakeNetEngine.setResponseText('test:/master', master);
1156
+
1157
+ // NOTE: Not using testHlsParser here because that unconditionally loads all
1158
+ // streams. We need to test the behavior specifically when streams are
1159
+ // _not_ loaded.
1160
+ const actual = await parser.start('test:/master', playerInterface);
1161
+ expect(actual).toEqual(manifest);
1162
+ });
1163
+
1038
1164
  it('gets mime type from header request', async () => {
1039
1165
  const master = [
1040
1166
  '#EXTM3U\n',
@@ -1130,6 +1256,7 @@ describe('HlsParser', () => {
1130
1256
  .setResponseValue('test:/main.mp4', segmentData);
1131
1257
 
1132
1258
  const actual = await parser.start('test:/master', playerInterface);
1259
+ await loadAllStreamsFor(actual);
1133
1260
  expect(actual).toEqual(manifest);
1134
1261
  });
1135
1262
 
@@ -1201,6 +1328,7 @@ describe('HlsParser', () => {
1201
1328
  .setResponseValue('test:/main.mp4', segmentData);
1202
1329
 
1203
1330
  const actual = await parser.start('test:/master', playerInterface);
1331
+ await loadAllStreamsFor(actual);
1204
1332
  expect(actual).toEqual(manifest);
1205
1333
  });
1206
1334
 
@@ -1264,6 +1392,7 @@ describe('HlsParser', () => {
1264
1392
  .setResponseValue('test:/main.mp4', segmentData);
1265
1393
 
1266
1394
  const actual = await parser.start('test:/master', playerInterface);
1395
+ await loadAllStreamsFor(actual);
1267
1396
  expect(actual).toEqual(manifest);
1268
1397
  });
1269
1398
 
@@ -1325,6 +1454,7 @@ describe('HlsParser', () => {
1325
1454
  .setResponseValue('test:/main.mp4', segmentData);
1326
1455
 
1327
1456
  const actual = await parser.start('test:/master', playerInterface);
1457
+ await loadAllStreamsFor(actual);
1328
1458
  expect(actual).toEqual(manifest);
1329
1459
  });
1330
1460
 
@@ -1388,6 +1518,7 @@ describe('HlsParser', () => {
1388
1518
  .setResponseValue('test:/main.mp4', segmentData);
1389
1519
 
1390
1520
  const actual = await parser.start('test:/master', playerInterface);
1521
+ await loadAllStreamsFor(actual);
1391
1522
  expect(actual).toEqual(manifest);
1392
1523
  });
1393
1524
 
@@ -1443,6 +1574,7 @@ describe('HlsParser', () => {
1443
1574
  .setResponseValue('test:/main.mp4', segmentData);
1444
1575
 
1445
1576
  const actual = await parser.start('test:/master', playerInterface);
1577
+ await loadAllStreamsFor(actual);
1446
1578
  // Duration should be the minimum of the streams, but ignore the text
1447
1579
  // stream.
1448
1580
  const timeline = actual.presentationTimeline;
@@ -1525,6 +1657,7 @@ describe('HlsParser', () => {
1525
1657
  .setResponseValue('test:/main.mp4', segmentData);
1526
1658
 
1527
1659
  const actual = await parser.start('test:/master', playerInterface);
1660
+ await loadAllStreamsFor(actual);
1528
1661
 
1529
1662
  expect(actual.imageStreams.length).toBe(1);
1530
1663
  expect(actual.textStreams.length).toBe(1);
@@ -1597,6 +1730,7 @@ describe('HlsParser', () => {
1597
1730
  .setResponseValue('test:/main.mp4', segmentData);
1598
1731
 
1599
1732
  const actual = await parser.start('test:/master', playerInterface);
1733
+ await loadAllStreamsFor(actual);
1600
1734
 
1601
1735
  expect(actual.variants.length).toBe(1);
1602
1736
 
@@ -1722,6 +1856,7 @@ describe('HlsParser', () => {
1722
1856
  parser.configure(config);
1723
1857
 
1724
1858
  const actual = await parser.start('test:/master', playerInterface);
1859
+ await loadAllStreamsFor(actual);
1725
1860
  const variant = actual.variants[0];
1726
1861
  expect(variant.audio).toBe(null);
1727
1862
  expect(variant.video).toBeTruthy();
@@ -1797,6 +1932,7 @@ describe('HlsParser', () => {
1797
1932
  parser.configure(config);
1798
1933
 
1799
1934
  const actual = await parser.start('test:/master', playerInterface);
1935
+ await loadAllStreamsFor(actual);
1800
1936
  const variant = actual.variants[0];
1801
1937
  expect(variant.audio).toBeTruthy();
1802
1938
  expect(variant.video).toBe(null);
@@ -1872,6 +2008,7 @@ describe('HlsParser', () => {
1872
2008
  parser.configure(config);
1873
2009
 
1874
2010
  const actual = await parser.start('test:/master', playerInterface);
2011
+ await loadAllStreamsFor(actual);
1875
2012
  const stream = actual.textStreams[0];
1876
2013
  expect(stream).toBeUndefined();
1877
2014
  });
@@ -1946,6 +2083,7 @@ describe('HlsParser', () => {
1946
2083
  parser.configure(config);
1947
2084
 
1948
2085
  const actual = await parser.start('test:/master', playerInterface);
2086
+ await loadAllStreamsFor(actual);
1949
2087
  const stream = actual.imageStreams[0];
1950
2088
  expect(stream).toBeUndefined();
1951
2089
  });
@@ -1990,6 +2128,7 @@ describe('HlsParser', () => {
1990
2128
  .setResponseValue('test:/main.mp4', segmentData);
1991
2129
 
1992
2130
  const actual = await parser.start('test:/master', playerInterface);
2131
+ await loadAllStreamsFor(actual);
1993
2132
  expect(actual).toEqual(manifest);
1994
2133
  });
1995
2134
 
@@ -2041,6 +2180,7 @@ describe('HlsParser', () => {
2041
2180
  .setResponseValue('test:/main.mp4', segmentData);
2042
2181
 
2043
2182
  const actual = await parser.start('test:/master', playerInterface);
2183
+ await loadAllStreamsFor(actual);
2044
2184
  expect(actual).toEqual(manifest);
2045
2185
  });
2046
2186
 
@@ -2083,6 +2223,7 @@ describe('HlsParser', () => {
2083
2223
  .setResponseValue('test:/main.mp4', segmentData);
2084
2224
 
2085
2225
  const actual = await parser.start('test:/master', playerInterface);
2226
+ await loadAllStreamsFor(actual);
2086
2227
  expect(actual).toEqual(manifest);
2087
2228
  });
2088
2229
 
@@ -2149,6 +2290,7 @@ describe('HlsParser', () => {
2149
2290
  .setResponseValue('test:/main.mp4', segmentData);
2150
2291
 
2151
2292
  const actual = await parser.start('test:/master', playerInterface);
2293
+ await loadAllStreamsFor(actual);
2152
2294
  expect(actual).toEqual(manifest);
2153
2295
  }
2154
2296
 
@@ -2294,6 +2436,7 @@ describe('HlsParser', () => {
2294
2436
 
2295
2437
  config.hls.ignoreTextStreamFailures = true;
2296
2438
  const actual = await parser.start('test:/master', playerInterface);
2439
+ await loadAllStreamsFor(actual);
2297
2440
  expect(actual).toEqual(manifest);
2298
2441
  });
2299
2442
 
@@ -2332,6 +2475,7 @@ describe('HlsParser', () => {
2332
2475
 
2333
2476
  config.hls.ignoreImageStreamFailures = true;
2334
2477
  const actual = await parser.start('test:/master', playerInterface);
2478
+ await loadAllStreamsFor(actual);
2335
2479
  expect(actual).toEqual(manifest);
2336
2480
  });
2337
2481
 
@@ -2397,6 +2541,7 @@ describe('HlsParser', () => {
2397
2541
 
2398
2542
  const actual =
2399
2543
  await parser.start('test:/host/master.m3u8', playerInterface);
2544
+ await loadAllStreamsFor(actual);
2400
2545
  const video = actual.variants[0].video;
2401
2546
  const audio = actual.variants[0].audio;
2402
2547
 
@@ -2595,6 +2740,7 @@ describe('HlsParser', () => {
2595
2740
  .setResponseValue('test:/selfInit.mp4', selfInitializingSegmentData);
2596
2741
 
2597
2742
  const actual = await parser.start('test:/master', playerInterface);
2743
+ await loadAllStreamsFor(actual);
2598
2744
  expect(actual).toEqual(manifest);
2599
2745
  });
2600
2746
 
@@ -2639,7 +2785,8 @@ describe('HlsParser', () => {
2639
2785
  shaka.util.Error.Severity.CRITICAL,
2640
2786
  shaka.util.Error.Category.MANIFEST,
2641
2787
  shaka.util.Error.Code.NO_WEB_CRYPTO_API));
2642
- await expectAsync(parser.start('test:/master', playerInterface))
2788
+ const actual = await parser.start('test:/master', playerInterface);
2789
+ await expectAsync(loadAllStreamsFor(actual))
2643
2790
  .toBeRejectedWith(expectedError);
2644
2791
  } finally {
2645
2792
  Object.defineProperty(window, 'crypto', {
@@ -2649,7 +2796,6 @@ describe('HlsParser', () => {
2649
2796
  }
2650
2797
  });
2651
2798
 
2652
-
2653
2799
  it('constructs DrmInfo for Widevine', async () => {
2654
2800
  const master = [
2655
2801
  '#EXTM3U\n',
@@ -2693,6 +2839,7 @@ describe('HlsParser', () => {
2693
2839
  });
2694
2840
 
2695
2841
  await testHlsParser(master, media, manifest);
2842
+ expect(newDrmInfoSpy).toHaveBeenCalled();
2696
2843
  });
2697
2844
 
2698
2845
  it('constructs DrmInfo for PlayReady', async () => {
@@ -2733,6 +2880,7 @@ describe('HlsParser', () => {
2733
2880
  });
2734
2881
 
2735
2882
  await testHlsParser(master, media, manifest);
2883
+ expect(newDrmInfoSpy).toHaveBeenCalled();
2736
2884
  });
2737
2885
 
2738
2886
  it('constructs DrmInfo for FairPlay', async () => {
@@ -2770,6 +2918,229 @@ describe('HlsParser', () => {
2770
2918
  });
2771
2919
 
2772
2920
  await testHlsParser(master, media, manifest);
2921
+ expect(newDrmInfoSpy).toHaveBeenCalled();
2922
+ });
2923
+
2924
+ it('constructs DrmInfo for ClearKey with explicit KEYFORMAT', async () => {
2925
+ const master = [
2926
+ '#EXTM3U\n',
2927
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
2928
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
2929
+ 'video\n',
2930
+ ].join('');
2931
+
2932
+ const media = [
2933
+ '#EXTM3U\n',
2934
+ '#EXT-X-TARGETDURATION:6\n',
2935
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2936
+ '#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
2937
+ 'KEYFORMAT="identity",',
2938
+ 'URI="key.bin",\n',
2939
+ '#EXT-X-MAP:URI="init.mp4"\n',
2940
+ '#EXTINF:5,\n',
2941
+ 'main.mp4',
2942
+ ].join('');
2943
+
2944
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
2945
+ manifest.anyTimeline();
2946
+ manifest.addPartialVariant((variant) => {
2947
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
2948
+ stream.encrypted = true;
2949
+ stream.addDrmInfo('org.w3.clearkey');
2950
+ });
2951
+ });
2952
+ manifest.sequenceMode = true;
2953
+ });
2954
+
2955
+ await testHlsParser(master, media, manifest);
2956
+ expect(newDrmInfoSpy).toHaveBeenCalled();
2957
+ });
2958
+
2959
+ it('constructs DrmInfo for ClearKey without explicit KEYFORMAT', async () => {
2960
+ const master = [
2961
+ '#EXTM3U\n',
2962
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
2963
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
2964
+ 'video\n',
2965
+ ].join('');
2966
+
2967
+ const media = [
2968
+ '#EXTM3U\n',
2969
+ '#EXT-X-TARGETDURATION:6\n',
2970
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
2971
+ '#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,',
2972
+ 'URI="key.bin",\n',
2973
+ '#EXT-X-MAP:URI="init.mp4"\n',
2974
+ '#EXTINF:5,\n',
2975
+ 'main.mp4',
2976
+ ].join('');
2977
+
2978
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
2979
+ manifest.anyTimeline();
2980
+ manifest.addPartialVariant((variant) => {
2981
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
2982
+ stream.encrypted = true;
2983
+ stream.addDrmInfo('org.w3.clearkey');
2984
+ });
2985
+ });
2986
+ manifest.sequenceMode = true;
2987
+ });
2988
+
2989
+ await testHlsParser(master, media, manifest);
2990
+ expect(newDrmInfoSpy).toHaveBeenCalled();
2991
+ });
2992
+
2993
+ describe('constructs DrmInfo with EXT-X-SESSION-KEY', () => {
2994
+ it('for Widevine', async () => {
2995
+ const initDataBase64 =
2996
+ 'dGhpcyBpbml0IGRhdGEgY29udGFpbnMgaGlkZGVuIHNlY3JldHMhISE=';
2997
+
2998
+ const keyId = 'abc123';
2999
+
3000
+ const master = [
3001
+ '#EXTM3U\n',
3002
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
3003
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
3004
+ 'video\n',
3005
+ '#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
3006
+ 'KEYID=0X' + keyId + ',',
3007
+ 'KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",',
3008
+ 'URI="data:text/plain;base64,',
3009
+ initDataBase64, '",\n',
3010
+ ].join('');
3011
+
3012
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3013
+ manifest.anyTimeline();
3014
+ manifest.addPartialVariant((variant) => {
3015
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
3016
+ stream.addDrmInfo('com.widevine.alpha', (drmInfo) => {
3017
+ drmInfo.addCencInitData(initDataBase64);
3018
+ drmInfo.keyIds.add(keyId);
3019
+ });
3020
+ });
3021
+ });
3022
+ manifest.sequenceMode = true;
3023
+ });
3024
+
3025
+ fakeNetEngine.setResponseText('test:/master', master);
3026
+
3027
+ const actual = await parser.start('test:/master', playerInterface);
3028
+ expect(actual).toEqual(manifest);
3029
+ });
3030
+
3031
+ it('for PlayReady', async () => {
3032
+ const initDataBase64 =
3033
+ 'AAAAKXBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAAlQbGF5cmVhZHk=';
3034
+
3035
+ const master = [
3036
+ '#EXTM3U\n',
3037
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
3038
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
3039
+ 'video\n',
3040
+ '#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
3041
+ 'KEYFORMAT="com.microsoft.playready",',
3042
+ 'URI="data:text/plain;base64,UGxheXJlYWR5",\n',
3043
+ ].join('');
3044
+
3045
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3046
+ manifest.anyTimeline();
3047
+ manifest.addPartialVariant((variant) => {
3048
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
3049
+ stream.addDrmInfo('com.microsoft.playready', (drmInfo) => {
3050
+ drmInfo.addCencInitData(initDataBase64);
3051
+ });
3052
+ });
3053
+ });
3054
+ manifest.sequenceMode = true;
3055
+ });
3056
+
3057
+ fakeNetEngine.setResponseText('test:/master', master);
3058
+
3059
+ const actual = await parser.start('test:/master', playerInterface);
3060
+ expect(actual).toEqual(manifest);
3061
+ });
3062
+
3063
+ it('for FairPlay', async () => {
3064
+ const master = [
3065
+ '#EXTM3U\n',
3066
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
3067
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
3068
+ 'video\n',
3069
+ '#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
3070
+ 'KEYFORMAT="com.apple.streamingkeydelivery",',
3071
+ 'URI="skd://f93d4e700d7ddde90529a27735d9e7cb",\n',
3072
+ ].join('');
3073
+
3074
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3075
+ manifest.anyTimeline();
3076
+ manifest.addPartialVariant((variant) => {
3077
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
3078
+ stream.addDrmInfo('com.apple.fps', (drmInfo) => {
3079
+ drmInfo.addInitData('sinf', new Uint8Array(0));
3080
+ });
3081
+ });
3082
+ });
3083
+ manifest.sequenceMode = true;
3084
+ });
3085
+
3086
+ fakeNetEngine.setResponseText('test:/master', master);
3087
+
3088
+ const actual = await parser.start('test:/master', playerInterface);
3089
+ expect(actual).toEqual(manifest);
3090
+ });
3091
+
3092
+ it('for ClearKey with explicit KEYFORMAT', async () => {
3093
+ const master = [
3094
+ '#EXTM3U\n',
3095
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
3096
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
3097
+ 'video\n',
3098
+ '#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
3099
+ 'KEYFORMAT="identity",',
3100
+ 'URI="key.bin",\n',
3101
+ ].join('');
3102
+
3103
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3104
+ manifest.anyTimeline();
3105
+ manifest.addPartialVariant((variant) => {
3106
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
3107
+ stream.addDrmInfo('org.w3.clearkey');
3108
+ });
3109
+ });
3110
+ manifest.sequenceMode = true;
3111
+ });
3112
+
3113
+ fakeNetEngine.setResponseText('test:/master', master);
3114
+
3115
+ const actual = await parser.start('test:/master', playerInterface);
3116
+ expect(actual).toEqual(manifest);
3117
+ });
3118
+
3119
+ it('for ClearKey without explicit KEYFORMAT', async () => {
3120
+ const master = [
3121
+ '#EXTM3U\n',
3122
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
3123
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
3124
+ 'video\n',
3125
+ '#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES-CTR,',
3126
+ 'URI="key.bin",\n',
3127
+ ].join('');
3128
+
3129
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3130
+ manifest.anyTimeline();
3131
+ manifest.addPartialVariant((variant) => {
3132
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
3133
+ stream.addDrmInfo('org.w3.clearkey');
3134
+ });
3135
+ });
3136
+ manifest.sequenceMode = true;
3137
+ });
3138
+
3139
+ fakeNetEngine.setResponseText('test:/master', master);
3140
+
3141
+ const actual = await parser.start('test:/master', playerInterface);
3142
+ expect(actual).toEqual(manifest);
3143
+ });
2773
3144
  });
2774
3145
 
2775
3146
  it('falls back to mp4 if HEAD request fails', async () => {
@@ -2818,8 +3189,9 @@ describe('HlsParser', () => {
2818
3189
  * @param {string} master
2819
3190
  * @param {string} media
2820
3191
  * @param {!shaka.util.Error} error
3192
+ * @param {boolean=} onCreateSegmentIndex
2821
3193
  */
2822
- async function verifyError(master, media, error) {
3194
+ async function verifyError(master, media, error, onCreateSegmentIndex) {
2823
3195
  fakeNetEngine
2824
3196
  .setResponseText('test:/master', master)
2825
3197
  .setResponseText('test:/audio', media)
@@ -2830,8 +3202,14 @@ describe('HlsParser', () => {
2830
3202
  .setResponseValue('data:text/plain;base64,AAECAwQFBgcICQoLDA0ODw==',
2831
3203
  aes128Key);
2832
3204
 
2833
- await expectAsync(parser.start('test:/master', playerInterface))
2834
- .toBeRejectedWith(Util.jasmineError(error));
3205
+ if (onCreateSegmentIndex) {
3206
+ const actual = await parser.start('test:/master', playerInterface);
3207
+ await expectAsync(loadAllStreamsFor(actual))
3208
+ .toBeRejectedWith(Util.jasmineError(error));
3209
+ } else {
3210
+ await expectAsync(parser.start('test:/master', playerInterface))
3211
+ .toBeRejectedWith(Util.jasmineError(error));
3212
+ }
2835
3213
  }
2836
3214
 
2837
3215
  it('if unable to guess codecs', async () => {
@@ -2968,7 +3346,7 @@ describe('HlsParser', () => {
2968
3346
  shaka.util.Error.Category.MANIFEST,
2969
3347
  Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
2970
3348
 
2971
- await verifyError(master, media, error);
3349
+ await verifyError(master, media, error, /* onCreateSegmentIndex= */ true);
2972
3350
  });
2973
3351
 
2974
3352
  describe('if required tags are missing', () => {
@@ -2984,7 +3362,8 @@ describe('HlsParser', () => {
2984
3362
  Code.HLS_REQUIRED_TAG_MISSING,
2985
3363
  tagName);
2986
3364
 
2987
- await verifyError(master, media, error);
3365
+ await verifyError(
3366
+ master, media, error, /* onCreateSegmentIndex= */ true);
2988
3367
  }
2989
3368
 
2990
3369
  it('EXTINF', async () => {
@@ -3271,6 +3650,67 @@ describe('HlsParser', () => {
3271
3650
  await testHlsParser(master, media, manifest);
3272
3651
  });
3273
3652
 
3653
+ // https://github.com/shaka-project/shaka-player/issues/4308
3654
+ it('handles unaligned streams', async () => {
3655
+ const master = [
3656
+ '#EXTM3U\n',
3657
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
3658
+ 'RESOLUTION=1280x720,FRAME-RATE=30\n',
3659
+ 'video\n',
3660
+ '#EXT-X-STREAM-INF:BANDWIDTH=400,CODECS="avc1",',
3661
+ 'RESOLUTION=1920x1080,FRAME-RATE=30\n',
3662
+ 'video2\n',
3663
+ ].join('');
3664
+
3665
+ const media = [
3666
+ '#EXTM3U\n',
3667
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
3668
+ '#EXTINF:5\n',
3669
+ 'main.mp4\n',
3670
+ '#EXTINF:5\n',
3671
+ 'main.mp4',
3672
+ ].join('');
3673
+
3674
+ const media2 = [
3675
+ '#EXTM3U\n',
3676
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
3677
+ '#EXTINF:4\n',
3678
+ 'main.mp4\n',
3679
+ '#EXTINF:5\n',
3680
+ 'main.mp4\n',
3681
+ '#EXTINF:1\n',
3682
+ 'main.mp4',
3683
+ ].join('');
3684
+
3685
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3686
+ manifest.anyTimeline();
3687
+ manifest.addPartialVariant((variant) => {
3688
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
3689
+ stream.size(1280, 720);
3690
+ });
3691
+ });
3692
+ manifest.addPartialVariant((variant) => {
3693
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
3694
+ stream.size(1920, 1080);
3695
+ });
3696
+ });
3697
+ manifest.sequenceMode = true;
3698
+ });
3699
+
3700
+ const actual = await testHlsParser(master, media, manifest, media2);
3701
+
3702
+ const videoSegments =
3703
+ Array.from(actual.variants[0].video.segmentIndex || '');
3704
+ expect(videoSegments[0].endTime).toBe(5);
3705
+ expect(videoSegments[1].endTime).toBe(10);
3706
+
3707
+ const videoSegments2 =
3708
+ Array.from(actual.variants[1].video.segmentIndex || '');
3709
+ expect(videoSegments2[0].endTime).toBe(4);
3710
+ expect(videoSegments2[1].endTime).toBe(9);
3711
+ expect(videoSegments2[2].endTime).toBe(10);
3712
+ });
3713
+
3274
3714
  // Issue #1875
3275
3715
  it('ignores audio groups on audio-only content', async () => {
3276
3716
  // NOTE: To reproduce the original issue accurately, the two audio playlist
@@ -3318,6 +3758,7 @@ describe('HlsParser', () => {
3318
3758
  .setResponseValue('test:/main.mp4', segmentData);
3319
3759
 
3320
3760
  const actual = await parser.start('test:/master', playerInterface);
3761
+ await loadAllStreamsFor(actual);
3321
3762
  expect(actual.variants.length).toBe(1);
3322
3763
  expect(actual).toEqual(manifest);
3323
3764
  });
@@ -3350,6 +3791,7 @@ describe('HlsParser', () => {
3350
3791
 
3351
3792
  const actual =
3352
3793
  await parser.start('test:/host/master.m3u8', playerInterface);
3794
+ await loadAllStreamsFor(actual);
3353
3795
  const video = actual.variants[0].video;
3354
3796
  const audio = actual.variants[0].audio;
3355
3797
 
@@ -3397,6 +3839,7 @@ describe('HlsParser', () => {
3397
3839
 
3398
3840
  const actual =
3399
3841
  await parser.start('test:/host/master.m3u8', playerInterface);
3842
+ await loadAllStreamsFor(actual);
3400
3843
  const video = actual.variants[0].video;
3401
3844
  const audio = actual.variants[0].audio;
3402
3845
 
@@ -3444,6 +3887,7 @@ describe('HlsParser', () => {
3444
3887
 
3445
3888
  const actual =
3446
3889
  await parser.start('test:/host/master.m3u8', playerInterface);
3890
+ await loadAllStreamsFor(actual);
3447
3891
  const video = actual.variants[0].video;
3448
3892
  const audio = actual.variants[0].audio;
3449
3893
 
@@ -3658,7 +4102,9 @@ describe('HlsParser', () => {
3658
4102
  });
3659
4103
  });
3660
4104
 
3661
- await testHlsParser(media, '', manifest);
4105
+ const actualManifest = await testHlsParser(media, '', manifest);
4106
+
4107
+ expect(actualManifest.presentationTimeline.getDuration()).toBe(5);
3662
4108
  });
3663
4109
 
3664
4110
  it('honors hls.mediaPlaylistFullMimeType', async () => {
@@ -3688,6 +4134,58 @@ describe('HlsParser', () => {
3688
4134
  await testHlsParser(media, '', manifest);
3689
4135
  });
3690
4136
 
4137
+ it('honors hls.mediaPlaylistFullMimeType but detects AAC', async () => {
4138
+ const media = [
4139
+ '#EXTM3U\n',
4140
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
4141
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
4142
+ '#EXTINF:5,\n',
4143
+ '#EXT-X-BYTERANGE:121090@616\n',
4144
+ 'main.aac',
4145
+ ].join('');
4146
+
4147
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
4148
+ parser.configure(config);
4149
+
4150
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
4151
+ manifest.sequenceMode = true;
4152
+ manifest.anyTimeline();
4153
+ manifest.addPartialVariant((variant) => {
4154
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
4155
+ stream.mime('audio/aac');
4156
+ });
4157
+ });
4158
+ });
4159
+
4160
+ await testHlsParser(media, '', manifest);
4161
+ });
4162
+
4163
+ it('honors hls.mediaPlaylistFullMimeType but detects MPEG', async () => {
4164
+ const media = [
4165
+ '#EXTM3U\n',
4166
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
4167
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
4168
+ '#EXTINF:5,\n',
4169
+ '#EXT-X-BYTERANGE:121090@616\n',
4170
+ 'main.mp3',
4171
+ ].join('');
4172
+
4173
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
4174
+ parser.configure(config);
4175
+
4176
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
4177
+ manifest.sequenceMode = true;
4178
+ manifest.anyTimeline();
4179
+ manifest.addPartialVariant((variant) => {
4180
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
4181
+ stream.mime('audio/mpeg');
4182
+ });
4183
+ });
4184
+ });
4185
+
4186
+ await testHlsParser(media, '', manifest);
4187
+ });
4188
+
3691
4189
  it('syncs on sequence with ignoreManifestProgramDateTime', async () => {
3692
4190
  const config = shaka.util.PlayerConfiguration.createDefault().manifest;
3693
4191
  config.hls.ignoreManifestProgramDateTime = true;
@@ -3775,4 +4273,125 @@ describe('HlsParser', () => {
3775
4273
  'test:/audio4.mp4',
3776
4274
  ]);
3777
4275
  });
4276
+
4277
+ it('lazy-loads TS content without filtering it out', async () => {
4278
+ const master = [
4279
+ '#EXTM3U\n',
4280
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",URI="audio"\n',
4281
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
4282
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
4283
+ 'video\n',
4284
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
4285
+ 'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud1"\n',
4286
+ 'video2\n',
4287
+ ].join('');
4288
+
4289
+ const video = [
4290
+ '#EXTM3U\n',
4291
+ '#EXT-X-TARGETDURATION:5\n',
4292
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
4293
+ '#EXT-X-MEDIA-SEQUENCE:1\n',
4294
+ '#EXTINF:5,\n',
4295
+ 'video1.ts\n',
4296
+ '#EXTINF:5,\n',
4297
+ 'video2.ts\n',
4298
+ ].join('');
4299
+
4300
+ const audio = [
4301
+ '#EXTM3U\n',
4302
+ '#EXT-X-TARGETDURATION:5\n',
4303
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
4304
+ '#EXT-X-MEDIA-SEQUENCE:3\n',
4305
+ '#EXTINF:5,\n',
4306
+ 'audio1.ts\n',
4307
+ '#EXTINF:5,\n',
4308
+ 'audio2.ts\n',
4309
+ ].join('');
4310
+
4311
+ fakeNetEngine
4312
+ .setResponseText('test:/master', master)
4313
+ .setResponseText('test:/audio', audio)
4314
+ .setResponseText('test:/video', video)
4315
+ .setResponseText('test:/video2', video)
4316
+ .setResponseValue('test:/init.mp4', initSegmentData);
4317
+
4318
+ const actualManifest = await parser.start('test:/master', playerInterface);
4319
+ expect(actualManifest.variants.length).toBe(2);
4320
+
4321
+ const actualVideo0 = actualManifest.variants[0].video;
4322
+ const actualVideo1 = actualManifest.variants[1].video;
4323
+
4324
+ // Before loading, all MIME types agree, and are defaulted to video/mp4.
4325
+ expect(actualVideo0.mimeType).toBe('video/mp4');
4326
+ expect(actualVideo1.mimeType).toBe('video/mp4');
4327
+
4328
+ await actualVideo0.createSegmentIndex();
4329
+
4330
+ // After loading just ONE stream, all MIME types agree again, and have been
4331
+ // updated to reflect the TS content found inside the loaded playlist.
4332
+ // This is how we avoid having the unloaded tracks filtered out during
4333
+ // startup.
4334
+ expect(actualVideo0.mimeType).toBe('video/mp2t');
4335
+ expect(actualVideo1.mimeType).toBe('video/mp2t');
4336
+ });
4337
+
4338
+ it('lazy-loads AAC content without filtering it out', async () => {
4339
+ const master = [
4340
+ '#EXTM3U\n',
4341
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",URI="audio"\n',
4342
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="spa",URI="audio2"\n',
4343
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
4344
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
4345
+ 'video\n',
4346
+ ].join('');
4347
+
4348
+ const video = [
4349
+ '#EXTM3U\n',
4350
+ '#EXT-X-TARGETDURATION:5\n',
4351
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
4352
+ '#EXT-X-MEDIA-SEQUENCE:1\n',
4353
+ '#EXTINF:5,\n',
4354
+ 'video1.ts\n',
4355
+ '#EXTINF:5,\n',
4356
+ 'video2.ts\n',
4357
+ ].join('');
4358
+
4359
+ const audio = [
4360
+ '#EXTM3U\n',
4361
+ '#EXT-X-TARGETDURATION:5\n',
4362
+ '#EXT-X-MEDIA-SEQUENCE:3\n',
4363
+ '#EXTINF:5,\n',
4364
+ 'audio1.aac\n',
4365
+ '#EXTINF:5,\n',
4366
+ 'audio2.aac\n',
4367
+ ].join('');
4368
+
4369
+ fakeNetEngine
4370
+ .setResponseText('test:/master', master)
4371
+ .setResponseText('test:/audio', audio)
4372
+ .setResponseText('test:/audio2', audio)
4373
+ .setResponseText('test:/video', video)
4374
+ .setResponseValue('test:/init.mp4', initSegmentData);
4375
+
4376
+ const actualManifest = await parser.start('test:/master', playerInterface);
4377
+ expect(actualManifest.variants.length).toBe(2);
4378
+
4379
+ const actualAudio0 = actualManifest.variants[0].audio;
4380
+ const actualAudio1 = actualManifest.variants[1].audio;
4381
+
4382
+ // Before loading, all MIME types agree, and are defaulted to audio/mp4.
4383
+ expect(actualAudio0.mimeType).toBe('audio/mp4');
4384
+ expect(actualAudio1.mimeType).toBe('audio/mp4');
4385
+
4386
+ await actualAudio0.createSegmentIndex();
4387
+
4388
+ // After loading just ONE stream, all MIME types agree again, and have been
4389
+ // updated to reflect the AAC content found inside the loaded playlist.
4390
+ // This is how we avoid having the unloaded tracks filtered out during
4391
+ // startup.
4392
+ expect(actualAudio0.mimeType).toBe('audio/aac');
4393
+ expect(actualAudio0.codecs).toBe('');
4394
+ expect(actualAudio1.mimeType).toBe('audio/aac');
4395
+ expect(actualAudio1.codecs).toBe('');
4396
+ });
3778
4397
  });