shaka-player 4.7.13 → 4.8.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 (114) hide show
  1. package/AUTHORS +1 -0
  2. package/CONTRIBUTORS +1 -0
  3. package/LICENSE +54 -0
  4. package/README.md +22 -1
  5. package/dist/controls.css +1 -1
  6. package/dist/controls.css.map +1 -1
  7. package/dist/deps.js +34 -23
  8. package/dist/locales.js +44 -34
  9. package/dist/shaka-player.compiled.d.ts +194 -44
  10. package/dist/shaka-player.compiled.debug.d.ts +194 -44
  11. package/dist/shaka-player.compiled.debug.externs.js +474 -337
  12. package/dist/shaka-player.compiled.debug.js +949 -832
  13. package/dist/shaka-player.compiled.debug.map +4 -4
  14. package/dist/shaka-player.compiled.externs.js +474 -337
  15. package/dist/shaka-player.compiled.js +1237 -1171
  16. package/dist/shaka-player.compiled.map +4 -4
  17. package/dist/shaka-player.ui.d.ts +257 -47
  18. package/dist/shaka-player.ui.debug.d.ts +257 -47
  19. package/dist/shaka-player.ui.debug.externs.js +591 -376
  20. package/dist/shaka-player.ui.debug.js +1165 -965
  21. package/dist/shaka-player.ui.debug.map +4 -4
  22. package/dist/shaka-player.ui.externs.js +591 -376
  23. package/dist/shaka-player.ui.js +1521 -1404
  24. package/dist/shaka-player.ui.map +4 -4
  25. package/externs/device_sensor_event.js +18 -0
  26. package/externs/hisense.js +16 -0
  27. package/externs/playstation.js +31 -0
  28. package/externs/shaka/abr_manager.js +3 -1
  29. package/externs/shaka/ads.js +5 -0
  30. package/externs/shaka/codecs.js +13 -0
  31. package/externs/shaka/manifest.js +58 -6
  32. package/externs/shaka/net.js +13 -1
  33. package/externs/shaka/player.js +184 -13
  34. package/externs/shaka/text.js +19 -0
  35. package/externs/tizen.js +41 -0
  36. package/externs/webos.js +19 -0
  37. package/externs/xbox.js +43 -0
  38. package/lib/abr/simple_abr_manager.js +9 -1
  39. package/lib/ads/ad_manager.js +19 -2
  40. package/lib/ads/client_side_ad_manager.js +5 -3
  41. package/lib/ads/media_tailor_ad_manager.js +9 -0
  42. package/lib/cast/cast_receiver.js +0 -14
  43. package/lib/cast/cast_utils.js +1 -0
  44. package/lib/cea/mp4_cea_parser.js +6 -1
  45. package/lib/dash/content_protection.js +97 -53
  46. package/lib/dash/dash_parser.js +230 -176
  47. package/lib/dash/mpd_utils.js +72 -65
  48. package/lib/dash/segment_base.js +27 -21
  49. package/lib/dash/segment_list.js +18 -16
  50. package/lib/dash/segment_template.js +26 -21
  51. package/lib/hls/hls_parser.js +358 -164
  52. package/lib/media/buffering_observer.js +25 -1
  53. package/lib/media/drm_engine.js +91 -71
  54. package/lib/media/manifest_filterer.js +220 -0
  55. package/lib/media/media_source_engine.js +34 -5
  56. package/lib/media/preload_manager.js +827 -0
  57. package/lib/media/presentation_timeline.js +11 -0
  58. package/lib/media/segment_index.js +40 -30
  59. package/lib/media/segment_prefetch.js +140 -43
  60. package/lib/media/segment_reference.js +31 -8
  61. package/lib/media/segment_utils.js +208 -25
  62. package/lib/media/streaming_engine.js +319 -63
  63. package/lib/mss/content_protection.js +31 -25
  64. package/lib/mss/mss_parser.js +74 -55
  65. package/lib/net/networking_engine.js +49 -4
  66. package/lib/offline/storage.js +3 -3
  67. package/lib/player.js +882 -673
  68. package/lib/polyfill/media_capabilities.js +16 -1
  69. package/lib/text/simple_text_displayer.js +9 -0
  70. package/lib/text/stub_text_displayer.js +7 -0
  71. package/lib/text/text_engine.js +16 -3
  72. package/lib/text/ttml_text_parser.js +116 -114
  73. package/lib/text/ui_text_displayer.js +28 -11
  74. package/lib/text/vtt_text_parser.js +54 -115
  75. package/lib/transmuxer/opus.js +91 -0
  76. package/lib/transmuxer/ts_transmuxer.js +115 -0
  77. package/lib/util/cmcd_manager.js +152 -3
  78. package/lib/util/dom_utils.js +11 -0
  79. package/lib/util/error.js +5 -0
  80. package/lib/util/fake_event.js +2 -0
  81. package/lib/util/manifest_parser_utils.js +97 -3
  82. package/lib/util/mp4_box_parsers.js +346 -17
  83. package/lib/util/mp4_generator.js +23 -0
  84. package/lib/util/periods.js +19 -0
  85. package/lib/util/platform.js +161 -9
  86. package/lib/util/player_configuration.js +61 -3
  87. package/lib/util/stats.js +16 -0
  88. package/lib/util/stream_utils.js +16 -6
  89. package/lib/util/string_utils.js +36 -0
  90. package/lib/util/tXml.js +814 -0
  91. package/lib/util/ts_parser.js +50 -0
  92. package/lib/util/xml_utils.js +10 -338
  93. package/package.json +2 -2
  94. package/shaka-player.uncompiled.js +8 -0
  95. package/ui/controls.js +187 -5
  96. package/ui/enums.js +2 -0
  97. package/ui/externs/ui.js +30 -3
  98. package/ui/gl_matrix/matrix_4x4.js +568 -0
  99. package/ui/gl_matrix/matrix_quaternion.js +45 -0
  100. package/ui/less/containers.less +11 -0
  101. package/ui/locales/en-GB.json +2 -0
  102. package/ui/locales/en.json +2 -0
  103. package/ui/locales/es-419.json +2 -0
  104. package/ui/locales/es.json +2 -0
  105. package/ui/locales/source.json +8 -0
  106. package/ui/recenter_vr.js +126 -0
  107. package/ui/resolution_selection.js +25 -5
  108. package/ui/statistics_button.js +12 -0
  109. package/ui/text_selection.js +1 -1
  110. package/ui/toggle_stereoscopic.js +127 -0
  111. package/ui/ui.js +74 -38
  112. package/ui/vr_manager.js +557 -0
  113. package/ui/vr_utils.js +232 -0
  114. package/ui/vr_webgl.js +697 -0
package/lib/player.js CHANGED
@@ -14,6 +14,7 @@ goog.require('shaka.media.AdaptationSetCriteria');
14
14
  goog.require('shaka.media.BufferingObserver');
15
15
  goog.require('shaka.media.DrmEngine');
16
16
  goog.require('shaka.media.ExampleBasedCriteria');
17
+ goog.require('shaka.media.ManifestFilterer');
17
18
  goog.require('shaka.media.ManifestParser');
18
19
  goog.require('shaka.media.MediaSourceEngine');
19
20
  goog.require('shaka.media.MediaSourcePlayhead');
@@ -22,10 +23,12 @@ goog.require('shaka.media.PlayRateController');
22
23
  goog.require('shaka.media.Playhead');
23
24
  goog.require('shaka.media.PlayheadObserverManager');
24
25
  goog.require('shaka.media.PreferenceBasedCriteria');
26
+ goog.require('shaka.media.PreloadManager');
25
27
  goog.require('shaka.media.QualityObserver');
26
28
  goog.require('shaka.media.RegionObserver');
27
29
  goog.require('shaka.media.RegionTimeline');
28
30
  goog.require('shaka.media.SegmentIndex');
31
+ goog.require('shaka.media.SegmentPrefetch');
29
32
  goog.require('shaka.media.SegmentReference');
30
33
  goog.require('shaka.media.SrcEqualsPlayhead');
31
34
  goog.require('shaka.media.StreamingEngine');
@@ -494,6 +497,29 @@ goog.requireType('shaka.media.PresentationTimeline');
494
497
  */
495
498
 
496
499
 
500
+ /**
501
+ * @event shaka.Player.SpatialVideoInfoEvent
502
+ * @description Fired when the video has spatial video info. If a previous
503
+ * event was fired, this include the new info.
504
+ * @property {string} type
505
+ * 'spatialvideoinfo'
506
+ * @property {shaka.extern.SpatialVideoInfo} detail
507
+ * An object which contains the content of the emsg box.
508
+ * @exportDoc
509
+ */
510
+
511
+
512
+ /**
513
+ * @event shaka.Player.NoSpatialVideoInfoEvent
514
+ * @description Fired when the video no longer has spatial video information.
515
+ * For it to be fired, the shaka.Player.SpatialVideoInfoEvent event must
516
+ * have been previously fired.
517
+ * @property {string} type
518
+ * 'nospatialvideoinfo'
519
+ * @exportDoc
520
+ */
521
+
522
+
497
523
  /**
498
524
  * @summary The main player object for Shaka Player.
499
525
  *
@@ -635,6 +661,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
635
661
  /** @private {?string} */
636
662
  this.assetUri_ = null;
637
663
 
664
+ /** @private {?string} */
665
+ this.mimeType_ = null;
666
+
638
667
  /** @private {?number} */
639
668
  this.startTime_ = null;
640
669
 
@@ -676,6 +705,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
676
705
  /** @private {{width: number, height: number}} */
677
706
  this.maxHwRes_ = {width: Infinity, height: Infinity};
678
707
 
708
+ /** @private {!shaka.media.ManifestFilterer} */
709
+ this.manifestFilterer_ = new shaka.media.ManifestFilterer(
710
+ this.config_, this.maxHwRes_, null);
711
+
679
712
  /** @private {shaka.util.Stats} */
680
713
  this.stats_ = null;
681
714
 
@@ -814,7 +847,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
814
847
  * @return {!shaka.util.FakeEvent}
815
848
  * @private
816
849
  */
817
- makeEvent_(name, data) {
850
+ static makeEvent_(name, data) {
818
851
  return new shaka.util.FakeEvent(name, data);
819
852
  }
820
853
 
@@ -997,7 +1030,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
997
1030
  * @private
998
1031
  */
999
1032
  makeStateChangeEvent_(nodeName) {
1000
- this.dispatchEvent(this.makeEvent_(
1033
+ this.dispatchEvent(shaka.Player.makeEvent_(
1001
1034
  /* name= */ shaka.util.FakeEvent.EventName.OnStateChange,
1002
1035
  /* data= */ (new Map()).set('state', nodeName)));
1003
1036
  }
@@ -1018,6 +1051,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1018
1051
  throw this.createAbortLoadError_();
1019
1052
  }
1020
1053
 
1054
+ const noop = this.video_ && this.video_ == mediaElement;
1055
+
1021
1056
  if (this.video_ && this.video_ != mediaElement) {
1022
1057
  await this.detach();
1023
1058
  }
@@ -1027,11 +1062,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1027
1062
  }
1028
1063
 
1029
1064
  try {
1030
- this.makeStateChangeEvent_('attach');
1065
+ if (!noop) {
1066
+ this.makeStateChangeEvent_('attach');
1031
1067
 
1032
- const onError = (error) => this.onVideoError_(error);
1033
- this.attachEventManager_.listen(mediaElement, 'error', onError);
1034
- this.video_ = mediaElement;
1068
+ const onError = (error) => this.onVideoError_(error);
1069
+ this.attachEventManager_.listen(mediaElement, 'error', onError);
1070
+ this.video_ = mediaElement;
1071
+ }
1035
1072
 
1036
1073
  // Only initialize media source if the platform supports it.
1037
1074
  if (initializeMediaSource &&
@@ -1163,7 +1200,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1163
1200
 
1164
1201
  // Dispatch the unloading event.
1165
1202
  this.dispatchEvent(
1166
- this.makeEvent_(shaka.util.FakeEvent.EventName.Unloading));
1203
+ shaka.Player.makeEvent_(shaka.util.FakeEvent.EventName.Unloading));
1167
1204
 
1168
1205
  // Release the region timeline, which is created when parsing the
1169
1206
  // manifest.
@@ -1270,6 +1307,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1270
1307
  }
1271
1308
 
1272
1309
  this.assetUri_ = null;
1310
+ this.mimeType_ = null;
1273
1311
  this.bufferObserver_ = null;
1274
1312
 
1275
1313
  if (this.manifest_) {
@@ -1324,7 +1362,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1324
1362
  * Loads a new stream.
1325
1363
  * If another stream was already playing, first unloads that stream.
1326
1364
  *
1327
- * @param {string} assetUri
1365
+ * @param {string|shaka.media.PreloadManager} assetUriOrPreloader
1328
1366
  * @param {?number=} startTime
1329
1367
  * When <code>startTime</code> is <code>null</code> or
1330
1368
  * <code>undefined</code>, playback will start at the default start time (0
@@ -1333,12 +1371,22 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1333
1371
  * @return {!Promise}
1334
1372
  * @export
1335
1373
  */
1336
- async load(assetUri, startTime = null, mimeType) {
1374
+ async load(assetUriOrPreloader, startTime = null, mimeType) {
1337
1375
  // Do not allow the player to be used after |destroy| is called.
1338
1376
  if (this.loadMode_ == shaka.Player.LoadMode.DESTROYED) {
1339
1377
  throw this.createAbortLoadError_();
1340
1378
  }
1341
1379
 
1380
+ /** @type {?shaka.media.PreloadManager} */
1381
+ let preloadManager = null;
1382
+ let assetUri = '';
1383
+ if (assetUriOrPreloader instanceof shaka.media.PreloadManager) {
1384
+ preloadManager = assetUriOrPreloader;
1385
+ assetUri = preloadManager.getAssetUri() || '';
1386
+ } else {
1387
+ assetUri = assetUriOrPreloader || '';
1388
+ }
1389
+
1342
1390
  // Quickly acquire the mutex, so this will wait for other top-level
1343
1391
  // operations.
1344
1392
  await this.mutex_.acquire('load');
@@ -1358,8 +1406,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1358
1406
  // Add a mechanism to detect if the load process has been interrupted by a
1359
1407
  // call to another top-level operation (unload, load, etc).
1360
1408
  const operationId = ++this.operationId_;
1361
- const detectInterruption = () => {
1409
+ const detectInterruption = async () => {
1362
1410
  if (this.operationId_ != operationId) {
1411
+ if (preloadManager) {
1412
+ await preloadManager.destroy();
1413
+ }
1363
1414
  throw this.createAbortLoadError_();
1364
1415
  }
1365
1416
  };
@@ -1375,37 +1426,75 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1375
1426
  const mutexWrapOperation = async (operation, mutexIdentifier) => {
1376
1427
  try {
1377
1428
  await this.mutex_.acquire(mutexIdentifier);
1378
- detectInterruption();
1429
+ await detectInterruption();
1379
1430
  await operation();
1380
- detectInterruption();
1431
+ await detectInterruption();
1432
+ if (preloadManager && this.config_) {
1433
+ preloadManager.reconfigure(this.config_);
1434
+ }
1381
1435
  } finally {
1382
1436
  this.mutex_.release();
1383
1437
  }
1384
1438
  };
1385
1439
 
1386
1440
  try {
1441
+ if (startTime == null && preloadManager) {
1442
+ startTime = preloadManager.getStartTime();
1443
+ }
1387
1444
  this.startTime_ = startTime;
1388
1445
  this.fullyLoaded_ = false;
1389
1446
 
1390
1447
  // We dispatch the loading event when someone calls |load| because we want
1391
1448
  // to surface the user intent.
1392
- this.dispatchEvent(this.makeEvent_(
1449
+ this.dispatchEvent(shaka.Player.makeEvent_(
1393
1450
  shaka.util.FakeEvent.EventName.Loading));
1394
1451
 
1395
- const startTimeOfLoad = Date.now() / 1000;
1452
+ if (preloadManager) {
1453
+ mimeType = preloadManager.getMimeType();
1454
+ } else if (!mimeType) {
1455
+ await mutexWrapOperation(async () => {
1456
+ mimeType = await this.guessMimeType_(assetUri);
1457
+ }, 'guessMimeType_');
1458
+ }
1459
+
1460
+ const wasPreloaded = !!preloadManager;
1461
+ if (!preloadManager) {
1462
+ // For simplicity, if an asset is NOT preloaded, start an internal
1463
+ // "preload" here without prefetch.
1464
+ // That way, both a preload and normal load can follow the same code
1465
+ // paths.
1466
+ // NOTE: await preloadInner_ can be outside the mutex because it should
1467
+ // not mutate "this".
1468
+ preloadManager = await this.preloadInner_(
1469
+ assetUri, startTime, mimeType, /* standardLoad= */ true);
1470
+ if (preloadManager) {
1471
+ preloadManager.setEventHandoffTarget(this);
1472
+ this.stats_ = preloadManager.getStats();
1473
+ await mutexWrapOperation(() => preloadManager.start(), 'preload');
1474
+ } else {
1475
+ this.stats_ = new shaka.util.Stats();
1476
+ }
1477
+ } else {
1478
+ // Hook up events, so any events emitted by the preloadManager will
1479
+ // instead be emitted by the player.
1480
+ preloadManager.setEventHandoffTarget(this);
1481
+ this.stats_ = preloadManager.getStats();
1482
+ }
1483
+ // Now, if there is no preload manager, that means that this is a src=
1484
+ // asset.
1485
+ const shouldUseSrcEquals = !preloadManager;
1486
+
1487
+ const startTimeOfLoad = preloadManager ?
1488
+ preloadManager.getStartTimeOfLoad() : (Date.now() / 1000);
1396
1489
 
1397
1490
  // Stats are for a single playback/load session. Stats must be initialized
1398
1491
  // before we allow calls to |updateStateHistory|.
1399
- this.stats_ = new shaka.util.Stats();
1492
+ this.stats_ =
1493
+ preloadManager ? preloadManager.getStats() : new shaka.util.Stats();
1400
1494
 
1401
1495
  this.assetUri_ = assetUri;
1496
+ this.mimeType_ = mimeType || null;
1402
1497
 
1403
- if (!mimeType) {
1404
- await mutexWrapOperation(async () => {
1405
- mimeType = await this.guessMimeType_();
1406
- }, 'guessMimeType_');
1407
- }
1408
- const shouldUseSrcEquals = this.shouldUseSrcEquals_(assetUri, mimeType);
1409
1498
  if (shouldUseSrcEquals) {
1410
1499
  await mutexWrapOperation(async () => {
1411
1500
  goog.asserts.assert(mimeType, 'We should know the mimeType by now!');
@@ -1421,42 +1510,475 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1421
1510
  await this.initializeMediaSourceEngineInner_();
1422
1511
  }, 'initializeMediaSourceEngineInner_');
1423
1512
  }
1513
+
1514
+ // Get manifest and associated values from preloader.
1515
+ await mutexWrapOperation(
1516
+ () => preloadManager.waitForManifest(), 'waitForManifest');
1517
+ this.config_ = preloadManager.getConfiguration();
1518
+ this.manifestFilterer_ = preloadManager.getManifestFilterer();
1519
+ this.parserFactory_ = preloadManager.getParserFactory();
1520
+ this.parser_ = preloadManager.receiveParser();
1521
+ this.regionTimeline_ = preloadManager.receiveRegionTimeline();
1522
+ this.qualityObserver_ = preloadManager.getQualityObserver();
1523
+ this.manifest_ = preloadManager.getManifest();
1524
+ const currentAdaptationSetCriteria =
1525
+ preloadManager.getCurrentAdaptationSetCriteria();
1526
+ if (currentAdaptationSetCriteria) {
1527
+ this.currentAdaptationSetCriteria_ = currentAdaptationSetCriteria;
1528
+ }
1529
+ if (wasPreloaded && this.video_ && this.video_.nodeName === 'AUDIO') {
1530
+ // Filter the variants to be audio-only after the fact.
1531
+ // As, when preloading, we don't know if we are going to be attached
1532
+ // to a video or audio element when we load, we have to do the auto
1533
+ // audio-only filtering here, post-facto.
1534
+ this.makeManifestAudioOnly_();
1535
+ // And continue to do so in the future.
1536
+ this.configure('manifest.disableVideo', true);
1537
+ }
1538
+
1539
+ // Get drm engine from preloader, then finalize it.
1540
+ await mutexWrapOperation(
1541
+ () => preloadManager.waitForDrm(), 'waitForDrm');
1542
+ this.drmEngine_ = preloadManager.receiveDrmEngine();
1424
1543
  await mutexWrapOperation(async () => {
1425
- await this.parseManifestInner_(mimeType);
1426
- }, 'parseManifestInner_');
1427
- await mutexWrapOperation(async () => {
1428
- await this.initializeDrmInner_();
1429
- }, 'initializeDrmInner_');
1544
+ await this.drmEngine_.attach(this.video_);
1545
+ }, 'drmEngine_.attach');
1546
+
1547
+ // Choose an initial variant.
1548
+ await mutexWrapOperation(
1549
+ () => preloadManager.waitForChooseInitialVariant(),
1550
+ 'waitForChooseInitialVariant');
1551
+
1552
+ // Also get the ABR manager, which has special logic related to being
1553
+ // received.
1554
+ const abrManagerFactory = preloadManager.getAbrManagerFactory();
1555
+ if (abrManagerFactory) {
1556
+ if (!this.abrManagerFactory_ ||
1557
+ this.abrManagerFactory_ != abrManagerFactory) {
1558
+ this.abrManager_ = preloadManager.receiveAbrManager();
1559
+ this.abrManagerFactory_ = preloadManager.getAbrManagerFactory();
1560
+ if (typeof this.abrManager_.setMediaElement != 'function') {
1561
+ shaka.Deprecate.deprecateFeature(5,
1562
+ 'AbrManager w/o setMediaElement',
1563
+ 'Please use an AbrManager with setMediaElement function.');
1564
+ this.abrManager_.setMediaElement = () => {};
1565
+ }
1566
+ if (typeof this.abrManager_.setCmsdManager != 'function') {
1567
+ shaka.Deprecate.deprecateFeature(5,
1568
+ 'AbrManager w/o setCmsdManager',
1569
+ 'Please use an AbrManager with setCmsdManager function.');
1570
+ this.abrManager_.setCmsdManager = () => {};
1571
+ }
1572
+ if (typeof this.abrManager_.trySuggestStreams != 'function') {
1573
+ shaka.Deprecate.deprecateFeature(5,
1574
+ 'AbrManager w/o trySuggestStreams',
1575
+ 'Please use an AbrManager with trySuggestStreams function.');
1576
+ this.abrManager_.trySuggestStreams = () => {};
1577
+ }
1578
+ }
1579
+ }
1580
+
1581
+ // Load the asset.
1582
+ const segmentPrefetchById =
1583
+ preloadManager.receiveSegmentPrefetchesById();
1584
+ const prefetchedVariant = preloadManager.getPrefetchedVariant();
1430
1585
  await mutexWrapOperation(async () => {
1431
- await this.loadInner_(startTimeOfLoad);
1586
+ await this.loadInner_(
1587
+ startTimeOfLoad, prefetchedVariant, segmentPrefetchById);
1432
1588
  }, 'loadInner_');
1589
+ preloadManager.stopQueuingLatePhaseQueuedOperations();
1433
1590
  }
1434
- this.dispatchEvent(this.makeEvent_(
1591
+ this.dispatchEvent(shaka.Player.makeEvent_(
1435
1592
  shaka.util.FakeEvent.EventName.Loaded));
1436
1593
  } catch (error) {
1437
1594
  if (error.code != shaka.util.Error.Code.LOAD_INTERRUPTED) {
1438
1595
  await this.unload(/* initializeMediaSource= */ false);
1439
1596
  }
1440
1597
  throw error;
1598
+ } finally {
1599
+ if (preloadManager) {
1600
+ // This will cause any resources that were generated but not used to be
1601
+ // properly destroyed or released.
1602
+ await preloadManager.destroy();
1603
+ }
1604
+ }
1605
+ }
1606
+
1607
+ /**
1608
+ * Modifies the current manifest so that it is audio-only.
1609
+ * @private
1610
+ */
1611
+ makeManifestAudioOnly_() {
1612
+ for (const variant of this.manifest_.variants) {
1613
+ if (variant.video) {
1614
+ variant.video.closeSegmentIndex();
1615
+ variant.video = null;
1616
+ }
1617
+ if (variant.audio && variant.audio.bandwidth) {
1618
+ variant.bandwidth = variant.audio.bandwidth;
1619
+ } else {
1620
+ variant.bandwidth = 0;
1621
+ }
1441
1622
  }
1623
+ this.manifest_.variants = this.manifest_.variants.filter((v) => {
1624
+ return v.audio;
1625
+ });
1626
+ }
1627
+
1628
+ /**
1629
+ * Unloads the currently playing stream, if any, and returns a PreloadManager
1630
+ * that contains the loaded manifest of that asset, if any.
1631
+ * Allows for the asset to be re-loaded by this player faster, in the future.
1632
+ * When in src= mode, this unloads but does not make a PreloadManager.
1633
+ *
1634
+ * @param {boolean=} initializeMediaSource
1635
+ * @param {boolean=} keepAdManager
1636
+ * @return {!Promise.<?shaka.media.PreloadManager>}
1637
+ * @export
1638
+ */
1639
+ async unloadAndSavePreload(
1640
+ initializeMediaSource = true, keepAdManager = false) {
1641
+ let preloadManager = null;
1642
+ if (this.manifest_ && this.parser_ && this.parserFactory_ &&
1643
+ this.assetUri_) {
1644
+ let startTime = this.video_.currentTime;
1645
+ if (this.isLive()) {
1646
+ startTime = null;
1647
+ }
1648
+ // We have enough information to make a PreloadManager!
1649
+ const startTimeOfLoad = Date.now() / 1000;
1650
+ preloadManager = await this.makePreloadManager_(
1651
+ this.assetUri_,
1652
+ startTime,
1653
+ this.mimeType_,
1654
+ startTimeOfLoad,
1655
+ /* allowPrefetch= */ true,
1656
+ /* disableVideo= */ false,
1657
+ /* allowMakeAbrManager= */ false);
1658
+ preloadManager.attachManifest(
1659
+ this.manifest_, this.parser_, this.parserFactory_);
1660
+ preloadManager.attachAbrManager(
1661
+ this.abrManager_, this.abrManagerFactory_);
1662
+ preloadManager.attachAdaptationSetCriteria(
1663
+ this.currentAdaptationSetCriteria_);
1664
+ preloadManager.start();
1665
+ // Null the manifest and manifestParser, so that they won't be shut down
1666
+ // during unload and will continue to live inside the preloadManager.
1667
+ this.manifest_ = null;
1668
+ this.parser_ = null;
1669
+ this.parserFactory_ = null;
1670
+ // Null the abrManager and abrManagerFactory, so that they won't be shut
1671
+ // down during unload and will continue to live inside the preloadManager.
1672
+ this.abrManager_ = null;
1673
+ this.abrManagerFactory_ = null;
1674
+ }
1675
+ await this.unload(initializeMediaSource, keepAdManager);
1676
+ return preloadManager;
1677
+ }
1678
+
1679
+ /**
1680
+ * Starts to preload a given asset, and returns a PreloadManager object that
1681
+ * represents that preloading process.
1682
+ * The PreloadManager will load the manifest for that asset, as well as the
1683
+ * initialization segment. It will not preload anything more than that;
1684
+ * this feature is intended for reducing start-time latency, not for fully
1685
+ * downloading assets before playing them (for that, use
1686
+ * |shaka.offline.Storage|).
1687
+ * You can pass that PreloadManager object in to the |load| method on this
1688
+ * Player instance to finish loading that particular asset, or you can call
1689
+ * the |destroy| method on the manager if the preload is no longer necessary.
1690
+ * If this returns null rather than a PreloadManager, that indicates that the
1691
+ * asset must be played with src=, which cannot be preloaded.
1692
+ *
1693
+ * @param {string} assetUri
1694
+ * @param {?number=} startTime
1695
+ * When <code>startTime</code> is <code>null</code> or
1696
+ * <code>undefined</code>, playback will start at the default start time (0
1697
+ * for VOD and liveEdge for LIVE).
1698
+ * @param {?string=} mimeType
1699
+ * @return {!Promise.<?shaka.media.PreloadManager>}
1700
+ * @export
1701
+ */
1702
+ async preload(assetUri, startTime = null, mimeType) {
1703
+ const preloadManager = await this.preloadInner_(
1704
+ assetUri, startTime, mimeType);
1705
+ if (!preloadManager) {
1706
+ this.onError_(new shaka.util.Error(
1707
+ shaka.util.Error.Severity.CRITICAL,
1708
+ shaka.util.Error.Category.PLAYER,
1709
+ shaka.util.Error.Code.SRC_EQUALS_PRELOAD_NOT_SUPPORTED));
1710
+ } else {
1711
+ preloadManager.start().catch((error) => {}); // Catch errors.
1712
+ }
1713
+ return preloadManager;
1714
+ }
1715
+
1716
+ /**
1717
+ * @param {string} assetUri
1718
+ * @param {?number} startTime
1719
+ * @param {?string=} mimeType
1720
+ * @param {boolean=} standardLoad
1721
+ * @return {!Promise.<?shaka.media.PreloadManager>}
1722
+ * @private
1723
+ */
1724
+ async preloadInner_(assetUri, startTime, mimeType, standardLoad = false) {
1725
+ goog.asserts.assert(this.networkingEngine_, 'Should have a net engine!');
1726
+ goog.asserts.assert(this.config_, 'Config must not be null!');
1727
+ const startTimeOfLoad = Date.now() / 1000;
1728
+ if (!mimeType) {
1729
+ mimeType = await this.guessMimeType_(assetUri);
1730
+ }
1731
+ const shouldUseSrcEquals = this.shouldUseSrcEquals_(assetUri, mimeType);
1732
+ if (shouldUseSrcEquals) {
1733
+ // We cannot preload src= content.
1734
+ return null;
1735
+ }
1736
+ let disableVideo = false;
1737
+ let allowMakeAbrManager = true;
1738
+ if (standardLoad) {
1739
+ if (this.abrManager_ &&
1740
+ this.abrManagerFactory_ == this.config_.abrFactory) {
1741
+ // If there's already an abr manager, don't make a new abr manager at
1742
+ // all.
1743
+ // In standardLoad mode, the abr manager isn't used for anything anyway,
1744
+ // so it should only be created to create an abr manager for the player
1745
+ // to use... which is unnecessary if we already have one of the right
1746
+ // type.
1747
+ allowMakeAbrManager = false;
1748
+ }
1749
+ if (this.video_ && this.video_.nodeName === 'AUDIO') {
1750
+ disableVideo = true;
1751
+ }
1752
+ }
1753
+ return this.makePreloadManager_(
1754
+ assetUri, startTime, mimeType || null, startTimeOfLoad,
1755
+ /* allowPrefetch= */ !standardLoad, disableVideo, allowMakeAbrManager);
1756
+ }
1757
+
1758
+ /**
1759
+ * @param {string} assetUri
1760
+ * @param {?number} startTime
1761
+ * @param {?string} mimeType
1762
+ * @param {number} startTimeOfLoad
1763
+ * @param {boolean=} allowPrefetch
1764
+ * @param {boolean=} disableVideo
1765
+ * @param {boolean=} allowMakeAbrManager
1766
+ * @return {!Promise.<!shaka.media.PreloadManager>}
1767
+ * @private
1768
+ */
1769
+ async makePreloadManager_(assetUri, startTime, mimeType, startTimeOfLoad,
1770
+ allowPrefetch = true, disableVideo = false, allowMakeAbrManager = true) {
1771
+ goog.asserts.assert(this.networkingEngine_, 'Must have net engine');
1772
+ /** @type {?shaka.media.PreloadManager} */
1773
+ let preloadManager = null;
1774
+
1775
+ const config = shaka.util.ObjectUtils.cloneObject(this.config_);
1776
+ if (disableVideo) {
1777
+ config.manifest.disableVideo = true;
1778
+ }
1779
+
1780
+ const getPreloadManager = () => {
1781
+ goog.asserts.assert(preloadManager, 'Must have preload manager');
1782
+ if (preloadManager.hasBeenAttached() && preloadManager.isDestroyed()) {
1783
+ return null;
1784
+ }
1785
+ return preloadManager;
1786
+ };
1787
+
1788
+ const getConfig = () => {
1789
+ if (getPreloadManager()) {
1790
+ return getPreloadManager().getConfiguration();
1791
+ } else {
1792
+ return this.config_;
1793
+ }
1794
+ };
1795
+
1796
+ const setConfig = (name, value) => {
1797
+ if (getPreloadManager()) {
1798
+ preloadManager.configure(name, value);
1799
+ } else {
1800
+ this.configure(name, value);
1801
+ }
1802
+ };
1803
+
1804
+ // Avoid having to detect the resolution again if it has already been
1805
+ // detected or set
1806
+ if (this.maxHwRes_.width == Infinity &&
1807
+ this.maxHwRes_.height == Infinity) {
1808
+ const maxResolution =
1809
+ await shaka.util.Platform.detectMaxHardwareResolution();
1810
+ this.maxHwRes_.width = maxResolution.width;
1811
+ this.maxHwRes_.height = maxResolution.height;
1812
+ }
1813
+ const manifestFilterer = new shaka.media.ManifestFilterer(
1814
+ config, this.maxHwRes_, null);
1815
+ const manifestPlayerInterface = {
1816
+ networkingEngine: this.networkingEngine_,
1817
+ filter: async (manifest) => {
1818
+ const tracksChanged = await manifestFilterer.filterManifest(manifest);
1819
+ if (tracksChanged) {
1820
+ // Delay the 'trackschanged' event so StreamingEngine has time to
1821
+ // absorb the changes before the user tries to query it.
1822
+ const event = shaka.Player.makeEvent_(
1823
+ shaka.util.FakeEvent.EventName.TracksChanged);
1824
+ await Promise.resolve();
1825
+ preloadManager.dispatchEvent(event);
1826
+ }
1827
+ },
1828
+ makeTextStreamsForClosedCaptions: (manifest) => {
1829
+ return this.makeTextStreamsForClosedCaptions_(manifest);
1830
+ },
1831
+
1832
+ // Called when the parser finds a timeline region. This can be called
1833
+ // before we start playback or during playback (live/in-progress
1834
+ // manifest).
1835
+ onTimelineRegionAdded: (region) => {
1836
+ preloadManager.getRegionTimeline().addRegion(region);
1837
+ },
1838
+
1839
+ onEvent: (event) => preloadManager.dispatchEvent(event),
1840
+ onError: (error) => preloadManager.onError(error),
1841
+ isLowLatencyMode: () => getConfig().streaming.lowLatencyMode,
1842
+ isAutoLowLatencyMode: () => getConfig().streaming.autoLowLatencyMode,
1843
+ enableLowLatencyMode: () => {
1844
+ setConfig('streaming.lowLatencyMode', true);
1845
+ },
1846
+ updateDuration: () => {
1847
+ if (this.streamingEngine_ && preloadManager.hasBeenAttached()) {
1848
+ this.streamingEngine_.updateDuration();
1849
+ }
1850
+ },
1851
+ newDrmInfo: (stream) => {
1852
+ // We may need to create new sessions for any new init data.
1853
+ const drmEngine = preloadManager.getDrmEngine();
1854
+ const currentDrmInfo = drmEngine ? drmEngine.getDrmInfo() : null;
1855
+ // DrmEngine.newInitData() requires mediaKeys to be available.
1856
+ if (currentDrmInfo && drmEngine.getMediaKeys()) {
1857
+ manifestFilterer.processDrmInfos(currentDrmInfo.keySystem, stream);
1858
+ }
1859
+ },
1860
+ onManifestUpdated: () => {
1861
+ const eventName = shaka.util.FakeEvent.EventName.ManifestUpdated;
1862
+ const data = (new Map()).set('isLive', this.isLive());
1863
+ preloadManager.dispatchEvent(shaka.Player.makeEvent_(eventName, data));
1864
+
1865
+ preloadManager.addQueuedOperation(false, () => {
1866
+ if (this.adManager_) {
1867
+ this.adManager_.onManifestUpdated(this.isLive());
1868
+ }
1869
+ });
1870
+ },
1871
+ getBandwidthEstimate: () => this.abrManager_.getBandwidthEstimate(),
1872
+ };
1873
+ const regionTimeline =
1874
+ new shaka.media.RegionTimeline(() => this.seekRange());
1875
+ regionTimeline.addEventListener('regionadd', (event) => {
1876
+ /** @type {shaka.extern.TimelineRegionInfo} */
1877
+ const region = event['region'];
1878
+ this.onRegionEvent_(
1879
+ shaka.util.FakeEvent.EventName.TimelineRegionAdded, region,
1880
+ preloadManager);
1881
+
1882
+ preloadManager.addQueuedOperation(false, () => {
1883
+ if (this.adManager_) {
1884
+ this.adManager_.onDashTimedMetadata(region);
1885
+ }
1886
+ });
1887
+ });
1888
+ let qualityObserver = null;
1889
+ if (config.streaming.observeQualityChanges) {
1890
+ qualityObserver = new shaka.media.QualityObserver(
1891
+ () => this.getBufferedInfo());
1892
+
1893
+ qualityObserver.addEventListener('qualitychange', (event) => {
1894
+ /** @type {shaka.extern.MediaQualityInfo} */
1895
+ const mediaQualityInfo = event['quality'];
1896
+ /** @type {number} */
1897
+ const position = event['position'];
1898
+ this.onMediaQualityChange_(mediaQualityInfo, position);
1899
+ });
1900
+ }
1901
+ let firstEvent = true;
1902
+ const drmPlayerInterface = {
1903
+ netEngine: this.networkingEngine_,
1904
+ onError: (e) => preloadManager.onError(e),
1905
+ onKeyStatus: (map) => {
1906
+ preloadManager.addQueuedOperation(true, () => {
1907
+ this.onKeyStatus_(map);
1908
+ });
1909
+ },
1910
+ onExpirationUpdated: (id, expiration) => {
1911
+ const event = shaka.Player.makeEvent_(
1912
+ shaka.util.FakeEvent.EventName.ExpirationUpdated);
1913
+ preloadManager.dispatchEvent(event);
1914
+ const parser = preloadManager.getParser();
1915
+ if (parser && parser.onExpirationUpdated) {
1916
+ parser.onExpirationUpdated(id, expiration);
1917
+ }
1918
+ },
1919
+ onEvent: (e) => {
1920
+ preloadManager.dispatchEvent(e);
1921
+ if (e.type == shaka.util.FakeEvent.EventName.DrmSessionUpdate &&
1922
+ firstEvent) {
1923
+ firstEvent = false;
1924
+ const now = Date.now() / 1000;
1925
+ const delta = now - preloadManager.getStartTimeOfDRM();
1926
+ const stats = this.stats_ || preloadManager.getStats();
1927
+ stats.setDrmTime(delta);
1928
+ // LCEVC data by itself is not encrypted in DRM protected streams
1929
+ // and can therefore be accessed and decoded as normal. However,
1930
+ // the LCEVC decoder needs access to the VideoElement output in
1931
+ // order to apply the enhancement. In DRM contexts where the
1932
+ // browser CDM restricts access from our decoder, the enhancement
1933
+ // cannot be applied and therefore the LCEVC output canvas is
1934
+ // hidden accordingly.
1935
+ if (this.lcevcDec_) {
1936
+ this.lcevcDec_.hideCanvas();
1937
+ }
1938
+ }
1939
+ },
1940
+ };
1941
+
1942
+ // Sadly, as the network engine creation code must be replaceable by tests,
1943
+ // it cannot be made and use the utilities defined in this function.
1944
+ const networkingEngine = this.createNetworkingEngine(getPreloadManager);
1945
+
1946
+ /** @return {!shaka.media.DrmEngine} */
1947
+ const createDrmEngine = () => {
1948
+ return this.createDrmEngine(drmPlayerInterface);
1949
+ };
1950
+ /** @type {!shaka.media.PreloadManager.PlayerInterface} */
1951
+ const playerInterface = {
1952
+ config,
1953
+ manifestPlayerInterface,
1954
+ regionTimeline,
1955
+ qualityObserver,
1956
+ createDrmEngine,
1957
+ manifestFilterer,
1958
+ networkingEngine,
1959
+ allowPrefetch,
1960
+ allowMakeAbrManager,
1961
+ };
1962
+ preloadManager = new shaka.media.PreloadManager(
1963
+ assetUri, mimeType, startTimeOfLoad, startTime, playerInterface);
1964
+ return preloadManager;
1442
1965
  }
1443
1966
 
1444
1967
  /**
1445
1968
  * Determines the mimeType of the given asset, if we are not told that inside
1446
1969
  * the loading process.
1447
1970
  *
1971
+ * @param {string} assetUri
1448
1972
  * @return {!Promise.<?string>} mimeType
1449
1973
  * @private
1450
1974
  */
1451
- async guessMimeType_() {
1452
- goog.asserts.assert(this.assetUri_, 'should have a uri by now.');
1453
-
1975
+ async guessMimeType_(assetUri) {
1454
1976
  // If no MIME type is provided, and we can't base it on extension, make a
1455
1977
  // HEAD request to determine it.
1456
1978
  goog.asserts.assert(this.networkingEngine_, 'Should have a net engine!');
1457
1979
  const retryParams = this.config_.manifest.retryParameters;
1458
1980
  let mimeType = await shaka.net.NetworkingUtils.getMimeType(
1459
- this.assetUri_, this.networkingEngine_, retryParams);
1981
+ assetUri, this.networkingEngine_, retryParams);
1460
1982
  if (mimeType == 'application/x-mpegurl' && shaka.util.Platform.isApple()) {
1461
1983
  mimeType = 'application/vnd.apple.mpegurl';
1462
1984
  }
@@ -1516,6 +2038,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1516
2038
  return true;
1517
2039
  }
1518
2040
 
2041
+ // Native FairPlay HLS can be preferred on Apple platfforms.
2042
+ if (Platform.isApple() &&
2043
+ (this.config_.drm.servers['com.apple.fps'] ||
2044
+ this.config_.drm.servers['com.apple.fps.1_0'])) {
2045
+ return this.config_.streaming.useNativeHlsForFairPlay;
2046
+ }
2047
+
1519
2048
  // For Safari, we have an older flag which only applies to this one
1520
2049
  // browser:
1521
2050
  if (Platform.isApple()) {
@@ -1559,276 +2088,52 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1559
2088
  // until they are both initialized before setting the initial value.
1560
2089
  const textDisplayerFactory = this.config_.textDisplayFactory;
1561
2090
  const textDisplayer = textDisplayerFactory();
2091
+ if (textDisplayer.configure) {
2092
+ textDisplayer.configure(this.config_.textDisplayer);
2093
+ } else {
2094
+ shaka.Deprecate.deprecateFeature(5,
2095
+ 'Text displayer w/ configure',
2096
+ 'Text displayer should have a "configure" method!');
2097
+ }
1562
2098
  this.lastTextFactory_ = textDisplayerFactory;
1563
2099
 
1564
2100
  const mediaSourceEngine = this.createMediaSourceEngine(
1565
- this.video_,
1566
- textDisplayer,
1567
- (metadata, offset, endTime) => {
1568
- this.processTimedMetadataMediaSrc_(metadata, offset, endTime);
1569
- },
1570
- this.lcevcDec_);
1571
- mediaSourceEngine.configure(this.config_.mediaSource);
1572
- const {segmentRelativeVttTiming} = this.config_.manifest;
1573
- mediaSourceEngine.setSegmentRelativeVttTiming(segmentRelativeVttTiming);
1574
-
1575
- // Wait for media source engine to finish opening. This promise should
1576
- // NEVER be rejected as per the media source engine implementation.
1577
- await mediaSourceEngine.open();
1578
-
1579
- // Wait until it is ready to actually store the reference.
1580
- this.mediaSourceEngine_ = mediaSourceEngine;
1581
- }
1582
-
1583
- /**
1584
- * Pick an initialize a manifest parser, then have it download and parse the
1585
- * manifest.
1586
- *
1587
- * @param {?string=} mimeType
1588
- * @return {!Promise}
1589
- * @private
1590
- */
1591
- async parseManifestInner_(mimeType) {
1592
- goog.asserts.assert(
1593
- this.assetUri_,
1594
- 'should have a uri when making the parser.');
1595
- goog.asserts.assert(
1596
- this.video_,
1597
- 'We should have a media element when initializing the parser.');
1598
- goog.asserts.assert(
1599
- this.networkingEngine_,
1600
- 'Need networking engine when initializing the parser.');
1601
- goog.asserts.assert(
1602
- this.cmcdManager_,
1603
- 'Need CMCD manager to populate manifest request data.');
1604
- goog.asserts.assert(
1605
- this.cmsdManager_,
1606
- 'Need CMSD manager to process response headers.');
1607
- goog.asserts.assert(
1608
- this.config_,
1609
- 'Need player config when initializing the parser.');
1610
-
1611
- // Store references to things we asserted so that we don't need to
1612
- // reassert them again later.
1613
- const networkingEngine = this.networkingEngine_;
1614
-
1615
- this.makeStateChangeEvent_('manifest-parser');
1616
-
1617
- // Create the parser that we will use to parse the manifest.
1618
- this.parserFactory_ = shaka.media.ManifestParser.getFactory(
1619
- this.assetUri_,
1620
- mimeType || null);
1621
- goog.asserts.assert(this.parserFactory_, 'Must have manifest parser');
1622
- this.parser_ = this.parserFactory_();
1623
-
1624
- const manifestConfig =
1625
- shaka.util.ObjectUtils.cloneObject(this.config_.manifest);
1626
- // Don't read video segments if the player is attached to an audio element
1627
- if (this.video_ && this.video_.nodeName === 'AUDIO') {
1628
- manifestConfig.disableVideo = true;
1629
- }
1630
-
1631
- this.parser_.configure(manifestConfig);
1632
-
1633
- // This will be needed by the parser once it starts parsing, so we will
1634
- // initialize it now even through it appears a little out-of-place.
1635
- this.regionTimeline_ =
1636
- new shaka.media.RegionTimeline(() => this.seekRange());
1637
- this.regionTimeline_.addEventListener('regionadd', (event) => {
1638
- /** @type {shaka.extern.TimelineRegionInfo} */
1639
- const region = event['region'];
1640
- this.onRegionEvent_(
1641
- shaka.util.FakeEvent.EventName.TimelineRegionAdded, region);
1642
-
1643
- if (this.adManager_) {
1644
- this.adManager_.onDashTimedMetadata(region);
1645
- }
1646
- });
1647
-
1648
- this.qualityObserver_ = null;
1649
- if (this.config_.streaming.observeQualityChanges) {
1650
- this.qualityObserver_ = new shaka.media.QualityObserver(
1651
- () => this.getBufferedInfo());
1652
-
1653
- this.qualityObserver_.addEventListener('qualitychange', (event) => {
1654
- /** @type {shaka.extern.MediaQualityInfo} */
1655
- const mediaQualityInfo = event['quality'];
1656
- /** @type {number} */
1657
- const position = event['position'];
1658
- this.onMediaQualityChange_(mediaQualityInfo, position);
1659
- });
1660
- }
1661
-
1662
- const playerInterface = {
1663
- networkingEngine: networkingEngine,
1664
- filter: (manifest) => this.filterManifest_(manifest),
1665
- makeTextStreamsForClosedCaptions: (manifest) => {
1666
- return this.makeTextStreamsForClosedCaptions_(manifest);
1667
- },
1668
-
1669
- // Called when the parser finds a timeline region. This can be called
1670
- // before we start playback or during playback (live/in-progress
1671
- // manifest).
1672
- onTimelineRegionAdded: (region) => {
1673
- this.regionTimeline_.addRegion(region);
1674
- },
1675
-
1676
- onEvent: (event) => this.dispatchEvent(event),
1677
- onError: (error) => this.onError_(error),
1678
- isLowLatencyMode: () => this.isLowLatencyMode_(),
1679
- isAutoLowLatencyMode: () => this.isAutoLowLatencyMode_(),
1680
- enableLowLatencyMode: () => {
1681
- this.configure('streaming.lowLatencyMode', true);
1682
- },
1683
- updateDuration: () => {
1684
- if (this.streamingEngine_) {
1685
- this.streamingEngine_.updateDuration();
1686
- }
1687
- },
1688
- newDrmInfo: (stream) => {
1689
- // We may need to create new sessions for any new init data.
1690
- const currentDrmInfo =
1691
- this.drmEngine_ ? this.drmEngine_.getDrmInfo() : null;
1692
- // DrmEngine.newInitData() requires mediaKeys to be available.
1693
- if (currentDrmInfo && this.drmEngine_.getMediaKeys()) {
1694
- this.processDrmInfos_(currentDrmInfo.keySystem, stream);
1695
- }
1696
- },
1697
- onManifestUpdated: () => {
1698
- const eventName = shaka.util.FakeEvent.EventName.ManifestUpdated;
1699
- const data = (new Map()).set('isLive', this.isLive());
1700
- this.dispatchEvent(this.makeEvent_(eventName, data));
1701
- if (this.adManager_) {
1702
- this.adManager_.onManifestUpdated(this.isLive());
1703
- }
1704
- },
1705
- getBandwidthEstimate: () => this.abrManager_.getBandwidthEstimate(),
1706
- };
1707
-
1708
- const startTime = Date.now() / 1000;
1709
-
1710
- this.makeStateChangeEvent_('manifest');
1711
-
1712
- this.manifest_ = await this.parser_.start(
1713
- this.assetUri_, playerInterface);
1714
-
1715
- // This event is fired after the manifest is parsed, but before any
1716
- // filtering takes place.
1717
- const event =
1718
- this.makeEvent_(shaka.util.FakeEvent.EventName.ManifestParsed);
1719
- this.dispatchEvent(event);
1720
-
1721
- // We require all manifests to have at least one variant.
1722
- if (this.manifest_.variants.length == 0) {
1723
- throw new shaka.util.Error(
1724
- shaka.util.Error.Severity.CRITICAL,
1725
- shaka.util.Error.Category.MANIFEST,
1726
- shaka.util.Error.Code.NO_VARIANTS);
1727
- }
1728
-
1729
- // Make sure that all variants are either: audio-only, video-only, or
1730
- // audio-video.
1731
- shaka.Player.filterForAVVariants_(this.manifest_);
1732
-
1733
- const now = Date.now() / 1000;
1734
- const delta = now - startTime;
1735
- this.stats_.setManifestTime(delta);
1736
- }
1737
-
1738
- /**
1739
- * Initializes the DRM engine.
1740
- *
1741
- * @return {!Promise}
1742
- * @private
1743
- */
1744
- async initializeDrmInner_() {
1745
- goog.asserts.assert(
1746
- this.networkingEngine_,
1747
- '|onInitializeDrm_| should never be called after |destroy|');
1748
- goog.asserts.assert(
1749
- this.config_,
1750
- '|onInitializeDrm_| should never be called after |destroy|');
1751
- goog.asserts.assert(
1752
- this.manifest_,
1753
- '|this.manifest_| should have been set in an earlier step.');
1754
- goog.asserts.assert(
1755
- this.video_,
1756
- 'We should have a media element when initializing the DRM Engine.');
1757
-
1758
- this.makeStateChangeEvent_('drm-engine');
1759
-
1760
- const startTime = Date.now() / 1000;
1761
- let firstEvent = true;
1762
-
1763
- this.drmEngine_ = this.createDrmEngine({
1764
- netEngine: this.networkingEngine_,
1765
- onError: (e) => {
1766
- this.onError_(e);
1767
- },
1768
- onKeyStatus: (map) => {
1769
- this.onKeyStatus_(map);
1770
- },
1771
- onExpirationUpdated: (id, expiration) => {
1772
- this.onExpirationUpdated_(id, expiration);
1773
- },
1774
- onEvent: (e) => {
1775
- this.dispatchEvent(e);
1776
- if (e.type == shaka.util.FakeEvent.EventName.DrmSessionUpdate &&
1777
- firstEvent) {
1778
- firstEvent = false;
1779
- const now = Date.now() / 1000;
1780
- const delta = now - startTime;
1781
- this.stats_.setDrmTime(delta);
1782
- // LCEVC data by itself is not encrypted in DRM protected streams
1783
- // and can therefore be accessed and decoded as normal. However,
1784
- // the LCEVC decoder needs access to the VideoElement output in
1785
- // order to apply the enhancement. In DRM contexts where the
1786
- // browser CDM restricts access from our decoder, the enhancement
1787
- // cannot be applied and therefore the LCEVC output canvas is
1788
- // hidden accordingly.
1789
- if (this.lcevcDec_) {
1790
- this.lcevcDec_.hideCanvas();
1791
- }
1792
- }
1793
- },
1794
- });
1795
-
1796
- this.drmEngine_.configure(this.config_.drm);
1797
-
1798
- const tracksChanged = shaka.util.StreamUtils.applyRestrictions(
1799
- this.manifest_.variants, this.config_.restrictions, this.maxHwRes_);
1800
- if (tracksChanged && this.streamingEngine_) {
1801
- this.onTracksChanged_();
1802
- }
1803
-
1804
- const playableVariants = shaka.util.StreamUtils.getPlayableVariants(
1805
- this.manifest_.variants);
1806
-
1807
- await this.drmEngine_.initForPlayback(
1808
- playableVariants,
1809
- this.manifest_.offlineSessionIds);
2101
+ this.video_,
2102
+ textDisplayer,
2103
+ (metadata, offset, endTime) => {
2104
+ this.processTimedMetadataMediaSrc_(metadata, offset, endTime);
2105
+ },
2106
+ this.lcevcDec_);
2107
+ mediaSourceEngine.configure(this.config_.mediaSource);
2108
+ const {segmentRelativeVttTiming} = this.config_.manifest;
2109
+ mediaSourceEngine.setSegmentRelativeVttTiming(segmentRelativeVttTiming);
1810
2110
 
1811
- await this.drmEngine_.attach(this.video_);
2111
+ // Wait for media source engine to finish opening. This promise should
2112
+ // NEVER be rejected as per the media source engine implementation.
2113
+ await mediaSourceEngine.open();
1812
2114
 
1813
- // Now that we have drm information, filter the manifest (again) so that
1814
- // we can ensure we only use variants with the selected key system.
1815
- await this.filterManifest_(this.manifest_);
2115
+ // Wait until it is ready to actually store the reference.
2116
+ this.mediaSourceEngine_ = mediaSourceEngine;
1816
2117
  }
1817
2118
 
1818
2119
  /**
1819
2120
  * Starts loading the content described by the parsed manifest.
1820
2121
  *
1821
2122
  * @param {number} startTimeOfLoad
2123
+ * @param {?shaka.extern.Variant} prefetchedVariant
2124
+ * @param {!Map.<number, shaka.media.SegmentPrefetch>} segmentPrefetchById
1822
2125
  * @return {!Promise}
1823
2126
  * @private
1824
2127
  */
1825
- async loadInner_(startTimeOfLoad) {
2128
+ async loadInner_(startTimeOfLoad, prefetchedVariant, segmentPrefetchById) {
1826
2129
  goog.asserts.assert(
1827
2130
  this.video_, 'We should have a media element by now.');
1828
2131
  goog.asserts.assert(
1829
2132
  this.manifest_, 'The manifest should already be parsed.');
1830
2133
  goog.asserts.assert(
1831
2134
  this.assetUri_, 'We should have an asset uri by now.');
2135
+ goog.asserts.assert(
2136
+ this.abrManager_, 'We should have an abr manager by now.');
1832
2137
 
1833
2138
  this.makeStateChangeEvent_('load');
1834
2139
 
@@ -1852,46 +2157,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1852
2157
  // depending on the config.
1853
2158
  this.setupLcevc_(this.config_);
1854
2159
 
1855
- const abrFactory = this.config_.abrFactory;
1856
- if (!this.abrManager_ || this.abrManagerFactory_ != abrFactory) {
1857
- this.abrManagerFactory_ = abrFactory;
1858
- this.abrManager_ = abrFactory();
1859
- if (typeof this.abrManager_.setMediaElement != 'function') {
1860
- shaka.Deprecate.deprecateFeature(5,
1861
- 'AbrManager w/o setMediaElement',
1862
- 'Please use an AbrManager with setMediaElement function.');
1863
- this.abrManager_.setMediaElement = () => {};
1864
- }
1865
- if (typeof this.abrManager_.setCmsdManager != 'function') {
1866
- shaka.Deprecate.deprecateFeature(5,
1867
- 'AbrManager w/o setCmsdManager',
1868
- 'Please use an AbrManager with setCmsdManager function.');
1869
- this.abrManager_.setCmsdManager = () => {};
1870
- }
1871
- if (typeof this.abrManager_.trySuggestStreams != 'function') {
1872
- shaka.Deprecate.deprecateFeature(5,
1873
- 'AbrManager w/o trySuggestStreams',
1874
- 'Please use an AbrManager with trySuggestStreams function.');
1875
- this.abrManager_.trySuggestStreams = () => {};
1876
- }
1877
- this.abrManager_.configure(this.config_.abr);
1878
- }
1879
-
1880
- // Copy preferred languages from the config again, in case the config was
1881
- // changed between construction and playback.
1882
- this.currentAdaptationSetCriteria_ =
1883
- new shaka.media.PreferenceBasedCriteria(
1884
- this.config_.preferredAudioLanguage,
1885
- this.config_.preferredVariantRole,
1886
- this.config_.preferredAudioChannelCount,
1887
- this.config_.preferredVideoHdrLevel,
1888
- this.config_.preferSpatialAudio,
1889
- this.config_.preferredVideoLayout,
1890
- this.config_.preferredAudioLabel,
1891
- this.config_.preferredVideoLabel,
1892
- this.config_.mediaSource.codecSwitchingStrategy,
1893
- this.config_.manifest.dash.enableAudioGroups);
1894
-
1895
2160
  this.currentTextLanguage_ = this.config_.preferredTextLanguage;
1896
2161
  this.currentTextRole_ = this.config_.preferredTextRole;
1897
2162
  this.currentTextForced_ = this.config_.preferForcedSubs;
@@ -1906,14 +2171,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1906
2171
  this.abrManager_.setMediaElement(mediaElement);
1907
2172
  this.abrManager_.setCmsdManager(this.cmsdManager_);
1908
2173
 
1909
- // If the content is multi-codec and the browser can play more than one of
1910
- // them, choose codecs now before we initialize streaming.
1911
- shaka.util.StreamUtils.chooseCodecsAndFilterManifest(
1912
- this.manifest_,
1913
- this.config_.preferredVideoCodecs,
1914
- this.config_.preferredAudioCodecs,
1915
- this.config_.preferredDecodingAttributes);
1916
-
1917
2174
  this.streamingEngine_ = this.createStreamingEngine();
1918
2175
  this.streamingEngine_.configure(this.config_.streaming);
1919
2176
 
@@ -1944,15 +2201,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1944
2201
  // The event must be fired after we filter by restrictions but before the
1945
2202
  // active stream is picked to allow those listening for the "streaming"
1946
2203
  // event to make changes before streaming starts.
1947
- this.dispatchEvent(
1948
- this.makeEvent_(shaka.util.FakeEvent.EventName.Streaming));
2204
+ this.dispatchEvent(shaka.Player.makeEvent_(
2205
+ shaka.util.FakeEvent.EventName.Streaming));
1949
2206
 
1950
2207
  // Pick the initial streams to play.
1951
2208
  // Unless the user has already picked a variant, anyway, by calling
1952
2209
  // selectVariantTrack before this loading stage.
1953
- let initialVariant = null;
2210
+ let initialVariant = prefetchedVariant;
1954
2211
  const activeVariant = this.streamingEngine_.getCurrentVariant();
1955
- if (!activeVariant) {
2212
+ if (!activeVariant && !initialVariant) {
1956
2213
  initialVariant = this.chooseVariant_(/* initialSelection= */ true);
1957
2214
  goog.asserts.assert(initialVariant, 'Must choose an initial variant!');
1958
2215
  }
@@ -1977,31 +2234,49 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1977
2234
  this.config_.playRangeStart,
1978
2235
  this.config_.playRangeEnd);
1979
2236
 
1980
- this.playhead_ = this.createPlayhead(this.startTime_);
1981
- this.playheadObservers_ =
1982
- this.createPlayheadObserversForMSE_(startTimeOfLoad);
1983
2237
 
1984
- // We need to start the buffer management code near the end because it
1985
- // will set the initial buffering state and that depends on other
1986
- // components being initialized.
1987
- const rebufferThreshold = Math.max(
1988
- this.manifest_.minBufferTime, this.config_.streaming.rebufferingGoal);
1989
- this.startBufferManagement_(mediaElement, rebufferThreshold);
2238
+ const setupPlayhead = (startTime) => {
2239
+ this.playhead_ = this.createPlayhead(startTime);
2240
+ this.playheadObservers_ =
2241
+ this.createPlayheadObserversForMSE_(startTimeOfLoad);
2242
+
2243
+ // We need to start the buffer management code near the end because it
2244
+ // will set the initial buffering state and that depends on other
2245
+ // components being initialized.
2246
+ const rebufferThreshold = Math.max(
2247
+ this.manifest_.minBufferTime,
2248
+ this.config_.streaming.rebufferingGoal);
2249
+ this.startBufferManagement_(mediaElement, rebufferThreshold);
2250
+ };
2251
+
2252
+ if (!this.config_.streaming.startAtSegmentBoundary) {
2253
+ setupPlayhead(this.startTime_);
2254
+ }
1990
2255
 
1991
2256
  // Now we can switch to the initial variant.
1992
2257
  if (!activeVariant) {
1993
2258
  goog.asserts.assert(initialVariant,
1994
2259
  'Must have choosen an initial variant!');
1995
2260
 
1996
- this.switchVariant_(initialVariant, /* fromAdaptation= */ true,
1997
- /* clearBuffer= */ false, /* safeMargin= */ 0);
1998
-
1999
2261
  // Now that we have initial streams, we may adjust the start time to
2000
2262
  // align to a segment boundary.
2001
2263
  if (this.config_.streaming.startAtSegmentBoundary) {
2002
- this.playhead_.setStartTime(await this.adjustStartTime_(
2003
- initialVariant, this.playhead_.getTime()));
2264
+ const timeline = this.manifest_.presentationTimeline;
2265
+ let initialTime = this.startTime_ || this.video_.currentTime;
2266
+ const seekRangeStart = timeline.getSeekRangeStart();
2267
+ const seekRangeEnd = timeline.getSeekRangeEnd();
2268
+ if (initialTime < seekRangeStart) {
2269
+ initialTime = seekRangeStart;
2270
+ } else if (initialTime > seekRangeEnd) {
2271
+ initialTime = seekRangeEnd;
2272
+ }
2273
+ const startTime = await this.adjustStartTime_(
2274
+ initialVariant, initialTime);
2275
+ setupPlayhead(startTime);
2004
2276
  }
2277
+
2278
+ this.switchVariant_(initialVariant, /* fromAdaptation= */ true,
2279
+ /* clearBuffer= */ false, /* safeMargin= */ 0);
2005
2280
  }
2006
2281
 
2007
2282
  this.playhead_.ready();
@@ -2033,7 +2308,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2033
2308
 
2034
2309
  // Start streaming content. This will start the flow of content down to
2035
2310
  // media source.
2036
- await this.streamingEngine_.start();
2311
+ await this.streamingEngine_.start(segmentPrefetchById);
2037
2312
 
2038
2313
  if (this.config_.abr.enabled) {
2039
2314
  this.abrManager_.enable();
@@ -2061,8 +2336,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2061
2336
 
2062
2337
  const isLive = this.isLive();
2063
2338
 
2064
- if (isLive && (this.config_.streaming.liveSync ||
2065
- this.manifest_.serviceDescription)) {
2339
+ if ((isLive && (this.config_.streaming.liveSync ||
2340
+ this.manifest_.serviceDescription ||
2341
+ this.config_.streaming.liveSyncPanicMode)) ||
2342
+ this.config_.streaming.vodDynamicPlaybackRate) {
2066
2343
  const onTimeUpdate = () => this.onTimeUpdate_();
2067
2344
  this.loadEventManager_.listen(mediaElement, 'timeupdate', onTimeUpdate);
2068
2345
  } else if (!isLive) {
@@ -2112,10 +2389,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2112
2389
  this.onError_(e);
2113
2390
  },
2114
2391
  onKeyStatus: (map) => {
2115
- this.onKeyStatus_(map);
2392
+ // According to this.onKeyStatus_, we can't even use this information
2393
+ // in src= mode, so this is just a no-op.
2116
2394
  },
2117
2395
  onExpirationUpdated: (id, expiration) => {
2118
- this.onExpirationUpdated_(id, expiration);
2396
+ const event = shaka.Player.makeEvent_(
2397
+ shaka.util.FakeEvent.EventName.ExpirationUpdated);
2398
+ this.dispatchEvent(event);
2119
2399
  },
2120
2400
  onEvent: (e) => {
2121
2401
  this.dispatchEvent(e);
@@ -2316,6 +2596,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2316
2596
  mediaElement.load();
2317
2597
  }
2318
2598
 
2599
+ // In Safari using HLS won't load anything unless you call load()
2600
+ // explicitly, no matter the value of the preload attribute.
2601
+ // Note: this only happens when there are not autoplay.
2602
+ if (mediaElement.preload != 'none' && !mediaElement.autoplay &&
2603
+ shaka.util.MimeUtils.isHlsType(mimeType) &&
2604
+ shaka.util.Platform.safariVersion()) {
2605
+ mediaElement.load();
2606
+ }
2607
+
2319
2608
  // Set the load mode last so that we know that all our components are
2320
2609
  // initialized.
2321
2610
  this.loadMode_ = shaka.Player.LoadMode.SRC_EQUALS;
@@ -2323,8 +2612,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2323
2612
  // The event doesn't mean as much for src= playback, since we don't
2324
2613
  // control streaming. But we should fire it in this path anyway since
2325
2614
  // some applications may be expecting it as a life-cycle event.
2326
- this.dispatchEvent(
2327
- this.makeEvent_(shaka.util.FakeEvent.EventName.Streaming));
2615
+ this.dispatchEvent(shaka.Player.makeEvent_(
2616
+ shaka.util.FakeEvent.EventName.Streaming));
2328
2617
 
2329
2618
  // The "load" Promise is resolved when we have loaded the metadata. If we
2330
2619
  // wait for the full data, that won't happen on Safari until the play
@@ -2396,7 +2685,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2396
2685
 
2397
2686
  const isLive = this.isLive();
2398
2687
 
2399
- if (isLive && this.config_.streaming.liveSync) {
2688
+ if ((isLive && (this.config_.streaming.liveSync ||
2689
+ this.config_.streaming.liveSyncPanicMode)) ||
2690
+ this.config_.streaming.vodDynamicPlaybackRate) {
2400
2691
  const onTimeUpdate = () => this.onTimeUpdate_();
2401
2692
  this.loadEventManager_.listen(mediaElement, 'timeupdate', onTimeUpdate);
2402
2693
  } else if (!isLive) {
@@ -2556,7 +2847,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2556
2847
  .set('endTime', endTime)
2557
2848
  .set('metadataType', metadataType)
2558
2849
  .set('payload', payload);
2559
- this.dispatchEvent(this.makeEvent_(eventName, data));
2850
+ this.dispatchEvent(shaka.Player.makeEvent_(eventName, data));
2560
2851
  }
2561
2852
 
2562
2853
  /**
@@ -2586,35 +2877,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2586
2877
  });
2587
2878
  }
2588
2879
 
2589
- /**
2590
- * Take a series of variants and ensure that they only contain one type of
2591
- * variant. The different options are:
2592
- * 1. Audio-Video
2593
- * 2. Audio-Only
2594
- * 3. Video-Only
2595
- *
2596
- * A manifest can only contain a single type because once we initialize media
2597
- * source to expect specific streams, it must always have content for those
2598
- * streams. If we were to start with audio+video and switch to an audio-only
2599
- * variant, media source would block waiting for video content.
2600
- *
2601
- * @param {shaka.extern.Manifest} manifest
2602
- * @private
2603
- */
2604
- static filterForAVVariants_(manifest) {
2605
- const isAVVariant = (variant) => {
2606
- // Audio-video variants may include both streams separately or may be
2607
- // single multiplexed streams with multiple codecs.
2608
- return (variant.video && variant.audio) ||
2609
- (variant.video && variant.video.codecs.includes(','));
2610
- };
2611
- if (manifest.variants.some(isAVVariant)) {
2612
- shaka.log.debug('Found variant with audio and video content, ' +
2613
- 'so filtering out audio-only content.');
2614
- manifest.variants = manifest.variants.filter(isAVVariant);
2615
- }
2616
- }
2617
-
2618
2880
  /**
2619
2881
  * Releases all of the mutexes of the player. Meant for use by the tests.
2620
2882
  * @export
@@ -2632,25 +2894,66 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2632
2894
  * @return {!shaka.media.DrmEngine}
2633
2895
  */
2634
2896
  createDrmEngine(playerInterface) {
2635
- const updateExpirationTime = this.config_.drm.updateExpirationTime;
2636
- return new shaka.media.DrmEngine(playerInterface, updateExpirationTime);
2897
+ return new shaka.media.DrmEngine(playerInterface);
2637
2898
  }
2638
2899
 
2639
2900
  /**
2640
2901
  * Creates a new instance of NetworkingEngine. This can be replaced by tests
2641
2902
  * to create fake instances instead.
2642
2903
  *
2904
+ * @param {(function():?shaka.media.PreloadManager)=} getPreloadManager
2643
2905
  * @return {!shaka.net.NetworkingEngine}
2644
2906
  */
2645
- createNetworkingEngine() {
2646
- /** @type {function(number, number, boolean)} */
2647
- const onProgressUpdated_ = (deltaTimeMs, bytesDownloaded, allowSwitch) => {
2907
+ createNetworkingEngine(getPreloadManager) {
2908
+ if (!getPreloadManager) {
2909
+ getPreloadManager = () => null;
2910
+ }
2911
+
2912
+ const getAbrManager = () => {
2913
+ if (getPreloadManager()) {
2914
+ return getPreloadManager().getAbrManager();
2915
+ } else {
2916
+ return this.abrManager_;
2917
+ }
2918
+ };
2919
+ const getParser = () => {
2920
+ if (getPreloadManager()) {
2921
+ return getPreloadManager().getParser();
2922
+ } else {
2923
+ return this.parser_;
2924
+ }
2925
+ };
2926
+ const lateQueue = (fn) => {
2927
+ if (getPreloadManager()) {
2928
+ getPreloadManager().addQueuedOperation(true, fn);
2929
+ } else {
2930
+ fn();
2931
+ }
2932
+ };
2933
+ const dispatchEvent = (event) => {
2934
+ if (getPreloadManager()) {
2935
+ getPreloadManager().dispatchEvent(event);
2936
+ } else {
2937
+ this.dispatchEvent(event);
2938
+ }
2939
+ };
2940
+ const getStats = () => {
2941
+ if (getPreloadManager()) {
2942
+ return getPreloadManager().getStats();
2943
+ } else {
2944
+ return this.stats_;
2945
+ }
2946
+ };
2947
+ /** @type {shaka.net.NetworkingEngine.onProgressUpdated} */
2948
+ const onProgressUpdated_ = (deltaTimeMs,
2949
+ bytesDownloaded, allowSwitch, request) => {
2648
2950
  // In some situations, such as during offline storage, the abr manager
2649
2951
  // might not yet exist. Therefore, we need to check if abr manager has
2650
2952
  // been initialized before using it.
2651
- if (this.abrManager_) {
2652
- this.abrManager_.segmentDownloaded(
2653
- deltaTimeMs, bytesDownloaded, allowSwitch);
2953
+ const abrManager = getAbrManager();
2954
+ if (abrManager) {
2955
+ abrManager.segmentDownloaded(deltaTimeMs, bytesDownloaded,
2956
+ allowSwitch, request);
2654
2957
  }
2655
2958
  };
2656
2959
  /** @type {shaka.net.NetworkingEngine.OnHeadersReceived} */
@@ -2661,10 +2964,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2661
2964
  .set('headers', headers)
2662
2965
  .set('request', request)
2663
2966
  .set('requestType', requestType);
2664
- this.dispatchEvent(this.makeEvent_(name, data));
2665
- if (this.cmsdManager_) {
2666
- this.cmsdManager_.processHeaders(headers);
2667
- }
2967
+ dispatchEvent(shaka.Player.makeEvent_(name, data));
2968
+ lateQueue(() => {
2969
+ if (this.cmsdManager_) {
2970
+ this.cmsdManager_.processHeaders(headers);
2971
+ }
2972
+ });
2668
2973
  };
2669
2974
  /** @type {shaka.net.NetworkingEngine.OnDownloadFailed} */
2670
2975
  const onDownloadFailed_ = (request, error, httpResponseCode, aborted) => {
@@ -2675,23 +2980,35 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2675
2980
  .set('error', error)
2676
2981
  .set('httpResponseCode', httpResponseCode)
2677
2982
  .set('aborted', aborted);
2678
- this.dispatchEvent(this.makeEvent_(name, data));
2983
+ dispatchEvent(shaka.Player.makeEvent_(name, data));
2679
2984
  };
2680
2985
  /** @type {shaka.net.NetworkingEngine.OnRequest} */
2681
2986
  const onRequest_ = (type, request, context) => {
2682
- this.cmcdManager_.applyData(type, request, context);
2987
+ lateQueue(() => {
2988
+ this.cmcdManager_.applyData(type, request, context);
2989
+ });
2683
2990
  };
2684
2991
 
2685
-
2992
+ /** @type {shaka.net.NetworkingEngine.OnRetry} */
2686
2993
  const onRetry_ = (type, context, newUrl, oldUrl) => {
2687
- if (this.parser_ && this.parser_.banLocation) {
2688
- this.parser_.banLocation(oldUrl);
2994
+ const parser = getParser();
2995
+ if (parser && parser.banLocation) {
2996
+ parser.banLocation(oldUrl);
2997
+ }
2998
+ };
2999
+
3000
+ /** @type {shaka.net.NetworkingEngine.OnResponse} */
3001
+ const onResponse_ = (type, response, context) => {
3002
+ const bytesDownloaded = response.data.byteLength;
3003
+ const stats = getStats();
3004
+ if (stats) {
3005
+ stats.addBytesDownloaded(bytesDownloaded);
2689
3006
  }
2690
3007
  };
2691
3008
 
2692
3009
  return new shaka.net.NetworkingEngine(
2693
3010
  onProgressUpdated_, onHeadersReceived_, onDownloadFailed_, onRequest_,
2694
- onRetry_);
3011
+ onRetry_, onResponse_);
2695
3012
  }
2696
3013
 
2697
3014
  /**
@@ -3018,11 +3335,33 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3018
3335
  delete config['streaming']['forceTransmux'];
3019
3336
  }
3020
3337
 
3338
+ // Deprecate 'streaming.useNativeHlsOnSafari' configuration.
3339
+ if (config['streaming'] && 'useNativeHlsOnSafari' in config['streaming']) {
3340
+ shaka.Deprecate.deprecateFeature(5,
3341
+ 'streaming.useNativeHlsOnSafari configuration',
3342
+ 'Please Use streaming.useNativeHlsForFairPlay or ' +
3343
+ 'streaming.preferNativeHls instead.');
3344
+ }
3345
+
3346
+ // Deprecate 'mediaSource.sourceBufferExtraFeatures' configuration.
3347
+ if (config['mediaSource'] &&
3348
+ 'sourceBufferExtraFeatures' in config['mediaSource']) {
3349
+ shaka.Deprecate.deprecateFeature(5,
3350
+ 'mediaSource.sourceBufferExtraFeatures configuration',
3351
+ 'Please Use mediaSource.addExtraFeaturesToSourceBuffer() instead.');
3352
+ const sourceBufferExtraFeatures =
3353
+ config['mediaSource']['sourceBufferExtraFeatures'];
3354
+ config['mediaSource']['addExtraFeaturesToSourceBuffer'] = () => {
3355
+ return sourceBufferExtraFeatures;
3356
+ };
3357
+ delete config['mediaSource']['sourceBufferExtraFeatures'];
3358
+ }
3359
+
3021
3360
  // If lowLatencyMode is enabled, and inaccurateManifestTolerance and
3022
3361
  // rebufferingGoal and segmentPrefetchLimit and baseDelay are not
3023
3362
  // specified, set inaccurateManifestTolerance to 0 and rebufferingGoal
3024
- // to 0.01 and segmentPrefetchLimit to 2 and baseDelay to 100 by default
3025
- // for low latency streaming.
3363
+ // to 0.01 and segmentPrefetchLimit to 2 and updateIntervalSeconds to
3364
+ // 0.1 and baseDelay to 100 by default for low latency streaming.
3026
3365
  if (config['streaming'] && config['streaming']['lowLatencyMode']) {
3027
3366
  if (config['streaming']['inaccurateManifestTolerance'] == undefined) {
3028
3367
  config['streaming']['inaccurateManifestTolerance'] = 0;
@@ -3033,6 +3372,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3033
3372
  if (config['streaming']['segmentPrefetchLimit'] == undefined) {
3034
3373
  config['streaming']['segmentPrefetchLimit'] = 2;
3035
3374
  }
3375
+ if (config['streaming']['updateIntervalSeconds'] == undefined) {
3376
+ config['streaming']['updateIntervalSeconds'] = 0.1;
3377
+ }
3036
3378
  if (config['streaming']['retryParameters'] == undefined) {
3037
3379
  config['streaming']['retryParameters'] = {};
3038
3380
  }
@@ -3070,6 +3412,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3070
3412
  * @private
3071
3413
  */
3072
3414
  applyConfig_() {
3415
+ this.manifestFilterer_ = new shaka.media.ManifestFilterer(
3416
+ this.config_, this.maxHwRes_, this.drmEngine_);
3073
3417
  if (this.parser_) {
3074
3418
  const manifestConfig =
3075
3419
  shaka.util.ObjectUtils.cloneObject(this.config_.manifest);
@@ -3086,9 +3430,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3086
3430
  this.streamingEngine_.configure(this.config_.streaming);
3087
3431
 
3088
3432
  // Need to apply the restrictions.
3433
+ // this.filterManifestWithRestrictions_() may throw.
3089
3434
  try {
3090
- // this.filterManifestWithRestrictions_() may throw.
3091
- this.filterManifestWithRestrictions_(this.manifest_);
3435
+ if (this.loadMode_ != shaka.Player.LoadMode.DESTROYED) {
3436
+ if (this.manifestFilterer_.filterManifestWithRestrictions(
3437
+ this.manifest_)) {
3438
+ this.onTracksChanged_();
3439
+ }
3440
+ }
3092
3441
  } catch (error) {
3093
3442
  this.onError_(error);
3094
3443
  }
@@ -3121,6 +3470,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3121
3470
  const textDisplayerFactory = this.config_.textDisplayFactory;
3122
3471
  if (this.lastTextFactory_ != textDisplayerFactory) {
3123
3472
  const displayer = textDisplayerFactory();
3473
+ if (displayer.configure) {
3474
+ displayer.configure(this.config_.textDisplayer);
3475
+ } else {
3476
+ shaka.Deprecate.deprecateFeature(5,
3477
+ 'Text displayer w/ configure',
3478
+ 'Text displayer should have a "configure" method!');
3479
+ }
3124
3480
  this.mediaSourceEngine_.setTextDisplayer(displayer);
3125
3481
  this.lastTextFactory_ = textDisplayerFactory;
3126
3482
 
@@ -3128,6 +3484,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3128
3484
  // Reload the text stream, so the cues will load again.
3129
3485
  this.streamingEngine_.reloadTextStream();
3130
3486
  }
3487
+ } else {
3488
+ const displayer = this.mediaSourceEngine_.getTextDisplayer();
3489
+ if (displayer.configure) {
3490
+ displayer.configure(this.config_.textDisplayer);
3491
+ }
3131
3492
  }
3132
3493
  }
3133
3494
  if (this.abrManager_) {
@@ -3397,24 +3758,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3397
3758
  }
3398
3759
  }
3399
3760
 
3400
- /**
3401
- * Return the value of lowLatencyMode configuration.
3402
- * @return {boolean}
3403
- * @private
3404
- */
3405
- isLowLatencyMode_() {
3406
- return this.config_.streaming.lowLatencyMode;
3407
- }
3408
-
3409
- /**
3410
- * Return the value of autoLowLatencyMode configuration.
3411
- * @return {boolean}
3412
- * @private
3413
- */
3414
- isAutoLowLatencyMode_() {
3415
- return this.config_.streaming.autoLowLatencyMode;
3416
- }
3417
-
3418
3761
  /**
3419
3762
  * Get the range of time (in seconds) that seeking is allowed. If the player
3420
3763
  * has not loaded content and the manifest is HLS, this will return a range
@@ -4472,19 +4815,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4472
4815
  }
4473
4816
 
4474
4817
  /**
4475
- * Get the current playhead position as a date. This should only be called
4476
- * when the player has loaded a live stream. If the player has not loaded a
4477
- * live stream, this will return <code>null</code>.
4818
+ * Get the current playhead position as a date.
4478
4819
  *
4479
4820
  * @return {Date}
4480
4821
  * @export
4481
4822
  */
4482
4823
  getPlayheadTimeAsDate() {
4483
- if (!this.isLive()) {
4484
- shaka.log.warning('getPlayheadTimeAsDate is for live streams!');
4485
- return null;
4486
- }
4487
-
4488
4824
  let presentationTime = 0;
4489
4825
  if (this.playhead_) {
4490
4826
  presentationTime = this.playhead_.getTime();
@@ -4521,22 +4857,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4521
4857
  }
4522
4858
 
4523
4859
  /**
4524
- * Get the presentation start time as a date. This should only be called when
4525
- * the player has loaded a live stream. If the player has not loaded a live
4526
- * stream, this will return <code>null</code>.
4860
+ * Get the presentation start time as a date.
4527
4861
  *
4528
4862
  * @return {Date}
4529
4863
  * @export
4530
4864
  */
4531
4865
  getPresentationStartTimeAsDate() {
4532
- if (!this.isLive()) {
4533
- shaka.log.warning('getPresentationStartTimeAsDate is for live streams!');
4534
- return null;
4535
- }
4536
-
4537
4866
  if (this.manifest_) {
4538
4867
  const timeline = this.manifest_.presentationTimeline;
4539
- const startTime = timeline.getPresentationStartTime();
4868
+ const startTime = timeline.getInitialProgramDateTime() ||
4869
+ timeline.getPresentationStartTime();
4540
4870
  goog.asserts.assert(startTime != null,
4541
4871
  'Presentation start time should not be null!');
4542
4872
  return new Date(/* ms= */ startTime * 1000);
@@ -4557,6 +4887,29 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4557
4887
  }
4558
4888
  }
4559
4889
 
4890
+ /**
4891
+ * Get the presentation segment availability duration. This should only be
4892
+ * called when the player has loaded a live stream. If the player has not
4893
+ * loaded a live stream, this will return <code>null</code>.
4894
+ *
4895
+ * @return {?number}
4896
+ * @export
4897
+ */
4898
+ getSegmentAvailabilityDuration() {
4899
+ if (!this.isLive()) {
4900
+ shaka.log.warning('getSegmentAvailabilityDuration is for live streams!');
4901
+ return null;
4902
+ }
4903
+
4904
+ if (this.manifest_) {
4905
+ const timeline = this.manifest_.presentationTimeline;
4906
+ return timeline.getSegmentAvailabilityDuration();
4907
+ } else {
4908
+ shaka.log.warning('No way to get segment segment availability duration!');
4909
+ return null;
4910
+ }
4911
+ }
4912
+
4560
4913
  /**
4561
4914
  * Get information about what the player has buffered. If the player has not
4562
4915
  * loaded content or is currently loading content, the buffered content will
@@ -5181,8 +5534,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5181
5534
 
5182
5535
  /**
5183
5536
  * Set the maximum resolution that the platform's hardware can handle.
5184
- * This will be called automatically by <code>shaka.cast.CastReceiver</code>
5185
- * to enforce limitations of the Chromecast hardware.
5186
5537
  *
5187
5538
  * @param {number} width
5188
5539
  * @param {number} height
@@ -5285,8 +5636,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5285
5636
  // TextDisplay factory must capture a reference to "this".
5286
5637
  config.textDisplayFactory = () => {
5287
5638
  if (this.videoContainer_) {
5639
+ const latestConfig = this.getConfiguration();
5288
5640
  return new shaka.text.UITextDisplayer(
5289
- this.video_, this.videoContainer_);
5641
+ this.video_, this.videoContainer_, latestConfig.textDisplayer);
5290
5642
  } else {
5291
5643
  // eslint-disable-next-line no-restricted-syntax
5292
5644
  if (HTMLMediaElement.prototype.addTextTrack) {
@@ -5412,82 +5764,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5412
5764
  }
5413
5765
  }
5414
5766
 
5415
- /**
5416
- * Filters a manifest, removing unplayable streams/variants.
5417
- *
5418
- * @param {?shaka.extern.Manifest} manifest
5419
- * @private
5420
- */
5421
- async filterManifest_(manifest) {
5422
- await this.filterManifestWithStreamUtils_(manifest);
5423
- this.filterManifestWithRestrictions_(manifest);
5424
- }
5425
-
5426
- /**
5427
- * Filters a manifest, removing unplayable streams/variants.
5428
- *
5429
- * @param {?shaka.extern.Manifest} manifest
5430
- * @private
5431
- */
5432
- async filterManifestWithStreamUtils_(manifest) {
5433
- goog.asserts.assert(manifest, 'Manifest should exist!');
5434
- await shaka.util.StreamUtils.filterManifest(this.drmEngine_, manifest);
5435
- this.checkPlayableVariants_(manifest);
5436
- }
5437
-
5438
-
5439
- /**
5440
- * Apply the restrictions configuration to the manifest, and check if there's
5441
- * a variant that meets the restrictions.
5442
- *
5443
- * @param {?shaka.extern.Manifest} manifest
5444
- * @private
5445
- */
5446
- filterManifestWithRestrictions_(manifest) {
5447
- // Return if |destroy| is called.
5448
- if (this.loadMode_ == shaka.Player.LoadMode.DESTROYED) {
5449
- return;
5450
- }
5451
- const tracksChanged = shaka.util.StreamUtils.applyRestrictions(
5452
- manifest.variants, this.config_.restrictions, this.maxHwRes_);
5453
- if (tracksChanged && this.streamingEngine_) {
5454
- this.onTracksChanged_();
5455
- }
5456
-
5457
- // We may need to create new sessions for any new init data.
5458
- const currentDrmInfo =
5459
- this.drmEngine_ ? this.drmEngine_.getDrmInfo() : null;
5460
- // DrmEngine.newInitData() requires mediaKeys to be available.
5461
- if (currentDrmInfo && this.drmEngine_.getMediaKeys()) {
5462
- for (const variant of manifest.variants) {
5463
- this.processDrmInfos_(currentDrmInfo.keySystem, variant.video);
5464
- this.processDrmInfos_(currentDrmInfo.keySystem, variant.audio);
5465
- }
5466
- }
5467
- this.checkRestrictedVariants_(manifest);
5468
- }
5469
-
5470
- /**
5471
- * @param {string} keySystem
5472
- * @param {?shaka.extern.Stream} stream
5473
- * @private
5474
- */
5475
- processDrmInfos_(keySystem, stream) {
5476
- if (!stream) {
5477
- return;
5478
- }
5479
-
5480
- for (const drmInfo of stream.drmInfos) {
5481
- // Ignore any data for different key systems.
5482
- if (drmInfo.keySystem == keySystem) {
5483
- for (const initData of (drmInfo.initData || [])) {
5484
- this.drmEngine_.newInitData(
5485
- initData.initDataType, initData.initData);
5486
- }
5487
- }
5488
- }
5489
- }
5490
-
5491
5767
  /**
5492
5768
  * @param {shaka.extern.Variant} initialVariant
5493
5769
  * @param {number} time
@@ -5566,7 +5842,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5566
5842
  // buffering.
5567
5843
  const eventName = shaka.util.FakeEvent.EventName.Buffering;
5568
5844
  const data = (new Map()).set('buffering', isBuffering);
5569
- this.dispatchEvent(this.makeEvent_(eventName, data));
5845
+ this.dispatchEvent(shaka.Player.makeEvent_(eventName, data));
5570
5846
  }
5571
5847
 
5572
5848
  /**
@@ -5599,7 +5875,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5599
5875
  this.playRateController_.set(newRate);
5600
5876
  }
5601
5877
 
5602
- const event = this.makeEvent_(shaka.util.FakeEvent.EventName.RateChange);
5878
+ const event = shaka.Player.makeEvent_(
5879
+ shaka.util.FakeEvent.EventName.RateChange);
5603
5880
  this.dispatchEvent(event);
5604
5881
  }
5605
5882
 
@@ -5635,20 +5912,46 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5635
5912
  if (stateChanged) {
5636
5913
  const eventName = shaka.util.FakeEvent.EventName.StateChanged;
5637
5914
  const data = (new Map()).set('newstate', updateState);
5638
- this.dispatchEvent(this.makeEvent_(eventName, data));
5915
+ this.dispatchEvent(shaka.Player.makeEvent_(eventName, data));
5639
5916
  }
5640
5917
  }
5641
5918
 
5642
5919
  /**
5643
- * Callback for liveSync
5920
+ * Callback for liveSync and vodDynamicPlaybackRate
5644
5921
  *
5645
5922
  * @private
5646
5923
  */
5647
5924
  onTimeUpdate_() {
5925
+ const playbackRate = this.video_.playbackRate;
5926
+ const isLive = this.isLive();
5927
+
5928
+ if (this.config_.streaming.vodDynamicPlaybackRate && !isLive) {
5929
+ const minPlaybackRate =
5930
+ this.config_.streaming.vodDynamicPlaybackRateLowBufferRate;
5931
+ const bufferFullness = this.getBufferFullness();
5932
+ const bufferThreshold =
5933
+ this.config_.streaming.vodDynamicPlaybackRateBufferRatio;
5934
+
5935
+ if (bufferFullness <= bufferThreshold) {
5936
+ if (playbackRate != minPlaybackRate) {
5937
+ shaka.log.debug('Buffer fullness ratio (' + bufferFullness + ') ' +
5938
+ 'is less than the vodDynamicPlaybackRateBufferRatio (' +
5939
+ bufferThreshold + '). Updating playbackRate to ' + minPlaybackRate);
5940
+ this.trickPlay(minPlaybackRate);
5941
+ }
5942
+ } else if (bufferFullness == 1) {
5943
+ if (playbackRate !== this.playRateController_.getDefaultRate()) {
5944
+ shaka.log.debug('Buffer is full. Cancel trick play.');
5945
+ this.cancelTrickPlay();
5946
+ }
5947
+ }
5948
+ }
5949
+
5648
5950
  // If the live stream has reached its end, do not sync.
5649
- if (!this.isLive()) {
5951
+ if (!isLive) {
5650
5952
  return;
5651
5953
  }
5954
+
5652
5955
  const seekRange = this.seekRange();
5653
5956
  if (!Number.isFinite(seekRange.end)) {
5654
5957
  return;
@@ -5693,7 +5996,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5693
5996
  }
5694
5997
  }
5695
5998
 
5696
- const playbackRate = this.video_.playbackRate;
5697
5999
  const latency = seekRange.end - this.video_.currentTime;
5698
6000
  let offset = 0;
5699
6001
  // In src= mode, the seek range isn't updated frequently enough, so we need
@@ -5708,7 +6010,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5708
6010
  }
5709
6011
  }
5710
6012
 
5711
- if (liveSyncMaxLatency && liveSyncPlaybackRate &&
6013
+ const panicMode = this.config_.streaming.liveSyncPanicMode;
6014
+ const panicThreshold = this.config_.streaming.liveSyncPanicThreshold * 1000;
6015
+ const timeSinceLastRebuffer =
6016
+ Date.now() - this.bufferObserver_.getLastRebufferTime();
6017
+ if (panicMode && !liveSyncMinPlaybackRate) {
6018
+ liveSyncMinPlaybackRate = this.config_.streaming.liveSyncMinPlaybackRate;
6019
+ }
6020
+
6021
+ if (panicMode && liveSyncMinPlaybackRate &&
6022
+ timeSinceLastRebuffer <= panicThreshold) {
6023
+ if (playbackRate != liveSyncMinPlaybackRate) {
6024
+ shaka.log.debug('Time since last rebuffer (' +
6025
+ timeSinceLastRebuffer + 's) ' +
6026
+ 'is less than the liveSyncPanicThreshold (' + panicThreshold +
6027
+ 's). Updating playbackRate to ' + liveSyncMinPlaybackRate);
6028
+ this.trickPlay(liveSyncMinPlaybackRate);
6029
+ }
6030
+ } else if (liveSyncMaxLatency && liveSyncPlaybackRate &&
5712
6031
  (latency - offset) > liveSyncMaxLatency) {
5713
6032
  if (playbackRate != liveSyncPlaybackRate) {
5714
6033
  shaka.log.debug('Latency (' + latency + 's) ' +
@@ -5756,15 +6075,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5756
6075
  if (hasNewCompletionPercent) {
5757
6076
  let event;
5758
6077
  if (this.completionPercent_ == 0) {
5759
- event = this.makeEvent_(shaka.util.FakeEvent.EventName.Started);
6078
+ event = shaka.Player.makeEvent_(shaka.util.FakeEvent.EventName.Started);
5760
6079
  } else if (this.completionPercent_ == 25) {
5761
- event = this.makeEvent_(shaka.util.FakeEvent.EventName.FirstQuartile);
6080
+ event = shaka.Player.makeEvent_(
6081
+ shaka.util.FakeEvent.EventName.FirstQuartile);
5762
6082
  } else if (this.completionPercent_ == 50) {
5763
- event = this.makeEvent_(shaka.util.FakeEvent.EventName.Midpoint);
6083
+ event = shaka.Player.makeEvent_(
6084
+ shaka.util.FakeEvent.EventName.Midpoint);
5764
6085
  } else if (this.completionPercent_ == 75) {
5765
- event = this.makeEvent_(shaka.util.FakeEvent.EventName.ThirdQuartile);
6086
+ event = shaka.Player.makeEvent_(
6087
+ shaka.util.FakeEvent.EventName.ThirdQuartile);
5766
6088
  } else if (this.completionPercent_ == 100) {
5767
- event = this.makeEvent_(shaka.util.FakeEvent.EventName.Complete);
6089
+ event = shaka.Player.makeEvent_(
6090
+ shaka.util.FakeEvent.EventName.Complete);
5768
6091
  }
5769
6092
  if (event) {
5770
6093
  this.dispatchEvent(event);
@@ -5804,7 +6127,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5804
6127
  updateAbrManagerVariants_() {
5805
6128
  try {
5806
6129
  goog.asserts.assert(this.manifest_, 'Manifest should exist by now!');
5807
- this.checkRestrictedVariants_(this.manifest_);
6130
+ this.manifestFilterer_.checkRestrictedVariants(this.manifest_);
5808
6131
  } catch (e) {
5809
6132
  this.onError_(e);
5810
6133
  return false;
@@ -6133,7 +6456,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6133
6456
  .set('start', start)
6134
6457
  .set('end', end)
6135
6458
  .set('contentType', contentType);
6136
- this.dispatchEvent(this.makeEvent_(
6459
+ this.dispatchEvent(shaka.Player.makeEvent_(
6137
6460
  shaka.util.FakeEvent.EventName.SegmentAppended, data));
6138
6461
  }
6139
6462
 
@@ -6183,8 +6506,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6183
6506
  if (this.lcevcDec_) {
6184
6507
  this.lcevcDec_.updateVariant(to, this.getManifestType());
6185
6508
  }
6186
- const event =
6187
- this.makeEvent_(shaka.util.FakeEvent.EventName.Adaptation, data);
6509
+ const event = shaka.Player.makeEvent_(
6510
+ shaka.util.FakeEvent.EventName.Adaptation, data);
6188
6511
  this.delayDispatchEvent_(event);
6189
6512
  }
6190
6513
 
@@ -6195,7 +6518,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6195
6518
  onTracksChanged_() {
6196
6519
  // Delay the 'trackschanged' event so StreamingEngine has time to absorb the
6197
6520
  // changes before the user tries to query it.
6198
- const event = this.makeEvent_(shaka.util.FakeEvent.EventName.TracksChanged);
6521
+ const event = shaka.Player.makeEvent_(
6522
+ shaka.util.FakeEvent.EventName.TracksChanged);
6199
6523
  this.delayDispatchEvent_(event);
6200
6524
  }
6201
6525
 
@@ -6215,8 +6539,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6215
6539
  this.lcevcDec_.updateVariant(to, this.getManifestType());
6216
6540
  }
6217
6541
 
6218
- const event =
6219
- this.makeEvent_(shaka.util.FakeEvent.EventName.VariantChanged, data);
6542
+ const event = shaka.Player.makeEvent_(
6543
+ shaka.util.FakeEvent.EventName.VariantChanged, data);
6220
6544
  this.delayDispatchEvent_(event);
6221
6545
  }
6222
6546
 
@@ -6227,14 +6551,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6227
6551
  onTextChanged_() {
6228
6552
  // Delay the 'textchanged' event so StreamingEngine time to absorb the
6229
6553
  // changes before the user tries to query it.
6230
- const event = this.makeEvent_(shaka.util.FakeEvent.EventName.TextChanged);
6554
+ const event = shaka.Player.makeEvent_(
6555
+ shaka.util.FakeEvent.EventName.TextChanged);
6231
6556
  this.delayDispatchEvent_(event);
6232
6557
  }
6233
6558
 
6234
6559
  /** @private */
6235
6560
  onTextTrackVisibility_() {
6236
- const event =
6237
- this.makeEvent_(shaka.util.FakeEvent.EventName.TextTrackVisibility);
6561
+ const event = shaka.Player.makeEvent_(
6562
+ shaka.util.FakeEvent.EventName.TextTrackVisibility);
6238
6563
  this.delayDispatchEvent_(event);
6239
6564
  }
6240
6565
 
@@ -6246,7 +6571,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6246
6571
  }
6247
6572
 
6248
6573
  const data = (new Map()).set('newStatus', this.config_.abr.enabled);
6249
- this.delayDispatchEvent_(this.makeEvent_(
6574
+ this.delayDispatchEvent_(shaka.Player.makeEvent_(
6250
6575
  shaka.util.FakeEvent.EventName.AbrStatusChanged, data));
6251
6576
  }
6252
6577
 
@@ -6380,7 +6705,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6380
6705
  }
6381
6706
 
6382
6707
  const eventName = shaka.util.FakeEvent.EventName.Error;
6383
- const event = this.makeEvent_(eventName, (new Map()).set('detail', error));
6708
+ const event = shaka.Player.makeEvent_(
6709
+ eventName, (new Map()).set('detail', error));
6384
6710
  this.dispatchEvent(event);
6385
6711
  if (event.defaultPrevented) {
6386
6712
  error.handled = true;
@@ -6395,10 +6721,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6395
6721
  *
6396
6722
  * @param {!shaka.util.FakeEvent.EventName} eventName
6397
6723
  * @param {shaka.extern.TimelineRegionInfo} region
6724
+ * @param {shaka.util.FakeEventTarget=} eventTarget
6398
6725
  *
6399
6726
  * @private
6400
6727
  */
6401
- onRegionEvent_(eventName, region) {
6728
+ onRegionEvent_(eventName, region, eventTarget = this) {
6402
6729
  // Always make a copy to avoid exposing our internal data to the app.
6403
6730
  const clone = {
6404
6731
  schemeIdUri: region.schemeIdUri,
@@ -6407,10 +6734,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6407
6734
  endTime: region.endTime,
6408
6735
  id: region.id,
6409
6736
  eventElement: region.eventElement,
6737
+ eventNode: region.eventNode,
6410
6738
  };
6411
6739
 
6412
6740
  const data = (new Map()).set('detail', clone);
6413
- this.dispatchEvent(this.makeEvent_(eventName, data));
6741
+ eventTarget.dispatchEvent(shaka.Player.makeEvent_(eventName, data));
6414
6742
  }
6415
6743
 
6416
6744
  /**
@@ -6441,7 +6769,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6441
6769
  .set('mediaQuality', clone)
6442
6770
  .set('position', position);
6443
6771
 
6444
- this.dispatchEvent(this.makeEvent_(
6772
+ this.dispatchEvent(shaka.Player.makeEvent_(
6445
6773
  shaka.util.FakeEvent.EventName.MediaQualityChanged, data));
6446
6774
  }
6447
6775
 
@@ -6504,14 +6832,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6504
6832
  * @private
6505
6833
  */
6506
6834
  onKeyStatus_(keyStatusMap) {
6507
- if (!this.streamingEngine_) {
6508
- // We can't use this info to manage restrictions in src= mode, so ignore
6509
- // it.
6510
- return;
6511
- }
6835
+ goog.asserts.assert(this.streamingEngine_, 'Cannot be called in src= mode');
6512
6836
 
6513
- const event =
6514
- this.makeEvent_(shaka.util.FakeEvent.EventName.KeyStatusChanged);
6837
+ const event = shaka.Player.makeEvent_(
6838
+ shaka.util.FakeEvent.EventName.KeyStatusChanged);
6515
6839
  this.dispatchEvent(event);
6516
6840
 
6517
6841
  const keyIds = Object.keys(keyStatusMap);
@@ -6533,7 +6857,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6533
6857
  'restrictions so we don\'t select those tracks.');
6534
6858
  }
6535
6859
 
6536
- const restrictedStatuses = shaka.Player.restrictedStatuses_;
6860
+ const restrictedStatuses = shaka.media.ManifestFilterer.restrictedStatuses;
6537
6861
  let tracksChanged = false;
6538
6862
 
6539
6863
  goog.asserts.assert(this.drmEngine_, 'drmEngine should be non-null here.');
@@ -6582,22 +6906,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6582
6906
  }
6583
6907
  }
6584
6908
 
6585
- /**
6586
- * Callback from DrmEngine
6587
- * @param {string} keyId
6588
- * @param {number} expiration
6589
- * @private
6590
- */
6591
- onExpirationUpdated_(keyId, expiration) {
6592
- if (this.parser_ && this.parser_.onExpirationUpdated) {
6593
- this.parser_.onExpirationUpdated(keyId, expiration);
6594
- }
6595
-
6596
- const event =
6597
- this.makeEvent_(shaka.util.FakeEvent.EventName.ExpirationUpdated);
6598
- this.dispatchEvent(event);
6599
- }
6600
-
6601
6909
  /**
6602
6910
  * @return {boolean} true if we should stream text right now.
6603
6911
  * @private
@@ -6641,93 +6949,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6641
6949
  }
6642
6950
  }
6643
6951
 
6644
- /**
6645
- * Checks if the variants are all restricted, and throw an appropriate
6646
- * exception if so.
6647
- *
6648
- * @param {shaka.extern.Manifest} manifest
6649
- *
6650
- * @private
6651
- */
6652
- checkRestrictedVariants_(manifest) {
6653
- const restrictedStatuses = shaka.Player.restrictedStatuses_;
6654
- const keyStatusMap =
6655
- this.drmEngine_ ? this.drmEngine_.getKeyStatuses() : {};
6656
- const keyIds = Object.keys(keyStatusMap);
6657
- const isGlobalStatus = keyIds.length && keyIds[0] == '00';
6658
-
6659
- let hasPlayable = false;
6660
- let hasAppRestrictions = false;
6661
-
6662
- /** @type {!Set.<string>} */
6663
- const missingKeys = new Set();
6664
-
6665
- /** @type {!Set.<string>} */
6666
- const badKeyStatuses = new Set();
6667
-
6668
- for (const variant of manifest.variants) {
6669
- // TODO: Combine with onKeyStatus_.
6670
- const streams = [];
6671
- if (variant.audio) {
6672
- streams.push(variant.audio);
6673
- }
6674
- if (variant.video) {
6675
- streams.push(variant.video);
6676
- }
6677
-
6678
- for (const stream of streams) {
6679
- if (stream.keyIds.size) {
6680
- for (const keyId of stream.keyIds) {
6681
- const keyStatus = keyStatusMap[isGlobalStatus ? '00' : keyId];
6682
- if (!keyStatus) {
6683
- missingKeys.add(keyId);
6684
- } else if (restrictedStatuses.includes(keyStatus)) {
6685
- badKeyStatuses.add(keyStatus);
6686
- }
6687
- }
6688
- } // if (stream.keyIds.size)
6689
- }
6690
-
6691
- if (!variant.allowedByApplication) {
6692
- hasAppRestrictions = true;
6693
- } else if (variant.allowedByKeySystem) {
6694
- hasPlayable = true;
6695
- }
6696
- }
6697
-
6698
- if (!hasPlayable) {
6699
- /** @type {shaka.extern.RestrictionInfo} */
6700
- const data = {
6701
- hasAppRestrictions,
6702
- missingKeys: Array.from(missingKeys),
6703
- restrictedKeyStatuses: Array.from(badKeyStatuses),
6704
- };
6705
- throw new shaka.util.Error(
6706
- shaka.util.Error.Severity.CRITICAL,
6707
- shaka.util.Error.Category.MANIFEST,
6708
- shaka.util.Error.Code.RESTRICTIONS_CANNOT_BE_MET,
6709
- data);
6710
- }
6711
- }
6712
-
6713
- /**
6714
- * Confirm some variants are playable. Otherwise, throw an exception.
6715
- * @param {!shaka.extern.Manifest} manifest
6716
- * @private
6717
- */
6718
- checkPlayableVariants_(manifest) {
6719
- const valid = manifest.variants.some(shaka.util.StreamUtils.isPlayable);
6720
-
6721
- // If none of the variants are playable, throw
6722
- // CONTENT_UNSUPPORTED_BY_BROWSER.
6723
- if (!valid) {
6724
- throw new shaka.util.Error(
6725
- shaka.util.Error.Severity.CRITICAL,
6726
- shaka.util.Error.Category.MANIFEST,
6727
- shaka.util.Error.Code.CONTENT_UNSUPPORTED_BY_BROWSER);
6728
- }
6729
- }
6730
-
6731
6952
  /**
6732
6953
  * Fire an event, but wait a little bit so that the immediate execution can
6733
6954
  * complete before the event is handled.
@@ -6960,25 +7181,13 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6960
7181
  * @export
6961
7182
  */
6962
7183
  // eslint-disable-next-line no-useless-concat
6963
- shaka.Player.version = 'v4.7.13' + '-uncompiled'; // x-release-please-version
7184
+ shaka.Player.version = 'v4.8.0' + '-uncompiled'; // x-release-please-version
6964
7185
 
6965
7186
  // Initialize the deprecation system using the version string we just set
6966
7187
  // on the player.
6967
7188
  shaka.Deprecate.init(shaka.Player.version);
6968
7189
 
6969
7190
 
6970
- /**
6971
- * These are the EME key statuses that represent restricted playback.
6972
- * 'usable', 'released', 'output-downscaled', 'status-pending' are statuses
6973
- * of the usable keys. 'expired' status is being handled separately in
6974
- * DrmEngine.
6975
- *
6976
- * @const {!Array.<string>}
6977
- * @private
6978
- */
6979
- shaka.Player.restrictedStatuses_ = ['output-restricted', 'internal-error'];
6980
-
6981
-
6982
7191
  /** @private {!Object.<string, function():*>} */
6983
7192
  shaka.Player.supportPlugins_ = {};
6984
7193