shaka-player 4.10.13 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +5 -1
  2. package/dist/controls.css +3 -3
  3. package/dist/controls.css.map +1 -1
  4. package/dist/deps.js +11 -11
  5. package/dist/locales.js +38 -34
  6. package/dist/shaka-player.compiled.d.ts +68 -33
  7. package/dist/shaka-player.compiled.debug.d.ts +68 -33
  8. package/dist/shaka-player.compiled.debug.externs.js +584 -544
  9. package/dist/shaka-player.compiled.debug.js +715 -676
  10. package/dist/shaka-player.compiled.debug.map +4 -4
  11. package/dist/shaka-player.compiled.externs.js +584 -544
  12. package/dist/shaka-player.compiled.js +1394 -1371
  13. package/dist/shaka-player.compiled.map +4 -4
  14. package/dist/shaka-player.ui.d.ts +76 -34
  15. package/dist/shaka-player.ui.debug.d.ts +76 -34
  16. package/dist/shaka-player.ui.debug.externs.js +77 -26
  17. package/dist/shaka-player.ui.debug.js +774 -720
  18. package/dist/shaka-player.ui.debug.map +4 -4
  19. package/dist/shaka-player.ui.externs.js +77 -26
  20. package/dist/shaka-player.ui.js +1649 -1617
  21. package/dist/shaka-player.ui.map +4 -4
  22. package/externs/mediasession.js +10 -1
  23. package/externs/shaka/ads.js +79 -2
  24. package/externs/shaka/manifest.js +6 -1
  25. package/externs/shaka/manifest_parser.js +6 -2
  26. package/externs/shaka/net.js +23 -2
  27. package/externs/shaka/player.js +414 -103
  28. package/externs/shaka/text.js +2 -1
  29. package/lib/abr/simple_abr_manager.js +5 -0
  30. package/lib/ads/ad_manager.js +45 -1
  31. package/lib/ads/ad_utils.js +124 -1
  32. package/lib/ads/interstitial_ad.js +9 -2
  33. package/lib/ads/interstitial_ad_manager.js +216 -101
  34. package/lib/ads/media_tailor_ad.js +1 -39
  35. package/lib/ads/media_tailor_ad_manager.js +32 -16
  36. package/lib/cea/cea608_data_channel.js +40 -8
  37. package/lib/cea/cea608_memory.js +28 -0
  38. package/lib/dash/dash_parser.js +162 -25
  39. package/lib/dash/segment_base.js +3 -2
  40. package/lib/dash/segment_list.js +6 -3
  41. package/lib/dash/segment_template.js +104 -20
  42. package/lib/hls/hls_parser.js +319 -215
  43. package/lib/media/drm_engine.js +11 -3
  44. package/lib/media/media_source_engine.js +0 -8
  45. package/lib/media/preload_manager.js +16 -4
  46. package/lib/media/segment_reference.js +3 -0
  47. package/lib/media/segment_utils.js +8 -1
  48. package/lib/media/streaming_engine.js +10 -6
  49. package/lib/mss/mss_parser.js +14 -2
  50. package/lib/net/http_fetch_plugin.js +12 -5
  51. package/lib/net/http_xhr_plugin.js +7 -3
  52. package/lib/net/networking_engine.js +21 -1
  53. package/lib/offline/manifest_converter.js +1 -0
  54. package/lib/offline/storage.js +1 -0
  55. package/lib/player.js +336 -121
  56. package/lib/text/cue.js +373 -0
  57. package/lib/text/mp4_ttml_parser.js +51 -1
  58. package/lib/text/mp4_vtt_parser.js +2 -5
  59. package/lib/text/sbv_text_parser.js +2 -31
  60. package/lib/text/srt_text_parser.js +1 -1
  61. package/lib/text/text_engine.js +1 -1
  62. package/lib/text/text_utils.js +1 -0
  63. package/lib/text/ttml_text_parser.js +14 -4
  64. package/lib/text/ui_text_displayer.js +0 -4
  65. package/lib/text/vtt_text_parser.js +10 -415
  66. package/lib/util/abortable_operation.js +8 -0
  67. package/lib/util/cmcd_manager.js +38 -0
  68. package/lib/util/error.js +16 -0
  69. package/lib/util/fake_event.js +1 -0
  70. package/lib/util/manifest_parser_utils.js +12 -3
  71. package/lib/util/player_configuration.js +4 -2
  72. package/lib/util/stream_utils.js +57 -2
  73. package/lib/util/tXml.js +2 -1
  74. package/lib/util/text_parser.js +46 -0
  75. package/lib/util/ts_parser.js +32 -3
  76. package/package.json +1 -1
  77. package/shaka-player.uncompiled.js +1 -1
  78. package/ui/chapter_selection.js +179 -0
  79. package/ui/controls.js +151 -0
  80. package/ui/enums.js +1 -0
  81. package/ui/externs/ui.js +7 -1
  82. package/ui/less/containers.less +3 -4
  83. package/ui/less/overflow_menu.less +5 -0
  84. package/ui/less/tooltip.less +1 -1
  85. package/ui/locales/en.json +1 -0
  86. package/ui/locales/es.json +1 -0
  87. package/ui/locales/fr.json +1 -0
  88. package/ui/locales/source.json +4 -0
  89. package/ui/seek_bar.js +3 -2
  90. package/ui/ui.js +2 -0
  91. package/lib/polyfill/abort_controller.js +0 -158
package/lib/player.js CHANGED
@@ -38,6 +38,7 @@ goog.require('shaka.net.NetworkingUtils');
38
38
  goog.require('shaka.text.SimpleTextDisplayer');
39
39
  goog.require('shaka.text.StubTextDisplayer');
40
40
  goog.require('shaka.text.TextEngine');
41
+ goog.require('shaka.text.Utils');
41
42
  goog.require('shaka.text.UITextDisplayer');
42
43
  goog.require('shaka.text.WebVttGenerator');
43
44
  goog.require('shaka.util.BufferUtils');
@@ -50,6 +51,7 @@ goog.require('shaka.util.Error');
50
51
  goog.require('shaka.util.EventManager');
51
52
  goog.require('shaka.util.FakeEvent');
52
53
  goog.require('shaka.util.FakeEventTarget');
54
+ goog.require('shaka.util.Functional');
53
55
  goog.require('shaka.util.IDestroyable');
54
56
  goog.require('shaka.util.LanguageUtils');
55
57
  goog.require('shaka.util.ManifestParserUtils');
@@ -118,9 +120,9 @@ goog.requireType('shaka.media.PresentationTimeline');
118
120
  /**
119
121
  * @event shaka.Player.DownloadHeadersReceived
120
122
  * @description Fired when the networking engine has received the headers for
121
- * a download, but before the body has been downloaded.
122
- * If the HTTP plugin being used does not track this information, this event
123
- * will default to being fired when the body is received, instead.
123
+ * a download, but before the body has been downloaded.
124
+ * If the HTTP plugin being used does not track this information, this event
125
+ * will default to being fired when the body is received, instead.
124
126
  * @property {!Object.<string, string>} headers
125
127
  * @property {!shaka.extern.Request} request
126
128
  * @property {!shaka.net.NetworkingEngine.RequestType} type
@@ -173,8 +175,8 @@ goog.requireType('shaka.media.PresentationTimeline');
173
175
  /**
174
176
  * @event shaka.Player.MediaQualityChangedEvent
175
177
  * @description Fired when the media quality changes at the playhead.
176
- * That may be caused by an adaptation change or a DASH period transition.
177
- * Separate events are emitted for audio and video contentTypes.
178
+ * That may be caused by an adaptation change or a DASH period transition.
179
+ * Separate events are emitted for audio and video contentTypes.
178
180
  * @property {string} type
179
181
  * 'mediaqualitychanged'
180
182
  * @property {shaka.extern.MediaQualityInfo} mediaQuality
@@ -184,10 +186,19 @@ goog.requireType('shaka.media.PresentationTimeline');
184
186
  * @exportDoc
185
187
  */
186
188
 
189
+ /**
190
+ * @event shaka.Player.MediaSourceRecoveredEvent
191
+ * @description Fired when MediaSource has been successfully recovered
192
+ * after occurrence of video error.
193
+ * @property {string} type
194
+ * 'mediasourcerecovered'
195
+ * @exportDoc
196
+ */
197
+
187
198
  /**
188
199
  * @event shaka.Player.AudioTrackChangedEvent
189
200
  * @description Fired when the audio track changes at the playhead.
190
- * That may be caused by a user requesting to chang audio tracks.
201
+ * That may be caused by a user requesting to chang audio tracks.
191
202
  * @property {string} type
192
203
  * 'audiotrackchanged'
193
204
  * @property {shaka.extern.MediaQualityInfo} mediaQuality
@@ -243,6 +254,8 @@ goog.requireType('shaka.media.PresentationTimeline');
243
254
  /**
244
255
  * @event shaka.Player.TextTrackVisibilityEvent
245
256
  * @description Fired when text track visibility changes.
257
+ * An app may want to look at <code>getStats()</code> or
258
+ * <code>getVariantTracks()</code> to see what happened.
246
259
  * @property {string} type
247
260
  * 'texttrackvisibility'
248
261
  * @exportDoc
@@ -253,6 +266,8 @@ goog.requireType('shaka.media.PresentationTimeline');
253
266
  * @event shaka.Player.TracksChangedEvent
254
267
  * @description Fired when the list of tracks changes. For example, this will
255
268
  * happen when new tracks are added/removed or when track restrictions change.
269
+ * An app may want to look at <code>getVariantTracks()</code> to see what
270
+ * happened.
256
271
  * @property {string} type
257
272
  * 'trackschanged'
258
273
  * @exportDoc
@@ -276,9 +291,11 @@ goog.requireType('shaka.media.PresentationTimeline');
276
291
  /**
277
292
  * @event shaka.Player.VariantChangedEvent
278
293
  * @description Fired when a call from the application caused a variant change.
279
- * Can be triggered by calls to <code>selectVariantTrack()</code> or
280
- * <code>selectAudioLanguage()</code>. Does not fire when an automatic
281
- * adaptation causes a variant change.
294
+ * Can be triggered by calls to <code>selectVariantTrack()</code> or
295
+ * <code>selectAudioLanguage()</code>. Does not fire when an automatic
296
+ * adaptation causes a variant change.
297
+ * An app may want to look at <code>getStats()</code> or
298
+ * <code>getVariantTracks()</code> to see what happened.
282
299
  * @property {string} type
283
300
  * 'variantchanged'
284
301
  * @property {shaka.extern.Track} oldTrack
@@ -290,8 +307,10 @@ goog.requireType('shaka.media.PresentationTimeline');
290
307
  /**
291
308
  * @event shaka.Player.TextChangedEvent
292
309
  * @description Fired when a call from the application caused a text stream
293
- * change. Can be triggered by calls to <code>selectTextTrack()</code> or
294
- * <code>selectTextLanguage()</code>.
310
+ * change. Can be triggered by calls to <code>selectTextTrack()</code> or
311
+ * <code>selectTextLanguage()</code>.
312
+ * An app may want to look at <code>getStats()</code> or
313
+ * <code>getTextTracks()</code> to see what happened.
295
314
  * @property {string} type
296
315
  * 'textchanged'
297
316
  * @exportDoc
@@ -370,8 +389,8 @@ goog.requireType('shaka.media.PresentationTimeline');
370
389
  * @property {string} type
371
390
  * 'abrstatuschanged'
372
391
  * @property {boolean} newStatus
373
- * The new status of the application. True for 'is enabled' and
374
- * false otherwise.
392
+ * The new status of the application. True for 'is enabled' and
393
+ * false otherwise.
375
394
  * @exportDoc
376
395
  */
377
396
 
@@ -425,7 +444,8 @@ goog.requireType('shaka.media.PresentationTimeline');
425
444
  /**
426
445
  * @event shaka.Player.StallDetectedEvent
427
446
  * @description Fired when a stall in playback is detected by the StallDetector.
428
- * Not all stalls are caused by gaps in the buffered ranges.
447
+ * Not all stalls are caused by gaps in the buffered ranges.
448
+ * An app may want to look at <code>getStats()</code> to see what happened.
429
449
  * @property {string} type
430
450
  * 'stalldetected'
431
451
  * @exportDoc
@@ -435,7 +455,8 @@ goog.requireType('shaka.media.PresentationTimeline');
435
455
  /**
436
456
  * @event shaka.Player.GapJumpedEvent
437
457
  * @description Fired when the GapJumpingController jumps over a gap in the
438
- * buffered ranges.
458
+ * buffered ranges.
459
+ * An app may want to look at <code>getStats()</code> to see what happened.
439
460
  * @property {string} type
440
461
  * 'gapjumped'
441
462
  * @exportDoc
@@ -475,7 +496,7 @@ goog.requireType('shaka.media.PresentationTimeline');
475
496
  /**
476
497
  * @event shaka.Player.FirstQuartile
477
498
  * @description Fires when the content playhead crosses first quartile.
478
- * Only for VoD.
499
+ * Only for VoD.
479
500
  * @property {string} type
480
501
  * 'firstquartile'
481
502
  * @exportDoc
@@ -485,7 +506,7 @@ goog.requireType('shaka.media.PresentationTimeline');
485
506
  /**
486
507
  * @event shaka.Player.Midpoint
487
508
  * @description Fires when the content playhead crosses midpoint.
488
- * Only for VoD.
509
+ * Only for VoD.
489
510
  * @property {string} type
490
511
  * 'midpoint'
491
512
  * @exportDoc
@@ -495,7 +516,7 @@ goog.requireType('shaka.media.PresentationTimeline');
495
516
  /**
496
517
  * @event shaka.Player.ThirdQuartile
497
518
  * @description Fires when the content playhead crosses third quartile.
498
- * Only for VoD.
519
+ * Only for VoD.
499
520
  * @property {string} type
500
521
  * 'thirdquartile'
501
522
  * @exportDoc
@@ -505,7 +526,7 @@ goog.requireType('shaka.media.PresentationTimeline');
505
526
  /**
506
527
  * @event shaka.Player.Complete
507
528
  * @description Fires when the content completes playing.
508
- * Only for VoD.
529
+ * Only for VoD.
509
530
  * @property {string} type
510
531
  * 'complete'
511
532
  * @exportDoc
@@ -547,10 +568,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
547
568
  * When provided, the player will attach to <code>mediaElement</code>,
548
569
  * similar to calling <code>attach</code>. When not provided, the player
549
570
  * will remain detached.
571
+ * @param {HTMLElement=} videoContainer
572
+ * The videoContainer to construct UITextDisplayer
550
573
  * @param {function(shaka.Player)=} dependencyInjector Optional callback
551
574
  * which is called to inject mocks into the Player. Used for testing.
552
575
  */
553
- constructor(mediaElement, dependencyInjector) {
576
+ constructor(mediaElement, videoContainer = null, dependencyInjector) {
554
577
  super();
555
578
 
556
579
  /** @private {shaka.Player.LoadMode} */
@@ -560,7 +583,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
560
583
  this.video_ = null;
561
584
 
562
585
  /** @private {HTMLElement} */
563
- this.videoContainer_ = null;
586
+ this.videoContainer_ = videoContainer;
564
587
 
565
588
  /**
566
589
  * Since we may not always have a text displayer created (e.g. before |load|
@@ -790,6 +813,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
790
813
  this.networkingEngine_ = this.createNetworkingEngine();
791
814
  this.networkingEngine_.setForceHTTP(this.config_.streaming.forceHTTP);
792
815
  this.networkingEngine_.setForceHTTPS(this.config_.streaming.forceHTTPS);
816
+ this.networkingEngine_.setMinBytesForProgressEvents(
817
+ this.config_.streaming.minBytesForProgressEvents);
793
818
 
794
819
  /** @private {shaka.extern.IAdManager} */
795
820
  this.adManager_ = null;
@@ -887,6 +912,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
887
912
  'use the attach method instead.');
888
913
  this.attach(mediaElement, /* initializeMediaSource= */ true);
889
914
  }
915
+
916
+ /** @private {?shaka.extern.TextDisplayer} */
917
+ this.textDisplayer_ = null;
890
918
  }
891
919
 
892
920
  /**
@@ -1410,6 +1438,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1410
1438
  this.cmsdManager_.reset();
1411
1439
  }
1412
1440
 
1441
+ if (this.textDisplayer_) {
1442
+ await this.textDisplayer_.destroy();
1443
+ this.textDisplayer_ = null;
1444
+ }
1445
+
1413
1446
  if (this.video_) {
1414
1447
  // Remove all track nodes
1415
1448
  shaka.util.Dom.removeAllChildren(this.video_);
@@ -1623,6 +1656,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1623
1656
  preloadManager = await this.preloadInner_(
1624
1657
  assetUri, startTime, mimeType, /* standardLoad= */ true);
1625
1658
  if (preloadManager) {
1659
+ preloadManager.markIsLoad();
1626
1660
  preloadManager.setEventHandoffTarget(this);
1627
1661
  this.stats_ = preloadManager.getStats();
1628
1662
  preloadManager.start();
@@ -1967,6 +2001,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1967
2001
  this.createdPreloadManagers_.push(preloadManager);
1968
2002
  return preloadManager;
1969
2003
  });
2004
+ } else {
2005
+ preloadManagerPromise = preloadManagerPromise.then((preloadManager) => {
2006
+ preloadManager.markIsLoad();
2007
+ return preloadManager;
2008
+ });
1970
2009
  }
1971
2010
  return preloadManagerPromise;
1972
2011
  }
@@ -2088,7 +2127,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2088
2127
  let metadataType = type;
2089
2128
  if (type == 'com.apple.hls.interstitial') {
2090
2129
  metadataType = 'com.apple.quicktime.HLS';
2091
- /** @type {shaka.extern.Interstitial} */
2130
+ /** @type {shaka.extern.HLSInterstitial} */
2092
2131
  const interstitial = {
2093
2132
  startTime,
2094
2133
  endTime,
@@ -2096,11 +2135,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2096
2135
  };
2097
2136
  if (this.adManager_) {
2098
2137
  goog.asserts.assert(this.video_, 'Must have video');
2099
- this.adManager_.onInterstitialMetadata(
2138
+ this.adManager_.onHLSInterstitialMetadata(
2100
2139
  this, this.video_, interstitial);
2101
2140
  }
2102
2141
  }
2103
2142
  for (const payload of values) {
2143
+ if (payload.name == 'ID') {
2144
+ continue;
2145
+ }
2104
2146
  preloadManager.addQueuedOperation(false, () => {
2105
2147
  this.dispatchMetadataEvent_(
2106
2148
  startTime, endTime, metadataType, payload);
@@ -2109,6 +2151,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2109
2151
  },
2110
2152
  disableStream: (stream) => this.disableStream(
2111
2153
  stream, this.config_.streaming.maxDisabledTime),
2154
+ addFont: (name, url) => this.addFont(name, url),
2112
2155
  };
2113
2156
  const regionTimeline =
2114
2157
  new shaka.media.RegionTimeline(() => this.seekRange());
@@ -2122,6 +2165,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2122
2165
  preloadManager.addQueuedOperation(false, () => {
2123
2166
  if (this.adManager_) {
2124
2167
  this.adManager_.onDashTimedMetadata(region);
2168
+ goog.asserts.assert(this.video_, 'Must have video');
2169
+ this.adManager_.onDASHInterstitialMetadata(
2170
+ this, this.video_, region);
2125
2171
  }
2126
2172
  });
2127
2173
  });
@@ -2304,6 +2350,30 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2304
2350
  return false;
2305
2351
  }
2306
2352
 
2353
+ /**
2354
+ * @private
2355
+ */
2356
+ createTextDisplayer_() {
2357
+ // When changing text visibility we need to update both the text displayer
2358
+ // and streaming engine because we don't always stream text. To ensure
2359
+ // that the text displayer and streaming engine are always in sync, wait
2360
+ // until they are both initialized before setting the initial value.
2361
+ const textDisplayerFactory = this.config_.textDisplayFactory;
2362
+ if (textDisplayerFactory === this.lastTextFactory_) {
2363
+ return;
2364
+ }
2365
+ this.textDisplayer_ = textDisplayerFactory();
2366
+ if (this.textDisplayer_.configure) {
2367
+ this.textDisplayer_.configure(this.config_.textDisplayer);
2368
+ } else {
2369
+ shaka.Deprecate.deprecateFeature(5,
2370
+ 'Text displayer w/ configure',
2371
+ 'Text displayer should have a "configure" method!');
2372
+ }
2373
+ this.lastTextFactory_ = textDisplayerFactory;
2374
+ this.textDisplayer_.setTextVisibility(this.isTextVisible_);
2375
+ }
2376
+
2307
2377
  /**
2308
2378
  * Initializes the media source engine.
2309
2379
  *
@@ -2324,24 +2394,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2324
2394
 
2325
2395
  this.makeStateChangeEvent_('media-source');
2326
2396
 
2327
- // When changing text visibility we need to update both the text displayer
2328
- // and streaming engine because we don't always stream text. To ensure
2329
- // that the text displayer and streaming engine are always in sync, wait
2330
- // until they are both initialized before setting the initial value.
2331
- const textDisplayerFactory = this.config_.textDisplayFactory;
2332
- const textDisplayer = textDisplayerFactory();
2333
- if (textDisplayer.configure) {
2334
- textDisplayer.configure(this.config_.textDisplayer);
2335
- } else {
2336
- shaka.Deprecate.deprecateFeature(5,
2337
- 'Text displayer w/ configure',
2338
- 'Text displayer should have a "configure" method!');
2339
- }
2340
- this.lastTextFactory_ = textDisplayerFactory;
2341
-
2397
+ this.createTextDisplayer_();
2398
+ goog.asserts.assert(this.textDisplayer_,
2399
+ 'Text displayer should be created already');
2342
2400
  const mediaSourceEngine = this.createMediaSourceEngine(
2343
2401
  this.video_,
2344
- textDisplayer,
2402
+ this.textDisplayer_,
2345
2403
  {
2346
2404
  getKeySystem: () => this.keySystem(),
2347
2405
  onMetadata: (metadata, offset, endTime) => {
@@ -2487,6 +2545,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2487
2545
  'https://bit.ly/3clctcj for details.');
2488
2546
  }
2489
2547
 
2548
+ if (this.cmcdManager_) {
2549
+ this.cmcdManager_.setLowLatency(
2550
+ this.manifest_.isLowLatency && this.config_.streaming.lowLatencyMode);
2551
+ }
2552
+
2490
2553
  shaka.Player.applyPlayRange_(this.manifest_.presentationTimeline,
2491
2554
  this.config_.playRangeStart,
2492
2555
  this.config_.playRangeEnd);
@@ -2509,7 +2572,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2509
2572
  };
2510
2573
 
2511
2574
  if (!this.config_.streaming.startAtSegmentBoundary) {
2512
- setupPlayhead(this.startTime_);
2575
+ let startTime = this.startTime_;
2576
+ if (startTime == null && this.manifest_.startTime) {
2577
+ startTime = this.manifest_.startTime;
2578
+ }
2579
+ setupPlayhead(startTime);
2513
2580
  }
2514
2581
 
2515
2582
  // Now we can switch to the initial variant.
@@ -2522,6 +2589,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2522
2589
  if (this.config_.streaming.startAtSegmentBoundary) {
2523
2590
  const timeline = this.manifest_.presentationTimeline;
2524
2591
  let initialTime = this.startTime_ || this.video_.currentTime;
2592
+ if (this.startTime_ == null && this.manifest_.startTime) {
2593
+ initialTime = this.manifest_.startTime;
2594
+ }
2525
2595
  const seekRangeStart = timeline.getSeekRangeStart();
2526
2596
  const seekRangeEnd = timeline.getSeekRangeEnd();
2527
2597
  if (initialTime < seekRangeStart) {
@@ -2841,6 +2911,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2841
2911
  }
2842
2912
 
2843
2913
  if (mediaElement.textTracks) {
2914
+ this.createTextDisplayer_();
2844
2915
  this.loadEventManager_.listen(
2845
2916
  mediaElement.textTracks, 'addtrack', (e) => {
2846
2917
  const trackEvent = /** @type {!TrackEvent} */(e);
@@ -2871,6 +2942,22 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2871
2942
  this.loadEventManager_.listen(
2872
2943
  mediaElement.textTracks, 'change',
2873
2944
  () => this.onTracksChanged_());
2945
+ this.loadEventManager_.listen(
2946
+ mediaElement, 'enterpictureinpicture', () => {
2947
+ const track = this.getFilteredTextTracks_()
2948
+ .find((t) => t.mode !== 'disabled');
2949
+ if (track) {
2950
+ track.mode = 'showing';
2951
+ }
2952
+ });
2953
+ this.loadEventManager_.listen(
2954
+ mediaElement, 'leavepictureinpicture', () => {
2955
+ const track = this.getFilteredTextTracks_()
2956
+ .find((t) => t.mode !== 'disabled');
2957
+ if (track) {
2958
+ track.mode = 'hidden';
2959
+ }
2960
+ });
2874
2961
  }
2875
2962
 
2876
2963
  // By setting |src| we are done "loading" with src=. We don't need to set
@@ -2955,6 +3042,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2955
3042
  });
2956
3043
  } else if (textTracks.length > 0) {
2957
3044
  this.isTextVisible_ = true;
3045
+ this.textDisplayer_.setTextVisibility(true);
2958
3046
  }
2959
3047
 
2960
3048
  // If we have moved on to another piece of content while waiting for
@@ -2962,6 +3050,20 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2962
3050
  if (unloaded) {
2963
3051
  return;
2964
3052
  }
3053
+ let enabledNativeTrack = false;
3054
+ for (const track of textTracks) {
3055
+ if (track.mode !== 'disabled') {
3056
+ if (!enabledNativeTrack) {
3057
+ this.enableNativeTrack_(track);
3058
+ enabledNativeTrack = true;
3059
+ } else {
3060
+ track.mode = 'disabled';
3061
+ shaka.log.alwaysWarn(
3062
+ 'Found more than one enabled text track, disabling it',
3063
+ track);
3064
+ }
3065
+ }
3066
+ }
2965
3067
 
2966
3068
  this.setupPreferredTextOnSrc_();
2967
3069
  });
@@ -3083,55 +3185,58 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3083
3185
  // Hidden mode is required for the cuechange event to launch correctly
3084
3186
  track.mode = 'hidden';
3085
3187
  this.loadEventManager_.listen(track, 'cuechange', () => {
3086
- if (!track.activeCues) {
3087
- return;
3088
- }
3188
+ if (track.activeCues) {
3189
+ for (const cue of track.activeCues) {
3190
+ this.dispatchMetadataEvent_(cue.startTime, cue.endTime,
3191
+ cue.type, cue.value);
3089
3192
 
3090
- /** @type {!Array.<shaka.extern.Interstitial>} */
3091
- const interstitials = [];
3092
-
3093
- for (const cue of track.activeCues) {
3094
- this.dispatchMetadataEvent_(cue.startTime, cue.endTime,
3095
- cue.type, cue.value);
3096
-
3097
- if (this.adManager_) {
3098
- this.adManager_.onCueMetadataChange(cue.value);
3099
- }
3100
- if (cue.type == 'com.apple.quicktime.HLS' && cue.startTime != null) {
3101
- let interstitial = interstitials.find((i) => {
3102
- return i.startTime == cue.startTime && i.endTime == cue.endTime;
3103
- });
3104
- if (!interstitial) {
3105
- interstitial = /** @type {shaka.extern.Interstitial} */ ({
3106
- startTime: cue.startTime,
3107
- endTime: cue.endTime,
3108
- values: [],
3109
- });
3110
- interstitials.push(interstitial);
3193
+ if (this.adManager_) {
3194
+ this.adManager_.onCueMetadataChange(cue.value);
3111
3195
  }
3112
- interstitial.values.push(cue.value);
3113
3196
  }
3114
3197
  }
3115
- for (const interstitial of interstitials) {
3116
- const isValidInterstitial = interstitial.values.some((value) => {
3117
- return value.key == 'X-ASSET-URI' || value.key == 'X-ASSET-LIST';
3118
- });
3119
- if (!isValidInterstitial) {
3120
- continue;
3198
+ if (track.cues) {
3199
+ /** @type {!Array.<shaka.extern.HLSInterstitial>} */
3200
+ const interstitials = [];
3201
+
3202
+ for (const cue of track.cues) {
3203
+ if (cue.type == 'com.apple.quicktime.HLS' && cue.startTime != null) {
3204
+ let interstitial = interstitials.find((i) => {
3205
+ return i.startTime == cue.startTime && i.endTime == cue.endTime;
3206
+ });
3207
+ if (!interstitial) {
3208
+ interstitial = /** @type {shaka.extern.HLSInterstitial} */ ({
3209
+ startTime: cue.startTime,
3210
+ endTime: cue.endTime,
3211
+ values: [],
3212
+ });
3213
+ interstitials.push(interstitial);
3214
+ }
3215
+ interstitial.values.push(cue.value);
3216
+ }
3121
3217
  }
3122
- if (this.adManager_) {
3123
- // It seems that CUE is natively omitted, by default we use CUE=ONCE
3124
- // to avoid repeating them.
3125
- interstitial.values.push({
3126
- key: 'CUE',
3127
- description: '',
3128
- data: 'ONCE',
3129
- mimeType: null,
3130
- pictureType: null,
3218
+ for (const interstitial of interstitials) {
3219
+ const isValidInterstitial = interstitial.values.some((value) => {
3220
+ return value.key == 'X-ASSET-URI' || value.key == 'X-ASSET-LIST';
3131
3221
  });
3132
- goog.asserts.assert(this.video_, 'Must have video');
3133
- this.adManager_.onInterstitialMetadata(
3134
- this, this.video_, interstitial);
3222
+ if (!isValidInterstitial) {
3223
+ continue;
3224
+ }
3225
+ if (this.adManager_) {
3226
+ const isPreRoll = interstitial.startTime == 0;
3227
+ // It seems that CUE is natively omitted, by default we use CUE=ONCE
3228
+ // to avoid repeating them.
3229
+ interstitial.values.push({
3230
+ key: 'CUE',
3231
+ description: '',
3232
+ data: isPreRoll ? 'ONCE,PRE' : 'ONCE',
3233
+ mimeType: null,
3234
+ pictureType: null,
3235
+ });
3236
+ goog.asserts.assert(this.video_, 'Must have video');
3237
+ this.adManager_.onHLSInterstitialMetadata(
3238
+ this, this.video_, interstitial);
3239
+ }
3135
3240
  }
3136
3241
  }
3137
3242
  });
@@ -3836,6 +3941,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3836
3941
  delete config['mediaSource']['sourceBufferExtraFeatures'];
3837
3942
  }
3838
3943
 
3944
+ // Deprecate 'manifest.hls.useSafariBehaviorForLive' configuration.
3945
+ if (config['manifest'] && config['manifest']['hls'] &&
3946
+ 'useSafariBehaviorForLive' in config['manifest']['hls']) {
3947
+ shaka.Deprecate.deprecateFeature(5,
3948
+ 'manifest.hls.useSafariBehaviorForLive configuration',
3949
+ 'Please Use liveSync config to keep on live Edge instead.');
3950
+ delete config['manifest']['hls']['useSafariBehaviorForLive'];
3951
+ }
3952
+
3839
3953
  // If lowLatencyMode is enabled, and inaccurateManifestTolerance and
3840
3954
  // rebufferingGoal and segmentPrefetchLimit and baseDelay and
3841
3955
  // autoCorrectDrift and maxDisabledTime are not specified, set
@@ -3950,6 +4064,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3950
4064
  if (this.networkingEngine_) {
3951
4065
  this.networkingEngine_.setForceHTTP(this.config_.streaming.forceHTTP);
3952
4066
  this.networkingEngine_.setForceHTTPS(this.config_.streaming.forceHTTPS);
4067
+ this.networkingEngine_.setMinBytesForProgressEvents(
4068
+ this.config_.streaming.minBytesForProgressEvents);
3953
4069
  }
3954
4070
 
3955
4071
  if (this.mediaSourceEngine_) {
@@ -3957,18 +4073,25 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3957
4073
  const {segmentRelativeVttTiming} = this.config_.manifest;
3958
4074
  this.mediaSourceEngine_.setSegmentRelativeVttTiming(
3959
4075
  segmentRelativeVttTiming);
4076
+ }
3960
4077
 
4078
+ if (this.textDisplayer_) {
3961
4079
  const textDisplayerFactory = this.config_.textDisplayFactory;
3962
4080
  if (this.lastTextFactory_ != textDisplayerFactory) {
3963
- const displayer = textDisplayerFactory();
3964
- if (displayer.configure) {
3965
- displayer.configure(this.config_.textDisplayer);
4081
+ const oldDisplayer = this.textDisplayer_;
4082
+ this.textDisplayer_ = textDisplayerFactory();
4083
+ if (this.textDisplayer_.configure) {
4084
+ this.textDisplayer_.configure(this.config_.textDisplayer);
3966
4085
  } else {
3967
4086
  shaka.Deprecate.deprecateFeature(5,
3968
4087
  'Text displayer w/ configure',
3969
4088
  'Text displayer should have a "configure" method!');
3970
4089
  }
3971
- this.mediaSourceEngine_.setTextDisplayer(displayer);
4090
+ this.textDisplayer_.setTextVisibility(oldDisplayer.isTextVisible());
4091
+ oldDisplayer.destroy();
4092
+ if (this.mediaSourceEngine_) {
4093
+ this.mediaSourceEngine_.setTextDisplayer(this.textDisplayer_);
4094
+ }
3972
4095
  this.lastTextFactory_ = textDisplayerFactory;
3973
4096
 
3974
4097
  if (this.streamingEngine_) {
@@ -3976,12 +4099,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3976
4099
  this.streamingEngine_.reloadTextStream();
3977
4100
  }
3978
4101
  } else {
3979
- const displayer = this.mediaSourceEngine_.getTextDisplayer();
3980
- if (displayer.configure) {
3981
- displayer.configure(this.config_.textDisplayer);
4102
+ if (this.textDisplayer_.configure) {
4103
+ this.textDisplayer_.configure(this.config_.textDisplayer);
3982
4104
  }
3983
4105
  }
3984
4106
  }
4107
+
3985
4108
  if (this.abrManager_) {
3986
4109
  this.abrManager_.configure(this.config_.abr);
3987
4110
  // Simply enable/disable ABR with each call, since multiple calls to these
@@ -4829,20 +4952,42 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4829
4952
  this.currentTextLanguage_ = stream.language;
4830
4953
  } else if (this.video_ && this.video_.src && this.video_.textTracks) {
4831
4954
  const textTracks = this.getFilteredTextTracks_();
4832
- for (const textTrack of textTracks) {
4833
- if (shaka.util.StreamUtils.html5TrackId(textTrack) == track.id) {
4834
- // Leave the track in 'hidden' if it's selected but not showing.
4835
- textTrack.mode = this.isTextVisible_ ? 'showing' : 'hidden';
4836
- } else {
4837
- // Safari allows multiple text tracks to have mode == 'showing', so be
4838
- // explicit in resetting the others.
4839
- textTrack.mode = 'disabled';
4955
+ const oldTrack = textTracks.find((textTrack) =>
4956
+ textTrack.mode !== 'disabled');
4957
+ const newTrack = textTracks.find((textTrack) =>
4958
+ shaka.util.StreamUtils.html5TrackId(textTrack) === track.id);
4959
+ if (oldTrack !== newTrack) {
4960
+ if (oldTrack) {
4961
+ oldTrack.mode = 'disabled';
4962
+ this.loadEventManager_.unlisten(oldTrack, 'cuechange');
4963
+ this.textDisplayer_.remove(0, Infinity);
4964
+ }
4965
+ if (newTrack) {
4966
+ this.enableNativeTrack_(newTrack);
4840
4967
  }
4841
4968
  }
4842
4969
  this.onTextChanged_();
4843
4970
  }
4844
4971
  }
4845
4972
 
4973
+ /**
4974
+ * @param {!TextTrack} track
4975
+ * @private
4976
+ */
4977
+ enableNativeTrack_(track) {
4978
+ this.loadEventManager_.listen(track, 'cuechange', () => {
4979
+ // Always remove cues from the past to avoid memory grow.
4980
+ const removeEnd = Math.max(0,
4981
+ this.video_.currentTime - this.config_.streaming.bufferBehind);
4982
+ this.textDisplayer_.remove(0, removeEnd);
4983
+ const cues = Array.from(track.activeCues || [])
4984
+ .map(shaka.text.Utils.mapNativeCueToShakaCue)
4985
+ .filter(shaka.util.Functional.isNotNull);
4986
+ this.textDisplayer_.append(cues);
4987
+ });
4988
+ track.mode = document.pictureInPictureElement ? 'showing' : 'hidden';
4989
+ }
4990
+
4846
4991
  /**
4847
4992
  * Select a specific variant track to play. <code>track</code> should come
4848
4993
  * from a call to <code>getVariantTracks</code>. If <code>track</code> cannot
@@ -5159,20 +5304,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5159
5304
  */
5160
5305
  isTextTrackVisible() {
5161
5306
  const expected = this.isTextVisible_;
5162
-
5163
- if (this.mediaSourceEngine_ &&
5164
- this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE) {
5165
- // Make sure our values are still in-sync.
5166
- const actual = this.mediaSourceEngine_.getTextDisplayer().isTextVisible();
5307
+ if (this.textDisplayer_) {
5308
+ const actual = this.textDisplayer_.isTextVisible();
5167
5309
  goog.asserts.assert(
5168
5310
  actual == expected, 'text visibility has fallen out of sync');
5169
5311
 
5170
5312
  // Always return the actual value so that the app has the most accurate
5171
5313
  // information (in the case that the values come out of sync in prod).
5172
5314
  return actual;
5173
- } else if (this.video_ && this.video_.src && this.video_.textTracks) {
5174
- const textTracks = this.getFilteredTextTracks_();
5175
- return textTracks.some((t) => t.mode == 'showing');
5176
5315
  }
5177
5316
 
5178
5317
  return expected;
@@ -5301,8 +5440,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5301
5440
  // Hold of on setting the text visibility until we have all the components
5302
5441
  // we need. This ensures that they stay in-sync.
5303
5442
  if (this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE) {
5304
- this.mediaSourceEngine_.getTextDisplayer()
5305
- .setTextVisibility(newVisibility);
5443
+ this.textDisplayer_.setTextVisibility(newVisibility);
5306
5444
 
5307
5445
  // When the user wants to see captions, we stream captions. When the user
5308
5446
  // doesn't want to see captions, we don't stream captions. This is to
@@ -5332,15 +5470,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5332
5470
  }
5333
5471
  }
5334
5472
  } else if (this.video_ && this.video_.src && this.video_.textTracks) {
5335
- const textTracks = this.getFilteredTextTracks_();
5336
- // Find the active track by looking for one which is not disabled. This
5337
- // is the only way to identify the track which is currently displayed.
5338
- // Set it to 'showing' or 'hidden' based on newVisibility.
5339
- for (const textTrack of textTracks) {
5340
- if (textTrack.mode != 'disabled') {
5341
- textTrack.mode = newVisibility ? 'showing' : 'hidden';
5342
- }
5343
- }
5473
+ this.textDisplayer_.setTextVisibility(newVisibility);
5344
5474
  }
5345
5475
 
5346
5476
  // We need to fire the event after we have updated everything so that
@@ -5795,7 +5925,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5795
5925
  vttOffset: 0,
5796
5926
  };
5797
5927
  const data = shaka.util.BufferUtils.toUint8(buffer);
5798
- const cues = TextParser.parseMedia(data, time, uri);
5928
+ const cues = TextParser.parseMedia(data, time, uri, /* images= */ []);
5799
5929
 
5800
5930
  const references = [];
5801
5931
  for (const cue of cues) {
@@ -6062,7 +6192,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6062
6192
  vttOffset: 0,
6063
6193
  };
6064
6194
  const data = shaka.util.BufferUtils.toUint8(buffer);
6065
- const cues = obj.parseMedia(data, time, /* uri= */ null);
6195
+ const cues = obj.parseMedia(
6196
+ data, time, /* uri= */ null, /* images= */ []);
6066
6197
  return shaka.text.WebVttGenerator.convert(cues, adCuePoints);
6067
6198
  }
6068
6199
  throw new shaka.util.Error(
@@ -6141,6 +6272,50 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6141
6272
  return this.parserFactory_;
6142
6273
  }
6143
6274
 
6275
+ /**
6276
+ * Gets information about the currently fetched video, audio, and text.
6277
+ * In the case of a multi-codec or multi-mimeType manifest, this can let you
6278
+ * determine the exact codecs and mimeTypes being fetched at the moment.
6279
+ *
6280
+ * @return {!shaka.extern.PlaybackInfo}
6281
+ */
6282
+ getFetchedPlaybackInfo() {
6283
+ const output = /** @type {!shaka.extern.PlaybackInfo} */ ({
6284
+ 'video': null,
6285
+ 'audio': null,
6286
+ 'text': null,
6287
+ });
6288
+ if (this.loadMode_ != shaka.Player.LoadMode.MEDIA_SOURCE) {
6289
+ return output;
6290
+ }
6291
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
6292
+ const variant = this.streamingEngine_.getCurrentVariant();
6293
+ const textStream = this.streamingEngine_.getCurrentTextStream();
6294
+ const currentTime = this.video_.currentTime;
6295
+ for (const stream of [variant.video, variant.audio, textStream]) {
6296
+ if (!stream || !stream.segmentIndex) {
6297
+ continue;
6298
+ }
6299
+ const position = stream.segmentIndex.find(currentTime);
6300
+ const reference = stream.segmentIndex.get(position);
6301
+ const info = /** @type {!shaka.extern.PlaybackStreamInfo} */ ({
6302
+ 'codecs': reference.codecs || stream.codecs,
6303
+ 'mimeType': reference.mimeType || stream.mimeType,
6304
+ 'bandwidth': reference.bandwidth || stream.bandwidth,
6305
+ });
6306
+ if (stream.type == ContentType.VIDEO) {
6307
+ info['width'] = stream.width;
6308
+ info['height'] = stream.height;
6309
+ output['video'] = info;
6310
+ } else if (stream.type == ContentType.AUDIO) {
6311
+ output['audio'] = info;
6312
+ } else if (stream.type == ContentType.TEXT) {
6313
+ output['text'] = info;
6314
+ }
6315
+ }
6316
+ return output;
6317
+ }
6318
+
6144
6319
  /**
6145
6320
  * @param {shaka.extern.Variant} variant
6146
6321
  * @param {boolean} fromAdaptation
@@ -6953,7 +7128,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6953
7128
  if (this.isTextVisible_) {
6954
7129
  // If the cached value says to show text, then update the text displayer
6955
7130
  // since it defaults to not shown.
6956
- this.mediaSourceEngine_.getTextDisplayer().setTextVisibility(true);
7131
+ this.textDisplayer_.setTextVisibility(true);
6957
7132
  goog.asserts.assert(this.shouldStreamText_(),
6958
7133
  'Should be streaming text');
6959
7134
  }
@@ -7335,6 +7510,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
7335
7510
  try {
7336
7511
  const ret = await this.streamingEngine_.resetMediaSource();
7337
7512
  fireError = !ret;
7513
+ if (ret) {
7514
+ const event = shaka.Player.makeEvent_(
7515
+ shaka.util.FakeEvent.EventName.MediaSourceRecovered);
7516
+ this.dispatchEvent(event);
7517
+ }
7338
7518
  } catch (e) {
7339
7519
  fireError = true;
7340
7520
  }
@@ -7357,6 +7537,41 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
7357
7537
  }
7358
7538
  }
7359
7539
 
7540
+ /**
7541
+ * Load a new font on the page. If the font was already loaded, it does
7542
+ * nothing.
7543
+ *
7544
+ * @param {string} name
7545
+ * @param {string} url
7546
+ * @export
7547
+ */
7548
+ async addFont(name, url) {
7549
+ if ('fonts' in document && 'FontFace' in window ) {
7550
+ await document.fonts.ready;
7551
+ if (!('entries' in document.fonts)) {
7552
+ return;
7553
+ }
7554
+ const fontFaceSetIteratorToArray = (target) => {
7555
+ const iterable = target.entries();
7556
+ const results = [];
7557
+ let iterator = iterable.next();
7558
+ while (iterator.done === false) {
7559
+ results.push(iterator.value);
7560
+ iterator = iterable.next();
7561
+ }
7562
+ return results;
7563
+ };
7564
+ for (const fontFace of fontFaceSetIteratorToArray(document.fonts)) {
7565
+ if (fontFace.family == name && fontFace.display == 'swap') {
7566
+ // Font already loaded.
7567
+ return;
7568
+ }
7569
+ }
7570
+ const fontFace = new FontFace(name, `url(${url})`, {display: 'swap'});
7571
+ document.fonts.add(fontFace);
7572
+ }
7573
+ }
7574
+
7360
7575
  /**
7361
7576
  * When we fire region events, we need to copy the information out of the
7362
7577
  * region to break the connection with the player's internal data. We do the
@@ -7848,7 +8063,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
7848
8063
  * @export
7849
8064
  */
7850
8065
  // eslint-disable-next-line no-useless-concat
7851
- shaka.Player.version = 'v4.10.13' + '-uncompiled'; // x-release-please-version
8066
+ shaka.Player.version = 'v4.11.0' + '-uncompiled'; // x-release-please-version
7852
8067
 
7853
8068
  // Initialize the deprecation system using the version string we just set
7854
8069
  // on the player.