shaka-player 4.3.4 → 4.3.6
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 +4 -4
- package/.github/workflows/demo-version-index.yaml +6 -0
- package/.github/workflows/selenium-lab-tests.yaml +78 -17
- package/.github/workflows/update-screenshots.yaml +142 -0
- package/AUTHORS +2 -1
- package/CHANGELOG.md +56 -0
- package/CONTRIBUTORS +4 -1
- package/demo/common/asset.js +15 -0
- package/demo/common/assets.js +4 -2
- package/demo/common/message_ids.js +1 -0
- package/demo/custom.js +19 -0
- package/demo/locales/en.json +1 -0
- package/demo/locales/source.json +4 -0
- package/demo/main.js +14 -0
- package/dist/controls.css +2 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +184 -311
- package/dist/demo.compiled.debug.map +4 -4
- package/dist/demo.compiled.js +184 -311
- package/dist/demo.compiled.map +4 -4
- package/dist/deps.js +2 -2
- package/dist/locales.js +29 -29
- package/dist/receiver.compiled.debug.js +51 -80
- package/dist/receiver.compiled.debug.map +4 -4
- package/dist/receiver.compiled.js +51 -80
- package/dist/receiver.compiled.map +4 -4
- package/dist/shaka-player.compiled.d.ts +19 -3
- package/dist/shaka-player.compiled.debug.d.ts +19 -3
- package/dist/shaka-player.compiled.debug.externs.js +20 -14
- package/dist/shaka-player.compiled.debug.js +291 -283
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +20 -14
- package/dist/shaka-player.compiled.js +830 -825
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +19 -3
- package/dist/shaka-player.ui.debug.d.ts +19 -3
- package/dist/shaka-player.ui.debug.externs.js +8 -2
- package/dist/shaka-player.ui.debug.js +301 -292
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +8 -2
- package/dist/shaka-player.ui.js +927 -922
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/a11y.md +0 -4
- package/docs/tutorials/ad_monetization.md +0 -4
- package/docs/tutorials/basic-usage.md +0 -5
- package/docs/tutorials/config.md +0 -5
- package/docs/tutorials/debugging.md +0 -5
- package/docs/tutorials/drm-config.md +0 -5
- package/docs/tutorials/errors.md +94 -0
- package/docs/tutorials/fairplay.md +4 -4
- package/docs/tutorials/index.json +2 -0
- package/docs/tutorials/license-server-auth.md +0 -5
- package/docs/tutorials/license-wrapping.md +0 -5
- package/docs/tutorials/network-and-buffering-config.md +0 -5
- package/docs/tutorials/ui-customization.md +0 -5
- package/docs/tutorials/ui.md +0 -7
- package/docs/tutorials/welcome.md +0 -5
- package/docs/tutorials/widevine-service-certs.md +45 -0
- package/externs/ima.js +9 -0
- package/externs/network_information.js +7 -2
- package/externs/shaka/abr_manager.js +6 -0
- package/externs/shaka/net.js +1 -0
- package/externs/shaka/player.js +13 -7
- package/lib/abr/simple_abr_manager.js +27 -3
- package/lib/ads/client_side_ad_manager.js +11 -5
- package/lib/dash/dash_parser.js +49 -48
- package/lib/debug/log.js +9 -13
- package/lib/hls/hls_parser.js +23 -6
- package/lib/media/drm_engine.js +4 -2
- package/lib/media/media_source_engine.js +27 -9
- package/lib/media/presentation_timeline.js +9 -2
- package/lib/media/segment_index.js +2 -0
- package/lib/media/segment_reference.js +3 -2
- package/lib/media/streaming_engine.js +31 -7
- package/lib/media/transmuxer.js +13 -7
- package/lib/media/video_wrapper.js +3 -3
- package/lib/net/http_fetch_plugin.js +79 -57
- package/lib/net/networking_engine.js +1 -0
- package/lib/offline/indexeddb/storage_mechanism.js +1 -1
- package/lib/player.js +24 -3
- package/lib/polyfill/media_capabilities.js +15 -8
- package/lib/polyfill/mediasource.js +24 -2
- package/lib/text/mp4_vtt_parser.js +4 -0
- package/lib/text/text_engine.js +1 -0
- package/lib/text/ui_text_displayer.js +1 -0
- package/lib/text/vtt_text_parser.js +4 -5
- package/lib/util/manifest_parser_utils.js +1 -2
- package/lib/util/mime_utils.js +12 -1
- package/lib/util/periods.js +5 -5
- package/lib/util/stream_utils.js +28 -16
- package/maintained-branches.md +2 -3
- package/package.json +1 -1
- package/test/dash/dash_parser_live_unit.js +3 -2
- package/test/hls/hls_parser_unit.js +156 -0
- package/test/media/content_workarounds_unit.js +43 -0
- package/test/media/drm_engine_integration.js +4 -17
- package/test/media/streaming_engine_unit.js +6 -1
- package/test/net/networking_engine_unit.js +6 -1
- package/test/player_integration.js +39 -0
- package/test/player_unit.js +57 -0
- package/test/polyfill/media_capabilities_unit.js +22 -22
- package/test/test/assets/text-clip-alt.vtt +63 -0
- package/test/test/util/fake_drm_engine.js +3 -0
- package/test/test/util/fake_media_source_engine.js +4 -0
- package/test/test/util/simple_fakes.js +6 -0
- package/test/test/util/test_scheme.js +9 -14
- package/test/text/mp4_vtt_parser_unit.js +8 -0
- package/test/text/vtt_text_parser_unit.js +26 -2
- package/test/text/web_vtt_layout_integration.js +0 -3
- package/test/util/mime_utils_unit.js +10 -0
- package/ui/less/containers.less +4 -0
package/lib/hls/hls_parser.js
CHANGED
|
@@ -311,7 +311,10 @@ shaka.hls.HlsParser = class {
|
|
|
311
311
|
const PresentationType = shaka.hls.HlsParser.PresentationType_;
|
|
312
312
|
this.setPresentationType_(PresentationType.VOD);
|
|
313
313
|
|
|
314
|
-
|
|
314
|
+
// The duration is the minimum of the end times of all active streams.
|
|
315
|
+
// Non-active streams are not guaranteed to have useful maxTimestamp
|
|
316
|
+
// values, due to the lazy-loading system, so they are ignored.
|
|
317
|
+
const maxTimestamps = activeStreamInfos.map((s) => s.maxTimestamp);
|
|
315
318
|
// The duration is the minimum of the end times of all streams.
|
|
316
319
|
this.presentationTimeline_.setDuration(Math.min(...maxTimestamps));
|
|
317
320
|
this.playerInterface_.updateDuration();
|
|
@@ -1455,9 +1458,9 @@ shaka.hls.HlsParser = class {
|
|
|
1455
1458
|
let codecs = '';
|
|
1456
1459
|
/** @type {string} */
|
|
1457
1460
|
const type = this.getType_(tag);
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
+
if (type == shaka.util.ManifestParserUtils.ContentType.TEXT) {
|
|
1462
|
+
codecs = tag.getAttributeValue('CODECS') || '';
|
|
1463
|
+
} else if (groupId && this.groupIdToCodecsMap_.has(groupId)) {
|
|
1461
1464
|
codecs = this.groupIdToCodecsMap_.get(groupId);
|
|
1462
1465
|
}
|
|
1463
1466
|
|
|
@@ -1733,6 +1736,13 @@ shaka.hls.HlsParser = class {
|
|
|
1733
1736
|
}
|
|
1734
1737
|
}
|
|
1735
1738
|
|
|
1739
|
+
if (type == ContentType.TEXT) {
|
|
1740
|
+
const firstSegment = realStream.segmentIndex.get(0);
|
|
1741
|
+
if (firstSegment && firstSegment.initSegmentReference) {
|
|
1742
|
+
stream.mimeType = 'application/mp4';
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1736
1746
|
// Add finishing touches to the stream that can only be done once we have
|
|
1737
1747
|
// more full context on the media as a whole.
|
|
1738
1748
|
if (this.hasEnoughInfoToFinalizeStreams_()) {
|
|
@@ -2368,13 +2378,15 @@ shaka.hls.HlsParser = class {
|
|
|
2368
2378
|
* @param {!Map.<string, string>} variables
|
|
2369
2379
|
* @param {string} absoluteMediaPlaylistUri
|
|
2370
2380
|
* @param {string} type
|
|
2381
|
+
* @param {number} timestampOffset
|
|
2371
2382
|
* @param {shaka.extern.HlsAes128Key=} hlsAes128Key
|
|
2372
2383
|
* @return {shaka.media.SegmentReference}
|
|
2373
2384
|
* @private
|
|
2374
2385
|
*/
|
|
2375
2386
|
createSegmentReference_(
|
|
2376
2387
|
initSegmentReference, previousReference, hlsSegment, startTime,
|
|
2377
|
-
variables, absoluteMediaPlaylistUri, type,
|
|
2388
|
+
variables, absoluteMediaPlaylistUri, type, timestampOffset,
|
|
2389
|
+
hlsAes128Key) {
|
|
2378
2390
|
const tags = hlsSegment.tags;
|
|
2379
2391
|
const absoluteSegmentUri = this.variableSubstitution_(
|
|
2380
2392
|
hlsSegment.absoluteUri, variables);
|
|
@@ -2544,7 +2556,7 @@ shaka.hls.HlsParser = class {
|
|
|
2544
2556
|
startByte,
|
|
2545
2557
|
endByte,
|
|
2546
2558
|
initSegmentReference,
|
|
2547
|
-
|
|
2559
|
+
timestampOffset, // This value is ignored in sequence mode.
|
|
2548
2560
|
/* appendWindowStart= */ 0,
|
|
2549
2561
|
/* appendWindowEnd= */ Infinity,
|
|
2550
2562
|
partialSegmentRefs,
|
|
@@ -2629,6 +2641,7 @@ shaka.hls.HlsParser = class {
|
|
|
2629
2641
|
const references = [];
|
|
2630
2642
|
|
|
2631
2643
|
let previousReference = null;
|
|
2644
|
+
let lastDiscontinuityStartTime = firstStartTime;
|
|
2632
2645
|
|
|
2633
2646
|
for (let i = 0; i < hlsSegments.length; i++) {
|
|
2634
2647
|
const item = hlsSegments[i];
|
|
@@ -2640,6 +2653,7 @@ shaka.hls.HlsParser = class {
|
|
|
2640
2653
|
item.tags, 'EXT-X-DISCONTINUITY');
|
|
2641
2654
|
if (discontinuityTag) {
|
|
2642
2655
|
discontinuitySequence++;
|
|
2656
|
+
lastDiscontinuityStartTime = startTime;
|
|
2643
2657
|
}
|
|
2644
2658
|
|
|
2645
2659
|
// Apply new AES-128 tags as you see them, keeping a running total.
|
|
@@ -2678,6 +2692,7 @@ shaka.hls.HlsParser = class {
|
|
|
2678
2692
|
variables,
|
|
2679
2693
|
playlist.absoluteUri,
|
|
2680
2694
|
type,
|
|
2695
|
+
lastDiscontinuityStartTime,
|
|
2681
2696
|
hlsAes128Key);
|
|
2682
2697
|
previousReference = reference;
|
|
2683
2698
|
|
|
@@ -3317,6 +3332,7 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
|
3317
3332
|
'cmfa': 'audio/mp4',
|
|
3318
3333
|
// MPEG2-TS also uses video/ for audio: https://bit.ly/TsMse
|
|
3319
3334
|
'ts': 'video/mp2t',
|
|
3335
|
+
'tsa': 'video/mp2t',
|
|
3320
3336
|
};
|
|
3321
3337
|
|
|
3322
3338
|
|
|
@@ -3333,6 +3349,7 @@ shaka.hls.HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
|
3333
3349
|
'm4f': 'video/mp4',
|
|
3334
3350
|
'cmfv': 'video/mp4',
|
|
3335
3351
|
'ts': 'video/mp2t',
|
|
3352
|
+
'tsv': 'video/mp2t',
|
|
3336
3353
|
};
|
|
3337
3354
|
|
|
3338
3355
|
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -480,8 +480,10 @@ shaka.media.DrmEngine = class {
|
|
|
480
480
|
() => this.closeOpenSessions_());
|
|
481
481
|
}
|
|
482
482
|
|
|
483
|
-
const manifestInitData = this.currentDrmInfo_
|
|
484
|
-
|
|
483
|
+
const manifestInitData = this.currentDrmInfo_ ?
|
|
484
|
+
this.currentDrmInfo_.initData.find(
|
|
485
|
+
(initDataOverride) => initDataOverride.initData.length > 0,
|
|
486
|
+
) : null;
|
|
485
487
|
|
|
486
488
|
/**
|
|
487
489
|
* We can attach media keys before the playback actually begins when:
|
|
@@ -202,6 +202,9 @@ shaka.media.MediaSourceEngine = class {
|
|
|
202
202
|
'audio/mp4; codecs="ec-3"',
|
|
203
203
|
'audio/mp4; codecs="opus"',
|
|
204
204
|
'audio/mp4; codecs="flac"',
|
|
205
|
+
'audio/mp4; codecs="dtsc"', // DTS Digital Surround
|
|
206
|
+
'audio/mp4; codecs="dtse"', // DTS Express
|
|
207
|
+
'audio/mp4; codecs="dtsx"', // DTS:X
|
|
205
208
|
// WebM types
|
|
206
209
|
'video/webm; codecs="vp8"',
|
|
207
210
|
'video/webm; codecs="vp9"',
|
|
@@ -375,7 +378,22 @@ shaka.media.MediaSourceEngine = class {
|
|
|
375
378
|
shaka.media.Transmuxer.convertCodecs(contentType, mimeType);
|
|
376
379
|
}
|
|
377
380
|
const type = mimeType + this.config_.sourceBufferExtraFeatures;
|
|
378
|
-
|
|
381
|
+
|
|
382
|
+
this.destroyer_.ensureNotDestroyed();
|
|
383
|
+
|
|
384
|
+
let sourceBuffer;
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
sourceBuffer = this.mediaSource_.addSourceBuffer(type);
|
|
388
|
+
} catch (exception) {
|
|
389
|
+
throw new shaka.util.Error(
|
|
390
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
391
|
+
shaka.util.Error.Category.MEDIA,
|
|
392
|
+
shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_THREW,
|
|
393
|
+
exception,
|
|
394
|
+
'The mediaSource_ status was' + this.mediaSource_.readyState +
|
|
395
|
+
'expected \'open\'');
|
|
396
|
+
}
|
|
379
397
|
|
|
380
398
|
this.eventManager_.listen(
|
|
381
399
|
sourceBuffer, 'error',
|
|
@@ -628,7 +646,8 @@ shaka.media.MediaSourceEngine = class {
|
|
|
628
646
|
// For HLS CEA-608/708 CLOSED-CAPTIONS, text data is embedded in
|
|
629
647
|
// the video stream, so textEngine may not have been initialized.
|
|
630
648
|
if (!this.textEngine_) {
|
|
631
|
-
this.reinitText(
|
|
649
|
+
this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE,
|
|
650
|
+
this.sequenceMode_);
|
|
632
651
|
}
|
|
633
652
|
// This doesn't work for native TS support (ex. Edge/Chromecast),
|
|
634
653
|
// since no transmuxing is needed for native TS.
|
|
@@ -647,7 +666,8 @@ shaka.media.MediaSourceEngine = class {
|
|
|
647
666
|
data = transmuxedData.data;
|
|
648
667
|
} else if (hasClosedCaptions && contentType == ContentType.VIDEO) {
|
|
649
668
|
if (!this.textEngine_) {
|
|
650
|
-
this.reinitText(
|
|
669
|
+
this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE,
|
|
670
|
+
this.sequenceMode_);
|
|
651
671
|
}
|
|
652
672
|
if (!this.captionParser_) {
|
|
653
673
|
this.captionParser_ = this.getCaptionParser(mimeType);
|
|
@@ -937,7 +957,6 @@ shaka.media.MediaSourceEngine = class {
|
|
|
937
957
|
|
|
938
958
|
/**
|
|
939
959
|
* Adjust timestamp offset to maintain AV sync across discontinuities.
|
|
940
|
-
* Only used in sequence mode.
|
|
941
960
|
*
|
|
942
961
|
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
|
|
943
962
|
* @param {number} timestampOffset
|
|
@@ -946,9 +965,6 @@ shaka.media.MediaSourceEngine = class {
|
|
|
946
965
|
async resync(contentType, timestampOffset) {
|
|
947
966
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
948
967
|
|
|
949
|
-
goog.asserts.assert(this.sequenceMode_,
|
|
950
|
-
'resync only used with sequence mode!');
|
|
951
|
-
|
|
952
968
|
if (contentType == ContentType.TEXT) {
|
|
953
969
|
// This operation is for audio and video only.
|
|
954
970
|
return;
|
|
@@ -978,8 +994,10 @@ shaka.media.MediaSourceEngine = class {
|
|
|
978
994
|
async endOfStream(reason) {
|
|
979
995
|
await this.enqueueBlockingOperation_(() => {
|
|
980
996
|
// If endOfStream() has already been called on the media source,
|
|
981
|
-
// don't call it again.
|
|
982
|
-
|
|
997
|
+
// don't call it again. Also do not call if readyState is
|
|
998
|
+
// 'closed' (not attached to video element) since it is not a
|
|
999
|
+
// valid operation.
|
|
1000
|
+
if (this.ended() || this.mediaSource_.readyState === 'closed') {
|
|
983
1001
|
return;
|
|
984
1002
|
}
|
|
985
1003
|
// Tizen won't let us pass undefined, but it will let us omit the
|
|
@@ -236,7 +236,16 @@ shaka.media.PresentationTimeline = class {
|
|
|
236
236
|
|
|
237
237
|
let firstReferenceStartTime = references[0].startTime;
|
|
238
238
|
let lastReferenceEndTime = references[0].endTime;
|
|
239
|
+
|
|
240
|
+
// Date.now() is in milliseconds, from which we compute "now" in seconds.
|
|
241
|
+
const now = (Date.now() + this.clockOffset_) / 1000.0;
|
|
242
|
+
|
|
239
243
|
for (const reference of references) {
|
|
244
|
+
// Exclude segments that are in the "future".
|
|
245
|
+
if (now < reference.startTime) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
240
249
|
firstReferenceStartTime = Math.min(
|
|
241
250
|
firstReferenceStartTime, reference.startTime);
|
|
242
251
|
lastReferenceEndTime = Math.max(lastReferenceEndTime, reference.endTime);
|
|
@@ -251,8 +260,6 @@ shaka.media.PresentationTimeline = class {
|
|
|
251
260
|
!this.startTimeLocked_) {
|
|
252
261
|
// Since we have explicit segment end times, calculate a presentation
|
|
253
262
|
// start based on them. This start time accounts for drift.
|
|
254
|
-
// Date.now() is in milliseconds, from which we compute "now" in seconds.
|
|
255
|
-
const now = (Date.now() + this.clockOffset_) / 1000.0;
|
|
256
263
|
this.presentationStartTime_ =
|
|
257
264
|
now - this.maxSegmentEndTime_ - this.maxSegmentDuration_;
|
|
258
265
|
}
|
|
@@ -144,8 +144,9 @@ shaka.media.SegmentReference = class {
|
|
|
144
144
|
* minus the first segment's tfdt box's 'baseMediaDecodeTime' field (after
|
|
145
145
|
* it has been converted to seconds).
|
|
146
146
|
* <br>
|
|
147
|
-
* For HLS, this value should be
|
|
148
|
-
*
|
|
147
|
+
* For HLS, this value should be the start time of the most recent
|
|
148
|
+
* discontinuity, or 0 if there is no preceding discontinuity. Only used
|
|
149
|
+
* in segments mode.
|
|
149
150
|
* @param {number} appendWindowStart
|
|
150
151
|
* The start of the append window for this reference, relative to the
|
|
151
152
|
* presentation. Any content from before this time will be removed by
|
|
@@ -78,6 +78,9 @@ shaka.media.StreamingEngine = class {
|
|
|
78
78
|
/** @private {?shaka.extern.Stream} */
|
|
79
79
|
this.currentTextStream_ = null;
|
|
80
80
|
|
|
81
|
+
/** @private {number} */
|
|
82
|
+
this.textStreamSequenceId_ = 0;
|
|
83
|
+
|
|
81
84
|
/** @private {boolean} */
|
|
82
85
|
this.parsedPrftEventRaised_ = false;
|
|
83
86
|
|
|
@@ -224,6 +227,8 @@ shaka.media.StreamingEngine = class {
|
|
|
224
227
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
225
228
|
goog.asserts.assert(!this.mediaStates_.has(ContentType.TEXT),
|
|
226
229
|
'Should not call loadNewTextStream_ while streaming text!');
|
|
230
|
+
this.textStreamSequenceId_++;
|
|
231
|
+
const currentSequenceId = this.textStreamSequenceId_;
|
|
227
232
|
|
|
228
233
|
try {
|
|
229
234
|
// Clear MediaSource's buffered text, so that the new text stream will
|
|
@@ -246,7 +251,7 @@ shaka.media.StreamingEngine = class {
|
|
|
246
251
|
const streamText =
|
|
247
252
|
textDisplayer.isTextVisible() || this.config_.alwaysStreamText;
|
|
248
253
|
|
|
249
|
-
if (streamText) {
|
|
254
|
+
if (streamText && (this.textStreamSequenceId_ == currentSequenceId)) {
|
|
250
255
|
const state = this.createMediaState_(stream);
|
|
251
256
|
this.mediaStates_.set(ContentType.TEXT, state);
|
|
252
257
|
this.scheduleUpdate_(state, 0);
|
|
@@ -357,7 +362,7 @@ shaka.media.StreamingEngine = class {
|
|
|
357
362
|
/**
|
|
358
363
|
* @param {shaka.extern.Stream} textStream
|
|
359
364
|
*/
|
|
360
|
-
switchTextStream(textStream) {
|
|
365
|
+
async switchTextStream(textStream) {
|
|
361
366
|
this.currentTextStream_ = textStream;
|
|
362
367
|
|
|
363
368
|
if (!this.startupComplete_) {
|
|
@@ -369,6 +374,13 @@ shaka.media.StreamingEngine = class {
|
|
|
369
374
|
goog.asserts.assert(textStream && textStream.type == ContentType.TEXT,
|
|
370
375
|
'Wrong stream type passed to switchTextStream!');
|
|
371
376
|
|
|
377
|
+
// In HLS it is possible that the mimetype changes when the media
|
|
378
|
+
// playlist is downloaded, so it is necessary to have the updated data
|
|
379
|
+
// here.
|
|
380
|
+
if (!textStream.segmentIndex) {
|
|
381
|
+
await textStream.createSegmentIndex();
|
|
382
|
+
}
|
|
383
|
+
|
|
372
384
|
this.switchInternal_(
|
|
373
385
|
textStream, /* clearBuffer= */ true,
|
|
374
386
|
/* safeMargin= */ 0, /* force= */ false);
|
|
@@ -1564,10 +1576,10 @@ shaka.media.StreamingEngine = class {
|
|
|
1564
1576
|
'waiting for another stream to recover...');
|
|
1565
1577
|
}
|
|
1566
1578
|
|
|
1567
|
-
// QuotaExceededError gets thrown if
|
|
1579
|
+
// QuotaExceededError gets thrown if eviction didn't help to make room
|
|
1568
1580
|
// for a segment. We want to wait for a while (4 seconds is just an
|
|
1569
1581
|
// arbitrary number) before updating to give the playhead a chance to
|
|
1570
|
-
// advance, so we don't
|
|
1582
|
+
// advance, so we don't immediately throw again.
|
|
1571
1583
|
this.scheduleUpdate_(mediaState, 4);
|
|
1572
1584
|
}
|
|
1573
1585
|
|
|
@@ -1675,19 +1687,31 @@ shaka.media.StreamingEngine = class {
|
|
|
1675
1687
|
}
|
|
1676
1688
|
}
|
|
1677
1689
|
|
|
1690
|
+
const lastDiscontinuitySequence =
|
|
1691
|
+
mediaState.lastSegmentReference ?
|
|
1692
|
+
mediaState.lastSegmentReference.discontinuitySequence : null;
|
|
1678
1693
|
if (this.manifest_.sequenceMode) {
|
|
1679
1694
|
// Across discontinuity bounds, we should resync timestamps for
|
|
1680
1695
|
// sequence mode playbacks. The next segment appended should
|
|
1681
1696
|
// land at its theoretical timestamp from the segment index.
|
|
1682
|
-
const lastDiscontinuitySequence =
|
|
1683
|
-
mediaState.lastSegmentReference ?
|
|
1684
|
-
mediaState.lastSegmentReference.discontinuitySequence : null;
|
|
1685
1697
|
if (reference.discontinuitySequence != lastDiscontinuitySequence ||
|
|
1686
1698
|
mediaState.needsResync) {
|
|
1687
1699
|
mediaState.needsResync = false;
|
|
1688
1700
|
operations.push(this.playerInterface_.mediaSourceEngine.resync(
|
|
1689
1701
|
mediaState.type, reference.startTime));
|
|
1690
1702
|
}
|
|
1703
|
+
} else {
|
|
1704
|
+
// In segments mode, we need to resync to set the timestampOffset
|
|
1705
|
+
// to the start of the current discontinuity sequence. This is
|
|
1706
|
+
// because individual discontinuity sequences may have internal
|
|
1707
|
+
// timestamps that overlap, so we adjust the timestampOffset to avoid
|
|
1708
|
+
// having the SourceBuffer get overwritten.
|
|
1709
|
+
if (reference.discontinuitySequence != lastDiscontinuitySequence) {
|
|
1710
|
+
operations.push(
|
|
1711
|
+
this.playerInterface_.mediaSourceEngine.resync(
|
|
1712
|
+
mediaState.type,
|
|
1713
|
+
reference.timestampOffset));
|
|
1714
|
+
}
|
|
1691
1715
|
}
|
|
1692
1716
|
|
|
1693
1717
|
await Promise.all(operations);
|
package/lib/media/transmuxer.js
CHANGED
|
@@ -35,9 +35,7 @@ shaka.media.Transmuxer = class {
|
|
|
35
35
|
this.muxjs_ = shaka.dependencies.muxjs();
|
|
36
36
|
|
|
37
37
|
/** @private {muxjs.mp4.Transmuxer} */
|
|
38
|
-
this.muxTransmuxer_ =
|
|
39
|
-
'keepOriginalTimestamps': true,
|
|
40
|
-
});
|
|
38
|
+
this.muxTransmuxer_ = null;
|
|
41
39
|
|
|
42
40
|
/** @private {shaka.util.PublicPromise} */
|
|
43
41
|
this.transmuxPromise_ = null;
|
|
@@ -51,16 +49,22 @@ shaka.media.Transmuxer = class {
|
|
|
51
49
|
/** @private {boolean} */
|
|
52
50
|
this.isTransmuxing_ = false;
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
if (this.muxjs_) {
|
|
53
|
+
this.muxTransmuxer_ = new this.muxjs_.mp4.Transmuxer({
|
|
54
|
+
'keepOriginalTimestamps': true,
|
|
55
|
+
});
|
|
56
|
+
this.muxTransmuxer_.on('data', (segment) => this.onTransmuxed_(segment));
|
|
57
|
+
this.muxTransmuxer_.on('done', () => this.onTransmuxDone_());
|
|
58
|
+
}
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
/**
|
|
60
62
|
* @override
|
|
61
63
|
*/
|
|
62
64
|
destroy() {
|
|
63
|
-
this.muxTransmuxer_
|
|
65
|
+
if (this.muxTransmuxer_) {
|
|
66
|
+
this.muxTransmuxer_.dispose();
|
|
67
|
+
}
|
|
64
68
|
this.muxTransmuxer_ = null;
|
|
65
69
|
return Promise.resolve();
|
|
66
70
|
}
|
|
@@ -216,6 +220,8 @@ shaka.media.Transmuxer = class {
|
|
|
216
220
|
* captions: !Array.<!muxjs.mp4.ClosedCaption>}>}
|
|
217
221
|
*/
|
|
218
222
|
transmux(data) {
|
|
223
|
+
goog.asserts.assert(this.muxTransmuxer_,
|
|
224
|
+
'mux.js should be available.');
|
|
219
225
|
goog.asserts.assert(!this.isTransmuxing_,
|
|
220
226
|
'No transmuxing should be in progress.');
|
|
221
227
|
this.isTransmuxing_ = true;
|
|
@@ -135,15 +135,15 @@ shaka.media.VideoWrapper = class {
|
|
|
135
135
|
// calling |Player.load|, meaning that |currentTime| is more meaningful than
|
|
136
136
|
// |startTime|.
|
|
137
137
|
//
|
|
138
|
-
// Seeking to the current time is a work around for Issue 1298
|
|
139
|
-
// do this, the video may get stuck and not play.
|
|
138
|
+
// Seeking to the current time is a work around for Issue 1298 and 4888.
|
|
139
|
+
// If we don't do this, the video may get stuck and not play.
|
|
140
140
|
//
|
|
141
141
|
// TODO: Need further investigation why it happens. Before and after
|
|
142
142
|
// setting the current time, video.readyState is 1, video.paused is true,
|
|
143
143
|
// and video.buffered's TimeRanges length is 0.
|
|
144
144
|
// See: https://github.com/shaka-project/shaka-player/issues/1298
|
|
145
145
|
this.mover_.moveTo(
|
|
146
|
-
this.video_.currentTime == 0 ?
|
|
146
|
+
(!this.video_.currentTime || this.video_.currentTime == 0) ?
|
|
147
147
|
startTime :
|
|
148
148
|
this.video_.currentTime);
|
|
149
149
|
}
|
|
@@ -120,65 +120,77 @@ shaka.net.HttpFetchPlugin = class {
|
|
|
120
120
|
// not the body yet.
|
|
121
121
|
headersReceived(shaka.net.HttpFetchPlugin.headersToGenericObject_(
|
|
122
122
|
response.headers));
|
|
123
|
-
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
123
|
+
|
|
124
|
+
// In new versions of Chromium, HEAD requests now have a response body
|
|
125
|
+
// that is null.
|
|
126
|
+
// So just don't try to download the body at all, if it's a HEAD request,
|
|
127
|
+
// to avoid null reference errors.
|
|
128
|
+
// See: https://crbug.com/1297060
|
|
129
|
+
if (init.method != 'HEAD') {
|
|
130
|
+
goog.asserts.assert(response.body,
|
|
131
|
+
'non-HEAD responses should have a body');
|
|
132
|
+
|
|
133
|
+
// Getting the reader in this way allows us to observe the process of
|
|
134
|
+
// downloading the body, instead of just waiting for an opaque promise
|
|
135
|
+
// to resolve.
|
|
136
|
+
// We first clone the response because calling getReader locks the body
|
|
137
|
+
// stream; if we didn't clone it here, we would be unable to get the
|
|
138
|
+
// response's arrayBuffer later.
|
|
139
|
+
const reader = response.clone().body.getReader();
|
|
140
|
+
|
|
141
|
+
const contentLengthRaw = response.headers.get('Content-Length');
|
|
142
|
+
const contentLength =
|
|
143
|
+
contentLengthRaw ? parseInt(contentLengthRaw, 10) : 0;
|
|
144
|
+
|
|
145
|
+
const start = (controller) => {
|
|
146
|
+
const push = async () => {
|
|
147
|
+
let readObj;
|
|
148
|
+
try {
|
|
149
|
+
readObj = await reader.read();
|
|
150
|
+
} catch (e) {
|
|
151
|
+
// If we abort the request, we'll get an error here. Just ignore
|
|
152
|
+
// it since real errors will be reported when we read the buffer
|
|
153
|
+
// below.
|
|
154
|
+
shaka.log.v1('error reading from stream', e.message);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!readObj.done) {
|
|
159
|
+
loaded += readObj.value.byteLength;
|
|
160
|
+
if (streamDataCallback) {
|
|
161
|
+
await streamDataCallback(readObj.value);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const currentTime = Date.now();
|
|
166
|
+
// If the time between last time and this time we got progress event
|
|
167
|
+
// is long enough, or if a whole segment is downloaded, call
|
|
168
|
+
// progressUpdated().
|
|
169
|
+
if (currentTime - lastTime > 100 || readObj.done) {
|
|
170
|
+
progressUpdated(currentTime - lastTime, loaded - lastLoaded,
|
|
171
|
+
contentLength - loaded);
|
|
172
|
+
lastLoaded = loaded;
|
|
173
|
+
lastTime = currentTime;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (readObj.done) {
|
|
177
|
+
goog.asserts.assert(!readObj.value,
|
|
178
|
+
'readObj should be unset when "done" is true.');
|
|
179
|
+
controller.close();
|
|
180
|
+
} else {
|
|
181
|
+
controller.enqueue(readObj.value);
|
|
182
|
+
push();
|
|
151
183
|
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const currentTime = Date.now();
|
|
155
|
-
// If the time between last time and this time we got progress event
|
|
156
|
-
// is long enough, or if a whole segment is downloaded, call
|
|
157
|
-
// progressUpdated().
|
|
158
|
-
if (currentTime - lastTime > 100 || readObj.done) {
|
|
159
|
-
progressUpdated(currentTime - lastTime, loaded - lastLoaded,
|
|
160
|
-
contentLength - loaded);
|
|
161
|
-
lastLoaded = loaded;
|
|
162
|
-
lastTime = currentTime;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (readObj.done) {
|
|
166
|
-
goog.asserts.assert(!readObj.value,
|
|
167
|
-
'readObj should be unset when "done" is true.');
|
|
168
|
-
controller.close();
|
|
169
|
-
} else {
|
|
170
|
-
controller.enqueue(readObj.value);
|
|
171
|
-
push();
|
|
172
|
-
}
|
|
184
|
+
};
|
|
185
|
+
push();
|
|
173
186
|
};
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
arrayBuffer = await response.arrayBuffer();
|
|
187
|
+
// Create a ReadableStream to use the reader. We don't need to use the
|
|
188
|
+
// actual stream for anything, though, as we are using the response's
|
|
189
|
+
// arrayBuffer method to get the body, so we don't store the
|
|
190
|
+
// ReadableStream.
|
|
191
|
+
new ReadableStream({start}); // eslint-disable-line no-new
|
|
192
|
+
arrayBuffer = await response.arrayBuffer();
|
|
193
|
+
}
|
|
182
194
|
} catch (error) {
|
|
183
195
|
if (abortStatus.canceled) {
|
|
184
196
|
throw new shaka.util.Error(
|
|
@@ -243,6 +255,16 @@ shaka.net.HttpFetchPlugin = class {
|
|
|
243
255
|
} else {
|
|
244
256
|
return false;
|
|
245
257
|
}
|
|
258
|
+
// Old fetch implementations hasn't body and ReadableStream implementation
|
|
259
|
+
// See: https://github.com/shaka-project/shaka-player/issues/5088
|
|
260
|
+
if (window.Response) {
|
|
261
|
+
const response = new Response('');
|
|
262
|
+
if (!response.body) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
246
268
|
return !!(window.fetch && window.AbortController);
|
|
247
269
|
}
|
|
248
270
|
};
|
|
@@ -312,6 +312,7 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
|
|
|
312
312
|
if (!responseAndGotProgress.gotProgress &&
|
|
313
313
|
this.onProgressUpdated_ &&
|
|
314
314
|
!response.fromCache &&
|
|
315
|
+
request.method != 'HEAD' &&
|
|
315
316
|
type == shaka.net.NetworkingEngine.RequestType.SEGMENT) {
|
|
316
317
|
this.onProgressUpdated_(response.timeMs, response.data.byteLength);
|
|
317
318
|
}
|
|
@@ -67,7 +67,7 @@ shaka.offline.indexeddb.StorageMechanism = class {
|
|
|
67
67
|
shaka.util.Error.Category.STORAGE,
|
|
68
68
|
shaka.util.Error.Code.INDEXED_DB_INIT_TIMED_OUT));
|
|
69
69
|
});
|
|
70
|
-
timeOutTimer.tickAfter(
|
|
70
|
+
timeOutTimer.tickAfter(5);
|
|
71
71
|
|
|
72
72
|
const open = window.indexedDB.open(name, version);
|
|
73
73
|
open.onsuccess = (event) => {
|
package/lib/player.js
CHANGED
|
@@ -897,7 +897,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
897
897
|
}
|
|
898
898
|
|
|
899
899
|
this.abrManagerFactory_ = null;
|
|
900
|
-
this.abrManager_ = null;
|
|
901
900
|
this.config_ = null;
|
|
902
901
|
this.stats_ = null;
|
|
903
902
|
this.videoContainer_ = null;
|
|
@@ -908,6 +907,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
908
907
|
this.networkingEngine_ = null;
|
|
909
908
|
}
|
|
910
909
|
|
|
910
|
+
if (this.abrManager_) {
|
|
911
|
+
this.abrManager_.release();
|
|
912
|
+
this.abrManager_ = null;
|
|
913
|
+
}
|
|
914
|
+
|
|
911
915
|
// FakeEventTarget implements IReleasable
|
|
912
916
|
super.release();
|
|
913
917
|
}
|
|
@@ -1342,6 +1346,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1342
1346
|
*/
|
|
1343
1347
|
shouldUseSrcEquals_(payload) {
|
|
1344
1348
|
const Platform = shaka.util.Platform;
|
|
1349
|
+
const MimeUtils = shaka.util.MimeUtils;
|
|
1345
1350
|
|
|
1346
1351
|
// If we are using a platform that does not support media source, we will
|
|
1347
1352
|
// fall back to src= to handle all playback.
|
|
@@ -1396,7 +1401,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1396
1401
|
// version there.
|
|
1397
1402
|
|
|
1398
1403
|
// Native HLS can be preferred on any platform via this flag:
|
|
1399
|
-
if (
|
|
1404
|
+
if (MimeUtils.isHlsType(mimeType) &&
|
|
1405
|
+
this.config_.streaming.preferNativeHls) {
|
|
1400
1406
|
return true;
|
|
1401
1407
|
}
|
|
1402
1408
|
|
|
@@ -2584,6 +2590,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2584
2590
|
// goes wrong.
|
|
2585
2591
|
new shaka.util.Timer(resolve).tickAfter(1);
|
|
2586
2592
|
});
|
|
2593
|
+
} else if (textTracks.length > 0) {
|
|
2594
|
+
this.isTextVisible_ = true;
|
|
2587
2595
|
}
|
|
2588
2596
|
|
|
2589
2597
|
// If we have moved on to another piece of content while waiting for
|
|
@@ -4274,6 +4282,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4274
4282
|
firstVariantWithLabel.language, '', 0, label);
|
|
4275
4283
|
|
|
4276
4284
|
this.chooseVariantAndSwitch_();
|
|
4285
|
+
} else if (this.video_ && this.video_.audioTracks) {
|
|
4286
|
+
const audioTracks = Array.from(this.video_.audioTracks);
|
|
4287
|
+
|
|
4288
|
+
let trackMatch = null;
|
|
4289
|
+
|
|
4290
|
+
for (const audioTrack of audioTracks) {
|
|
4291
|
+
if (audioTrack.label == label) {
|
|
4292
|
+
trackMatch = audioTrack;
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
if (trackMatch) {
|
|
4296
|
+
this.switchHtml5Track_(trackMatch);
|
|
4297
|
+
}
|
|
4277
4298
|
}
|
|
4278
4299
|
}
|
|
4279
4300
|
|
|
@@ -6984,7 +7005,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6984
7005
|
* @define {string} A version number taken from git at compile time.
|
|
6985
7006
|
* @export
|
|
6986
7007
|
*/
|
|
6987
|
-
shaka.Player.version = 'v4.3.
|
|
7008
|
+
shaka.Player.version = 'v4.3.6-uncompiled';
|
|
6988
7009
|
|
|
6989
7010
|
// Initialize the deprecation system using the version string we just set
|
|
6990
7011
|
// on the player.
|