shaka-player 4.9.28 → 4.9.30
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/README.md +2 -4
- package/dist/cast-boot.js +7 -0
- package/dist/deps.js +4 -4
- package/dist/locales.js +34 -34
- package/dist/shaka-player.compiled.debug.js +79 -76
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.js +699 -694
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.debug.js +97 -94
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.js +445 -440
- package/dist/shaka-player.ui.map +3 -3
- package/lib/dash/dash_parser.js +39 -11
- package/lib/dash/segment_template.js +1 -1
- package/lib/media/drm_engine.js +11 -0
- package/lib/media/media_source_engine.js +19 -30
- package/lib/media/playhead.js +3 -1
- package/lib/media/preload_manager.js +17 -0
- package/lib/media/presentation_timeline.js +7 -1
- package/lib/media/streaming_engine.js +43 -12
- package/lib/player.js +40 -32
- package/lib/polyfill/encryption_scheme.js +9 -0
- package/lib/polyfill/media_capabilities.js +8 -2
- package/lib/util/periods.js +3 -0
- package/lib/util/platform.js +12 -3
- package/lib/util/player_configuration.js +3 -1
- package/package.json +1 -1
- package/ui/externs/ui.js +2 -1
- package/ui/locales/zh-HK.json +30 -11
- package/ui/remote_button.js +3 -2
- package/ui/resolution_selection.js +4 -1
- package/ui/ui.js +6 -3
package/lib/dash/dash_parser.js
CHANGED
|
@@ -1285,11 +1285,13 @@ shaka.dash.DashParser = class {
|
|
|
1285
1285
|
for (let i = 0; i < periodNodes.length; i++) {
|
|
1286
1286
|
const elem = periodNodes[i];
|
|
1287
1287
|
const next = periodNodes[i + 1];
|
|
1288
|
-
|
|
1288
|
+
let start = /** @type {number} */ (
|
|
1289
1289
|
TXml.parseAttr(elem, 'start', TXml.parseDuration, prevEnd));
|
|
1290
1290
|
const periodId = elem.attributes['id'];
|
|
1291
1291
|
const givenDuration =
|
|
1292
1292
|
TXml.parseAttr(elem, 'duration', TXml.parseDuration);
|
|
1293
|
+
start = (i == 0 && start == 0 && this.isTransitionFromDynamicToStatic_) ?
|
|
1294
|
+
seekRangeStart : start;
|
|
1293
1295
|
|
|
1294
1296
|
let periodDuration = null;
|
|
1295
1297
|
if (next) {
|
|
@@ -1299,7 +1301,7 @@ shaka.dash.DashParser = class {
|
|
|
1299
1301
|
const nextStart =
|
|
1300
1302
|
TXml.parseAttr(next, 'start', TXml.parseDuration);
|
|
1301
1303
|
if (nextStart != null) {
|
|
1302
|
-
periodDuration = nextStart - start;
|
|
1304
|
+
periodDuration = nextStart - start + seekRangeStart;
|
|
1303
1305
|
}
|
|
1304
1306
|
} else if (presentationDuration != null) {
|
|
1305
1307
|
// "The Period extends until the Period.start of the next Period, or
|
|
@@ -2575,25 +2577,51 @@ shaka.dash.DashParser = class {
|
|
|
2575
2577
|
return intValue;
|
|
2576
2578
|
}
|
|
2577
2579
|
|
|
2580
|
+
case 'tag:dolby.com,2015:dash:audio_channel_configuration:2015': {
|
|
2581
|
+
// ETSI TS 103 190-2 v1.2.1, Annex G.3
|
|
2582
|
+
// LSB-to-MSB order
|
|
2583
|
+
const channelCountMapping =
|
|
2584
|
+
[2, 1, 2, 2, 2, 2, 1, 2, 2, 1, 1, 1, 1, 2, 1, 1, 2, 2];
|
|
2585
|
+
const hexValue = parseInt(value, 16);
|
|
2586
|
+
if (!hexValue) { // 0 or NaN
|
|
2587
|
+
shaka.log.warning('Channel parsing failure! ' +
|
|
2588
|
+
'Ignoring scheme and value', scheme, value);
|
|
2589
|
+
continue;
|
|
2590
|
+
}
|
|
2591
|
+
let numBits = 0;
|
|
2592
|
+
for (let i = 0; i < channelCountMapping.length; i++) {
|
|
2593
|
+
if (hexValue & (1<<i)) {
|
|
2594
|
+
numBits += channelCountMapping[i];
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
if (numBits) {
|
|
2598
|
+
return numBits;
|
|
2599
|
+
}
|
|
2600
|
+
continue;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2578
2603
|
case 'tag:dolby.com,2014:dash:audio_channel_configuration:2011':
|
|
2579
2604
|
case 'urn:dolby:dash:audio_channel_configuration:2011': {
|
|
2580
|
-
//
|
|
2581
|
-
//
|
|
2582
|
-
|
|
2605
|
+
// Defined by https://ott.dolby.com/OnDelKits/DDP/Dolby_Digital_Plus_Online_Delivery_Kit_v1.5/Documentation/Content_Creation/SDM/help_files/topics/ddp_mpeg_dash_c_mpd_auchlconfig.html
|
|
2606
|
+
// keep list in order of the spec; reverse for LSB-to-MSB order
|
|
2607
|
+
const channelCountMapping =
|
|
2608
|
+
[1, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 2, 1, 2, 1, 1].reverse();
|
|
2609
|
+
const hexValue = parseInt(value, 16);
|
|
2583
2610
|
if (!hexValue) { // 0 or NaN
|
|
2584
2611
|
shaka.log.warning('Channel parsing failure! ' +
|
|
2585
2612
|
'Ignoring scheme and value', scheme, value);
|
|
2586
2613
|
continue;
|
|
2587
2614
|
}
|
|
2588
|
-
// Count the 1-bits in hexValue.
|
|
2589
2615
|
let numBits = 0;
|
|
2590
|
-
|
|
2591
|
-
if (hexValue & 1) {
|
|
2592
|
-
|
|
2616
|
+
for (let i = 0; i < channelCountMapping.length; i++) {
|
|
2617
|
+
if (hexValue & (1<<i)) {
|
|
2618
|
+
numBits += channelCountMapping[i];
|
|
2593
2619
|
}
|
|
2594
|
-
hexValue >>= 1;
|
|
2595
2620
|
}
|
|
2596
|
-
|
|
2621
|
+
if (numBits) {
|
|
2622
|
+
return numBits;
|
|
2623
|
+
}
|
|
2624
|
+
continue;
|
|
2597
2625
|
}
|
|
2598
2626
|
|
|
2599
2627
|
// Defined by https://dashif.org/identifiers/audio_source_metadata/ and clause 8.2, in ISO/IEC 23001-8.
|
|
@@ -778,7 +778,7 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
|
|
|
778
778
|
if (currentTimeline.length) {
|
|
779
779
|
const lastCurrentEntry = currentTimeline[currentTimeline.length - 1];
|
|
780
780
|
newEntries = info.timeline.filter((entry) => {
|
|
781
|
-
return entry.
|
|
781
|
+
return entry.end > lastCurrentEntry.end;
|
|
782
782
|
});
|
|
783
783
|
} else {
|
|
784
784
|
newEntries = info.timeline.slice();
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -743,6 +743,17 @@ shaka.media.DrmEngine = class {
|
|
|
743
743
|
return;
|
|
744
744
|
}
|
|
745
745
|
}
|
|
746
|
+
let duplicate = false;
|
|
747
|
+
this.storedPersistentSessions_.forEach((metadata, sessionId) => {
|
|
748
|
+
if (!duplicate &&
|
|
749
|
+
shaka.util.BufferUtils.equal(initData, metadata.initData)) {
|
|
750
|
+
duplicate = true;
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
if (duplicate) {
|
|
754
|
+
shaka.log.debug('Ignoring duplicate init data.');
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
746
757
|
}
|
|
747
758
|
|
|
748
759
|
// If there are pre-existing sessions that have all been loaded
|
|
@@ -116,7 +116,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
116
116
|
/** @private {boolean} */
|
|
117
117
|
this.playbackHasBegun_ = false;
|
|
118
118
|
|
|
119
|
-
/** @private {
|
|
119
|
+
/** @private {MediaSource} */
|
|
120
120
|
this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
|
|
121
121
|
|
|
122
122
|
/** @private {boolean} */
|
|
@@ -175,13 +175,16 @@ shaka.media.MediaSourceEngine = class {
|
|
|
175
175
|
* Replaced by unit tests.
|
|
176
176
|
*
|
|
177
177
|
* @param {!shaka.util.PublicPromise} p
|
|
178
|
-
* @return {!
|
|
178
|
+
* @return {!MediaSource}
|
|
179
179
|
*/
|
|
180
180
|
createMediaSource(p) {
|
|
181
|
+
/** @type {!MediaSource} */
|
|
182
|
+
let mediaSource;
|
|
183
|
+
|
|
181
184
|
if (window.ManagedMediaSource) {
|
|
182
185
|
this.video_.disableRemotePlayback = true;
|
|
183
186
|
|
|
184
|
-
|
|
187
|
+
mediaSource = new ManagedMediaSource();
|
|
185
188
|
|
|
186
189
|
this.eventManager_.listen(
|
|
187
190
|
mediaSource, 'startstreaming', () => {
|
|
@@ -192,39 +195,25 @@ shaka.media.MediaSourceEngine = class {
|
|
|
192
195
|
mediaSource, 'endstreaming', () => {
|
|
193
196
|
this.streamingAllowed_ = false;
|
|
194
197
|
});
|
|
195
|
-
|
|
196
|
-
this.eventManager_.listenOnce(
|
|
197
|
-
mediaSource, 'sourceopen', () => this.onSourceOpen_(p));
|
|
198
|
-
|
|
199
|
-
// Correctly set when playback has begun.
|
|
200
|
-
this.eventManager_.listenOnce(this.video_, 'playing', () => {
|
|
201
|
-
this.playbackHasBegun_ = true;
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
this.url_ = shaka.media.MediaSourceEngine.createObjectURL(mediaSource);
|
|
205
|
-
|
|
206
|
-
this.video_.src = this.url_;
|
|
207
|
-
|
|
208
|
-
return mediaSource;
|
|
209
198
|
} else {
|
|
210
|
-
|
|
199
|
+
mediaSource = new MediaSource();
|
|
200
|
+
}
|
|
211
201
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
202
|
+
// Set up MediaSource on the video element.
|
|
203
|
+
this.eventManager_.listenOnce(
|
|
204
|
+
mediaSource, 'sourceopen', () => this.onSourceOpen_(p));
|
|
215
205
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
206
|
+
// Correctly set when playback has begun.
|
|
207
|
+
this.eventManager_.listenOnce(this.video_, 'playing', () => {
|
|
208
|
+
this.playbackHasBegun_ = true;
|
|
209
|
+
});
|
|
220
210
|
|
|
221
|
-
|
|
222
|
-
|
|
211
|
+
// Store the object URL for releasing it later.
|
|
212
|
+
this.url_ = shaka.media.MediaSourceEngine.createObjectURL(mediaSource);
|
|
223
213
|
|
|
224
|
-
|
|
214
|
+
this.video_.src = this.url_;
|
|
225
215
|
|
|
226
|
-
|
|
227
|
-
}
|
|
216
|
+
return mediaSource;
|
|
228
217
|
}
|
|
229
218
|
|
|
230
219
|
/**
|
package/lib/media/playhead.js
CHANGED
|
@@ -427,7 +427,9 @@ shaka.media.MediaSourcePlayhead = class {
|
|
|
427
427
|
// infinite loop on systems where changing currentTime takes a
|
|
428
428
|
// significant amount of time (e.g. Chromecast).
|
|
429
429
|
const time = Date.now() / 1000;
|
|
430
|
-
|
|
430
|
+
const seekDelay = shaka.util.Platform.isFuchsiaCastDevice() ? 3 : 1;
|
|
431
|
+
if (!this.lastCorrectiveSeek_ ||
|
|
432
|
+
this.lastCorrectiveSeek_ < time - seekDelay) {
|
|
431
433
|
this.lastCorrectiveSeek_ = time;
|
|
432
434
|
canCorrectiveSeek = true;
|
|
433
435
|
}
|
|
@@ -131,6 +131,9 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
|
|
|
131
131
|
/** @private {!shaka.util.Stats} */
|
|
132
132
|
this.stats_ = new shaka.util.Stats();
|
|
133
133
|
|
|
134
|
+
/** @private {!shaka.util.PublicPromise} */
|
|
135
|
+
this.manifestPromise_ = new shaka.util.PublicPromise();
|
|
136
|
+
|
|
134
137
|
/** @private {!shaka.util.PublicPromise} */
|
|
135
138
|
this.successPromise_ = new shaka.util.PublicPromise();
|
|
136
139
|
|
|
@@ -383,6 +386,8 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
|
|
|
383
386
|
await this.parseManifestInner_();
|
|
384
387
|
this.throwIfDestroyed_();
|
|
385
388
|
|
|
389
|
+
this.manifestPromise_.resolve();
|
|
390
|
+
|
|
386
391
|
await this.initializeDrmInner_();
|
|
387
392
|
this.throwIfDestroyed_();
|
|
388
393
|
|
|
@@ -703,6 +708,18 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
|
|
|
703
708
|
return this.successPromise_;
|
|
704
709
|
}
|
|
705
710
|
|
|
711
|
+
/**
|
|
712
|
+
* Waits for the manifest to be loaded (or to fail with an error).
|
|
713
|
+
* @return {!Promise}
|
|
714
|
+
*/
|
|
715
|
+
waitForManifest() {
|
|
716
|
+
const promises = [
|
|
717
|
+
this.manifestPromise_,
|
|
718
|
+
this.successPromise_,
|
|
719
|
+
];
|
|
720
|
+
return Promise.race(promises);
|
|
721
|
+
}
|
|
722
|
+
|
|
706
723
|
/**
|
|
707
724
|
* Releases or stops all non-entrusted resources.
|
|
708
725
|
*
|
|
@@ -78,6 +78,9 @@ shaka.media.PresentationTimeline = class {
|
|
|
78
78
|
/** @private {boolean} */
|
|
79
79
|
this.static_ = true;
|
|
80
80
|
|
|
81
|
+
/** @private {boolean} */
|
|
82
|
+
this.isLive2VodTransition_ = false;
|
|
83
|
+
|
|
81
84
|
/** @private {number} */
|
|
82
85
|
this.userSeekStart_ = 0;
|
|
83
86
|
|
|
@@ -179,6 +182,9 @@ shaka.media.PresentationTimeline = class {
|
|
|
179
182
|
*/
|
|
180
183
|
setStatic(isStatic) {
|
|
181
184
|
// NOTE: the argument name is not "static" because that's a keyword in ES6
|
|
185
|
+
if (isStatic && !this.static_) {
|
|
186
|
+
this.isLive2VodTransition_ = true;
|
|
187
|
+
}
|
|
182
188
|
this.static_ = isStatic;
|
|
183
189
|
}
|
|
184
190
|
|
|
@@ -384,7 +390,7 @@ shaka.media.PresentationTimeline = class {
|
|
|
384
390
|
// No data yet, and Math.min(null, startTime) is always 0. So just store
|
|
385
391
|
// startTime.
|
|
386
392
|
this.minSegmentStartTime_ = startTime;
|
|
387
|
-
} else {
|
|
393
|
+
} else if (!this.isLive2VodTransition_) {
|
|
388
394
|
this.minSegmentStartTime_ =
|
|
389
395
|
Math.min(this.minSegmentStartTime_, startTime);
|
|
390
396
|
}
|
|
@@ -82,7 +82,7 @@ shaka.media.StreamingEngine = class {
|
|
|
82
82
|
this.deferredCloseSegmentIndex_ = new Map();
|
|
83
83
|
|
|
84
84
|
/** @private {number} */
|
|
85
|
-
this.
|
|
85
|
+
this.bufferingScale_ = 1;
|
|
86
86
|
|
|
87
87
|
/** @private {?shaka.extern.Variant} */
|
|
88
88
|
this.currentVariant_ = null;
|
|
@@ -148,6 +148,9 @@ shaka.media.StreamingEngine = class {
|
|
|
148
148
|
|
|
149
149
|
/** @private {number} */
|
|
150
150
|
this.playRangeEnd_ = Infinity;
|
|
151
|
+
|
|
152
|
+
/** @private {?shaka.media.StreamingEngine.MediaState_} */
|
|
153
|
+
this.lastTextMediaStateBeforeUnload_ = null;
|
|
151
154
|
}
|
|
152
155
|
|
|
153
156
|
/** @override */
|
|
@@ -380,6 +383,8 @@ shaka.media.StreamingEngine = class {
|
|
|
380
383
|
if (state) {
|
|
381
384
|
this.cancelUpdate_(state);
|
|
382
385
|
this.abortOperations_(state).catch(() => {});
|
|
386
|
+
this.lastTextMediaStateBeforeUnload_ =
|
|
387
|
+
this.mediaStates_.get(ContentType.TEXT);
|
|
383
388
|
this.mediaStates_.delete(ContentType.TEXT);
|
|
384
389
|
}
|
|
385
390
|
this.currentTextStream_ = null;
|
|
@@ -476,6 +481,7 @@ shaka.media.StreamingEngine = class {
|
|
|
476
481
|
* @param {shaka.extern.Stream} textStream
|
|
477
482
|
*/
|
|
478
483
|
async switchTextStream(textStream) {
|
|
484
|
+
this.lastTextMediaStateBeforeUnload_ = null;
|
|
479
485
|
this.currentTextStream_ = textStream;
|
|
480
486
|
|
|
481
487
|
if (!this.startupComplete_) {
|
|
@@ -1352,7 +1358,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1352
1358
|
this.config_.bufferingGoal);
|
|
1353
1359
|
|
|
1354
1360
|
const scaledBufferingGoal = Math.max(1,
|
|
1355
|
-
unscaledBufferingGoal * this.
|
|
1361
|
+
unscaledBufferingGoal * this.bufferingScale_);
|
|
1356
1362
|
|
|
1357
1363
|
// Check if we've buffered to the end of the presentation.
|
|
1358
1364
|
const timeUntilEnd =
|
|
@@ -1793,7 +1799,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1793
1799
|
}
|
|
1794
1800
|
this.mediaStates_.delete(ContentType.TEXT);
|
|
1795
1801
|
} else if (error.code == shaka.util.Error.Code.QUOTA_EXCEEDED_ERROR) {
|
|
1796
|
-
this.handleQuotaExceeded_(mediaState, error);
|
|
1802
|
+
await this.handleQuotaExceeded_(mediaState, error);
|
|
1797
1803
|
} else {
|
|
1798
1804
|
shaka.log.error(logPrefix, 'failed fetch and append: code=' +
|
|
1799
1805
|
error.code);
|
|
@@ -1906,9 +1912,10 @@ shaka.media.StreamingEngine = class {
|
|
|
1906
1912
|
*
|
|
1907
1913
|
* @param {shaka.media.StreamingEngine.MediaState_} mediaState
|
|
1908
1914
|
* @param {!shaka.util.Error} error
|
|
1915
|
+
* @return {!Promise}
|
|
1909
1916
|
* @private
|
|
1910
1917
|
*/
|
|
1911
|
-
handleQuotaExceeded_(mediaState, error) {
|
|
1918
|
+
async handleQuotaExceeded_(mediaState, error) {
|
|
1912
1919
|
const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
|
|
1913
1920
|
|
|
1914
1921
|
// The segment cannot fit into the SourceBuffer. Ideally, MediaSource would
|
|
@@ -1930,18 +1937,22 @@ shaka.media.StreamingEngine = class {
|
|
|
1930
1937
|
});
|
|
1931
1938
|
|
|
1932
1939
|
if (!waitingForAnotherStreamToRecover) {
|
|
1940
|
+
const maxDisabledTime = this.getDisabledTime_(error);
|
|
1941
|
+
if (maxDisabledTime) {
|
|
1942
|
+
shaka.log.debug(logPrefix, 'Disabling stream due to quota', error);
|
|
1943
|
+
}
|
|
1933
1944
|
const handled = this.playerInterface_.disableStream(
|
|
1934
|
-
mediaState.stream,
|
|
1945
|
+
mediaState.stream, maxDisabledTime);
|
|
1935
1946
|
if (handled) {
|
|
1936
1947
|
return;
|
|
1937
1948
|
}
|
|
1938
1949
|
// Reduction schedule: 80%, 60%, 40%, 20%, 16%, 12%, 8%, 4%, fail.
|
|
1939
1950
|
// Note: percentages are used for comparisons to avoid rounding errors.
|
|
1940
|
-
const percentBefore = Math.round(100 * this.
|
|
1951
|
+
const percentBefore = Math.round(100 * this.bufferingScale_);
|
|
1941
1952
|
if (percentBefore > 20) {
|
|
1942
|
-
this.
|
|
1953
|
+
this.bufferingScale_ -= 0.2;
|
|
1943
1954
|
} else if (percentBefore > 4) {
|
|
1944
|
-
this.
|
|
1955
|
+
this.bufferingScale_ -= 0.04;
|
|
1945
1956
|
} else {
|
|
1946
1957
|
shaka.log.error(
|
|
1947
1958
|
logPrefix, 'MediaSource threw QuotaExceededError too many times');
|
|
@@ -1950,12 +1961,14 @@ shaka.media.StreamingEngine = class {
|
|
|
1950
1961
|
this.playerInterface_.onError(error);
|
|
1951
1962
|
return;
|
|
1952
1963
|
}
|
|
1953
|
-
const percentAfter = Math.round(100 * this.
|
|
1964
|
+
const percentAfter = Math.round(100 * this.bufferingScale_);
|
|
1954
1965
|
shaka.log.warning(
|
|
1955
1966
|
logPrefix,
|
|
1956
1967
|
'MediaSource threw QuotaExceededError:',
|
|
1957
1968
|
'reducing buffering goals by ' + (100 - percentAfter) + '%');
|
|
1958
1969
|
mediaState.recovering = true;
|
|
1970
|
+
const presentationTime = this.playerInterface_.getPresentationTime();
|
|
1971
|
+
await this.evict_(mediaState, presentationTime);
|
|
1959
1972
|
} else {
|
|
1960
1973
|
shaka.log.debug(
|
|
1961
1974
|
logPrefix,
|
|
@@ -2484,12 +2497,16 @@ shaka.media.StreamingEngine = class {
|
|
|
2484
2497
|
// Use the max segment duration, if it is longer than the bufferBehind, to
|
|
2485
2498
|
// avoid accidentally clearing too much data when dealing with a manifest
|
|
2486
2499
|
// with a long keyframe interval.
|
|
2487
|
-
const bufferBehind = Math.max(
|
|
2500
|
+
const bufferBehind = Math.max(
|
|
2501
|
+
this.config_.bufferBehind * this.bufferingScale_,
|
|
2488
2502
|
this.manifest_.presentationTimeline.getMaxSegmentDuration());
|
|
2489
2503
|
|
|
2490
2504
|
const startTime =
|
|
2491
2505
|
this.playerInterface_.mediaSourceEngine.bufferStart(mediaState.type);
|
|
2492
2506
|
if (startTime == null) {
|
|
2507
|
+
if (this.lastTextMediaStateBeforeUnload_ == mediaState) {
|
|
2508
|
+
this.lastTextMediaStateBeforeUnload_ = null;
|
|
2509
|
+
}
|
|
2493
2510
|
shaka.log.v2(logPrefix,
|
|
2494
2511
|
'buffer behind okay because nothing buffered:',
|
|
2495
2512
|
'presentationTime=' + presentationTime,
|
|
@@ -2524,6 +2541,11 @@ shaka.media.StreamingEngine = class {
|
|
|
2524
2541
|
|
|
2525
2542
|
this.destroyer_.ensureNotDestroyed();
|
|
2526
2543
|
shaka.log.v1(logPrefix, 'evicted ' + overflow + ' seconds');
|
|
2544
|
+
|
|
2545
|
+
if (this.lastTextMediaStateBeforeUnload_) {
|
|
2546
|
+
await this.evict_(this.lastTextMediaStateBeforeUnload_, presentationTime);
|
|
2547
|
+
this.destroyer_.ensureNotDestroyed();
|
|
2548
|
+
}
|
|
2527
2549
|
}
|
|
2528
2550
|
|
|
2529
2551
|
|
|
@@ -2768,18 +2790,26 @@ shaka.media.StreamingEngine = class {
|
|
|
2768
2790
|
* @private
|
|
2769
2791
|
*/
|
|
2770
2792
|
async handleStreamingError_(mediaState, error) {
|
|
2793
|
+
const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
|
|
2794
|
+
|
|
2771
2795
|
// If we invoke the callback right away, the application could trigger a
|
|
2772
2796
|
// rapid retry cycle that could be very unkind to the server. Instead,
|
|
2773
2797
|
// use the backoff system to delay and backoff the error handling.
|
|
2774
2798
|
await this.failureCallbackBackoff_.attempt();
|
|
2775
2799
|
this.destroyer_.ensureNotDestroyed();
|
|
2776
|
-
|
|
2777
|
-
|
|
2800
|
+
|
|
2801
|
+
// Try to recover from network errors, but not timeouts.
|
|
2802
|
+
// See https://github.com/shaka-project/shaka-player/issues/7368
|
|
2803
|
+
if (error.category === shaka.util.Error.Category.NETWORK &&
|
|
2804
|
+
error.code != shaka.util.Error.Code.TIMEOUT) {
|
|
2778
2805
|
if (mediaState.restoreStreamAfterTrickPlay) {
|
|
2779
2806
|
this.setTrickPlay(/* on= */ false);
|
|
2780
2807
|
return;
|
|
2781
2808
|
}
|
|
2782
2809
|
const maxDisabledTime = this.getDisabledTime_(error);
|
|
2810
|
+
if (maxDisabledTime) {
|
|
2811
|
+
shaka.log.debug(logPrefix, 'Disabling stream due to error', error);
|
|
2812
|
+
}
|
|
2783
2813
|
error.handled = this.playerInterface_.disableStream(
|
|
2784
2814
|
mediaState.stream, maxDisabledTime);
|
|
2785
2815
|
|
|
@@ -2804,6 +2834,7 @@ shaka.media.StreamingEngine = class {
|
|
|
2804
2834
|
|
|
2805
2835
|
/**
|
|
2806
2836
|
* @param {!shaka.util.Error} error
|
|
2837
|
+
* @return {number}
|
|
2807
2838
|
* @private
|
|
2808
2839
|
*/
|
|
2809
2840
|
getDisabledTime_(error) {
|
package/lib/player.js
CHANGED
|
@@ -1406,13 +1406,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1406
1406
|
//
|
|
1407
1407
|
// DrmEngine requires this to be done before we destroy DrmEngine itself.
|
|
1408
1408
|
if (this.video_ && this.video_.src) {
|
|
1409
|
-
// TODO: Investigate this more. Only reproduces on Firefox 69.
|
|
1410
|
-
// Introduce a delay before detaching the video source. We are seeing
|
|
1411
|
-
// spurious Promise rejections involving an AbortError in our tests
|
|
1412
|
-
// otherwise.
|
|
1413
|
-
await new Promise(
|
|
1414
|
-
(resolve) => new shaka.util.Timer(resolve).tickAfter(0.1));
|
|
1415
|
-
|
|
1416
1409
|
this.video_.removeAttribute('src');
|
|
1417
1410
|
this.video_.load();
|
|
1418
1411
|
}
|
|
@@ -1646,6 +1639,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1646
1639
|
await this.srcEqualsInner_(startTimeOfLoad, mimeType);
|
|
1647
1640
|
}, 'srcEqualsInner_');
|
|
1648
1641
|
} else {
|
|
1642
|
+
// Wait for the manifest to be parsed.
|
|
1643
|
+
await mutexWrapOperation(async () => {
|
|
1644
|
+
await preloadManager.waitForManifest();
|
|
1645
|
+
}, 'waitForFinish');
|
|
1646
|
+
|
|
1647
|
+
// Retrieve the manifest. This is specifically put before the media
|
|
1648
|
+
// source engine is initialized, for the benefit of event handlers.
|
|
1649
|
+
this.parserFactory_ = preloadManager.getParserFactory();
|
|
1650
|
+
this.parser_ = preloadManager.receiveParser();
|
|
1651
|
+
this.manifest_ = preloadManager.getManifest();
|
|
1652
|
+
|
|
1649
1653
|
if (!this.mediaSourceEngine_) {
|
|
1650
1654
|
await mutexWrapOperation(async () => {
|
|
1651
1655
|
await this.initializeMediaSourceEngineInner_();
|
|
@@ -1660,11 +1664,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1660
1664
|
// Get manifest and associated values from preloader.
|
|
1661
1665
|
this.config_ = preloadManager.getConfiguration();
|
|
1662
1666
|
this.manifestFilterer_ = preloadManager.getManifestFilterer();
|
|
1663
|
-
this.parserFactory_ = preloadManager.getParserFactory();
|
|
1664
|
-
this.parser_ = preloadManager.receiveParser();
|
|
1665
1667
|
this.regionTimeline_ = preloadManager.receiveRegionTimeline();
|
|
1666
1668
|
this.qualityObserver_ = preloadManager.getQualityObserver();
|
|
1667
|
-
this.manifest_ = preloadManager.getManifest();
|
|
1668
1669
|
const currentAdaptationSetCriteria =
|
|
1669
1670
|
preloadManager.getCurrentAdaptationSetCriteria();
|
|
1670
1671
|
if (currentAdaptationSetCriteria) {
|
|
@@ -2802,6 +2803,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2802
2803
|
}
|
|
2803
2804
|
}
|
|
2804
2805
|
mediaElement.src = playbackUri;
|
|
2806
|
+
// If ManagedMediaSource exists, we've disabled remote playback on our own.
|
|
2807
|
+
// Reenable it so AirPlay can be used via RemotePlayback API.
|
|
2808
|
+
if (this.mediaSourceEngine_ && window.ManagedMediaSource) {
|
|
2809
|
+
mediaElement.disableRemotePlayback = false;
|
|
2810
|
+
}
|
|
2805
2811
|
|
|
2806
2812
|
// Tizen 3 / WebOS won't load anything unless you call load() explicitly,
|
|
2807
2813
|
// no matter the value of the preload attribute. This is harmful on some
|
|
@@ -6589,6 +6595,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6589
6595
|
oldTrack = shaka.util.StreamUtils.variantToTrack(currentVariant);
|
|
6590
6596
|
}
|
|
6591
6597
|
const newTrack = shaka.util.StreamUtils.variantToTrack(variant);
|
|
6598
|
+
newTrack.active = true;
|
|
6592
6599
|
|
|
6593
6600
|
if (fromAdaptation) {
|
|
6594
6601
|
// Dispatch an 'adaptation' event
|
|
@@ -6740,27 +6747,28 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6740
6747
|
* @private
|
|
6741
6748
|
*/
|
|
6742
6749
|
onSegmentAppended_(start, end, contentType, isMuxed) {
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6750
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
6751
|
+
if (contentType != ContentType.TEXT) {
|
|
6752
|
+
// When we append a segment to media source (via streaming engine) we are
|
|
6753
|
+
// changing what data we have buffered, so notify the playhead of the
|
|
6754
|
+
// change.
|
|
6755
|
+
if (this.playhead_) {
|
|
6756
|
+
this.playhead_.notifyOfBufferingChange();
|
|
6757
|
+
// Skip the initial buffer gap
|
|
6758
|
+
const startTime = this.mediaSourceEngine_.bufferStart(contentType);
|
|
6759
|
+
if (
|
|
6760
|
+
!this.isLive() &&
|
|
6761
|
+
// If not paused then GapJumpingController will handle this gap.
|
|
6762
|
+
this.video_.paused &&
|
|
6763
|
+
startTime != null &&
|
|
6764
|
+
startTime > 0 &&
|
|
6765
|
+
this.playhead_.getTime() < startTime
|
|
6766
|
+
) {
|
|
6767
|
+
this.playhead_.setStartTime(startTime);
|
|
6768
|
+
}
|
|
6769
|
+
}
|
|
6770
|
+
this.pollBufferState_();
|
|
6771
|
+
}
|
|
6764
6772
|
|
|
6765
6773
|
// Dispatch an event for users to consume, too.
|
|
6766
6774
|
const data = new Map()
|
|
@@ -7527,7 +7535,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
7527
7535
|
* @export
|
|
7528
7536
|
*/
|
|
7529
7537
|
// eslint-disable-next-line no-useless-concat, max-len
|
|
7530
|
-
shaka.Player.version = 'v4.9.
|
|
7538
|
+
shaka.Player.version = 'v4.9.30' + '-uncompiled'; // x-release-please-version
|
|
7531
7539
|
|
|
7532
7540
|
// Initialize the deprecation system using the version string we just set
|
|
7533
7541
|
// on the player.
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
goog.provide('shaka.polyfill.EncryptionScheme');
|
|
8
8
|
|
|
9
9
|
goog.require('shaka.polyfill');
|
|
10
|
+
goog.require('shaka.util.Platform');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* @summary A polyfill to add support for EncryptionScheme queries in EME.
|
|
@@ -23,6 +24,14 @@ shaka.polyfill.EncryptionScheme = class {
|
|
|
23
24
|
* @export
|
|
24
25
|
*/
|
|
25
26
|
static install() {
|
|
27
|
+
// Skip polyfill for PlayStation 4 and SkyQ devices due to known crashes
|
|
28
|
+
// caused by unsupported encryptionScheme handling. These platforms do not
|
|
29
|
+
// require the polyfill, and forcing encryptionScheme processing can result
|
|
30
|
+
// in playback crashes.
|
|
31
|
+
if (shaka.util.Platform.isPS4() || shaka.util.Platform.isSkyQ()) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
EncryptionSchemePolyfills.install();
|
|
27
36
|
}
|
|
28
37
|
};
|
|
@@ -10,6 +10,7 @@ goog.require('shaka.log');
|
|
|
10
10
|
goog.require('shaka.media.Capabilities');
|
|
11
11
|
goog.require('shaka.polyfill');
|
|
12
12
|
goog.require('shaka.util.DrmUtils');
|
|
13
|
+
goog.require('shaka.util.MimeUtils');
|
|
13
14
|
goog.require('shaka.util.Platform');
|
|
14
15
|
|
|
15
16
|
|
|
@@ -222,6 +223,7 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
222
223
|
* @private
|
|
223
224
|
*/
|
|
224
225
|
static async checkDrmSupport_(videoConfig, audioConfig, mcapKeySystemConfig) {
|
|
226
|
+
const MimeUtils = shaka.util.MimeUtils;
|
|
225
227
|
const audioCapabilities = [];
|
|
226
228
|
const videoCapabilities = [];
|
|
227
229
|
|
|
@@ -277,8 +279,12 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
277
279
|
mediaKeySystemConfig.videoCapabilities = videoCapabilities;
|
|
278
280
|
}
|
|
279
281
|
|
|
280
|
-
const
|
|
281
|
-
const
|
|
282
|
+
const videoMimeType = videoConfig ? videoConfig.contentType : '';
|
|
283
|
+
const audioMimeType = audioConfig ? audioConfig.contentType : '';
|
|
284
|
+
const videoCodec = MimeUtils.getBasicType(videoMimeType) + ';' +
|
|
285
|
+
MimeUtils.getCodecBase(videoMimeType);
|
|
286
|
+
const audioCodec = MimeUtils.getBasicType(audioMimeType) + ';' +
|
|
287
|
+
MimeUtils.getCodecBase(audioMimeType);
|
|
282
288
|
const keySystem = mcapKeySystemConfig.keySystem;
|
|
283
289
|
|
|
284
290
|
/** @type {MediaKeySystemAccess} */
|
package/lib/util/periods.js
CHANGED