shaka-player 4.0.0 → 4.1.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/.github/config.yml +1 -0
- package/.github/workflows/release-please.yaml +6 -11
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTORS +1 -0
- package/README.md +4 -4
- package/build/misspellings.txt +1 -0
- package/demo/common/message_ids.js +1 -0
- package/demo/config.js +3 -1
- package/demo/demo.less +0 -1
- package/demo/locales/en.json +1 -0
- package/demo/locales/source.json +4 -0
- package/dist/controls.css +3 -3
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +4 -4
- package/dist/demo.compiled.debug.map +2 -2
- package/dist/demo.compiled.js +4 -4
- package/dist/demo.compiled.map +2 -2
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +3 -3
- package/dist/locales.js +32 -32
- package/dist/receiver.compiled.debug.js +2 -2
- package/dist/receiver.compiled.debug.map +2 -2
- package/dist/receiver.compiled.js +2 -2
- package/dist/receiver.compiled.map +2 -2
- package/dist/shaka-player.compiled.d.ts +15 -8
- package/dist/shaka-player.compiled.debug.d.ts +15 -8
- package/dist/shaka-player.compiled.debug.externs.js +11 -2
- package/dist/shaka-player.compiled.debug.js +266 -253
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +11 -2
- package/dist/shaka-player.compiled.js +792 -784
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +16 -9
- package/dist/shaka-player.ui.debug.d.ts +16 -9
- package/dist/shaka-player.ui.debug.externs.js +11 -2
- package/dist/shaka-player.ui.debug.js +299 -282
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +11 -2
- package/dist/shaka-player.ui.js +948 -941
- package/dist/shaka-player.ui.map +3 -3
- package/docs/design/chromecast.md +3 -1
- package/docs/tutorials/drm-config.md +16 -0
- package/docs/tutorials/ui.md +26 -0
- package/docs/tutorials/upgrade.md +4 -2
- package/externs/chromecast.js +9 -2
- package/externs/shaka/manifest.js +7 -0
- package/externs/shaka/player.js +32 -2
- package/externs/shaka/text.js +7 -0
- package/karma.conf.js +4 -0
- package/lib/abr/simple_abr_manager.js +2 -1
- package/lib/cast/cast_proxy.js +19 -8
- package/lib/cast/cast_receiver.js +3 -3
- package/lib/cast/cast_sender.js +12 -2
- package/lib/dash/dash_parser.js +2 -2
- package/lib/hls/hls_parser.js +46 -15
- package/lib/media/drm_engine.js +36 -8
- package/lib/media/gap_jumping_controller.js +23 -1
- package/lib/media/media_source_engine.js +91 -37
- package/lib/media/playhead.js +47 -4
- package/lib/media/stall_detector.js +19 -2
- package/lib/media/streaming_engine.js +9 -3
- package/lib/offline/indexeddb/storage_mechanism.js +4 -2
- package/lib/offline/manifest_converter.js +1 -0
- package/lib/player.js +339 -175
- package/lib/text/cue.js +6 -0
- package/lib/text/ui_text_displayer.js +1 -0
- package/lib/text/vtt_text_parser.js +11 -7
- package/lib/util/config_utils.js +25 -0
- package/lib/util/error.js +13 -0
- package/lib/util/fake_event.js +39 -0
- package/lib/util/manifest_parser_utils.js +1 -1
- package/lib/util/mime_utils.js +62 -6
- package/lib/util/periods.js +5 -4
- package/lib/util/player_configuration.js +16 -14
- package/lib/util/stats.js +28 -1
- package/lib/util/stream_utils.js +11 -2
- package/package.json +5 -2
- package/test/cast/cast_proxy_unit.js +3 -1
- package/test/cast/cast_sender_unit.js +21 -6
- package/test/demo/demo_unit.js +2 -1
- package/test/hls/hls_live_unit.js +79 -7
- package/test/hls/manifest_text_parser_unit.js +19 -0
- package/test/media/adaptation_set_unit.js +1 -0
- package/test/media/playhead_unit.js +114 -25
- package/test/media/stall_detector_unit.js +20 -2
- package/test/media/streaming_engine_integration.js +2 -1
- package/test/media/streaming_engine_unit.js +1 -0
- package/test/offline/indexeddb_storage_unit.js +1 -1
- package/test/offline/storage_compatibility_unit.js +1 -1
- package/test/player_external.js +7 -1
- package/test/player_integration.js +9 -10
- package/test/player_src_equals_integration.js +81 -83
- package/test/player_unit.js +240 -2
- package/test/test/boot.js +9 -0
- package/test/test/externs/filters.js +4 -0
- package/test/test/util/manifest_generator.js +2 -0
- package/test/test/util/simple_fakes.js +45 -2
- package/test/test/util/streaming_engine_util.js +1 -0
- package/test/text/vtt_text_parser_unit.js +68 -5
- package/test/ui/ui_unit.js +18 -5
- package/test/util/mime_utils_unit.js +54 -0
- package/test/util/periods_unit.js +79 -0
- package/test/util/stream_utils_unit.js +108 -0
- package/ui/controls.js +12 -8
- package/ui/externs/ui.js +10 -1
- package/ui/less/containers.less +1 -1
- package/ui/less/general.less +1 -1
- package/ui/less/range_elements.less +1 -0
- package/ui/resolution_selection.js +1 -1
- package/ui/statistics_button.js +10 -0
- package/ui/ui.js +12 -1
- package/.github/pull_request_template.md +0 -10
- package/docs/tutorials/gsoc-ideas-list.md +0 -3
package/lib/player.js
CHANGED
|
@@ -385,6 +385,26 @@ goog.requireType('shaka.routing.Payload');
|
|
|
385
385
|
*/
|
|
386
386
|
|
|
387
387
|
|
|
388
|
+
/**
|
|
389
|
+
* @event shaka.Player.StallDetectedEvent
|
|
390
|
+
* @description Fired when a stall in playback is detected by the StallDetector.
|
|
391
|
+
* Not all stalls are caused by gaps in the buffered ranges.
|
|
392
|
+
* @property {string} type
|
|
393
|
+
* 'stalldetected'
|
|
394
|
+
* @exportDoc
|
|
395
|
+
*/
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* @event shaka.Player.GapJumpedEvent
|
|
400
|
+
* @description Fired when the GapJumpingController jumps over a gap in the
|
|
401
|
+
* buffered ranges.
|
|
402
|
+
* @property {string} type
|
|
403
|
+
* 'gapjumped'
|
|
404
|
+
* @exportDoc
|
|
405
|
+
*/
|
|
406
|
+
|
|
407
|
+
|
|
388
408
|
/**
|
|
389
409
|
* @summary The main player object for Shaka Player.
|
|
390
410
|
*
|
|
@@ -423,8 +443,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
423
443
|
*/
|
|
424
444
|
this.isTextVisible_ = false;
|
|
425
445
|
|
|
426
|
-
/**
|
|
427
|
-
|
|
446
|
+
/**
|
|
447
|
+
* For listeners scoped to the lifetime of the Player instance.
|
|
448
|
+
* @private {shaka.util.EventManager}
|
|
449
|
+
*/
|
|
450
|
+
this.globalEventManager_ = new shaka.util.EventManager();
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* For listeners scoped to the lifetime of the media element attachment.
|
|
454
|
+
* @private {shaka.util.EventManager}
|
|
455
|
+
*/
|
|
456
|
+
this.attachEventManager_ = new shaka.util.EventManager();
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* For listeners scoped to the lifetime of the loaded content.
|
|
460
|
+
* @private {shaka.util.EventManager}
|
|
461
|
+
*/
|
|
462
|
+
this.loadEventManager_ = new shaka.util.EventManager();
|
|
428
463
|
|
|
429
464
|
/** @private {shaka.net.NetworkingEngine} */
|
|
430
465
|
this.networkingEngine_ = null;
|
|
@@ -564,7 +599,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
564
599
|
|
|
565
600
|
// If the browser comes back online after being offline, then try to play
|
|
566
601
|
// again.
|
|
567
|
-
this.
|
|
602
|
+
this.globalEventManager_.listen(window, 'online', () => {
|
|
568
603
|
this.retryStreaming();
|
|
569
604
|
});
|
|
570
605
|
|
|
@@ -637,7 +672,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
637
672
|
},
|
|
638
673
|
enterNode: (node, has, wants) => {
|
|
639
674
|
this.dispatchEvent(this.makeEvent_(
|
|
640
|
-
/* name= */ shaka.
|
|
675
|
+
/* name= */ shaka.util.FakeEvent.EventName.OnStateChange,
|
|
641
676
|
/* data= */ (new Map()).set('state', node.name)));
|
|
642
677
|
|
|
643
678
|
const action = actions.get(node);
|
|
@@ -668,7 +703,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
668
703
|
},
|
|
669
704
|
onIdle: (node) => {
|
|
670
705
|
this.dispatchEvent(this.makeEvent_(
|
|
671
|
-
/* name= */ shaka.
|
|
706
|
+
/* name= */ shaka.util.FakeEvent.EventName.OnStateIdle,
|
|
672
707
|
/* data= */ (new Map()).set('state', node.name)));
|
|
673
708
|
},
|
|
674
709
|
};
|
|
@@ -679,6 +714,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
679
714
|
shaka.Player.createEmptyPayload_(),
|
|
680
715
|
walkerImplementation);
|
|
681
716
|
|
|
717
|
+
/** @private {shaka.util.Timer} */
|
|
718
|
+
this.checkVariantsTimer_ =
|
|
719
|
+
new shaka.util.Timer(() => this.checkVariants_());
|
|
720
|
+
|
|
682
721
|
// Even though |attach| will start in later interpreter cycles, it should be
|
|
683
722
|
// the LAST thing we do in the constructor because conceptually it relies on
|
|
684
723
|
// player having been initialized.
|
|
@@ -688,7 +727,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
688
727
|
}
|
|
689
728
|
|
|
690
729
|
/**
|
|
691
|
-
* @param {!shaka.
|
|
730
|
+
* @param {!shaka.util.FakeEvent.EventName} name
|
|
692
731
|
* @param {Map.<string, Object>=} data
|
|
693
732
|
* @return {!shaka.util.FakeEvent}
|
|
694
733
|
* @private
|
|
@@ -752,10 +791,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
752
791
|
});
|
|
753
792
|
await this.walker_.destroy();
|
|
754
793
|
|
|
755
|
-
// Tear-down the event
|
|
756
|
-
if (this.
|
|
757
|
-
this.
|
|
758
|
-
this.
|
|
794
|
+
// Tear-down the event managers to ensure handlers stop firing.
|
|
795
|
+
if (this.globalEventManager_) {
|
|
796
|
+
this.globalEventManager_.release();
|
|
797
|
+
this.globalEventManager_ = null;
|
|
798
|
+
}
|
|
799
|
+
if (this.attachEventManager_) {
|
|
800
|
+
this.attachEventManager_.release();
|
|
801
|
+
this.attachEventManager_ = null;
|
|
802
|
+
}
|
|
803
|
+
if (this.loadEventManager_) {
|
|
804
|
+
this.loadEventManager_.release();
|
|
805
|
+
this.loadEventManager_ = null;
|
|
759
806
|
}
|
|
760
807
|
|
|
761
808
|
this.abrManagerFactory_ = null;
|
|
@@ -1106,7 +1153,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1106
1153
|
|
|
1107
1154
|
// We dispatch the loading event when someone calls |load| because we want
|
|
1108
1155
|
// to surface the user intent.
|
|
1109
|
-
this.dispatchEvent(this.makeEvent_(shaka.
|
|
1156
|
+
this.dispatchEvent(this.makeEvent_(shaka.util.FakeEvent.EventName.Loading));
|
|
1110
1157
|
|
|
1111
1158
|
// Right away we know what the asset uri and start-of-load time are. We will
|
|
1112
1159
|
// fill-in the rest of the information later.
|
|
@@ -1170,7 +1217,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1170
1217
|
events.onEnd = () => {
|
|
1171
1218
|
resolve();
|
|
1172
1219
|
// We dispatch the loaded event when the load promise is resolved
|
|
1173
|
-
this.dispatchEvent(
|
|
1220
|
+
this.dispatchEvent(
|
|
1221
|
+
this.makeEvent_(shaka.util.FakeEvent.EventName.Loaded));
|
|
1174
1222
|
};
|
|
1175
1223
|
events.onCancel = () => reject(this.createAbortLoadError_());
|
|
1176
1224
|
events.onError = (e) => reject(e);
|
|
@@ -1298,7 +1346,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1298
1346
|
has.mediaElement = wants.mediaElement;
|
|
1299
1347
|
|
|
1300
1348
|
const onError = (error) => this.onVideoError_(error);
|
|
1301
|
-
this.
|
|
1349
|
+
this.attachEventManager_.listen(has.mediaElement, 'error', onError);
|
|
1302
1350
|
}
|
|
1303
1351
|
|
|
1304
1352
|
this.video_ = has.mediaElement;
|
|
@@ -1325,10 +1373,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1325
1373
|
* @private
|
|
1326
1374
|
*/
|
|
1327
1375
|
onDetach_(has, wants) {
|
|
1328
|
-
// If we
|
|
1376
|
+
// If we were going from "detached" to "detached" we wouldn't have
|
|
1329
1377
|
// a media element to detach from.
|
|
1330
1378
|
if (has.mediaElement) {
|
|
1331
|
-
this.
|
|
1379
|
+
this.attachEventManager_.removeAll();
|
|
1332
1380
|
has.mediaElement = null;
|
|
1333
1381
|
}
|
|
1334
1382
|
|
|
@@ -1375,7 +1423,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1375
1423
|
await Promise.all(cleanupTasks);
|
|
1376
1424
|
|
|
1377
1425
|
// Dispatch the unloading event.
|
|
1378
|
-
this.dispatchEvent(
|
|
1426
|
+
this.dispatchEvent(
|
|
1427
|
+
this.makeEvent_(shaka.util.FakeEvent.EventName.Unloading));
|
|
1379
1428
|
|
|
1380
1429
|
// Remove everything that has to do with loading content from our payload
|
|
1381
1430
|
// since we are releasing everything that depended on it.
|
|
@@ -1386,13 +1435,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1386
1435
|
// In most cases we should have a media element. The one exception would
|
|
1387
1436
|
// be if there was an error and we, by chance, did not have a media element.
|
|
1388
1437
|
if (has.mediaElement) {
|
|
1389
|
-
this.
|
|
1390
|
-
this.eventManager_.unlisten(has.mediaElement, 'playing');
|
|
1391
|
-
this.eventManager_.unlisten(has.mediaElement, 'pause');
|
|
1392
|
-
this.eventManager_.unlisten(has.mediaElement, 'ended');
|
|
1393
|
-
this.eventManager_.unlisten(has.mediaElement, 'ratechange');
|
|
1438
|
+
this.loadEventManager_.removeAll();
|
|
1394
1439
|
}
|
|
1395
1440
|
|
|
1441
|
+
// Stop the variant checker timer
|
|
1442
|
+
this.checkVariantsTimer_.stop();
|
|
1443
|
+
|
|
1396
1444
|
// Some observers use some playback components, shutting down the observers
|
|
1397
1445
|
// first ensures that they don't try to use the playback components
|
|
1398
1446
|
// mid-destroy.
|
|
@@ -1680,7 +1728,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1680
1728
|
this.regionTimeline_.addEventListener('regionadd', (event) => {
|
|
1681
1729
|
/** @type {shaka.extern.TimelineRegionInfo} */
|
|
1682
1730
|
const region = event['region'];
|
|
1683
|
-
this.onRegionEvent_(
|
|
1731
|
+
this.onRegionEvent_(
|
|
1732
|
+
shaka.util.FakeEvent.EventName.TimelineRegionAdded, region);
|
|
1684
1733
|
|
|
1685
1734
|
if (this.adManager_) {
|
|
1686
1735
|
this.adManager_.onDashTimedMetadata(region);
|
|
@@ -1735,7 +1784,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1735
1784
|
|
|
1736
1785
|
// This event is fired after the manifest is parsed, but before any
|
|
1737
1786
|
// filtering takes place.
|
|
1738
|
-
const event =
|
|
1787
|
+
const event =
|
|
1788
|
+
this.makeEvent_(shaka.util.FakeEvent.EventName.ManifestParsed);
|
|
1739
1789
|
this.dispatchEvent(event);
|
|
1740
1790
|
|
|
1741
1791
|
// We require all manifests to have at least one variant.
|
|
@@ -1809,7 +1859,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1809
1859
|
},
|
|
1810
1860
|
onEvent: (e) => {
|
|
1811
1861
|
this.dispatchEvent(e);
|
|
1812
|
-
if (e.type == shaka.
|
|
1862
|
+
if (e.type == shaka.util.FakeEvent.EventName.DrmSessionUpdate &&
|
|
1863
|
+
firstEvent) {
|
|
1813
1864
|
firstEvent = false;
|
|
1814
1865
|
const now = Date.now() / 1000;
|
|
1815
1866
|
const delta = now - startTime;
|
|
@@ -1885,18 +1936,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1885
1936
|
this.assetUri_ = assetUri;
|
|
1886
1937
|
|
|
1887
1938
|
this.playRateController_ = new shaka.media.PlayRateController({
|
|
1888
|
-
getRate: () =>
|
|
1889
|
-
getDefaultRate: () =>
|
|
1890
|
-
setRate: (rate) => {
|
|
1891
|
-
movePlayhead: (delta) => {
|
|
1939
|
+
getRate: () => mediaElement.playbackRate,
|
|
1940
|
+
getDefaultRate: () => mediaElement.defaultPlaybackRate,
|
|
1941
|
+
setRate: (rate) => { mediaElement.playbackRate = rate; },
|
|
1942
|
+
movePlayhead: (delta) => { mediaElement.currentTime += delta; },
|
|
1892
1943
|
});
|
|
1893
1944
|
|
|
1894
1945
|
const updateStateHistory = () => this.updateStateHistory_();
|
|
1895
1946
|
const onRateChange = () => this.onRateChange_();
|
|
1896
|
-
this.
|
|
1897
|
-
this.
|
|
1898
|
-
this.
|
|
1899
|
-
this.
|
|
1947
|
+
this.loadEventManager_.listen(mediaElement, 'playing', updateStateHistory);
|
|
1948
|
+
this.loadEventManager_.listen(mediaElement, 'pause', updateStateHistory);
|
|
1949
|
+
this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
|
|
1950
|
+
this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
|
|
1900
1951
|
|
|
1901
1952
|
const abrFactory = this.config_.abrFactory;
|
|
1902
1953
|
if (!this.abrManager_ || this.abrManagerFactory_ != abrFactory) {
|
|
@@ -1953,10 +2004,29 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1953
2004
|
// "streaming" so that they can access internal information.
|
|
1954
2005
|
this.loadMode_ = shaka.Player.LoadMode.MEDIA_SOURCE;
|
|
1955
2006
|
|
|
2007
|
+
if (mediaElement.textTracks) {
|
|
2008
|
+
this.loadEventManager_.listen(
|
|
2009
|
+
mediaElement.textTracks, 'addtrack', (e) => {
|
|
2010
|
+
const trackEvent = /** @type {!TrackEvent} */(e);
|
|
2011
|
+
if (trackEvent.track) {
|
|
2012
|
+
const track = trackEvent.track;
|
|
2013
|
+
goog.asserts.assert(
|
|
2014
|
+
track instanceof TextTrack, 'Wrong track type!');
|
|
2015
|
+
|
|
2016
|
+
switch (track.kind) {
|
|
2017
|
+
case 'chapters':
|
|
2018
|
+
this.activateChaptersTrack_(track);
|
|
2019
|
+
break;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
|
|
1956
2025
|
// The event must be fired after we filter by restrictions but before the
|
|
1957
2026
|
// active stream is picked to allow those listening for the "streaming"
|
|
1958
2027
|
// event to make changes before streaming starts.
|
|
1959
|
-
this.dispatchEvent(
|
|
2028
|
+
this.dispatchEvent(
|
|
2029
|
+
this.makeEvent_(shaka.util.FakeEvent.EventName.Streaming));
|
|
1960
2030
|
|
|
1961
2031
|
// Pick the initial streams to play.
|
|
1962
2032
|
// however, we would skip switch to initial variant
|
|
@@ -2039,7 +2109,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2039
2109
|
}
|
|
2040
2110
|
|
|
2041
2111
|
// Wait for the 'loadedmetadata' event to measure load() latency.
|
|
2042
|
-
this.
|
|
2112
|
+
this.loadEventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
|
|
2043
2113
|
const now = Date.now() / 1000;
|
|
2044
2114
|
const delta = now - wants.startTimeOfLoad;
|
|
2045
2115
|
this.stats_.setLoadLatency(delta);
|
|
@@ -2084,7 +2154,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2084
2154
|
},
|
|
2085
2155
|
onEvent: (e) => {
|
|
2086
2156
|
this.dispatchEvent(e);
|
|
2087
|
-
if (e.type == shaka.
|
|
2157
|
+
if (e.type == shaka.util.FakeEvent.EventName.DrmSessionUpdate &&
|
|
2158
|
+
firstEvent) {
|
|
2088
2159
|
firstEvent = false;
|
|
2089
2160
|
const now = Date.now() / 1000;
|
|
2090
2161
|
const delta = now - startTime;
|
|
@@ -2117,6 +2188,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2117
2188
|
const variant = {
|
|
2118
2189
|
id: 0,
|
|
2119
2190
|
language: 'und',
|
|
2191
|
+
disabledUntilTime: 0,
|
|
2120
2192
|
primary: false,
|
|
2121
2193
|
audio: null,
|
|
2122
2194
|
video: {
|
|
@@ -2192,11 +2264,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2192
2264
|
// Save the uri so that it can be used outside of the load-graph.
|
|
2193
2265
|
this.assetUri_ = has.uri;
|
|
2194
2266
|
|
|
2195
|
-
|
|
2267
|
+
const mediaElement = has.mediaElement;
|
|
2196
2268
|
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
//
|
|
2269
|
+
this.playhead_ = new shaka.media.SrcEqualsPlayhead(mediaElement);
|
|
2270
|
+
|
|
2271
|
+
// This flag is used below in the language preference setup to check if
|
|
2272
|
+
// this load was canceled before the necessary awaits completed.
|
|
2200
2273
|
let unloaded = false;
|
|
2201
2274
|
this.cleanupOnUnload_.push(() => {
|
|
2202
2275
|
unloaded = true;
|
|
@@ -2207,10 +2280,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2207
2280
|
}
|
|
2208
2281
|
|
|
2209
2282
|
this.playRateController_ = new shaka.media.PlayRateController({
|
|
2210
|
-
getRate: () =>
|
|
2211
|
-
getDefaultRate: () =>
|
|
2212
|
-
setRate: (rate) => {
|
|
2213
|
-
movePlayhead: (delta) => {
|
|
2283
|
+
getRate: () => mediaElement.playbackRate,
|
|
2284
|
+
getDefaultRate: () => mediaElement.defaultPlaybackRate,
|
|
2285
|
+
setRate: (rate) => { mediaElement.playbackRate = rate; },
|
|
2286
|
+
movePlayhead: (delta) => { mediaElement.currentTime += delta; },
|
|
2214
2287
|
});
|
|
2215
2288
|
|
|
2216
2289
|
// We need to start the buffer management code near the end because it will
|
|
@@ -2222,17 +2295,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2222
2295
|
// Add all media element listeners.
|
|
2223
2296
|
const updateStateHistory = () => this.updateStateHistory_();
|
|
2224
2297
|
const onRateChange = () => this.onRateChange_();
|
|
2225
|
-
this.
|
|
2226
|
-
this.
|
|
2227
|
-
this.
|
|
2228
|
-
this.
|
|
2298
|
+
this.loadEventManager_.listen(mediaElement, 'playing', updateStateHistory);
|
|
2299
|
+
this.loadEventManager_.listen(mediaElement, 'pause', updateStateHistory);
|
|
2300
|
+
this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
|
|
2301
|
+
this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
|
|
2229
2302
|
|
|
2230
2303
|
// Wait for the 'loadedmetadata' event to measure load() latency, but only
|
|
2231
2304
|
// if preload is set in a way that would result in this event firing
|
|
2232
2305
|
// automatically.
|
|
2233
2306
|
// See https://github.com/shaka-project/shaka-player/issues/2483
|
|
2234
|
-
if (
|
|
2235
|
-
this.
|
|
2307
|
+
if (mediaElement.preload != 'none') {
|
|
2308
|
+
this.loadEventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
|
|
2236
2309
|
const now = Date.now() / 1000;
|
|
2237
2310
|
const delta = now - wants.startTimeOfLoad;
|
|
2238
2311
|
this.stats_.setLoadLatency(delta);
|
|
@@ -2242,44 +2315,47 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2242
2315
|
// The audio tracks are only available on Safari at the moment, but this
|
|
2243
2316
|
// drives the tracks API for Safari's native HLS. So when they change,
|
|
2244
2317
|
// fire the corresponding Shaka Player event.
|
|
2245
|
-
if (
|
|
2246
|
-
this.
|
|
2247
|
-
|
|
2248
|
-
this.
|
|
2249
|
-
|
|
2318
|
+
if (mediaElement.audioTracks) {
|
|
2319
|
+
this.loadEventManager_.listen(
|
|
2320
|
+
mediaElement.audioTracks, 'addtrack', () => this.onTracksChanged_());
|
|
2321
|
+
this.loadEventManager_.listen(
|
|
2322
|
+
mediaElement.audioTracks, 'removetrack',
|
|
2250
2323
|
() => this.onTracksChanged_());
|
|
2251
|
-
this.
|
|
2252
|
-
|
|
2253
|
-
}
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2324
|
+
this.loadEventManager_.listen(
|
|
2325
|
+
mediaElement.audioTracks, 'change', () => this.onTracksChanged_());
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
if (mediaElement.textTracks) {
|
|
2329
|
+
this.loadEventManager_.listen(
|
|
2330
|
+
mediaElement.textTracks, 'addtrack', (e) => {
|
|
2331
|
+
const trackEvent = /** @type {!TrackEvent} */(e);
|
|
2332
|
+
if (trackEvent.track) {
|
|
2333
|
+
const track = trackEvent.track;
|
|
2334
|
+
goog.asserts.assert(
|
|
2335
|
+
track instanceof TextTrack, 'Wrong track type!');
|
|
2336
|
+
|
|
2337
|
+
switch (track.kind) {
|
|
2338
|
+
case 'metadata':
|
|
2339
|
+
this.processTimedMetadataSrcEqls_(track);
|
|
2340
|
+
break;
|
|
2341
|
+
|
|
2342
|
+
case 'chapters':
|
|
2343
|
+
this.activateChaptersTrack_(track);
|
|
2344
|
+
break;
|
|
2345
|
+
|
|
2346
|
+
default:
|
|
2347
|
+
this.onTracksChanged_();
|
|
2348
|
+
break;
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
});
|
|
2261
2352
|
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
this.processTimedMetadataSrcEqls_(track);
|
|
2269
|
-
break;
|
|
2270
|
-
case 'chapters':
|
|
2271
|
-
this.processChaptersTrack_(track);
|
|
2272
|
-
break;
|
|
2273
|
-
default:
|
|
2274
|
-
this.onTracksChanged_();
|
|
2275
|
-
break;
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
|
-
});
|
|
2279
|
-
this.eventManager_.listen(
|
|
2280
|
-
this.video_.textTracks, 'removetrack', () => this.onTracksChanged_());
|
|
2281
|
-
this.eventManager_.listen(
|
|
2282
|
-
this.video_.textTracks, 'change', () => this.onTracksChanged_());
|
|
2353
|
+
this.loadEventManager_.listen(
|
|
2354
|
+
mediaElement.textTracks, 'removetrack',
|
|
2355
|
+
() => this.onTracksChanged_());
|
|
2356
|
+
this.loadEventManager_.listen(
|
|
2357
|
+
mediaElement.textTracks, 'change',
|
|
2358
|
+
() => this.onTracksChanged_());
|
|
2283
2359
|
}
|
|
2284
2360
|
|
|
2285
2361
|
const extension = shaka.media.ManifestParser.getExtension(has.uri);
|
|
@@ -2288,14 +2364,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2288
2364
|
|
|
2289
2365
|
// By setting |src| we are done "loading" with src=. We don't need to set
|
|
2290
2366
|
// the current time because |playhead| will do that for us.
|
|
2291
|
-
|
|
2367
|
+
mediaElement.src = this.cmcdManager_.appendSrcData(has.uri, mimeType);
|
|
2292
2368
|
|
|
2293
2369
|
// Tizen 3 / WebOS won't load anything unless you call load() explicitly,
|
|
2294
2370
|
// no matter the value of the preload attribute. This is harmful on some
|
|
2295
2371
|
// other platforms by triggering unbounded loading of media data, but is
|
|
2296
2372
|
// necessary here.
|
|
2297
2373
|
if (shaka.util.Platform.isTizen() || shaka.util.Platform.isWebOS()) {
|
|
2298
|
-
|
|
2374
|
+
mediaElement.load();
|
|
2299
2375
|
}
|
|
2300
2376
|
|
|
2301
2377
|
// Set the load mode last so that we know that all our components are
|
|
@@ -2305,30 +2381,25 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2305
2381
|
// The event doesn't mean as much for src= playback, since we don't control
|
|
2306
2382
|
// streaming. But we should fire it in this path anyway since some
|
|
2307
2383
|
// applications may be expecting it as a life-cycle event.
|
|
2308
|
-
this.dispatchEvent(
|
|
2384
|
+
this.dispatchEvent(
|
|
2385
|
+
this.makeEvent_(shaka.util.FakeEvent.EventName.Streaming));
|
|
2309
2386
|
|
|
2310
2387
|
// The "load" Promise is resolved when we have loaded the metadata. If we
|
|
2311
2388
|
// wait for the full data, that won't happen on Safari until the play button
|
|
2312
2389
|
// is hit.
|
|
2313
2390
|
const fullyLoaded = new shaka.util.PublicPromise();
|
|
2314
|
-
shaka.util.MediaReadyState.waitForReadyState(
|
|
2391
|
+
shaka.util.MediaReadyState.waitForReadyState(mediaElement,
|
|
2315
2392
|
HTMLMediaElement.HAVE_METADATA,
|
|
2316
|
-
this.
|
|
2393
|
+
this.loadEventManager_,
|
|
2317
2394
|
() => {
|
|
2318
2395
|
fullyLoaded.resolve();
|
|
2319
2396
|
});
|
|
2320
2397
|
|
|
2321
2398
|
// We can't switch to preferred languages, though, until the data is loaded.
|
|
2322
|
-
shaka.util.MediaReadyState.waitForReadyState(
|
|
2399
|
+
shaka.util.MediaReadyState.waitForReadyState(mediaElement,
|
|
2323
2400
|
HTMLMediaElement.HAVE_CURRENT_DATA,
|
|
2324
|
-
this.
|
|
2401
|
+
this.loadEventManager_,
|
|
2325
2402
|
async () => {
|
|
2326
|
-
// If we have moved on to another piece of content while waiting for
|
|
2327
|
-
// the above event, we should not change tracks here.
|
|
2328
|
-
if (unloaded) {
|
|
2329
|
-
return;
|
|
2330
|
-
}
|
|
2331
|
-
|
|
2332
2403
|
this.setupPreferredAudioOnSrc_();
|
|
2333
2404
|
|
|
2334
2405
|
// Applying the text preference too soon can result in it being
|
|
@@ -2336,8 +2407,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2336
2407
|
const textTracks = this.getFilteredTextTracks_();
|
|
2337
2408
|
if (!textTracks.find((t) => t.mode != 'disabled')) {
|
|
2338
2409
|
await new Promise((resolve) => {
|
|
2339
|
-
this.
|
|
2340
|
-
|
|
2410
|
+
this.loadEventManager_.listenOnce(
|
|
2411
|
+
mediaElement.textTracks, 'change', resolve);
|
|
2412
|
+
|
|
2341
2413
|
// We expect the event to fire because it does on Safari.
|
|
2342
2414
|
// But in case it doesn't on some other platform or future
|
|
2343
2415
|
// version, move on in 1 second no matter what. This keeps the
|
|
@@ -2356,10 +2428,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2356
2428
|
this.setupPreferredTextOnSrc_();
|
|
2357
2429
|
});
|
|
2358
2430
|
|
|
2359
|
-
if (
|
|
2431
|
+
if (mediaElement.error) {
|
|
2360
2432
|
// Already failed!
|
|
2361
2433
|
fullyLoaded.reject(this.videoErrorToShakaError_());
|
|
2362
|
-
} else if (
|
|
2434
|
+
} else if (mediaElement.preload == 'none') {
|
|
2363
2435
|
shaka.log.alwaysWarn(
|
|
2364
2436
|
'With <video preload="none">, the browser will not load anything ' +
|
|
2365
2437
|
'until play() is called. We are unable to measure load latency in ' +
|
|
@@ -2370,7 +2442,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2370
2442
|
fullyLoaded.resolve();
|
|
2371
2443
|
}
|
|
2372
2444
|
|
|
2373
|
-
this.
|
|
2445
|
+
this.loadEventManager_.listenOnce(mediaElement, 'error', () => {
|
|
2374
2446
|
fullyLoaded.reject(this.videoErrorToShakaError_());
|
|
2375
2447
|
});
|
|
2376
2448
|
|
|
@@ -2454,7 +2526,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2454
2526
|
|
|
2455
2527
|
// Hidden mode is required for the cuechange event to launch correctly
|
|
2456
2528
|
track.mode = 'hidden';
|
|
2457
|
-
this.
|
|
2529
|
+
this.loadEventManager_.listen(track, 'cuechange', () => {
|
|
2458
2530
|
if (!track.activeCues) {
|
|
2459
2531
|
return;
|
|
2460
2532
|
}
|
|
@@ -2522,7 +2594,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2522
2594
|
dispatchMetadataEvent_(startTime, endTime, metadataType, payload) {
|
|
2523
2595
|
goog.asserts.assert(!endTime || startTime <= endTime,
|
|
2524
2596
|
'Metadata start time should be less or equal to the end time!');
|
|
2525
|
-
const eventName = shaka.
|
|
2597
|
+
const eventName = shaka.util.FakeEvent.EventName.Metadata;
|
|
2526
2598
|
const data = new Map()
|
|
2527
2599
|
.set('startTime', startTime)
|
|
2528
2600
|
.set('endTime', endTime)
|
|
@@ -2532,12 +2604,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2532
2604
|
}
|
|
2533
2605
|
|
|
2534
2606
|
/**
|
|
2535
|
-
*
|
|
2607
|
+
* Set the mode on a chapters track so that it loads.
|
|
2536
2608
|
*
|
|
2537
2609
|
* @param {?TextTrack} track
|
|
2538
2610
|
* @private
|
|
2539
2611
|
*/
|
|
2540
|
-
|
|
2612
|
+
activateChaptersTrack_(track) {
|
|
2541
2613
|
if (!track || track.kind != 'chapters') {
|
|
2542
2614
|
return;
|
|
2543
2615
|
}
|
|
@@ -2550,10 +2622,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2550
2622
|
// this process to be repeated several times to ensure that it has been put
|
|
2551
2623
|
// in the correct mode.
|
|
2552
2624
|
const timer = new shaka.util.Timer(() => {
|
|
2553
|
-
|
|
2554
|
-
for (const chaptersTrack of chaptersTracks) {
|
|
2555
|
-
chaptersTrack.mode = 'hidden';
|
|
2556
|
-
}
|
|
2625
|
+
track.mode = 'hidden';
|
|
2557
2626
|
}).tickNow().tickAfter(0.5);
|
|
2558
2627
|
|
|
2559
2628
|
this.cleanupOnUnload_.push(() => {
|
|
@@ -2622,7 +2691,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2622
2691
|
/** @type {shaka.net.NetworkingEngine.OnHeadersReceived} */
|
|
2623
2692
|
const onHeadersReceived_ = (headers, request, requestType) => {
|
|
2624
2693
|
// Release a 'downloadheadersreceived' event.
|
|
2625
|
-
const name = shaka.
|
|
2694
|
+
const name = shaka.util.FakeEvent.EventName.DownloadHeadersReceived;
|
|
2626
2695
|
const data = new Map()
|
|
2627
2696
|
.set('headers', headers)
|
|
2628
2697
|
.set('request', request)
|
|
@@ -2632,7 +2701,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2632
2701
|
/** @type {shaka.net.NetworkingEngine.OnDownloadFailed} */
|
|
2633
2702
|
const onDownloadFailed_ = (request, error, httpResponseCode, aborted) => {
|
|
2634
2703
|
// Release a 'downloadfailed' event.
|
|
2635
|
-
const name = shaka.
|
|
2704
|
+
const name = shaka.util.FakeEvent.EventName.DownloadFailed;
|
|
2636
2705
|
const data = new Map()
|
|
2637
2706
|
.set('request', request)
|
|
2638
2707
|
.set('error', error)
|
|
@@ -2660,7 +2729,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2660
2729
|
this.manifest_,
|
|
2661
2730
|
this.config_.streaming,
|
|
2662
2731
|
startTime,
|
|
2663
|
-
() => this.onSeek_()
|
|
2732
|
+
() => this.onSeek_(),
|
|
2733
|
+
(event) => this.dispatchEvent(event));
|
|
2664
2734
|
}
|
|
2665
2735
|
|
|
2666
2736
|
/**
|
|
@@ -2682,13 +2752,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2682
2752
|
regionObserver.addEventListener('enter', (event) => {
|
|
2683
2753
|
/** @type {shaka.extern.TimelineRegionInfo} */
|
|
2684
2754
|
const region = event['region'];
|
|
2685
|
-
this.onRegionEvent_(
|
|
2755
|
+
this.onRegionEvent_(
|
|
2756
|
+
shaka.util.FakeEvent.EventName.TimelineRegionEnter, region);
|
|
2686
2757
|
});
|
|
2687
2758
|
|
|
2688
2759
|
regionObserver.addEventListener('exit', (event) => {
|
|
2689
2760
|
/** @type {shaka.extern.TimelineRegionInfo} */
|
|
2690
2761
|
const region = event['region'];
|
|
2691
|
-
this.onRegionEvent_(
|
|
2762
|
+
this.onRegionEvent_(
|
|
2763
|
+
shaka.util.FakeEvent.EventName.TimelineRegionExit, region);
|
|
2692
2764
|
});
|
|
2693
2765
|
|
|
2694
2766
|
regionObserver.addEventListener('skip', (event) => {
|
|
@@ -2699,8 +2771,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2699
2771
|
// If we are seeking, we don't want to surface the enter/exit events since
|
|
2700
2772
|
// they didn't play through them.
|
|
2701
2773
|
if (!seeking) {
|
|
2702
|
-
this.onRegionEvent_(
|
|
2703
|
-
|
|
2774
|
+
this.onRegionEvent_(
|
|
2775
|
+
shaka.util.FakeEvent.EventName.TimelineRegionEnter, region);
|
|
2776
|
+
this.onRegionEvent_(
|
|
2777
|
+
shaka.util.FakeEvent.EventName.TimelineRegionExit, region);
|
|
2704
2778
|
}
|
|
2705
2779
|
});
|
|
2706
2780
|
|
|
@@ -4056,8 +4130,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4056
4130
|
for (const chaptersTrack of chaptersTracksWithLanguage) {
|
|
4057
4131
|
if (chaptersTrack && chaptersTrack.cues) {
|
|
4058
4132
|
for (const cue of chaptersTrack.cues) {
|
|
4133
|
+
let id = cue.id;
|
|
4134
|
+
if (!id || id == '') {
|
|
4135
|
+
id = cue.startTime + '-' + cue.endTime + '-' + cue.text;
|
|
4136
|
+
}
|
|
4059
4137
|
/** @type {shaka.extern.Chapter} */
|
|
4060
4138
|
const chapter = {
|
|
4139
|
+
id: id,
|
|
4061
4140
|
title: cue.text,
|
|
4062
4141
|
startTime: cue.startTime,
|
|
4063
4142
|
endTime: cue.endTime,
|
|
@@ -4322,6 +4401,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4322
4401
|
this.stats_.setCompletionPercent(Math.round(100 * completionRatio));
|
|
4323
4402
|
}
|
|
4324
4403
|
|
|
4404
|
+
if (this.playhead_) {
|
|
4405
|
+
this.stats_.setGapsJumped(this.playhead_.getGapsJumped());
|
|
4406
|
+
this.stats_.setStallsDetected(this.playhead_.getStallsDetected());
|
|
4407
|
+
}
|
|
4408
|
+
|
|
4325
4409
|
if (element.getVideoPlaybackQuality) {
|
|
4326
4410
|
const info = element.getVideoPlaybackQuality();
|
|
4327
4411
|
|
|
@@ -4537,25 +4621,41 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4537
4621
|
if (!mimeType) {
|
|
4538
4622
|
mimeType = await this.getTextMimetype_(uri);
|
|
4539
4623
|
}
|
|
4624
|
+
|
|
4540
4625
|
let adCuePoints = [];
|
|
4541
4626
|
if (this.adManager_) {
|
|
4542
4627
|
try {
|
|
4543
4628
|
adCuePoints = this.adManager_.getServerSideCuePoints();
|
|
4544
4629
|
} catch (error) {}
|
|
4545
4630
|
}
|
|
4546
|
-
|
|
4547
|
-
|
|
4631
|
+
|
|
4632
|
+
/** @type {!HTMLTrackElement} */
|
|
4633
|
+
const trackElement = await this.addSrcTrackElement_(
|
|
4634
|
+
uri, language, /* kind= */ 'chapters', mimeType, /* label= */ '',
|
|
4635
|
+
adCuePoints);
|
|
4636
|
+
|
|
4548
4637
|
const chaptersTracks = this.getChaptersTracks();
|
|
4549
4638
|
const chaptersTrack = chaptersTracks.find((t) => {
|
|
4550
4639
|
return t.language == language;
|
|
4551
4640
|
});
|
|
4641
|
+
|
|
4552
4642
|
if (chaptersTrack) {
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4643
|
+
await new Promise((resolve, reject) => {
|
|
4644
|
+
// The chapter data isn't available until the 'load' event fires, and
|
|
4645
|
+
// that won't happen until the chapters track is activated by the
|
|
4646
|
+
// activateChaptersTrack_ method.
|
|
4647
|
+
this.loadEventManager_.listenOnce(trackElement, 'load', resolve);
|
|
4648
|
+
this.loadEventManager_.listenOnce(trackElement, 'error', (event) => {
|
|
4649
|
+
reject(new shaka.util.Error(
|
|
4650
|
+
shaka.util.Error.Severity.RECOVERABLE,
|
|
4651
|
+
shaka.util.Error.Category.TEXT,
|
|
4652
|
+
shaka.util.Error.Code.CHAPTERS_TRACK_FAILED));
|
|
4653
|
+
});
|
|
4654
|
+
});
|
|
4655
|
+
|
|
4557
4656
|
return chaptersTrack;
|
|
4558
4657
|
}
|
|
4658
|
+
|
|
4559
4659
|
// This should not happen, but there are browser implementations that may
|
|
4560
4660
|
// not support the Track element.
|
|
4561
4661
|
shaka.log.error('Cannot add this text when loaded with src=');
|
|
@@ -4609,6 +4709,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4609
4709
|
* @param {string} mimeType
|
|
4610
4710
|
* @param {string} label
|
|
4611
4711
|
* @param {!Array.<!shaka.extern.AdCuePoint>} adCuePoints
|
|
4712
|
+
* @return {!Promise.<!HTMLTrackElement>}
|
|
4612
4713
|
* @private
|
|
4613
4714
|
*/
|
|
4614
4715
|
async addSrcTrackElement_(uri, language, kind, mimeType, label,
|
|
@@ -4624,12 +4725,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4624
4725
|
uri = shaka.media.MediaSourceEngine.createObjectURL(blob);
|
|
4625
4726
|
mimeType = 'text/vtt';
|
|
4626
4727
|
}
|
|
4728
|
+
|
|
4627
4729
|
const trackElement =
|
|
4628
4730
|
/** @type {!HTMLTrackElement} */(document.createElement('track'));
|
|
4629
4731
|
trackElement.src = this.cmcdManager_.appendTextTrackData(uri);
|
|
4630
4732
|
trackElement.label = label;
|
|
4631
4733
|
trackElement.kind = kind;
|
|
4632
4734
|
trackElement.srclang = language;
|
|
4735
|
+
|
|
4633
4736
|
// Because we're pulling in the text track file via Javascript, the
|
|
4634
4737
|
// same-origin policy applies. If you'd like to have a player served
|
|
4635
4738
|
// from one domain, but the text track served from another, you'll
|
|
@@ -4639,7 +4742,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4639
4742
|
if (!this.video_.getAttribute('crossorigin')) {
|
|
4640
4743
|
this.video_.setAttribute('crossorigin', 'anonymous');
|
|
4641
4744
|
}
|
|
4745
|
+
|
|
4642
4746
|
this.video_.appendChild(trackElement);
|
|
4747
|
+
return trackElement;
|
|
4643
4748
|
}
|
|
4644
4749
|
|
|
4645
4750
|
/**
|
|
@@ -5010,7 +5115,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5010
5115
|
}
|
|
5011
5116
|
|
|
5012
5117
|
await stream.createSegmentIndex();
|
|
5013
|
-
const
|
|
5118
|
+
const iter = stream.segmentIndex.getIteratorForTime(time);
|
|
5119
|
+
const ref = iter ? iter.next().value : null;
|
|
5014
5120
|
if (!ref) {
|
|
5015
5121
|
return null;
|
|
5016
5122
|
}
|
|
@@ -5063,7 +5169,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5063
5169
|
|
|
5064
5170
|
// Surface the buffering event so that the app knows if/when we are
|
|
5065
5171
|
// buffering.
|
|
5066
|
-
const eventName = shaka.
|
|
5172
|
+
const eventName = shaka.util.FakeEvent.EventName.Buffering;
|
|
5067
5173
|
const data = (new Map()).set('buffering', isBuffering);
|
|
5068
5174
|
this.dispatchEvent(this.makeEvent_(eventName, data));
|
|
5069
5175
|
}
|
|
@@ -5098,7 +5204,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5098
5204
|
this.playRateController_.set(newRate);
|
|
5099
5205
|
}
|
|
5100
5206
|
|
|
5101
|
-
const event = this.makeEvent_(shaka.
|
|
5207
|
+
const event = this.makeEvent_(shaka.util.FakeEvent.EventName.RateChange);
|
|
5102
5208
|
this.dispatchEvent(event);
|
|
5103
5209
|
}
|
|
5104
5210
|
|
|
@@ -5198,6 +5304,21 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5198
5304
|
}
|
|
5199
5305
|
}
|
|
5200
5306
|
|
|
5307
|
+
/**
|
|
5308
|
+
* Re-apply restrictions to the variants, to re-enable variants that were
|
|
5309
|
+
* temporarily disabled due to network errors.
|
|
5310
|
+
* If any variants are enabled this way, a new variant might be chosen for
|
|
5311
|
+
* playback.
|
|
5312
|
+
* @private
|
|
5313
|
+
*/
|
|
5314
|
+
checkVariants_() {
|
|
5315
|
+
const tracksChanged = shaka.util.StreamUtils.applyRestrictions(
|
|
5316
|
+
this.manifest_.variants, this.config_.restrictions, this.maxHwRes_);
|
|
5317
|
+
if (tracksChanged) {
|
|
5318
|
+
this.chooseVariant_();
|
|
5319
|
+
}
|
|
5320
|
+
}
|
|
5321
|
+
|
|
5201
5322
|
/**
|
|
5202
5323
|
* Choose a text stream from all possible text streams while taking into
|
|
5203
5324
|
* account user preference.
|
|
@@ -5430,7 +5551,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5430
5551
|
const data = new Map()
|
|
5431
5552
|
.set('oldTrack', from)
|
|
5432
5553
|
.set('newTrack', to);
|
|
5433
|
-
const event =
|
|
5554
|
+
const event =
|
|
5555
|
+
this.makeEvent_(shaka.util.FakeEvent.EventName.Adaptation, data);
|
|
5434
5556
|
this.delayDispatchEvent_(event);
|
|
5435
5557
|
}
|
|
5436
5558
|
|
|
@@ -5441,7 +5563,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5441
5563
|
onTracksChanged_() {
|
|
5442
5564
|
// Delay the 'trackschanged' event so StreamingEngine has time to absorb the
|
|
5443
5565
|
// changes before the user tries to query it.
|
|
5444
|
-
const event = this.makeEvent_(shaka.
|
|
5566
|
+
const event = this.makeEvent_(shaka.util.FakeEvent.EventName.TracksChanged);
|
|
5445
5567
|
this.delayDispatchEvent_(event);
|
|
5446
5568
|
}
|
|
5447
5569
|
|
|
@@ -5457,7 +5579,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5457
5579
|
const data = new Map()
|
|
5458
5580
|
.set('oldTrack', from)
|
|
5459
5581
|
.set('newTrack', to);
|
|
5460
|
-
const event =
|
|
5582
|
+
const event =
|
|
5583
|
+
this.makeEvent_(shaka.util.FakeEvent.EventName.VariantChanged, data);
|
|
5461
5584
|
this.delayDispatchEvent_(event);
|
|
5462
5585
|
}
|
|
5463
5586
|
|
|
@@ -5468,13 +5591,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5468
5591
|
onTextChanged_() {
|
|
5469
5592
|
// Delay the 'textchanged' event so StreamingEngine time to absorb the
|
|
5470
5593
|
// changes before the user tries to query it.
|
|
5471
|
-
const event = this.makeEvent_(shaka.
|
|
5594
|
+
const event = this.makeEvent_(shaka.util.FakeEvent.EventName.TextChanged);
|
|
5472
5595
|
this.delayDispatchEvent_(event);
|
|
5473
5596
|
}
|
|
5474
5597
|
|
|
5475
5598
|
/** @private */
|
|
5476
5599
|
onTextTrackVisibility_() {
|
|
5477
|
-
const event =
|
|
5600
|
+
const event =
|
|
5601
|
+
this.makeEvent_(shaka.util.FakeEvent.EventName.TextTrackVisibility);
|
|
5478
5602
|
this.delayDispatchEvent_(event);
|
|
5479
5603
|
}
|
|
5480
5604
|
|
|
@@ -5482,7 +5606,73 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5482
5606
|
onAbrStatusChanged_() {
|
|
5483
5607
|
const data = (new Map()).set('newStatus', this.config_.abr.enabled);
|
|
5484
5608
|
this.delayDispatchEvent_(this.makeEvent_(
|
|
5485
|
-
shaka.
|
|
5609
|
+
shaka.util.FakeEvent.EventName.AbrStatusChanged, data));
|
|
5610
|
+
}
|
|
5611
|
+
|
|
5612
|
+
/**
|
|
5613
|
+
* Tries to recover from NETWORK HTTP_ERROR, temporary disabling the current
|
|
5614
|
+
* problematic variant.
|
|
5615
|
+
* @param {!shaka.util.Error} error
|
|
5616
|
+
* @return {boolean}
|
|
5617
|
+
* @private
|
|
5618
|
+
*/
|
|
5619
|
+
tryToRecoverFromError_(error) {
|
|
5620
|
+
if ((error.code != shaka.util.Error.Code.HTTP_ERROR &&
|
|
5621
|
+
error.code != shaka.util.Error.Code.SEGMENT_MISSING) ||
|
|
5622
|
+
error.category != shaka.util.Error.Category.NETWORK) {
|
|
5623
|
+
return false;
|
|
5624
|
+
}
|
|
5625
|
+
|
|
5626
|
+
let maxDisabledTime = this.config_.streaming.maxDisabledTime;
|
|
5627
|
+
if (maxDisabledTime == 0) {
|
|
5628
|
+
if (error.code == shaka.util.Error.Code.SEGMENT_MISSING) {
|
|
5629
|
+
// Spec: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-6.3.3
|
|
5630
|
+
// The client SHOULD NOT attempt to load Media Segments that have been
|
|
5631
|
+
// marked with an EXT-X-GAP tag, or to load Partial Segments with a
|
|
5632
|
+
// GAP=YES attribute. Instead, clients are encouraged to look for
|
|
5633
|
+
// another Variant Stream of the same Rendition which does not have the
|
|
5634
|
+
// same gap, and play that instead.
|
|
5635
|
+
maxDisabledTime = 1;
|
|
5636
|
+
} else {
|
|
5637
|
+
return false;
|
|
5638
|
+
}
|
|
5639
|
+
}
|
|
5640
|
+
|
|
5641
|
+
if (error.code == shaka.util.Error.Code.HTTP_ERROR) {
|
|
5642
|
+
shaka.log.debug('Recoverable NETWORK HTTP_ERROR, trying to recover...');
|
|
5643
|
+
}
|
|
5644
|
+
|
|
5645
|
+
// Obtain the active variant and disable it from manifest variants
|
|
5646
|
+
const activeVariantTrack = this.getVariantTracks().find((t) => t.active);
|
|
5647
|
+
goog.asserts.assert(activeVariantTrack, 'Active variant should be found');
|
|
5648
|
+
const manifest = this.manifest_;
|
|
5649
|
+
for (const variant of manifest.variants) {
|
|
5650
|
+
if (variant.id === activeVariantTrack.id) {
|
|
5651
|
+
variant.disabledUntilTime = (Date.now() / 1000) + maxDisabledTime;
|
|
5652
|
+
}
|
|
5653
|
+
}
|
|
5654
|
+
|
|
5655
|
+
// Apply restrictions in order to disable variants
|
|
5656
|
+
shaka.util.StreamUtils.applyRestrictions(
|
|
5657
|
+
manifest.variants, this.config_.restrictions, this.maxHwRes_);
|
|
5658
|
+
|
|
5659
|
+
// Select for a new variant
|
|
5660
|
+
const chosenVariant = this.chooseVariant_();
|
|
5661
|
+
if (!chosenVariant) {
|
|
5662
|
+
shaka.log.warning('Not enough variants to recover from error');
|
|
5663
|
+
return false;
|
|
5664
|
+
}
|
|
5665
|
+
|
|
5666
|
+
// Get the safeMargin to ensure a seamless playback
|
|
5667
|
+
const {video} = this.getBufferedInfo();
|
|
5668
|
+
const safeMargin =
|
|
5669
|
+
video.reduce((size, {start, end}) => size + end - start, 0);
|
|
5670
|
+
|
|
5671
|
+
this.switchVariant_(chosenVariant, /* fromAdaptation= */ false,
|
|
5672
|
+
/* clearBuffers= */ true, /* safeMargin= */ safeMargin);
|
|
5673
|
+
|
|
5674
|
+
this.checkVariantsTimer_.tickAfter(maxDisabledTime);
|
|
5675
|
+
return true;
|
|
5486
5676
|
}
|
|
5487
5677
|
|
|
5488
5678
|
/**
|
|
@@ -5498,7 +5688,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5498
5688
|
return;
|
|
5499
5689
|
}
|
|
5500
5690
|
|
|
5501
|
-
|
|
5691
|
+
|
|
5692
|
+
if (this.tryToRecoverFromError_(error)) {
|
|
5693
|
+
error.handled = true;
|
|
5694
|
+
return;
|
|
5695
|
+
}
|
|
5696
|
+
|
|
5697
|
+
const eventName = shaka.util.FakeEvent.EventName.Error;
|
|
5502
5698
|
const event = this.makeEvent_(eventName, (new Map()).set('detail', error));
|
|
5503
5699
|
this.dispatchEvent(event);
|
|
5504
5700
|
if (event.defaultPrevented) {
|
|
@@ -5512,7 +5708,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5512
5708
|
* copy here because this is the transition point between the player and the
|
|
5513
5709
|
* app.
|
|
5514
5710
|
*
|
|
5515
|
-
* @param {!shaka.
|
|
5711
|
+
* @param {!shaka.util.FakeEvent.EventName} eventName
|
|
5516
5712
|
* @param {shaka.extern.TimelineRegionInfo} region
|
|
5517
5713
|
*
|
|
5518
5714
|
* @private
|
|
@@ -5561,7 +5757,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5561
5757
|
.set('position', position);
|
|
5562
5758
|
|
|
5563
5759
|
this.dispatchEvent(this.makeEvent_(
|
|
5564
|
-
shaka.
|
|
5760
|
+
shaka.util.FakeEvent.EventName.MediaQualityChanged, data));
|
|
5565
5761
|
}
|
|
5566
5762
|
|
|
5567
5763
|
/**
|
|
@@ -5679,7 +5875,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5679
5875
|
} // if (keyIds.size)
|
|
5680
5876
|
|
|
5681
5877
|
if (tracksChanged) {
|
|
5682
|
-
this.updateAbrManagerVariants_();
|
|
5878
|
+
const variantsUpdated = this.updateAbrManagerVariants_();
|
|
5879
|
+
if (!variantsUpdated) {
|
|
5880
|
+
return;
|
|
5881
|
+
}
|
|
5683
5882
|
}
|
|
5684
5883
|
|
|
5685
5884
|
const currentVariant = this.streamingEngine_.getCurrentVariant();
|
|
@@ -5704,7 +5903,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5704
5903
|
this.parser_.onExpirationUpdated(keyId, expiration);
|
|
5705
5904
|
}
|
|
5706
5905
|
|
|
5707
|
-
const event =
|
|
5906
|
+
const event =
|
|
5907
|
+
this.makeEvent_(shaka.util.FakeEvent.EventName.ExpirationUpdated);
|
|
5708
5908
|
this.dispatchEvent(event);
|
|
5709
5909
|
}
|
|
5710
5910
|
|
|
@@ -6317,42 +6517,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6317
6517
|
}
|
|
6318
6518
|
};
|
|
6319
6519
|
|
|
6320
|
-
/**
|
|
6321
|
-
* An internal enum that contains the string values of all of the player events.
|
|
6322
|
-
* This exists primarily to act as an implicit list of events, for tests.
|
|
6323
|
-
*
|
|
6324
|
-
* @enum {string}
|
|
6325
|
-
*/
|
|
6326
|
-
shaka.Player.EventName = {
|
|
6327
|
-
AbrStatusChanged: 'abrstatuschanged',
|
|
6328
|
-
Adaptation: 'adaptation',
|
|
6329
|
-
Buffering: 'buffering',
|
|
6330
|
-
DownloadFailed: 'downloadfailed',
|
|
6331
|
-
DownloadHeadersReceived: 'downloadheadersreceived',
|
|
6332
|
-
DrmSessionUpdate: 'drmsessionupdate',
|
|
6333
|
-
Emsg: 'emsg',
|
|
6334
|
-
Error: 'error',
|
|
6335
|
-
ExpirationUpdated: 'expirationupdated',
|
|
6336
|
-
Loaded: 'loaded',
|
|
6337
|
-
Loading: 'loading',
|
|
6338
|
-
ManifestParsed: 'manifestparsed',
|
|
6339
|
-
MediaQualityChanged: 'mediaqualitychanged',
|
|
6340
|
-
Metadata: 'metadata',
|
|
6341
|
-
OnStateChange: 'onstatechange',
|
|
6342
|
-
OnStateIdle: 'onstateidle',
|
|
6343
|
-
RateChange: 'ratechange',
|
|
6344
|
-
SessionDataEvent: 'sessiondata',
|
|
6345
|
-
Streaming: 'streaming',
|
|
6346
|
-
TextChanged: 'textchanged',
|
|
6347
|
-
TextTrackVisibility: 'texttrackvisibility',
|
|
6348
|
-
TimelineRegionAdded: 'timelineregionadded',
|
|
6349
|
-
TimelineRegionEnter: 'timelineregionenter',
|
|
6350
|
-
TimelineRegionExit: 'timelineregionexit',
|
|
6351
|
-
TracksChanged: 'trackschanged',
|
|
6352
|
-
Unloading: 'unloading',
|
|
6353
|
-
VariantChanged: 'variantchanged',
|
|
6354
|
-
};
|
|
6355
|
-
|
|
6356
6520
|
|
|
6357
6521
|
/**
|
|
6358
6522
|
* In order to know what method of loading the player used for some content, we
|
|
@@ -6392,7 +6556,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6392
6556
|
* @define {string} A version number taken from git at compile time.
|
|
6393
6557
|
* @export
|
|
6394
6558
|
*/
|
|
6395
|
-
shaka.Player.version = 'v4.
|
|
6559
|
+
shaka.Player.version = 'v4.1.0-uncompiled';
|
|
6396
6560
|
|
|
6397
6561
|
// Initialize the deprecation system using the version string we just set
|
|
6398
6562
|
// on the player.
|