shaka-player 3.2.6 → 3.2.7
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/workflows/build-and-test.yaml +0 -1
- package/.github/workflows/release-please.yaml +1 -1
- package/.github/workflows/selenium-lab-tests.yaml +0 -1
- package/CHANGELOG.md +16 -0
- package/build/README.md +2 -2
- package/build/checkversion.py +1 -1
- package/build/generateTsDefs.py +0 -2
- package/build/test.py +7 -1
- package/dist/controls.css +1 -1
- package/dist/locales.js +31 -31
- package/dist/shaka-player.compiled.d.ts +225 -226
- package/dist/shaka-player.compiled.debug.d.ts +225 -226
- package/dist/shaka-player.compiled.debug.externs.js +1 -0
- package/dist/shaka-player.compiled.debug.js +217 -212
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +1 -0
- package/dist/shaka-player.compiled.js +771 -767
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +267 -268
- package/dist/shaka-player.ui.debug.d.ts +267 -268
- package/dist/shaka-player.ui.debug.externs.js +1 -0
- package/dist/shaka-player.ui.debug.js +138 -133
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +1 -0
- package/dist/shaka-player.ui.js +893 -888
- package/dist/shaka-player.ui.map +3 -3
- package/karma.conf.js +4 -0
- package/lib/dash/dash_parser.js +2 -3
- package/lib/hls/hls_parser.js +23 -11
- package/lib/media/media_source_engine.js +7 -1
- package/lib/offline/indexeddb/storage_mechanism.js +4 -2
- package/lib/player.js +165 -107
- package/lib/util/config_utils.js +25 -0
- package/lib/util/error.js +7 -0
- package/lib/util/manifest_parser_utils.js +1 -1
- package/lib/util/mime_utils.js +48 -1
- package/lib/util/periods.js +5 -4
- package/lib/util/player_configuration.js +9 -14
- package/lib/util/stream_utils.js +4 -2
- package/package.json +1 -1
- package/test/cast/cast_utils_unit.js +1 -0
- package/test/dash/dash_parser_manifest_unit.js +0 -24
- package/test/hls/hls_live_unit.js +26 -0
- package/test/hls/manifest_text_parser_unit.js +19 -0
- package/test/media/media_source_engine_integration.js +1 -0
- package/test/media/media_source_engine_unit.js +4 -4
- package/test/media/streaming_engine_integration.js +2 -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 +6 -10
- package/test/player_src_equals_integration.js +81 -83
- package/test/player_unit.js +98 -1
- package/test/test/boot.js +9 -0
- package/test/test/externs/filters.js +4 -0
- package/test/test/util/simple_fakes.js +31 -2
- package/test/util/mime_utils_unit.js +54 -0
- package/test/util/periods_unit.js +79 -0
- package/docs/tutorials/gsoc-ideas-list.md +0 -3
package/karma.conf.js
CHANGED
|
@@ -363,6 +363,8 @@ module.exports = (config) => {
|
|
|
363
363
|
// If testing custom assets, we don't serve other unit or integration tests.
|
|
364
364
|
// External asset tests are the basis for custom asset testing, so this file
|
|
365
365
|
// is automatically included.
|
|
366
|
+
clientArgs.testFiles.push('demo/common/asset.js');
|
|
367
|
+
clientArgs.testFiles.push('demo/common/assets.js');
|
|
366
368
|
clientArgs.testFiles.push('test/player_external.js');
|
|
367
369
|
} else {
|
|
368
370
|
// In a normal test run, we serve unit tests.
|
|
@@ -374,6 +376,8 @@ module.exports = (config) => {
|
|
|
374
376
|
}
|
|
375
377
|
if (settings.external) {
|
|
376
378
|
// If --external is present, we serve external asset tests.
|
|
379
|
+
clientArgs.testFiles.push('demo/common/asset.js');
|
|
380
|
+
clientArgs.testFiles.push('demo/common/assets.js');
|
|
377
381
|
clientArgs.testFiles.push('test/**/*_external.js');
|
|
378
382
|
}
|
|
379
383
|
}
|
package/lib/dash/dash_parser.js
CHANGED
|
@@ -755,8 +755,8 @@ shaka.dash.DashParser = class {
|
|
|
755
755
|
// currently support that. Just choose one.
|
|
756
756
|
// TODO: https://github.com/shaka-project/shaka-player/issues/1528
|
|
757
757
|
stream.trickModeVideo = trickModeSet.streams.find((trickStream) =>
|
|
758
|
-
shaka.util.MimeUtils.
|
|
759
|
-
shaka.util.MimeUtils.
|
|
758
|
+
shaka.util.MimeUtils.getNormalizedCodec(stream.codecs) ==
|
|
759
|
+
shaka.util.MimeUtils.getNormalizedCodec(trickStream.codecs));
|
|
760
760
|
}
|
|
761
761
|
}
|
|
762
762
|
}
|
|
@@ -1218,7 +1218,6 @@ shaka.dash.DashParser = class {
|
|
|
1218
1218
|
});
|
|
1219
1219
|
let spatialAudio = false;
|
|
1220
1220
|
if (hasJoc) {
|
|
1221
|
-
context.representation.mimeType = 'audio/eac3-joc';
|
|
1222
1221
|
spatialAudio = true;
|
|
1223
1222
|
}
|
|
1224
1223
|
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -1736,8 +1736,8 @@ shaka.hls.HlsParser = class {
|
|
|
1736
1736
|
* @param {number} timestampOffset
|
|
1737
1737
|
* @param {!Map.<string, string>} variables
|
|
1738
1738
|
* @param {string} absoluteMediaPlaylistUri
|
|
1739
|
-
* @return {!shaka.media.SegmentReference}
|
|
1740
1739
|
* @param {string} type
|
|
1740
|
+
* @return {shaka.media.SegmentReference}
|
|
1741
1741
|
* @private
|
|
1742
1742
|
*/
|
|
1743
1743
|
createSegmentReference_(
|
|
@@ -1753,9 +1753,23 @@ shaka.hls.HlsParser = class {
|
|
|
1753
1753
|
let startByte = 0;
|
|
1754
1754
|
let endByte = null;
|
|
1755
1755
|
|
|
1756
|
+
if (!extinfTag) {
|
|
1757
|
+
if (hlsSegment.partialSegments.length == 0) {
|
|
1758
|
+
// EXTINF tag must be available if the segment has no partial segments.
|
|
1759
|
+
throw new shaka.util.Error(
|
|
1760
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1761
|
+
shaka.util.Error.Category.MANIFEST,
|
|
1762
|
+
shaka.util.Error.Code.HLS_REQUIRED_TAG_MISSING, 'EXTINF');
|
|
1763
|
+
} else if (!this.lowLatencyMode_) {
|
|
1764
|
+
// Without EXTINF and without low-latency mode, partial segments get
|
|
1765
|
+
// ignored.
|
|
1766
|
+
return null;
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1756
1770
|
// Create SegmentReferences for the partial segments.
|
|
1757
1771
|
const partialSegmentRefs = [];
|
|
1758
|
-
if (this.lowLatencyMode_
|
|
1772
|
+
if (this.lowLatencyMode_) {
|
|
1759
1773
|
for (let i = 0; i < hlsSegment.partialSegments.length; i++) {
|
|
1760
1774
|
const item = hlsSegment.partialSegments[i];
|
|
1761
1775
|
const pPreviousReference = i == 0 ?
|
|
@@ -1796,14 +1810,6 @@ shaka.hls.HlsParser = class {
|
|
|
1796
1810
|
/* appendWindowEnd= */ Infinity);
|
|
1797
1811
|
partialSegmentRefs.push(partial);
|
|
1798
1812
|
} // for-loop of hlsSegment.partialSegments
|
|
1799
|
-
} else {
|
|
1800
|
-
// EXTINF tag must be available if the segment has no partial segments.
|
|
1801
|
-
if (!extinfTag) {
|
|
1802
|
-
throw new shaka.util.Error(
|
|
1803
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
1804
|
-
shaka.util.Error.Category.MANIFEST,
|
|
1805
|
-
shaka.util.Error.Code.HLS_REQUIRED_TAG_MISSING, 'EXTINF');
|
|
1806
|
-
}
|
|
1807
1813
|
}
|
|
1808
1814
|
|
|
1809
1815
|
// If the segment has EXTINF tag, set the segment's end time, start byte
|
|
@@ -1822,6 +1828,7 @@ shaka.hls.HlsParser = class {
|
|
|
1822
1828
|
} else {
|
|
1823
1829
|
endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime;
|
|
1824
1830
|
}
|
|
1831
|
+
|
|
1825
1832
|
// If the segment has EXT-X-BYTERANGE tag, set the start byte and end byte
|
|
1826
1833
|
// base on the byterange information. If segment has no EXT-X-BYTERANGE tag
|
|
1827
1834
|
// and has partial segments, set the start byte and end byte base on the
|
|
@@ -2047,7 +2054,9 @@ shaka.hls.HlsParser = class {
|
|
|
2047
2054
|
playlist.absoluteUri,
|
|
2048
2055
|
type);
|
|
2049
2056
|
|
|
2050
|
-
|
|
2057
|
+
if (reference) {
|
|
2058
|
+
references.push(reference);
|
|
2059
|
+
}
|
|
2051
2060
|
} else if (!this.lowLatencyMode_) {
|
|
2052
2061
|
// If a segment has no extinfTag, it must contain partial segments.
|
|
2053
2062
|
shaka.log.alwaysWarn('Low-latency HLS live stream detected, but ' +
|
|
@@ -2191,6 +2200,9 @@ shaka.hls.HlsParser = class {
|
|
|
2191
2200
|
variables,
|
|
2192
2201
|
/* absoluteMediaPlaylistUri= */ '',
|
|
2193
2202
|
type);
|
|
2203
|
+
goog.asserts.assert(
|
|
2204
|
+
segmentRef != null, 'Segment reference should not be null!');
|
|
2205
|
+
|
|
2194
2206
|
// If we are updating the manifest, we can usually skip fetching the segment
|
|
2195
2207
|
// by examining the references we already have. This won't be possible if
|
|
2196
2208
|
// there was some kind of lag or delay updating the manifest on the server,
|
|
@@ -63,6 +63,11 @@ shaka.media.MediaSourceEngine = class {
|
|
|
63
63
|
string>} */
|
|
64
64
|
this.sourceBufferTypes_ = {};
|
|
65
65
|
|
|
66
|
+
|
|
67
|
+
/** @private {!Object.<shaka.util.ManifestParserUtils.ContentType,
|
|
68
|
+
boolean>} */
|
|
69
|
+
this.expectedEncryption_ = {};
|
|
70
|
+
|
|
66
71
|
/** @private {shaka.text.TextEngine} */
|
|
67
72
|
this.textEngine_ = null;
|
|
68
73
|
|
|
@@ -345,6 +350,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
345
350
|
this.sourceBuffers_[contentType] = sourceBuffer;
|
|
346
351
|
this.sourceBufferTypes_[contentType] = mimeType;
|
|
347
352
|
this.queues_[contentType] = [];
|
|
353
|
+
this.expectedEncryption_[contentType] = !!stream.drmInfos.length;
|
|
348
354
|
}
|
|
349
355
|
}
|
|
350
356
|
}
|
|
@@ -1091,7 +1097,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1091
1097
|
*/
|
|
1092
1098
|
workAroundBrokenPlatforms_(segment, startTime, contentType) {
|
|
1093
1099
|
const isInitSegment = startTime == null;
|
|
1094
|
-
const encryptionExpected = this.
|
|
1100
|
+
const encryptionExpected = this.expectedEncryption_[contentType];
|
|
1095
1101
|
|
|
1096
1102
|
// If:
|
|
1097
1103
|
// 1. this is an init segment,
|
|
@@ -368,8 +368,10 @@ shaka.offline.indexeddb.StorageMechanism.SESSION_ID_STORE = 'session-ids';
|
|
|
368
368
|
shaka.offline.StorageMuxer.register(
|
|
369
369
|
'idb',
|
|
370
370
|
() => {
|
|
371
|
-
// Offline storage is not supported on the Chromecast
|
|
372
|
-
|
|
371
|
+
// Offline storage is not supported on the Chromecast or Xbox One
|
|
372
|
+
// platforms.
|
|
373
|
+
if (shaka.util.Platform.isChromecast() ||
|
|
374
|
+
shaka.util.Platform.isXboxOne()) {
|
|
373
375
|
return null;
|
|
374
376
|
}
|
|
375
377
|
// Offline storage requires the IndexedDB API.
|
package/lib/player.js
CHANGED
|
@@ -397,8 +397,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
397
397
|
*/
|
|
398
398
|
this.isTextVisible_ = false;
|
|
399
399
|
|
|
400
|
-
/**
|
|
401
|
-
|
|
400
|
+
/**
|
|
401
|
+
* For listeners scoped to the lifetime of the Player instance.
|
|
402
|
+
* @private {shaka.util.EventManager}
|
|
403
|
+
*/
|
|
404
|
+
this.globalEventManager_ = new shaka.util.EventManager();
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* For listeners scoped to the lifetime of the media element attachment.
|
|
408
|
+
* @private {shaka.util.EventManager}
|
|
409
|
+
*/
|
|
410
|
+
this.attachEventManager_ = new shaka.util.EventManager();
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* For listeners scoped to the lifetime of the loaded content.
|
|
414
|
+
* @private {shaka.util.EventManager}
|
|
415
|
+
*/
|
|
416
|
+
this.loadEventManager_ = new shaka.util.EventManager();
|
|
402
417
|
|
|
403
418
|
/** @private {shaka.net.NetworkingEngine} */
|
|
404
419
|
this.networkingEngine_ = null;
|
|
@@ -524,7 +539,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
524
539
|
|
|
525
540
|
// If the browser comes back online after being offline, then try to play
|
|
526
541
|
// again.
|
|
527
|
-
this.
|
|
542
|
+
this.globalEventManager_.listen(window, 'online', () => {
|
|
528
543
|
this.retryStreaming();
|
|
529
544
|
});
|
|
530
545
|
|
|
@@ -712,10 +727,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
712
727
|
});
|
|
713
728
|
await this.walker_.destroy();
|
|
714
729
|
|
|
715
|
-
// Tear-down the event
|
|
716
|
-
if (this.
|
|
717
|
-
this.
|
|
718
|
-
this.
|
|
730
|
+
// Tear-down the event managers to ensure handlers stop firing.
|
|
731
|
+
if (this.globalEventManager_) {
|
|
732
|
+
this.globalEventManager_.release();
|
|
733
|
+
this.globalEventManager_ = null;
|
|
734
|
+
}
|
|
735
|
+
if (this.attachEventManager_) {
|
|
736
|
+
this.attachEventManager_.release();
|
|
737
|
+
this.attachEventManager_ = null;
|
|
738
|
+
}
|
|
739
|
+
if (this.loadEventManager_) {
|
|
740
|
+
this.loadEventManager_.release();
|
|
741
|
+
this.loadEventManager_ = null;
|
|
719
742
|
}
|
|
720
743
|
|
|
721
744
|
this.abrManagerFactory_ = null;
|
|
@@ -1239,7 +1262,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1239
1262
|
has.mediaElement = wants.mediaElement;
|
|
1240
1263
|
|
|
1241
1264
|
const onError = (error) => this.onVideoError_(error);
|
|
1242
|
-
this.
|
|
1265
|
+
this.attachEventManager_.listen(has.mediaElement, 'error', onError);
|
|
1243
1266
|
}
|
|
1244
1267
|
|
|
1245
1268
|
this.video_ = has.mediaElement;
|
|
@@ -1266,10 +1289,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1266
1289
|
* @private
|
|
1267
1290
|
*/
|
|
1268
1291
|
onDetach_(has, wants) {
|
|
1269
|
-
// If we
|
|
1292
|
+
// If we were going from "detached" to "detached" we wouldn't have
|
|
1270
1293
|
// a media element to detach from.
|
|
1271
1294
|
if (has.mediaElement) {
|
|
1272
|
-
this.
|
|
1295
|
+
this.attachEventManager_.removeAll();
|
|
1273
1296
|
has.mediaElement = null;
|
|
1274
1297
|
}
|
|
1275
1298
|
|
|
@@ -1327,11 +1350,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1327
1350
|
// In most cases we should have a media element. The one exception would
|
|
1328
1351
|
// be if there was an error and we, by chance, did not have a media element.
|
|
1329
1352
|
if (has.mediaElement) {
|
|
1330
|
-
this.
|
|
1331
|
-
this.eventManager_.unlisten(has.mediaElement, 'playing');
|
|
1332
|
-
this.eventManager_.unlisten(has.mediaElement, 'pause');
|
|
1333
|
-
this.eventManager_.unlisten(has.mediaElement, 'ended');
|
|
1334
|
-
this.eventManager_.unlisten(has.mediaElement, 'ratechange');
|
|
1353
|
+
this.loadEventManager_.removeAll();
|
|
1335
1354
|
}
|
|
1336
1355
|
|
|
1337
1356
|
// Some observers use some playback components, shutting down the observers
|
|
@@ -1795,18 +1814,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1795
1814
|
this.assetUri_ = assetUri;
|
|
1796
1815
|
|
|
1797
1816
|
this.playRateController_ = new shaka.media.PlayRateController({
|
|
1798
|
-
getRate: () =>
|
|
1799
|
-
getDefaultRate: () =>
|
|
1800
|
-
setRate: (rate) => {
|
|
1801
|
-
movePlayhead: (delta) => {
|
|
1817
|
+
getRate: () => mediaElement.playbackRate,
|
|
1818
|
+
getDefaultRate: () => mediaElement.defaultPlaybackRate,
|
|
1819
|
+
setRate: (rate) => { mediaElement.playbackRate = rate; },
|
|
1820
|
+
movePlayhead: (delta) => { mediaElement.currentTime += delta; },
|
|
1802
1821
|
});
|
|
1803
1822
|
|
|
1804
1823
|
const updateStateHistory = () => this.updateStateHistory_();
|
|
1805
1824
|
const onRateChange = () => this.onRateChange_();
|
|
1806
|
-
this.
|
|
1807
|
-
this.
|
|
1808
|
-
this.
|
|
1809
|
-
this.
|
|
1825
|
+
this.loadEventManager_.listen(mediaElement, 'playing', updateStateHistory);
|
|
1826
|
+
this.loadEventManager_.listen(mediaElement, 'pause', updateStateHistory);
|
|
1827
|
+
this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
|
|
1828
|
+
this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
|
|
1810
1829
|
|
|
1811
1830
|
const abrFactory = this.config_.abrFactory;
|
|
1812
1831
|
if (!this.abrManager_ || this.abrManagerFactory_ != abrFactory) {
|
|
@@ -1869,6 +1888,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1869
1888
|
// "streaming" so that they can access internal information.
|
|
1870
1889
|
this.loadMode_ = shaka.Player.LoadMode.MEDIA_SOURCE;
|
|
1871
1890
|
|
|
1891
|
+
if (mediaElement.textTracks) {
|
|
1892
|
+
this.loadEventManager_.listen(
|
|
1893
|
+
mediaElement.textTracks, 'addtrack', (e) => {
|
|
1894
|
+
const trackEvent = /** @type {!TrackEvent} */(e);
|
|
1895
|
+
if (trackEvent.track) {
|
|
1896
|
+
const track = trackEvent.track;
|
|
1897
|
+
goog.asserts.assert(
|
|
1898
|
+
track instanceof TextTrack, 'Wrong track type!');
|
|
1899
|
+
|
|
1900
|
+
switch (track.kind) {
|
|
1901
|
+
case 'chapters':
|
|
1902
|
+
this.activateChaptersTrack_(track);
|
|
1903
|
+
break;
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1872
1909
|
// The event must be fired after we filter by restrictions but before the
|
|
1873
1910
|
// active stream is picked to allow those listening for the "streaming"
|
|
1874
1911
|
// event to make changes before streaming starts.
|
|
@@ -1955,7 +1992,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1955
1992
|
}
|
|
1956
1993
|
|
|
1957
1994
|
// Wait for the 'loadedmetadata' event to measure load() latency.
|
|
1958
|
-
this.
|
|
1995
|
+
this.loadEventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
|
|
1959
1996
|
const now = Date.now() / 1000;
|
|
1960
1997
|
const delta = now - wants.startTimeOfLoad;
|
|
1961
1998
|
this.stats_.setLoadLatency(delta);
|
|
@@ -2108,11 +2145,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2108
2145
|
// Save the uri so that it can be used outside of the load-graph.
|
|
2109
2146
|
this.assetUri_ = has.uri;
|
|
2110
2147
|
|
|
2111
|
-
|
|
2148
|
+
const mediaElement = has.mediaElement;
|
|
2149
|
+
|
|
2150
|
+
this.playhead_ = new shaka.media.SrcEqualsPlayhead(mediaElement);
|
|
2112
2151
|
|
|
2113
|
-
// This flag is used below in the language preference setup
|
|
2114
|
-
//
|
|
2115
|
-
// events fired.
|
|
2152
|
+
// This flag is used below in the language preference setup to check if
|
|
2153
|
+
// this load was canceled before the necessary awaits completed.
|
|
2116
2154
|
let unloaded = false;
|
|
2117
2155
|
this.cleanupOnUnload_.push(() => {
|
|
2118
2156
|
unloaded = true;
|
|
@@ -2123,10 +2161,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2123
2161
|
}
|
|
2124
2162
|
|
|
2125
2163
|
this.playRateController_ = new shaka.media.PlayRateController({
|
|
2126
|
-
getRate: () =>
|
|
2127
|
-
getDefaultRate: () =>
|
|
2128
|
-
setRate: (rate) => {
|
|
2129
|
-
movePlayhead: (delta) => {
|
|
2164
|
+
getRate: () => mediaElement.playbackRate,
|
|
2165
|
+
getDefaultRate: () => mediaElement.defaultPlaybackRate,
|
|
2166
|
+
setRate: (rate) => { mediaElement.playbackRate = rate; },
|
|
2167
|
+
movePlayhead: (delta) => { mediaElement.currentTime += delta; },
|
|
2130
2168
|
});
|
|
2131
2169
|
|
|
2132
2170
|
// We need to start the buffer management code near the end because it will
|
|
@@ -2138,17 +2176,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2138
2176
|
// Add all media element listeners.
|
|
2139
2177
|
const updateStateHistory = () => this.updateStateHistory_();
|
|
2140
2178
|
const onRateChange = () => this.onRateChange_();
|
|
2141
|
-
this.
|
|
2142
|
-
this.
|
|
2143
|
-
this.
|
|
2144
|
-
this.
|
|
2179
|
+
this.loadEventManager_.listen(mediaElement, 'playing', updateStateHistory);
|
|
2180
|
+
this.loadEventManager_.listen(mediaElement, 'pause', updateStateHistory);
|
|
2181
|
+
this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
|
|
2182
|
+
this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
|
|
2145
2183
|
|
|
2146
2184
|
// Wait for the 'loadedmetadata' event to measure load() latency, but only
|
|
2147
2185
|
// if preload is set in a way that would result in this event firing
|
|
2148
2186
|
// automatically.
|
|
2149
2187
|
// See https://github.com/shaka-project/shaka-player/issues/2483
|
|
2150
|
-
if (
|
|
2151
|
-
this.
|
|
2188
|
+
if (mediaElement.preload != 'none') {
|
|
2189
|
+
this.loadEventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
|
|
2152
2190
|
const now = Date.now() / 1000;
|
|
2153
2191
|
const delta = now - wants.startTimeOfLoad;
|
|
2154
2192
|
this.stats_.setLoadLatency(delta);
|
|
@@ -2158,56 +2196,59 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2158
2196
|
// The audio tracks are only available on Safari at the moment, but this
|
|
2159
2197
|
// drives the tracks API for Safari's native HLS. So when they change,
|
|
2160
2198
|
// fire the corresponding Shaka Player event.
|
|
2161
|
-
if (
|
|
2162
|
-
this.
|
|
2163
|
-
|
|
2164
|
-
this.
|
|
2165
|
-
|
|
2199
|
+
if (mediaElement.audioTracks) {
|
|
2200
|
+
this.loadEventManager_.listen(
|
|
2201
|
+
mediaElement.audioTracks, 'addtrack', () => this.onTracksChanged_());
|
|
2202
|
+
this.loadEventManager_.listen(
|
|
2203
|
+
mediaElement.audioTracks, 'removetrack',
|
|
2166
2204
|
() => this.onTracksChanged_());
|
|
2167
|
-
this.
|
|
2168
|
-
|
|
2169
|
-
}
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2205
|
+
this.loadEventManager_.listen(
|
|
2206
|
+
mediaElement.audioTracks, 'change', () => this.onTracksChanged_());
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
if (mediaElement.textTracks) {
|
|
2210
|
+
this.loadEventManager_.listen(
|
|
2211
|
+
mediaElement.textTracks, 'addtrack', (e) => {
|
|
2212
|
+
const trackEvent = /** @type {!TrackEvent} */(e);
|
|
2213
|
+
if (trackEvent.track) {
|
|
2214
|
+
const track = trackEvent.track;
|
|
2215
|
+
goog.asserts.assert(
|
|
2216
|
+
track instanceof TextTrack, 'Wrong track type!');
|
|
2217
|
+
|
|
2218
|
+
switch (track.kind) {
|
|
2219
|
+
case 'metadata':
|
|
2220
|
+
this.processTimedMetadataSrcEqls_(track);
|
|
2221
|
+
break;
|
|
2222
|
+
|
|
2223
|
+
case 'chapters':
|
|
2224
|
+
this.activateChaptersTrack_(track);
|
|
2225
|
+
break;
|
|
2226
|
+
|
|
2227
|
+
default:
|
|
2228
|
+
this.onTracksChanged_();
|
|
2229
|
+
break;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
});
|
|
2177
2233
|
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
this.processTimedMetadataSrcEqls_(track);
|
|
2185
|
-
break;
|
|
2186
|
-
case 'chapters':
|
|
2187
|
-
this.processChaptersTrack_(track);
|
|
2188
|
-
break;
|
|
2189
|
-
default:
|
|
2190
|
-
this.onTracksChanged_();
|
|
2191
|
-
break;
|
|
2192
|
-
}
|
|
2193
|
-
}
|
|
2194
|
-
});
|
|
2195
|
-
this.eventManager_.listen(
|
|
2196
|
-
this.video_.textTracks, 'removetrack', () => this.onTracksChanged_());
|
|
2197
|
-
this.eventManager_.listen(
|
|
2198
|
-
this.video_.textTracks, 'change', () => this.onTracksChanged_());
|
|
2234
|
+
this.loadEventManager_.listen(
|
|
2235
|
+
mediaElement.textTracks, 'removetrack',
|
|
2236
|
+
() => this.onTracksChanged_());
|
|
2237
|
+
this.loadEventManager_.listen(
|
|
2238
|
+
mediaElement.textTracks, 'change',
|
|
2239
|
+
() => this.onTracksChanged_());
|
|
2199
2240
|
}
|
|
2200
2241
|
|
|
2201
2242
|
// By setting |src| we are done "loading" with src=. We don't need to set
|
|
2202
2243
|
// the current time because |playhead| will do that for us.
|
|
2203
|
-
|
|
2244
|
+
mediaElement.src = has.uri;
|
|
2204
2245
|
|
|
2205
2246
|
// Tizen 3 / WebOS won't load anything unless you call load() explicitly,
|
|
2206
2247
|
// no matter the value of the preload attribute. This is harmful on some
|
|
2207
2248
|
// other platforms by triggering unbounded loading of media data, but is
|
|
2208
2249
|
// necessary here.
|
|
2209
2250
|
if (shaka.util.Platform.isTizen() || shaka.util.Platform.isWebOS()) {
|
|
2210
|
-
|
|
2251
|
+
mediaElement.load();
|
|
2211
2252
|
}
|
|
2212
2253
|
|
|
2213
2254
|
// Set the load mode last so that we know that all our components are
|
|
@@ -2223,24 +2264,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2223
2264
|
// wait for the full data, that won't happen on Safari until the play button
|
|
2224
2265
|
// is hit.
|
|
2225
2266
|
const fullyLoaded = new shaka.util.PublicPromise();
|
|
2226
|
-
shaka.util.MediaReadyState.waitForReadyState(
|
|
2267
|
+
shaka.util.MediaReadyState.waitForReadyState(mediaElement,
|
|
2227
2268
|
HTMLMediaElement.HAVE_METADATA,
|
|
2228
|
-
this.
|
|
2269
|
+
this.loadEventManager_,
|
|
2229
2270
|
() => {
|
|
2230
2271
|
fullyLoaded.resolve();
|
|
2231
2272
|
});
|
|
2232
2273
|
|
|
2233
2274
|
// We can't switch to preferred languages, though, until the data is loaded.
|
|
2234
|
-
shaka.util.MediaReadyState.waitForReadyState(
|
|
2275
|
+
shaka.util.MediaReadyState.waitForReadyState(mediaElement,
|
|
2235
2276
|
HTMLMediaElement.HAVE_CURRENT_DATA,
|
|
2236
|
-
this.
|
|
2277
|
+
this.loadEventManager_,
|
|
2237
2278
|
async () => {
|
|
2238
|
-
// If we have moved on to another piece of content while waiting for
|
|
2239
|
-
// the above event, we should not change tracks here.
|
|
2240
|
-
if (unloaded) {
|
|
2241
|
-
return;
|
|
2242
|
-
}
|
|
2243
|
-
|
|
2244
2279
|
this.setupPreferredAudioOnSrc_();
|
|
2245
2280
|
|
|
2246
2281
|
// Applying the text preference too soon can result in it being
|
|
@@ -2248,8 +2283,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2248
2283
|
const textTracks = this.getFilteredTextTracks_();
|
|
2249
2284
|
if (!textTracks.find((t) => t.mode != 'disabled')) {
|
|
2250
2285
|
await new Promise((resolve) => {
|
|
2251
|
-
this.
|
|
2252
|
-
|
|
2286
|
+
this.loadEventManager_.listenOnce(
|
|
2287
|
+
mediaElement.textTracks, 'change', resolve);
|
|
2288
|
+
|
|
2253
2289
|
// We expect the event to fire because it does on Safari.
|
|
2254
2290
|
// But in case it doesn't on some other platform or future
|
|
2255
2291
|
// version, move on in 1 second no matter what. This keeps the
|
|
@@ -2268,10 +2304,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2268
2304
|
this.setupPreferredTextOnSrc_();
|
|
2269
2305
|
});
|
|
2270
2306
|
|
|
2271
|
-
if (
|
|
2307
|
+
if (mediaElement.error) {
|
|
2272
2308
|
// Already failed!
|
|
2273
2309
|
fullyLoaded.reject(this.videoErrorToShakaError_());
|
|
2274
|
-
} else if (
|
|
2310
|
+
} else if (mediaElement.preload == 'none') {
|
|
2275
2311
|
shaka.log.alwaysWarn(
|
|
2276
2312
|
'With <video preload="none">, the browser will not load anything ' +
|
|
2277
2313
|
'until play() is called. We are unable to measure load latency in ' +
|
|
@@ -2282,7 +2318,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2282
2318
|
fullyLoaded.resolve();
|
|
2283
2319
|
}
|
|
2284
2320
|
|
|
2285
|
-
this.
|
|
2321
|
+
this.loadEventManager_.listenOnce(mediaElement, 'error', () => {
|
|
2286
2322
|
fullyLoaded.reject(this.videoErrorToShakaError_());
|
|
2287
2323
|
});
|
|
2288
2324
|
|
|
@@ -2366,7 +2402,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2366
2402
|
|
|
2367
2403
|
// Hidden mode is required for the cuechange event to launch correctly
|
|
2368
2404
|
track.mode = 'hidden';
|
|
2369
|
-
this.
|
|
2405
|
+
this.loadEventManager_.listen(track, 'cuechange', () => {
|
|
2370
2406
|
if (!track.activeCues) {
|
|
2371
2407
|
return;
|
|
2372
2408
|
}
|
|
@@ -2444,12 +2480,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2444
2480
|
}
|
|
2445
2481
|
|
|
2446
2482
|
/**
|
|
2447
|
-
*
|
|
2483
|
+
* Set the mode on a chapters track so that it loads.
|
|
2448
2484
|
*
|
|
2449
2485
|
* @param {?TextTrack} track
|
|
2450
2486
|
* @private
|
|
2451
2487
|
*/
|
|
2452
|
-
|
|
2488
|
+
activateChaptersTrack_(track) {
|
|
2453
2489
|
if (!track || track.kind != 'chapters') {
|
|
2454
2490
|
return;
|
|
2455
2491
|
}
|
|
@@ -2462,10 +2498,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2462
2498
|
// this process to be repeated several times to ensure that it has been put
|
|
2463
2499
|
// in the correct mode.
|
|
2464
2500
|
const timer = new shaka.util.Timer(() => {
|
|
2465
|
-
|
|
2466
|
-
for (const chaptersTrack of chaptersTracks) {
|
|
2467
|
-
chaptersTrack.mode = 'hidden';
|
|
2468
|
-
}
|
|
2501
|
+
track.mode = 'hidden';
|
|
2469
2502
|
}).tickNow().tickAfter(0.5);
|
|
2470
2503
|
|
|
2471
2504
|
this.cleanupOnUnload_.push(() => {
|
|
@@ -4557,25 +4590,41 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4557
4590
|
if (!mimeType) {
|
|
4558
4591
|
mimeType = await this.getTextMimetype_(uri);
|
|
4559
4592
|
}
|
|
4593
|
+
|
|
4560
4594
|
let adCuePoints = [];
|
|
4561
4595
|
if (this.adManager_) {
|
|
4562
4596
|
try {
|
|
4563
4597
|
adCuePoints = this.adManager_.getServerSideCuePoints();
|
|
4564
4598
|
} catch (error) {}
|
|
4565
4599
|
}
|
|
4566
|
-
|
|
4567
|
-
|
|
4600
|
+
|
|
4601
|
+
/** @type {!HTMLTrackElement} */
|
|
4602
|
+
const trackElement = await this.addSrcTrackElement_(
|
|
4603
|
+
uri, language, /* kind= */ 'chapters', mimeType, /* label= */ '',
|
|
4604
|
+
adCuePoints);
|
|
4605
|
+
|
|
4568
4606
|
const chaptersTracks = this.getChaptersTracks();
|
|
4569
4607
|
const chaptersTrack = chaptersTracks.find((t) => {
|
|
4570
4608
|
return t.language == language;
|
|
4571
4609
|
});
|
|
4610
|
+
|
|
4572
4611
|
if (chaptersTrack) {
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4612
|
+
await new Promise((resolve, reject) => {
|
|
4613
|
+
// The chapter data isn't available until the 'load' event fires, and
|
|
4614
|
+
// that won't happen until the chapters track is activated by the
|
|
4615
|
+
// activateChaptersTrack_ method.
|
|
4616
|
+
this.loadEventManager_.listenOnce(trackElement, 'load', resolve);
|
|
4617
|
+
this.loadEventManager_.listenOnce(trackElement, 'error', (event) => {
|
|
4618
|
+
reject(new shaka.util.Error(
|
|
4619
|
+
shaka.util.Error.Severity.RECOVERABLE,
|
|
4620
|
+
shaka.util.Error.Category.TEXT,
|
|
4621
|
+
shaka.util.Error.Code.CHAPTERS_TRACK_FAILED));
|
|
4622
|
+
});
|
|
4623
|
+
});
|
|
4624
|
+
|
|
4577
4625
|
return chaptersTrack;
|
|
4578
4626
|
}
|
|
4627
|
+
|
|
4579
4628
|
// This should not happen, but there are browser implementations that may
|
|
4580
4629
|
// not support the Track element.
|
|
4581
4630
|
shaka.log.error('Cannot add this text when loaded with src=');
|
|
@@ -4629,6 +4678,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4629
4678
|
* @param {string} mimeType
|
|
4630
4679
|
* @param {string} label
|
|
4631
4680
|
* @param {!Array.<!shaka.extern.AdCuePoint>} adCuePoints
|
|
4681
|
+
* @return {!Promise.<!HTMLTrackElement>}
|
|
4632
4682
|
* @private
|
|
4633
4683
|
*/
|
|
4634
4684
|
async addSrcTrackElement_(uri, language, kind, mimeType, label,
|
|
@@ -4644,12 +4694,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4644
4694
|
uri = shaka.media.MediaSourceEngine.createObjectURL(blob);
|
|
4645
4695
|
mimeType = 'text/vtt';
|
|
4646
4696
|
}
|
|
4697
|
+
|
|
4647
4698
|
const trackElement =
|
|
4648
4699
|
/** @type {!HTMLTrackElement} */(document.createElement('track'));
|
|
4649
4700
|
trackElement.src = uri;
|
|
4650
4701
|
trackElement.label = label;
|
|
4651
4702
|
trackElement.kind = kind;
|
|
4652
4703
|
trackElement.srclang = language;
|
|
4704
|
+
|
|
4653
4705
|
// Because we're pulling in the text track file via Javascript, the
|
|
4654
4706
|
// same-origin policy applies. If you'd like to have a player served
|
|
4655
4707
|
// from one domain, but the text track served from another, you'll
|
|
@@ -4659,7 +4711,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4659
4711
|
if (!this.video_.getAttribute('crossorigin')) {
|
|
4660
4712
|
this.video_.setAttribute('crossorigin', 'anonymous');
|
|
4661
4713
|
}
|
|
4714
|
+
|
|
4662
4715
|
this.video_.appendChild(trackElement);
|
|
4716
|
+
return trackElement;
|
|
4663
4717
|
}
|
|
4664
4718
|
|
|
4665
4719
|
/**
|
|
@@ -5027,7 +5081,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5027
5081
|
}
|
|
5028
5082
|
|
|
5029
5083
|
await stream.createSegmentIndex();
|
|
5030
|
-
const
|
|
5084
|
+
const iter = stream.segmentIndex.getIteratorForTime(time);
|
|
5085
|
+
const ref = iter ? iter.next().value : null;
|
|
5031
5086
|
if (!ref) {
|
|
5032
5087
|
return null;
|
|
5033
5088
|
}
|
|
@@ -5661,7 +5716,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5661
5716
|
} // if (keyIds.size)
|
|
5662
5717
|
|
|
5663
5718
|
if (tracksChanged) {
|
|
5664
|
-
this.updateAbrManagerVariants_();
|
|
5719
|
+
const variantsUpdated = this.updateAbrManagerVariants_();
|
|
5720
|
+
if (!variantsUpdated) {
|
|
5721
|
+
return;
|
|
5722
|
+
}
|
|
5665
5723
|
}
|
|
5666
5724
|
|
|
5667
5725
|
const currentVariant = this.streamingEngine_.getCurrentVariant();
|
|
@@ -6372,7 +6430,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6372
6430
|
* @define {string} A version number taken from git at compile time.
|
|
6373
6431
|
* @export
|
|
6374
6432
|
*/
|
|
6375
|
-
shaka.Player.version = 'v3.2.
|
|
6433
|
+
shaka.Player.version = 'v3.2.7-uncompiled';
|
|
6376
6434
|
|
|
6377
6435
|
// Initialize the deprecation system using the version string we just set
|
|
6378
6436
|
// on the player.
|