shaka-player 4.7.12 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORS +1 -0
- package/CONTRIBUTORS +1 -0
- package/LICENSE +54 -0
- package/README.md +22 -1
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/deps.js +36 -25
- package/dist/locales.js +40 -30
- package/dist/shaka-player.compiled.d.ts +196 -46
- package/dist/shaka-player.compiled.debug.d.ts +196 -46
- package/dist/shaka-player.compiled.debug.externs.js +474 -337
- package/dist/shaka-player.compiled.debug.js +992 -871
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +474 -337
- package/dist/shaka-player.compiled.js +1248 -1174
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +259 -49
- package/dist/shaka-player.ui.debug.d.ts +259 -49
- package/dist/shaka-player.ui.debug.externs.js +591 -376
- package/dist/shaka-player.ui.debug.js +1208 -1003
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +591 -376
- package/dist/shaka-player.ui.js +1533 -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 +69 -7
- package/externs/shaka/net.js +13 -1
- package/externs/shaka/offline.js +7 -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 +240 -179
- package/lib/dash/mpd_utils.js +72 -65
- package/lib/dash/segment_base.js +35 -22
- package/lib/dash/segment_list.js +39 -31
- package/lib/dash/segment_template.js +47 -23
- package/lib/hls/hls_parser.js +376 -165
- 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 +108 -29
- package/lib/media/playhead.js +3 -3
- package/lib/media/preload_manager.js +827 -0
- package/lib/media/presentation_timeline.js +11 -0
- package/lib/media/segment_index.js +60 -49
- package/lib/media/segment_prefetch.js +140 -43
- package/lib/media/segment_reference.js +31 -8
- package/lib/media/segment_utils.js +208 -25
- package/lib/media/streaming_engine.js +321 -77
- package/lib/mss/content_protection.js +31 -25
- package/lib/mss/mss_parser.js +78 -55
- package/lib/net/networking_engine.js +49 -4
- package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
- package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
- package/lib/offline/manifest_converter.js +10 -3
- package/lib/offline/storage.js +5 -3
- package/lib/player.js +896 -674
- package/lib/polyfill/media_capabilities.js +16 -1
- package/lib/text/simple_text_displayer.js +9 -0
- package/lib/text/stub_text_displayer.js +7 -0
- package/lib/text/text_engine.js +16 -3
- package/lib/text/ttml_text_parser.js +116 -114
- package/lib/text/ui_text_displayer.js +28 -11
- package/lib/text/vtt_text_parser.js +54 -115
- package/lib/transmuxer/opus.js +91 -0
- package/lib/transmuxer/ts_transmuxer.js +115 -0
- package/lib/util/cmcd_manager.js +152 -3
- package/lib/util/dom_utils.js +11 -0
- package/lib/util/error.js +5 -0
- package/lib/util/fake_event.js +2 -0
- package/lib/util/manifest_parser_utils.js +97 -3
- package/lib/util/mime_utils.js +10 -5
- package/lib/util/mp4_box_parsers.js +346 -17
- package/lib/util/mp4_generator.js +23 -0
- package/lib/util/periods.js +24 -37
- package/lib/util/platform.js +161 -9
- package/lib/util/player_configuration.js +61 -3
- package/lib/util/stats.js +16 -0
- package/lib/util/stream_utils.js +350 -277
- 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);
|
|
@@ -2174,12 +2454,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2174
2454
|
accessibilityPurpose: null,
|
|
2175
2455
|
external: false,
|
|
2176
2456
|
fastSwitching: false,
|
|
2457
|
+
fullMimeTypes: new Set(),
|
|
2177
2458
|
},
|
|
2178
2459
|
bandwidth: 100,
|
|
2179
2460
|
allowedByApplication: true,
|
|
2180
2461
|
allowedByKeySystem: true,
|
|
2181
2462
|
decodingInfos: [],
|
|
2182
2463
|
};
|
|
2464
|
+
variant.video.fullMimeTypes.add(shaka.util.MimeUtils.getFullType(
|
|
2465
|
+
variant.video.mimeType, variant.video.codecs));
|
|
2183
2466
|
|
|
2184
2467
|
this.drmEngine_.setSrcEquals(/* srcEquals= */ true);
|
|
2185
2468
|
await this.drmEngine_.initForPlayback(
|
|
@@ -2313,6 +2596,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2313
2596
|
mediaElement.load();
|
|
2314
2597
|
}
|
|
2315
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
|
+
|
|
2316
2608
|
// Set the load mode last so that we know that all our components are
|
|
2317
2609
|
// initialized.
|
|
2318
2610
|
this.loadMode_ = shaka.Player.LoadMode.SRC_EQUALS;
|
|
@@ -2320,8 +2612,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2320
2612
|
// The event doesn't mean as much for src= playback, since we don't
|
|
2321
2613
|
// control streaming. But we should fire it in this path anyway since
|
|
2322
2614
|
// some applications may be expecting it as a life-cycle event.
|
|
2323
|
-
this.dispatchEvent(
|
|
2324
|
-
|
|
2615
|
+
this.dispatchEvent(shaka.Player.makeEvent_(
|
|
2616
|
+
shaka.util.FakeEvent.EventName.Streaming));
|
|
2325
2617
|
|
|
2326
2618
|
// The "load" Promise is resolved when we have loaded the metadata. If we
|
|
2327
2619
|
// wait for the full data, that won't happen on Safari until the play
|
|
@@ -2393,7 +2685,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2393
2685
|
|
|
2394
2686
|
const isLive = this.isLive();
|
|
2395
2687
|
|
|
2396
|
-
if (isLive && this.config_.streaming.liveSync
|
|
2688
|
+
if ((isLive && (this.config_.streaming.liveSync ||
|
|
2689
|
+
this.config_.streaming.liveSyncPanicMode)) ||
|
|
2690
|
+
this.config_.streaming.vodDynamicPlaybackRate) {
|
|
2397
2691
|
const onTimeUpdate = () => this.onTimeUpdate_();
|
|
2398
2692
|
this.loadEventManager_.listen(mediaElement, 'timeupdate', onTimeUpdate);
|
|
2399
2693
|
} else if (!isLive) {
|
|
@@ -2553,7 +2847,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2553
2847
|
.set('endTime', endTime)
|
|
2554
2848
|
.set('metadataType', metadataType)
|
|
2555
2849
|
.set('payload', payload);
|
|
2556
|
-
this.dispatchEvent(
|
|
2850
|
+
this.dispatchEvent(shaka.Player.makeEvent_(eventName, data));
|
|
2557
2851
|
}
|
|
2558
2852
|
|
|
2559
2853
|
/**
|
|
@@ -2583,35 +2877,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2583
2877
|
});
|
|
2584
2878
|
}
|
|
2585
2879
|
|
|
2586
|
-
/**
|
|
2587
|
-
* Take a series of variants and ensure that they only contain one type of
|
|
2588
|
-
* variant. The different options are:
|
|
2589
|
-
* 1. Audio-Video
|
|
2590
|
-
* 2. Audio-Only
|
|
2591
|
-
* 3. Video-Only
|
|
2592
|
-
*
|
|
2593
|
-
* A manifest can only contain a single type because once we initialize media
|
|
2594
|
-
* source to expect specific streams, it must always have content for those
|
|
2595
|
-
* streams. If we were to start with audio+video and switch to an audio-only
|
|
2596
|
-
* variant, media source would block waiting for video content.
|
|
2597
|
-
*
|
|
2598
|
-
* @param {shaka.extern.Manifest} manifest
|
|
2599
|
-
* @private
|
|
2600
|
-
*/
|
|
2601
|
-
static filterForAVVariants_(manifest) {
|
|
2602
|
-
const isAVVariant = (variant) => {
|
|
2603
|
-
// Audio-video variants may include both streams separately or may be
|
|
2604
|
-
// single multiplexed streams with multiple codecs.
|
|
2605
|
-
return (variant.video && variant.audio) ||
|
|
2606
|
-
(variant.video && variant.video.codecs.includes(','));
|
|
2607
|
-
};
|
|
2608
|
-
if (manifest.variants.some(isAVVariant)) {
|
|
2609
|
-
shaka.log.debug('Found variant with audio and video content, ' +
|
|
2610
|
-
'so filtering out audio-only content.');
|
|
2611
|
-
manifest.variants = manifest.variants.filter(isAVVariant);
|
|
2612
|
-
}
|
|
2613
|
-
}
|
|
2614
|
-
|
|
2615
2880
|
/**
|
|
2616
2881
|
* Releases all of the mutexes of the player. Meant for use by the tests.
|
|
2617
2882
|
* @export
|
|
@@ -2629,25 +2894,66 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2629
2894
|
* @return {!shaka.media.DrmEngine}
|
|
2630
2895
|
*/
|
|
2631
2896
|
createDrmEngine(playerInterface) {
|
|
2632
|
-
|
|
2633
|
-
return new shaka.media.DrmEngine(playerInterface, updateExpirationTime);
|
|
2897
|
+
return new shaka.media.DrmEngine(playerInterface);
|
|
2634
2898
|
}
|
|
2635
2899
|
|
|
2636
2900
|
/**
|
|
2637
2901
|
* Creates a new instance of NetworkingEngine. This can be replaced by tests
|
|
2638
2902
|
* to create fake instances instead.
|
|
2639
2903
|
*
|
|
2904
|
+
* @param {(function():?shaka.media.PreloadManager)=} getPreloadManager
|
|
2640
2905
|
* @return {!shaka.net.NetworkingEngine}
|
|
2641
2906
|
*/
|
|
2642
|
-
createNetworkingEngine() {
|
|
2643
|
-
|
|
2644
|
-
|
|
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) => {
|
|
2645
2950
|
// In some situations, such as during offline storage, the abr manager
|
|
2646
2951
|
// might not yet exist. Therefore, we need to check if abr manager has
|
|
2647
2952
|
// been initialized before using it.
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2953
|
+
const abrManager = getAbrManager();
|
|
2954
|
+
if (abrManager) {
|
|
2955
|
+
abrManager.segmentDownloaded(deltaTimeMs, bytesDownloaded,
|
|
2956
|
+
allowSwitch, request);
|
|
2651
2957
|
}
|
|
2652
2958
|
};
|
|
2653
2959
|
/** @type {shaka.net.NetworkingEngine.OnHeadersReceived} */
|
|
@@ -2658,10 +2964,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2658
2964
|
.set('headers', headers)
|
|
2659
2965
|
.set('request', request)
|
|
2660
2966
|
.set('requestType', requestType);
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
this.cmsdManager_
|
|
2664
|
-
|
|
2967
|
+
dispatchEvent(shaka.Player.makeEvent_(name, data));
|
|
2968
|
+
lateQueue(() => {
|
|
2969
|
+
if (this.cmsdManager_) {
|
|
2970
|
+
this.cmsdManager_.processHeaders(headers);
|
|
2971
|
+
}
|
|
2972
|
+
});
|
|
2665
2973
|
};
|
|
2666
2974
|
/** @type {shaka.net.NetworkingEngine.OnDownloadFailed} */
|
|
2667
2975
|
const onDownloadFailed_ = (request, error, httpResponseCode, aborted) => {
|
|
@@ -2672,23 +2980,35 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2672
2980
|
.set('error', error)
|
|
2673
2981
|
.set('httpResponseCode', httpResponseCode)
|
|
2674
2982
|
.set('aborted', aborted);
|
|
2675
|
-
|
|
2983
|
+
dispatchEvent(shaka.Player.makeEvent_(name, data));
|
|
2676
2984
|
};
|
|
2677
2985
|
/** @type {shaka.net.NetworkingEngine.OnRequest} */
|
|
2678
2986
|
const onRequest_ = (type, request, context) => {
|
|
2679
|
-
|
|
2987
|
+
lateQueue(() => {
|
|
2988
|
+
this.cmcdManager_.applyData(type, request, context);
|
|
2989
|
+
});
|
|
2680
2990
|
};
|
|
2681
2991
|
|
|
2682
|
-
|
|
2992
|
+
/** @type {shaka.net.NetworkingEngine.OnRetry} */
|
|
2683
2993
|
const onRetry_ = (type, context, newUrl, oldUrl) => {
|
|
2684
|
-
|
|
2685
|
-
|
|
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);
|
|
2686
3006
|
}
|
|
2687
3007
|
};
|
|
2688
3008
|
|
|
2689
3009
|
return new shaka.net.NetworkingEngine(
|
|
2690
3010
|
onProgressUpdated_, onHeadersReceived_, onDownloadFailed_, onRequest_,
|
|
2691
|
-
onRetry_);
|
|
3011
|
+
onRetry_, onResponse_);
|
|
2692
3012
|
}
|
|
2693
3013
|
|
|
2694
3014
|
/**
|
|
@@ -3015,11 +3335,33 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3015
3335
|
delete config['streaming']['forceTransmux'];
|
|
3016
3336
|
}
|
|
3017
3337
|
|
|
3338
|
+
// Deprecate 'streaming.useNativeHlsOnSafari' configuration.
|
|
3339
|
+
if (config['streaming'] && 'useNativeHlsOnSafari' in config['streaming']) {
|
|
3340
|
+
shaka.Deprecate.deprecateFeature(5,
|
|
3341
|
+
'streaming.useNativeHlsOnSafari configuration',
|
|
3342
|
+
'Please Use streaming.useNativeHlsForFairPlay or ' +
|
|
3343
|
+
'streaming.preferNativeHls instead.');
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
// Deprecate 'mediaSource.sourceBufferExtraFeatures' configuration.
|
|
3347
|
+
if (config['mediaSource'] &&
|
|
3348
|
+
'sourceBufferExtraFeatures' in config['mediaSource']) {
|
|
3349
|
+
shaka.Deprecate.deprecateFeature(5,
|
|
3350
|
+
'mediaSource.sourceBufferExtraFeatures configuration',
|
|
3351
|
+
'Please Use mediaSource.addExtraFeaturesToSourceBuffer() instead.');
|
|
3352
|
+
const sourceBufferExtraFeatures =
|
|
3353
|
+
config['mediaSource']['sourceBufferExtraFeatures'];
|
|
3354
|
+
config['mediaSource']['addExtraFeaturesToSourceBuffer'] = () => {
|
|
3355
|
+
return sourceBufferExtraFeatures;
|
|
3356
|
+
};
|
|
3357
|
+
delete config['mediaSource']['sourceBufferExtraFeatures'];
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3018
3360
|
// If lowLatencyMode is enabled, and inaccurateManifestTolerance and
|
|
3019
3361
|
// rebufferingGoal and segmentPrefetchLimit and baseDelay are not
|
|
3020
3362
|
// specified, set inaccurateManifestTolerance to 0 and rebufferingGoal
|
|
3021
|
-
// to 0.01 and segmentPrefetchLimit to 2 and
|
|
3022
|
-
// for low latency streaming.
|
|
3363
|
+
// to 0.01 and segmentPrefetchLimit to 2 and updateIntervalSeconds to
|
|
3364
|
+
// 0.1 and baseDelay to 100 by default for low latency streaming.
|
|
3023
3365
|
if (config['streaming'] && config['streaming']['lowLatencyMode']) {
|
|
3024
3366
|
if (config['streaming']['inaccurateManifestTolerance'] == undefined) {
|
|
3025
3367
|
config['streaming']['inaccurateManifestTolerance'] = 0;
|
|
@@ -3030,6 +3372,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3030
3372
|
if (config['streaming']['segmentPrefetchLimit'] == undefined) {
|
|
3031
3373
|
config['streaming']['segmentPrefetchLimit'] = 2;
|
|
3032
3374
|
}
|
|
3375
|
+
if (config['streaming']['updateIntervalSeconds'] == undefined) {
|
|
3376
|
+
config['streaming']['updateIntervalSeconds'] = 0.1;
|
|
3377
|
+
}
|
|
3033
3378
|
if (config['streaming']['retryParameters'] == undefined) {
|
|
3034
3379
|
config['streaming']['retryParameters'] = {};
|
|
3035
3380
|
}
|
|
@@ -3067,6 +3412,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3067
3412
|
* @private
|
|
3068
3413
|
*/
|
|
3069
3414
|
applyConfig_() {
|
|
3415
|
+
this.manifestFilterer_ = new shaka.media.ManifestFilterer(
|
|
3416
|
+
this.config_, this.maxHwRes_, this.drmEngine_);
|
|
3070
3417
|
if (this.parser_) {
|
|
3071
3418
|
const manifestConfig =
|
|
3072
3419
|
shaka.util.ObjectUtils.cloneObject(this.config_.manifest);
|
|
@@ -3083,9 +3430,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3083
3430
|
this.streamingEngine_.configure(this.config_.streaming);
|
|
3084
3431
|
|
|
3085
3432
|
// Need to apply the restrictions.
|
|
3433
|
+
// this.filterManifestWithRestrictions_() may throw.
|
|
3086
3434
|
try {
|
|
3087
|
-
|
|
3088
|
-
|
|
3435
|
+
if (this.loadMode_ != shaka.Player.LoadMode.DESTROYED) {
|
|
3436
|
+
if (this.manifestFilterer_.filterManifestWithRestrictions(
|
|
3437
|
+
this.manifest_)) {
|
|
3438
|
+
this.onTracksChanged_();
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3089
3441
|
} catch (error) {
|
|
3090
3442
|
this.onError_(error);
|
|
3091
3443
|
}
|
|
@@ -3118,6 +3470,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3118
3470
|
const textDisplayerFactory = this.config_.textDisplayFactory;
|
|
3119
3471
|
if (this.lastTextFactory_ != textDisplayerFactory) {
|
|
3120
3472
|
const displayer = textDisplayerFactory();
|
|
3473
|
+
if (displayer.configure) {
|
|
3474
|
+
displayer.configure(this.config_.textDisplayer);
|
|
3475
|
+
} else {
|
|
3476
|
+
shaka.Deprecate.deprecateFeature(5,
|
|
3477
|
+
'Text displayer w/ configure',
|
|
3478
|
+
'Text displayer should have a "configure" method!');
|
|
3479
|
+
}
|
|
3121
3480
|
this.mediaSourceEngine_.setTextDisplayer(displayer);
|
|
3122
3481
|
this.lastTextFactory_ = textDisplayerFactory;
|
|
3123
3482
|
|
|
@@ -3125,6 +3484,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3125
3484
|
// Reload the text stream, so the cues will load again.
|
|
3126
3485
|
this.streamingEngine_.reloadTextStream();
|
|
3127
3486
|
}
|
|
3487
|
+
} else {
|
|
3488
|
+
const displayer = this.mediaSourceEngine_.getTextDisplayer();
|
|
3489
|
+
if (displayer.configure) {
|
|
3490
|
+
displayer.configure(this.config_.textDisplayer);
|
|
3491
|
+
}
|
|
3128
3492
|
}
|
|
3129
3493
|
}
|
|
3130
3494
|
if (this.abrManager_) {
|
|
@@ -3394,24 +3758,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3394
3758
|
}
|
|
3395
3759
|
}
|
|
3396
3760
|
|
|
3397
|
-
/**
|
|
3398
|
-
* Return the value of lowLatencyMode configuration.
|
|
3399
|
-
* @return {boolean}
|
|
3400
|
-
* @private
|
|
3401
|
-
*/
|
|
3402
|
-
isLowLatencyMode_() {
|
|
3403
|
-
return this.config_.streaming.lowLatencyMode;
|
|
3404
|
-
}
|
|
3405
|
-
|
|
3406
|
-
/**
|
|
3407
|
-
* Return the value of autoLowLatencyMode configuration.
|
|
3408
|
-
* @return {boolean}
|
|
3409
|
-
* @private
|
|
3410
|
-
*/
|
|
3411
|
-
isAutoLowLatencyMode_() {
|
|
3412
|
-
return this.config_.streaming.autoLowLatencyMode;
|
|
3413
|
-
}
|
|
3414
|
-
|
|
3415
3761
|
/**
|
|
3416
3762
|
* Get the range of time (in seconds) that seeking is allowed. If the player
|
|
3417
3763
|
* has not loaded content and the manifest is HLS, this will return a range
|
|
@@ -3620,12 +3966,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3620
3966
|
const currentTime = this.video_.currentTime;
|
|
3621
3967
|
const seekRange = this.seekRange();
|
|
3622
3968
|
const safeSeekOffset = this.config_.streaming.safeSeekOffset;
|
|
3969
|
+
|
|
3970
|
+
// Cancel trick play if we hit the beginning or end of the seekable
|
|
3971
|
+
// (Sub-second accuracy not required here)
|
|
3623
3972
|
if (rate > 0) {
|
|
3624
|
-
if (currentTime >= seekRange.end) {
|
|
3973
|
+
if (Math.floor(currentTime) >= Math.floor(seekRange.end)) {
|
|
3625
3974
|
this.cancelTrickPlay();
|
|
3626
3975
|
}
|
|
3627
3976
|
} else {
|
|
3628
|
-
if (currentTime <=
|
|
3977
|
+
if (Math.floor(currentTime) <=
|
|
3978
|
+
Math.floor(seekRange.start + safeSeekOffset)) {
|
|
3629
3979
|
this.cancelTrickPlay();
|
|
3630
3980
|
}
|
|
3631
3981
|
}
|
|
@@ -4465,19 +4815,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4465
4815
|
}
|
|
4466
4816
|
|
|
4467
4817
|
/**
|
|
4468
|
-
* Get the current playhead position as a date.
|
|
4469
|
-
* when the player has loaded a live stream. If the player has not loaded a
|
|
4470
|
-
* live stream, this will return <code>null</code>.
|
|
4818
|
+
* Get the current playhead position as a date.
|
|
4471
4819
|
*
|
|
4472
4820
|
* @return {Date}
|
|
4473
4821
|
* @export
|
|
4474
4822
|
*/
|
|
4475
4823
|
getPlayheadTimeAsDate() {
|
|
4476
|
-
if (!this.isLive()) {
|
|
4477
|
-
shaka.log.warning('getPlayheadTimeAsDate is for live streams!');
|
|
4478
|
-
return null;
|
|
4479
|
-
}
|
|
4480
|
-
|
|
4481
4824
|
let presentationTime = 0;
|
|
4482
4825
|
if (this.playhead_) {
|
|
4483
4826
|
presentationTime = this.playhead_.getTime();
|
|
@@ -4514,22 +4857,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4514
4857
|
}
|
|
4515
4858
|
|
|
4516
4859
|
/**
|
|
4517
|
-
* Get the presentation start time as a date.
|
|
4518
|
-
* the player has loaded a live stream. If the player has not loaded a live
|
|
4519
|
-
* stream, this will return <code>null</code>.
|
|
4860
|
+
* Get the presentation start time as a date.
|
|
4520
4861
|
*
|
|
4521
4862
|
* @return {Date}
|
|
4522
4863
|
* @export
|
|
4523
4864
|
*/
|
|
4524
4865
|
getPresentationStartTimeAsDate() {
|
|
4525
|
-
if (!this.isLive()) {
|
|
4526
|
-
shaka.log.warning('getPresentationStartTimeAsDate is for live streams!');
|
|
4527
|
-
return null;
|
|
4528
|
-
}
|
|
4529
|
-
|
|
4530
4866
|
if (this.manifest_) {
|
|
4531
4867
|
const timeline = this.manifest_.presentationTimeline;
|
|
4532
|
-
const startTime = timeline.
|
|
4868
|
+
const startTime = timeline.getInitialProgramDateTime() ||
|
|
4869
|
+
timeline.getPresentationStartTime();
|
|
4533
4870
|
goog.asserts.assert(startTime != null,
|
|
4534
4871
|
'Presentation start time should not be null!');
|
|
4535
4872
|
return new Date(/* ms= */ startTime * 1000);
|
|
@@ -4550,6 +4887,29 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4550
4887
|
}
|
|
4551
4888
|
}
|
|
4552
4889
|
|
|
4890
|
+
/**
|
|
4891
|
+
* Get the presentation segment availability duration. This should only be
|
|
4892
|
+
* called when the player has loaded a live stream. If the player has not
|
|
4893
|
+
* loaded a live stream, this will return <code>null</code>.
|
|
4894
|
+
*
|
|
4895
|
+
* @return {?number}
|
|
4896
|
+
* @export
|
|
4897
|
+
*/
|
|
4898
|
+
getSegmentAvailabilityDuration() {
|
|
4899
|
+
if (!this.isLive()) {
|
|
4900
|
+
shaka.log.warning('getSegmentAvailabilityDuration is for live streams!');
|
|
4901
|
+
return null;
|
|
4902
|
+
}
|
|
4903
|
+
|
|
4904
|
+
if (this.manifest_) {
|
|
4905
|
+
const timeline = this.manifest_.presentationTimeline;
|
|
4906
|
+
return timeline.getSegmentAvailabilityDuration();
|
|
4907
|
+
} else {
|
|
4908
|
+
shaka.log.warning('No way to get segment segment availability duration!');
|
|
4909
|
+
return null;
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
|
|
4553
4913
|
/**
|
|
4554
4914
|
* Get information about what the player has buffered. If the player has not
|
|
4555
4915
|
* loaded content or is currently loading content, the buffered content will
|
|
@@ -4804,6 +5164,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4804
5164
|
accessibilityPurpose: null,
|
|
4805
5165
|
external: true,
|
|
4806
5166
|
fastSwitching: false,
|
|
5167
|
+
fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType(
|
|
5168
|
+
mimeType || '', codec || '')]),
|
|
4807
5169
|
};
|
|
4808
5170
|
|
|
4809
5171
|
const fullMimeType = shaka.util.MimeUtils.getFullType(
|
|
@@ -4960,6 +5322,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4960
5322
|
accessibilityPurpose: null,
|
|
4961
5323
|
external: true,
|
|
4962
5324
|
fastSwitching: false,
|
|
5325
|
+
fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType(
|
|
5326
|
+
mimeType || '', '')]),
|
|
4963
5327
|
};
|
|
4964
5328
|
|
|
4965
5329
|
if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
|
|
@@ -5170,8 +5534,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5170
5534
|
|
|
5171
5535
|
/**
|
|
5172
5536
|
* Set the maximum resolution that the platform's hardware can handle.
|
|
5173
|
-
* This will be called automatically by <code>shaka.cast.CastReceiver</code>
|
|
5174
|
-
* to enforce limitations of the Chromecast hardware.
|
|
5175
5537
|
*
|
|
5176
5538
|
* @param {number} width
|
|
5177
5539
|
* @param {number} height
|
|
@@ -5274,8 +5636,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5274
5636
|
// TextDisplay factory must capture a reference to "this".
|
|
5275
5637
|
config.textDisplayFactory = () => {
|
|
5276
5638
|
if (this.videoContainer_) {
|
|
5639
|
+
const latestConfig = this.getConfiguration();
|
|
5277
5640
|
return new shaka.text.UITextDisplayer(
|
|
5278
|
-
this.video_, this.videoContainer_);
|
|
5641
|
+
this.video_, this.videoContainer_, latestConfig.textDisplayer);
|
|
5279
5642
|
} else {
|
|
5280
5643
|
// eslint-disable-next-line no-restricted-syntax
|
|
5281
5644
|
if (HTMLMediaElement.prototype.addTextTrack) {
|
|
@@ -5390,6 +5753,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5390
5753
|
accessibilityPurpose: null,
|
|
5391
5754
|
external: false,
|
|
5392
5755
|
fastSwitching: false,
|
|
5756
|
+
fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType(
|
|
5757
|
+
mimeType, '')]),
|
|
5393
5758
|
};
|
|
5394
5759
|
manifest.textStreams.push(textStream);
|
|
5395
5760
|
closedCaptionsSet.add(id);
|
|
@@ -5399,82 +5764,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5399
5764
|
}
|
|
5400
5765
|
}
|
|
5401
5766
|
|
|
5402
|
-
/**
|
|
5403
|
-
* Filters a manifest, removing unplayable streams/variants.
|
|
5404
|
-
*
|
|
5405
|
-
* @param {?shaka.extern.Manifest} manifest
|
|
5406
|
-
* @private
|
|
5407
|
-
*/
|
|
5408
|
-
async filterManifest_(manifest) {
|
|
5409
|
-
await this.filterManifestWithStreamUtils_(manifest);
|
|
5410
|
-
this.filterManifestWithRestrictions_(manifest);
|
|
5411
|
-
}
|
|
5412
|
-
|
|
5413
|
-
/**
|
|
5414
|
-
* Filters a manifest, removing unplayable streams/variants.
|
|
5415
|
-
*
|
|
5416
|
-
* @param {?shaka.extern.Manifest} manifest
|
|
5417
|
-
* @private
|
|
5418
|
-
*/
|
|
5419
|
-
async filterManifestWithStreamUtils_(manifest) {
|
|
5420
|
-
goog.asserts.assert(manifest, 'Manifest should exist!');
|
|
5421
|
-
await shaka.util.StreamUtils.filterManifest(this.drmEngine_, manifest);
|
|
5422
|
-
this.checkPlayableVariants_(manifest);
|
|
5423
|
-
}
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
/**
|
|
5427
|
-
* Apply the restrictions configuration to the manifest, and check if there's
|
|
5428
|
-
* a variant that meets the restrictions.
|
|
5429
|
-
*
|
|
5430
|
-
* @param {?shaka.extern.Manifest} manifest
|
|
5431
|
-
* @private
|
|
5432
|
-
*/
|
|
5433
|
-
filterManifestWithRestrictions_(manifest) {
|
|
5434
|
-
// Return if |destroy| is called.
|
|
5435
|
-
if (this.loadMode_ == shaka.Player.LoadMode.DESTROYED) {
|
|
5436
|
-
return;
|
|
5437
|
-
}
|
|
5438
|
-
const tracksChanged = shaka.util.StreamUtils.applyRestrictions(
|
|
5439
|
-
manifest.variants, this.config_.restrictions, this.maxHwRes_);
|
|
5440
|
-
if (tracksChanged && this.streamingEngine_) {
|
|
5441
|
-
this.onTracksChanged_();
|
|
5442
|
-
}
|
|
5443
|
-
|
|
5444
|
-
// We may need to create new sessions for any new init data.
|
|
5445
|
-
const currentDrmInfo =
|
|
5446
|
-
this.drmEngine_ ? this.drmEngine_.getDrmInfo() : null;
|
|
5447
|
-
// DrmEngine.newInitData() requires mediaKeys to be available.
|
|
5448
|
-
if (currentDrmInfo && this.drmEngine_.getMediaKeys()) {
|
|
5449
|
-
for (const variant of manifest.variants) {
|
|
5450
|
-
this.processDrmInfos_(currentDrmInfo.keySystem, variant.video);
|
|
5451
|
-
this.processDrmInfos_(currentDrmInfo.keySystem, variant.audio);
|
|
5452
|
-
}
|
|
5453
|
-
}
|
|
5454
|
-
this.checkRestrictedVariants_(manifest);
|
|
5455
|
-
}
|
|
5456
|
-
|
|
5457
|
-
/**
|
|
5458
|
-
* @param {string} keySystem
|
|
5459
|
-
* @param {?shaka.extern.Stream} stream
|
|
5460
|
-
* @private
|
|
5461
|
-
*/
|
|
5462
|
-
processDrmInfos_(keySystem, stream) {
|
|
5463
|
-
if (!stream) {
|
|
5464
|
-
return;
|
|
5465
|
-
}
|
|
5466
|
-
|
|
5467
|
-
for (const drmInfo of stream.drmInfos) {
|
|
5468
|
-
// Ignore any data for different key systems.
|
|
5469
|
-
if (drmInfo.keySystem == keySystem) {
|
|
5470
|
-
for (const initData of (drmInfo.initData || [])) {
|
|
5471
|
-
this.drmEngine_.newInitData(
|
|
5472
|
-
initData.initDataType, initData.initData);
|
|
5473
|
-
}
|
|
5474
|
-
}
|
|
5475
|
-
}
|
|
5476
|
-
}
|
|
5477
|
-
|
|
5478
5767
|
/**
|
|
5479
5768
|
* @param {shaka.extern.Variant} initialVariant
|
|
5480
5769
|
* @param {number} time
|
|
@@ -5553,7 +5842,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5553
5842
|
// buffering.
|
|
5554
5843
|
const eventName = shaka.util.FakeEvent.EventName.Buffering;
|
|
5555
5844
|
const data = (new Map()).set('buffering', isBuffering);
|
|
5556
|
-
this.dispatchEvent(
|
|
5845
|
+
this.dispatchEvent(shaka.Player.makeEvent_(eventName, data));
|
|
5557
5846
|
}
|
|
5558
5847
|
|
|
5559
5848
|
/**
|
|
@@ -5586,7 +5875,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5586
5875
|
this.playRateController_.set(newRate);
|
|
5587
5876
|
}
|
|
5588
5877
|
|
|
5589
|
-
const event =
|
|
5878
|
+
const event = shaka.Player.makeEvent_(
|
|
5879
|
+
shaka.util.FakeEvent.EventName.RateChange);
|
|
5590
5880
|
this.dispatchEvent(event);
|
|
5591
5881
|
}
|
|
5592
5882
|
|
|
@@ -5622,20 +5912,46 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5622
5912
|
if (stateChanged) {
|
|
5623
5913
|
const eventName = shaka.util.FakeEvent.EventName.StateChanged;
|
|
5624
5914
|
const data = (new Map()).set('newstate', updateState);
|
|
5625
|
-
this.dispatchEvent(
|
|
5915
|
+
this.dispatchEvent(shaka.Player.makeEvent_(eventName, data));
|
|
5626
5916
|
}
|
|
5627
5917
|
}
|
|
5628
5918
|
|
|
5629
5919
|
/**
|
|
5630
|
-
* Callback for liveSync
|
|
5920
|
+
* Callback for liveSync and vodDynamicPlaybackRate
|
|
5631
5921
|
*
|
|
5632
5922
|
* @private
|
|
5633
5923
|
*/
|
|
5634
5924
|
onTimeUpdate_() {
|
|
5925
|
+
const playbackRate = this.video_.playbackRate;
|
|
5926
|
+
const isLive = this.isLive();
|
|
5927
|
+
|
|
5928
|
+
if (this.config_.streaming.vodDynamicPlaybackRate && !isLive) {
|
|
5929
|
+
const minPlaybackRate =
|
|
5930
|
+
this.config_.streaming.vodDynamicPlaybackRateLowBufferRate;
|
|
5931
|
+
const bufferFullness = this.getBufferFullness();
|
|
5932
|
+
const bufferThreshold =
|
|
5933
|
+
this.config_.streaming.vodDynamicPlaybackRateBufferRatio;
|
|
5934
|
+
|
|
5935
|
+
if (bufferFullness <= bufferThreshold) {
|
|
5936
|
+
if (playbackRate != minPlaybackRate) {
|
|
5937
|
+
shaka.log.debug('Buffer fullness ratio (' + bufferFullness + ') ' +
|
|
5938
|
+
'is less than the vodDynamicPlaybackRateBufferRatio (' +
|
|
5939
|
+
bufferThreshold + '). Updating playbackRate to ' + minPlaybackRate);
|
|
5940
|
+
this.trickPlay(minPlaybackRate);
|
|
5941
|
+
}
|
|
5942
|
+
} else if (bufferFullness == 1) {
|
|
5943
|
+
if (playbackRate !== this.playRateController_.getDefaultRate()) {
|
|
5944
|
+
shaka.log.debug('Buffer is full. Cancel trick play.');
|
|
5945
|
+
this.cancelTrickPlay();
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
|
|
5635
5950
|
// If the live stream has reached its end, do not sync.
|
|
5636
|
-
if (!
|
|
5951
|
+
if (!isLive) {
|
|
5637
5952
|
return;
|
|
5638
5953
|
}
|
|
5954
|
+
|
|
5639
5955
|
const seekRange = this.seekRange();
|
|
5640
5956
|
if (!Number.isFinite(seekRange.end)) {
|
|
5641
5957
|
return;
|
|
@@ -5680,7 +5996,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5680
5996
|
}
|
|
5681
5997
|
}
|
|
5682
5998
|
|
|
5683
|
-
const playbackRate = this.video_.playbackRate;
|
|
5684
5999
|
const latency = seekRange.end - this.video_.currentTime;
|
|
5685
6000
|
let offset = 0;
|
|
5686
6001
|
// In src= mode, the seek range isn't updated frequently enough, so we need
|
|
@@ -5695,7 +6010,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5695
6010
|
}
|
|
5696
6011
|
}
|
|
5697
6012
|
|
|
5698
|
-
|
|
6013
|
+
const panicMode = this.config_.streaming.liveSyncPanicMode;
|
|
6014
|
+
const panicThreshold = this.config_.streaming.liveSyncPanicThreshold * 1000;
|
|
6015
|
+
const timeSinceLastRebuffer =
|
|
6016
|
+
Date.now() - this.bufferObserver_.getLastRebufferTime();
|
|
6017
|
+
if (panicMode && !liveSyncMinPlaybackRate) {
|
|
6018
|
+
liveSyncMinPlaybackRate = this.config_.streaming.liveSyncMinPlaybackRate;
|
|
6019
|
+
}
|
|
6020
|
+
|
|
6021
|
+
if (panicMode && liveSyncMinPlaybackRate &&
|
|
6022
|
+
timeSinceLastRebuffer <= panicThreshold) {
|
|
6023
|
+
if (playbackRate != liveSyncMinPlaybackRate) {
|
|
6024
|
+
shaka.log.debug('Time since last rebuffer (' +
|
|
6025
|
+
timeSinceLastRebuffer + 's) ' +
|
|
6026
|
+
'is less than the liveSyncPanicThreshold (' + panicThreshold +
|
|
6027
|
+
's). Updating playbackRate to ' + liveSyncMinPlaybackRate);
|
|
6028
|
+
this.trickPlay(liveSyncMinPlaybackRate);
|
|
6029
|
+
}
|
|
6030
|
+
} else if (liveSyncMaxLatency && liveSyncPlaybackRate &&
|
|
5699
6031
|
(latency - offset) > liveSyncMaxLatency) {
|
|
5700
6032
|
if (playbackRate != liveSyncPlaybackRate) {
|
|
5701
6033
|
shaka.log.debug('Latency (' + latency + 's) ' +
|
|
@@ -5743,15 +6075,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5743
6075
|
if (hasNewCompletionPercent) {
|
|
5744
6076
|
let event;
|
|
5745
6077
|
if (this.completionPercent_ == 0) {
|
|
5746
|
-
event =
|
|
6078
|
+
event = shaka.Player.makeEvent_(shaka.util.FakeEvent.EventName.Started);
|
|
5747
6079
|
} else if (this.completionPercent_ == 25) {
|
|
5748
|
-
event =
|
|
6080
|
+
event = shaka.Player.makeEvent_(
|
|
6081
|
+
shaka.util.FakeEvent.EventName.FirstQuartile);
|
|
5749
6082
|
} else if (this.completionPercent_ == 50) {
|
|
5750
|
-
event =
|
|
6083
|
+
event = shaka.Player.makeEvent_(
|
|
6084
|
+
shaka.util.FakeEvent.EventName.Midpoint);
|
|
5751
6085
|
} else if (this.completionPercent_ == 75) {
|
|
5752
|
-
event =
|
|
6086
|
+
event = shaka.Player.makeEvent_(
|
|
6087
|
+
shaka.util.FakeEvent.EventName.ThirdQuartile);
|
|
5753
6088
|
} else if (this.completionPercent_ == 100) {
|
|
5754
|
-
event =
|
|
6089
|
+
event = shaka.Player.makeEvent_(
|
|
6090
|
+
shaka.util.FakeEvent.EventName.Complete);
|
|
5755
6091
|
}
|
|
5756
6092
|
if (event) {
|
|
5757
6093
|
this.dispatchEvent(event);
|
|
@@ -5791,7 +6127,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5791
6127
|
updateAbrManagerVariants_() {
|
|
5792
6128
|
try {
|
|
5793
6129
|
goog.asserts.assert(this.manifest_, 'Manifest should exist by now!');
|
|
5794
|
-
this.
|
|
6130
|
+
this.manifestFilterer_.checkRestrictedVariants(this.manifest_);
|
|
5795
6131
|
} catch (e) {
|
|
5796
6132
|
this.onError_(e);
|
|
5797
6133
|
return false;
|
|
@@ -6120,7 +6456,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6120
6456
|
.set('start', start)
|
|
6121
6457
|
.set('end', end)
|
|
6122
6458
|
.set('contentType', contentType);
|
|
6123
|
-
this.dispatchEvent(
|
|
6459
|
+
this.dispatchEvent(shaka.Player.makeEvent_(
|
|
6124
6460
|
shaka.util.FakeEvent.EventName.SegmentAppended, data));
|
|
6125
6461
|
}
|
|
6126
6462
|
|
|
@@ -6170,8 +6506,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6170
6506
|
if (this.lcevcDec_) {
|
|
6171
6507
|
this.lcevcDec_.updateVariant(to, this.getManifestType());
|
|
6172
6508
|
}
|
|
6173
|
-
const event =
|
|
6174
|
-
|
|
6509
|
+
const event = shaka.Player.makeEvent_(
|
|
6510
|
+
shaka.util.FakeEvent.EventName.Adaptation, data);
|
|
6175
6511
|
this.delayDispatchEvent_(event);
|
|
6176
6512
|
}
|
|
6177
6513
|
|
|
@@ -6182,7 +6518,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6182
6518
|
onTracksChanged_() {
|
|
6183
6519
|
// Delay the 'trackschanged' event so StreamingEngine has time to absorb the
|
|
6184
6520
|
// changes before the user tries to query it.
|
|
6185
|
-
const event =
|
|
6521
|
+
const event = shaka.Player.makeEvent_(
|
|
6522
|
+
shaka.util.FakeEvent.EventName.TracksChanged);
|
|
6186
6523
|
this.delayDispatchEvent_(event);
|
|
6187
6524
|
}
|
|
6188
6525
|
|
|
@@ -6202,8 +6539,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6202
6539
|
this.lcevcDec_.updateVariant(to, this.getManifestType());
|
|
6203
6540
|
}
|
|
6204
6541
|
|
|
6205
|
-
const event =
|
|
6206
|
-
|
|
6542
|
+
const event = shaka.Player.makeEvent_(
|
|
6543
|
+
shaka.util.FakeEvent.EventName.VariantChanged, data);
|
|
6207
6544
|
this.delayDispatchEvent_(event);
|
|
6208
6545
|
}
|
|
6209
6546
|
|
|
@@ -6214,14 +6551,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6214
6551
|
onTextChanged_() {
|
|
6215
6552
|
// Delay the 'textchanged' event so StreamingEngine time to absorb the
|
|
6216
6553
|
// changes before the user tries to query it.
|
|
6217
|
-
const event =
|
|
6554
|
+
const event = shaka.Player.makeEvent_(
|
|
6555
|
+
shaka.util.FakeEvent.EventName.TextChanged);
|
|
6218
6556
|
this.delayDispatchEvent_(event);
|
|
6219
6557
|
}
|
|
6220
6558
|
|
|
6221
6559
|
/** @private */
|
|
6222
6560
|
onTextTrackVisibility_() {
|
|
6223
|
-
const event =
|
|
6224
|
-
|
|
6561
|
+
const event = shaka.Player.makeEvent_(
|
|
6562
|
+
shaka.util.FakeEvent.EventName.TextTrackVisibility);
|
|
6225
6563
|
this.delayDispatchEvent_(event);
|
|
6226
6564
|
}
|
|
6227
6565
|
|
|
@@ -6233,7 +6571,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6233
6571
|
}
|
|
6234
6572
|
|
|
6235
6573
|
const data = (new Map()).set('newStatus', this.config_.abr.enabled);
|
|
6236
|
-
this.delayDispatchEvent_(
|
|
6574
|
+
this.delayDispatchEvent_(shaka.Player.makeEvent_(
|
|
6237
6575
|
shaka.util.FakeEvent.EventName.AbrStatusChanged, data));
|
|
6238
6576
|
}
|
|
6239
6577
|
|
|
@@ -6367,7 +6705,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6367
6705
|
}
|
|
6368
6706
|
|
|
6369
6707
|
const eventName = shaka.util.FakeEvent.EventName.Error;
|
|
6370
|
-
const event =
|
|
6708
|
+
const event = shaka.Player.makeEvent_(
|
|
6709
|
+
eventName, (new Map()).set('detail', error));
|
|
6371
6710
|
this.dispatchEvent(event);
|
|
6372
6711
|
if (event.defaultPrevented) {
|
|
6373
6712
|
error.handled = true;
|
|
@@ -6382,10 +6721,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6382
6721
|
*
|
|
6383
6722
|
* @param {!shaka.util.FakeEvent.EventName} eventName
|
|
6384
6723
|
* @param {shaka.extern.TimelineRegionInfo} region
|
|
6724
|
+
* @param {shaka.util.FakeEventTarget=} eventTarget
|
|
6385
6725
|
*
|
|
6386
6726
|
* @private
|
|
6387
6727
|
*/
|
|
6388
|
-
onRegionEvent_(eventName, region) {
|
|
6728
|
+
onRegionEvent_(eventName, region, eventTarget = this) {
|
|
6389
6729
|
// Always make a copy to avoid exposing our internal data to the app.
|
|
6390
6730
|
const clone = {
|
|
6391
6731
|
schemeIdUri: region.schemeIdUri,
|
|
@@ -6394,10 +6734,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6394
6734
|
endTime: region.endTime,
|
|
6395
6735
|
id: region.id,
|
|
6396
6736
|
eventElement: region.eventElement,
|
|
6737
|
+
eventNode: region.eventNode,
|
|
6397
6738
|
};
|
|
6398
6739
|
|
|
6399
6740
|
const data = (new Map()).set('detail', clone);
|
|
6400
|
-
|
|
6741
|
+
eventTarget.dispatchEvent(shaka.Player.makeEvent_(eventName, data));
|
|
6401
6742
|
}
|
|
6402
6743
|
|
|
6403
6744
|
/**
|
|
@@ -6428,7 +6769,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6428
6769
|
.set('mediaQuality', clone)
|
|
6429
6770
|
.set('position', position);
|
|
6430
6771
|
|
|
6431
|
-
this.dispatchEvent(
|
|
6772
|
+
this.dispatchEvent(shaka.Player.makeEvent_(
|
|
6432
6773
|
shaka.util.FakeEvent.EventName.MediaQualityChanged, data));
|
|
6433
6774
|
}
|
|
6434
6775
|
|
|
@@ -6491,14 +6832,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6491
6832
|
* @private
|
|
6492
6833
|
*/
|
|
6493
6834
|
onKeyStatus_(keyStatusMap) {
|
|
6494
|
-
|
|
6495
|
-
// We can't use this info to manage restrictions in src= mode, so ignore
|
|
6496
|
-
// it.
|
|
6497
|
-
return;
|
|
6498
|
-
}
|
|
6835
|
+
goog.asserts.assert(this.streamingEngine_, 'Cannot be called in src= mode');
|
|
6499
6836
|
|
|
6500
|
-
const event =
|
|
6501
|
-
|
|
6837
|
+
const event = shaka.Player.makeEvent_(
|
|
6838
|
+
shaka.util.FakeEvent.EventName.KeyStatusChanged);
|
|
6502
6839
|
this.dispatchEvent(event);
|
|
6503
6840
|
|
|
6504
6841
|
const keyIds = Object.keys(keyStatusMap);
|
|
@@ -6520,7 +6857,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6520
6857
|
'restrictions so we don\'t select those tracks.');
|
|
6521
6858
|
}
|
|
6522
6859
|
|
|
6523
|
-
const restrictedStatuses = shaka.
|
|
6860
|
+
const restrictedStatuses = shaka.media.ManifestFilterer.restrictedStatuses;
|
|
6524
6861
|
let tracksChanged = false;
|
|
6525
6862
|
|
|
6526
6863
|
goog.asserts.assert(this.drmEngine_, 'drmEngine should be non-null here.');
|
|
@@ -6569,22 +6906,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6569
6906
|
}
|
|
6570
6907
|
}
|
|
6571
6908
|
|
|
6572
|
-
/**
|
|
6573
|
-
* Callback from DrmEngine
|
|
6574
|
-
* @param {string} keyId
|
|
6575
|
-
* @param {number} expiration
|
|
6576
|
-
* @private
|
|
6577
|
-
*/
|
|
6578
|
-
onExpirationUpdated_(keyId, expiration) {
|
|
6579
|
-
if (this.parser_ && this.parser_.onExpirationUpdated) {
|
|
6580
|
-
this.parser_.onExpirationUpdated(keyId, expiration);
|
|
6581
|
-
}
|
|
6582
|
-
|
|
6583
|
-
const event =
|
|
6584
|
-
this.makeEvent_(shaka.util.FakeEvent.EventName.ExpirationUpdated);
|
|
6585
|
-
this.dispatchEvent(event);
|
|
6586
|
-
}
|
|
6587
|
-
|
|
6588
6909
|
/**
|
|
6589
6910
|
* @return {boolean} true if we should stream text right now.
|
|
6590
6911
|
* @private
|
|
@@ -6628,93 +6949,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6628
6949
|
}
|
|
6629
6950
|
}
|
|
6630
6951
|
|
|
6631
|
-
/**
|
|
6632
|
-
* Checks if the variants are all restricted, and throw an appropriate
|
|
6633
|
-
* exception if so.
|
|
6634
|
-
*
|
|
6635
|
-
* @param {shaka.extern.Manifest} manifest
|
|
6636
|
-
*
|
|
6637
|
-
* @private
|
|
6638
|
-
*/
|
|
6639
|
-
checkRestrictedVariants_(manifest) {
|
|
6640
|
-
const restrictedStatuses = shaka.Player.restrictedStatuses_;
|
|
6641
|
-
const keyStatusMap =
|
|
6642
|
-
this.drmEngine_ ? this.drmEngine_.getKeyStatuses() : {};
|
|
6643
|
-
const keyIds = Object.keys(keyStatusMap);
|
|
6644
|
-
const isGlobalStatus = keyIds.length && keyIds[0] == '00';
|
|
6645
|
-
|
|
6646
|
-
let hasPlayable = false;
|
|
6647
|
-
let hasAppRestrictions = false;
|
|
6648
|
-
|
|
6649
|
-
/** @type {!Set.<string>} */
|
|
6650
|
-
const missingKeys = new Set();
|
|
6651
|
-
|
|
6652
|
-
/** @type {!Set.<string>} */
|
|
6653
|
-
const badKeyStatuses = new Set();
|
|
6654
|
-
|
|
6655
|
-
for (const variant of manifest.variants) {
|
|
6656
|
-
// TODO: Combine with onKeyStatus_.
|
|
6657
|
-
const streams = [];
|
|
6658
|
-
if (variant.audio) {
|
|
6659
|
-
streams.push(variant.audio);
|
|
6660
|
-
}
|
|
6661
|
-
if (variant.video) {
|
|
6662
|
-
streams.push(variant.video);
|
|
6663
|
-
}
|
|
6664
|
-
|
|
6665
|
-
for (const stream of streams) {
|
|
6666
|
-
if (stream.keyIds.size) {
|
|
6667
|
-
for (const keyId of stream.keyIds) {
|
|
6668
|
-
const keyStatus = keyStatusMap[isGlobalStatus ? '00' : keyId];
|
|
6669
|
-
if (!keyStatus) {
|
|
6670
|
-
missingKeys.add(keyId);
|
|
6671
|
-
} else if (restrictedStatuses.includes(keyStatus)) {
|
|
6672
|
-
badKeyStatuses.add(keyStatus);
|
|
6673
|
-
}
|
|
6674
|
-
}
|
|
6675
|
-
} // if (stream.keyIds.size)
|
|
6676
|
-
}
|
|
6677
|
-
|
|
6678
|
-
if (!variant.allowedByApplication) {
|
|
6679
|
-
hasAppRestrictions = true;
|
|
6680
|
-
} else if (variant.allowedByKeySystem) {
|
|
6681
|
-
hasPlayable = true;
|
|
6682
|
-
}
|
|
6683
|
-
}
|
|
6684
|
-
|
|
6685
|
-
if (!hasPlayable) {
|
|
6686
|
-
/** @type {shaka.extern.RestrictionInfo} */
|
|
6687
|
-
const data = {
|
|
6688
|
-
hasAppRestrictions,
|
|
6689
|
-
missingKeys: Array.from(missingKeys),
|
|
6690
|
-
restrictedKeyStatuses: Array.from(badKeyStatuses),
|
|
6691
|
-
};
|
|
6692
|
-
throw new shaka.util.Error(
|
|
6693
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
6694
|
-
shaka.util.Error.Category.MANIFEST,
|
|
6695
|
-
shaka.util.Error.Code.RESTRICTIONS_CANNOT_BE_MET,
|
|
6696
|
-
data);
|
|
6697
|
-
}
|
|
6698
|
-
}
|
|
6699
|
-
|
|
6700
|
-
/**
|
|
6701
|
-
* Confirm some variants are playable. Otherwise, throw an exception.
|
|
6702
|
-
* @param {!shaka.extern.Manifest} manifest
|
|
6703
|
-
* @private
|
|
6704
|
-
*/
|
|
6705
|
-
checkPlayableVariants_(manifest) {
|
|
6706
|
-
const valid = manifest.variants.some(shaka.util.StreamUtils.isPlayable);
|
|
6707
|
-
|
|
6708
|
-
// If none of the variants are playable, throw
|
|
6709
|
-
// CONTENT_UNSUPPORTED_BY_BROWSER.
|
|
6710
|
-
if (!valid) {
|
|
6711
|
-
throw new shaka.util.Error(
|
|
6712
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
6713
|
-
shaka.util.Error.Category.MANIFEST,
|
|
6714
|
-
shaka.util.Error.Code.CONTENT_UNSUPPORTED_BY_BROWSER);
|
|
6715
|
-
}
|
|
6716
|
-
}
|
|
6717
|
-
|
|
6718
6952
|
/**
|
|
6719
6953
|
* Fire an event, but wait a little bit so that the immediate execution can
|
|
6720
6954
|
* complete before the event is handled.
|
|
@@ -6947,25 +7181,13 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6947
7181
|
* @export
|
|
6948
7182
|
*/
|
|
6949
7183
|
// eslint-disable-next-line no-useless-concat
|
|
6950
|
-
shaka.Player.version = 'v4.
|
|
7184
|
+
shaka.Player.version = 'v4.8.0' + '-uncompiled'; // x-release-please-version
|
|
6951
7185
|
|
|
6952
7186
|
// Initialize the deprecation system using the version string we just set
|
|
6953
7187
|
// on the player.
|
|
6954
7188
|
shaka.Deprecate.init(shaka.Player.version);
|
|
6955
7189
|
|
|
6956
7190
|
|
|
6957
|
-
/**
|
|
6958
|
-
* These are the EME key statuses that represent restricted playback.
|
|
6959
|
-
* 'usable', 'released', 'output-downscaled', 'status-pending' are statuses
|
|
6960
|
-
* of the usable keys. 'expired' status is being handled separately in
|
|
6961
|
-
* DrmEngine.
|
|
6962
|
-
*
|
|
6963
|
-
* @const {!Array.<string>}
|
|
6964
|
-
* @private
|
|
6965
|
-
*/
|
|
6966
|
-
shaka.Player.restrictedStatuses_ = ['output-restricted', 'internal-error'];
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
7191
|
/** @private {!Object.<string, function():*>} */
|
|
6970
7192
|
shaka.Player.supportPlugins_ = {};
|
|
6971
7193
|
|