shaka-player 4.7.13 → 4.8.1
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 +2 -0
- package/CONTRIBUTORS +2 -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 +41 -31
- package/dist/shaka-player.compiled.d.ts +196 -45
- package/dist/shaka-player.compiled.debug.d.ts +196 -45
- package/dist/shaka-player.compiled.debug.externs.js +491 -348
- package/dist/shaka-player.compiled.debug.js +969 -847
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +491 -348
- package/dist/shaka-player.compiled.js +1238 -1171
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +259 -48
- package/dist/shaka-player.ui.debug.d.ts +259 -48
- package/dist/shaka-player.ui.debug.externs.js +598 -377
- package/dist/shaka-player.ui.debug.js +1185 -980
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +598 -377
- package/dist/shaka-player.ui.js +1526 -1408
- 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 +237 -177
- 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 +45 -22
- 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 +93 -47
- package/lib/media/segment_prefetch.js +140 -43
- package/lib/media/segment_reference.js +43 -8
- package/lib/media/segment_utils.js +208 -25
- package/lib/media/streaming_engine.js +355 -66
- package/lib/mss/content_protection.js +31 -25
- package/lib/mss/mss_parser.js +79 -55
- package/lib/net/networking_engine.js +49 -4
- package/lib/offline/storage.js +3 -3
- package/lib/player.js +882 -672
- 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 +153 -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 +79 -13
- package/lib/util/stats.js +16 -0
- package/lib/util/stream_utils.js +22 -8
- 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
|
+
}
|
|
1441
1604
|
}
|
|
1442
1605
|
}
|
|
1443
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
|
+
}
|
|
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;
|
|
1965
|
+
}
|
|
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
2101
|
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);
|
|
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
|
/**
|
|
@@ -2929,6 +3246,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2929
3246
|
const playerInterface = {
|
|
2930
3247
|
getPresentationTime: () => this.playhead_ ? this.playhead_.getTime() : 0,
|
|
2931
3248
|
getBandwidthEstimate: () => this.abrManager_.getBandwidthEstimate(),
|
|
3249
|
+
getPlaybackRate: () => this.getPlaybackRate(),
|
|
2932
3250
|
mediaSourceEngine: this.mediaSourceEngine_,
|
|
2933
3251
|
netEngine: this.networkingEngine_,
|
|
2934
3252
|
onError: (error) => this.onError_(error),
|
|
@@ -3018,11 +3336,33 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3018
3336
|
delete config['streaming']['forceTransmux'];
|
|
3019
3337
|
}
|
|
3020
3338
|
|
|
3339
|
+
// Deprecate 'streaming.useNativeHlsOnSafari' configuration.
|
|
3340
|
+
if (config['streaming'] && 'useNativeHlsOnSafari' in config['streaming']) {
|
|
3341
|
+
shaka.Deprecate.deprecateFeature(5,
|
|
3342
|
+
'streaming.useNativeHlsOnSafari configuration',
|
|
3343
|
+
'Please Use streaming.useNativeHlsForFairPlay or ' +
|
|
3344
|
+
'streaming.preferNativeHls instead.');
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
// Deprecate 'mediaSource.sourceBufferExtraFeatures' configuration.
|
|
3348
|
+
if (config['mediaSource'] &&
|
|
3349
|
+
'sourceBufferExtraFeatures' in config['mediaSource']) {
|
|
3350
|
+
shaka.Deprecate.deprecateFeature(5,
|
|
3351
|
+
'mediaSource.sourceBufferExtraFeatures configuration',
|
|
3352
|
+
'Please Use mediaSource.addExtraFeaturesToSourceBuffer() instead.');
|
|
3353
|
+
const sourceBufferExtraFeatures =
|
|
3354
|
+
config['mediaSource']['sourceBufferExtraFeatures'];
|
|
3355
|
+
config['mediaSource']['addExtraFeaturesToSourceBuffer'] = () => {
|
|
3356
|
+
return sourceBufferExtraFeatures;
|
|
3357
|
+
};
|
|
3358
|
+
delete config['mediaSource']['sourceBufferExtraFeatures'];
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3021
3361
|
// If lowLatencyMode is enabled, and inaccurateManifestTolerance and
|
|
3022
3362
|
// rebufferingGoal and segmentPrefetchLimit and baseDelay are not
|
|
3023
3363
|
// specified, set inaccurateManifestTolerance to 0 and rebufferingGoal
|
|
3024
|
-
// to 0.01 and segmentPrefetchLimit to 2 and
|
|
3025
|
-
// for low latency streaming.
|
|
3364
|
+
// to 0.01 and segmentPrefetchLimit to 2 and updateIntervalSeconds to
|
|
3365
|
+
// 0.1 and baseDelay to 100 by default for low latency streaming.
|
|
3026
3366
|
if (config['streaming'] && config['streaming']['lowLatencyMode']) {
|
|
3027
3367
|
if (config['streaming']['inaccurateManifestTolerance'] == undefined) {
|
|
3028
3368
|
config['streaming']['inaccurateManifestTolerance'] = 0;
|
|
@@ -3033,6 +3373,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3033
3373
|
if (config['streaming']['segmentPrefetchLimit'] == undefined) {
|
|
3034
3374
|
config['streaming']['segmentPrefetchLimit'] = 2;
|
|
3035
3375
|
}
|
|
3376
|
+
if (config['streaming']['updateIntervalSeconds'] == undefined) {
|
|
3377
|
+
config['streaming']['updateIntervalSeconds'] = 0.1;
|
|
3378
|
+
}
|
|
3036
3379
|
if (config['streaming']['retryParameters'] == undefined) {
|
|
3037
3380
|
config['streaming']['retryParameters'] = {};
|
|
3038
3381
|
}
|
|
@@ -3070,6 +3413,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3070
3413
|
* @private
|
|
3071
3414
|
*/
|
|
3072
3415
|
applyConfig_() {
|
|
3416
|
+
this.manifestFilterer_ = new shaka.media.ManifestFilterer(
|
|
3417
|
+
this.config_, this.maxHwRes_, this.drmEngine_);
|
|
3073
3418
|
if (this.parser_) {
|
|
3074
3419
|
const manifestConfig =
|
|
3075
3420
|
shaka.util.ObjectUtils.cloneObject(this.config_.manifest);
|
|
@@ -3086,9 +3431,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3086
3431
|
this.streamingEngine_.configure(this.config_.streaming);
|
|
3087
3432
|
|
|
3088
3433
|
// Need to apply the restrictions.
|
|
3434
|
+
// this.filterManifestWithRestrictions_() may throw.
|
|
3089
3435
|
try {
|
|
3090
|
-
|
|
3091
|
-
|
|
3436
|
+
if (this.loadMode_ != shaka.Player.LoadMode.DESTROYED) {
|
|
3437
|
+
if (this.manifestFilterer_.filterManifestWithRestrictions(
|
|
3438
|
+
this.manifest_)) {
|
|
3439
|
+
this.onTracksChanged_();
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3092
3442
|
} catch (error) {
|
|
3093
3443
|
this.onError_(error);
|
|
3094
3444
|
}
|
|
@@ -3121,6 +3471,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3121
3471
|
const textDisplayerFactory = this.config_.textDisplayFactory;
|
|
3122
3472
|
if (this.lastTextFactory_ != textDisplayerFactory) {
|
|
3123
3473
|
const displayer = textDisplayerFactory();
|
|
3474
|
+
if (displayer.configure) {
|
|
3475
|
+
displayer.configure(this.config_.textDisplayer);
|
|
3476
|
+
} else {
|
|
3477
|
+
shaka.Deprecate.deprecateFeature(5,
|
|
3478
|
+
'Text displayer w/ configure',
|
|
3479
|
+
'Text displayer should have a "configure" method!');
|
|
3480
|
+
}
|
|
3124
3481
|
this.mediaSourceEngine_.setTextDisplayer(displayer);
|
|
3125
3482
|
this.lastTextFactory_ = textDisplayerFactory;
|
|
3126
3483
|
|
|
@@ -3128,6 +3485,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3128
3485
|
// Reload the text stream, so the cues will load again.
|
|
3129
3486
|
this.streamingEngine_.reloadTextStream();
|
|
3130
3487
|
}
|
|
3488
|
+
} else {
|
|
3489
|
+
const displayer = this.mediaSourceEngine_.getTextDisplayer();
|
|
3490
|
+
if (displayer.configure) {
|
|
3491
|
+
displayer.configure(this.config_.textDisplayer);
|
|
3492
|
+
}
|
|
3131
3493
|
}
|
|
3132
3494
|
}
|
|
3133
3495
|
if (this.abrManager_) {
|
|
@@ -3397,24 +3759,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3397
3759
|
}
|
|
3398
3760
|
}
|
|
3399
3761
|
|
|
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
3762
|
/**
|
|
3419
3763
|
* Get the range of time (in seconds) that seeking is allowed. If the player
|
|
3420
3764
|
* has not loaded content and the manifest is HLS, this will return a range
|
|
@@ -4472,19 +4816,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4472
4816
|
}
|
|
4473
4817
|
|
|
4474
4818
|
/**
|
|
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>.
|
|
4819
|
+
* Get the current playhead position as a date.
|
|
4478
4820
|
*
|
|
4479
4821
|
* @return {Date}
|
|
4480
4822
|
* @export
|
|
4481
4823
|
*/
|
|
4482
4824
|
getPlayheadTimeAsDate() {
|
|
4483
|
-
if (!this.isLive()) {
|
|
4484
|
-
shaka.log.warning('getPlayheadTimeAsDate is for live streams!');
|
|
4485
|
-
return null;
|
|
4486
|
-
}
|
|
4487
|
-
|
|
4488
4825
|
let presentationTime = 0;
|
|
4489
4826
|
if (this.playhead_) {
|
|
4490
4827
|
presentationTime = this.playhead_.getTime();
|
|
@@ -4521,22 +4858,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4521
4858
|
}
|
|
4522
4859
|
|
|
4523
4860
|
/**
|
|
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>.
|
|
4861
|
+
* Get the presentation start time as a date.
|
|
4527
4862
|
*
|
|
4528
4863
|
* @return {Date}
|
|
4529
4864
|
* @export
|
|
4530
4865
|
*/
|
|
4531
4866
|
getPresentationStartTimeAsDate() {
|
|
4532
|
-
if (!this.isLive()) {
|
|
4533
|
-
shaka.log.warning('getPresentationStartTimeAsDate is for live streams!');
|
|
4534
|
-
return null;
|
|
4535
|
-
}
|
|
4536
|
-
|
|
4537
4867
|
if (this.manifest_) {
|
|
4538
4868
|
const timeline = this.manifest_.presentationTimeline;
|
|
4539
|
-
const startTime = timeline.
|
|
4869
|
+
const startTime = timeline.getInitialProgramDateTime() ||
|
|
4870
|
+
timeline.getPresentationStartTime();
|
|
4540
4871
|
goog.asserts.assert(startTime != null,
|
|
4541
4872
|
'Presentation start time should not be null!');
|
|
4542
4873
|
return new Date(/* ms= */ startTime * 1000);
|
|
@@ -4557,6 +4888,29 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4557
4888
|
}
|
|
4558
4889
|
}
|
|
4559
4890
|
|
|
4891
|
+
/**
|
|
4892
|
+
* Get the presentation segment availability duration. This should only be
|
|
4893
|
+
* called when the player has loaded a live stream. If the player has not
|
|
4894
|
+
* loaded a live stream, this will return <code>null</code>.
|
|
4895
|
+
*
|
|
4896
|
+
* @return {?number}
|
|
4897
|
+
* @export
|
|
4898
|
+
*/
|
|
4899
|
+
getSegmentAvailabilityDuration() {
|
|
4900
|
+
if (!this.isLive()) {
|
|
4901
|
+
shaka.log.warning('getSegmentAvailabilityDuration is for live streams!');
|
|
4902
|
+
return null;
|
|
4903
|
+
}
|
|
4904
|
+
|
|
4905
|
+
if (this.manifest_) {
|
|
4906
|
+
const timeline = this.manifest_.presentationTimeline;
|
|
4907
|
+
return timeline.getSegmentAvailabilityDuration();
|
|
4908
|
+
} else {
|
|
4909
|
+
shaka.log.warning('No way to get segment segment availability duration!');
|
|
4910
|
+
return null;
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4913
|
+
|
|
4560
4914
|
/**
|
|
4561
4915
|
* Get information about what the player has buffered. If the player has not
|
|
4562
4916
|
* loaded content or is currently loading content, the buffered content will
|
|
@@ -5181,8 +5535,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5181
5535
|
|
|
5182
5536
|
/**
|
|
5183
5537
|
* 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
5538
|
*
|
|
5187
5539
|
* @param {number} width
|
|
5188
5540
|
* @param {number} height
|
|
@@ -5285,8 +5637,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5285
5637
|
// TextDisplay factory must capture a reference to "this".
|
|
5286
5638
|
config.textDisplayFactory = () => {
|
|
5287
5639
|
if (this.videoContainer_) {
|
|
5640
|
+
const latestConfig = this.getConfiguration();
|
|
5288
5641
|
return new shaka.text.UITextDisplayer(
|
|
5289
|
-
this.video_, this.videoContainer_);
|
|
5642
|
+
this.video_, this.videoContainer_, latestConfig.textDisplayer);
|
|
5290
5643
|
} else {
|
|
5291
5644
|
// eslint-disable-next-line no-restricted-syntax
|
|
5292
5645
|
if (HTMLMediaElement.prototype.addTextTrack) {
|
|
@@ -5412,82 +5765,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5412
5765
|
}
|
|
5413
5766
|
}
|
|
5414
5767
|
|
|
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
5768
|
/**
|
|
5492
5769
|
* @param {shaka.extern.Variant} initialVariant
|
|
5493
5770
|
* @param {number} time
|
|
@@ -5566,7 +5843,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5566
5843
|
// buffering.
|
|
5567
5844
|
const eventName = shaka.util.FakeEvent.EventName.Buffering;
|
|
5568
5845
|
const data = (new Map()).set('buffering', isBuffering);
|
|
5569
|
-
this.dispatchEvent(
|
|
5846
|
+
this.dispatchEvent(shaka.Player.makeEvent_(eventName, data));
|
|
5570
5847
|
}
|
|
5571
5848
|
|
|
5572
5849
|
/**
|
|
@@ -5599,7 +5876,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5599
5876
|
this.playRateController_.set(newRate);
|
|
5600
5877
|
}
|
|
5601
5878
|
|
|
5602
|
-
const event =
|
|
5879
|
+
const event = shaka.Player.makeEvent_(
|
|
5880
|
+
shaka.util.FakeEvent.EventName.RateChange);
|
|
5603
5881
|
this.dispatchEvent(event);
|
|
5604
5882
|
}
|
|
5605
5883
|
|
|
@@ -5635,20 +5913,46 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5635
5913
|
if (stateChanged) {
|
|
5636
5914
|
const eventName = shaka.util.FakeEvent.EventName.StateChanged;
|
|
5637
5915
|
const data = (new Map()).set('newstate', updateState);
|
|
5638
|
-
this.dispatchEvent(
|
|
5916
|
+
this.dispatchEvent(shaka.Player.makeEvent_(eventName, data));
|
|
5639
5917
|
}
|
|
5640
5918
|
}
|
|
5641
5919
|
|
|
5642
5920
|
/**
|
|
5643
|
-
* Callback for liveSync
|
|
5921
|
+
* Callback for liveSync and vodDynamicPlaybackRate
|
|
5644
5922
|
*
|
|
5645
5923
|
* @private
|
|
5646
5924
|
*/
|
|
5647
5925
|
onTimeUpdate_() {
|
|
5926
|
+
const playbackRate = this.video_.playbackRate;
|
|
5927
|
+
const isLive = this.isLive();
|
|
5928
|
+
|
|
5929
|
+
if (this.config_.streaming.vodDynamicPlaybackRate && !isLive) {
|
|
5930
|
+
const minPlaybackRate =
|
|
5931
|
+
this.config_.streaming.vodDynamicPlaybackRateLowBufferRate;
|
|
5932
|
+
const bufferFullness = this.getBufferFullness();
|
|
5933
|
+
const bufferThreshold =
|
|
5934
|
+
this.config_.streaming.vodDynamicPlaybackRateBufferRatio;
|
|
5935
|
+
|
|
5936
|
+
if (bufferFullness <= bufferThreshold) {
|
|
5937
|
+
if (playbackRate != minPlaybackRate) {
|
|
5938
|
+
shaka.log.debug('Buffer fullness ratio (' + bufferFullness + ') ' +
|
|
5939
|
+
'is less than the vodDynamicPlaybackRateBufferRatio (' +
|
|
5940
|
+
bufferThreshold + '). Updating playbackRate to ' + minPlaybackRate);
|
|
5941
|
+
this.trickPlay(minPlaybackRate);
|
|
5942
|
+
}
|
|
5943
|
+
} else if (bufferFullness == 1) {
|
|
5944
|
+
if (playbackRate !== this.playRateController_.getDefaultRate()) {
|
|
5945
|
+
shaka.log.debug('Buffer is full. Cancel trick play.');
|
|
5946
|
+
this.cancelTrickPlay();
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
}
|
|
5950
|
+
|
|
5648
5951
|
// If the live stream has reached its end, do not sync.
|
|
5649
|
-
if (!
|
|
5952
|
+
if (!isLive) {
|
|
5650
5953
|
return;
|
|
5651
5954
|
}
|
|
5955
|
+
|
|
5652
5956
|
const seekRange = this.seekRange();
|
|
5653
5957
|
if (!Number.isFinite(seekRange.end)) {
|
|
5654
5958
|
return;
|
|
@@ -5693,7 +5997,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5693
5997
|
}
|
|
5694
5998
|
}
|
|
5695
5999
|
|
|
5696
|
-
const playbackRate = this.video_.playbackRate;
|
|
5697
6000
|
const latency = seekRange.end - this.video_.currentTime;
|
|
5698
6001
|
let offset = 0;
|
|
5699
6002
|
// In src= mode, the seek range isn't updated frequently enough, so we need
|
|
@@ -5708,7 +6011,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5708
6011
|
}
|
|
5709
6012
|
}
|
|
5710
6013
|
|
|
5711
|
-
|
|
6014
|
+
const panicMode = this.config_.streaming.liveSyncPanicMode;
|
|
6015
|
+
const panicThreshold = this.config_.streaming.liveSyncPanicThreshold * 1000;
|
|
6016
|
+
const timeSinceLastRebuffer =
|
|
6017
|
+
Date.now() - this.bufferObserver_.getLastRebufferTime();
|
|
6018
|
+
if (panicMode && !liveSyncMinPlaybackRate) {
|
|
6019
|
+
liveSyncMinPlaybackRate = this.config_.streaming.liveSyncMinPlaybackRate;
|
|
6020
|
+
}
|
|
6021
|
+
|
|
6022
|
+
if (panicMode && liveSyncMinPlaybackRate &&
|
|
6023
|
+
timeSinceLastRebuffer <= panicThreshold) {
|
|
6024
|
+
if (playbackRate != liveSyncMinPlaybackRate) {
|
|
6025
|
+
shaka.log.debug('Time since last rebuffer (' +
|
|
6026
|
+
timeSinceLastRebuffer + 's) ' +
|
|
6027
|
+
'is less than the liveSyncPanicThreshold (' + panicThreshold +
|
|
6028
|
+
's). Updating playbackRate to ' + liveSyncMinPlaybackRate);
|
|
6029
|
+
this.trickPlay(liveSyncMinPlaybackRate);
|
|
6030
|
+
}
|
|
6031
|
+
} else if (liveSyncMaxLatency && liveSyncPlaybackRate &&
|
|
5712
6032
|
(latency - offset) > liveSyncMaxLatency) {
|
|
5713
6033
|
if (playbackRate != liveSyncPlaybackRate) {
|
|
5714
6034
|
shaka.log.debug('Latency (' + latency + 's) ' +
|
|
@@ -5756,15 +6076,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5756
6076
|
if (hasNewCompletionPercent) {
|
|
5757
6077
|
let event;
|
|
5758
6078
|
if (this.completionPercent_ == 0) {
|
|
5759
|
-
event =
|
|
6079
|
+
event = shaka.Player.makeEvent_(shaka.util.FakeEvent.EventName.Started);
|
|
5760
6080
|
} else if (this.completionPercent_ == 25) {
|
|
5761
|
-
event =
|
|
6081
|
+
event = shaka.Player.makeEvent_(
|
|
6082
|
+
shaka.util.FakeEvent.EventName.FirstQuartile);
|
|
5762
6083
|
} else if (this.completionPercent_ == 50) {
|
|
5763
|
-
event =
|
|
6084
|
+
event = shaka.Player.makeEvent_(
|
|
6085
|
+
shaka.util.FakeEvent.EventName.Midpoint);
|
|
5764
6086
|
} else if (this.completionPercent_ == 75) {
|
|
5765
|
-
event =
|
|
6087
|
+
event = shaka.Player.makeEvent_(
|
|
6088
|
+
shaka.util.FakeEvent.EventName.ThirdQuartile);
|
|
5766
6089
|
} else if (this.completionPercent_ == 100) {
|
|
5767
|
-
event =
|
|
6090
|
+
event = shaka.Player.makeEvent_(
|
|
6091
|
+
shaka.util.FakeEvent.EventName.Complete);
|
|
5768
6092
|
}
|
|
5769
6093
|
if (event) {
|
|
5770
6094
|
this.dispatchEvent(event);
|
|
@@ -5804,7 +6128,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5804
6128
|
updateAbrManagerVariants_() {
|
|
5805
6129
|
try {
|
|
5806
6130
|
goog.asserts.assert(this.manifest_, 'Manifest should exist by now!');
|
|
5807
|
-
this.
|
|
6131
|
+
this.manifestFilterer_.checkRestrictedVariants(this.manifest_);
|
|
5808
6132
|
} catch (e) {
|
|
5809
6133
|
this.onError_(e);
|
|
5810
6134
|
return false;
|
|
@@ -6133,7 +6457,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6133
6457
|
.set('start', start)
|
|
6134
6458
|
.set('end', end)
|
|
6135
6459
|
.set('contentType', contentType);
|
|
6136
|
-
this.dispatchEvent(
|
|
6460
|
+
this.dispatchEvent(shaka.Player.makeEvent_(
|
|
6137
6461
|
shaka.util.FakeEvent.EventName.SegmentAppended, data));
|
|
6138
6462
|
}
|
|
6139
6463
|
|
|
@@ -6183,8 +6507,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6183
6507
|
if (this.lcevcDec_) {
|
|
6184
6508
|
this.lcevcDec_.updateVariant(to, this.getManifestType());
|
|
6185
6509
|
}
|
|
6186
|
-
const event =
|
|
6187
|
-
|
|
6510
|
+
const event = shaka.Player.makeEvent_(
|
|
6511
|
+
shaka.util.FakeEvent.EventName.Adaptation, data);
|
|
6188
6512
|
this.delayDispatchEvent_(event);
|
|
6189
6513
|
}
|
|
6190
6514
|
|
|
@@ -6195,7 +6519,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6195
6519
|
onTracksChanged_() {
|
|
6196
6520
|
// Delay the 'trackschanged' event so StreamingEngine has time to absorb the
|
|
6197
6521
|
// changes before the user tries to query it.
|
|
6198
|
-
const event =
|
|
6522
|
+
const event = shaka.Player.makeEvent_(
|
|
6523
|
+
shaka.util.FakeEvent.EventName.TracksChanged);
|
|
6199
6524
|
this.delayDispatchEvent_(event);
|
|
6200
6525
|
}
|
|
6201
6526
|
|
|
@@ -6215,8 +6540,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6215
6540
|
this.lcevcDec_.updateVariant(to, this.getManifestType());
|
|
6216
6541
|
}
|
|
6217
6542
|
|
|
6218
|
-
const event =
|
|
6219
|
-
|
|
6543
|
+
const event = shaka.Player.makeEvent_(
|
|
6544
|
+
shaka.util.FakeEvent.EventName.VariantChanged, data);
|
|
6220
6545
|
this.delayDispatchEvent_(event);
|
|
6221
6546
|
}
|
|
6222
6547
|
|
|
@@ -6227,14 +6552,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6227
6552
|
onTextChanged_() {
|
|
6228
6553
|
// Delay the 'textchanged' event so StreamingEngine time to absorb the
|
|
6229
6554
|
// changes before the user tries to query it.
|
|
6230
|
-
const event =
|
|
6555
|
+
const event = shaka.Player.makeEvent_(
|
|
6556
|
+
shaka.util.FakeEvent.EventName.TextChanged);
|
|
6231
6557
|
this.delayDispatchEvent_(event);
|
|
6232
6558
|
}
|
|
6233
6559
|
|
|
6234
6560
|
/** @private */
|
|
6235
6561
|
onTextTrackVisibility_() {
|
|
6236
|
-
const event =
|
|
6237
|
-
|
|
6562
|
+
const event = shaka.Player.makeEvent_(
|
|
6563
|
+
shaka.util.FakeEvent.EventName.TextTrackVisibility);
|
|
6238
6564
|
this.delayDispatchEvent_(event);
|
|
6239
6565
|
}
|
|
6240
6566
|
|
|
@@ -6246,7 +6572,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6246
6572
|
}
|
|
6247
6573
|
|
|
6248
6574
|
const data = (new Map()).set('newStatus', this.config_.abr.enabled);
|
|
6249
|
-
this.delayDispatchEvent_(
|
|
6575
|
+
this.delayDispatchEvent_(shaka.Player.makeEvent_(
|
|
6250
6576
|
shaka.util.FakeEvent.EventName.AbrStatusChanged, data));
|
|
6251
6577
|
}
|
|
6252
6578
|
|
|
@@ -6380,7 +6706,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6380
6706
|
}
|
|
6381
6707
|
|
|
6382
6708
|
const eventName = shaka.util.FakeEvent.EventName.Error;
|
|
6383
|
-
const event =
|
|
6709
|
+
const event = shaka.Player.makeEvent_(
|
|
6710
|
+
eventName, (new Map()).set('detail', error));
|
|
6384
6711
|
this.dispatchEvent(event);
|
|
6385
6712
|
if (event.defaultPrevented) {
|
|
6386
6713
|
error.handled = true;
|
|
@@ -6395,10 +6722,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6395
6722
|
*
|
|
6396
6723
|
* @param {!shaka.util.FakeEvent.EventName} eventName
|
|
6397
6724
|
* @param {shaka.extern.TimelineRegionInfo} region
|
|
6725
|
+
* @param {shaka.util.FakeEventTarget=} eventTarget
|
|
6398
6726
|
*
|
|
6399
6727
|
* @private
|
|
6400
6728
|
*/
|
|
6401
|
-
onRegionEvent_(eventName, region) {
|
|
6729
|
+
onRegionEvent_(eventName, region, eventTarget = this) {
|
|
6402
6730
|
// Always make a copy to avoid exposing our internal data to the app.
|
|
6403
6731
|
const clone = {
|
|
6404
6732
|
schemeIdUri: region.schemeIdUri,
|
|
@@ -6407,10 +6735,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6407
6735
|
endTime: region.endTime,
|
|
6408
6736
|
id: region.id,
|
|
6409
6737
|
eventElement: region.eventElement,
|
|
6738
|
+
eventNode: region.eventNode,
|
|
6410
6739
|
};
|
|
6411
6740
|
|
|
6412
6741
|
const data = (new Map()).set('detail', clone);
|
|
6413
|
-
|
|
6742
|
+
eventTarget.dispatchEvent(shaka.Player.makeEvent_(eventName, data));
|
|
6414
6743
|
}
|
|
6415
6744
|
|
|
6416
6745
|
/**
|
|
@@ -6441,7 +6770,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6441
6770
|
.set('mediaQuality', clone)
|
|
6442
6771
|
.set('position', position);
|
|
6443
6772
|
|
|
6444
|
-
this.dispatchEvent(
|
|
6773
|
+
this.dispatchEvent(shaka.Player.makeEvent_(
|
|
6445
6774
|
shaka.util.FakeEvent.EventName.MediaQualityChanged, data));
|
|
6446
6775
|
}
|
|
6447
6776
|
|
|
@@ -6504,14 +6833,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6504
6833
|
* @private
|
|
6505
6834
|
*/
|
|
6506
6835
|
onKeyStatus_(keyStatusMap) {
|
|
6507
|
-
|
|
6508
|
-
// We can't use this info to manage restrictions in src= mode, so ignore
|
|
6509
|
-
// it.
|
|
6510
|
-
return;
|
|
6511
|
-
}
|
|
6836
|
+
goog.asserts.assert(this.streamingEngine_, 'Cannot be called in src= mode');
|
|
6512
6837
|
|
|
6513
|
-
const event =
|
|
6514
|
-
|
|
6838
|
+
const event = shaka.Player.makeEvent_(
|
|
6839
|
+
shaka.util.FakeEvent.EventName.KeyStatusChanged);
|
|
6515
6840
|
this.dispatchEvent(event);
|
|
6516
6841
|
|
|
6517
6842
|
const keyIds = Object.keys(keyStatusMap);
|
|
@@ -6533,7 +6858,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6533
6858
|
'restrictions so we don\'t select those tracks.');
|
|
6534
6859
|
}
|
|
6535
6860
|
|
|
6536
|
-
const restrictedStatuses = shaka.
|
|
6861
|
+
const restrictedStatuses = shaka.media.ManifestFilterer.restrictedStatuses;
|
|
6537
6862
|
let tracksChanged = false;
|
|
6538
6863
|
|
|
6539
6864
|
goog.asserts.assert(this.drmEngine_, 'drmEngine should be non-null here.');
|
|
@@ -6582,22 +6907,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6582
6907
|
}
|
|
6583
6908
|
}
|
|
6584
6909
|
|
|
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
6910
|
/**
|
|
6602
6911
|
* @return {boolean} true if we should stream text right now.
|
|
6603
6912
|
* @private
|
|
@@ -6641,93 +6950,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6641
6950
|
}
|
|
6642
6951
|
}
|
|
6643
6952
|
|
|
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
6953
|
/**
|
|
6732
6954
|
* Fire an event, but wait a little bit so that the immediate execution can
|
|
6733
6955
|
* complete before the event is handled.
|
|
@@ -6960,25 +7182,13 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6960
7182
|
* @export
|
|
6961
7183
|
*/
|
|
6962
7184
|
// eslint-disable-next-line no-useless-concat
|
|
6963
|
-
shaka.Player.version = 'v4.
|
|
7185
|
+
shaka.Player.version = 'v4.8.1' + '-uncompiled'; // x-release-please-version
|
|
6964
7186
|
|
|
6965
7187
|
// Initialize the deprecation system using the version string we just set
|
|
6966
7188
|
// on the player.
|
|
6967
7189
|
shaka.Deprecate.init(shaka.Player.version);
|
|
6968
7190
|
|
|
6969
7191
|
|
|
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
7192
|
/** @private {!Object.<string, function():*>} */
|
|
6983
7193
|
shaka.Player.supportPlugins_ = {};
|
|
6984
7194
|
|