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.
- package/AUTHORS +1 -0
- package/CONTRIBUTORS +1 -0
- package/LICENSE +54 -0
- package/README.md +22 -1
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/deps.js +34 -23
- package/dist/locales.js +44 -34
- package/dist/shaka-player.compiled.d.ts +194 -44
- package/dist/shaka-player.compiled.debug.d.ts +194 -44
- package/dist/shaka-player.compiled.debug.externs.js +474 -337
- package/dist/shaka-player.compiled.debug.js +949 -832
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +474 -337
- package/dist/shaka-player.compiled.js +1237 -1171
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +257 -47
- package/dist/shaka-player.ui.debug.d.ts +257 -47
- package/dist/shaka-player.ui.debug.externs.js +591 -376
- package/dist/shaka-player.ui.debug.js +1165 -965
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +591 -376
- package/dist/shaka-player.ui.js +1521 -1404
- package/dist/shaka-player.ui.map +4 -4
- package/externs/device_sensor_event.js +18 -0
- package/externs/hisense.js +16 -0
- package/externs/playstation.js +31 -0
- package/externs/shaka/abr_manager.js +3 -1
- package/externs/shaka/ads.js +5 -0
- package/externs/shaka/codecs.js +13 -0
- package/externs/shaka/manifest.js +58 -6
- package/externs/shaka/net.js +13 -1
- package/externs/shaka/player.js +184 -13
- package/externs/shaka/text.js +19 -0
- package/externs/tizen.js +41 -0
- package/externs/webos.js +19 -0
- package/externs/xbox.js +43 -0
- package/lib/abr/simple_abr_manager.js +9 -1
- package/lib/ads/ad_manager.js +19 -2
- package/lib/ads/client_side_ad_manager.js +5 -3
- package/lib/ads/media_tailor_ad_manager.js +9 -0
- package/lib/cast/cast_receiver.js +0 -14
- package/lib/cast/cast_utils.js +1 -0
- package/lib/cea/mp4_cea_parser.js +6 -1
- package/lib/dash/content_protection.js +97 -53
- package/lib/dash/dash_parser.js +230 -176
- package/lib/dash/mpd_utils.js +72 -65
- package/lib/dash/segment_base.js +27 -21
- package/lib/dash/segment_list.js +18 -16
- package/lib/dash/segment_template.js +26 -21
- package/lib/hls/hls_parser.js +358 -164
- package/lib/media/buffering_observer.js +25 -1
- package/lib/media/drm_engine.js +91 -71
- package/lib/media/manifest_filterer.js +220 -0
- package/lib/media/media_source_engine.js +34 -5
- package/lib/media/preload_manager.js +827 -0
- package/lib/media/presentation_timeline.js +11 -0
- package/lib/media/segment_index.js +40 -30
- package/lib/media/segment_prefetch.js +140 -43
- package/lib/media/segment_reference.js +31 -8
- package/lib/media/segment_utils.js +208 -25
- package/lib/media/streaming_engine.js +319 -63
- package/lib/mss/content_protection.js +31 -25
- package/lib/mss/mss_parser.js +74 -55
- package/lib/net/networking_engine.js +49 -4
- package/lib/offline/storage.js +3 -3
- package/lib/player.js +882 -673
- package/lib/polyfill/media_capabilities.js +16 -1
- package/lib/text/simple_text_displayer.js +9 -0
- package/lib/text/stub_text_displayer.js +7 -0
- package/lib/text/text_engine.js +16 -3
- package/lib/text/ttml_text_parser.js +116 -114
- package/lib/text/ui_text_displayer.js +28 -11
- package/lib/text/vtt_text_parser.js +54 -115
- package/lib/transmuxer/opus.js +91 -0
- package/lib/transmuxer/ts_transmuxer.js +115 -0
- package/lib/util/cmcd_manager.js +152 -3
- package/lib/util/dom_utils.js +11 -0
- package/lib/util/error.js +5 -0
- package/lib/util/fake_event.js +2 -0
- package/lib/util/manifest_parser_utils.js +97 -3
- package/lib/util/mp4_box_parsers.js +346 -17
- package/lib/util/mp4_generator.js +23 -0
- package/lib/util/periods.js +19 -0
- package/lib/util/platform.js +161 -9
- package/lib/util/player_configuration.js +61 -3
- package/lib/util/stats.js +16 -0
- package/lib/util/stream_utils.js +16 -6
- package/lib/util/string_utils.js +36 -0
- package/lib/util/tXml.js +814 -0
- package/lib/util/ts_parser.js +50 -0
- package/lib/util/xml_utils.js +10 -338
- package/package.json +2 -2
- package/shaka-player.uncompiled.js +8 -0
- package/ui/controls.js +187 -5
- package/ui/enums.js +2 -0
- package/ui/externs/ui.js +30 -3
- package/ui/gl_matrix/matrix_4x4.js +568 -0
- package/ui/gl_matrix/matrix_quaternion.js +45 -0
- package/ui/less/containers.less +11 -0
- package/ui/locales/en-GB.json +2 -0
- package/ui/locales/en.json +2 -0
- package/ui/locales/es-419.json +2 -0
- package/ui/locales/es.json +2 -0
- package/ui/locales/source.json +8 -0
- package/ui/recenter_vr.js +126 -0
- package/ui/resolution_selection.js +25 -5
- package/ui/statistics_button.js +12 -0
- package/ui/text_selection.js +1 -1
- package/ui/toggle_stereoscopic.js +127 -0
- package/ui/ui.js +74 -38
- package/ui/vr_manager.js +557 -0
- package/ui/vr_utils.js +232 -0
- 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(
|
|
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
|
-
|
|
1065
|
+
if (!noop) {
|
|
1066
|
+
this.makeStateChangeEvent_('attach');
|
|
1031
1067
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
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
|
-
|
|
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}
|
|
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(
|
|
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(
|
|
1449
|
+
this.dispatchEvent(shaka.Player.makeEvent_(
|
|
1393
1450
|
shaka.util.FakeEvent.EventName.Loading));
|
|
1394
1451
|
|
|
1395
|
-
|
|
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_ =
|
|
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.
|
|
1426
|
-
}, '
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
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_(
|
|
1586
|
+
await this.loadInner_(
|
|
1587
|
+
startTimeOfLoad, prefetchedVariant, segmentPrefetchById);
|
|
1432
1588
|
}, 'loadInner_');
|
|
1589
|
+
preloadManager.stopQueuingLatePhaseQueuedOperations();
|
|
1433
1590
|
}
|
|
1434
|
-
this.dispatchEvent(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1814
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
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.
|
|
2003
|
-
|
|
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_
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
2647
|
-
|
|
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
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
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
|
-
|
|
2665
|
-
|
|
2666
|
-
this.cmsdManager_
|
|
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
|
-
|
|
2983
|
+
dispatchEvent(shaka.Player.makeEvent_(name, data));
|
|
2679
2984
|
};
|
|
2680
2985
|
/** @type {shaka.net.NetworkingEngine.OnRequest} */
|
|
2681
2986
|
const onRequest_ = (type, request, context) => {
|
|
2682
|
-
|
|
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
|
-
|
|
2688
|
-
|
|
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
|
|
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
|
-
|
|
3091
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
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 =
|
|
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(
|
|
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 (!
|
|
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
|
-
|
|
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 =
|
|
6078
|
+
event = shaka.Player.makeEvent_(shaka.util.FakeEvent.EventName.Started);
|
|
5760
6079
|
} else if (this.completionPercent_ == 25) {
|
|
5761
|
-
event =
|
|
6080
|
+
event = shaka.Player.makeEvent_(
|
|
6081
|
+
shaka.util.FakeEvent.EventName.FirstQuartile);
|
|
5762
6082
|
} else if (this.completionPercent_ == 50) {
|
|
5763
|
-
event =
|
|
6083
|
+
event = shaka.Player.makeEvent_(
|
|
6084
|
+
shaka.util.FakeEvent.EventName.Midpoint);
|
|
5764
6085
|
} else if (this.completionPercent_ == 75) {
|
|
5765
|
-
event =
|
|
6086
|
+
event = shaka.Player.makeEvent_(
|
|
6087
|
+
shaka.util.FakeEvent.EventName.ThirdQuartile);
|
|
5766
6088
|
} else if (this.completionPercent_ == 100) {
|
|
5767
|
-
event =
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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_(
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|