shaka-player 3.2.0 → 3.2.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 (162) hide show
  1. package/.github/workflows/build_and_test.yaml +73 -0
  2. package/.github/workflows/release.yaml +53 -0
  3. package/AUTHORS +3 -0
  4. package/CHANGELOG.md +182 -1
  5. package/CONTRIBUTORS +3 -0
  6. package/README.md +4 -4
  7. package/build/checkversion.py +1 -1
  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/build/test.py +6 -0
  13. package/demo/common/assets.js +1 -1
  14. package/demo/index.html +1 -1
  15. package/dist/controls.css +2 -2
  16. package/dist/controls.css.map +1 -1
  17. package/dist/demo.compiled.debug.js +1 -1
  18. package/dist/demo.compiled.js +1 -1
  19. package/dist/deps.js +8 -8
  20. package/dist/locales.js +37 -37
  21. package/dist/receiver.compiled.debug.js +1 -1
  22. package/dist/receiver.compiled.js +1 -1
  23. package/dist/shaka-player.compiled.d.ts +195 -5
  24. package/dist/shaka-player.compiled.debug.d.ts +195 -5
  25. package/dist/shaka-player.compiled.debug.externs.js +226 -14
  26. package/dist/shaka-player.compiled.debug.js +424 -399
  27. package/dist/shaka-player.compiled.debug.map +4 -4
  28. package/dist/shaka-player.compiled.externs.js +226 -14
  29. package/dist/shaka-player.compiled.js +826 -812
  30. package/dist/shaka-player.compiled.map +4 -4
  31. package/dist/shaka-player.ui.d.ts +197 -16
  32. package/dist/shaka-player.ui.debug.d.ts +197 -16
  33. package/dist/shaka-player.ui.debug.externs.js +230 -27
  34. package/dist/shaka-player.ui.debug.js +466 -442
  35. package/dist/shaka-player.ui.debug.map +4 -4
  36. package/dist/shaka-player.ui.externs.js +230 -27
  37. package/dist/shaka-player.ui.js +946 -932
  38. package/dist/shaka-player.ui.map +4 -4
  39. package/docs/tutorials/fairplay.md +1 -1
  40. package/docs/tutorials/faq.md +14 -0
  41. package/docs/tutorials/ui.md +5 -5
  42. package/externs/ima.js +4 -0
  43. package/externs/shaka/ads.js +25 -0
  44. package/externs/shaka/player.js +8 -0
  45. package/externs/shaka/text.js +10 -0
  46. package/karma.conf.js +20 -15
  47. package/lib/ads/ad_manager.js +46 -26
  48. package/lib/ads/client_side_ad_manager.js +82 -88
  49. package/lib/ads/server_side_ad_manager.js +45 -13
  50. package/lib/cast/cast_proxy.js +6 -2
  51. package/lib/cast/cast_receiver.js +5 -1
  52. package/lib/cast/cast_sender.js +2 -2
  53. package/lib/cea/sei_processor.js +24 -17
  54. package/lib/dash/dash_parser.js +131 -24
  55. package/lib/dash/segment_list.js +1 -1
  56. package/lib/dash/segment_template.js +1 -1
  57. package/lib/hls/hls_parser.js +74 -17
  58. package/lib/hls/manifest_text_parser.js +1 -0
  59. package/lib/media/closed_caption_parser.js +3 -1
  60. package/lib/media/content_workarounds.js +12 -0
  61. package/lib/media/drm_engine.js +20 -1
  62. package/lib/media/gap_jumping_controller.js +8 -4
  63. package/lib/media/media_source_engine.js +1 -2
  64. package/lib/media/playhead.js +1 -1
  65. package/lib/media/region_observer.js +39 -33
  66. package/lib/media/region_timeline.js +17 -19
  67. package/lib/media/segment_index.js +2 -1
  68. package/lib/media/segment_reference.js +19 -1
  69. package/lib/media/streaming_engine.js +8 -4
  70. package/lib/net/networking_engine.js +6 -1
  71. package/lib/offline/download_manager.js +1 -2
  72. package/lib/offline/storage.js +8 -9
  73. package/lib/player.js +129 -59
  74. package/lib/polyfill/aria.js +12 -2
  75. package/lib/polyfill/encryption_scheme.js +2 -0
  76. package/lib/polyfill/fullscreen.js +2 -0
  77. package/lib/polyfill/mathround.js +2 -0
  78. package/lib/polyfill/media_capabilities.js +73 -21
  79. package/lib/polyfill/mediasource.js +8 -0
  80. package/lib/polyfill/orientation.js +3 -1
  81. package/lib/polyfill/patchedmediakeys_apple.js +6 -4
  82. package/lib/polyfill/patchedmediakeys_ms.js +6 -4
  83. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  84. package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
  85. package/lib/polyfill/pip_webkit.js +2 -0
  86. package/lib/polyfill/storage_estimate.js +2 -0
  87. package/lib/polyfill/video_play_promise.js +2 -0
  88. package/lib/polyfill/videoplaybackquality.js +2 -0
  89. package/lib/polyfill/vttcue.js +2 -0
  90. package/lib/text/cue.js +6 -0
  91. package/lib/text/mp4_ttml_parser.js +3 -0
  92. package/lib/text/text_engine.js +14 -2
  93. package/lib/text/ttml_text_parser.js +135 -87
  94. package/lib/text/ui_text_displayer.js +213 -80
  95. package/lib/text/vtt_text_parser.js +4 -2
  96. package/lib/text/web_vtt_generator.js +21 -12
  97. package/lib/util/fake_event.js +53 -7
  98. package/lib/util/fake_event_target.js +21 -1
  99. package/lib/util/mp4_box_parsers.js +1 -1
  100. package/lib/util/periods.js +4 -1
  101. package/lib/util/platform.js +13 -1
  102. package/lib/util/stream_utils.js +105 -13
  103. package/lib/util/xml_utils.js +50 -12
  104. package/package.json +3 -6
  105. package/roadmap.md +24 -22
  106. package/test/cast/cast_proxy_unit.js +12 -6
  107. package/test/cast/cast_receiver_integration.js +1 -1
  108. package/test/cast/cast_utils_unit.js +1 -1
  109. package/test/cea/mp4_cea_parser_unit.js +26 -0
  110. package/test/dash/dash_parser_live_unit.js +43 -0
  111. package/test/dash/dash_parser_manifest_unit.js +222 -3
  112. package/test/hls/hls_parser_unit.js +79 -1
  113. package/test/media/closed_caption_parser_unit.js +20 -0
  114. package/test/media/drm_engine_integration.js +86 -2
  115. package/test/media/drm_engine_unit.js +57 -0
  116. package/test/media/playhead_unit.js +27 -1
  117. package/test/media/region_observer_unit.js +40 -5
  118. package/test/media/region_timeline_unit.js +40 -22
  119. package/test/media/streaming_engine_integration.js +2 -1
  120. package/test/media/streaming_engine_unit.js +145 -0
  121. package/test/player_integration.js +79 -8
  122. package/test/player_src_equals_integration.js +41 -8
  123. package/test/player_unit.js +14 -4
  124. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  125. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  126. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  127. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  128. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  129. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  130. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  131. package/test/test/assets/3675/dash_0.mpd +30 -0
  132. package/test/test/assets/3675/dash_1.mpd +30 -0
  133. package/test/test/assets/3675/dash_2.mpd +30 -0
  134. package/test/test/assets/3675/dash_3.mpd +30 -0
  135. package/test/test/assets/3675/dash_4.mpd +30 -0
  136. package/test/test/assets/3675/dash_5.mpd +26 -0
  137. package/test/test/assets/3675/init-stream0.m4s +0 -0
  138. package/test/test/assets/chapters.vtt +2 -2
  139. package/test/test/assets/chapters2.vtt +10 -0
  140. package/test/test/assets/empty_caption_video_init.mp4 +0 -0
  141. package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
  142. package/test/test/util/fake_ad_manager.js +11 -1
  143. package/test/test/util/ttml_utils.js +81 -0
  144. package/test/test/util/util.js +10 -7
  145. package/test/text/mp4_ttml_parser_unit.js +11 -18
  146. package/test/text/ttml_text_parser_unit.js +542 -220
  147. package/test/text/ui_text_displayer_unit.js +93 -20
  148. package/test/text/vtt_text_parser_unit.js +17 -1
  149. package/test/text/web_vtt_generator_unit.js +58 -3
  150. package/test/ui/localization_unit.js +15 -14
  151. package/test/ui/text_displayer_layout_unit.js +21 -8
  152. package/test/util/mp4_box_parsers_unit.js +28 -0
  153. package/test/util/stream_utils_unit.js +60 -3
  154. package/ui/cast_button.js +2 -3
  155. package/ui/controls.js +11 -6
  156. package/ui/less/buttons.less +0 -4
  157. package/ui/less/containers.less +6 -7
  158. package/ui/locales/ru.json +6 -6
  159. package/ui/localization.js +40 -87
  160. package/ui/pip_button.js +2 -3
  161. package/ui/seek_bar.js +1 -2
  162. package/ui/text_selection.js +1 -0
@@ -4,7 +4,6 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- goog.require('shaka.test.UiUtils');
8
7
  goog.require('shaka.test.Util');
9
8
  goog.require('shaka.text.Cue');
10
9
  goog.require('shaka.text.UITextDisplayer');
@@ -51,8 +50,7 @@ describe('UITextDisplayer', () => {
51
50
  /** @type {!HTMLElement} */ (document.createElement('div'));
52
51
  videoContainer.style.height = `${videoContainerHeight}px`;
53
52
  document.body.appendChild(videoContainer);
54
- video = shaka.test.UiUtils.createVideoElement();
55
- videoContainer.appendChild(video);
53
+ video = new shaka.test.FakeVideo();
56
54
  });
57
55
 
58
56
  beforeEach(() => {
@@ -86,20 +84,16 @@ describe('UITextDisplayer', () => {
86
84
  // Wait until updateCaptions_() gets called.
87
85
  await shaka.test.Util.delay(0.5);
88
86
 
89
- const textContainer =
90
- videoContainer.querySelector('.shaka-text-container');
91
- const captions =
92
- textContainer.querySelector('span:not(.shaka-text-wrapper)');
87
+ const textContainer = videoContainer.querySelector('.shaka-text-container');
88
+ const captions = textContainer.querySelector('div');
93
89
  const cssObj = parseCssText(captions.style.cssText);
94
90
 
95
91
  const expectCssObj = {
96
92
  'color': 'green',
97
- 'background-color': 'black',
98
93
  'direction': 'ltr',
99
94
  'font-size': '10px',
100
95
  'font-style': 'normal',
101
96
  'font-weight': 400,
102
- 'line-height': 2,
103
97
  'text-align': 'center',
104
98
  };
105
99
 
@@ -116,6 +110,8 @@ describe('UITextDisplayer', () => {
116
110
  }
117
111
 
118
112
  expect(cssObj).toEqual(jasmine.objectContaining(expectCssObj));
113
+ expect(parseCssText(textContainer.querySelector('span').style.cssText))
114
+ .toEqual(jasmine.objectContaining({'background-color': 'black'}));
119
115
  });
120
116
 
121
117
  it('correctly displays styles for nested cues', async () => {
@@ -139,15 +135,13 @@ describe('UITextDisplayer', () => {
139
135
  await shaka.test.Util.delay(0.5);
140
136
 
141
137
  // Verify styles applied to the nested cues.
142
- const textContainer =
143
- videoContainer.querySelector('.shaka-text-container');
138
+ const textContainer = videoContainer.querySelector('.shaka-text-container');
144
139
  const captions =
145
140
  textContainer.querySelector('span:not(.shaka-text-wrapper)');
146
141
  const cssObj = parseCssText(captions.style.cssText);
147
142
 
148
143
  const expectCssObj = {
149
144
  'color': 'green',
150
- 'background-color': 'black',
151
145
  'font-size': '10px',
152
146
  'font-style': 'normal',
153
147
  'font-weight': 400,
@@ -167,6 +161,8 @@ describe('UITextDisplayer', () => {
167
161
  }
168
162
 
169
163
  expect(cssObj).toEqual(jasmine.objectContaining(expectCssObj));
164
+ expect(parseCssText(captions.querySelector('span').style.cssText))
165
+ .toEqual(jasmine.objectContaining({'background-color': 'black'}));
170
166
  });
171
167
 
172
168
  it('correctly displays styles for cellResolution units', async () => {
@@ -193,10 +189,8 @@ describe('UITextDisplayer', () => {
193
189
  // style.
194
190
  const expectedLinePadding = '11.25px';
195
191
 
196
- const textContainer =
197
- videoContainer.querySelector('.shaka-text-container');
198
- const captions =
199
- textContainer.querySelector('span:not(.shaka-text-wrapper)');
192
+ const textContainer = videoContainer.querySelector('.shaka-text-container');
193
+ const captions = textContainer.querySelector('div');
200
194
  const cssObj = parseCssText(captions.style.cssText);
201
195
  expect(cssObj).toEqual(
202
196
  jasmine.objectContaining({
@@ -224,10 +218,8 @@ describe('UITextDisplayer', () => {
224
218
  // videoContainerHeight=450px and tts:fontSize="90%" on the default style.
225
219
  const expectedFontSize = '27px';
226
220
 
227
- const textContainer =
228
- videoContainer.querySelector('.shaka-text-container');
229
- const captions =
230
- textContainer.querySelector('span:not(.shaka-text-wrapper)');
221
+ const textContainer = videoContainer.querySelector('.shaka-text-container');
222
+ const captions = textContainer.querySelector('div');
231
223
  const cssObj = parseCssText(captions.style.cssText);
232
224
  expect(cssObj).toEqual(
233
225
  jasmine.objectContaining({'font-size': expectedFontSize}));
@@ -320,4 +312,85 @@ describe('UITextDisplayer', () => {
320
312
  // duplicates.
321
313
  expect(captions.length).toBe(3);
322
314
  });
315
+
316
+ it('hides currently displayed cue when removed', async () => {
317
+ const cue = new shaka.text.Cue(0, 50, 'One');
318
+ textDisplayer.setTextVisibility(true);
319
+ textDisplayer.append([cue]);
320
+ video.currentTime = 10;
321
+ await shaka.test.Util.delay(0.5);
322
+ const textContainer = videoContainer.querySelector('.shaka-text-container');
323
+
324
+ let cueElements = textContainer.querySelectorAll('div');
325
+ expect(cueElements.length).toBe(1);
326
+ expect(cueElements[0].textContent).toBe('One');
327
+
328
+ textDisplayer.remove(0, 100);
329
+
330
+ cueElements = textContainer.querySelectorAll('div');
331
+ expect(cueElements.length).toBe(0);
332
+ });
333
+
334
+ it('hides and shows nested cues at appropriate times', async () => {
335
+ const parentCue1 = new shaka.text.Cue(0, 100, '');
336
+ const cue1 = new shaka.text.Cue(0, 50, 'One');
337
+ parentCue1.nestedCues.push(cue1);
338
+ const cue2 = new shaka.text.Cue(25, 75, 'Two');
339
+ parentCue1.nestedCues.push(cue2);
340
+ const cue3 = new shaka.text.Cue(50, 100, 'Three');
341
+ parentCue1.nestedCues.push(cue3);
342
+
343
+ const parentCue2 = new shaka.text.Cue(90, 190, '');
344
+ const cue4 = new shaka.text.Cue(90, 130, 'Four');
345
+ parentCue2.nestedCues.push(cue4);
346
+
347
+ textDisplayer.setTextVisibility(true);
348
+ textDisplayer.append([parentCue1, parentCue2]);
349
+
350
+ video.currentTime = 10;
351
+ await shaka.test.Util.delay(0.5);
352
+ /** @type {Element} */
353
+ const textContainer = videoContainer.querySelector('.shaka-text-container');
354
+ let parentCueElements = textContainer.querySelectorAll('div');
355
+
356
+ expect(parentCueElements.length).toBe(1);
357
+ expect(parentCueElements[0].textContent).toBe('One');
358
+
359
+ video.currentTime = 35;
360
+ await shaka.test.Util.delay(0.5);
361
+ parentCueElements = textContainer.querySelectorAll('div');
362
+ expect(parentCueElements.length).toBe(1);
363
+ expect(parentCueElements[0].textContent).toBe('OneTwo');
364
+
365
+ video.currentTime = 60;
366
+ await shaka.test.Util.delay(0.5);
367
+ parentCueElements = textContainer.querySelectorAll('div');
368
+ expect(parentCueElements.length).toBe(1);
369
+ expect(parentCueElements[0].textContent).toBe('TwoThree');
370
+
371
+ video.currentTime = 85;
372
+ await shaka.test.Util.delay(0.5);
373
+ parentCueElements = textContainer.querySelectorAll('div');
374
+ expect(parentCueElements.length).toBe(1);
375
+ expect(parentCueElements[0].textContent).toBe('Three');
376
+
377
+ video.currentTime = 95;
378
+ await shaka.test.Util.delay(0.5);
379
+ parentCueElements = textContainer.querySelectorAll('div');
380
+ expect(parentCueElements.length).toBe(2);
381
+ expect(parentCueElements[0].textContent).toBe('Three');
382
+ expect(parentCueElements[1].textContent).toBe('Four');
383
+
384
+ video.currentTime = 105;
385
+ await shaka.test.Util.delay(0.5);
386
+ parentCueElements = textContainer.querySelectorAll('div');
387
+ expect(parentCueElements.length).toBe(1);
388
+ expect(parentCueElements[0].textContent).toBe('Four');
389
+
390
+ video.currentTime = 150;
391
+ await shaka.test.Util.delay(0.5);
392
+ parentCueElements = textContainer.querySelectorAll('div');
393
+ expect(parentCueElements.length).toBe(1);
394
+ expect(parentCueElements[0].textContent).toBe('');
395
+ });
323
396
  });
@@ -924,6 +924,18 @@ describe('VttTextParser', () => {
924
924
  payload: '<c.lime><b>Parse fail 2</c></b>',
925
925
  nestedCues: [],
926
926
  },
927
+ {
928
+ startTime: 100,
929
+ endTime: 110,
930
+ payload: '<c.lime>forward slash 1/2 in text</c>',
931
+ nestedCues: [],
932
+ },
933
+ {
934
+ startTime: 110,
935
+ endTime: 120,
936
+ payload: '<c.lime>less or more < > in text</c>',
937
+ nestedCues: [],
938
+ },
927
939
  ],
928
940
  'WEBVTT\n\n' +
929
941
  '00:00:20.000 --> 00:00:40.000\n' +
@@ -939,7 +951,11 @@ describe('VttTextParser', () => {
939
951
  '00:01:20.000 --> 00:01:30.000\n' +
940
952
  '<b><c.lime>Parse fail 1</b></c>\n\n' +
941
953
  '00:01:30.000 --> 00:01:40.000\n' +
942
- '<c.lime><b>Parse fail 2</c></b>',
954
+ '<c.lime><b>Parse fail 2</c></b>\n\n' +
955
+ '00:01:40.000 --> 00:01:50.000\n' +
956
+ '<c.lime>forward slash 1/2 in text</c>\n\n' +
957
+ '00:01:50.000 --> 00:02:00.000\n' +
958
+ '<c.lime>less or more < > in text</c>',
943
959
  {periodStart: 0, segmentStart: 0, segmentEnd: 0});
944
960
  });
945
961
 
@@ -9,7 +9,7 @@ goog.require('shaka.text.WebVttGenerator');
9
9
 
10
10
  describe('WebVttGenerator', () => {
11
11
  it('supports no cues', () => {
12
- verifyHelper([], 'WEBVTT\n\n');
12
+ verifyHelper([], [], 'WEBVTT\n\n');
13
13
  });
14
14
 
15
15
  it('convert cues to WebVTT', () => {
@@ -26,6 +26,8 @@ describe('WebVttGenerator', () => {
26
26
  const shakaCue5 = new shaka.text.Cue(53, 54, 'Test5');
27
27
  shakaCue5.textAlign = shaka.text.Cue.textAlign.END;
28
28
 
29
+ const adCuePoints = [];
30
+
29
31
  verifyHelper(
30
32
  [
31
33
  shakaCue1,
@@ -34,6 +36,7 @@ describe('WebVttGenerator', () => {
34
36
  shakaCue4,
35
37
  shakaCue5,
36
38
  ],
39
+ adCuePoints,
37
40
  'WEBVTT\n\n' +
38
41
  '00:00:20.000 --> 00:00:40.000 align:left vertical:lr\n' +
39
42
  'Test\n\n' +
@@ -68,19 +71,71 @@ describe('WebVttGenerator', () => {
68
71
  nestedCue4.textDecoration.push(shaka.text.Cue.textDecoration.UNDERLINE);
69
72
 
70
73
  shakaCue.nestedCues = [nestedCue1, nestedCue2, nestedCue3, nestedCue4];
74
+
75
+ const adCuePoints = [];
76
+
71
77
  verifyHelper(
72
78
  [shakaCue],
79
+ adCuePoints,
73
80
  'WEBVTT\n\n' +
74
81
  '00:00:10.000 --> 00:00:20.000 align:middle\n' +
75
82
  '<i><u>Test1</u></i><b><i>Test2</i></b>Test3<u>Test4</u>\n\n');
76
83
  });
77
84
 
85
+ it('computes the time with ad cue points', () => {
86
+ const shakaCue1 = new shaka.text.Cue(20, 30, 'Test');
87
+ shakaCue1.textAlign = shaka.text.Cue.textAlign.LEFT;
88
+ shakaCue1.writingMode = shaka.text.Cue.writingMode.VERTICAL_LEFT_TO_RIGHT;
89
+ const shakaCue2 = new shaka.text.Cue(40, 50, 'Test2');
90
+ shakaCue2.textAlign = shaka.text.Cue.textAlign.RIGHT;
91
+ shakaCue2.writingMode = shaka.text.Cue.writingMode.VERTICAL_RIGHT_TO_LEFT;
92
+ const shakaCue3 = new shaka.text.Cue(50, 51, 'Test3');
93
+ shakaCue3.textAlign = shaka.text.Cue.textAlign.CENTER;
94
+ const shakaCue4 = new shaka.text.Cue(52, 53, 'Test4');
95
+ shakaCue4.textAlign = shaka.text.Cue.textAlign.START;
96
+ const shakaCue5 = new shaka.text.Cue(53, 54, 'Test5');
97
+ shakaCue5.textAlign = shaka.text.Cue.textAlign.END;
98
+
99
+ const adCuePoints = [
100
+ {
101
+ start: 0,
102
+ end: 10,
103
+ },
104
+ {
105
+ start: 35,
106
+ end: 45,
107
+ },
108
+ ];
109
+
110
+ verifyHelper(
111
+ [
112
+ shakaCue1,
113
+ shakaCue2,
114
+ shakaCue3,
115
+ shakaCue4,
116
+ shakaCue5,
117
+ ],
118
+ adCuePoints,
119
+ 'WEBVTT\n\n' +
120
+ '00:00:30.000 --> 00:00:40.000 align:left vertical:lr\n' +
121
+ 'Test\n\n' +
122
+ '00:01:00.000 --> 00:01:10.000 align:right vertical:rl\n' +
123
+ 'Test2\n\n' +
124
+ '00:01:10.000 --> 00:01:11.000 align:middle\n' +
125
+ 'Test3\n\n' +
126
+ '00:01:12.000 --> 00:01:13.000 align:start\n' +
127
+ 'Test4\n\n' +
128
+ '00:01:13.000 --> 00:01:14.000 align:end\n' +
129
+ 'Test5\n\n');
130
+ });
131
+
78
132
  /**
79
133
  * @param {!Array} cues
134
+ * @param {!Array} adCuePoints
80
135
  * @param {string} text
81
136
  */
82
- function verifyHelper(cues, text) {
83
- const result = shaka.text.WebVttGenerator.convert(cues);
137
+ function verifyHelper(cues, adCuePoints, text) {
138
+ const result = shaka.text.WebVttGenerator.convert(cues, adCuePoints);
84
139
  expect(text).toBe(result);
85
140
  }
86
141
  });
@@ -198,7 +198,7 @@ describe('Localization', () => {
198
198
  localization.changeLocale([ELVISH_WOODLAND]);
199
199
 
200
200
  expect(events.length).toBe(1);
201
- expect(events[0].locales).toEqual([ELVISH_WOODLAND]);
201
+ expect(events[0]['locales']).toEqual([ELVISH_WOODLAND]);
202
202
  });
203
203
 
204
204
 
@@ -214,23 +214,23 @@ describe('Localization', () => {
214
214
  localization.changeLocale([ELVISH_WOODLAND, DWARFISH_NORTH]);
215
215
 
216
216
  expect(events.length).toBe(1);
217
- expect(events[0].locales).toEqual([ELVISH_WOODLAND, DWARFISH_NORTH]);
217
+ expect(events[0]['locales']).toEqual([ELVISH_WOODLAND, DWARFISH_NORTH]);
218
218
 
219
219
  // We should see an event telling us that dwarfish is still missing.
220
220
  localization.insert(ELVISH_WOODLAND, new Map());
221
221
  localization.changeLocale([ELVISH_WOODLAND, DWARFISH_NORTH]);
222
222
 
223
223
  expect(events.length).toBe(2);
224
- expect(events[0].locales).toEqual([ELVISH_WOODLAND, DWARFISH_NORTH]);
225
- expect(events[1].locales).toEqual([DWARFISH_NORTH]);
224
+ expect(events[0]['locales']).toEqual([ELVISH_WOODLAND, DWARFISH_NORTH]);
225
+ expect(events[1]['locales']).toEqual([DWARFISH_NORTH]);
226
226
 
227
227
  // There should be no more events now that we added the last language.
228
228
  localization.insert(DWARFISH_NORTH, new Map());
229
229
  localization.changeLocale([ELVISH_WOODLAND, DWARFISH_NORTH]);
230
230
 
231
231
  expect(events.length).toBe(2);
232
- expect(events[0].locales).toEqual([ELVISH_WOODLAND, DWARFISH_NORTH]);
233
- expect(events[1].locales).toEqual([DWARFISH_NORTH]);
232
+ expect(events[0]['locales']).toEqual([ELVISH_WOODLAND, DWARFISH_NORTH]);
233
+ expect(events[1]['locales']).toEqual([DWARFISH_NORTH]);
234
234
  });
235
235
  });
236
236
 
@@ -373,8 +373,8 @@ describe('Localization', () => {
373
373
  // it was in the fallback (HALFLING) but not our preferred language
374
374
  // (EVLISH).
375
375
  expect(events.length).toBe(1);
376
- expect(events[0].locales).toEqual([ELVISH_WOODLAND]);
377
- expect(events[0].missing).toEqual(['hello']);
376
+ expect(events[0]['locales']).toEqual([ELVISH_WOODLAND]);
377
+ expect(events[0]['missing']).toEqual(['hello']);
378
378
  });
379
379
 
380
380
  it('fires when key/value is missing from preferred but found in base',
@@ -400,8 +400,8 @@ describe('Localization', () => {
400
400
  // because it was in the base (ELVISH) but not our preferred language
401
401
  // (EVLISH_WOODLAND).
402
402
  expect(events.length).toBe(1);
403
- expect(events[0].locales).toEqual([ELVISH_WOODLAND]);
404
- expect(events[0].missing).toEqual(['Best Wishes']);
403
+ expect(events[0]['locales']).toEqual([ELVISH_WOODLAND]);
404
+ expect(events[0]['missing']).toEqual(['Best Wishes']);
405
405
  });
406
406
 
407
407
  it('fires when key/value is missing from preferred but found in sibling',
@@ -427,8 +427,8 @@ describe('Localization', () => {
427
427
  // understand" because it was in a sibling locale (HALFLING_SHIRE)
428
428
  // but not our preferred language (HALFLING_COMMON).
429
429
  expect(events.length).toBe(1);
430
- expect(events[0].locales).toEqual([HALFLING_COMMON]);
431
- expect(events[0].missing).toEqual(['Do you understand']);
430
+ expect(events[0]['locales']).toEqual([HALFLING_COMMON]);
431
+ expect(events[0]['missing']).toEqual(['Do you understand']);
432
432
  });
433
433
 
434
434
  it('fires when key/value is missing from some preferred languages',
@@ -454,8 +454,9 @@ describe('Localization', () => {
454
454
 
455
455
  // There should have no missing localization events.
456
456
  expect(events.length).toBe(1);
457
- expect(events[0].locales).toEqual([ELVISH_WOODLAND, DWARFISH_NORTH]);
458
- expect(events[0].missing).toEqual(['may your forge burn bright']);
457
+ expect(events[0]['locales']).toEqual(
458
+ [ELVISH_WOODLAND, DWARFISH_NORTH]);
459
+ expect(events[0]['missing']).toEqual(['may your forge burn bright']);
459
460
  });
460
461
  });
461
462
 
@@ -34,11 +34,8 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
34
34
  */
35
35
  let beforeScreenshot;
36
36
 
37
- // Legacy Edge seems to have inconsistent font kerning. A one-pixel offset in
38
- // the position of one character appears about 60% of the time, requiring us
39
- // to have this change tolerance in our tests. So far, all past bugs in our
40
- // implementation that we have tests for would exceed this threshold by a lot.
41
- const threshold = 160; // px
37
+ // A minimum similarity score for screenshots, between 0 and 1.
38
+ const minSimilarity = 0.95;
42
39
 
43
40
  const originalCast = window.chrome && window.chrome.cast;
44
41
 
@@ -141,7 +138,9 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
141
138
  cue.region.id = '1';
142
139
  // Position the cue *explicitly* at the bottom of the screen.
143
140
  cue.region.viewportAnchorX = 0; // %
144
- cue.region.viewportAnchorY = 100; // %
141
+ cue.region.viewportAnchorY = 90; // %
142
+ cue.region.width = 100; // %
143
+ cue.region.height = 10; // %
145
144
  textDisplayer.append([cue]);
146
145
 
147
146
  await checkScreenshot('ui', 'cue-with-controls');
@@ -194,6 +193,7 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
194
193
 
195
194
  // The video must be played a little now, after the cues were appended,
196
195
  // but before the screenshot.
196
+ video.playbackRate = 1;
197
197
  video.play();
198
198
  await waiter.failOnTimeout(false).timeoutAfter(5)
199
199
  .waitForMovement(video);
@@ -201,10 +201,13 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
201
201
 
202
202
  // Seek to a time when cues should be showing.
203
203
  video.currentTime = time;
204
+ // Get into a playing state, but without movement.
205
+ video.playbackRate = 0;
206
+ video.play();
204
207
 
205
208
  // Add a short delay to ensure that the system has caught up and that
206
209
  // native text displayers have been updated by the browser.
207
- await Util.shortDelay();
210
+ await Util.delay(0.1);
208
211
  };
209
212
  });
210
213
 
@@ -314,6 +317,16 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
314
317
  await checkScreenshot(prefix, 'nested-cues-with-linebreak');
315
318
  });
316
319
 
320
+ // Regression test for #3600
321
+ it('cue positioning', async () => {
322
+ const cue = new shaka.text.Cue(0, 1, 'Text');
323
+ cue.line = 10;
324
+ cue.lineInterpretation = shaka.text.Cue.lineInterpretation.PERCENTAGE;
325
+ textDisplayer.append([cue]);
326
+
327
+ await checkScreenshot(prefix, 'cue-position');
328
+ });
329
+
317
330
  // Regression test for #2157 and #2584
318
331
  it('region positioning', async () => {
319
332
  const nestedCue = new shaka.text.Cue(
@@ -463,6 +476,6 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
463
476
  return Util.checkScreenshot(
464
477
  /* element= */ videoContainer,
465
478
  prefix + '-' + baseName,
466
- threshold);
479
+ minSimilarity);
467
480
  }
468
481
  });
@@ -149,4 +149,32 @@ describe('Mp4BoxParsers', () => {
149
149
  expect(defaultSampleDuration).toBe(expectedDefaultSampleDuration);
150
150
  expect(baseMediaDecodeTime).toBe(expectedBaseMediaDecodeTime);
151
151
  });
152
+
153
+ /**
154
+ * Test on parsing an incomplete TKHD V1 box, since the parser doesn't
155
+ * parse the other fields
156
+ *
157
+ * Explanation on the Uint8Array:
158
+ * [
159
+ * <creation_time, 8 bytes>,
160
+ * <modification_time, 8 bytes>,
161
+ * <track_id, 4 bytes>
162
+ * ]
163
+ *
164
+ * Time is a 32B integer expressed in seconds since Jan 1, 1904, 0000 UTC
165
+ *
166
+ */
167
+ it('parses TKHD v1 box', () => {
168
+ const tkhdBox = new Uint8Array([
169
+ 0x00, 0x00, 0x00, 0x00, 0xDC, 0xBF, 0x0F, 0xD7, // Creation time
170
+ 0x00, 0x00, 0x00, 0x00, 0xDC, 0xBF, 0x0F, 0xD7, // Modification time
171
+ 0x00, 0x00, 0x00, 0x01, // Track ID
172
+ // Remaining fields are not processed in parseTKHD()
173
+ ]);
174
+ const reader = new shaka.util.DataViewReader(
175
+ tkhdBox, shaka.util.DataViewReader.Endianness.BIG_ENDIAN);
176
+ const parsedTkhd = shaka.util.Mp4BoxParsers
177
+ .parseTKHD(reader, /* version= */ 1);
178
+ expect(parsedTkhd.trackId).toBe(1);
179
+ });
152
180
  });
@@ -488,7 +488,23 @@ 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();
@@ -517,10 +533,51 @@ 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
+ const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo;
543
+
544
+ try {
545
+ navigator.mediaCapabilities.decodingInfo =
546
+ shaka.test.Util.spyFunc(decodingInfoSpy);
547
+
548
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
549
+ manifest.addVariant(0, (variant) => {
550
+ variant.addVideo(0, (stream) => {
551
+ stream.mime('video/mp4', 'avc1.640028');
552
+ stream.hdr = 'SDR';
553
+ });
554
+ });
555
+ manifest.addVariant(1, (variant) => {
556
+ variant.addVideo(1, (stream) => {
557
+ stream.mime('video/mp4', 'hvc1.2.4.L150.90');
558
+ stream.hdr = 'PQ';
559
+ });
560
+ });
561
+ manifest.addVariant(2, (variant) => {
562
+ variant.addVideo(2, (stream) => {
563
+ stream.mime('video/mp4', 'hvc1.2.4.L153.B0');
564
+ stream.hdr = 'HLG';
565
+ });
566
+ });
567
+ });
568
+
569
+ await StreamUtils.getDecodingInfosForVariants(manifest.variants,
570
+ /* usePersistentLicenses= */ false, /* srcEquals= */ false);
571
+ expect(decodingInfoSpy.calls.argsFor(0)[0].video.transferFunction)
572
+ .toBe('srgb');
573
+ expect(decodingInfoSpy.calls.argsFor(1)[0].video.transferFunction)
574
+ .toBe('pq');
575
+ expect(decodingInfoSpy.calls.argsFor(2)[0].video.transferFunction)
576
+ .toBe('hlg');
577
+ } finally {
578
+ navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
579
+ }
580
+ });
524
581
  });
525
582
 
526
583
  describe('filterManifest', () => {
@@ -820,7 +877,7 @@ describe('StreamUtils', () => {
820
877
  });
821
878
 
822
879
  await StreamUtils.getDecodingInfosForVariants(manifest.variants,
823
- /* usePersistentLicenses= */false);
880
+ /* usePersistentLicenses= */false, /* srcEquals= */ false);
824
881
 
825
882
  shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest,
826
883
  /* preferredVideoCodecs= */[],
package/ui/cast_button.js CHANGED
@@ -101,9 +101,8 @@ shaka.ui.CastButton = class extends shaka.ui.Element {
101
101
  } catch (error) {
102
102
  this.castButton_.disabled = false;
103
103
  if (error.code != shaka.util.Error.Code.CAST_CANCELED_BY_USER) {
104
- this.controls.dispatchEvent(new shaka.util.FakeEvent('error', {
105
- detail: error,
106
- }));
104
+ this.controls.dispatchEvent(new shaka.util.FakeEvent(
105
+ 'error', (new Map()).set('detail', error)));
107
106
  }
108
107
  }
109
108
  }
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;
@@ -249,6 +253,9 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
249
253
 
250
254
  this.localization_ = null;
251
255
  this.pressedKeys_.clear();
256
+
257
+ // FakeEventTarget implements IReleasable
258
+ super.release();
252
259
  }
253
260
 
254
261
 
@@ -569,9 +576,8 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
569
576
  }
570
577
  }
571
578
  } catch (error) {
572
- this.dispatchEvent(new shaka.util.FakeEvent('error', {
573
- detail: error,
574
- }));
579
+ this.dispatchEvent(new shaka.util.FakeEvent(
580
+ 'error', (new Map()).set('detail', error)));
575
581
  }
576
582
  }
577
583
  }
@@ -1158,9 +1164,8 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
1158
1164
  /** @private */
1159
1165
  onCastStatusChange_() {
1160
1166
  const isCasting = this.castProxy_.isCasting();
1161
- this.dispatchEvent(new shaka.util.FakeEvent('caststatuschanged', {
1162
- newStatus: isCasting,
1163
- }));
1167
+ this.dispatchEvent(new shaka.util.FakeEvent(
1168
+ 'caststatuschanged', (new Map()).set('newStatus', isCasting)));
1164
1169
 
1165
1170
  if (isCasting) {
1166
1171
  this.controlsContainer_.setAttribute('casting', 'true');
@@ -75,10 +75,6 @@
75
75
  .shaka-current-time {
76
76
  font-size: 14px;
77
77
  color: rgb(255, 255, 255);
78
-
79
- /* Make the time element the right size for the text, instead of defaulting
80
- * to the same size as the rest of the controls. */
81
- height: auto;
82
78
  cursor: pointer;
83
79
 
84
80
  &[disabled] {