shaka-player 4.9.35 → 4.9.36
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/README.md +9 -3
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/deps.js +5 -5
- package/dist/locales.js +36 -36
- package/dist/shaka-player.compiled.d.ts +1 -0
- package/dist/shaka-player.compiled.debug.d.ts +1 -0
- package/dist/shaka-player.compiled.debug.externs.js +4 -3
- package/dist/shaka-player.compiled.debug.js +436 -421
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +4 -3
- package/dist/shaka-player.compiled.js +1263 -1251
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.dash.d.ts +4099 -0
- package/dist/shaka-player.dash.debug.d.ts +4099 -0
- package/dist/shaka-player.dash.debug.externs.js +4188 -0
- package/dist/shaka-player.dash.debug.js +2161 -0
- package/dist/shaka-player.dash.debug.map +8 -0
- package/dist/shaka-player.dash.externs.js +4188 -0
- package/dist/shaka-player.dash.js +1007 -0
- package/dist/shaka-player.dash.map +8 -0
- package/dist/shaka-player.hls.d.ts +4196 -0
- package/dist/shaka-player.hls.debug.d.ts +4196 -0
- package/dist/shaka-player.hls.debug.externs.js +4403 -0
- package/dist/shaka-player.hls.debug.js +2243 -0
- package/dist/shaka-player.hls.debug.map +8 -0
- package/dist/shaka-player.hls.externs.js +4403 -0
- package/dist/shaka-player.hls.js +1076 -0
- package/dist/shaka-player.hls.map +8 -0
- package/dist/shaka-player.ui.d.ts +1 -0
- package/dist/shaka-player.ui.debug.d.ts +1 -0
- package/dist/shaka-player.ui.debug.externs.js +4 -3
- package/dist/shaka-player.ui.debug.js +460 -445
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +4 -3
- package/dist/shaka-player.ui.js +1462 -1453
- package/dist/shaka-player.ui.map +3 -3
- package/externs/shaka/player.js +8 -4
- package/lib/cast/cast_proxy.js +48 -10
- package/lib/cea/cea708_service.js +9 -16
- package/lib/dash/dash_parser.js +23 -16
- package/lib/dash/segment_template.js +41 -40
- package/lib/hls/hls_parser.js +2 -2
- package/lib/media/content_workarounds.js +110 -72
- package/lib/media/drm_engine.js +72 -11
- package/lib/media/gap_jumping_controller.js +14 -1
- package/lib/media/media_source_engine.js +29 -11
- package/lib/media/region_timeline.js +4 -3
- package/lib/media/streaming_engine.js +37 -11
- package/lib/net/http_plugin_utils.js +1 -1
- package/lib/net/networking_engine.js +8 -0
- package/lib/net/networking_utils.js +1 -0
- package/lib/player.js +109 -37
- package/lib/polyfill/patchedmediakeys_apple.js +21 -0
- package/lib/text/cue.js +6 -2
- package/lib/text/mp4_ttml_parser.js +94 -6
- package/lib/text/ui_text_displayer.js +35 -4
- package/lib/transmuxer/ac3_transmuxer.js +6 -1
- package/lib/util/config_utils.js +13 -3
- package/lib/util/drm_utils.js +8 -0
- package/lib/util/error.js +6 -0
- package/lib/util/mp4_generator.js +19 -4
- package/lib/util/periods.js +4 -2
- package/lib/util/platform.js +60 -12
- package/lib/util/player_configuration.js +6 -1
- package/lib/util/stream_utils.js +18 -0
- package/lib/util/tXml.js +3 -2
- package/package.json +3 -3
- package/ui/controls.js +2 -6
- package/ui/less/thumbnails.less +1 -0
- package/ui/statistics_button.js +3 -1
- package/ui/ui.js +44 -35
package/lib/media/drm_engine.js
CHANGED
|
@@ -1004,7 +1004,17 @@ shaka.media.DrmEngine = class {
|
|
|
1004
1004
|
|
|
1005
1005
|
// If we have configured preferredKeySystems, choose a preferred keySystem
|
|
1006
1006
|
// if available.
|
|
1007
|
-
|
|
1007
|
+
let preferredKeySystems = this.config_.preferredKeySystems;
|
|
1008
|
+
if (!preferredKeySystems.length) {
|
|
1009
|
+
// If there is no preference set and we only have one license server, we
|
|
1010
|
+
// use this as preference. This is used to override manifests on those
|
|
1011
|
+
// that have the embedded license and the browser supports multiple DRMs.
|
|
1012
|
+
const servers = shaka.util.MapUtils.asMap(this.config_.servers);
|
|
1013
|
+
if (servers.size == 1) {
|
|
1014
|
+
preferredKeySystems = Array.from(servers.keys());
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
for (const preferredKeySystem of preferredKeySystems) {
|
|
1008
1018
|
for (const variant of variants) {
|
|
1009
1019
|
const decodingInfo = variant.decodingInfos.find((decodingInfo) => {
|
|
1010
1020
|
return decodingInfo.supported &&
|
|
@@ -1028,8 +1038,15 @@ shaka.media.DrmEngine = class {
|
|
|
1028
1038
|
if (!decodingInfo.supported || !decodingInfo.keySystemAccess) {
|
|
1029
1039
|
continue;
|
|
1030
1040
|
}
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1041
|
+
const originalKeySystem = decodingInfo.keySystemAccess.keySystem;
|
|
1042
|
+
if (preferredKeySystems.includes(originalKeySystem)) {
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
let drmInfos = drmInfosByKeySystem.get(originalKeySystem);
|
|
1046
|
+
if (!drmInfos && this.config_.keySystemsMapping[originalKeySystem]) {
|
|
1047
|
+
drmInfos = drmInfosByKeySystem.get(
|
|
1048
|
+
this.config_.keySystemsMapping[originalKeySystem]);
|
|
1049
|
+
}
|
|
1033
1050
|
for (const info of drmInfos) {
|
|
1034
1051
|
if (!!info.licenseServerUri == shouldHaveLicenseServer) {
|
|
1035
1052
|
return decodingInfo.keySystemAccess;
|
|
@@ -1800,7 +1817,18 @@ shaka.media.DrmEngine = class {
|
|
|
1800
1817
|
*/
|
|
1801
1818
|
const processMediaKeySystemAccess = async (keySystem, access) => {
|
|
1802
1819
|
try {
|
|
1803
|
-
|
|
1820
|
+
// Workaround: Our automated test lab runs Windows browsers under a
|
|
1821
|
+
// headless service. In this environment, Firefox's ClearKey CDM seems
|
|
1822
|
+
// to crash when we create the CDM here.
|
|
1823
|
+
if (goog.DEBUG && // not a production build
|
|
1824
|
+
shaka.util.Platform.isWindows() && // on Windows
|
|
1825
|
+
shaka.util.Platform.isFirefox()) { // with Firefox
|
|
1826
|
+
// Reject this, since it crashes our tests.
|
|
1827
|
+
throw new Error('Suppressing Firefox Windows ClearKey in testing!');
|
|
1828
|
+
} else {
|
|
1829
|
+
// Otherwise, create the CDM.
|
|
1830
|
+
await access.createMediaKeys();
|
|
1831
|
+
}
|
|
1804
1832
|
} catch (error) {
|
|
1805
1833
|
// In some cases, we can get a successful access object but fail to
|
|
1806
1834
|
// create a MediaKeys instance. When this happens, don't update the
|
|
@@ -1947,17 +1975,47 @@ shaka.media.DrmEngine = class {
|
|
|
1947
1975
|
support.set(keySystem, null);
|
|
1948
1976
|
}
|
|
1949
1977
|
|
|
1978
|
+
const checkKeySystem = (keySystem) => {
|
|
1979
|
+
// Our Polyfill will reject anything apart com.apple.fps key systems.
|
|
1980
|
+
// It seems the Safari modern EME API will allow to request a
|
|
1981
|
+
// MediaKeySystemAccess for the ClearKey CDM, create and update a key
|
|
1982
|
+
// session but playback will never start
|
|
1983
|
+
// Safari bug: https://bugs.webkit.org/show_bug.cgi?id=231006
|
|
1984
|
+
if (shaka.util.DrmUtils.isClearKeySystem(keySystem) &&
|
|
1985
|
+
shaka.util.Platform.isSafari()) {
|
|
1986
|
+
return false;
|
|
1987
|
+
}
|
|
1988
|
+
// FairPlay is a proprietary DRM from Apple and will never work on
|
|
1989
|
+
// Windows.
|
|
1990
|
+
if (shaka.util.DrmUtils.isFairPlayKeySystem(keySystem) &&
|
|
1991
|
+
shaka.util.Platform.isWindows()) {
|
|
1992
|
+
return false;
|
|
1993
|
+
}
|
|
1994
|
+
// PlayReady is a proprietary DRM from Microsoft and will never work on
|
|
1995
|
+
// Apple platforms
|
|
1996
|
+
if (shaka.util.DrmUtils.isPlayReadyKeySystem(keySystem) &&
|
|
1997
|
+
(shaka.util.Platform.isMac() || shaka.util.Platform.isApple())) {
|
|
1998
|
+
return false;
|
|
1999
|
+
}
|
|
2000
|
+
// Mozilla has no intention of supporting PlayReady according to
|
|
2001
|
+
// comments posted on Bugzilla.
|
|
2002
|
+
if (shaka.util.DrmUtils.isPlayReadyKeySystem(keySystem) &&
|
|
2003
|
+
shaka.util.Platform.isFirefox()) {
|
|
2004
|
+
return false;
|
|
2005
|
+
}
|
|
2006
|
+
// We are sure that WisePlay is not supported on Windows or macOS.
|
|
2007
|
+
if (shaka.util.DrmUtils.isWisePlayKeySystem(keySystem) &&
|
|
2008
|
+
(shaka.util.Platform.isWindows() || shaka.util.Platform.isMac())) {
|
|
2009
|
+
return false;
|
|
2010
|
+
}
|
|
2011
|
+
return true;
|
|
2012
|
+
};
|
|
2013
|
+
|
|
1950
2014
|
// Test each key system and encryption scheme.
|
|
1951
2015
|
const tests = [];
|
|
1952
2016
|
for (const encryptionScheme of testEncryptionSchemes) {
|
|
1953
2017
|
for (const keySystem of testKeySystems) {
|
|
1954
|
-
|
|
1955
|
-
// It seems the Safari modern EME API will allow to request a
|
|
1956
|
-
// MediaKeySystemAccess for the ClearKey CDM, create and update a key
|
|
1957
|
-
// session but playback will never start
|
|
1958
|
-
// Safari bug: https://bugs.webkit.org/show_bug.cgi?id=231006
|
|
1959
|
-
if (keySystem === 'org.w3.clearkey' &&
|
|
1960
|
-
shaka.util.Platform.isSafari()) {
|
|
2018
|
+
if (!checkKeySystem(keySystem)) {
|
|
1961
2019
|
continue;
|
|
1962
2020
|
}
|
|
1963
2021
|
tests.push(testSystemEme(keySystem, encryptionScheme, '', ''));
|
|
@@ -1967,6 +2025,9 @@ shaka.media.DrmEngine = class {
|
|
|
1967
2025
|
|
|
1968
2026
|
for (const keySystem of testKeySystems) {
|
|
1969
2027
|
for (const robustness of (testRobustness[keySystem] || [])) {
|
|
2028
|
+
if (!checkKeySystem(keySystem)) {
|
|
2029
|
+
continue;
|
|
2030
|
+
}
|
|
1970
2031
|
tests.push(testSystemEme(keySystem, null, robustness, ''));
|
|
1971
2032
|
tests.push(testSystemEme(keySystem, null, '', robustness));
|
|
1972
2033
|
tests.push(testSystemMcap(keySystem, null, robustness, ''));
|
|
@@ -53,6 +53,9 @@ shaka.media.GapJumpingController = class {
|
|
|
53
53
|
/** @private {shaka.util.EventManager} */
|
|
54
54
|
this.eventManager_ = new shaka.util.EventManager();
|
|
55
55
|
|
|
56
|
+
/** @private {boolean} */
|
|
57
|
+
this.started_ = false;
|
|
58
|
+
|
|
56
59
|
/** @private {boolean} */
|
|
57
60
|
this.seekingEventReceived_ = false;
|
|
58
61
|
|
|
@@ -90,7 +93,7 @@ shaka.media.GapJumpingController = class {
|
|
|
90
93
|
*/
|
|
91
94
|
this.gapJumpTimer_ = new shaka.util.Timer(() => {
|
|
92
95
|
this.onPollGapJump_();
|
|
93
|
-
})
|
|
96
|
+
});
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
|
|
@@ -134,16 +137,22 @@ shaka.media.GapJumpingController = class {
|
|
|
134
137
|
* @param {number} startTime
|
|
135
138
|
*/
|
|
136
139
|
onStarted(startTime) {
|
|
140
|
+
this.started_ = true;
|
|
137
141
|
if (this.video_.seeking && !this.seekingEventReceived_) {
|
|
138
142
|
this.seekingEventReceived_ = true;
|
|
139
143
|
this.startTime_ = startTime;
|
|
140
144
|
}
|
|
145
|
+
if (this.gapJumpTimer_) {
|
|
146
|
+
this.gapJumpTimer_.tickEvery(this.config_.gapJumpTimerTime);
|
|
147
|
+
}
|
|
148
|
+
this.onPollGapJump_();
|
|
141
149
|
}
|
|
142
150
|
|
|
143
151
|
/** Called when a seek has started. */
|
|
144
152
|
onSeeking() {
|
|
145
153
|
this.seekingEventReceived_ = true;
|
|
146
154
|
this.hadSegmentAppended_ = false;
|
|
155
|
+
this.onPollGapJump_();
|
|
147
156
|
}
|
|
148
157
|
|
|
149
158
|
|
|
@@ -167,6 +176,10 @@ shaka.media.GapJumpingController = class {
|
|
|
167
176
|
if (this.video_.readyState == 0) {
|
|
168
177
|
return;
|
|
169
178
|
}
|
|
179
|
+
// Don't gap jump before playback started
|
|
180
|
+
if (!this.started_) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
170
183
|
// Do not gap jump if seeking has begun, but the seeking event has not
|
|
171
184
|
// yet fired for this particular seek.
|
|
172
185
|
if (this.video_.seeking) {
|
|
@@ -543,6 +543,11 @@ shaka.media.MediaSourceEngine = class {
|
|
|
543
543
|
stream, contentType),
|
|
544
544
|
'Type negotiation should happen before MediaSourceEngine.init!');
|
|
545
545
|
|
|
546
|
+
if (contentType == ContentType.AUDIO && codecs) {
|
|
547
|
+
codecs = shaka.util.StreamUtils.getCorrectAudioCodecs(
|
|
548
|
+
codecs, stream.mimeType);
|
|
549
|
+
}
|
|
550
|
+
|
|
546
551
|
let mimeType = shaka.util.MimeUtils.getFullType(
|
|
547
552
|
stream.mimeType, codecs);
|
|
548
553
|
if (contentType == ContentType.TEXT) {
|
|
@@ -1157,7 +1162,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1157
1162
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
1158
1163
|
if (contentType == ContentType.TEXT) {
|
|
1159
1164
|
await this.textEngine_.remove(startTime, endTime);
|
|
1160
|
-
} else {
|
|
1165
|
+
} else if (endTime > startTime) {
|
|
1161
1166
|
await this.enqueueOperation_(
|
|
1162
1167
|
contentType,
|
|
1163
1168
|
() => this.remove_(contentType, startTime, endTime),
|
|
@@ -1309,20 +1314,24 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1309
1314
|
null));
|
|
1310
1315
|
}
|
|
1311
1316
|
}
|
|
1312
|
-
|
|
1313
|
-
contentType,
|
|
1314
|
-
() => this.setAppendWindow_(
|
|
1315
|
-
contentType, appendWindowStart, appendWindowEnd),
|
|
1316
|
-
null));
|
|
1317
|
-
if (this.needSplitMuxedContent_) {
|
|
1317
|
+
if (appendWindowStart != 0 || appendWindowEnd != Infinity) {
|
|
1318
1318
|
operations.push(this.enqueueOperation_(
|
|
1319
|
-
|
|
1319
|
+
contentType,
|
|
1320
1320
|
() => this.setAppendWindow_(
|
|
1321
|
-
|
|
1321
|
+
contentType, appendWindowStart, appendWindowEnd),
|
|
1322
1322
|
null));
|
|
1323
|
+
if (this.needSplitMuxedContent_) {
|
|
1324
|
+
operations.push(this.enqueueOperation_(
|
|
1325
|
+
ContentType.AUDIO,
|
|
1326
|
+
() => this.setAppendWindow_(
|
|
1327
|
+
ContentType.AUDIO, appendWindowStart, appendWindowEnd),
|
|
1328
|
+
null));
|
|
1329
|
+
}
|
|
1323
1330
|
}
|
|
1324
1331
|
|
|
1325
|
-
|
|
1332
|
+
if (operations.length) {
|
|
1333
|
+
await Promise.all(operations);
|
|
1334
|
+
}
|
|
1326
1335
|
}
|
|
1327
1336
|
|
|
1328
1337
|
/**
|
|
@@ -1841,6 +1850,14 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1841
1850
|
segment = shaka.media.ContentWorkarounds.fakeEncryption(segment, uri);
|
|
1842
1851
|
}
|
|
1843
1852
|
|
|
1853
|
+
if (isInitSegment &&
|
|
1854
|
+
Platform.requiresEC3InitSegments() &&
|
|
1855
|
+
shaka.util.MimeUtils.getContainerType(
|
|
1856
|
+
this.sourceBufferTypes_[contentType]) == 'mp4') {
|
|
1857
|
+
shaka.log.debug('Forcing fake EC-3 information in init segment.');
|
|
1858
|
+
segment = shaka.media.ContentWorkarounds.fakeEC3(segment);
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1844
1861
|
return segment;
|
|
1845
1862
|
}
|
|
1846
1863
|
|
|
@@ -2169,7 +2186,8 @@ shaka.media.MediaSourceEngine = class {
|
|
|
2169
2186
|
const newMimeType = realInfo.mimeType;
|
|
2170
2187
|
|
|
2171
2188
|
let muxedContentCheck = true;
|
|
2172
|
-
if (transmuxerMuxed
|
|
2189
|
+
if (transmuxerMuxed &&
|
|
2190
|
+
ContentType.AUDIO in this.sourceBufferTypes_) {
|
|
2173
2191
|
const muxedRealInfo =
|
|
2174
2192
|
this.getRealInfo_(ContentType.AUDIO, mimeType, codecs);
|
|
2175
2193
|
const muxedCurrentCodec = MimeUtils.getNormalizedCodec(
|
|
@@ -102,13 +102,14 @@ shaka.media.RegionTimeline = class extends shaka.util.FakeEventTarget {
|
|
|
102
102
|
* @private
|
|
103
103
|
*/
|
|
104
104
|
findSimilarRegion_(region) {
|
|
105
|
+
const isDiffNegligible = (a, b) => Math.abs(a - b) < 0.1;
|
|
105
106
|
for (const existing of this.regions_) {
|
|
106
107
|
// The same scheme ID and time range means that it is similar-enough to
|
|
107
108
|
// be the same region.
|
|
108
109
|
const isSimilar = existing.schemeIdUri == region.schemeIdUri &&
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
existing.id == region.id &&
|
|
111
|
+
isDiffNegligible(existing.startTime, region.startTime) &&
|
|
112
|
+
isDiffNegligible(existing.endTime, region.endTime);
|
|
112
113
|
|
|
113
114
|
if (isSimilar) {
|
|
114
115
|
return existing;
|
|
@@ -1314,12 +1314,17 @@ shaka.media.StreamingEngine = class {
|
|
|
1314
1314
|
this.playerInterface_.mediaSourceEngine.clearSelectedClosedCaptionId();
|
|
1315
1315
|
}
|
|
1316
1316
|
|
|
1317
|
+
// Update updateIntervalSeconds according to our current playbackrate,
|
|
1318
|
+
// and always considering a minimum of 1.
|
|
1319
|
+
const updateIntervalSeconds = this.config_.updateIntervalSeconds /
|
|
1320
|
+
Math.max(1, Math.abs(this.playerInterface_.getPlaybackRate()));
|
|
1321
|
+
|
|
1317
1322
|
if (!this.playerInterface_.mediaSourceEngine.isStreamingAllowed() &&
|
|
1318
1323
|
mediaState.type != ContentType.TEXT) {
|
|
1319
1324
|
// It is not allowed to add segments yet, so we schedule an update to
|
|
1320
1325
|
// check again later. So any prediction we make now could be terribly
|
|
1321
1326
|
// invalid soon.
|
|
1322
|
-
return
|
|
1327
|
+
return updateIntervalSeconds / 2;
|
|
1323
1328
|
}
|
|
1324
1329
|
|
|
1325
1330
|
const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
|
|
@@ -1393,9 +1398,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1393
1398
|
shaka.log.v2(logPrefix, 'buffering goal met');
|
|
1394
1399
|
|
|
1395
1400
|
// Do not try to predict the next update. Just poll according to
|
|
1396
|
-
// configuration (seconds).
|
|
1397
|
-
|
|
1398
|
-
return this.config_.updateIntervalSeconds / 2;
|
|
1401
|
+
// configuration (seconds).
|
|
1402
|
+
return updateIntervalSeconds / 2;
|
|
1399
1403
|
}
|
|
1400
1404
|
|
|
1401
1405
|
// Lack of segment iterator is the best indicator stream has changed.
|
|
@@ -1407,7 +1411,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1407
1411
|
// In any case just try again... if the manifest is incomplete or is not
|
|
1408
1412
|
// being updated then we'll idle forever; otherwise, we'll end up getting
|
|
1409
1413
|
// a SegmentReference eventually.
|
|
1410
|
-
return
|
|
1414
|
+
return updateIntervalSeconds;
|
|
1411
1415
|
}
|
|
1412
1416
|
// Get media state adaptation and reset this value. By guarding it during
|
|
1413
1417
|
// actual stream change we ensure it won't be cleaned by accident on regular
|
|
@@ -1446,7 +1450,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1446
1450
|
// For example, let video buffering catch up to audio buffering before
|
|
1447
1451
|
// fetching another audio segment.
|
|
1448
1452
|
shaka.log.v2(logPrefix, 'waiting for other streams to buffer');
|
|
1449
|
-
return
|
|
1453
|
+
return updateIntervalSeconds;
|
|
1450
1454
|
}
|
|
1451
1455
|
|
|
1452
1456
|
if (mediaState.segmentPrefetch && mediaState.segmentIterator &&
|
|
@@ -1509,7 +1513,18 @@ shaka.media.StreamingEngine = class {
|
|
|
1509
1513
|
// Something is buffered from the same Stream. Use the current position
|
|
1510
1514
|
// in the segment index. This is updated via next() after each segment is
|
|
1511
1515
|
// appended.
|
|
1512
|
-
|
|
1516
|
+
let ref = mediaState.segmentIterator.current();
|
|
1517
|
+
if (ref && mediaState.lastSegmentReference) {
|
|
1518
|
+
// In HLS sometimes the segment iterator adds or removes segments very
|
|
1519
|
+
// quickly, so we have to be sure that we do not add the last segment
|
|
1520
|
+
// again, tolerating a difference of 1ms.
|
|
1521
|
+
const isDiffNegligible = (a, b) => Math.abs(a - b) < 0.001;
|
|
1522
|
+
const lastStartTime = mediaState.lastSegmentReference.startTime;
|
|
1523
|
+
if (isDiffNegligible(lastStartTime, ref.startTime)) {
|
|
1524
|
+
ref = mediaState.segmentIterator.next().value;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
return ref;
|
|
1513
1528
|
} else if (mediaState.lastSegmentReference || bufferEnd) {
|
|
1514
1529
|
// Something is buffered from another Stream.
|
|
1515
1530
|
const time = mediaState.lastSegmentReference ?
|
|
@@ -2512,15 +2527,26 @@ shaka.media.StreamingEngine = class {
|
|
|
2512
2527
|
}
|
|
2513
2528
|
const bufferedBehind = presentationTime - startTime;
|
|
2514
2529
|
|
|
2515
|
-
const
|
|
2530
|
+
const evictionGoal = this.config_.evictionGoal;
|
|
2531
|
+
|
|
2532
|
+
const seekRangeStart =
|
|
2533
|
+
this.manifest_.presentationTimeline.getSeekRangeStart();
|
|
2534
|
+
const seekRangeEnd =
|
|
2535
|
+
this.manifest_.presentationTimeline.getSeekRangeEnd();
|
|
2536
|
+
|
|
2537
|
+
let overflow = bufferedBehind - bufferBehind;
|
|
2538
|
+
if (seekRangeEnd - seekRangeStart > evictionGoal) {
|
|
2539
|
+
overflow = Math.max(bufferedBehind - bufferBehind,
|
|
2540
|
+
seekRangeStart - evictionGoal - startTime);
|
|
2541
|
+
}
|
|
2516
2542
|
// See: https://github.com/shaka-project/shaka-player/issues/6240
|
|
2517
|
-
if (overflow <=
|
|
2543
|
+
if (overflow <= evictionGoal) {
|
|
2518
2544
|
shaka.log.v2(logPrefix,
|
|
2519
2545
|
'buffer behind okay:',
|
|
2520
2546
|
'presentationTime=' + presentationTime,
|
|
2521
2547
|
'bufferedBehind=' + bufferedBehind,
|
|
2522
2548
|
'bufferBehind=' + bufferBehind,
|
|
2523
|
-
'evictionGoal=' +
|
|
2549
|
+
'evictionGoal=' + evictionGoal,
|
|
2524
2550
|
'underflow=' + Math.abs(overflow));
|
|
2525
2551
|
return;
|
|
2526
2552
|
}
|
|
@@ -2530,7 +2556,7 @@ shaka.media.StreamingEngine = class {
|
|
|
2530
2556
|
'presentationTime=' + presentationTime,
|
|
2531
2557
|
'bufferedBehind=' + bufferedBehind,
|
|
2532
2558
|
'bufferBehind=' + bufferBehind,
|
|
2533
|
-
'evictionGoal=' +
|
|
2559
|
+
'evictionGoal=' + evictionGoal,
|
|
2534
2560
|
'overflow=' + overflow);
|
|
2535
2561
|
|
|
2536
2562
|
await this.playerInterface_.mediaSourceEngine.remove(mediaState.type,
|
|
@@ -30,7 +30,7 @@ shaka.net.HttpPluginUtils = class {
|
|
|
30
30
|
static makeResponse(headers, data, status, uri, responseURL, requestType) {
|
|
31
31
|
goog.asserts.assert(data, 'Data should be non-null!');
|
|
32
32
|
|
|
33
|
-
if (status >= 200 && status <= 299 && status != 202) {
|
|
33
|
+
if ((status >= 200 && status <= 299 && status != 202) || status == 304) {
|
|
34
34
|
// Most 2xx HTTP codes are success cases.
|
|
35
35
|
/** @type {shaka.extern.Response} */
|
|
36
36
|
const response = {
|
|
@@ -667,8 +667,16 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
|
|
|
667
667
|
if (error.severity == shaka.util.Error.Severity.RECOVERABLE) {
|
|
668
668
|
const data = (new Map()).set('error', error);
|
|
669
669
|
const event = new shaka.util.FakeEvent('retry', data);
|
|
670
|
+
// A user can call preventDefault() on a cancelable event.
|
|
671
|
+
event.cancelable = true;
|
|
670
672
|
this.dispatchEvent(event);
|
|
671
673
|
|
|
674
|
+
if (event.defaultPrevented) {
|
|
675
|
+
// If the caller uses preventDefault() on the 'retry' event, don't
|
|
676
|
+
// retry any more.
|
|
677
|
+
throw error;
|
|
678
|
+
}
|
|
679
|
+
|
|
672
680
|
// Move to the next URI.
|
|
673
681
|
index = (index + 1) % request.uris.length;
|
|
674
682
|
return this.send_(
|
|
@@ -88,6 +88,7 @@ shaka.net.NetworkingUtils.EXTENSIONS_TO_MIME_TYPES_ = {
|
|
|
88
88
|
'ogg': 'audio/ogg',
|
|
89
89
|
'mpg': 'video/mpeg',
|
|
90
90
|
'mpeg': 'video/mpeg',
|
|
91
|
+
'mov': 'video/quicktime',
|
|
91
92
|
'm3u8': 'application/x-mpegurl',
|
|
92
93
|
'mpd': 'application/dash+xml',
|
|
93
94
|
'ism': 'application/vnd.ms-sstr+xml',
|
package/lib/player.js
CHANGED
|
@@ -53,6 +53,7 @@ goog.require('shaka.util.FakeEventTarget');
|
|
|
53
53
|
goog.require('shaka.util.IDestroyable');
|
|
54
54
|
goog.require('shaka.util.LanguageUtils');
|
|
55
55
|
goog.require('shaka.util.ManifestParserUtils');
|
|
56
|
+
goog.require('shaka.util.MapUtils');
|
|
56
57
|
goog.require('shaka.util.MediaReadyState');
|
|
57
58
|
goog.require('shaka.util.MimeUtils');
|
|
58
59
|
goog.require('shaka.util.Mutex');
|
|
@@ -1508,6 +1509,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1508
1509
|
let preloadManager = null;
|
|
1509
1510
|
let assetUri = '';
|
|
1510
1511
|
if (assetUriOrPreloader instanceof shaka.media.PreloadManager) {
|
|
1512
|
+
if (assetUriOrPreloader.isDestroyed()) {
|
|
1513
|
+
throw new shaka.util.Error(
|
|
1514
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1515
|
+
shaka.util.Error.Category.PLAYER,
|
|
1516
|
+
shaka.util.Error.Code.PRELOAD_DESTROYED);
|
|
1517
|
+
}
|
|
1511
1518
|
preloadManager = assetUriOrPreloader;
|
|
1512
1519
|
assetUri = preloadManager.getAssetUri() || '';
|
|
1513
1520
|
} else {
|
|
@@ -4225,7 +4232,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4225
4232
|
shaka.log.alwaysWarn('A trick play rate of 0 is unsupported!');
|
|
4226
4233
|
return;
|
|
4227
4234
|
}
|
|
4228
|
-
this.trickPlayEventManager_.removeAll();
|
|
4229
4235
|
|
|
4230
4236
|
this.playRateController_.set(rate);
|
|
4231
4237
|
|
|
@@ -4233,26 +4239,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4233
4239
|
this.abrManager_.playbackRateChanged(rate);
|
|
4234
4240
|
this.streamingEngine_.setTrickPlay(Math.abs(rate) > 1);
|
|
4235
4241
|
}
|
|
4236
|
-
|
|
4237
|
-
this.trickPlayEventManager_.listen(this.video_, 'timeupdate', () => {
|
|
4238
|
-
const currentTime = this.video_.currentTime;
|
|
4239
|
-
const seekRange = this.seekRange();
|
|
4240
|
-
const safeSeekOffset = this.config_.streaming.safeSeekOffset;
|
|
4241
|
-
|
|
4242
|
-
// Cancel trick play if we hit the beginning or end of the seekable
|
|
4243
|
-
// (Sub-second accuracy not required here)
|
|
4244
|
-
if (rate > 0) {
|
|
4245
|
-
if (Math.floor(currentTime) >= Math.floor(seekRange.end)) {
|
|
4246
|
-
this.cancelTrickPlay();
|
|
4247
|
-
}
|
|
4248
|
-
} else {
|
|
4249
|
-
if (Math.floor(currentTime) <=
|
|
4250
|
-
Math.floor(seekRange.start + safeSeekOffset)) {
|
|
4251
|
-
this.cancelTrickPlay();
|
|
4252
|
-
}
|
|
4253
|
-
}
|
|
4254
|
-
});
|
|
4255
|
-
}
|
|
4242
|
+
this.setupTrickPlayEventListeners_(rate);
|
|
4256
4243
|
}
|
|
4257
4244
|
|
|
4258
4245
|
/**
|
|
@@ -4407,7 +4394,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4407
4394
|
if (!imageStream.segmentIndex) {
|
|
4408
4395
|
await imageStream.createSegmentIndex();
|
|
4409
4396
|
}
|
|
4410
|
-
const
|
|
4397
|
+
const thumbnails = [];
|
|
4411
4398
|
imageStream.segmentIndex.forEachTopLevelReference((reference) => {
|
|
4412
4399
|
const dimensions = this.parseTilesLayout_(
|
|
4413
4400
|
reference.getTilesLayout() || imageStream.tilesLayout);
|
|
@@ -4416,12 +4403,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4416
4403
|
const duration = reference.trueEndTime - reference.startTime;
|
|
4417
4404
|
for (let i = 0; i < numThumbnails; i++) {
|
|
4418
4405
|
const sampleTime = reference.startTime + duration * i / numThumbnails;
|
|
4419
|
-
|
|
4406
|
+
const thumbnail =
|
|
4407
|
+
this.getThumbnailsByStream_(
|
|
4408
|
+
/** @type {shaka.extern.Stream} */ (imageStream), sampleTime);
|
|
4409
|
+
if (thumbnail) {
|
|
4410
|
+
thumbnails.push(thumbnail);
|
|
4411
|
+
}
|
|
4420
4412
|
}
|
|
4421
4413
|
}
|
|
4422
4414
|
});
|
|
4423
|
-
|
|
4424
|
-
|
|
4415
|
+
if (imageStream.closeSegmentIndex) {
|
|
4416
|
+
imageStream.closeSegmentIndex();
|
|
4417
|
+
}
|
|
4418
|
+
return thumbnails;
|
|
4425
4419
|
}
|
|
4426
4420
|
|
|
4427
4421
|
/**
|
|
@@ -4478,6 +4472,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4478
4472
|
if (!imageStream.segmentIndex) {
|
|
4479
4473
|
await imageStream.createSegmentIndex();
|
|
4480
4474
|
}
|
|
4475
|
+
|
|
4476
|
+
return this.getThumbnailsByStream_(imageStream, time);
|
|
4477
|
+
}
|
|
4478
|
+
|
|
4479
|
+
/**
|
|
4480
|
+
* Return a Thumbnail object from a image stream and time.
|
|
4481
|
+
*
|
|
4482
|
+
* @param {shaka.extern.Stream} imageStream
|
|
4483
|
+
* @param {number} time
|
|
4484
|
+
* @return {?shaka.extern.Thumbnail}
|
|
4485
|
+
* @private
|
|
4486
|
+
*/
|
|
4487
|
+
getThumbnailsByStream_(imageStream, time) {
|
|
4481
4488
|
const referencePosition = imageStream.segmentIndex.find(time);
|
|
4482
4489
|
if (referencePosition == null) {
|
|
4483
4490
|
return null;
|
|
@@ -6171,6 +6178,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6171
6178
|
// The playback rate has changed. This could be us or someone else.
|
|
6172
6179
|
// If this was us, setting the rate again will be a no-op.
|
|
6173
6180
|
this.playRateController_.set(newRate);
|
|
6181
|
+
|
|
6182
|
+
if (this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE) {
|
|
6183
|
+
this.abrManager_.playbackRateChanged(newRate);
|
|
6184
|
+
}
|
|
6185
|
+
this.setupTrickPlayEventListeners_(newRate);
|
|
6174
6186
|
}
|
|
6175
6187
|
|
|
6176
6188
|
const event = shaka.Player.makeEvent_(
|
|
@@ -6178,6 +6190,36 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6178
6190
|
this.dispatchEvent(event);
|
|
6179
6191
|
}
|
|
6180
6192
|
|
|
6193
|
+
/**
|
|
6194
|
+
* Configures all the necessary listeners when trick play is being performed.
|
|
6195
|
+
*
|
|
6196
|
+
* @param {number} rate
|
|
6197
|
+
* @private
|
|
6198
|
+
*/
|
|
6199
|
+
setupTrickPlayEventListeners_(rate) {
|
|
6200
|
+
this.trickPlayEventManager_.removeAll();
|
|
6201
|
+
if (this.isLive()) {
|
|
6202
|
+
this.trickPlayEventManager_.listen(this.video_, 'timeupdate', () => {
|
|
6203
|
+
const currentTime = this.video_.currentTime;
|
|
6204
|
+
const seekRange = this.seekRange();
|
|
6205
|
+
const safeSeekOffset = this.config_.streaming.safeSeekOffset;
|
|
6206
|
+
|
|
6207
|
+
// Cancel trick play if we hit the beginning or end of the seekable
|
|
6208
|
+
// (Sub-second accuracy not required here)
|
|
6209
|
+
if (rate > 0) {
|
|
6210
|
+
if (Math.floor(currentTime) >= Math.floor(seekRange.end)) {
|
|
6211
|
+
this.cancelTrickPlay();
|
|
6212
|
+
}
|
|
6213
|
+
} else {
|
|
6214
|
+
if (Math.floor(currentTime) <=
|
|
6215
|
+
Math.floor(seekRange.start + safeSeekOffset)) {
|
|
6216
|
+
this.cancelTrickPlay();
|
|
6217
|
+
}
|
|
6218
|
+
}
|
|
6219
|
+
});
|
|
6220
|
+
}
|
|
6221
|
+
}
|
|
6222
|
+
|
|
6181
6223
|
/**
|
|
6182
6224
|
* Try updating the state history. If the player has not finished
|
|
6183
6225
|
* initializing, this will be a no-op.
|
|
@@ -7255,6 +7297,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
7255
7297
|
|
|
7256
7298
|
// Only filter tracks for keys if we have some key statuses to look at.
|
|
7257
7299
|
if (keyIds.length) {
|
|
7300
|
+
const currentKeySystem = this.keySystem();
|
|
7301
|
+
const clearKeys = shaka.util.MapUtils.asMap(this.config_.drm.clearKeys);
|
|
7302
|
+
|
|
7258
7303
|
for (const variant of this.manifest_.variants) {
|
|
7259
7304
|
const streams = shaka.util.StreamUtils.getVariantStreams(variant);
|
|
7260
7305
|
|
|
@@ -7264,16 +7309,42 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
7264
7309
|
// Only update if we have key IDs for the stream. If the keys aren't
|
|
7265
7310
|
// all present, then the track should be restricted.
|
|
7266
7311
|
if (stream.keyIds.size) {
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7312
|
+
// If we are not using clearkeys, and the stream has drmInfos we
|
|
7313
|
+
// only want to check the keyIds of the keySystem we are using.
|
|
7314
|
+
// Other keySystems might have other keyIds that might not be
|
|
7315
|
+
// valid in this case. This can happen in HLS if the manifest
|
|
7316
|
+
// has Widevine with keyIds and PlayReady without keyIds and we are
|
|
7317
|
+
// using PlayReady.
|
|
7318
|
+
if (stream.drmInfos.length && !clearKeys.size) {
|
|
7319
|
+
for (const drmInfo of stream.drmInfos) {
|
|
7320
|
+
if (drmInfo.keyIds.size &&
|
|
7321
|
+
drmInfo.keySystem == currentKeySystem) {
|
|
7322
|
+
variant.allowedByKeySystem = true;
|
|
7323
|
+
|
|
7324
|
+
for (const keyId of drmInfo.keyIds) {
|
|
7325
|
+
const keyStatus =
|
|
7326
|
+
keyStatusMap[isGlobalStatus ? '00' : keyId];
|
|
7327
|
+
if (keyStatus || this.drmEngine_.hasManifestInitData()) {
|
|
7328
|
+
variant.allowedByKeySystem =
|
|
7329
|
+
variant.allowedByKeySystem &&
|
|
7330
|
+
!!keyStatus &&
|
|
7331
|
+
!restrictedStatuses.includes(keyStatus);
|
|
7332
|
+
} // if (keyStatus || this.drmEngine_.hasManifestInitData())
|
|
7333
|
+
} // for (const keyId of drmInfo.keyIds)
|
|
7334
|
+
} // if (drmInfo.keyIds.size && ...
|
|
7335
|
+
} // for (const drmInfo of stream.drmInfos
|
|
7336
|
+
} else {
|
|
7337
|
+
variant.allowedByKeySystem = true;
|
|
7338
|
+
|
|
7339
|
+
for (const keyId of stream.keyIds) {
|
|
7340
|
+
const keyStatus = keyStatusMap[isGlobalStatus ? '00' : keyId];
|
|
7341
|
+
if (keyStatus || this.drmEngine_.hasManifestInitData()) {
|
|
7342
|
+
variant.allowedByKeySystem = variant.allowedByKeySystem &&
|
|
7343
|
+
!!keyStatus && !restrictedStatuses.includes(keyStatus);
|
|
7344
|
+
}
|
|
7345
|
+
} // for (const keyId of stream.keyIds)
|
|
7346
|
+
} // if (stream.drmInfos.length && !clearKeys.size)
|
|
7347
|
+
} // if (stream.keyIds.size)
|
|
7277
7348
|
|
|
7278
7349
|
if (originalAllowed != variant.allowedByKeySystem) {
|
|
7279
7350
|
tracksChanged = true;
|
|
@@ -7542,7 +7613,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
7542
7613
|
if (!this.video_ || this.video_.ended) {
|
|
7543
7614
|
return true;
|
|
7544
7615
|
}
|
|
7545
|
-
return
|
|
7616
|
+
return this.fullyLoaded_ && !this.isLive() &&
|
|
7617
|
+
this.video_.currentTime >= this.seekRange().end;
|
|
7546
7618
|
}
|
|
7547
7619
|
};
|
|
7548
7620
|
|
|
@@ -7585,7 +7657,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
7585
7657
|
* @export
|
|
7586
7658
|
*/
|
|
7587
7659
|
// eslint-disable-next-line no-useless-concat, max-len
|
|
7588
|
-
shaka.Player.version = 'v4.9.
|
|
7660
|
+
shaka.Player.version = 'v4.9.36' + '-uncompiled'; // x-release-please-version
|
|
7589
7661
|
|
|
7590
7662
|
// Initialize the deprecation system using the version string we just set
|
|
7591
7663
|
// on the player.
|