shaka-player 3.1.5 → 3.2.3

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 (134) hide show
  1. package/CHANGELOG.md +129 -6
  2. package/README.md +4 -4
  3. package/build/types/polyfill +1 -0
  4. package/demo/common/asset.js +9 -7
  5. package/demo/common/assets.js +10 -1
  6. package/demo/common/message_ids.js +2 -1
  7. package/demo/config.js +5 -2
  8. package/demo/index.html +1 -1
  9. package/demo/locales/en.json +2 -1
  10. package/demo/locales/source.json +8 -4
  11. package/demo/main.js +2 -1
  12. package/dist/demo.compiled.debug.js +14 -14
  13. package/dist/demo.compiled.debug.map +2 -2
  14. package/dist/demo.compiled.js +14 -14
  15. package/dist/demo.compiled.map +2 -2
  16. package/dist/deps.js +21 -20
  17. package/dist/locales.js +30 -30
  18. package/dist/receiver.compiled.debug.js +8 -8
  19. package/dist/receiver.compiled.debug.map +2 -2
  20. package/dist/receiver.compiled.js +8 -8
  21. package/dist/receiver.compiled.map +2 -2
  22. package/dist/shaka-player.compiled.d.ts +63 -10
  23. package/dist/shaka-player.compiled.debug.d.ts +63 -10
  24. package/dist/shaka-player.compiled.debug.externs.js +786 -716
  25. package/dist/shaka-player.compiled.debug.js +563 -534
  26. package/dist/shaka-player.compiled.debug.map +4 -4
  27. package/dist/shaka-player.compiled.externs.js +786 -716
  28. package/dist/shaka-player.compiled.js +820 -809
  29. package/dist/shaka-player.compiled.map +4 -4
  30. package/dist/shaka-player.ui.d.ts +63 -10
  31. package/dist/shaka-player.ui.debug.d.ts +63 -10
  32. package/dist/shaka-player.ui.debug.externs.js +542 -472
  33. package/dist/shaka-player.ui.debug.js +594 -566
  34. package/dist/shaka-player.ui.debug.map +4 -4
  35. package/dist/shaka-player.ui.externs.js +542 -472
  36. package/dist/shaka-player.ui.js +938 -927
  37. package/dist/shaka-player.ui.map +4 -4
  38. package/docs/tutorials/ui.md +5 -5
  39. package/externs/aria.js +6 -0
  40. package/externs/shaka/ads.js +24 -1
  41. package/externs/shaka/manifest.js +6 -1
  42. package/externs/shaka/offline.js +5 -0
  43. package/externs/shaka/player.js +61 -9
  44. package/externs/shaka/text.js +10 -10
  45. package/externs/texttrack.js +3 -0
  46. package/lib/ads/ad_manager.js +16 -15
  47. package/lib/ads/client_side_ad_manager.js +6 -2
  48. package/lib/ads/server_side_ad_manager.js +19 -2
  49. package/lib/cast/cast_proxy.js +8 -0
  50. package/lib/cast/cast_utils.js +6 -1
  51. package/lib/cea/cea608_data_channel.js +56 -56
  52. package/lib/cea/cea608_memory.js +33 -33
  53. package/lib/cea/cea708_service.js +43 -43
  54. package/lib/cea/cea708_window.js +38 -38
  55. package/lib/cea/cea_decoder.js +13 -14
  56. package/lib/cea/cea_utils.js +23 -23
  57. package/lib/cea/dtvcc_packet_builder.js +7 -7
  58. package/lib/cea/i_caption_decoder.js +2 -3
  59. package/lib/cea/i_cea_parser.js +2 -2
  60. package/lib/cea/mp4_cea_parser.js +6 -6
  61. package/lib/dash/dash_parser.js +5 -0
  62. package/lib/hls/hls_parser.js +159 -12
  63. package/lib/media/closed_caption_parser.js +1 -2
  64. package/lib/media/drm_engine.js +162 -192
  65. package/lib/media/segment_index.js +4 -1
  66. package/lib/media/segment_reference.js +37 -2
  67. package/lib/media/streaming_engine.js +6 -6
  68. package/lib/net/networking_engine.js +1 -0
  69. package/lib/offline/download_manager.js +1 -2
  70. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  71. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  72. package/lib/offline/manifest_converter.js +3 -1
  73. package/lib/offline/storage.js +12 -14
  74. package/lib/player.js +426 -149
  75. package/lib/polyfill/aria.js +82 -0
  76. package/lib/routing/walker.js +2 -3
  77. package/lib/text/srt_text_parser.js +4 -4
  78. package/lib/text/text_engine.js +2 -2
  79. package/lib/text/vtt_text_parser.js +2 -2
  80. package/lib/text/web_vtt_generator.js +55 -12
  81. package/lib/util/error.js +6 -0
  82. package/lib/util/manifest_parser_utils.js +1 -0
  83. package/lib/util/mp4_box_parsers.js +20 -20
  84. package/lib/util/multi_map.js +8 -0
  85. package/lib/util/player_configuration.js +13 -1
  86. package/lib/util/stream_utils.js +168 -88
  87. package/lib/util/xml_utils.js +31 -12
  88. package/package.json +1 -1
  89. package/roadmap.md +24 -22
  90. package/shaka-player.uncompiled.js +1 -0
  91. package/test/cast/cast_utils_unit.js +1 -0
  92. package/test/cea/cea608_memory_unit.js +1 -1
  93. package/test/cea/cea708_service_unit.js +8 -8
  94. package/test/cea/cea708_window_unit.js +6 -6
  95. package/test/cea/cea_decoder_unit.js +1 -1
  96. package/test/cea/dtvcc_packet_builder_unit.js +2 -2
  97. package/test/dash/dash_parser_manifest_unit.js +43 -1
  98. package/test/hls/hls_parser_unit.js +250 -0
  99. package/test/media/drm_engine_integration.js +86 -12
  100. package/test/media/drm_engine_unit.js +310 -450
  101. package/test/offline/manifest_convert_unit.js +2 -0
  102. package/test/offline/storage_integration.js +9 -19
  103. package/test/player_integration.js +85 -0
  104. package/test/player_src_equals_integration.js +85 -0
  105. package/test/player_unit.js +25 -37
  106. package/test/test/assets/chapters.srt +11 -0
  107. package/test/test/assets/chapters.vtt +10 -0
  108. package/test/test/assets/chapters2.vtt +10 -0
  109. package/test/test/util/cea_utils.js +12 -12
  110. package/test/test/util/fake_ad_manager.js +7 -0
  111. package/test/test/util/manifest_generator.js +2 -0
  112. package/test/test/util/manifest_parser_util.js +3 -1
  113. package/test/text/web_vtt_generator_unit.js +81 -7
  114. package/test/util/stream_utils_unit.js +118 -10
  115. package/ui/airplay_button.js +1 -1
  116. package/ui/audio_language_selection.js +2 -5
  117. package/ui/cast_button.js +4 -6
  118. package/ui/constants.js +0 -3
  119. package/ui/fast_forward_button.js +2 -3
  120. package/ui/fullscreen_button.js +1 -3
  121. package/ui/language_utils.js +1 -1
  122. package/ui/loop_button.js +1 -3
  123. package/ui/mute_button.js +1 -3
  124. package/ui/overflow_menu.js +2 -3
  125. package/ui/pip_button.js +5 -7
  126. package/ui/playback_rate_selection.js +3 -6
  127. package/ui/presentation_time.js +1 -3
  128. package/ui/resolution_selection.js +4 -7
  129. package/ui/rewind_button.js +2 -3
  130. package/ui/seek_bar.js +2 -4
  131. package/ui/spacer.js +1 -1
  132. package/ui/text_selection.js +8 -11
  133. package/ui/ui_utils.js +1 -1
  134. package/ui/volume_bar.js +1 -3
@@ -36,7 +36,7 @@ describe('DashParser Manifest', () => {
36
36
  /** @type {!ArrayBuffer} */
37
37
  let mp4Index;
38
38
 
39
- /** @type {!string} */
39
+ /** @type {string} */
40
40
  const thumbnailScheme = 'http://dashif.org/guidelines/thumbnail_tile';
41
41
  /**
42
42
  * CICP scheme. parameter must be one of the following: "ColourPrimaries",
@@ -1798,6 +1798,48 @@ describe('DashParser Manifest', () => {
1798
1798
  expect(manifest.presentationTimeline).toBeTruthy();
1799
1799
  });
1800
1800
 
1801
+ it('Invokes manifestPreprocessor in config', async () => {
1802
+ const manifestText = [
1803
+ '<MPD minBufferTime="PT75S">',
1804
+ ' <Period id="1" duration="PT30S">',
1805
+ ' <AdaptationSet id="2" mimeType="video/mp4">',
1806
+ ' <Representation id="video-sd" width="640" height="480">',
1807
+ ' <BaseURL>v-sd.mp4</BaseURL>',
1808
+ ' <SegmentBase indexRange="100-200" />',
1809
+ ' </Representation>',
1810
+ ' </AdaptationSet>',
1811
+ ' <AdaptationSet id="3" mimeType="audio/mp4">',
1812
+ ' <Representation id="audio-en">',
1813
+ ' <BaseURL>a-en.mp4</BaseURL>',
1814
+ ' <SegmentBase indexRange="100-200" />',
1815
+ ' </Representation>',
1816
+ ' </AdaptationSet>',
1817
+ ' <AdaptationSet mimeType="text/vtt" lang="de">',
1818
+ ' <Representation>',
1819
+ ' <BaseURL>http://example.com/de.vtt</BaseURL>',
1820
+ ' </Representation>',
1821
+ ' </AdaptationSet>',
1822
+ ' </Period>',
1823
+ '</MPD>',
1824
+ ].join('\n');
1825
+
1826
+ fakeNetEngine.setResponseText('dummy://foo', manifestText);
1827
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
1828
+ config.dash.manifestPreprocessor = (mpd) => {
1829
+ const selector = 'AdaptationSet[mimeType="text/vtt"';
1830
+ const vttElements = mpd.querySelectorAll(selector);
1831
+ for (const element of vttElements) {
1832
+ element.parentNode.removeChild(element);
1833
+ }
1834
+ };
1835
+ parser.configure(config);
1836
+
1837
+ /** @type {shaka.extern.Manifest} */
1838
+ const manifest = await parser.start('dummy://foo', playerInterface);
1839
+ const stream = manifest.textStreams[0];
1840
+ expect(stream).toBeUndefined();
1841
+ });
1842
+
1801
1843
  it('converts Accessibility element to "kind"', async () => {
1802
1844
  const manifestText = [
1803
1845
  '<MPD minBufferTime="PT75S">',
@@ -1318,6 +1318,103 @@ describe('HlsParser', () => {
1318
1318
  expect(actual.variants[0].video).toBeTruthy();
1319
1319
  });
1320
1320
 
1321
+ it('parse image streams', async () => {
1322
+ const master = [
1323
+ '#EXTM3U\n',
1324
+ '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
1325
+ 'URI="text"\n',
1326
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
1327
+ 'CHANNELS="2",URI="audio"\n',
1328
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
1329
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
1330
+ 'video\n',
1331
+ '#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg",',
1332
+ 'URI="image"\n',
1333
+ ].join('');
1334
+
1335
+ const video = [
1336
+ '#EXTM3U\n',
1337
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1338
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
1339
+ '#EXTINF:5,\n',
1340
+ 'main.mp4\n',
1341
+ '#EXTINF:5,\n',
1342
+ 'main.mp4\n',
1343
+ '#EXTINF:5,\n',
1344
+ 'main.mp4\n',
1345
+ ].join('');
1346
+
1347
+ const audio = [
1348
+ '#EXTM3U\n',
1349
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1350
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
1351
+ '#EXTINF:5,\n',
1352
+ 'main.mp4\n',
1353
+ '#EXTINF:5,\n',
1354
+ 'main.mp4\n',
1355
+ '#EXTINF:5,\n',
1356
+ 'main.mp4\n',
1357
+ ].join('');
1358
+
1359
+ const text = [
1360
+ '#EXTM3U\n',
1361
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1362
+ '#EXTINF:5,\n',
1363
+ '#EXT-X-BYTERANGE:121090@616\n',
1364
+ 'main.vtt',
1365
+ ].join('');
1366
+
1367
+ const image = [
1368
+ '#EXTM3U\n',
1369
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1370
+ '#EXTINF:5,\n',
1371
+ 'image.jpg\n',
1372
+ '#EXTINF:5,\n',
1373
+ '#EXT-X-TILES:RESOLUTION=640x360,LAYOUT=5x2,DURATION=6.006\n',
1374
+ 'image.jpg\n',
1375
+ '#EXTINF:5,\n',
1376
+ 'image.jpg\n',
1377
+ ].join('');
1378
+
1379
+ fakeNetEngine
1380
+ .setResponseText('test:/master', master)
1381
+ .setResponseText('test:/audio', audio)
1382
+ .setResponseText('test:/video', video)
1383
+ .setResponseText('test:/text', text)
1384
+ .setResponseText('test:/image', image)
1385
+ .setResponseText('test:/main.vtt', vttText)
1386
+ .setResponseValue('test:/init.mp4', initSegmentData)
1387
+ .setResponseValue('test:/main.mp4', segmentData);
1388
+
1389
+ const actual = await parser.start('test:/master', playerInterface);
1390
+
1391
+ expect(actual.imageStreams.length).toBe(1);
1392
+ expect(actual.textStreams.length).toBe(1);
1393
+ expect(actual.variants.length).toBe(1);
1394
+
1395
+ const thumbnails = actual.imageStreams[0];
1396
+
1397
+ await thumbnails.createSegmentIndex();
1398
+ goog.asserts.assert(thumbnails.segmentIndex != null, 'Null segmentIndex!');
1399
+
1400
+ const firstThumbnailReference = thumbnails.segmentIndex.get(0);
1401
+ const secondThumbnailReference = thumbnails.segmentIndex.get(1);
1402
+ const thirdThumbnailReference = thumbnails.segmentIndex.get(2);
1403
+
1404
+ expect(firstThumbnailReference).not.toBe(null);
1405
+ expect(secondThumbnailReference).not.toBe(null);
1406
+ expect(thirdThumbnailReference).not.toBe(null);
1407
+ if (firstThumbnailReference) {
1408
+ expect(firstThumbnailReference.getTilesLayout()).toBe('1x1');
1409
+ }
1410
+ if (secondThumbnailReference) {
1411
+ expect(secondThumbnailReference.getTilesLayout()).toBe('5x2');
1412
+ }
1413
+ if (thirdThumbnailReference) {
1414
+ expect(thirdThumbnailReference.getTilesLayout()).toBe('1x1');
1415
+ }
1416
+ });
1417
+
1321
1418
  it('Disable audio does not create audio streams', async () => {
1322
1419
  const master = [
1323
1420
  '#EXTM3U\n',
@@ -1328,6 +1425,8 @@ describe('HlsParser', () => {
1328
1425
  '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
1329
1426
  'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
1330
1427
  'video\n',
1428
+ '#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg",',
1429
+ 'URI="image"\n',
1331
1430
  ].join('');
1332
1431
 
1333
1432
  const video = [
@@ -1360,11 +1459,23 @@ describe('HlsParser', () => {
1360
1459
  'main.vtt',
1361
1460
  ].join('');
1362
1461
 
1462
+ const image = [
1463
+ '#EXTM3U\n',
1464
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1465
+ '#EXTINF:5,\n',
1466
+ 'image.jpg\n',
1467
+ '#EXTINF:5,\n',
1468
+ 'image.jpg\n',
1469
+ '#EXTINF:5,\n',
1470
+ 'image.jpg\n',
1471
+ ].join('');
1472
+
1363
1473
  fakeNetEngine
1364
1474
  .setResponseText('test:/master', master)
1365
1475
  .setResponseText('test:/audio', audio)
1366
1476
  .setResponseText('test:/video', video)
1367
1477
  .setResponseText('test:/text', text)
1478
+ .setResponseText('test:/image', image)
1368
1479
  .setResponseText('test:/main.vtt', vttText)
1369
1480
  .setResponseValue('test:/init.mp4', initSegmentData)
1370
1481
  .setResponseValue('test:/main.mp4', segmentData);
@@ -1389,6 +1500,8 @@ describe('HlsParser', () => {
1389
1500
  '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
1390
1501
  'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
1391
1502
  'video\n',
1503
+ '#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg",',
1504
+ 'URI="image"\n',
1392
1505
  ].join('');
1393
1506
 
1394
1507
  const video = [
@@ -1421,11 +1534,23 @@ describe('HlsParser', () => {
1421
1534
  'main.vtt',
1422
1535
  ].join('');
1423
1536
 
1537
+ const image = [
1538
+ '#EXTM3U\n',
1539
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1540
+ '#EXTINF:5,\n',
1541
+ 'image.jpg\n',
1542
+ '#EXTINF:5,\n',
1543
+ 'image.jpg\n',
1544
+ '#EXTINF:5,\n',
1545
+ 'image.jpg\n',
1546
+ ].join('');
1547
+
1424
1548
  fakeNetEngine
1425
1549
  .setResponseText('test:/master', master)
1426
1550
  .setResponseText('test:/audio', audio)
1427
1551
  .setResponseText('test:/video', video)
1428
1552
  .setResponseText('test:/text', text)
1553
+ .setResponseText('test:/image', image)
1429
1554
  .setResponseText('test:/main.vtt', vttText)
1430
1555
  .setResponseValue('test:/init.mp4', initSegmentData)
1431
1556
  .setResponseValue('test:/main.mp4', segmentData);
@@ -1450,6 +1575,8 @@ describe('HlsParser', () => {
1450
1575
  '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
1451
1576
  'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
1452
1577
  'video\n',
1578
+ '#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg",',
1579
+ 'URI="image"\n',
1453
1580
  ].join('');
1454
1581
 
1455
1582
  const video = [
@@ -1482,11 +1609,23 @@ describe('HlsParser', () => {
1482
1609
  'main.vtt',
1483
1610
  ].join('');
1484
1611
 
1612
+ const image = [
1613
+ '#EXTM3U\n',
1614
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1615
+ '#EXTINF:5,\n',
1616
+ 'image.jpg\n',
1617
+ '#EXTINF:5,\n',
1618
+ 'image.jpg\n',
1619
+ '#EXTINF:5,\n',
1620
+ 'image.jpg\n',
1621
+ ].join('');
1622
+
1485
1623
  fakeNetEngine
1486
1624
  .setResponseText('test:/master', master)
1487
1625
  .setResponseText('test:/audio', audio)
1488
1626
  .setResponseText('test:/video', video)
1489
1627
  .setResponseText('test:/text', text)
1628
+ .setResponseText('test:/image', image)
1490
1629
  .setResponseText('test:/main.vtt', vttText)
1491
1630
  .setResponseValue('test:/init.mp4', initSegmentData)
1492
1631
  .setResponseValue('test:/main.mp4', segmentData);
@@ -1500,6 +1639,80 @@ describe('HlsParser', () => {
1500
1639
  expect(stream).toBeUndefined();
1501
1640
  });
1502
1641
 
1642
+ it('Disable thumbnails does not create image streams', async () => {
1643
+ const master = [
1644
+ '#EXTM3U\n',
1645
+ '#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
1646
+ 'URI="text"\n',
1647
+ '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
1648
+ 'CHANNELS="2",URI="audio"\n',
1649
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
1650
+ 'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1",SUBTITLES="sub1"\n',
1651
+ 'video\n',
1652
+ '#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg",',
1653
+ 'URI="image"\n',
1654
+ ].join('');
1655
+
1656
+ const video = [
1657
+ '#EXTM3U\n',
1658
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1659
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
1660
+ '#EXTINF:5,\n',
1661
+ 'main.mp4\n',
1662
+ '#EXTINF:5,\n',
1663
+ 'main.mp4\n',
1664
+ '#EXTINF:5,\n',
1665
+ 'main.mp4\n',
1666
+ ].join('');
1667
+
1668
+ const audio = [
1669
+ '#EXTM3U\n',
1670
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1671
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
1672
+ '#EXTINF:5,\n',
1673
+ 'main.mp4\n',
1674
+ '#EXTINF:5,\n',
1675
+ 'main.mp4\n',
1676
+ ].join('');
1677
+
1678
+ const text = [
1679
+ '#EXTM3U\n',
1680
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1681
+ '#EXTINF:5,\n',
1682
+ '#EXT-X-BYTERANGE:121090@616\n',
1683
+ 'main.vtt',
1684
+ ].join('');
1685
+
1686
+ const image = [
1687
+ '#EXTM3U\n',
1688
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1689
+ '#EXTINF:5,\n',
1690
+ 'image.jpg\n',
1691
+ '#EXTINF:5,\n',
1692
+ 'image.jpg\n',
1693
+ '#EXTINF:5,\n',
1694
+ 'image.jpg\n',
1695
+ ].join('');
1696
+
1697
+ fakeNetEngine
1698
+ .setResponseText('test:/master', master)
1699
+ .setResponseText('test:/audio', audio)
1700
+ .setResponseText('test:/video', video)
1701
+ .setResponseText('test:/text', text)
1702
+ .setResponseText('test:/image', image)
1703
+ .setResponseText('test:/main.vtt', vttText)
1704
+ .setResponseValue('test:/init.mp4', initSegmentData)
1705
+ .setResponseValue('test:/main.mp4', segmentData);
1706
+
1707
+ const config = shaka.util.PlayerConfiguration.createDefault().manifest;
1708
+ config.disableThumbnails = true;
1709
+ parser.configure(config);
1710
+
1711
+ const actual = await parser.start('test:/master', playerInterface);
1712
+ const stream = actual.imageStreams[0];
1713
+ expect(stream).toBeUndefined();
1714
+ });
1715
+
1503
1716
  it('parses manifest with MP4+TTML streams', async () => {
1504
1717
  const master = [
1505
1718
  '#EXTM3U\n',
@@ -1670,6 +1883,43 @@ describe('HlsParser', () => {
1670
1883
  expect(actual).toEqual(manifest);
1671
1884
  });
1672
1885
 
1886
+ it('drops failed image streams when configured to', async () => {
1887
+ const master = [
1888
+ '#EXTM3U\n',
1889
+ '#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",',
1890
+ 'RESOLUTION=960x540,FRAME-RATE=60\n',
1891
+ 'video\n',
1892
+ '#EXT-X-IMAGE-STREAM-INF:RESOLUTION=240×135,CODECS="jpeg"\n',
1893
+ ].join('');
1894
+
1895
+ const media = [
1896
+ '#EXTM3U\n',
1897
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
1898
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
1899
+ '#EXTINF:5,\n',
1900
+ '#EXT-X-BYTERANGE:121090@616\n',
1901
+ 'main.mp4',
1902
+ ].join('');
1903
+
1904
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
1905
+ manifest.anyTimeline();
1906
+ manifest.addPartialVariant((variant) => {
1907
+ variant.addPartialStream(ContentType.VIDEO);
1908
+ });
1909
+ });
1910
+
1911
+ fakeNetEngine
1912
+ .setResponseText('test:/master', master)
1913
+ .setResponseText('test:/audio', media)
1914
+ .setResponseText('test:/video', media)
1915
+ .setResponseValue('test:/init.mp4', initSegmentData)
1916
+ .setResponseValue('test:/main.mp4', segmentData);
1917
+
1918
+ config.hls.ignoreImageStreamFailures = true;
1919
+ const actual = await parser.start('test:/master', playerInterface);
1920
+ expect(actual).toEqual(manifest);
1921
+ });
1922
+
1673
1923
  it('parses video described by a media tag', async () => {
1674
1924
  const master = [
1675
1925
  '#EXTM3U\n',
@@ -162,7 +162,7 @@ describe('DrmEngine', () => {
162
162
  document.body.removeChild(video);
163
163
  });
164
164
 
165
- function checkSupport() {
165
+ function checkTrueDrmSupport() {
166
166
  if (shaka.util.Platform.isXboxOne()) {
167
167
  // Axinom won't issue a license for an Xbox One. The error message from
168
168
  // the license server says "Your DRM client's security level is 150, but
@@ -174,16 +174,11 @@ describe('DrmEngine', () => {
174
174
  return support['com.widevine.alpha'] || support['com.microsoft.playready'];
175
175
  }
176
176
 
177
- for (const useMediaCapabilities of [true, false]) {
178
- const isEnabled = useMediaCapabilities ? 'enabled' : 'disabled';
179
- filterDescribe('basic flow with MediaCapabilities ' + isEnabled,
180
- checkSupport, () => {
181
- testBasicFlow(useMediaCapabilities);
182
- });
177
+ function checkClearKeySupport() {
178
+ return support['org.w3.clearkey'];
183
179
  }
184
180
 
185
-
186
- function testBasicFlow(useMediaCapabilities) {
181
+ filterDescribe('basic flow', checkTrueDrmSupport, () => {
187
182
  drmIt('gets a license and can play encrypted segments', async () => {
188
183
  // The error callback should not be invoked.
189
184
  onErrorSpy.and.callFake(fail);
@@ -226,8 +221,7 @@ describe('DrmEngine', () => {
226
221
 
227
222
  const variants = manifest.variants;
228
223
 
229
- await drmEngine.initForPlayback(variants, manifest.offlineSessionIds,
230
- useMediaCapabilities);
224
+ await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
231
225
  await drmEngine.attach(video);
232
226
  await mediaSourceEngine.appendBuffer(
233
227
  ContentType.VIDEO, videoInitSegment, null, null,
@@ -283,5 +277,85 @@ describe('DrmEngine', () => {
283
277
  expect(video.readyState).toBeGreaterThan(1);
284
278
  expect(video.currentTime).toBeGreaterThan(0);
285
279
  });
286
- } // describe('basic flow')
280
+ }); // describe('basic flow')
281
+
282
+ filterDescribe('ClearKey', checkClearKeySupport, () => {
283
+ drmIt('plays encrypted content with the ClearKey CDM', async () => {
284
+ // Configure DrmEngine for ClearKey playback.
285
+ const config = shaka.util.PlayerConfiguration.createDefault().drm;
286
+ config.clearKeys = {
287
+ // From https://github.com/Axinom/public-test-vectors/tree/conservative#v61-multidrm
288
+ '6e5a1d26275747d78046eaa5d1d34b5a': '197f26f572c864d2338b3ae5d114ea9c',
289
+ };
290
+ drmEngine.configure(config);
291
+
292
+ // The error callback should not be invoked.
293
+ onErrorSpy.and.callFake(fail);
294
+
295
+ /** @type {!shaka.util.PublicPromise} */
296
+ const encryptedEventSeen = new shaka.util.PublicPromise();
297
+ eventManager.listen(video, 'encrypted', () => {
298
+ encryptedEventSeen.resolve();
299
+ });
300
+ eventManager.listen(video, 'error', () => {
301
+ fail('MediaError code ' + video.error.code);
302
+ let extended = video.error.msExtendedCode;
303
+ if (extended) {
304
+ if (extended < 0) {
305
+ extended += Math.pow(2, 32);
306
+ }
307
+ fail('MediaError msExtendedCode ' + extended.toString(16));
308
+ }
309
+ });
310
+
311
+ /** @type {!shaka.util.PublicPromise} */
312
+ const keyStatusEventSeen = new shaka.util.PublicPromise();
313
+ onKeyStatusSpy.and.callFake(() => {
314
+ keyStatusEventSeen.resolve();
315
+ });
316
+
317
+ const variants = manifest.variants;
318
+
319
+ await drmEngine.initForPlayback(variants, manifest.offlineSessionIds);
320
+ await drmEngine.attach(video);
321
+ await mediaSourceEngine.appendBuffer(
322
+ ContentType.VIDEO, videoInitSegment, null, null,
323
+ /* hasClosedCaptions= */ false);
324
+ await mediaSourceEngine.appendBuffer(
325
+ ContentType.AUDIO, audioInitSegment, null, null,
326
+ /* hasClosedCaptions= */ false);
327
+ await encryptedEventSeen;
328
+
329
+ // Some platforms (notably 2017 Tizen TVs) do not fire key status
330
+ // events.
331
+ const keyStatusTimeout = shaka.test.Util.delay(5);
332
+ await Promise.race([keyStatusTimeout, keyStatusEventSeen]);
333
+
334
+ const call = onKeyStatusSpy.calls.mostRecent();
335
+ if (call) {
336
+ const map = /** @type {!Object} */ (call.args[0]);
337
+ expect(Object.keys(map).length).not.toBe(0);
338
+ for (const k in map) {
339
+ expect(map[k]).toBe('usable');
340
+ }
341
+ }
342
+
343
+ await mediaSourceEngine.appendBuffer(
344
+ ContentType.VIDEO, videoSegment, 0, 10,
345
+ /* hasClosedCaptions= */ false);
346
+ await mediaSourceEngine.appendBuffer(
347
+ ContentType.AUDIO, audioSegment, 0, 10,
348
+ /* hasClosedCaptions= */ false);
349
+
350
+ expect(video.buffered.end(0)).toBeGreaterThan(0);
351
+ video.play();
352
+
353
+ const waiter = new shaka.test.Waiter(eventManager).timeoutAfter(15);
354
+ await waiter.waitForMovement(video);
355
+
356
+ // Something should have played by now.
357
+ expect(video.readyState).toBeGreaterThan(1);
358
+ expect(video.currentTime).toBeGreaterThan(0);
359
+ });
360
+ }); // describe('ClearKey')
287
361
  });