shaka-player 4.2.0 → 4.2.2
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 +31 -16
- package/.github/workflows/custom-actions/set-commit-status/action.yaml +1 -5
- package/.github/workflows/deflake.yaml +28 -0
- package/.github/workflows/selenium-lab-tests.yaml +168 -54
- package/AUTHORS +3 -0
- package/CHANGELOG.md +30 -0
- package/CONTRIBUTORS +3 -0
- package/demo/common/message_ids.js +1 -0
- package/demo/config.js +3 -1
- package/demo/locales/en.json +1 -0
- package/demo/locales/source.json +4 -0
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +7 -6
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +7 -6
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +2 -2
- package/dist/locales.js +29 -29
- package/dist/receiver.compiled.debug.js +4 -3
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +4 -3
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +2 -4
- package/dist/shaka-player.compiled.debug.d.ts +2 -4
- package/dist/shaka-player.compiled.debug.externs.js +24 -26
- package/dist/shaka-player.compiled.debug.js +267 -260
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +24 -26
- package/dist/shaka-player.compiled.js +809 -807
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +2 -4
- package/dist/shaka-player.ui.debug.d.ts +2 -4
- package/dist/shaka-player.ui.debug.externs.js +24 -26
- package/dist/shaka-player.ui.debug.js +244 -236
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +24 -26
- package/dist/shaka-player.ui.js +973 -971
- package/dist/shaka-player.ui.map +4 -4
- package/docs/jsdoc-template/publish.js +33 -0
- package/docs/jsdoc-template/tmpl/container.tmpl +4 -1
- package/docs/jsdoc.conf.json +1 -0
- package/externs/shaka/manifest_parser.js +4 -1
- package/externs/shaka/player.js +7 -1
- package/lib/ads/client_side_ad_manager.js +7 -0
- package/lib/cast/cast_sender.js +39 -14
- package/lib/cea/cea608_memory.js +4 -0
- package/lib/hls/hls_parser.js +54 -82
- package/lib/media/drm_engine.js +52 -1
- package/lib/media/media_source_engine.js +43 -15
- package/lib/media/segment_index.js +4 -1
- package/lib/media/streaming_engine.js +79 -14
- package/lib/offline/storage.js +1 -0
- package/lib/player.js +29 -5
- package/lib/text/ttml_text_parser.js +6 -0
- package/lib/text/ui_text_displayer.js +8 -0
- package/lib/util/error.js +2 -10
- package/lib/util/platform.js +24 -0
- package/lib/util/player_configuration.js +5 -0
- package/lib/util/pssh.js +2 -1
- package/lib/util/stream_utils.js +2 -1
- package/package.json +1 -1
- package/test/cea/cea608_memory_unit.js +88 -0
- package/test/dash/dash_parser_content_protection_unit.js +1 -0
- package/test/dash/dash_parser_live_unit.js +1 -0
- package/test/dash/dash_parser_manifest_unit.js +1 -0
- package/test/dash/dash_parser_segment_base_unit.js +1 -0
- package/test/dash/dash_parser_segment_list_unit.js +1 -0
- package/test/dash/dash_parser_segment_template_unit.js +1 -0
- package/test/hls/hls_live_unit.js +169 -247
- package/test/hls/hls_parser_unit.js +112 -3
- package/test/media/drm_engine_unit.js +90 -0
- package/test/media/media_source_engine_integration.js +36 -0
- package/test/media/media_source_engine_unit.js +118 -1
- package/test/media/segment_index_unit.js +28 -2
- package/test/media/streaming_engine_integration.js +9 -7
- package/test/media/streaming_engine_unit.js +29 -0
- package/test/offline/offline_integration.js +3 -3
- package/test/player_integration.js +2 -2
- package/test/player_src_equals_integration.js +2 -0
- package/test/player_unit.js +39 -1
- package/test/test/assets/small.mp4 +0 -0
- package/test/test/util/dash_parser_util.js +2 -0
- package/test/test/util/ttml_utils.js +0 -2
- package/test/text/ttml_text_parser_unit.js +46 -0
- package/test/ui/text_displayer_layout_unit.js +17 -2
- package/ui/controls.js +0 -7
- package/ui/less/ad_controls.less +4 -0
|
@@ -522,9 +522,13 @@ shaka.media.MediaSourceEngine = class {
|
|
|
522
522
|
* @param {?boolean} hasClosedCaptions True if the buffer contains CEA closed
|
|
523
523
|
* captions
|
|
524
524
|
* @param {boolean=} seeked True if we just seeked
|
|
525
|
+
* @param {boolean=} adaptation True if we just automatically switched active
|
|
526
|
+
* variant(s).
|
|
525
527
|
* @return {!Promise}
|
|
526
528
|
*/
|
|
527
|
-
async appendBuffer(
|
|
529
|
+
async appendBuffer(
|
|
530
|
+
contentType, data, reference, hasClosedCaptions, seeked = false,
|
|
531
|
+
adaptation = false) {
|
|
528
532
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
529
533
|
|
|
530
534
|
if (contentType == ContentType.TEXT) {
|
|
@@ -625,6 +629,14 @@ shaka.media.MediaSourceEngine = class {
|
|
|
625
629
|
|
|
626
630
|
await this.enqueueOperation_(
|
|
627
631
|
contentType, () => this.append_(contentType, data));
|
|
632
|
+
// If the input buffer passed to SourceBuffer#appendBuffer() does not
|
|
633
|
+
// contain a complete media segment, the call will exit while the
|
|
634
|
+
// SourceBuffer's append state is
|
|
635
|
+
// still PARSING_MEDIA_SEGMENT. Reset the parser state by calling
|
|
636
|
+
// abort() to safely reset timestampOffset to 'originalOffset'.
|
|
637
|
+
// https://www.w3.org/TR/media-source-2/#sourcebuffer-segment-parser-loop
|
|
638
|
+
await this.enqueueOperation_(
|
|
639
|
+
contentType, () => this.abort_(contentType));
|
|
628
640
|
|
|
629
641
|
// Reset the offset and append window.
|
|
630
642
|
sourceBuffer.timestampOffset = originalOffset;
|
|
@@ -638,10 +650,17 @@ shaka.media.MediaSourceEngine = class {
|
|
|
638
650
|
const textOffset = (reference.startTime || 0) - (mediaStartTime || 0);
|
|
639
651
|
this.textSequenceModeOffset_.resolve(textOffset);
|
|
640
652
|
|
|
641
|
-
//
|
|
653
|
+
// Clear the buffer.
|
|
642
654
|
await this.enqueueOperation_(
|
|
643
655
|
contentType,
|
|
644
656
|
() => this.remove_(contentType, 0, this.mediaSource_.duration));
|
|
657
|
+
|
|
658
|
+
// Finally, flush the buffer in case of choppy video start on HLS fMP4.
|
|
659
|
+
if (contentType == ContentType.VIDEO) {
|
|
660
|
+
await this.enqueueOperation_(
|
|
661
|
+
contentType,
|
|
662
|
+
() => this.flush_(contentType));
|
|
663
|
+
}
|
|
645
664
|
}
|
|
646
665
|
|
|
647
666
|
// Now switch to sequence mode and fall through to our normal operations.
|
|
@@ -650,10 +669,15 @@ shaka.media.MediaSourceEngine = class {
|
|
|
650
669
|
|
|
651
670
|
if (reference && this.sequenceMode_ && contentType != ContentType.TEXT) {
|
|
652
671
|
// In sequence mode, for non-text streams, if we just cleared the buffer
|
|
653
|
-
// and are performing an unbuffered seek
|
|
654
|
-
// timestampOffset on the sourceBuffer.
|
|
655
|
-
if (seeked) {
|
|
672
|
+
// and are either performing an unbuffered seek or handling an automatic
|
|
673
|
+
// adaptation, we need to set a new timestampOffset on the sourceBuffer.
|
|
674
|
+
if (seeked || adaptation) {
|
|
656
675
|
const timestampOffset = reference.startTime;
|
|
676
|
+
// The logic to call abort() before setting the timestampOffset is
|
|
677
|
+
// extended during unbuffered seeks or automatic adaptations; it is
|
|
678
|
+
// possible for the append state to be PARSING_MEDIA_SEGMENT from the
|
|
679
|
+
// previous SourceBuffer#appendBuffer() call.
|
|
680
|
+
this.enqueueOperation_(contentType, () => this.abort_(contentType));
|
|
657
681
|
this.enqueueOperation_(
|
|
658
682
|
contentType,
|
|
659
683
|
() => this.setTimestampOffset_(contentType, timestampOffset));
|
|
@@ -849,21 +873,25 @@ shaka.media.MediaSourceEngine = class {
|
|
|
849
873
|
}
|
|
850
874
|
|
|
851
875
|
/**
|
|
852
|
-
* We only support increasing duration at this time. Decreasing duration
|
|
853
|
-
* causes the MSE removal algorithm to run, which results in an 'updateend'
|
|
854
|
-
* event. Supporting this scenario would be complicated, and is not currently
|
|
855
|
-
* needed.
|
|
856
|
-
*
|
|
857
876
|
* @param {number} duration
|
|
858
877
|
* @return {!Promise}
|
|
859
878
|
*/
|
|
860
879
|
async setDuration(duration) {
|
|
861
|
-
goog.asserts.assert(
|
|
862
|
-
isNaN(this.mediaSource_.duration) ||
|
|
863
|
-
this.mediaSource_.duration <= duration,
|
|
864
|
-
'duration cannot decrease: ' + this.mediaSource_.duration + ' -> ' +
|
|
865
|
-
duration);
|
|
866
880
|
await this.enqueueBlockingOperation_(() => {
|
|
881
|
+
// Reducing the duration causes the MSE removal algorithm to run, which
|
|
882
|
+
// triggers an 'updateend' event to fire. To handle this scenario, we
|
|
883
|
+
// have to insert a dummy operation into the beginning of each queue,
|
|
884
|
+
// which the 'updateend' handler will remove.
|
|
885
|
+
if (duration < this.mediaSource_.duration) {
|
|
886
|
+
for (const contentType in this.sourceBuffers_) {
|
|
887
|
+
const dummyOperation = {
|
|
888
|
+
start: () => {},
|
|
889
|
+
p: new shaka.util.PublicPromise(),
|
|
890
|
+
};
|
|
891
|
+
this.queues_[contentType].unshift(dummyOperation);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
867
895
|
this.mediaSource_.duration = duration;
|
|
868
896
|
});
|
|
869
897
|
}
|
|
@@ -364,7 +364,10 @@ shaka.media.SegmentIndex = class {
|
|
|
364
364
|
lastReference.partialReferences,
|
|
365
365
|
lastReference.tilesLayout,
|
|
366
366
|
lastReference.tileDuration,
|
|
367
|
-
lastReference.syncTime
|
|
367
|
+
lastReference.syncTime,
|
|
368
|
+
lastReference.status,
|
|
369
|
+
lastReference.hlsAes128Key,
|
|
370
|
+
);
|
|
368
371
|
}
|
|
369
372
|
|
|
370
373
|
|
|
@@ -320,8 +320,14 @@ shaka.media.StreamingEngine = class {
|
|
|
320
320
|
* @param {number=} safeMargin
|
|
321
321
|
* @param {boolean=} force
|
|
322
322
|
* If true, reload the variant even if it did not change.
|
|
323
|
+
* @param {boolean=} adaptation
|
|
324
|
+
* If true, update the media state to indicate MediaSourceEngine should
|
|
325
|
+
* reset the timestamp offset to ensure the new track segments are correctly
|
|
326
|
+
* placed on the timeline.
|
|
323
327
|
*/
|
|
324
|
-
switchVariant(
|
|
328
|
+
switchVariant(
|
|
329
|
+
variant, clearBuffer = false, safeMargin = 0, force = false,
|
|
330
|
+
adaptation = false) {
|
|
325
331
|
this.currentVariant_ = variant;
|
|
326
332
|
|
|
327
333
|
if (!this.startupComplete_) {
|
|
@@ -332,12 +338,14 @@ shaka.media.StreamingEngine = class {
|
|
|
332
338
|
if (variant.video) {
|
|
333
339
|
this.switchInternal_(
|
|
334
340
|
variant.video, /* clearBuffer= */ clearBuffer,
|
|
335
|
-
/* safeMargin= */ safeMargin, /* force= */ force
|
|
341
|
+
/* safeMargin= */ safeMargin, /* force= */ force,
|
|
342
|
+
/* adaptation= */ adaptation);
|
|
336
343
|
}
|
|
337
344
|
if (variant.audio) {
|
|
338
345
|
this.switchInternal_(
|
|
339
346
|
variant.audio, /* clearBuffer= */ clearBuffer,
|
|
340
|
-
/* safeMargin= */ safeMargin, /* force= */ force
|
|
347
|
+
/* safeMargin= */ safeMargin, /* force= */ force,
|
|
348
|
+
/* adaptation= */ adaptation);
|
|
341
349
|
}
|
|
342
350
|
}
|
|
343
351
|
|
|
@@ -383,9 +391,13 @@ shaka.media.StreamingEngine = class {
|
|
|
383
391
|
* @param {number} safeMargin
|
|
384
392
|
* @param {boolean} force
|
|
385
393
|
* If true, reload the text stream even if it did not change.
|
|
394
|
+
* @param {boolean=} adaptation
|
|
395
|
+
* If true, update the media state to indicate MediaSourceEngine should
|
|
396
|
+
* reset the timestamp offset to ensure the new track segments are correctly
|
|
397
|
+
* placed on the timeline.
|
|
386
398
|
* @private
|
|
387
399
|
*/
|
|
388
|
-
switchInternal_(stream, clearBuffer, safeMargin, force) {
|
|
400
|
+
switchInternal_(stream, clearBuffer, safeMargin, force, adaptation) {
|
|
389
401
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
390
402
|
const type = /** @type {!ContentType} */(stream.type);
|
|
391
403
|
const mediaState = this.mediaStates_.get(type);
|
|
@@ -440,6 +452,7 @@ shaka.media.StreamingEngine = class {
|
|
|
440
452
|
|
|
441
453
|
mediaState.stream = stream;
|
|
442
454
|
mediaState.segmentIterator = null;
|
|
455
|
+
mediaState.adaptation = !!adaptation;
|
|
443
456
|
|
|
444
457
|
const streamTag = shaka.media.StreamingEngine.logPrefix_(mediaState);
|
|
445
458
|
shaka.log.debug('switch: switching to Stream ' + streamTag);
|
|
@@ -769,12 +782,11 @@ shaka.media.StreamingEngine = class {
|
|
|
769
782
|
const mediaSourceEngine = this.playerInterface_.mediaSourceEngine;
|
|
770
783
|
const forceTransmuxTS = this.config_.forceTransmuxTS;
|
|
771
784
|
|
|
772
|
-
|
|
773
785
|
await mediaSourceEngine.init(streamsByType, forceTransmuxTS,
|
|
774
786
|
this.manifest_.sequenceMode);
|
|
775
787
|
this.destroyer_.ensureNotDestroyed();
|
|
776
788
|
|
|
777
|
-
this.
|
|
789
|
+
this.updateDuration();
|
|
778
790
|
|
|
779
791
|
for (const type of streamsByType.keys()) {
|
|
780
792
|
const stream = streamsByType.get(type);
|
|
@@ -824,9 +836,8 @@ shaka.media.StreamingEngine = class {
|
|
|
824
836
|
|
|
825
837
|
/**
|
|
826
838
|
* Sets the MediaSource's duration.
|
|
827
|
-
* @private
|
|
828
839
|
*/
|
|
829
|
-
|
|
840
|
+
updateDuration() {
|
|
830
841
|
const duration = this.manifest_.presentationTimeline.getDuration();
|
|
831
842
|
if (duration < Infinity) {
|
|
832
843
|
this.playerInterface_.mediaSourceEngine.setDuration(duration);
|
|
@@ -1001,7 +1012,11 @@ shaka.media.StreamingEngine = class {
|
|
|
1001
1012
|
const timeUntilEnd =
|
|
1002
1013
|
this.manifest_.presentationTimeline.getDuration() - timeNeeded;
|
|
1003
1014
|
const oneMicrosecond = 1e-6;
|
|
1004
|
-
|
|
1015
|
+
|
|
1016
|
+
const bufferEnd =
|
|
1017
|
+
this.playerInterface_.mediaSourceEngine.bufferEnd(mediaState.type);
|
|
1018
|
+
|
|
1019
|
+
if (timeUntilEnd < oneMicrosecond && !!bufferEnd) {
|
|
1005
1020
|
// We shouldn't rebuffer if the playhead is close to the end of the
|
|
1006
1021
|
// presentation.
|
|
1007
1022
|
shaka.log.debug(logPrefix, 'buffered to end of presentation');
|
|
@@ -1031,8 +1046,6 @@ shaka.media.StreamingEngine = class {
|
|
|
1031
1046
|
return this.config_.updateIntervalSeconds / 2;
|
|
1032
1047
|
}
|
|
1033
1048
|
|
|
1034
|
-
const bufferEnd =
|
|
1035
|
-
this.playerInterface_.mediaSourceEngine.bufferEnd(mediaState.type);
|
|
1036
1049
|
const reference = this.getSegmentReferenceNeeded_(
|
|
1037
1050
|
mediaState, presentationTime, bufferEnd);
|
|
1038
1051
|
if (!reference) {
|
|
@@ -1238,7 +1251,16 @@ shaka.media.StreamingEngine = class {
|
|
|
1238
1251
|
if (this.config_.lowLatencyMode && isReadableStreamSupported && isMP4 &&
|
|
1239
1252
|
!reference.hlsAes128Key) {
|
|
1240
1253
|
let remaining = new Uint8Array(0);
|
|
1254
|
+
let processingResult = false;
|
|
1255
|
+
let callbackCalled = false;
|
|
1241
1256
|
const streamDataCallback = async (data) => {
|
|
1257
|
+
if (processingResult) {
|
|
1258
|
+
// If the fallback result processing was triggered, don't also
|
|
1259
|
+
// append the buffer here. In theory this should never happen,
|
|
1260
|
+
// but it does on some older TVs.
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
callbackCalled = true;
|
|
1242
1264
|
this.destroyer_.ensureNotDestroyed();
|
|
1243
1265
|
if (this.fatalError_) {
|
|
1244
1266
|
return;
|
|
@@ -1269,7 +1291,33 @@ shaka.media.StreamingEngine = class {
|
|
|
1269
1291
|
}
|
|
1270
1292
|
};
|
|
1271
1293
|
|
|
1272
|
-
|
|
1294
|
+
const result =
|
|
1295
|
+
await this.fetch_(mediaState, reference, streamDataCallback);
|
|
1296
|
+
if (!callbackCalled) {
|
|
1297
|
+
// In some environments, we might be forced to use network plugins
|
|
1298
|
+
// that don't support streamDataCallback. In those cases, as a
|
|
1299
|
+
// fallback, append the buffer here.
|
|
1300
|
+
processingResult = true;
|
|
1301
|
+
this.destroyer_.ensureNotDestroyed();
|
|
1302
|
+
if (this.fatalError_) {
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// If the text stream gets switched between fetch_() and append_(),
|
|
1307
|
+
// the new text parser is initialized, but the new init segment is
|
|
1308
|
+
// not fetched yet. That would cause an error in
|
|
1309
|
+
// TextParser.parseMedia().
|
|
1310
|
+
// See http://b/168253400
|
|
1311
|
+
if (mediaState.waitingToClearBuffer) {
|
|
1312
|
+
shaka.log.info(logPrefix, 'waitingToClearBuffer, skip append');
|
|
1313
|
+
mediaState.performingUpdate = false;
|
|
1314
|
+
this.scheduleUpdate_(mediaState, 0);
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
await this.append_(
|
|
1319
|
+
mediaState, presentationTime, stream, reference, result);
|
|
1320
|
+
}
|
|
1273
1321
|
} else {
|
|
1274
1322
|
if (this.config_.lowLatencyMode && !isReadableStreamSupported) {
|
|
1275
1323
|
shaka.log.warning('Low latency streaming mode is enabled, but ' +
|
|
@@ -1590,6 +1638,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1590
1638
|
shaka.log.v1(logPrefix, 'appending init segment');
|
|
1591
1639
|
const hasClosedCaptions = mediaState.stream.closedCaptions &&
|
|
1592
1640
|
mediaState.stream.closedCaptions.size > 0;
|
|
1641
|
+
await this.playerInterface_.beforeAppendSegment(
|
|
1642
|
+
mediaState.type, initSegment);
|
|
1593
1643
|
await this.playerInterface_.mediaSourceEngine.appendBuffer(
|
|
1594
1644
|
mediaState.type, initSegment, /* reference= */ null,
|
|
1595
1645
|
hasClosedCaptions);
|
|
@@ -1640,14 +1690,21 @@ shaka.media.StreamingEngine = class {
|
|
|
1640
1690
|
shaka.log.v1(logPrefix, 'appending media segment at',
|
|
1641
1691
|
(reference.syncTime == null ? 'unknown' : reference.syncTime));
|
|
1642
1692
|
|
|
1693
|
+
// 'seeked' or 'adaptation' triggered logic applies only to this
|
|
1694
|
+
// appendBuffer() call.
|
|
1643
1695
|
const seeked = mediaState.seeked;
|
|
1644
1696
|
mediaState.seeked = false;
|
|
1697
|
+
const adaptation = mediaState.adaptation;
|
|
1698
|
+
mediaState.adaptation = false;
|
|
1699
|
+
|
|
1700
|
+
await this.playerInterface_.beforeAppendSegment(mediaState.type, segment);
|
|
1645
1701
|
await this.playerInterface_.mediaSourceEngine.appendBuffer(
|
|
1646
1702
|
mediaState.type,
|
|
1647
1703
|
segment,
|
|
1648
1704
|
reference,
|
|
1649
1705
|
hasClosedCaptions,
|
|
1650
|
-
seeked
|
|
1706
|
+
seeked,
|
|
1707
|
+
adaptation);
|
|
1651
1708
|
this.destroyer_.ensureNotDestroyed();
|
|
1652
1709
|
shaka.log.v2(logPrefix, 'appended media segment');
|
|
1653
1710
|
}
|
|
@@ -2024,7 +2081,9 @@ shaka.media.StreamingEngine = class {
|
|
|
2024
2081
|
* onEvent: function(!Event),
|
|
2025
2082
|
* onManifestUpdate: function(),
|
|
2026
2083
|
* onSegmentAppended: function(),
|
|
2027
|
-
* onInitSegmentAppended: function(!number,!shaka.media.InitSegmentReference)
|
|
2084
|
+
* onInitSegmentAppended: function(!number,!shaka.media.InitSegmentReference),
|
|
2085
|
+
* beforeAppendSegment: function(
|
|
2086
|
+
* shaka.util.ManifestParserUtils.ContentType,!BufferSource):Promise
|
|
2028
2087
|
* }}
|
|
2029
2088
|
*
|
|
2030
2089
|
* @property {function():number} getPresentationTime
|
|
@@ -2052,6 +2111,9 @@ shaka.media.StreamingEngine = class {
|
|
|
2052
2111
|
* @property
|
|
2053
2112
|
* {function(!number, !shaka.media.InitSegmentReference)} onInitSegmentAppended
|
|
2054
2113
|
* Called when an init segment is appended to a MediaSource.
|
|
2114
|
+
* @property {!function(shaka.util.ManifestParserUtils.ContentType,
|
|
2115
|
+
* !BufferSource):Promise} beforeAppendSegment
|
|
2116
|
+
* A function called just before appending to the source buffer.
|
|
2055
2117
|
*/
|
|
2056
2118
|
shaka.media.StreamingEngine.PlayerInterface;
|
|
2057
2119
|
|
|
@@ -2075,6 +2137,7 @@ shaka.media.StreamingEngine.PlayerInterface;
|
|
|
2075
2137
|
* clearBufferSafeMargin: number,
|
|
2076
2138
|
* clearingBuffer: boolean,
|
|
2077
2139
|
* seeked: boolean,
|
|
2140
|
+
* adaptation: boolean,
|
|
2078
2141
|
* recovering: boolean,
|
|
2079
2142
|
* hasError: boolean,
|
|
2080
2143
|
* operation: shaka.net.NetworkingEngine.PendingRequest
|
|
@@ -2121,6 +2184,8 @@ shaka.media.StreamingEngine.PlayerInterface;
|
|
|
2121
2184
|
* True indicates that the buffer is being cleared.
|
|
2122
2185
|
* @property {boolean} seeked
|
|
2123
2186
|
* True indicates that the presentation just seeked.
|
|
2187
|
+
* @property {boolean} adaptation
|
|
2188
|
+
* True indicates that the presentation just automatically switched variants.
|
|
2124
2189
|
* @property {boolean} recovering
|
|
2125
2190
|
* True indicates that the last segment was not appended because it could not
|
|
2126
2191
|
* fit in the buffer.
|
package/lib/offline/storage.js
CHANGED
package/lib/player.js
CHANGED
|
@@ -1770,6 +1770,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1770
1770
|
enableLowLatencyMode: () => {
|
|
1771
1771
|
this.configure('streaming.lowLatencyMode', true);
|
|
1772
1772
|
},
|
|
1773
|
+
updateDuration: () => {
|
|
1774
|
+
if (this.streamingEngine_) {
|
|
1775
|
+
this.streamingEngine_.updateDuration();
|
|
1776
|
+
}
|
|
1777
|
+
},
|
|
1773
1778
|
};
|
|
1774
1779
|
|
|
1775
1780
|
const startTime = Date.now() / 1000;
|
|
@@ -2952,6 +2957,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2952
2957
|
this.qualityObserver_.addMediaQualityChange(mediaQuality, position);
|
|
2953
2958
|
}
|
|
2954
2959
|
},
|
|
2960
|
+
beforeAppendSegment: (contentType, segment) => {
|
|
2961
|
+
return this.drmEngine_.parseInbandPssh(contentType, segment);
|
|
2962
|
+
},
|
|
2955
2963
|
};
|
|
2956
2964
|
|
|
2957
2965
|
return new shaka.media.StreamingEngine(this.manifest_, playerInterface);
|
|
@@ -3838,8 +3846,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3838
3846
|
return;
|
|
3839
3847
|
}
|
|
3840
3848
|
|
|
3841
|
-
this.switchVariant_(
|
|
3842
|
-
safeMargin);
|
|
3849
|
+
this.switchVariant_(
|
|
3850
|
+
variant, /* fromAdaptation= */ false, clearBuffer, safeMargin);
|
|
3843
3851
|
|
|
3844
3852
|
// Workaround for
|
|
3845
3853
|
// https://github.com/shaka-project/shaka-player/issues/1299
|
|
@@ -4459,6 +4467,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4459
4467
|
this.stats_.setBandwidthEstimate(estimate);
|
|
4460
4468
|
}
|
|
4461
4469
|
|
|
4470
|
+
if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
|
|
4471
|
+
this.stats_.setResolution(
|
|
4472
|
+
/* width= */ element.videoWidth || NaN,
|
|
4473
|
+
/* height= */ element.videoHeight || NaN);
|
|
4474
|
+
}
|
|
4475
|
+
|
|
4462
4476
|
return this.stats_.getBlob();
|
|
4463
4477
|
}
|
|
4464
4478
|
|
|
@@ -5370,7 +5384,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5370
5384
|
const currentVariant = this.streamingEngine_.getCurrentVariant();
|
|
5371
5385
|
if (variant == currentVariant) {
|
|
5372
5386
|
shaka.log.debug('Variant already selected.');
|
|
5373
|
-
// If you want to clear the buffer, we force to reselect the same variant
|
|
5387
|
+
// If you want to clear the buffer, we force to reselect the same variant.
|
|
5388
|
+
// We don't need to reset the timestampOffset since it's the same variant,
|
|
5389
|
+
// so 'adaptation' isn't passed here.
|
|
5374
5390
|
if (clearBuffer) {
|
|
5375
5391
|
this.streamingEngine_.switchVariant(variant, clearBuffer, safeMargin,
|
|
5376
5392
|
/* force= */ true);
|
|
@@ -5380,7 +5396,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5380
5396
|
|
|
5381
5397
|
// Add entries to the history.
|
|
5382
5398
|
this.addVariantToSwitchHistory_(variant, fromAdaptation);
|
|
5383
|
-
this.streamingEngine_.switchVariant(
|
|
5399
|
+
this.streamingEngine_.switchVariant(
|
|
5400
|
+
variant, clearBuffer, safeMargin, /* force= */ undefined,
|
|
5401
|
+
/* adaptation= */ fromAdaptation);
|
|
5384
5402
|
let oldTrack = null;
|
|
5385
5403
|
if (currentVariant) {
|
|
5386
5404
|
oldTrack = shaka.util.StreamUtils.variantToTrack(currentVariant);
|
|
@@ -5625,6 +5643,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5625
5643
|
return false;
|
|
5626
5644
|
}
|
|
5627
5645
|
|
|
5646
|
+
if (!navigator.onLine) {
|
|
5647
|
+
// Don't restrict variants if we're completely offline, or else we end up
|
|
5648
|
+
// rapidly restricting all of them.
|
|
5649
|
+
return false;
|
|
5650
|
+
}
|
|
5651
|
+
|
|
5628
5652
|
let maxDisabledTime = this.config_.streaming.maxDisabledTime;
|
|
5629
5653
|
if (maxDisabledTime == 0) {
|
|
5630
5654
|
if (error.code == shaka.util.Error.Code.SEGMENT_MISSING) {
|
|
@@ -6558,7 +6582,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6558
6582
|
* @define {string} A version number taken from git at compile time.
|
|
6559
6583
|
* @export
|
|
6560
6584
|
*/
|
|
6561
|
-
shaka.Player.version = 'v4.2.
|
|
6585
|
+
shaka.Player.version = 'v4.2.2-uncompiled';
|
|
6562
6586
|
|
|
6563
6587
|
// Initialize the deprecation system using the version string we just set
|
|
6564
6588
|
// on the player.
|
|
@@ -164,6 +164,12 @@ shaka.text.TtmlTextParser = class {
|
|
|
164
164
|
cellResolutionInfo, /* parentCueElement= */ null,
|
|
165
165
|
/* isContent= */ false);
|
|
166
166
|
if (cue) {
|
|
167
|
+
// According to the TTML spec, backgrounds default to transparent.
|
|
168
|
+
// So default the background of the top-level element to transparent.
|
|
169
|
+
// Nested elements may override that background color already.
|
|
170
|
+
if (!cue.backgroundColor) {
|
|
171
|
+
cue.backgroundColor = 'transparent';
|
|
172
|
+
}
|
|
167
173
|
cues.push(cue);
|
|
168
174
|
}
|
|
169
175
|
}
|
|
@@ -493,6 +493,14 @@ shaka.text.UITextDisplayer = class {
|
|
|
493
493
|
style.backgroundRepeat = 'no-repeat';
|
|
494
494
|
style.backgroundSize = 'contain';
|
|
495
495
|
style.backgroundPosition = 'center';
|
|
496
|
+
|
|
497
|
+
// Quoting https://www.w3.org/TR/ttml-imsc1.2/:
|
|
498
|
+
// "The width and height (in pixels) of the image resource referenced by
|
|
499
|
+
// smpte:backgroundImage SHALL be equal to the width and height expressed
|
|
500
|
+
// by the tts:extent attribute of the region in which the div element is
|
|
501
|
+
// presented".
|
|
502
|
+
style.width = '100%';
|
|
503
|
+
style.height = '100%';
|
|
496
504
|
} else {
|
|
497
505
|
// If we have both text and nested cues, then style everything; otherwise
|
|
498
506
|
// place the text in its own <span> so the background doesn't fill the
|
package/lib/util/error.js
CHANGED
|
@@ -656,16 +656,8 @@ shaka.util.Error.Code = {
|
|
|
656
656
|
*/
|
|
657
657
|
'CANNOT_ADD_EXTERNAL_TEXT_TO_LIVE_STREAM': 4033,
|
|
658
658
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
*/
|
|
662
|
-
'HLS_AES_128_ENCRYPTION_NOT_SUPPORTED': 4034,
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
* An internal error code that should never be seen by applications, thrown
|
|
666
|
-
* to force the HLS parser to skip an unsupported stream.
|
|
667
|
-
*/
|
|
668
|
-
'HLS_INTERNAL_SKIP_STREAM': 4035,
|
|
659
|
+
// RETIRED: 'HLS_AES_128_ENCRYPTION_NOT_SUPPORTED': 4034,
|
|
660
|
+
// RETIRED: 'HLS_INTERNAL_SKIP_STREAM': 4035,
|
|
669
661
|
|
|
670
662
|
/** The Manifest contained no Variants. */
|
|
671
663
|
'NO_VARIANTS': 4036,
|
package/lib/util/platform.js
CHANGED
|
@@ -173,6 +173,30 @@ shaka.util.Platform = class {
|
|
|
173
173
|
shaka.util.Platform.userAgentContains_('Android');
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Returns a major version number for Chrome, or Chromium-based browsers.
|
|
178
|
+
*
|
|
179
|
+
* For example:
|
|
180
|
+
* - Chrome 106.0.5249.61 returns 106.
|
|
181
|
+
* - Edge 106.0.1370.34 returns 106 (since this is based on Chromium).
|
|
182
|
+
* - Safari returns null (since this is independent of Chromium).
|
|
183
|
+
*
|
|
184
|
+
* @return {?number} A major version number or null if not Chromium-based.
|
|
185
|
+
*/
|
|
186
|
+
static chromeVersion() {
|
|
187
|
+
if (!shaka.util.Platform.userAgentContains_('Chrome')) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Looking for something like "Chrome/106.0.0.0".
|
|
192
|
+
const match = navigator.userAgent.match(/Chrome\/(\d+)/);
|
|
193
|
+
if (match) {
|
|
194
|
+
return parseInt(match[1], /* base= */ 10);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
176
200
|
/**
|
|
177
201
|
* Check if the current platform is Google Chrome.
|
|
178
202
|
*
|
|
@@ -75,6 +75,10 @@ shaka.util.PlayerConfiguration = class {
|
|
|
75
75
|
updateExpirationTime: 1,
|
|
76
76
|
preferredKeySystems: [],
|
|
77
77
|
keySystemsMapping: {},
|
|
78
|
+
// The Xbox One browser does not detect DRM key changes signalled by a
|
|
79
|
+
// change in the PSSH in media segments. We need to parse PSSH from media
|
|
80
|
+
// segments to detect key changes.
|
|
81
|
+
parseInbandPsshEnabled: shaka.util.Platform.isXboxOne(),
|
|
78
82
|
};
|
|
79
83
|
|
|
80
84
|
const manifest = {
|
|
@@ -314,6 +318,7 @@ shaka.util.PlayerConfiguration = class {
|
|
|
314
318
|
*/
|
|
315
319
|
static mergeConfigObjects(destination, updates, template) {
|
|
316
320
|
const overrides = {
|
|
321
|
+
'.drm.keySystemsMapping': '',
|
|
317
322
|
'.drm.servers': '',
|
|
318
323
|
'.drm.clearKeys': '',
|
|
319
324
|
'.drm.advanced': {
|
package/lib/util/pssh.js
CHANGED
|
@@ -42,11 +42,12 @@ shaka.util.Pssh = class {
|
|
|
42
42
|
|
|
43
43
|
new shaka.util.Mp4Parser()
|
|
44
44
|
.box('moov', shaka.util.Mp4Parser.children)
|
|
45
|
+
.box('moof', shaka.util.Mp4Parser.children)
|
|
45
46
|
.fullBox('pssh', (box) => this.parsePsshBox_(box))
|
|
46
47
|
.parse(psshBox);
|
|
47
48
|
|
|
48
49
|
if (this.data.length == 0) {
|
|
49
|
-
shaka.log.
|
|
50
|
+
shaka.log.v2('No pssh box found!');
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -474,7 +474,8 @@ shaka.util.StreamUtils = class {
|
|
|
474
474
|
if (shaka.util.Platform.isXboxOne() && video &&
|
|
475
475
|
((video.width && video.width > 1920) ||
|
|
476
476
|
(video.height && video.height > 1080)) &&
|
|
477
|
-
video.codecs.includes('avc1.')
|
|
477
|
+
(video.codecs.includes('avc1.') ||
|
|
478
|
+
video.codecs.includes('avc3.'))) {
|
|
478
479
|
shaka.log.debug('Dropping variant - not compatible with platform',
|
|
479
480
|
shaka.util.StreamUtils.getVariantSummaryString_(variant));
|
|
480
481
|
return false;
|
package/package.json
CHANGED
|
@@ -295,5 +295,93 @@ describe('Cea608Memory', () => {
|
|
|
295
295
|
const caption = memory.forceEmit(startTime, endTime);
|
|
296
296
|
expect(caption).toEqual(expectedCaption);
|
|
297
297
|
});
|
|
298
|
+
|
|
299
|
+
it('does not move rows if source row index is negative', () => {
|
|
300
|
+
const startTime = 1;
|
|
301
|
+
const endTime = 2;
|
|
302
|
+
const text = 'test';
|
|
303
|
+
|
|
304
|
+
// Add the text to the buffer, each character on separate rows.
|
|
305
|
+
// At this point, the memory looks like:
|
|
306
|
+
// [1]: t
|
|
307
|
+
// [2]: e
|
|
308
|
+
// [3]: s
|
|
309
|
+
// [4]: t
|
|
310
|
+
for (const c of text) {
|
|
311
|
+
memory.addChar(CharSet.BASIC_NORTH_AMERICAN,
|
|
312
|
+
c.charCodeAt(0));
|
|
313
|
+
memory.setRow(memory.getRow() + 1); // increment row
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const srcRowIdx = -1;
|
|
317
|
+
const dstRowIdx = 2;
|
|
318
|
+
const rowsToMove = 3;
|
|
319
|
+
memory.moveRows(dstRowIdx, srcRowIdx, rowsToMove);
|
|
320
|
+
|
|
321
|
+
// Expected text is 't\ne\ns\nt'
|
|
322
|
+
const topLevelCue = new shaka.text.Cue(startTime, endTime, '');
|
|
323
|
+
topLevelCue.nestedCues = [
|
|
324
|
+
CeaUtils.createDefaultCue(startTime, endTime, 't'),
|
|
325
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
326
|
+
CeaUtils.createDefaultCue(startTime, endTime, 'e'),
|
|
327
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
328
|
+
CeaUtils.createDefaultCue(startTime, endTime, 's'),
|
|
329
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
330
|
+
CeaUtils.createDefaultCue(startTime, endTime, 't'),
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
const expectedCaption = {
|
|
334
|
+
stream,
|
|
335
|
+
cue: topLevelCue,
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Force out the new memory.
|
|
339
|
+
const caption = memory.forceEmit(startTime, endTime);
|
|
340
|
+
expect(caption).toEqual(expectedCaption);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('does not move rows if destination row index is negative', () => {
|
|
344
|
+
const startTime = 1;
|
|
345
|
+
const endTime = 2;
|
|
346
|
+
const text = 'test';
|
|
347
|
+
|
|
348
|
+
// Add the text to the buffer, each character on separate rows.
|
|
349
|
+
// At this point, the memory looks like:
|
|
350
|
+
// [1]: t
|
|
351
|
+
// [2]: e
|
|
352
|
+
// [3]: s
|
|
353
|
+
// [4]: t
|
|
354
|
+
for (const c of text) {
|
|
355
|
+
memory.addChar(CharSet.BASIC_NORTH_AMERICAN,
|
|
356
|
+
c.charCodeAt(0));
|
|
357
|
+
memory.setRow(memory.getRow() + 1); // increment row
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const srcRowIdx = 1;
|
|
361
|
+
const dstRowIdx = -2;
|
|
362
|
+
const rowsToMove = 3;
|
|
363
|
+
memory.moveRows(dstRowIdx, srcRowIdx, rowsToMove);
|
|
364
|
+
|
|
365
|
+
// Expected text is 't\ne\ns\nt'
|
|
366
|
+
const topLevelCue = new shaka.text.Cue(startTime, endTime, '');
|
|
367
|
+
topLevelCue.nestedCues = [
|
|
368
|
+
CeaUtils.createDefaultCue(startTime, endTime, 't'),
|
|
369
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
370
|
+
CeaUtils.createDefaultCue(startTime, endTime, 'e'),
|
|
371
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
372
|
+
CeaUtils.createDefaultCue(startTime, endTime, 's'),
|
|
373
|
+
CeaUtils.createLineBreakCue(startTime, endTime),
|
|
374
|
+
CeaUtils.createDefaultCue(startTime, endTime, 't'),
|
|
375
|
+
];
|
|
376
|
+
|
|
377
|
+
const expectedCaption = {
|
|
378
|
+
stream,
|
|
379
|
+
cue: topLevelCue,
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// Force out the new memory.
|
|
383
|
+
const caption = memory.forceEmit(startTime, endTime);
|
|
384
|
+
expect(caption).toEqual(expectedCaption);
|
|
385
|
+
});
|
|
298
386
|
});
|
|
299
387
|
});
|