shaka-player 4.3.13 → 4.3.15

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 (137) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/question.md +7 -0
  3. package/.github/workflows/release-please.yaml +2 -27
  4. package/.github/workflows/shaka-bot-commands/lib.sh +37 -12
  5. package/.github/workflows/shaka-bot-commands/main.sh +4 -2
  6. package/.github/workflows/talk-to-shaka-bot.yaml +1 -0
  7. package/AUTHORS +5 -0
  8. package/CHANGELOG.md +96 -0
  9. package/CODE_OF_CONDUCT.md +1 -1
  10. package/CONTRIBUTORS +6 -0
  11. package/README.md +2 -0
  12. package/demo/common/asset.js +0 -1
  13. package/demo/common/assets.js +0 -1
  14. package/demo/custom.js +2 -0
  15. package/dist/controls.css +1 -1
  16. package/dist/controls.css.map +1 -1
  17. package/dist/demo.compiled.debug.js +1 -1
  18. package/dist/demo.compiled.debug.map +1 -1
  19. package/dist/demo.compiled.js +1 -1
  20. package/dist/demo.compiled.map +1 -1
  21. package/dist/deps.js +6 -6
  22. package/dist/locales.js +30 -30
  23. package/dist/receiver.compiled.debug.map +1 -1
  24. package/dist/receiver.compiled.map +1 -1
  25. package/dist/shaka-player.compiled.d.ts +25 -5
  26. package/dist/shaka-player.compiled.debug.d.ts +25 -5
  27. package/dist/shaka-player.compiled.debug.externs.js +73 -45
  28. package/dist/shaka-player.compiled.debug.js +426 -406
  29. package/dist/shaka-player.compiled.debug.map +4 -4
  30. package/dist/shaka-player.compiled.externs.js +73 -45
  31. package/dist/shaka-player.compiled.js +912 -903
  32. package/dist/shaka-player.compiled.map +4 -4
  33. package/dist/shaka-player.ui.d.ts +37 -6
  34. package/dist/shaka-player.ui.debug.d.ts +37 -6
  35. package/dist/shaka-player.ui.debug.externs.js +74 -45
  36. package/dist/shaka-player.ui.debug.js +449 -428
  37. package/dist/shaka-player.ui.debug.map +4 -4
  38. package/dist/shaka-player.ui.externs.js +74 -45
  39. package/dist/shaka-player.ui.js +1060 -1051
  40. package/dist/shaka-player.ui.map +4 -4
  41. package/docs/tutorials/ad_monetization.md +54 -0
  42. package/docs/tutorials/fairplay.md +2 -2
  43. package/docs/tutorials/index.json +1 -0
  44. package/docs/tutorials/plugins.md +4 -0
  45. package/docs/tutorials/text-displayer.md +15 -0
  46. package/docs/tutorials/ui-customization.md +29 -0
  47. package/docs/tutorials/ui.md +4 -1
  48. package/externs/lcevc.js +6 -0
  49. package/externs/shaka/manifest_parser.js +8 -0
  50. package/externs/shaka/player.js +1 -1
  51. package/externs/shaka/text.js +3 -1
  52. package/lib/abr/simple_abr_manager.js +1 -1
  53. package/lib/ads/client_side_ad.js +9 -0
  54. package/lib/ads/client_side_ad_manager.js +7 -2
  55. package/lib/ads/server_side_ad_manager.js +4 -0
  56. package/lib/cast/cast_proxy.js +18 -0
  57. package/lib/cea/cea608_memory.js +1 -1
  58. package/lib/cea/cea_decoder.js +11 -1
  59. package/lib/dash/dash_parser.js +75 -41
  60. package/lib/dash/mpd_utils.js +39 -27
  61. package/lib/deprecate/deprecate.js +1 -1
  62. package/lib/hls/hls_parser.js +106 -28
  63. package/lib/hls/hls_utils.js +5 -5
  64. package/lib/media/content_workarounds.js +30 -0
  65. package/lib/media/drm_engine.js +16 -12
  66. package/lib/media/manifest_parser.js +2 -1
  67. package/lib/media/media_source_engine.js +4 -2
  68. package/lib/media/play_rate_controller.js +1 -0
  69. package/lib/media/presentation_timeline.js +11 -0
  70. package/lib/media/quality_observer.js +1 -1
  71. package/lib/media/segment_reference.js +5 -2
  72. package/lib/media/streaming_engine.js +50 -30
  73. package/lib/media/transmuxer.js +1 -1
  74. package/lib/media/video_wrapper.js +4 -3
  75. package/lib/offline/offline_manifest_parser.js +5 -0
  76. package/lib/offline/session_deleter.js +0 -8
  77. package/lib/offline/storage.js +4 -1
  78. package/lib/player.js +80 -19
  79. package/lib/polyfill/media_capabilities.js +23 -3
  80. package/lib/polyfill/mediasource.js +5 -36
  81. package/lib/text/mp4_ttml_parser.js +2 -2
  82. package/lib/text/simple_text_displayer.js +7 -6
  83. package/lib/text/srt_text_parser.js +2 -2
  84. package/lib/text/text_engine.js +3 -2
  85. package/lib/text/ttml_text_parser.js +33 -5
  86. package/lib/text/ui_text_displayer.js +81 -15
  87. package/lib/text/vtt_text_parser.js +17 -3
  88. package/lib/util/buffer_utils.js +26 -4
  89. package/lib/util/cmcd_manager.js +10 -0
  90. package/lib/util/error.js +1 -0
  91. package/lib/util/fairplay_utils.js +1 -1
  92. package/lib/util/manifest_parser_utils.js +14 -8
  93. package/lib/util/object_utils.js +1 -1
  94. package/lib/util/periods.js +91 -36
  95. package/lib/util/platform.js +39 -2
  96. package/lib/util/player_configuration.js +5 -3
  97. package/lib/util/stream_utils.js +36 -28
  98. package/lib/util/xml_utils.js +5 -0
  99. package/package.json +3 -3
  100. package/test/cea/cea_decoder_unit.js +40 -0
  101. package/test/dash/dash_parser_live_unit.js +5 -8
  102. package/test/hls/hls_parser_unit.js +78 -3
  103. package/test/media/buffering_observer_unit.js +13 -0
  104. package/test/media/content_workarounds_unit.js +1 -1
  105. package/test/media/drm_engine_unit.js +30 -3
  106. package/test/media/media_source_engine_unit.js +1 -1
  107. package/test/media/transmuxer_integration.js +2 -2
  108. package/test/offline/storage_integration.js +3 -0
  109. package/test/player_integration.js +49 -41
  110. package/test/player_load_graph_integration.js +13 -7
  111. package/test/player_src_equals_integration.js +2 -2
  112. package/test/player_unit.js +40 -3
  113. package/test/polyfill/media_capabilities_unit.js +12 -6
  114. package/test/test/util/fake_drm_engine.js +7 -0
  115. package/test/test/util/layout_tests.js +2 -1
  116. package/test/test/util/simple_fakes.js +2 -2
  117. package/test/test/util/test_scheme.js +3 -0
  118. package/test/test/util/waiter.js +5 -0
  119. package/test/text/mp4_ttml_parser_unit.js +4 -4
  120. package/test/text/simple_text_displayer_unit.js +1 -1
  121. package/test/text/srt_text_parser_unit.js +1 -1
  122. package/test/text/text_engine_unit.js +6 -0
  123. package/test/text/ttml_text_parser_unit.js +49 -18
  124. package/test/text/ui_text_displayer_unit.js +9 -0
  125. package/test/text/vtt_text_parser_unit.js +31 -0
  126. package/test/util/buffer_utils_unit.js +1 -1
  127. package/test/util/periods_unit.js +113 -0
  128. package/third_party/closure-uri/uri.js +1 -1
  129. package/ui/externs/ui.js +24 -0
  130. package/ui/language_utils.js +11 -5
  131. package/ui/less/containers.less +1 -0
  132. package/ui/play_button.js +20 -0
  133. package/ui/playback_rate_selection.js +4 -0
  134. package/ui/presentation_time.js +5 -5
  135. package/ui/small_play_button.js +2 -2
  136. package/ui/volume_bar.js +10 -6
  137. /package/.github/{config.yml → ISSUE_TEMPLATE/config.yml} +0 -0
@@ -114,8 +114,8 @@ describe('Player Src Equals', () => {
114
114
  expect(video.currentTime).toBeLessThan(2.0);
115
115
 
116
116
  // Trigger a seek and then wait for the seek to take effect.
117
- // This seek target is very close to the duration of the video.
118
- video.currentTime = 10;
117
+ // This seek target is very close to the duration of the video (10.01s).
118
+ video.currentTime = 9.5;
119
119
  await waiter.waitForMovementOrFailOnTimeout(video, /* timeout= */10);
120
120
 
121
121
  // Make sure the playhead is roughly where we expect it to be after
@@ -559,15 +559,21 @@ describe('Player', () => {
559
559
  });
560
560
 
561
561
  describe('when config.streaming.preferNativeHls is set to true', () => {
562
- beforeEach(() => {
562
+ beforeAll(() => {
563
563
  shaka.media.ManifestParser.registerParserByMime(
564
564
  'application/x-mpegurl',
565
565
  () => new shaka.test.FakeManifestParser(manifest));
566
566
  });
567
567
 
568
+ afterAll(() => {
569
+ // IMPORTANT: restore the ORIGINAL parser. DO NOT just unregister the
570
+ // fake!
571
+ shaka.media.ManifestParser.registerParserByMime(
572
+ 'application/x-mpegurl',
573
+ () => new shaka.hls.HlsParser());
574
+ });
575
+
568
576
  afterEach(() => {
569
- shaka.media.ManifestParser.unregisterParserByMime(
570
- 'application/x-mpegurl');
571
577
  video.canPlayType.calls.reset();
572
578
  });
573
579
 
@@ -3291,6 +3297,37 @@ describe('Player', () => {
3291
3297
  expect(tracks[0].id).toBe(2);
3292
3298
  });
3293
3299
 
3300
+ it('removes based on frameRate', async () => {
3301
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3302
+ manifest.addVariant(0, (variant) => {
3303
+ variant.addVideo(1, (stream) => {
3304
+ stream.frameRate = 15;
3305
+ });
3306
+ });
3307
+
3308
+ manifest.addVariant(1, (variant) => {
3309
+ variant.addVideo(2, (stream) => {
3310
+ stream.frameRate = 30;
3311
+ });
3312
+ });
3313
+
3314
+ manifest.addVariant(2, (variant) => {
3315
+ variant.addVideo(3, (stream) => {
3316
+ stream.frameRate = 60;
3317
+ });
3318
+ });
3319
+ });
3320
+
3321
+ await player.load(fakeManifestUri, 0, fakeMimeType);
3322
+ expect(player.getVariantTracks().length).toBe(3);
3323
+
3324
+ player.configure({restrictions: {minFrameRate: 20, maxFrameRate: 40}});
3325
+
3326
+ const tracks = player.getVariantTracks();
3327
+ expect(tracks.length).toBe(1);
3328
+ expect(tracks[0].id).toBe(1);
3329
+ });
3330
+
3294
3331
  it('removes the whole variant if one stream is restricted', async () => {
3295
3332
  manifest = shaka.test.ManifestGenerator.generate((manifest) => {
3296
3333
  manifest.addVariant(0, (variant) => {
@@ -178,8 +178,10 @@ describe('MediaCapabilities', () => {
178
178
  await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
179
179
 
180
180
  expect(mockCanDisplayType).not.toHaveBeenCalled();
181
- // 1 (during install()) + 1 (for video config check).
182
- expect(isChromecastSpy).toHaveBeenCalledTimes(2);
181
+ // 1 (during install()) +
182
+ // 1 (for video config check) +
183
+ // 1 (for audio config check).
184
+ expect(isChromecastSpy).toHaveBeenCalledTimes(3);
183
185
  // 1 (fallback in canCastDisplayType()) +
184
186
  // 1 (mockDecodingConfig.audio).
185
187
  expect(supportMap.has(mockDecodingConfig.video.contentType))
@@ -201,8 +203,10 @@ describe('MediaCapabilities', () => {
201
203
  await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
202
204
 
203
205
  expect(mockCanDisplayType).not.toHaveBeenCalled();
204
- // 1 (during install()) + 1 (for video config check).
205
- expect(isChromecastSpy).toHaveBeenCalledTimes(2);
206
+ // 1 (during install()) +
207
+ // 1 (for video config check) +
208
+ // 1 (for audio config check).
209
+ expect(isChromecastSpy).toHaveBeenCalledTimes(3);
206
210
  // 1 (fallback in canCastDisplayType()) +
207
211
  // 1 (mockDecodingConfig.audio).
208
212
  expect(supportMap.has(mockDecodingConfig.video.contentType))
@@ -248,8 +252,10 @@ describe('MediaCapabilities', () => {
248
252
  shaka.polyfill.MediaCapabilities.install();
249
253
  await navigator.mediaCapabilities.decodingInfo(mockDecodingConfig);
250
254
 
251
- // 1 (during install()) + 1 (for video config check).
252
- expect(isChromecastSpy).toHaveBeenCalledTimes(2);
255
+ // 1 (during install()) +
256
+ // 1 (for video config check) +
257
+ // 1 (for audio config check).
258
+ expect(isChromecastSpy).toHaveBeenCalledTimes(3);
253
259
  // 1 (mockDecodingConfig.audio).
254
260
  expect(supportMap.has(chromecastType)).toBe(true);
255
261
  // Called once in canCastDisplayType.
@@ -100,4 +100,11 @@ shaka.test.FakeDrmEngine = class {
100
100
  // Copy the values to break the reference to the input value.
101
101
  this.offlineSessions_ = sessions.map((s) => s);
102
102
  }
103
+
104
+ /**
105
+ * @override
106
+ */
107
+ hasManifestInitData() {
108
+ return true;
109
+ }
103
110
  };
@@ -377,7 +377,8 @@ shaka.test.NativeTextLayoutTests = class extends shaka.test.TextLayoutTests {
377
377
 
378
378
  /** @override */
379
379
  recreateTextDisplayer() {
380
- this.textDisplayer = new shaka.text.SimpleTextDisplayer(this.video);
380
+ this.textDisplayer = new shaka.text.SimpleTextDisplayer(
381
+ this.video, shaka.Player.TextTrackLabel);
381
382
  this.textDisplayer.setTextVisibility(true);
382
383
  }
383
384
 
@@ -520,7 +520,7 @@ shaka.test.FakeTransmuxer = class {
520
520
  jasmine.createSpy('transmux').and.returnValue(Promise.resolve(output));
521
521
 
522
522
  /** @type {!jasmine.Spy} */
523
- this.getOrginalMimeType =
524
- jasmine.createSpy('getOrginalMimeType').and.returnValue('mimeType');
523
+ this.getOriginalMimeType =
524
+ jasmine.createSpy('getOriginalMimeType').and.returnValue('mimeType');
525
525
  }
526
526
  };
@@ -763,6 +763,9 @@ shaka.test.TestScheme.ManifestParser = class {
763
763
 
764
764
  /** @override */
765
765
  onExpirationUpdated() {}
766
+
767
+ /** @override */
768
+ onInitialVariantChosen() {}
766
769
  };
767
770
 
768
771
 
@@ -184,6 +184,7 @@ shaka.test.Waiter = class {
184
184
  }
185
185
  this.eventManager_.unlisten(mediaElement, 'timeupdate');
186
186
  this.eventManager_.unlisten(mediaElement, 'ended');
187
+ this.eventManager_.unlisten(mediaElement, 'durationchange');
187
188
  };
188
189
 
189
190
  const p = new Promise((resolve) => {
@@ -202,6 +203,10 @@ shaka.test.Waiter = class {
202
203
  timer.tickEvery(/* seconds= */ 1);
203
204
  this.eventManager_.listen(mediaElement, 'timeupdate', check);
204
205
  this.eventManager_.listen(mediaElement, 'ended', check);
206
+ // Some tests on Safari expose a condition where the ended flag can be
207
+ // found in the desired state in a brief window during a durationchange
208
+ // event.
209
+ this.eventManager_.listen(mediaElement, 'durationchange', check);
205
210
  });
206
211
 
207
212
  return this.waitUntilGeneric_(goalName, p, cleanup, mediaElement);
@@ -43,7 +43,7 @@ describe('Mp4TtmlParser', () => {
43
43
  parser.parseInit(ttmlInitSegment);
44
44
  const time =
45
45
  {periodStart: 0, segmentStart: 0, segmentEnd: 60, vttOffset: 0};
46
- const ret = parser.parseMedia(ttmlSegmentMultipleMDAT, time);
46
+ const ret = parser.parseMedia(ttmlSegmentMultipleMDAT, time, null);
47
47
  // Bodies.
48
48
  expect(ret.length).toBe(2);
49
49
  // Divs.
@@ -63,10 +63,10 @@ describe('Mp4TtmlParser', () => {
63
63
  const parser = new shaka.text.Mp4TtmlParser();
64
64
  parser.parseInit(ttmlInitSegment);
65
65
 
66
- const ret1 = parser.parseMedia(ttmlSegment, time1);
66
+ const ret1 = parser.parseMedia(ttmlSegment, time1, null);
67
67
  expect(ret1.length).toBeGreaterThan(0);
68
68
 
69
- const ret2 = parser.parseMedia(ttmlSegment, time2);
69
+ const ret2 = parser.parseMedia(ttmlSegment, time2, null);
70
70
  expect(ret2.length).toBeGreaterThan(0);
71
71
 
72
72
  expect(ret2[0].startTime).toBe(ret1[0].startTime + 7);
@@ -164,7 +164,7 @@ describe('Mp4TtmlParser', () => {
164
164
  parser.parseInit(ttmlInitSegment);
165
165
  const time =
166
166
  {periodStart: 0, segmentStart: 0, segmentEnd: 60, vttOffset: 0};
167
- const result = parser.parseMedia(ttmlSegment, time);
167
+ const result = parser.parseMedia(ttmlSegment, time, null);
168
168
  shaka.test.TtmlUtils.verifyHelper(
169
169
  cues, result, {startTime: 23, endTime: 53.5});
170
170
  });
@@ -18,7 +18,7 @@ describe('SimpleTextDisplayer', () => {
18
18
 
19
19
  beforeEach(() => {
20
20
  video = new shaka.test.FakeVideo();
21
- displayer = new SimpleTextDisplayer(video);
21
+ displayer = new SimpleTextDisplayer(video, shaka.Player.TextTrackLabel);
22
22
 
23
23
  expect(video.textTracks.length).toBe(1);
24
24
  mockTrack = /** @type {!shaka.test.FakeTextTrack} */ (video.textTracks[0]);
@@ -71,7 +71,7 @@ describe('SrtTextParser', () => {
71
71
  const data = BufferUtils.toUint8(StringUtils.toUTF8(text));
72
72
 
73
73
  const parser = new shaka.text.SrtTextParser();
74
- const result = parser.parseMedia(data, time);
74
+ const result = parser.parseMedia(data, time, null);
75
75
 
76
76
  const expected = cues.map((cue) => {
77
77
  if (cue.nestedCues) {
@@ -95,6 +95,7 @@ describe('TextEngine', () => {
95
95
  expect(mockParseMedia).toHaveBeenCalledOnceMoreWith([
96
96
  dummyData,
97
97
  {periodStart: 0, segmentStart: 0, segmentEnd: 3, vttOffset: 0},
98
+ undefined,
98
99
  ]);
99
100
 
100
101
  expect(mockDisplayer.appendSpy).toHaveBeenCalledOnceMoreWith([
@@ -110,6 +111,7 @@ describe('TextEngine', () => {
110
111
  expect(mockParseMedia).toHaveBeenCalledOnceMoreWith([
111
112
  dummyData,
112
113
  {periodStart: 0, segmentStart: 3, segmentEnd: 5, vttOffset: 0},
114
+ undefined,
113
115
  ]);
114
116
 
115
117
  expect(mockDisplayer.appendSpy).toHaveBeenCalledOnceMoreWith([
@@ -262,6 +264,7 @@ describe('TextEngine', () => {
262
264
  expect(mockParseMedia).toHaveBeenCalledOnceMoreWith([
263
265
  dummyData,
264
266
  {periodStart: 0, segmentStart: 0, segmentEnd: 3, vttOffset: 0},
267
+ undefined,
265
268
  ]);
266
269
  expect(mockDisplayer.appendSpy).toHaveBeenCalledOnceMoreWith([
267
270
  [
@@ -276,6 +279,7 @@ describe('TextEngine', () => {
276
279
  expect(mockParseMedia).toHaveBeenCalledOnceMoreWith([
277
280
  dummyData,
278
281
  {periodStart: 4, segmentStart: 4, segmentEnd: 7, vttOffset: 4},
282
+ undefined,
279
283
  ]);
280
284
  expect(mockDisplayer.appendSpy).toHaveBeenCalledOnceMoreWith([
281
285
  [
@@ -305,6 +309,7 @@ describe('TextEngine', () => {
305
309
  expect(mockParseMedia).toHaveBeenCalledOnceMoreWith([
306
310
  dummyData,
307
311
  {periodStart: 0, segmentStart: 0, segmentEnd: 3, vttOffset: 0},
312
+ undefined,
308
313
  ]);
309
314
 
310
315
  textEngine.setTimestampOffset(8);
@@ -314,6 +319,7 @@ describe('TextEngine', () => {
314
319
  expect(mockParseMedia).toHaveBeenCalledOnceMoreWith([
315
320
  dummyData,
316
321
  {periodStart: 8, segmentStart: 4, segmentEnd: 7, vttOffset: 4},
322
+ undefined,
317
323
  ]);
318
324
  });
319
325
  });
@@ -638,8 +638,8 @@ describe('TtmlTextParser', () => {
638
638
  id: 'subtitleArea',
639
639
  viewportAnchorX: 50,
640
640
  viewportAnchorY: 16,
641
- width: 100,
642
- height: 100,
641
+ width: 50,
642
+ height: 84,
643
643
  },
644
644
  startTime: 62.05,
645
645
  endTime: 3723.2,
@@ -666,8 +666,8 @@ describe('TtmlTextParser', () => {
666
666
  region: {
667
667
  viewportAnchorX: 50,
668
668
  viewportAnchorY: 16,
669
- width: 100,
670
- height: 100,
669
+ width: 50,
670
+ height: 84,
671
671
  },
672
672
  startTime: 62.05,
673
673
  endTime: 3723.2,
@@ -694,8 +694,8 @@ describe('TtmlTextParser', () => {
694
694
  region: {
695
695
  viewportAnchorX: 50,
696
696
  viewportAnchorY: 16,
697
- width: 100,
698
- height: 100,
697
+ width: 50,
698
+ height: 84,
699
699
  },
700
700
  startTime: 62.05,
701
701
  endTime: 3723.2,
@@ -872,8 +872,8 @@ describe('TtmlTextParser', () => {
872
872
  viewportAnchorY: 16,
873
873
  regionAnchorX: 0,
874
874
  regionAnchorY: 0,
875
- width: 100,
876
- height: 100,
875
+ width: 50,
876
+ height: 84,
877
877
  heightUnits: CueRegion.units.PERCENTAGE,
878
878
  widthUnits: CueRegion.units.PERCENTAGE,
879
879
  viewportAnchorUnits: CueRegion.units.PERCENTAGE,
@@ -945,8 +945,8 @@ describe('TtmlTextParser', () => {
945
945
  id: 'subtitleArea',
946
946
  viewportAnchorX: 50,
947
947
  viewportAnchorY: 16,
948
- width: 100,
949
- height: 100,
948
+ width: 50,
949
+ height: 84,
950
950
  },
951
951
  startTime: 62.05,
952
952
  endTime: 3723.2,
@@ -973,8 +973,8 @@ describe('TtmlTextParser', () => {
973
973
  region: {
974
974
  viewportAnchorX: 50,
975
975
  viewportAnchorY: 16,
976
- width: 100,
977
- height: 100,
976
+ width: 50,
977
+ height: 84,
978
978
  },
979
979
  startTime: 62.05,
980
980
  endTime: 3723.2,
@@ -1001,8 +1001,8 @@ describe('TtmlTextParser', () => {
1001
1001
  region: {
1002
1002
  viewportAnchorX: 50,
1003
1003
  viewportAnchorY: 16,
1004
- width: 100,
1005
- height: 100,
1004
+ width: 50,
1005
+ height: 84,
1006
1006
  },
1007
1007
  startTime: 62.05,
1008
1008
  endTime: 3723.2,
@@ -1032,8 +1032,8 @@ describe('TtmlTextParser', () => {
1032
1032
  id: 'subtitleArea',
1033
1033
  viewportAnchorX: 12.2,
1034
1034
  viewportAnchorY: 50.005,
1035
- width: 100,
1036
- height: 100,
1035
+ width: 87.8,
1036
+ height: 49.995,
1037
1037
  },
1038
1038
  startTime: 62.05,
1039
1039
  endTime: 3723.2,
@@ -1238,6 +1238,35 @@ describe('TtmlTextParser', () => {
1238
1238
  });
1239
1239
  });
1240
1240
 
1241
+ it('supports smpte:backgroundImage attribute with url', () => {
1242
+ verifyHelper(
1243
+ [
1244
+ {
1245
+ startTime: 62.05,
1246
+ endTime: 3723.2,
1247
+ payload: '',
1248
+ },
1249
+ ],
1250
+ '<tt ' +
1251
+ 'xmlns:ttm="http://www.w3.org/ns/ttml#metadata" ' +
1252
+ 'xmlns:smpte="http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt">' +
1253
+ '<metadata>' +
1254
+ '<smpte:image imageType="PNG" encoding="Base64" xml:id="img_0">' +
1255
+ 'base64EncodedImage</smpte:image>' +
1256
+ '</metadata>' +
1257
+ '<body><div smpte:backgroundImage="img_0.png">' +
1258
+ '<p begin="01:02.05" end="01:02:03.200"></p>' +
1259
+ '</div></body></tt>',
1260
+ {periodStart: 0, segmentStart: 60, segmentEnd: 3730, vttOffset: 0},
1261
+ {startTime: 62.05, endTime: 3723.2},
1262
+ {
1263
+ startTime: 62.05,
1264
+ endTime: 3723.2,
1265
+ backgroundImage: 'foo://bar/img_0.png',
1266
+ isContainer: false,
1267
+ });
1268
+ });
1269
+
1241
1270
  it('supports smpte:backgroundImage attribute in div element', () => {
1242
1271
  verifyHelper(
1243
1272
  [],
@@ -2060,7 +2089,8 @@ describe('TtmlTextParser', () => {
2060
2089
  function verifyHelper(cues, text, time, bodyProperties, divProperties) {
2061
2090
  const data =
2062
2091
  shaka.util.BufferUtils.toUint8(shaka.util.StringUtils.toUTF8(text));
2063
- const result = new shaka.text.TtmlTextParser().parseMedia(data, time);
2092
+ const result = new shaka.text.TtmlTextParser()
2093
+ .parseMedia(data, time, 'foo://bar');
2064
2094
  shaka.test.TtmlUtils.verifyHelper(
2065
2095
  cues, result, bodyProperties, divProperties);
2066
2096
  }
@@ -2087,7 +2117,8 @@ describe('TtmlTextParser', () => {
2087
2117
  expect(() => {
2088
2118
  new shaka.text.TtmlTextParser().parseMedia(
2089
2119
  shaka.util.BufferUtils.toUint8(data),
2090
- {periodStart: 0, segmentStart: 0, segmentEnd: 10, vttOffset: 0});
2120
+ {periodStart: 0, segmentStart: 0, segmentEnd: 10, vttOffset: 0},
2121
+ 'foo://bar');
2091
2122
  }).toThrow(error);
2092
2123
  }
2093
2124
  });
@@ -605,4 +605,13 @@ describe('UITextDisplayer', () => {
605
605
  (e) => e.nodeType == Node.ELEMENT_NODE);
606
606
  expect(childrenOfTwo.length).toBe(3);
607
607
  });
608
+
609
+ it('textDisplayer does not crash if destroy is called more than once', () => {
610
+ expect(videoContainer.childNodes.length).toBe(1);
611
+
612
+ textDisplayer.destroy();
613
+ textDisplayer.destroy();
614
+
615
+ expect(videoContainer.childNodes.length).toBe(0);
616
+ });
608
617
  });
@@ -1072,6 +1072,37 @@ describe('VttTextParser', () => {
1072
1072
  {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0});
1073
1073
  });
1074
1074
 
1075
+ it('passes text-shadow when multiple classes included', () => {
1076
+ verifyHelper(
1077
+ [
1078
+ {
1079
+ startTime: 20,
1080
+ endTime: 40,
1081
+ payload: '',
1082
+ nestedCues: [
1083
+ {
1084
+ startTime: 20,
1085
+ endTime: 40,
1086
+ payload: 'Test',
1087
+ fontStyle: Cue.fontStyle.ITALIC,
1088
+ textShadow: 'black 5%',
1089
+ },
1090
+ ],
1091
+ },
1092
+ ],
1093
+ 'WEBVTT\n\n' +
1094
+ 'STYLE\n' +
1095
+ '::cue(.shadow) {\n' +
1096
+ ' text-shadow: black 5%;\n' +
1097
+ '}\n' +
1098
+ '::cue(.italic) {\n' +
1099
+ ' font-style: italic;\n' +
1100
+ '}\n\n' +
1101
+ '00:00:20.000 --> 00:00:40.000\n' +
1102
+ '<c.shadow.italic>Test</c>',
1103
+ {periodStart: 0, segmentStart: 0, segmentEnd: 0, vttOffset: 0});
1104
+ });
1105
+
1075
1106
  it('supports karaoke style text', () => {
1076
1107
  verifyHelper(
1077
1108
  [
@@ -42,7 +42,7 @@ describe('BufferUtils', () => {
42
42
 
43
43
  it('compares with same buffer', () => {
44
44
  const a = new Uint8Array([0, 1, 2, 3]);
45
- const b = new Uint16Array(a.buffer);
45
+ const b = BufferUtils.toUint16(a);
46
46
  expect(BufferUtils.equal(a, b)).toBe(true);
47
47
  });
48
48
 
@@ -598,6 +598,55 @@ describe('PeriodCombiner', () => {
598
598
  }
599
599
  });
600
600
 
601
+ // Regression test for #6054, where we failed on multi-period content with
602
+ // different numbers of forced-subtitle streams per period.
603
+ it('Does not combine subtitle and forced-subtitle tracks', async () => {
604
+ const videoStreams = [makeVideoStream(1280)];
605
+ const audioStreams = [makeAudioStream('en', /* channels= */ 2)];
606
+ const imageStreams = [];
607
+
608
+ const forcedSubtitle = makeTextStream('de');
609
+ forcedSubtitle.roles = ['forced-subtitle'];
610
+ forcedSubtitle.forced = true;
611
+
612
+ const subtitle = makeTextStream('de');
613
+ subtitle.roles = ['subtitle'];
614
+
615
+ /** @type {!Array.<shaka.util.PeriodCombiner.Period>} */
616
+ const periods = [
617
+ {
618
+ id: '1',
619
+ videoStreams,
620
+ audioStreams,
621
+ textStreams: [
622
+ forcedSubtitle,
623
+ subtitle,
624
+ ],
625
+ imageStreams,
626
+ },
627
+ {
628
+ id: '2',
629
+ videoStreams,
630
+ audioStreams,
631
+ textStreams: [
632
+ subtitle,
633
+ ],
634
+ imageStreams,
635
+ },
636
+ {
637
+ id: '3',
638
+ videoStreams,
639
+ audioStreams,
640
+ textStreams: [],
641
+ imageStreams,
642
+ },
643
+ ];
644
+
645
+ await combiner.combinePeriods(periods, /* isDynamic= */ true);
646
+ const textStreams = combiner.getTextStreams();
647
+ expect(textStreams.every((s) => s.roles.length === 1)).toBe(true);
648
+ });
649
+
601
650
  // Regression test for #3383, where we failed on multi-period content with
602
651
  // multiple image streams per period.
603
652
  it('Can handle multiple image streams', async () => {
@@ -1004,6 +1053,70 @@ describe('PeriodCombiner', () => {
1004
1053
  expect(audio2.originalId).toBe('2,4');
1005
1054
  });
1006
1055
 
1056
+ it('Matches streams with labels', async () => {
1057
+ const stream1 = makeAudioStream('en', /* channels= */ 2);
1058
+ stream1.originalId = '1';
1059
+ stream1.bandwidth = 129597;
1060
+ stream1.codecs = 'mp4a.40.2';
1061
+
1062
+ const stream2 = makeAudioStream('en', /* channels= */ 2);
1063
+ stream2.originalId = '2';
1064
+ stream2.bandwidth = 129637;
1065
+ stream2.codecs = 'mp4a.40.2';
1066
+ stream2.label = 'description';
1067
+
1068
+ const stream3 = makeAudioStream('en', /* channels= */ 2);
1069
+ stream3.originalId = '3';
1070
+ stream3.bandwidth = 131037;
1071
+ stream3.codecs = 'mp4a.40.2';
1072
+
1073
+ const stream4 = makeAudioStream('en', /* channels= */ 2);
1074
+ stream4.originalId = '4';
1075
+ stream4.bandwidth = 131034;
1076
+ stream4.codecs = 'mp4a.40.2';
1077
+ stream4.label = 'description';
1078
+
1079
+ /** @type {!Array.<shaka.util.PeriodCombiner.Period>} */
1080
+ const periods = [
1081
+ {
1082
+ id: '0',
1083
+ videoStreams: [
1084
+ makeVideoStream(1080),
1085
+ ],
1086
+ audioStreams: [
1087
+ stream1,
1088
+ stream2,
1089
+ ],
1090
+ textStreams: [],
1091
+ imageStreams: [],
1092
+ },
1093
+ {
1094
+ id: '1',
1095
+ videoStreams: [
1096
+ makeVideoStream(1080),
1097
+ ],
1098
+ audioStreams: [
1099
+ stream3,
1100
+ stream4,
1101
+ ],
1102
+ textStreams: [],
1103
+ imageStreams: [],
1104
+ },
1105
+ ];
1106
+
1107
+ await combiner.combinePeriods(periods, /* isDynamic= */ true);
1108
+ const variants = combiner.getVariants();
1109
+ expect(variants.length).toBe(2);
1110
+ // We can use the originalId field to see what each track is composed of.
1111
+ const audio1 = variants[0].audio;
1112
+ expect(audio1.label).toBe(null);
1113
+ expect(audio1.originalId).toBe('1,3');
1114
+
1115
+ const audio2 = variants[1].audio;
1116
+ expect(audio2.label).toBe('description');
1117
+ expect(audio2.originalId).toBe('2,4');
1118
+ });
1119
+
1007
1120
  it('Matches streams with related codecs', async () => {
1008
1121
  const stream1 = makeVideoStream(1080);
1009
1122
  stream1.originalId = '1';
@@ -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
package/ui/externs/ui.js CHANGED
@@ -443,3 +443,27 @@ shaka.extern.IUISeekBar.Factory = class {
443
443
  */
444
444
  create(rootElement, controls) {}
445
445
  };
446
+
447
+ /**
448
+ * @interface
449
+ * @exportDoc
450
+ */
451
+ shaka.extern.IUIPlayButton = class {
452
+ /**
453
+ * @param {!HTMLElement} parent
454
+ * @param {!shaka.ui.Controls} controls
455
+ */
456
+ constructor(parent, controls) {
457
+ /**
458
+ * @protected {!HTMLButtonElement}
459
+ * @exportDoc
460
+ */
461
+ this.button;
462
+ }
463
+
464
+ /** @return {boolean} */
465
+ isPaused() {}
466
+
467
+ /** @return {boolean} */
468
+ isEnded() {}
469
+ };