shaka-player 3.1.0 → 3.1.4

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 (181) hide show
  1. package/AUTHORS +8 -2
  2. package/CHANGELOG.md +209 -0
  3. package/CONTRIBUTORS +10 -3
  4. package/README.md +3 -0
  5. package/build/apps.py +2 -2
  6. package/build/check.py +2 -2
  7. package/build/conformance.textproto +0 -9
  8. package/build/generateExterns.js +1 -1
  9. package/build/misspellings.txt +1 -0
  10. package/build/shakaBuildHelpers.py +6 -5
  11. package/build/subprocessWindowsPatch.py +103 -0
  12. package/demo/common/asset.js +11 -0
  13. package/demo/common/assets.js +1 -1
  14. package/demo/common/message_ids.js +5 -0
  15. package/demo/config.js +2 -1
  16. package/demo/custom.js +84 -3
  17. package/demo/demo.less +13 -1
  18. package/demo/demo_utils.js +1 -0
  19. package/demo/input.js +31 -12
  20. package/demo/input_container.js +24 -1
  21. package/demo/locales/en.json +6 -1
  22. package/demo/locales/source.json +20 -0
  23. package/demo/main.js +5 -1
  24. package/dist/controls.css +2 -2
  25. package/dist/controls.css.map +1 -1
  26. package/dist/demo.compiled.debug.js +99 -91
  27. package/dist/demo.compiled.debug.map +3 -3
  28. package/dist/demo.compiled.js +99 -91
  29. package/dist/demo.compiled.map +3 -3
  30. package/dist/demo.css +1 -1
  31. package/dist/demo.css.map +1 -1
  32. package/dist/deps.js +9 -9
  33. package/dist/locales.js +36 -34
  34. package/dist/receiver.compiled.debug.js +74 -70
  35. package/dist/receiver.compiled.debug.map +3 -3
  36. package/dist/receiver.compiled.js +74 -70
  37. package/dist/receiver.compiled.map +3 -3
  38. package/dist/shaka-player.compiled.d.ts +165 -9
  39. package/dist/shaka-player.compiled.debug.d.ts +165 -9
  40. package/dist/shaka-player.compiled.debug.externs.js +179 -11
  41. package/dist/shaka-player.compiled.debug.js +367 -339
  42. package/dist/shaka-player.compiled.debug.map +4 -4
  43. package/dist/shaka-player.compiled.externs.js +179 -11
  44. package/dist/shaka-player.compiled.js +818 -807
  45. package/dist/shaka-player.compiled.map +4 -4
  46. package/dist/shaka-player.ui.d.ts +165 -9
  47. package/dist/shaka-player.ui.debug.d.ts +165 -9
  48. package/dist/shaka-player.ui.debug.externs.js +181 -13
  49. package/dist/shaka-player.ui.debug.js +459 -429
  50. package/dist/shaka-player.ui.debug.map +4 -4
  51. package/dist/shaka-player.ui.externs.js +181 -13
  52. package/dist/shaka-player.ui.js +973 -961
  53. package/dist/shaka-player.ui.map +4 -4
  54. package/docs/tutorials/fairplay.md +1 -1
  55. package/docs/tutorials/faq.md +25 -0
  56. package/docs/tutorials/index.json +2 -2
  57. package/docs/tutorials/upgrade-v3.md +38 -0
  58. package/docs/tutorials/upgrade.md +3 -3
  59. package/externs/ima.js +4 -0
  60. package/externs/media_capabilities.js +16 -0
  61. package/externs/shaka/ads.js +2 -0
  62. package/externs/shaka/net.js +2 -2
  63. package/externs/shaka/player.js +6 -0
  64. package/karma.conf.js +1 -0
  65. package/lib/abr/simple_abr_manager.js +1 -1
  66. package/lib/ads/ad_manager.js +29 -11
  67. package/lib/ads/client_side_ad_manager.js +76 -86
  68. package/lib/ads/server_side_ad_manager.js +26 -11
  69. package/lib/cast/cast_proxy.js +3 -2
  70. package/lib/cast/cast_receiver.js +2 -1
  71. package/lib/cast/cast_sender.js +2 -2
  72. package/lib/cea/sei_processor.js +24 -17
  73. package/lib/dash/dash_parser.js +107 -4
  74. package/lib/dash/segment_list.js +1 -1
  75. package/lib/dash/segment_template.js +45 -15
  76. package/lib/dependencies/all.js +15 -15
  77. package/lib/hls/hls_parser.js +46 -18
  78. package/lib/media/closed_caption_parser.js +3 -1
  79. package/lib/media/content_workarounds.js +18 -0
  80. package/lib/media/drm_engine.js +20 -1
  81. package/lib/media/gap_jumping_controller.js +8 -4
  82. package/lib/media/media_source_engine.js +1 -2
  83. package/lib/media/playhead.js +1 -1
  84. package/lib/media/presentation_timeline.js +3 -1
  85. package/lib/media/segment_index.js +38 -12
  86. package/lib/media/segment_reference.js +8 -0
  87. package/lib/media/streaming_engine.js +30 -21
  88. package/lib/net/http_fetch_plugin.js +2 -2
  89. package/lib/net/networking_engine.js +3 -2
  90. package/lib/offline/storage.js +4 -0
  91. package/lib/player.js +113 -56
  92. package/lib/polyfill/encryption_scheme.js +2 -0
  93. package/lib/polyfill/fullscreen.js +2 -0
  94. package/lib/polyfill/mathround.js +2 -0
  95. package/lib/polyfill/media_capabilities.js +62 -23
  96. package/lib/polyfill/mediasource.js +8 -0
  97. package/lib/polyfill/orientation.js +3 -1
  98. package/lib/polyfill/patchedmediakeys_apple.js +6 -4
  99. package/lib/polyfill/patchedmediakeys_ms.js +6 -4
  100. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  101. package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
  102. package/lib/polyfill/pip_webkit.js +2 -0
  103. package/lib/polyfill/storage_estimate.js +2 -0
  104. package/lib/polyfill/video_play_promise.js +2 -0
  105. package/lib/polyfill/videoplaybackquality.js +2 -0
  106. package/lib/polyfill/vttcue.js +2 -0
  107. package/lib/text/cue.js +11 -0
  108. package/lib/text/mp4_ttml_parser.js +3 -0
  109. package/lib/text/text_engine.js +14 -2
  110. package/lib/text/ttml_text_parser.js +52 -30
  111. package/lib/text/ui_text_displayer.js +255 -82
  112. package/lib/text/vtt_text_parser.js +4 -2
  113. package/lib/util/error.js +1 -4
  114. package/lib/util/fake_event.js +53 -7
  115. package/lib/util/mime_utils.js +33 -0
  116. package/lib/util/mp4_box_parsers.js +1 -1
  117. package/lib/util/mp4_parser.js +14 -0
  118. package/lib/util/networking.js +1 -1
  119. package/lib/util/periods.js +132 -37
  120. package/lib/util/platform.js +13 -1
  121. package/lib/util/stream_utils.js +46 -7
  122. package/package.json +3 -3
  123. package/test/cast/cast_proxy_unit.js +12 -6
  124. package/test/cast/cast_receiver_integration.js +1 -1
  125. package/test/cast/cast_utils_unit.js +1 -1
  126. package/test/cea/mp4_cea_parser_unit.js +26 -0
  127. package/test/dash/dash_parser_live_unit.js +1 -0
  128. package/test/dash/dash_parser_manifest_unit.js +187 -1
  129. package/test/dash/dash_parser_segment_template_unit.js +3 -3
  130. package/test/demo/demo_unit.js +0 -1
  131. package/test/hls/hls_parser_unit.js +45 -1
  132. package/test/media/closed_caption_parser_unit.js +20 -0
  133. package/test/media/drm_engine_unit.js +59 -3
  134. package/test/media/playhead_unit.js +27 -1
  135. package/test/media/presentation_timeline_unit.js +19 -0
  136. package/test/media/segment_index_unit.js +76 -3
  137. package/test/media/streaming_engine_unit.js +3 -3
  138. package/test/player_integration.js +38 -0
  139. package/test/player_unit.js +125 -6
  140. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  141. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  142. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  143. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  144. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  145. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  146. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  147. package/test/test/assets/3675/dash_0.mpd +30 -0
  148. package/test/test/assets/3675/dash_1.mpd +30 -0
  149. package/test/test/assets/3675/dash_2.mpd +30 -0
  150. package/test/test/assets/3675/dash_3.mpd +30 -0
  151. package/test/test/assets/3675/dash_4.mpd +30 -0
  152. package/test/test/assets/3675/dash_5.mpd +26 -0
  153. package/test/test/assets/3675/init-stream0.m4s +0 -0
  154. package/test/test/assets/empty_caption_video_init.mp4 +0 -0
  155. package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
  156. package/test/test/util/fake_ad_manager.js +4 -1
  157. package/test/test/util/simple_fakes.js +5 -3
  158. package/test/test/util/waiter.js +36 -2
  159. package/test/text/ttml_text_parser_unit.js +55 -0
  160. package/test/text/ui_text_displayer_unit.js +95 -18
  161. package/test/text/vtt_text_parser_unit.js +17 -1
  162. package/test/ui/localization_unit.js +15 -14
  163. package/test/ui/text_displayer_layout_unit.js +52 -1
  164. package/test/ui/ui_unit.js +4 -0
  165. package/test/util/mp4_box_parsers_unit.js +28 -0
  166. package/test/util/periods_unit.js +190 -2
  167. package/test/util/stream_utils_unit.js +56 -5
  168. package/ui/airplay_button.js +5 -0
  169. package/ui/big_play_button.js +10 -0
  170. package/ui/cast_button.js +2 -3
  171. package/ui/controls.js +9 -7
  172. package/ui/less/containers.less +10 -5
  173. package/ui/locales/en.json +1 -0
  174. package/ui/locales/ru.json +6 -6
  175. package/ui/locales/source.json +4 -0
  176. package/ui/localization.js +28 -51
  177. package/ui/pip_button.js +2 -3
  178. package/ui/play_button.js +10 -10
  179. package/ui/small_play_button.js +12 -0
  180. package/ui/ui.js +1 -1
  181. package/docs/tutorials/upgrade-v2-4.md +0 -481
@@ -39,6 +39,7 @@ describe('PeriodCombiner', () => {
39
39
  makeAudioStream('en', /* channels= */ 2),
40
40
  ],
41
41
  textStreams: [],
42
+ imageStreams: [],
42
43
  },
43
44
  {
44
45
  id: 'ad',
@@ -49,6 +50,7 @@ describe('PeriodCombiner', () => {
49
50
  makeAudioStream('en', /* channels= */ 2),
50
51
  ],
51
52
  textStreams: [],
53
+ imageStreams: [],
52
54
  },
53
55
  ];
54
56
 
@@ -117,6 +119,7 @@ describe('PeriodCombiner', () => {
117
119
  makeAudioStream('en', /* channels= */ 2),
118
120
  ],
119
121
  textStreams: [],
122
+ imageStreams: [],
120
123
  },
121
124
  {
122
125
  id: 'main',
@@ -130,6 +133,7 @@ describe('PeriodCombiner', () => {
130
133
  makeAudioStream('en', /* channels= */ 2),
131
134
  ],
132
135
  textStreams: [],
136
+ imageStreams: [],
133
137
  },
134
138
  ];
135
139
 
@@ -194,6 +198,7 @@ describe('PeriodCombiner', () => {
194
198
  makeAudioStream('en'),
195
199
  ],
196
200
  textStreams: [],
201
+ imageStreams: [],
197
202
  },
198
203
  {
199
204
  id: '2',
@@ -204,6 +209,7 @@ describe('PeriodCombiner', () => {
204
209
  makeAudioStream('en'),
205
210
  ],
206
211
  textStreams: [],
212
+ imageStreams: [],
207
213
  },
208
214
  ];
209
215
 
@@ -242,6 +248,7 @@ describe('PeriodCombiner', () => {
242
248
  makeAudioStream('en'),
243
249
  ],
244
250
  textStreams: [],
251
+ imageStreams: [],
245
252
  },
246
253
  {
247
254
  id: '2',
@@ -252,6 +259,7 @@ describe('PeriodCombiner', () => {
252
259
  makeAudioStream('en'),
253
260
  ],
254
261
  textStreams: [],
262
+ imageStreams: [],
255
263
  },
256
264
  ];
257
265
 
@@ -289,6 +297,7 @@ describe('PeriodCombiner', () => {
289
297
  makeAudioStream('fr', /* channels= */ 2, /* primary= */ false),
290
298
  ],
291
299
  textStreams: [],
300
+ imageStreams: [],
292
301
  },
293
302
  {
294
303
  id: 'ad',
@@ -299,6 +308,7 @@ describe('PeriodCombiner', () => {
299
308
  makeAudioStream('en'),
300
309
  ],
301
310
  textStreams: [],
311
+ imageStreams: [],
302
312
  },
303
313
  {
304
314
  id: 'show2',
@@ -311,6 +321,7 @@ describe('PeriodCombiner', () => {
311
321
  makeAudioStream('fr', /* channels= */ 2, /* primary= */ true),
312
322
  ],
313
323
  textStreams: [],
324
+ imageStreams: [],
314
325
  },
315
326
  ];
316
327
 
@@ -361,6 +372,7 @@ describe('PeriodCombiner', () => {
361
372
  makeAudioStream('es'),
362
373
  ],
363
374
  textStreams: [],
375
+ imageStreams: [],
364
376
  },
365
377
  {
366
378
  id: 'show2',
@@ -371,6 +383,7 @@ describe('PeriodCombiner', () => {
371
383
  makeAudioStream('en'),
372
384
  ],
373
385
  textStreams: [],
386
+ imageStreams: [],
374
387
  },
375
388
  ];
376
389
 
@@ -411,6 +424,7 @@ describe('PeriodCombiner', () => {
411
424
  makeAudioStream('en'),
412
425
  ],
413
426
  textStreams: [],
427
+ imageStreams: [],
414
428
  },
415
429
  ];
416
430
 
@@ -429,7 +443,6 @@ describe('PeriodCombiner', () => {
429
443
  expect(highBandwidth.video.originalId).toBe('480');
430
444
  });
431
445
 
432
-
433
446
  it('Filters out duplicate streams', async () => {
434
447
  // v1 and v3 are duplicates
435
448
  const v1 = makeVideoStream(1280);
@@ -496,6 +509,16 @@ describe('PeriodCombiner', () => {
496
509
  t3.originalId = 't3';
497
510
  t3.roles = ['role1'];
498
511
 
512
+ // i1 and i3 are duplicates.
513
+ const i1 = makeImageStream(240);
514
+ i1.originalId = 'i1';
515
+
516
+ const i2 = makeImageStream(480);
517
+ i2.originalId = 'i2';
518
+
519
+ const i3 = makeImageStream(240);
520
+ i3.originalId = 'i3';
521
+
499
522
  /** @type {!Array.<shaka.util.PeriodCombiner.Period>} */
500
523
  const periods = [
501
524
  {
@@ -517,6 +540,11 @@ describe('PeriodCombiner', () => {
517
540
  t2,
518
541
  t3,
519
542
  ],
543
+ imageStreams: [
544
+ i1,
545
+ i2,
546
+ i3,
547
+ ],
520
548
  },
521
549
  ];
522
550
 
@@ -544,9 +572,61 @@ describe('PeriodCombiner', () => {
544
572
  for (const id of textIds) {
545
573
  expect(id).not.toBe('t3');
546
574
  }
575
+
576
+ const imageStreams = combiner.getImageStreams();
577
+ expect(imageStreams.length).toBe(2);
578
+
579
+ // i3 should've been filtered out
580
+ const imageIds = imageStreams.map((i) => i.originalId);
581
+ for (const id of imageIds) {
582
+ expect(id).not.toBe('i3');
583
+ }
547
584
  });
548
585
 
549
- it('Text track gaps', async () => {
586
+ // Regression test for #3383, where we failed on multi-period content with
587
+ // multiple image streams per period.
588
+ it('Can handle multiple image streams', async () => {
589
+ /** @type {!Array.<shaka.util.PeriodCombiner.Period>} */
590
+ const periods = [
591
+ {
592
+ id: '1',
593
+ videoStreams: [
594
+ makeVideoStream(1280),
595
+ ],
596
+ audioStreams: [],
597
+ textStreams: [],
598
+ imageStreams: [
599
+ makeImageStream(240),
600
+ makeImageStream(480),
601
+ ],
602
+ },
603
+ {
604
+ id: '2',
605
+ videoStreams: [
606
+ makeVideoStream(1280),
607
+ ],
608
+ audioStreams: [],
609
+ textStreams: [],
610
+ imageStreams: [
611
+ makeImageStream(240),
612
+ makeImageStream(480),
613
+ ],
614
+ },
615
+ ];
616
+
617
+ await combiner.combinePeriods(periods, /* isDynamic= */ true);
618
+
619
+ const imageStreams = combiner.getImageStreams();
620
+ expect(imageStreams.length).toBe(2);
621
+
622
+ const imageIds = imageStreams.map((i) => i.originalId);
623
+ expect(imageIds).toEqual([
624
+ '240,240',
625
+ '480,480',
626
+ ]);
627
+ });
628
+
629
+ it('handles text track gaps', async () => {
550
630
  /** @type {!Array.<shaka.util.PeriodCombiner.Period>} */
551
631
  const periods = [
552
632
  {
@@ -560,6 +640,7 @@ describe('PeriodCombiner', () => {
560
640
  textStreams: [
561
641
  makeTextStream('en'),
562
642
  ],
643
+ imageStreams: [],
563
644
  },
564
645
  {
565
646
  id: '2',
@@ -572,6 +653,7 @@ describe('PeriodCombiner', () => {
572
653
  textStreams: [
573
654
  /* No text streams */
574
655
  ],
656
+ imageStreams: [],
575
657
  },
576
658
  {
577
659
  id: '3',
@@ -585,6 +667,7 @@ describe('PeriodCombiner', () => {
585
667
  makeTextStream('en'),
586
668
  makeTextStream('es'),
587
669
  ],
670
+ imageStreams: [],
588
671
  },
589
672
  ];
590
673
 
@@ -608,6 +691,70 @@ describe('PeriodCombiner', () => {
608
691
  expect(english.originalId).toBe('en,,en');
609
692
  });
610
693
 
694
+ it('handles image track gaps', async () => {
695
+ /** @type {!Array.<shaka.util.PeriodCombiner.Period>} */
696
+ const periods = [
697
+ {
698
+ id: '1',
699
+ videoStreams: [
700
+ makeVideoStream(1080),
701
+ ],
702
+ audioStreams: [
703
+ makeAudioStream('en'),
704
+ ],
705
+ textStreams: [],
706
+ imageStreams: [
707
+ makeImageStream(240),
708
+ ],
709
+ },
710
+ {
711
+ id: '2',
712
+ videoStreams: [
713
+ makeVideoStream(1080),
714
+ ],
715
+ audioStreams: [
716
+ makeAudioStream('en'),
717
+ ],
718
+ textStreams: [],
719
+ imageStreams: [
720
+ /* No image streams in this period */
721
+ ],
722
+ },
723
+ {
724
+ id: '3',
725
+ videoStreams: [
726
+ makeVideoStream(1080),
727
+ ],
728
+ audioStreams: [
729
+ makeAudioStream('en'),
730
+ ],
731
+ textStreams: [],
732
+ imageStreams: [
733
+ makeImageStream(240),
734
+ makeImageStream(480),
735
+ ],
736
+ },
737
+ ];
738
+
739
+ await combiner.combinePeriods(periods, /* isDynamic= */ false);
740
+ const imageStreams = combiner.getImageStreams();
741
+ expect(imageStreams).toEqual(jasmine.arrayWithExactContents([
742
+ jasmine.objectContaining({
743
+ height: 240,
744
+ }),
745
+ jasmine.objectContaining({
746
+ height: 480,
747
+ }),
748
+ ]));
749
+
750
+ // We can use the originalId field to see what each track is composed of.
751
+
752
+ const i240 = imageStreams.find((s) => s.height == 240);
753
+ const i480 = imageStreams.find((s) => s.height == 480);
754
+ expect(i240.originalId).toBe('240,,240');
755
+ expect(i480.originalId).toBe('240,,480');
756
+ });
757
+
611
758
  it('Disjoint audio channels', async () => {
612
759
  /** @type {!Array.<shaka.util.PeriodCombiner.Period>} */
613
760
  const periods = [
@@ -620,6 +767,7 @@ describe('PeriodCombiner', () => {
620
767
  makeAudioStream('en', /* channels= */ 6),
621
768
  ],
622
769
  textStreams: [],
770
+ imageStreams: [],
623
771
  },
624
772
  {
625
773
  id: '2',
@@ -630,6 +778,7 @@ describe('PeriodCombiner', () => {
630
778
  makeAudioStream('en', /* channels= */ 2),
631
779
  ],
632
780
  textStreams: [],
781
+ imageStreams: [],
633
782
  },
634
783
  ];
635
784
 
@@ -666,6 +815,7 @@ describe('PeriodCombiner', () => {
666
815
  makeAudioStreamWithSampleRate(48000),
667
816
  ],
668
817
  textStreams: [],
818
+ imageStreams: [],
669
819
  },
670
820
  {
671
821
  id: '2',
@@ -676,6 +826,7 @@ describe('PeriodCombiner', () => {
676
826
  makeAudioStreamWithSampleRate(44100),
677
827
  ],
678
828
  textStreams: [],
829
+ imageStreams: [],
679
830
  },
680
831
  ];
681
832
 
@@ -712,6 +863,7 @@ describe('PeriodCombiner', () => {
712
863
  makeAudioStreamWithSampleRate(44100),
713
864
  ],
714
865
  textStreams: [],
866
+ imageStreams: [],
715
867
  },
716
868
  {
717
869
  id: '2',
@@ -722,6 +874,7 @@ describe('PeriodCombiner', () => {
722
874
  makeAudioStreamWithSampleRate(48000),
723
875
  ],
724
876
  textStreams: [],
877
+ imageStreams: [],
725
878
  },
726
879
  ];
727
880
 
@@ -752,6 +905,7 @@ describe('PeriodCombiner', () => {
752
905
  ],
753
906
  audioStreams: [],
754
907
  textStreams: [],
908
+ imageStreams: [],
755
909
  },
756
910
  {
757
911
  id: '2',
@@ -761,6 +915,7 @@ describe('PeriodCombiner', () => {
761
915
  ],
762
916
  audioStreams: [],
763
917
  textStreams: [],
918
+ imageStreams: [],
764
919
  },
765
920
  ];
766
921
 
@@ -805,6 +960,7 @@ describe('PeriodCombiner', () => {
805
960
  stream2,
806
961
  ],
807
962
  textStreams: [],
963
+ imageStreams: [],
808
964
  },
809
965
  {
810
966
  id: '1',
@@ -816,6 +972,7 @@ describe('PeriodCombiner', () => {
816
972
  stream4,
817
973
  ],
818
974
  textStreams: [],
975
+ imageStreams: [],
819
976
  },
820
977
  ];
821
978
 
@@ -864,6 +1021,7 @@ describe('PeriodCombiner', () => {
864
1021
  stream2,
865
1022
  ],
866
1023
  textStreams: [],
1024
+ imageStreams: [],
867
1025
  },
868
1026
  {
869
1027
  id: '2',
@@ -875,6 +1033,7 @@ describe('PeriodCombiner', () => {
875
1033
  stream4,
876
1034
  ],
877
1035
  textStreams: [],
1036
+ imageStreams: [],
878
1037
  },
879
1038
  ];
880
1039
 
@@ -999,24 +1158,28 @@ describe('PeriodCombiner', () => {
999
1158
  videoStreams: [v1, v2, v3, v4, v5],
1000
1159
  audioStreams: [],
1001
1160
  textStreams: [],
1161
+ imageStreams: [],
1002
1162
  },
1003
1163
  {
1004
1164
  id: '2',
1005
1165
  videoStreams: [v6, v7, v8, v9, v10],
1006
1166
  audioStreams: [],
1007
1167
  textStreams: [],
1168
+ imageStreams: [],
1008
1169
  },
1009
1170
  {
1010
1171
  id: '3',
1011
1172
  videoStreams: [v11, v12, v13, v14, v15],
1012
1173
  audioStreams: [],
1013
1174
  textStreams: [],
1175
+ imageStreams: [],
1014
1176
  },
1015
1177
  {
1016
1178
  id: '4',
1017
1179
  videoStreams: [v16, v17, v18, v19, v20],
1018
1180
  audioStreams: [],
1019
1181
  textStreams: [],
1182
+ imageStreams: [],
1020
1183
  },
1021
1184
  {
1022
1185
  id: '5',
@@ -1024,6 +1187,7 @@ describe('PeriodCombiner', () => {
1024
1187
  videoStreams: [v1, v2, v3, v4, v5],
1025
1188
  audioStreams: [],
1026
1189
  textStreams: [],
1190
+ imageStreams: [],
1027
1191
  },
1028
1192
  {
1029
1193
  id: '6',
@@ -1031,6 +1195,7 @@ describe('PeriodCombiner', () => {
1031
1195
  videoStreams: [v6, v7, v8, v9, v10],
1032
1196
  audioStreams: [],
1033
1197
  textStreams: [],
1198
+ imageStreams: [],
1034
1199
  },
1035
1200
  {
1036
1201
  id: '7',
@@ -1038,6 +1203,7 @@ describe('PeriodCombiner', () => {
1038
1203
  videoStreams: [v11, v12, v13, v14, v15],
1039
1204
  audioStreams: [],
1040
1205
  textStreams: [],
1206
+ imageStreams: [],
1041
1207
  },
1042
1208
  {
1043
1209
  id: '8',
@@ -1045,6 +1211,7 @@ describe('PeriodCombiner', () => {
1045
1211
  videoStreams: [v16, v17, v18, v19, v20],
1046
1212
  audioStreams: [],
1047
1213
  textStreams: [],
1214
+ imageStreams: [],
1048
1215
  },
1049
1216
  // Adding the 1st period again since it was the one that used to
1050
1217
  // cause trouble when repeated.
@@ -1054,6 +1221,7 @@ describe('PeriodCombiner', () => {
1054
1221
  videoStreams: [v1, v2, v3, v4, v5],
1055
1222
  audioStreams: [],
1056
1223
  textStreams: [],
1224
+ imageStreams: [],
1057
1225
  },
1058
1226
  ];
1059
1227
 
@@ -1181,6 +1349,26 @@ describe('PeriodCombiner', () => {
1181
1349
  return streamGenerator.build_();
1182
1350
  }
1183
1351
 
1352
+ /**
1353
+ * @param {number} height
1354
+ * @return {shaka.extern.Stream}
1355
+ * @suppress {accessControls}
1356
+ */
1357
+ function makeImageStream(height) {
1358
+ const width = height * 4 / 3;
1359
+ const streamGenerator = new shaka.test.ManifestGenerator.Stream(
1360
+ /* manifest= */ null,
1361
+ /* isPartial= */ false,
1362
+ /* id= */ nextId++,
1363
+ /* type= */ shaka.util.ManifestParserUtils.ContentType.IMAGE,
1364
+ /* lang= */ 'und');
1365
+ streamGenerator.size(width, height);
1366
+ streamGenerator.originalId = height.toString();
1367
+ streamGenerator.mime('image/jpeg');
1368
+ streamGenerator.tilesLayout = '1x1';
1369
+ return streamGenerator.build_();
1370
+ }
1371
+
1184
1372
  /**
1185
1373
  * @param {number} height
1186
1374
  * @param {string} language
@@ -16,14 +16,14 @@ describe('StreamUtils', () => {
16
16
  /** @type {!jasmine.Spy} */
17
17
  let decodingInfoSpy;
18
18
 
19
- const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo;
19
+ const originalDecodingInfo = window.shakaMediaCapabilities.decodingInfo;
20
20
 
21
21
  beforeEach(() => {
22
22
  decodingInfoSpy = jasmine.createSpy('decodingInfo');
23
23
  });
24
24
 
25
25
  afterEach(() => {
26
- navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
26
+ window.shakaMediaCapabilities.decodingInfo = originalDecodingInfo;
27
27
  });
28
28
 
29
29
  describe('filterStreamsByLanguageAndRole', () => {
@@ -488,14 +488,30 @@ describe('StreamUtils', () => {
488
488
  });
489
489
 
490
490
  await StreamUtils.getDecodingInfosForVariants(manifest.variants,
491
- /* usePersistentLicenses= */false);
491
+ /* usePersistentLicenses= */false, /* srcEquals= */ false);
492
+ expect(manifest.variants.length).toBeTruthy();
493
+ expect(manifest.variants[0].decodingInfos.length).toBe(1);
494
+ expect(manifest.variants[0].decodingInfos[0].supported).toBeTruthy();
495
+ });
496
+
497
+ it('for srcEquals content', async () => {
498
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
499
+ manifest.addVariant(0, (variant) => {
500
+ variant.addVideo(1, (stream) => {
501
+ stream.mime('video/mp4', 'avc1.4d400d');
502
+ });
503
+ });
504
+ });
505
+
506
+ await StreamUtils.getDecodingInfosForVariants(manifest.variants,
507
+ /* usePersistentLicenses= */false, /* srcEquals= */ true);
492
508
  expect(manifest.variants.length).toBeTruthy();
493
509
  expect(manifest.variants[0].decodingInfos.length).toBe(1);
494
510
  expect(manifest.variants[0].decodingInfos[0].supported).toBeTruthy();
495
511
  });
496
512
 
497
513
  it('handles decodingInfo exception', async () => {
498
- navigator.mediaCapabilities.decodingInfo =
514
+ window.shakaMediaCapabilities.decodingInfo =
499
515
  shaka.test.Util.spyFunc(decodingInfoSpy);
500
516
  // If decodingInfo() fails, setDecodingInfo should finish without throwing
501
517
  // an exception, and the variant should have no decodingInfo result.
@@ -517,10 +533,45 @@ describe('StreamUtils', () => {
517
533
  });
518
534
 
519
535
  await StreamUtils.getDecodingInfosForVariants(manifest.variants,
520
- /* usePersistentLicenses= */false);
536
+ /* usePersistentLicenses= */false, /* srcEquals= */ false);
521
537
  expect(manifest.variants.length).toBe(1);
522
538
  expect(manifest.variants[0].decodingInfos.length).toBe(0);
523
539
  });
540
+
541
+ it('includes transferFunction in config when hdr', async () => {
542
+ window.shakaMediaCapabilities.decodingInfo =
543
+ shaka.test.Util.spyFunc(decodingInfoSpy);
544
+
545
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
546
+ manifest.addVariant(0, (variant) => {
547
+ variant.addVideo(0, (stream) => {
548
+ stream.mime('video/mp4', 'avc1.640028');
549
+ stream.hdr = 'SDR';
550
+ });
551
+ });
552
+ manifest.addVariant(1, (variant) => {
553
+ variant.addVideo(1, (stream) => {
554
+ stream.mime('video/mp4', 'hvc1.2.4.L150.90');
555
+ stream.hdr = 'PQ';
556
+ });
557
+ });
558
+ manifest.addVariant(2, (variant) => {
559
+ variant.addVideo(2, (stream) => {
560
+ stream.mime('video/mp4', 'hvc1.2.4.L153.B0');
561
+ stream.hdr = 'HLG';
562
+ });
563
+ });
564
+ });
565
+
566
+ await StreamUtils.getDecodingInfosForVariants(manifest.variants,
567
+ /* usePersistentLicenses= */ false, /* srcEquals= */ false);
568
+ expect(decodingInfoSpy.calls.argsFor(0)[0].video.transferFunction)
569
+ .toBe('srgb');
570
+ expect(decodingInfoSpy.calls.argsFor(1)[0].video.transferFunction)
571
+ .toBe('pq');
572
+ expect(decodingInfoSpy.calls.argsFor(2)[0].video.transferFunction)
573
+ .toBe('hlg');
574
+ });
524
575
  });
525
576
 
526
577
  describe('filterManifest', () => {
@@ -44,6 +44,11 @@ shaka.ui.AirPlayButton = class extends shaka.ui.Element {
44
44
  this.airplayIcon_.textContent = shaka.ui.Enums.MaterialDesignIcons.AIRPLAY;
45
45
  this.airplayButton_.appendChild(this.airplayIcon_);
46
46
 
47
+ // Don't show the button if AirPlay is not supported.
48
+ if (!window.WebKitPlaybackTargetAvailabilityEvent) {
49
+ this.airplayButton_.classList.add('shaka-hidden');
50
+ }
51
+
47
52
  const label = shaka.util.Dom.createHTMLElement('label');
48
53
  label.classList.add('shaka-overflow-button-label');
49
54
  label.classList.add('shaka-overflow-menu-only');
@@ -7,6 +7,7 @@
7
7
 
8
8
  goog.provide('shaka.ui.BigPlayButton');
9
9
 
10
+ goog.require('shaka.ui.Locales');
10
11
  goog.require('shaka.ui.PlayButton');
11
12
  goog.requireType('shaka.ui.Controls');
12
13
 
@@ -40,4 +41,13 @@ shaka.ui.BigPlayButton = class extends shaka.ui.PlayButton {
40
41
  this.button.setAttribute('icon', 'pause');
41
42
  }
42
43
  }
44
+
45
+
46
+ /** @override */
47
+ updateAriaLabel() {
48
+ const LocIds = shaka.ui.Locales.Ids;
49
+ const label = this.isPaused() ? LocIds.PLAY : LocIds.PAUSE;
50
+
51
+ this.button.ariaLabel = this.localization.resolve(label);
52
+ }
43
53
  };
package/ui/cast_button.js CHANGED
@@ -102,9 +102,8 @@ shaka.ui.CastButton = class extends shaka.ui.Element {
102
102
  } catch (error) {
103
103
  this.castButton_.disabled = false;
104
104
  if (error.code != shaka.util.Error.Code.CAST_CANCELED_BY_USER) {
105
- this.controls.dispatchEvent(new shaka.util.FakeEvent('error', {
106
- detail: error,
107
- }));
105
+ this.controls.dispatchEvent(new shaka.util.FakeEvent(
106
+ 'error', (new Map()).set('detail', error)));
108
107
  }
109
108
  }
110
109
  }
package/ui/controls.js CHANGED
@@ -198,6 +198,10 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
198
198
  * @export
199
199
  */
200
200
  async destroy() {
201
+ if (document.pictureInPictureElement == this.localVideo_) {
202
+ await document.exitPictureInPicture();
203
+ }
204
+
201
205
  if (this.eventManager_) {
202
206
  this.eventManager_.release();
203
207
  this.eventManager_ = null;
@@ -569,9 +573,8 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
569
573
  }
570
574
  }
571
575
  } catch (error) {
572
- this.dispatchEvent(new shaka.util.FakeEvent('error', {
573
- detail: error,
574
- }));
576
+ this.dispatchEvent(new shaka.util.FakeEvent(
577
+ 'error', (new Map()).set('detail', error)));
575
578
  }
576
579
  }
577
580
  }
@@ -687,7 +690,7 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
687
690
  });
688
691
 
689
692
  this.eventManager_.listen(this.controlsContainer_, 'dblclick', () => {
690
- if (this.config_.doubleClickForFullscreen) {
693
+ if (this.config_.doubleClickForFullscreen && document.fullscreenEnabled) {
691
694
  this.toggleFullScreen();
692
695
  }
693
696
  });
@@ -1158,9 +1161,8 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
1158
1161
  /** @private */
1159
1162
  onCastStatusChange_() {
1160
1163
  const isCasting = this.castProxy_.isCasting();
1161
- this.dispatchEvent(new shaka.util.FakeEvent('caststatuschanged', {
1162
- newStatus: isCasting,
1163
- }));
1164
+ this.dispatchEvent(new shaka.util.FakeEvent(
1165
+ 'caststatuschanged', (new Map()).set('newStatus', isCasting)));
1164
1166
 
1165
1167
  if (isCasting) {
1166
1168
  this.controlsContainer_.setAttribute('casting', 'true');
@@ -157,6 +157,9 @@
157
157
  /* 32px tall controls. */
158
158
  height: 32px;
159
159
 
160
+ /* Consistent alignment of buttons. */
161
+ line-height: 0.5;
162
+
160
163
  /* Consistent margins (external) and padding (internal) between controls. */
161
164
  .bottom-panels-elements-margin();
162
165
 
@@ -226,11 +229,13 @@
226
229
  transition-delay: 500ms;
227
230
 
228
231
  /* These are defaults which are overridden by JS or cue styles. */
229
- span {
230
- font-size: 20px;
231
- line-height: 1.4; // relative to font size.
232
- background-color: rgba(0, 0, 0, 0.8);
233
- color: rgb(255, 255, 255);
232
+ font-size: 20px;
233
+ line-height: 1.4; // relative to font size.
234
+ color: rgb(255, 255, 255);
235
+
236
+ span.shaka-text-wrapper {
237
+ display: inline;
238
+ background: none;
234
239
  }
235
240
  }
236
241