shaka-player 4.5.0 → 4.6.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 (214) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/AUTHORS +2 -0
  3. package/CHANGELOG.md +98 -0
  4. package/CODE_OF_CONDUCT.md +1 -1
  5. package/CONTRIBUTORS +2 -0
  6. package/README.md +38 -32
  7. package/demo/common/asset.js +11 -17
  8. package/demo/common/assets.js +117 -11
  9. package/demo/config.js +28 -1
  10. package/demo/custom.js +52 -38
  11. package/demo/main.js +11 -3
  12. package/demo/search.js +7 -0
  13. package/dist/controls.css +1 -1
  14. package/dist/controls.css.map +1 -1
  15. package/dist/demo.compiled.debug.js +133 -124
  16. package/dist/demo.compiled.debug.map +3 -3
  17. package/dist/demo.compiled.js +133 -124
  18. package/dist/demo.compiled.map +3 -3
  19. package/dist/deps.js +20 -18
  20. package/dist/locales.js +35 -31
  21. package/dist/receiver.compiled.debug.js +58 -50
  22. package/dist/receiver.compiled.debug.map +3 -3
  23. package/dist/receiver.compiled.js +58 -50
  24. package/dist/receiver.compiled.map +3 -3
  25. package/dist/shaka-player.compiled.d.ts +440 -390
  26. package/dist/shaka-player.compiled.debug.d.ts +440 -390
  27. package/dist/shaka-player.compiled.debug.externs.js +162 -77
  28. package/dist/shaka-player.compiled.debug.js +786 -736
  29. package/dist/shaka-player.compiled.debug.map +4 -4
  30. package/dist/shaka-player.compiled.externs.js +162 -77
  31. package/dist/shaka-player.compiled.js +1154 -1121
  32. package/dist/shaka-player.compiled.map +4 -4
  33. package/dist/shaka-player.ui.d.ts +491 -437
  34. package/dist/shaka-player.ui.debug.d.ts +491 -437
  35. package/dist/shaka-player.ui.debug.externs.js +167 -77
  36. package/dist/shaka-player.ui.debug.js +877 -811
  37. package/dist/shaka-player.ui.debug.map +4 -4
  38. package/dist/shaka-player.ui.externs.js +167 -77
  39. package/dist/shaka-player.ui.js +1362 -1318
  40. package/dist/shaka-player.ui.map +4 -4
  41. package/docs/tutorials/ad_monetization.md +54 -0
  42. package/docs/tutorials/basic-usage.md +2 -1
  43. package/docs/tutorials/errors.md +2 -1
  44. package/docs/tutorials/offline.md +10 -8
  45. package/docs/tutorials/plugins.md +5 -0
  46. package/docs/tutorials/ui-customization.md +13 -0
  47. package/docs/tutorials/ui.md +2 -1
  48. package/docs/tutorials/upgrade.md +3 -0
  49. package/externs/managedmediasource.js +24 -0
  50. package/externs/shaka/abr_manager.js +6 -2
  51. package/externs/shaka/cea.js +7 -0
  52. package/externs/shaka/codecs.js +5 -3
  53. package/externs/shaka/manifest.js +21 -5
  54. package/externs/shaka/manifest_parser.js +17 -0
  55. package/externs/shaka/net.js +2 -2
  56. package/externs/shaka/offline.js +9 -2
  57. package/externs/shaka/player.js +53 -7
  58. package/externs/shaka/text.js +3 -1
  59. package/externs/shaka/transmuxer.js +1 -1
  60. package/lib/abr/simple_abr_manager.js +25 -8
  61. package/lib/ads/media_tailor_ad_manager.js +14 -3
  62. package/lib/cast/cast_receiver.js +1 -1
  63. package/lib/cea/cea_decoder.js +28 -0
  64. package/lib/cea/dummy_caption_decoder.js +5 -0
  65. package/lib/dash/dash_parser.js +249 -83
  66. package/lib/dash/mpd_utils.js +52 -31
  67. package/lib/dash/segment_base.js +4 -5
  68. package/lib/dash/segment_list.js +5 -4
  69. package/lib/dash/segment_template.js +83 -26
  70. package/lib/hls/hls_classes.js +28 -43
  71. package/lib/hls/hls_parser.js +761 -649
  72. package/lib/hls/hls_utils.js +64 -5
  73. package/lib/hls/manifest_text_parser.js +7 -11
  74. package/lib/media/adaptation_set.js +20 -13
  75. package/lib/media/adaptation_set_criteria.js +70 -18
  76. package/lib/media/closed_caption_parser.js +13 -0
  77. package/lib/media/content_workarounds.js +6 -0
  78. package/lib/media/drm_engine.js +8 -13
  79. package/lib/media/manifest_parser.js +8 -113
  80. package/lib/media/media_source_capabilities.js +10 -3
  81. package/lib/media/media_source_engine.js +75 -21
  82. package/lib/media/play_rate_controller.js +8 -38
  83. package/lib/media/presentation_timeline.js +7 -1
  84. package/lib/media/quality_observer.js +1 -1
  85. package/lib/media/segment_index.js +5 -5
  86. package/lib/media/segment_prefetch.js +92 -24
  87. package/lib/media/segment_reference.js +67 -1
  88. package/lib/media/segment_utils.js +343 -0
  89. package/lib/media/stall_detector.js +0 -3
  90. package/lib/media/streaming_engine.js +41 -21
  91. package/lib/mss/mss_parser.js +18 -2
  92. package/lib/net/networking_engine.js +66 -12
  93. package/lib/net/networking_utils.js +95 -0
  94. package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
  95. package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
  96. package/lib/offline/manifest_converter.js +2 -0
  97. package/lib/offline/offline_manifest_parser.js +10 -0
  98. package/lib/offline/storage.js +11 -7
  99. package/lib/player.js +993 -1502
  100. package/lib/polyfill/mediasource.js +22 -1
  101. package/lib/text/mp4_ttml_parser.js +2 -2
  102. package/lib/text/srt_text_parser.js +2 -2
  103. package/lib/text/stub_text_displayer.js +51 -0
  104. package/lib/text/text_engine.js +3 -2
  105. package/lib/text/ttml_text_parser.js +28 -5
  106. package/lib/text/ui_text_displayer.js +4 -0
  107. package/lib/text/vtt_text_parser.js +7 -2
  108. package/lib/transmuxer/aac_transmuxer.js +1 -1
  109. package/lib/transmuxer/ac3_transmuxer.js +1 -1
  110. package/lib/transmuxer/ec3_transmuxer.js +1 -1
  111. package/lib/transmuxer/h264.js +5 -4
  112. package/lib/transmuxer/h265.js +5 -4
  113. package/lib/transmuxer/mp3_transmuxer.js +1 -1
  114. package/lib/transmuxer/mpeg_ts_transmuxer.js +1 -1
  115. package/lib/transmuxer/mss_transmuxer.js +1 -1
  116. package/lib/transmuxer/ts_transmuxer.js +33 -13
  117. package/lib/util/buffer_utils.js +26 -4
  118. package/lib/util/content_steering_manager.js +358 -0
  119. package/lib/util/error.js +2 -6
  120. package/lib/util/fairplay_utils.js +1 -1
  121. package/lib/util/fake_event.js +5 -1
  122. package/lib/util/id3_utils.js +50 -1
  123. package/lib/util/manifest_parser_utils.js +12 -8
  124. package/lib/util/mp4_generator.js +2 -4
  125. package/lib/util/mutex.js +59 -0
  126. package/lib/util/object_utils.js +1 -1
  127. package/lib/util/periods.js +154 -179
  128. package/lib/util/platform.js +4 -3
  129. package/lib/util/player_configuration.js +35 -6
  130. package/lib/util/stream_utils.js +105 -98
  131. package/lib/util/ts_parser.js +124 -73
  132. package/lib/util/uint8array_utils.js +5 -1
  133. package/lib/util/xml_utils.js +5 -0
  134. package/maintained-branches.md +2 -2
  135. package/package.json +1 -1
  136. package/roadmap.md +15 -5
  137. package/shaka-player.uncompiled.js +0 -1
  138. package/test/abr/simple_abr_manager_unit.js +33 -33
  139. package/test/ads/ad_manager_unit.js +3 -2
  140. package/test/cast/cast_receiver_integration.js +3 -4
  141. package/test/cast/cast_utils_unit.js +2 -0
  142. package/test/cea/cea_decoder_unit.js +7 -0
  143. package/test/codec_switching/codec_switching_integration.js +2 -1
  144. package/test/dash/dash_parser_integration.js +2 -1
  145. package/test/dash/dash_parser_live_unit.js +0 -19
  146. package/test/dash/dash_parser_manifest_unit.js +494 -2
  147. package/test/dash/mpd_utils_unit.js +61 -30
  148. package/test/hls/hls_live_unit.js +12 -3
  149. package/test/hls/hls_parser_unit.js +377 -27
  150. package/test/hls/manifest_text_parser_unit.js +32 -99
  151. package/test/media/adaptation_set_criteria_unit.js +172 -21
  152. package/test/media/adaptation_set_unit.js +1 -0
  153. package/test/media/content_workarounds_unit.js +1 -1
  154. package/test/media/drm_engine_unit.js +3 -3
  155. package/test/media/media_source_engine_unit.js +1 -1
  156. package/test/media/play_rate_controller_unit.js +0 -48
  157. package/test/media/segment_index_unit.js +2 -1
  158. package/test/mss/mss_player_integration.js +3 -2
  159. package/test/net/networking_engine_unit.js +3 -3
  160. package/test/{media/manifest_parser_unit.js → net/networking_utils_unit.js} +2 -2
  161. package/test/offline/manifest_convert_unit.js +9 -0
  162. package/test/offline/offline_integration.js +2 -1
  163. package/test/offline/storage_integration.js +21 -9
  164. package/test/player_external.js +4 -2
  165. package/test/player_integration.js +15 -4
  166. package/test/player_load_graph_integration.js +47 -151
  167. package/test/player_src_equals_external.js +1 -0
  168. package/test/player_src_equals_integration.js +71 -4
  169. package/test/player_unit.js +80 -27
  170. package/test/test/util/fake_media_source_engine.js +5 -0
  171. package/test/test/util/fake_networking_engine.js +9 -1
  172. package/test/test/util/fake_segment_prefetch.js +3 -0
  173. package/test/test/util/manifest_generator.js +4 -0
  174. package/test/test/util/manifest_parser_util.js +14 -2
  175. package/test/test/util/offline_utils.js +1 -0
  176. package/test/test/util/simple_fakes.js +2 -2
  177. package/test/test/util/streaming_engine_util.js +3 -0
  178. package/test/test/util/test_scheme.js +6 -0
  179. package/test/test/util/ui_utils.js +4 -3
  180. package/test/text/mp4_ttml_parser_unit.js +4 -4
  181. package/test/text/srt_text_parser_unit.js +1 -1
  182. package/test/text/text_displayer_layout_unit.js +1 -1
  183. package/test/text/text_engine_unit.js +6 -0
  184. package/test/text/ttml_text_parser_unit.js +33 -2
  185. package/test/transmuxer/transmuxer_engine_integration.js +9 -0
  186. package/test/transmuxer/transmuxer_integration.js +33 -3
  187. package/test/ui/ad_ui_unit.js +2 -2
  188. package/test/ui/ui_customization_unit.js +17 -16
  189. package/test/ui/ui_integration.js +30 -20
  190. package/test/ui/ui_unit.js +25 -19
  191. package/test/util/buffer_utils_unit.js +1 -1
  192. package/test/util/content_steering_manager_unit.js +427 -0
  193. package/test/util/id3_utils_unit.js +52 -0
  194. package/test/util/stream_utils_unit.js +17 -113
  195. package/test/util/ts_parser_unit.js +3 -3
  196. package/third_party/closure-uri/uri.js +13 -1
  197. package/ui/audio_language_selection.js +9 -2
  198. package/ui/externs/ui.js +17 -2
  199. package/ui/language_utils.js +43 -8
  200. package/ui/less/range_elements.less +33 -0
  201. package/ui/locales/en.json +1 -0
  202. package/ui/locales/es-419.json +1 -0
  203. package/ui/locales/es.json +1 -0
  204. package/ui/locales/source.json +4 -0
  205. package/ui/localization.js +10 -0
  206. package/ui/remote_button.js +3 -1
  207. package/ui/resolution_selection.js +47 -10
  208. package/ui/seek_bar.js +296 -3
  209. package/ui/text_selection.js +2 -1
  210. package/ui/ui.js +6 -2
  211. package/lib/routing/node.js +0 -23
  212. package/lib/routing/payload.js +0 -47
  213. package/lib/routing/walker.js +0 -587
  214. package/test/routing/walker_unit.js +0 -505
@@ -390,88 +390,6 @@ describe('StreamUtils', () => {
390
390
  });
391
391
  });
392
392
 
393
- describe('filterVariantsByAudioChannelCount', () => {
394
- it('chooses variants with preferred audio channels count', () => {
395
- manifest = shaka.test.ManifestGenerator.generate((manifest) => {
396
- manifest.addVariant(0, (variant) => {
397
- variant.addAudio(0, (stream) => {
398
- stream.channelsCount = 2;
399
- });
400
- });
401
- manifest.addVariant(1, (variant) => {
402
- variant.addAudio(1, (stream) => {
403
- stream.channelsCount = 6;
404
- });
405
- });
406
- manifest.addVariant(2, (variant) => {
407
- variant.addAudio(2, (stream) => {
408
- stream.channelsCount = 2;
409
- });
410
- });
411
- });
412
-
413
- const chosen = StreamUtils.filterVariantsByAudioChannelCount(
414
- manifest.variants, 2);
415
- expect(chosen.length).toBe(2);
416
- expect(chosen[0]).toBe(manifest.variants[0]);
417
- expect(chosen[1]).toBe(manifest.variants[2]);
418
- });
419
-
420
- it('chooses variants with largest audio channel count less than config' +
421
- ' when no exact audio channel count match is possible', () => {
422
- manifest = shaka.test.ManifestGenerator.generate((manifest) => {
423
- manifest.addVariant(0, (variant) => {
424
- variant.addAudio(0, (stream) => {
425
- stream.channelsCount = 2;
426
- });
427
- });
428
- manifest.addVariant(1, (variant) => {
429
- variant.addAudio(1, (stream) => {
430
- stream.channelsCount = 8;
431
- });
432
- });
433
- manifest.addVariant(2, (variant) => {
434
- variant.addAudio(2, (stream) => {
435
- stream.channelsCount = 2;
436
- });
437
- });
438
- });
439
-
440
- const chosen = StreamUtils.filterVariantsByAudioChannelCount(
441
- manifest.variants, 6);
442
- expect(chosen.length).toBe(2);
443
- expect(chosen[0]).toBe(manifest.variants[0]);
444
- expect(chosen[1]).toBe(manifest.variants[2]);
445
- });
446
-
447
- it('chooses variants with fewest audio channels when none fit in the ' +
448
- 'config', () => {
449
- manifest = shaka.test.ManifestGenerator.generate((manifest) => {
450
- manifest.addVariant(0, (variant) => {
451
- variant.addAudio(0, (stream) => {
452
- stream.channelsCount = 6;
453
- });
454
- });
455
- manifest.addVariant(1, (variant) => {
456
- variant.addAudio(1, (stream) => {
457
- stream.channelsCount = 8;
458
- });
459
- });
460
- manifest.addVariant(2, (variant) => {
461
- variant.addAudio(2, (stream) => {
462
- stream.channelsCount = 6;
463
- });
464
- });
465
- });
466
-
467
- const chosen = StreamUtils.filterVariantsByAudioChannelCount(
468
- manifest.variants, 2);
469
- expect(chosen.length).toBe(2);
470
- expect(chosen[0]).toBe(manifest.variants[0]);
471
- expect(chosen[1]).toBe(manifest.variants[2]);
472
- });
473
- });
474
-
475
393
  describe('getDecodingInfosForVariants', () => {
476
394
  it('for multiplexd content', async () => {
477
395
  manifest = shaka.test.ManifestGenerator.generate((manifest) => {
@@ -634,10 +552,7 @@ describe('StreamUtils', () => {
634
552
  });
635
553
  });
636
554
 
637
- const noVariant = null;
638
- await shaka.util.StreamUtils.filterManifest(
639
- fakeDrmEngine, noVariant, manifest,
640
- shaka.config.CodecSwitchingStrategy.RELOAD);
555
+ await shaka.util.StreamUtils.filterManifest(fakeDrmEngine, manifest);
641
556
 
642
557
  // Covers a regression in which we would remove streams with codecs.
643
558
  // The last two streams should be removed because their full MIME types
@@ -672,10 +587,7 @@ describe('StreamUtils', () => {
672
587
  });
673
588
  });
674
589
 
675
- const noVariant = null;
676
- await shaka.util.StreamUtils.filterManifest(
677
- fakeDrmEngine, noVariant, manifest,
678
- shaka.config.CodecSwitchingStrategy.RELOAD);
590
+ await shaka.util.StreamUtils.filterManifest(fakeDrmEngine, manifest);
679
591
 
680
592
  // Covers a regression in which we would remove streams with codecs.
681
593
  // The first 4 streams should be there because they are always supported.
@@ -714,9 +626,7 @@ describe('StreamUtils', () => {
714
626
  shaka.util.StreamUtils.filterManifestByCurrentVariant =
715
627
  shaka.test.Util.spyFunc(filterManifestByCurrentVariantSpy);
716
628
 
717
- await shaka.util.StreamUtils.filterManifest(
718
- fakeDrmEngine, /* currentVariant= */ null, manifest,
719
- shaka.config.CodecSwitchingStrategy.RELOAD);
629
+ await shaka.util.StreamUtils.filterManifest(fakeDrmEngine, manifest);
720
630
 
721
631
  expect(filterManifestByCurrentVariantSpy).not.toHaveBeenCalled();
722
632
  } finally {
@@ -738,9 +648,7 @@ describe('StreamUtils', () => {
738
648
  });
739
649
  });
740
650
 
741
- await shaka.util.StreamUtils.filterManifest(
742
- fakeDrmEngine, /* currentVariant= */ null, manifest,
743
- shaka.config.CodecSwitchingStrategy.RELOAD);
651
+ await shaka.util.StreamUtils.filterManifest(fakeDrmEngine, manifest);
744
652
 
745
653
  // Covers a regression in which we would remove streams with codecs.
746
654
  // The last two streams should be removed because their full MIME types
@@ -766,9 +674,7 @@ describe('StreamUtils', () => {
766
674
  });
767
675
  });
768
676
 
769
- await shaka.util.StreamUtils.filterManifest(
770
- fakeDrmEngine, /* currentVariant= */ null, manifest,
771
- shaka.config.CodecSwitchingStrategy.RELOAD);
677
+ await shaka.util.StreamUtils.filterManifest(fakeDrmEngine, manifest);
772
678
  expect(manifest.variants.length).toBe(1);
773
679
  });
774
680
 
@@ -784,9 +690,7 @@ describe('StreamUtils', () => {
784
690
  });
785
691
  });
786
692
 
787
- await shaka.util.StreamUtils.filterManifest(
788
- fakeDrmEngine, /* currentVariant= */ null, manifest,
789
- shaka.config.CodecSwitchingStrategy.RELOAD);
693
+ await shaka.util.StreamUtils.filterManifest(fakeDrmEngine, manifest);
790
694
 
791
695
  expect(manifest.variants.length).toBe(1);
792
696
  });
@@ -808,8 +712,7 @@ describe('StreamUtils', () => {
808
712
  });
809
713
  });
810
714
 
811
- await shaka.util.StreamUtils.filterManifest(
812
- fakeDrmEngine, /* currentVariant= */ null, manifest);
715
+ await shaka.util.StreamUtils.filterManifest(fakeDrmEngine, manifest);
813
716
 
814
717
  expect(manifest.variants.length).toBe(2);
815
718
  });
@@ -831,8 +734,7 @@ describe('StreamUtils', () => {
831
734
  });
832
735
  });
833
736
 
834
- await shaka.util.StreamUtils.filterManifest(
835
- fakeDrmEngine, /* currentVariant= */ null, manifest);
737
+ await shaka.util.StreamUtils.filterManifest(fakeDrmEngine, manifest);
836
738
 
837
739
  expect(manifest.variants.length).toBe(2);
838
740
  });
@@ -849,9 +751,7 @@ describe('StreamUtils', () => {
849
751
  });
850
752
  });
851
753
 
852
- await shaka.util.StreamUtils.filterManifest(
853
- fakeDrmEngine, /* currentVariant= */ null, manifest,
854
- shaka.config.CodecSwitchingStrategy.RELOAD);
754
+ await shaka.util.StreamUtils.filterManifest(fakeDrmEngine, manifest);
855
755
 
856
756
  expect(manifest.variants.length).toBe(1);
857
757
  });
@@ -937,7 +837,6 @@ describe('StreamUtils', () => {
937
837
  shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest,
938
838
  /* preferredVideoCodecs= */[],
939
839
  /* preferredAudioCodecs= */[],
940
- /* preferredAudioChannelCount= */2,
941
840
  /* preferredDecodingAttributes= */[]);
942
841
 
943
842
  expect(manifest.variants.length).toBe(2);
@@ -947,12 +846,17 @@ describe('StreamUtils', () => {
947
846
 
948
847
  it('should filter variants by the best available bandwidth' +
949
848
  ' for audio language', () => {
849
+ // This test is flaky in some Tizen devices, due to codec restrictions.
850
+ if (shaka.util.Platform.isTizen()) {
851
+ pending('Skip flaky test in Tizen');
852
+ }
950
853
  manifest = shaka.test.ManifestGenerator.generate((manifest) => {
951
854
  manifest.addVariant(0, (variant) => {
952
855
  variant.bandwidth = 4058558;
953
856
  variant.addAudio(1, (stream) => {
954
857
  stream.bandwidth = 100000;
955
858
  stream.language = 'en';
859
+ stream.mime('audio/mp4', 'mp4a.40.2');
956
860
  });
957
861
  });
958
862
  manifest.addVariant(2, (variant) => {
@@ -960,18 +864,21 @@ describe('StreamUtils', () => {
960
864
  variant.addAudio(3, (stream) => {
961
865
  stream.bandwidth = 200000;
962
866
  stream.language = 'en';
867
+ stream.mime('audio/mp4', 'flac');
963
868
  });
964
869
  });
965
870
  manifest.addVariant(4, (variant) => {
966
871
  variant.addAudio(5, (stream) => {
967
872
  stream.bandwidth = 100000;
968
873
  stream.language = 'es';
874
+ stream.mime('audio/mp4', 'mp4a.40.2');
969
875
  });
970
876
  });
971
877
  manifest.addVariant(6, (variant) => {
972
878
  variant.addAudio(7, (stream) => {
973
879
  stream.bandwidth = 500000;
974
880
  stream.language = 'es';
881
+ stream.mime('audio/mp4', 'flac');
975
882
  });
976
883
  });
977
884
  });
@@ -979,7 +886,6 @@ describe('StreamUtils', () => {
979
886
  shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest,
980
887
  /* preferredVideoCodecs= */[],
981
888
  /* preferredAudioCodecs= */[],
982
- /* preferredAudioChannelCount= */2,
983
889
  /* preferredDecodingAttributes= */[]);
984
890
 
985
891
  expect(manifest.variants.length).toBe(2);
@@ -1009,7 +915,6 @@ describe('StreamUtils', () => {
1009
915
  shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest,
1010
916
  /* preferredVideoCodecs= */[],
1011
917
  /* preferredAudioCodecs= */[],
1012
- /* preferredAudioChannelCount= */2,
1013
918
  /* preferredDecodingAttributes= */[]);
1014
919
 
1015
920
  expect(manifest.variants.length).toBe(2);
@@ -1128,7 +1033,6 @@ describe('StreamUtils', () => {
1128
1033
  shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest,
1129
1034
  /* preferredVideoCodecs= */[],
1130
1035
  /* preferredAudioCodecs= */[],
1131
- /* preferredAudioChannelCount= */2,
1132
1036
  /* preferredDecodingAttributes= */
1133
1037
  [shaka.util.StreamUtils.DecodingAttributes.SMOOTH]);
1134
1038
  // 2 video codecs are smooth. Choose the one with the lowest bandwidth.
@@ -7,6 +7,7 @@
7
7
  describe('TsParser', () => {
8
8
  const Util = shaka.test.Util;
9
9
  const BufferUtils = shaka.util.BufferUtils;
10
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
10
11
 
11
12
  it('probes a TS segment', async () => {
12
13
  const responses = await Promise.all([
@@ -59,9 +60,8 @@ describe('TsParser', () => {
59
60
  ]);
60
61
  const tsSegment = BufferUtils.toUint8(responses[0]);
61
62
  const starttime = new shaka.util.TsParser().parse(tsSegment)
62
- .getStartTime();
63
- expect(starttime.audio).toBeCloseTo(90019.586, 3);
64
- expect(starttime.video).toBe(null);
63
+ .getStartTime(ContentType.AUDIO);
64
+ expect(starttime).toBeCloseTo(90019.586, 3);
65
65
  });
66
66
 
67
67
  it('get the codecs from a TS segment', async () => {
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * Use new goog.Uri(string) to parse a URI string.
10
10
  *
11
- * e.g: <code>var myUri = new goog.Uri(window.location);</code>
11
+ * e.g.: <code>var myUri = new goog.Uri(window.location);</code>
12
12
  *
13
13
  * Implements RFC 3986 for parsing/formatting URIs.
14
14
  * http://www.ietf.org/rfc/rfc3986.txt
@@ -830,6 +830,18 @@ goog.Uri.QueryData.prototype.add = function(key, value) {
830
830
  };
831
831
 
832
832
 
833
+ /**
834
+ * Get the values from a key.
835
+ *
836
+ * @param {string} key Name.
837
+ * @return {Array.<string>}
838
+ */
839
+ goog.Uri.QueryData.prototype.get = function(key) {
840
+ this.ensureKeyMapInitialized_();
841
+ return this.keyMap_[key] || [];
842
+ };
843
+
844
+
833
845
  /**
834
846
  * @return {string} Encoded query string.
835
847
  * @override
@@ -68,7 +68,8 @@ shaka.ui.AudioLanguageSelection = class extends shaka.ui.SettingsMenu {
68
68
  shaka.ui.LanguageUtils.updateTracks(tracks, this.menu,
69
69
  (track) => this.onAudioTrackSelected_(track),
70
70
  /* updateChosen= */ true, this.currentSelection, this.localization,
71
- this.controls.getConfig().trackLabelFormat);
71
+ this.controls.getConfig().trackLabelFormat,
72
+ this.controls.getConfig().showAudioChannelCountVariants);
72
73
  shaka.ui.Utils.focusOnTheChosenItem(this.menu);
73
74
 
74
75
  this.controls.dispatchEvent(
@@ -89,7 +90,13 @@ shaka.ui.AudioLanguageSelection = class extends shaka.ui.SettingsMenu {
89
90
  * @private
90
91
  */
91
92
  onAudioTrackSelected_(track) {
92
- this.player.selectAudioLanguage(track.language, track.roles[0]);
93
+ if (track.channelsCount &&
94
+ this.controls.getConfig().showAudioChannelCountVariants) {
95
+ this.player.selectAudioLanguage(
96
+ track.language, track.roles[0], track.channelsCount);
97
+ } else {
98
+ this.player.selectAudioLanguage(track.language, track.roles[0]);
99
+ }
93
100
  }
94
101
 
95
102
 
package/ui/externs/ui.js CHANGED
@@ -21,7 +21,9 @@ shaka.extern = {};
21
21
  * base: string,
22
22
  * buffered: string,
23
23
  * played: string,
24
- * adBreaks: string
24
+ * adBreaks: string,
25
+ * chapterMarks: string,
26
+ * chapterLabels: string
25
27
  * }}
26
28
  *
27
29
  * @property {string} base
@@ -36,6 +38,13 @@ shaka.extern = {};
36
38
  * @property {string} adBreaks
37
39
  * The CSS background color applied to the portion of the seek bar showing
38
40
  * when the ad breaks are scheduled to occur on the timeline.
41
+ * @property {string} chapterMarks
42
+ * The CSS border color applied to sections of the seek bar showing
43
+ * when the chapters start and end on the timeline.
44
+ * Defaults to dark grey rgb(27, 27, 27).
45
+ * @property {string} chapterLabels
46
+ * The CSS text color applied to the chapter labels that appear above the
47
+ * seek bar on hover. Defaults to white rgb(255, 255, 255).
39
48
  * @exportDoc
40
49
  */
41
50
  shaka.extern.UISeekBarColors;
@@ -88,7 +97,8 @@ shaka.extern.UIVolumeBarColors;
88
97
  * keyboardSeekDistance: number,
89
98
  * keyboardLargeSeekDistance: number,
90
99
  * fullScreenElement: HTMLElement,
91
- * preferDocumentPictureInPicture: boolean
100
+ * preferDocumentPictureInPicture: boolean,
101
+ * showAudioChannelCountVariants: boolean
92
102
  * }}
93
103
  *
94
104
  * @property {!Array.<string>} controlPanelElements
@@ -200,6 +210,11 @@ shaka.extern.UIVolumeBarColors;
200
210
  * Changing this property in mid-playback may produce undesired behavior if
201
211
  * you are already in PiP.
202
212
  * Defaults to true.
213
+ * @property {boolean} showAudioChannelCountVariants
214
+ * Indicates whether the combination of language and channel count should be
215
+ * displayed or if, on the contrary, only the language should be displayed
216
+ * regardless of the channel count.
217
+ * Defaults to true.
203
218
  * @exportDoc
204
219
  */
205
220
  shaka.extern.UIConfiguration;
@@ -26,9 +26,13 @@ shaka.ui.LanguageUtils = class {
26
26
  * @param {!HTMLElement} currentSelectionElement
27
27
  * @param {shaka.ui.Localization} localization
28
28
  * @param {shaka.ui.Overlay.TrackLabelFormat} trackLabelFormat
29
+ * @param {boolean} showAudioChannelCountVariants
29
30
  */
30
31
  static updateTracks(tracks, langMenu, onTrackSelected, updateChosen,
31
- currentSelectionElement, localization, trackLabelFormat) {
32
+ currentSelectionElement, localization, trackLabelFormat,
33
+ showAudioChannelCountVariants) {
34
+ const LocIds = shaka.ui.Locales.Ids;
35
+
32
36
  // TODO: Do the benefits of having this common code in a method still
33
37
  // outweigh the complexity of the parameter list?
34
38
  const selectedTrack = tracks.find((track) => {
@@ -55,8 +59,27 @@ shaka.ui.LanguageUtils = class {
55
59
  }
56
60
  };
57
61
 
58
- const getCombination = (language, rolesString) => {
59
- return language + ': ' + rolesString;
62
+ const getCombination = (language, rolesString, label, channelsCount) => {
63
+ const keys = [
64
+ language,
65
+ rolesString,
66
+ ];
67
+ if (showAudioChannelCountVariants && channelsCount != null) {
68
+ keys.push(channelsCount);
69
+ }
70
+ if (label &&
71
+ trackLabelFormat == shaka.ui.Overlay.TrackLabelFormat.LABEL) {
72
+ keys.push(label);
73
+ }
74
+ return keys.join(': ');
75
+ };
76
+
77
+ const getChannelsCountName = (channelsCount) => {
78
+ let name = '';
79
+ if (channelsCount >= 5) {
80
+ name = ' ' + localization.resolve(LocIds.SURROUND);
81
+ }
82
+ return name;
60
83
  };
61
84
 
62
85
  /** @type {!Map.<string, !Set.<string>>} */
@@ -72,15 +95,18 @@ shaka.ui.LanguageUtils = class {
72
95
  /** @type {!Set.<string>} */
73
96
  const combinationsMade = new Set();
74
97
  const selectedCombination = selectedTrack ? getCombination(
75
- selectedTrack.language, getRolesString(selectedTrack)) : '';
98
+ selectedTrack.language, getRolesString(selectedTrack),
99
+ selectedTrack.label, selectedTrack.channelsCount) : '';
76
100
 
77
101
  for (const track of tracks) {
78
102
  const language = track.language;
79
103
  const forced = track.forced;
80
- const LocIds = shaka.ui.Locales.Ids;
81
104
  const forcedString = localization.resolve(LocIds.SUBTITLE_FORCED);
82
105
  const rolesString = getRolesString(track);
83
- const combinationName = getCombination(language, rolesString);
106
+ const label = track.label;
107
+ const channelsCount = track.channelsCount;
108
+ const combinationName =
109
+ getCombination(language, rolesString, label, channelsCount);
84
110
  if (combinationsMade.has(combinationName)) {
85
111
  continue;
86
112
  }
@@ -98,11 +124,17 @@ shaka.ui.LanguageUtils = class {
98
124
  shaka.ui.LanguageUtils.getLanguageName(language, localization);
99
125
  switch (trackLabelFormat) {
100
126
  case shaka.ui.Overlay.TrackLabelFormat.LANGUAGE:
127
+ if (showAudioChannelCountVariants) {
128
+ span.textContent += getChannelsCountName(channelsCount);
129
+ }
101
130
  if (forced) {
102
131
  span.textContent += ' (' + forcedString + ')';
103
132
  }
104
133
  break;
105
134
  case shaka.ui.Overlay.TrackLabelFormat.ROLE:
135
+ if (showAudioChannelCountVariants) {
136
+ span.textContent += getChannelsCountName(channelsCount);
137
+ }
106
138
  if (!rolesString) {
107
139
  // Fallback behavior. This probably shouldn't happen.
108
140
  shaka.log.alwaysWarn('Track #' + track.id + ' does not have a ' +
@@ -116,6 +148,9 @@ shaka.ui.LanguageUtils = class {
116
148
  }
117
149
  break;
118
150
  case shaka.ui.Overlay.TrackLabelFormat.LANGUAGE_ROLE:
151
+ if (showAudioChannelCountVariants) {
152
+ span.textContent += getChannelsCountName(channelsCount);
153
+ }
119
154
  if (rolesString) {
120
155
  span.textContent += ': ' + rolesString;
121
156
  }
@@ -124,8 +159,8 @@ shaka.ui.LanguageUtils = class {
124
159
  }
125
160
  break;
126
161
  case shaka.ui.Overlay.TrackLabelFormat.LABEL:
127
- if (track.label) {
128
- span.textContent = track.label;
162
+ if (label) {
163
+ span.textContent = label;
129
164
  } else {
130
165
  // Fallback behavior. This probably shouldn't happen.
131
166
  shaka.log.alwaysWarn('Track #' + track.id + ' does not have a ' +
@@ -154,3 +154,36 @@
154
154
  .shaka-ad-markers {
155
155
  .overlay-child();
156
156
  }
157
+
158
+ #shaka-player-ui-chapters-container {
159
+ width: 100%;
160
+ height: 100%;
161
+ display: flex;
162
+ flex-direction: row;
163
+ z-index: 0;
164
+ }
165
+
166
+ .shaka-chapter {
167
+ height: 100%;
168
+ position: relative;
169
+ }
170
+
171
+ .shaka-chapter-label {
172
+ position: relative;
173
+ top: -2.5em;
174
+ width: 100%;
175
+ text-align: center;
176
+ text-shadow: 1px 1px gray;
177
+ line-height: normal;
178
+ }
179
+
180
+ .shaka-chapter > div:first-child {
181
+ width: 100%;
182
+ height: 100%;
183
+ border-right: 1px solid;
184
+ position: absolute;
185
+ }
186
+
187
+ .shaka-chapter:last-child > div:first-child {
188
+ border-right: none;
189
+ }
@@ -36,6 +36,7 @@
36
36
  "SKIP_TO_LIVE": "Skip ahead to live",
37
37
  "STATISTICS": "Statistics",
38
38
  "SUBTITLE_FORCED": "Forced",
39
+ "SURROUND": "Surround",
39
40
  "UNDETERMINED_LANGUAGE": "Undetermined",
40
41
  "UNMUTE": "Unmute",
41
42
  "UNRECOGNIZED_LANGUAGE": "Unrecognized",
@@ -32,6 +32,7 @@
32
32
  "SKIP_AD": "Omitir anuncio",
33
33
  "SKIP_TO_LIVE": "Adelantar hasta la transmisión en vivo",
34
34
  "SUBTITLE_FORCED": "Forzado",
35
+ "SURROUND": "Envolvente",
35
36
  "UNDETERMINED_LANGUAGE": "Sin especificar",
36
37
  "UNMUTE": "Dejar de silenciar",
37
38
  "UNRECOGNIZED_LANGUAGE": "No reconocida",
@@ -32,6 +32,7 @@
32
32
  "SKIP_AD": "Saltar anuncio",
33
33
  "SKIP_TO_LIVE": "Ir al vídeo en directo",
34
34
  "SUBTITLE_FORCED": "Forzado",
35
+ "SURROUND": "Envolvente",
35
36
  "UNDETERMINED_LANGUAGE": "Sin especificar",
36
37
  "UNMUTE": "Activar sonido",
37
38
  "UNRECOGNIZED_LANGUAGE": "No reconocida",
@@ -151,6 +151,10 @@
151
151
  "description": "Label used to identify a subtitle track that is forced to be shown.",
152
152
  "message": "Forced"
153
153
  },
154
+ "SURROUND": {
155
+ "description": "Label used to identify a audio track that has surround audio.",
156
+ "message": "Surround"
157
+ },
154
158
  "UNDETERMINED_LANGUAGE": {
155
159
  "description": "Label for a button used to select an audio track whose language is undetermined or unknown.",
156
160
  "message": "Undetermined"
@@ -230,6 +230,16 @@ shaka.ui.Localization = class extends shaka.util.FakeEventTarget {
230
230
  return '';
231
231
  }
232
232
 
233
+ /**
234
+ * The locales currently used. An empty set means "no preference".
235
+ *
236
+ * @return {!Set.<string>}
237
+ * @export
238
+ */
239
+ getCurrentLocales() {
240
+ return this.currentLocales_;
241
+ }
242
+
233
243
  /**
234
244
  * @private
235
245
  */
@@ -116,7 +116,9 @@ shaka.ui.RemoteButton = class extends shaka.ui.Element {
116
116
  /** @override */
117
117
  release() {
118
118
  if (this.video.remote && this.callbackId_ != -1) {
119
- this.video.remote.cancelWatchAvailability(this.callbackId_);
119
+ this.video.remote.cancelWatchAvailability(this.callbackId_).catch(() => {
120
+ // Ignore this error.
121
+ });
120
122
  }
121
123
 
122
124
  super.release();
@@ -89,10 +89,18 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
89
89
  // Remove duplicate entries with the same resolution or quality depending
90
90
  // on content type. Pick an arbitrary one.
91
91
  tracks = tracks.filter((track, idx) => {
92
- // Keep the first one with the same height or bandwidth.
93
- const otherIdx = this.player.isAudioOnly() ?
94
- tracks.findIndex((t) => t.bandwidth == track.bandwidth) :
95
- tracks.findIndex((t) => t.height == track.height);
92
+ // Keep the first one with the same height and framrate or bandwidth.
93
+ let otherIdx = -1;
94
+ if (this.player.isAudioOnly()) {
95
+ otherIdx = tracks.findIndex((t) => t.bandwidth == track.bandwidth);
96
+ } else {
97
+ otherIdx = tracks.findIndex((t) => {
98
+ return t.height == track.height &&
99
+ t.frameRate == track.frameRate &&
100
+ t.hdr == track.hdr &&
101
+ t.videoLayout == track.videoLayout;
102
+ });
103
+ }
96
104
  return otherIdx == idx;
97
105
  });
98
106
 
@@ -132,14 +140,12 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
132
140
  () => this.onTrackSelected_(track));
133
141
 
134
142
  const span = shaka.util.Dom.createHTMLElement('span');
135
- if (this.player.isAudioOnly()) {
143
+ if (this.player.isAudioOnly() && track.bandwidth) {
136
144
  span.textContent = Math.round(track.bandwidth / 1000) + ' kbits/s';
145
+ } else if (track.height && track.width) {
146
+ span.textContent = this.getResolutionLabel_(track);
137
147
  } else {
138
- if (track.hdr == 'PQ' || track.hdr == 'HLG') {
139
- span.textContent = track.height + 'p (HDR)';
140
- } else {
141
- span.textContent = track.height + 'p';
142
- }
148
+ span.textContent = 'Unknown';
143
149
  }
144
150
  button.appendChild(span);
145
151
 
@@ -191,6 +197,37 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
191
197
  }
192
198
 
193
199
 
200
+ /**
201
+ * @param {!shaka.extern.Track} track
202
+ * @return {string}
203
+ * @private
204
+ */
205
+ getResolutionLabel_(track) {
206
+ const trackHeight = track.height || 0;
207
+ const trackWidth = track.width || 0;
208
+ let height = trackHeight;
209
+ const aspectRatio = trackWidth / trackHeight;
210
+ if (aspectRatio > (16 / 9)) {
211
+ height = Math.round(trackWidth * 9 / 16);
212
+ }
213
+ let text = height + 'p';
214
+ if (height == 2160) {
215
+ text = '4K';
216
+ }
217
+ const frameRate = track.frameRate;
218
+ if (frameRate && (frameRate >= 50 || frameRate <= 20)) {
219
+ text += Math.round(track.frameRate);
220
+ }
221
+ if (track.hdr == 'PQ' || track.hdr == 'HLG') {
222
+ text += ' (HDR)';
223
+ }
224
+ if (track.videoLayout == 'CH-STEREO') {
225
+ text += ' (3D)';
226
+ }
227
+ return text;
228
+ }
229
+
230
+
194
231
  /**
195
232
  * @param {!shaka.extern.Track} track
196
233
  * @private