shaka-player 3.2.0 → 3.2.4
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 +73 -0
- package/.github/workflows/release.yaml +53 -0
- package/AUTHORS +3 -0
- package/CHANGELOG.md +182 -1
- package/CONTRIBUTORS +3 -0
- package/README.md +4 -4
- package/build/checkversion.py +1 -1
- package/build/generateExterns.js +1 -1
- package/build/misspellings.txt +1 -0
- package/build/shakaBuildHelpers.py +6 -5
- package/build/subprocessWindowsPatch.py +103 -0
- package/build/test.py +6 -0
- package/demo/common/assets.js +1 -1
- package/demo/index.html +1 -1
- package/dist/controls.css +2 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +1 -1
- package/dist/demo.compiled.js +1 -1
- package/dist/deps.js +8 -8
- package/dist/locales.js +37 -37
- package/dist/receiver.compiled.debug.js +1 -1
- package/dist/receiver.compiled.js +1 -1
- package/dist/shaka-player.compiled.d.ts +195 -5
- package/dist/shaka-player.compiled.debug.d.ts +195 -5
- package/dist/shaka-player.compiled.debug.externs.js +226 -14
- package/dist/shaka-player.compiled.debug.js +424 -399
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +226 -14
- package/dist/shaka-player.compiled.js +826 -812
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +197 -16
- package/dist/shaka-player.ui.debug.d.ts +197 -16
- package/dist/shaka-player.ui.debug.externs.js +230 -27
- package/dist/shaka-player.ui.debug.js +466 -442
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +230 -27
- package/dist/shaka-player.ui.js +946 -932
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/fairplay.md +1 -1
- package/docs/tutorials/faq.md +14 -0
- package/docs/tutorials/ui.md +5 -5
- package/externs/ima.js +4 -0
- package/externs/shaka/ads.js +25 -0
- package/externs/shaka/player.js +8 -0
- package/externs/shaka/text.js +10 -0
- package/karma.conf.js +20 -15
- package/lib/ads/ad_manager.js +46 -26
- package/lib/ads/client_side_ad_manager.js +82 -88
- package/lib/ads/server_side_ad_manager.js +45 -13
- package/lib/cast/cast_proxy.js +6 -2
- package/lib/cast/cast_receiver.js +5 -1
- package/lib/cast/cast_sender.js +2 -2
- package/lib/cea/sei_processor.js +24 -17
- package/lib/dash/dash_parser.js +131 -24
- package/lib/dash/segment_list.js +1 -1
- package/lib/dash/segment_template.js +1 -1
- package/lib/hls/hls_parser.js +74 -17
- package/lib/hls/manifest_text_parser.js +1 -0
- package/lib/media/closed_caption_parser.js +3 -1
- package/lib/media/content_workarounds.js +12 -0
- package/lib/media/drm_engine.js +20 -1
- package/lib/media/gap_jumping_controller.js +8 -4
- package/lib/media/media_source_engine.js +1 -2
- package/lib/media/playhead.js +1 -1
- package/lib/media/region_observer.js +39 -33
- package/lib/media/region_timeline.js +17 -19
- package/lib/media/segment_index.js +2 -1
- package/lib/media/segment_reference.js +19 -1
- package/lib/media/streaming_engine.js +8 -4
- package/lib/net/networking_engine.js +6 -1
- package/lib/offline/download_manager.js +1 -2
- package/lib/offline/storage.js +8 -9
- package/lib/player.js +129 -59
- package/lib/polyfill/aria.js +12 -2
- package/lib/polyfill/encryption_scheme.js +2 -0
- package/lib/polyfill/fullscreen.js +2 -0
- package/lib/polyfill/mathround.js +2 -0
- package/lib/polyfill/media_capabilities.js +73 -21
- package/lib/polyfill/mediasource.js +8 -0
- package/lib/polyfill/orientation.js +3 -1
- package/lib/polyfill/patchedmediakeys_apple.js +6 -4
- package/lib/polyfill/patchedmediakeys_ms.js +6 -4
- package/lib/polyfill/patchedmediakeys_nop.js +2 -0
- package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
- package/lib/polyfill/pip_webkit.js +2 -0
- package/lib/polyfill/storage_estimate.js +2 -0
- package/lib/polyfill/video_play_promise.js +2 -0
- package/lib/polyfill/videoplaybackquality.js +2 -0
- package/lib/polyfill/vttcue.js +2 -0
- package/lib/text/cue.js +6 -0
- package/lib/text/mp4_ttml_parser.js +3 -0
- package/lib/text/text_engine.js +14 -2
- package/lib/text/ttml_text_parser.js +135 -87
- package/lib/text/ui_text_displayer.js +213 -80
- package/lib/text/vtt_text_parser.js +4 -2
- package/lib/text/web_vtt_generator.js +21 -12
- package/lib/util/fake_event.js +53 -7
- package/lib/util/fake_event_target.js +21 -1
- package/lib/util/mp4_box_parsers.js +1 -1
- package/lib/util/periods.js +4 -1
- package/lib/util/platform.js +13 -1
- package/lib/util/stream_utils.js +105 -13
- package/lib/util/xml_utils.js +50 -12
- package/package.json +3 -6
- package/roadmap.md +24 -22
- package/test/cast/cast_proxy_unit.js +12 -6
- package/test/cast/cast_receiver_integration.js +1 -1
- package/test/cast/cast_utils_unit.js +1 -1
- package/test/cea/mp4_cea_parser_unit.js +26 -0
- package/test/dash/dash_parser_live_unit.js +43 -0
- package/test/dash/dash_parser_manifest_unit.js +222 -3
- package/test/hls/hls_parser_unit.js +79 -1
- package/test/media/closed_caption_parser_unit.js +20 -0
- package/test/media/drm_engine_integration.js +86 -2
- package/test/media/drm_engine_unit.js +57 -0
- package/test/media/playhead_unit.js +27 -1
- package/test/media/region_observer_unit.js +40 -5
- package/test/media/region_timeline_unit.js +40 -22
- package/test/media/streaming_engine_integration.js +2 -1
- package/test/media/streaming_engine_unit.js +145 -0
- package/test/player_integration.js +79 -8
- package/test/player_src_equals_integration.js +41 -8
- package/test/player_unit.js +14 -4
- package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
- package/test/test/assets/3675/dash_0.mpd +30 -0
- package/test/test/assets/3675/dash_1.mpd +30 -0
- package/test/test/assets/3675/dash_2.mpd +30 -0
- package/test/test/assets/3675/dash_3.mpd +30 -0
- package/test/test/assets/3675/dash_4.mpd +30 -0
- package/test/test/assets/3675/dash_5.mpd +26 -0
- package/test/test/assets/3675/init-stream0.m4s +0 -0
- package/test/test/assets/chapters.vtt +2 -2
- package/test/test/assets/chapters2.vtt +10 -0
- package/test/test/assets/empty_caption_video_init.mp4 +0 -0
- package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
- package/test/test/util/fake_ad_manager.js +11 -1
- package/test/test/util/ttml_utils.js +81 -0
- package/test/test/util/util.js +10 -7
- package/test/text/mp4_ttml_parser_unit.js +11 -18
- package/test/text/ttml_text_parser_unit.js +542 -220
- package/test/text/ui_text_displayer_unit.js +93 -20
- package/test/text/vtt_text_parser_unit.js +17 -1
- package/test/text/web_vtt_generator_unit.js +58 -3
- package/test/ui/localization_unit.js +15 -14
- package/test/ui/text_displayer_layout_unit.js +21 -8
- package/test/util/mp4_box_parsers_unit.js +28 -0
- package/test/util/stream_utils_unit.js +60 -3
- package/ui/cast_button.js +2 -3
- package/ui/controls.js +11 -6
- package/ui/less/buttons.less +0 -4
- package/ui/less/containers.less +6 -7
- package/ui/locales/ru.json +6 -6
- package/ui/localization.js +40 -87
- package/ui/pip_button.js +2 -3
- package/ui/seek_bar.js +1 -2
- package/ui/text_selection.js +1 -0
package/lib/dash/dash_parser.js
CHANGED
|
@@ -95,6 +95,18 @@ shaka.dash.DashParser = class {
|
|
|
95
95
|
/** @private {!shaka.util.OperationManager} */
|
|
96
96
|
this.operationManager_ = new shaka.util.OperationManager();
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Largest period start time seen.
|
|
100
|
+
* @private {?number}
|
|
101
|
+
*/
|
|
102
|
+
this.largestPeriodStartTime_ = null;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Period IDs seen in previous manifest.
|
|
106
|
+
* @private {!Array.<number>}
|
|
107
|
+
*/
|
|
108
|
+
this.lastManifestUpdatePeriodIds_ = [];
|
|
109
|
+
|
|
98
110
|
/**
|
|
99
111
|
* The minimum of the availabilityTimeOffset values among the adaptation
|
|
100
112
|
* sets.
|
|
@@ -389,6 +401,24 @@ shaka.dash.DashParser = class {
|
|
|
389
401
|
this.config_.dash.autoCorrectDrift);
|
|
390
402
|
}
|
|
391
403
|
|
|
404
|
+
presentationTimeline.setStatic(mpdType == 'static');
|
|
405
|
+
|
|
406
|
+
const isLive = presentationTimeline.isLive();
|
|
407
|
+
|
|
408
|
+
// If it's live, we check for an override.
|
|
409
|
+
if (isLive && !isNaN(this.config_.availabilityWindowOverride)) {
|
|
410
|
+
segmentAvailabilityDuration = this.config_.availabilityWindowOverride;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// If it's null, that means segments are always available. This is always
|
|
414
|
+
// the case for VOD, and sometimes the case for live.
|
|
415
|
+
if (segmentAvailabilityDuration == null) {
|
|
416
|
+
segmentAvailabilityDuration = Infinity;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
presentationTimeline.setSegmentAvailabilityDuration(
|
|
420
|
+
segmentAvailabilityDuration);
|
|
421
|
+
|
|
392
422
|
const profiles = mpd.getAttribute('profiles') || '';
|
|
393
423
|
|
|
394
424
|
/** @type {shaka.dash.DashParser.Context} */
|
|
@@ -411,7 +441,6 @@ shaka.dash.DashParser = class {
|
|
|
411
441
|
const duration = periodsAndDuration.duration;
|
|
412
442
|
const periods = periodsAndDuration.periods;
|
|
413
443
|
|
|
414
|
-
presentationTimeline.setStatic(mpdType == 'static');
|
|
415
444
|
if (mpdType == 'static' ||
|
|
416
445
|
!periodsAndDuration.durationDerivedFromPeriods) {
|
|
417
446
|
// Ignore duration calculated from Period lengths if this is dynamic.
|
|
@@ -430,6 +459,7 @@ shaka.dash.DashParser = class {
|
|
|
430
459
|
this.lowLatencyMode_ = this.playerInterface_.isLowLatencyMode();
|
|
431
460
|
}
|
|
432
461
|
}
|
|
462
|
+
|
|
433
463
|
if (this.lowLatencyMode_) {
|
|
434
464
|
presentationTimeline.setAvailabilityTimeOffset(
|
|
435
465
|
this.minTotalAvailabilityTimeOffset_);
|
|
@@ -443,22 +473,6 @@ shaka.dash.DashParser = class {
|
|
|
443
473
|
'https://bit.ly/3clctcj for details.');
|
|
444
474
|
}
|
|
445
475
|
|
|
446
|
-
const isLive = presentationTimeline.isLive();
|
|
447
|
-
|
|
448
|
-
// If it's live, we check for an override.
|
|
449
|
-
if (isLive && !isNaN(this.config_.availabilityWindowOverride)) {
|
|
450
|
-
segmentAvailabilityDuration = this.config_.availabilityWindowOverride;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// If it's null, that means segments are always available. This is always
|
|
454
|
-
// the case for VOD, and sometimes the case for live.
|
|
455
|
-
if (segmentAvailabilityDuration == null) {
|
|
456
|
-
segmentAvailabilityDuration = Infinity;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
presentationTimeline.setSegmentAvailabilityDuration(
|
|
460
|
-
segmentAvailabilityDuration);
|
|
461
|
-
|
|
462
476
|
// Use @maxSegmentDuration to override smaller, derived values.
|
|
463
477
|
presentationTimeline.notifyMaxSegmentDuration(maxSegmentDuration || 1);
|
|
464
478
|
if (goog.DEBUG) {
|
|
@@ -538,6 +552,7 @@ shaka.dash.DashParser = class {
|
|
|
538
552
|
for (const {i, item: elem, next} of enumerate(periodNodes)) {
|
|
539
553
|
const start = /** @type {number} */ (
|
|
540
554
|
XmlUtils.parseAttr(elem, 'start', XmlUtils.parseDuration, prevEnd));
|
|
555
|
+
const periodId = elem.id;
|
|
541
556
|
const givenDuration =
|
|
542
557
|
XmlUtils.parseAttr(elem, 'duration', XmlUtils.parseDuration);
|
|
543
558
|
|
|
@@ -571,6 +586,42 @@ shaka.dash.DashParser = class {
|
|
|
571
586
|
periodDuration = givenDuration;
|
|
572
587
|
}
|
|
573
588
|
|
|
589
|
+
/**
|
|
590
|
+
* This is to improve robustness when the player observes manifest with
|
|
591
|
+
* past periods that are inconsistent to previous ones.
|
|
592
|
+
*
|
|
593
|
+
* This may happen when a CDN or proxy server switches its upstream from
|
|
594
|
+
* one encoder to another redundant encoder.
|
|
595
|
+
*
|
|
596
|
+
* Skip periods that match all of the following criteria:
|
|
597
|
+
* - Start time is earlier than latest period start time ever seen
|
|
598
|
+
* - Period ID is never seen in the previous manifest
|
|
599
|
+
* - Not the last period in the manifest
|
|
600
|
+
*
|
|
601
|
+
* Periods that meet the aforementioned criteria are considered invalid
|
|
602
|
+
* and should be safe to discard.
|
|
603
|
+
*/
|
|
604
|
+
|
|
605
|
+
if (this.largestPeriodStartTime_ !== null &&
|
|
606
|
+
periodId !== null && start !== null &&
|
|
607
|
+
start < this.largestPeriodStartTime_ &&
|
|
608
|
+
!this.lastManifestUpdatePeriodIds_.includes(periodId) &&
|
|
609
|
+
i + 1 != periodNodes.length) {
|
|
610
|
+
shaka.log.debug(
|
|
611
|
+
`Skipping Period with ID ${periodId} as its start time is smaller` +
|
|
612
|
+
' than the largest period start time that has been seen, and ID ' +
|
|
613
|
+
'is unseen before');
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
// Save maximum period start time if it is the last period
|
|
619
|
+
if (start !== null &&
|
|
620
|
+
(this.largestPeriodStartTime_ === null ||
|
|
621
|
+
start > this.largestPeriodStartTime_)) {
|
|
622
|
+
this.largestPeriodStartTime_ = start;
|
|
623
|
+
}
|
|
624
|
+
|
|
574
625
|
// Parse child nodes.
|
|
575
626
|
const info = {
|
|
576
627
|
start: start,
|
|
@@ -602,6 +653,9 @@ shaka.dash.DashParser = class {
|
|
|
602
653
|
prevEnd = start + periodDuration;
|
|
603
654
|
} // end of period parsing loop
|
|
604
655
|
|
|
656
|
+
// Replace previous seen periods with the current one.
|
|
657
|
+
this.lastManifestUpdatePeriodIds_ = periods.map((el) => el.id);
|
|
658
|
+
|
|
605
659
|
if (presentationDuration != null) {
|
|
606
660
|
if (prevEnd != presentationDuration) {
|
|
607
661
|
shaka.log.warning(
|
|
@@ -653,8 +707,12 @@ shaka.dash.DashParser = class {
|
|
|
653
707
|
|
|
654
708
|
const eventStreamNodes =
|
|
655
709
|
XmlUtils.findChildren(periodInfo.node, 'EventStream');
|
|
710
|
+
const availabilityStart =
|
|
711
|
+
context.presentationTimeline.getSegmentAvailabilityStart();
|
|
712
|
+
|
|
656
713
|
for (const node of eventStreamNodes) {
|
|
657
|
-
this.parseEventStream_(
|
|
714
|
+
this.parseEventStream_(
|
|
715
|
+
periodInfo.start, periodInfo.duration, node, availabilityStart);
|
|
658
716
|
}
|
|
659
717
|
|
|
660
718
|
const adaptationSetNodes =
|
|
@@ -813,6 +871,25 @@ shaka.dash.DashParser = class {
|
|
|
813
871
|
}
|
|
814
872
|
}
|
|
815
873
|
|
|
874
|
+
// Parallel for HLS VIDEO-RANGE as defined in DASH-IF IOP v4.3 6.2.5.1.
|
|
875
|
+
let videoRange;
|
|
876
|
+
const videoRangeScheme = 'urn:mpeg:mpegB:cicp:TransferCharacteristics';
|
|
877
|
+
const getVideoRangeFromTransferCharacteristicCICP = (cicp) => {
|
|
878
|
+
switch (cicp) {
|
|
879
|
+
case 1:
|
|
880
|
+
case 6:
|
|
881
|
+
case 13:
|
|
882
|
+
case 14:
|
|
883
|
+
case 15:
|
|
884
|
+
return 'SDR';
|
|
885
|
+
case 16:
|
|
886
|
+
return 'PQ';
|
|
887
|
+
case 18:
|
|
888
|
+
return 'HLG';
|
|
889
|
+
}
|
|
890
|
+
return undefined;
|
|
891
|
+
};
|
|
892
|
+
|
|
816
893
|
const essentialProperties =
|
|
817
894
|
XmlUtils.findChildren(elem, 'EssentialProperty');
|
|
818
895
|
// ID of real AdaptationSet if this is a trick mode set:
|
|
@@ -822,11 +899,26 @@ shaka.dash.DashParser = class {
|
|
|
822
899
|
const schemeId = prop.getAttribute('schemeIdUri');
|
|
823
900
|
if (schemeId == 'http://dashif.org/guidelines/trickmode') {
|
|
824
901
|
trickModeFor = prop.getAttribute('value');
|
|
902
|
+
} else if (schemeId == videoRangeScheme) {
|
|
903
|
+
videoRange = getVideoRangeFromTransferCharacteristicCICP(
|
|
904
|
+
parseInt(prop.getAttribute('value'), 10),
|
|
905
|
+
);
|
|
825
906
|
} else {
|
|
826
907
|
unrecognizedEssentialProperty = true;
|
|
827
908
|
}
|
|
828
909
|
}
|
|
829
910
|
|
|
911
|
+
const supplementalProperties =
|
|
912
|
+
XmlUtils.findChildren(elem, 'SupplementalProperty');
|
|
913
|
+
for (const prop of supplementalProperties) {
|
|
914
|
+
const schemeId = prop.getAttribute('schemeIdUri');
|
|
915
|
+
if (schemeId == videoRangeScheme) {
|
|
916
|
+
videoRange = getVideoRangeFromTransferCharacteristicCICP(
|
|
917
|
+
parseInt(prop.getAttribute('value'), 10),
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
830
922
|
const accessibilities = XmlUtils.findChildren(elem, 'Accessibility');
|
|
831
923
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
832
924
|
const closedCaptions = new Map();
|
|
@@ -863,7 +955,9 @@ shaka.dash.DashParser = class {
|
|
|
863
955
|
channel = channelAndLanguage[0].startsWith('CC') ?
|
|
864
956
|
channelAndLanguage[0] : `CC${channelAndLanguage[0]}`;
|
|
865
957
|
|
|
866
|
-
|
|
958
|
+
// 3 letters (ISO 639-2). In b/187442669, we saw a blank string
|
|
959
|
+
// (CC2=;CC3=), so default to "und" (the code for "undetermined").
|
|
960
|
+
language = channelAndLanguage[1] || 'und';
|
|
867
961
|
}
|
|
868
962
|
closedCaptions.set(channel, LanguageUtils.normalize(language));
|
|
869
963
|
}
|
|
@@ -950,8 +1044,13 @@ shaka.dash.DashParser = class {
|
|
|
950
1044
|
// Parse Representations into Streams.
|
|
951
1045
|
const representations = XmlUtils.findChildren(elem, 'Representation');
|
|
952
1046
|
const streams = representations.map((representation) => {
|
|
953
|
-
|
|
954
|
-
language, label, main, roleValues,
|
|
1047
|
+
const parsedRepresentation = this.parseRepresentation_(context,
|
|
1048
|
+
contentProtection, kind, language, label, main, roleValues,
|
|
1049
|
+
closedCaptions, representation);
|
|
1050
|
+
if (parsedRepresentation) {
|
|
1051
|
+
parsedRepresentation.hdr = parsedRepresentation.hdr || videoRange;
|
|
1052
|
+
}
|
|
1053
|
+
return parsedRepresentation;
|
|
955
1054
|
}).filter((s) => !!s);
|
|
956
1055
|
|
|
957
1056
|
if (streams.length == 0) {
|
|
@@ -1127,8 +1226,10 @@ shaka.dash.DashParser = class {
|
|
|
1127
1226
|
|
|
1128
1227
|
let forced = false;
|
|
1129
1228
|
if (isText) {
|
|
1130
|
-
// See: https://github.com/google/shaka-player/issues/2122
|
|
1131
|
-
|
|
1229
|
+
// See: https://github.com/google/shaka-player/issues/2122 and
|
|
1230
|
+
// https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/165
|
|
1231
|
+
forced = roles.includes('forced_subtitle') ||
|
|
1232
|
+
roles.includes('forced-subtitle');
|
|
1132
1233
|
}
|
|
1133
1234
|
|
|
1134
1235
|
let tilesLayout;
|
|
@@ -1642,9 +1743,10 @@ shaka.dash.DashParser = class {
|
|
|
1642
1743
|
* @param {number} periodStart
|
|
1643
1744
|
* @param {?number} periodDuration
|
|
1644
1745
|
* @param {!Element} elem
|
|
1746
|
+
* @param {number} availabilityStart
|
|
1645
1747
|
* @private
|
|
1646
1748
|
*/
|
|
1647
|
-
parseEventStream_(periodStart, periodDuration, elem) {
|
|
1749
|
+
parseEventStream_(periodStart, periodDuration, elem, availabilityStart) {
|
|
1648
1750
|
const XmlUtils = shaka.util.XmlUtils;
|
|
1649
1751
|
const parseNumber = XmlUtils.parseNonNegativeInt;
|
|
1650
1752
|
|
|
@@ -1667,6 +1769,11 @@ shaka.dash.DashParser = class {
|
|
|
1667
1769
|
endTime = Math.min(endTime, periodStart + periodDuration);
|
|
1668
1770
|
}
|
|
1669
1771
|
|
|
1772
|
+
// Don't add unavailable regions to the timeline.
|
|
1773
|
+
if (endTime < availabilityStart) {
|
|
1774
|
+
continue;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1670
1777
|
/** @type {shaka.extern.TimelineRegionInfo} */
|
|
1671
1778
|
const region = {
|
|
1672
1779
|
schemeIdUri: schemeIdUri,
|
package/lib/dash/segment_list.js
CHANGED
|
@@ -63,12 +63,12 @@ shaka.dash.SegmentList = class {
|
|
|
63
63
|
const start = context.presentationTimeline.getSegmentAvailabilityStart();
|
|
64
64
|
segmentIndex.mergeAndEvict(references, start);
|
|
65
65
|
} else {
|
|
66
|
-
context.presentationTimeline.notifySegments(references);
|
|
67
66
|
segmentIndex = new shaka.media.SegmentIndex(references);
|
|
68
67
|
if (id && context.dynamic) {
|
|
69
68
|
segmentIndexMap[id] = segmentIndex;
|
|
70
69
|
}
|
|
71
70
|
}
|
|
71
|
+
context.presentationTimeline.notifySegments(references);
|
|
72
72
|
|
|
73
73
|
if (!context.dynamic || !context.periodInfo.isLastPeriod) {
|
|
74
74
|
const periodStart = context.periodInfo.start;
|
|
@@ -116,12 +116,12 @@ shaka.dash.SegmentTemplate = class {
|
|
|
116
116
|
segmentIndex.mergeAndEvict(references,
|
|
117
117
|
context.presentationTimeline.getSegmentAvailabilityStart());
|
|
118
118
|
} else {
|
|
119
|
-
context.presentationTimeline.notifySegments(references);
|
|
120
119
|
segmentIndex = new shaka.media.SegmentIndex(references);
|
|
121
120
|
if (id && context.dynamic) {
|
|
122
121
|
segmentIndexMap[id] = segmentIndex;
|
|
123
122
|
}
|
|
124
123
|
}
|
|
124
|
+
context.presentationTimeline.notifySegments(references);
|
|
125
125
|
|
|
126
126
|
if (shouldFit) {
|
|
127
127
|
segmentIndex.fit(periodStart, periodEnd);
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -418,18 +418,16 @@ shaka.hls.HlsParser = class {
|
|
|
418
418
|
const uri = tag.getAttributeValue('URI');
|
|
419
419
|
const language = tag.getAttributeValue('LANGUAGE');
|
|
420
420
|
const value = tag.getAttributeValue('VALUE');
|
|
421
|
-
const data =
|
|
422
|
-
'id': id,
|
|
423
|
-
};
|
|
421
|
+
const data = (new Map()).set('id', id);
|
|
424
422
|
if (uri) {
|
|
425
|
-
data.uri
|
|
426
|
-
shaka.hls.Utils.constructAbsoluteUri(this.masterPlaylistUri_, uri);
|
|
423
|
+
data.set('uri',
|
|
424
|
+
shaka.hls.Utils.constructAbsoluteUri(this.masterPlaylistUri_, uri));
|
|
427
425
|
}
|
|
428
426
|
if (language) {
|
|
429
|
-
data.language
|
|
427
|
+
data.set('language', language);
|
|
430
428
|
}
|
|
431
429
|
if (value) {
|
|
432
|
-
data.value
|
|
430
|
+
data.set('value', value);
|
|
433
431
|
}
|
|
434
432
|
const event = new shaka.util.FakeEvent('sessiondata', data);
|
|
435
433
|
if (this.playerInterface_) {
|
|
@@ -534,6 +532,30 @@ shaka.hls.HlsParser = class {
|
|
|
534
532
|
}
|
|
535
533
|
}
|
|
536
534
|
|
|
535
|
+
// Now that the content has been fit, notify segments.
|
|
536
|
+
this.segmentsToNotifyByStream_ = [];
|
|
537
|
+
const streamsToNotify = [];
|
|
538
|
+
for (const variant of variants) {
|
|
539
|
+
for (const stream of [variant.video, variant.audio]) {
|
|
540
|
+
if (stream) {
|
|
541
|
+
streamsToNotify.push(stream);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
await Promise.all(streamsToNotify.map(async (stream) => {
|
|
546
|
+
await stream.createSegmentIndex();
|
|
547
|
+
}));
|
|
548
|
+
for (const stream of streamsToNotify) {
|
|
549
|
+
this.segmentsToNotifyByStream_.push(stream.segmentIndex.references);
|
|
550
|
+
}
|
|
551
|
+
this.notifySegments_();
|
|
552
|
+
|
|
553
|
+
// This asserts that the live edge is being calculated from segment times.
|
|
554
|
+
// For VOD and event streams, this check should still pass.
|
|
555
|
+
goog.asserts.assert(
|
|
556
|
+
!this.presentationTimeline_.usingPresentationStartTime(),
|
|
557
|
+
'We should not be using the presentation start time in HLS!');
|
|
558
|
+
|
|
537
559
|
this.manifest_ = {
|
|
538
560
|
presentationTimeline: this.presentationTimeline_,
|
|
539
561
|
variants,
|
|
@@ -1184,7 +1206,7 @@ shaka.hls.HlsParser = class {
|
|
|
1184
1206
|
// "AUTOSELECT=YES". A value of "AUTOSELECT=NO" would imply that it may
|
|
1185
1207
|
// only be selected explicitly by the user, and we don't have a way to
|
|
1186
1208
|
// represent that in our model.
|
|
1187
|
-
const defaultAttrValue = tag.
|
|
1209
|
+
const defaultAttrValue = tag.getAttributeValue('DEFAULT');
|
|
1188
1210
|
const primary = defaultAttrValue == 'YES';
|
|
1189
1211
|
|
|
1190
1212
|
const channelsCount = type == 'audio' ? this.getChannelsCount_(tag) : null;
|
|
@@ -1258,6 +1280,30 @@ shaka.hls.HlsParser = class {
|
|
|
1258
1280
|
if (this.uriToStreamInfosMap_.has(verbatimImagePlaylistUri)) {
|
|
1259
1281
|
return this.uriToStreamInfosMap_.get(verbatimImagePlaylistUri);
|
|
1260
1282
|
}
|
|
1283
|
+
|
|
1284
|
+
// Parse misc attributes.
|
|
1285
|
+
const resolution = tag.getAttributeValue('RESOLUTION');
|
|
1286
|
+
if (resolution) {
|
|
1287
|
+
// The RESOLUTION tag represents the resolution of a single thumbnail, not
|
|
1288
|
+
// of the entire sheet at once (like we expect in the output).
|
|
1289
|
+
// So multiply by the layout size.
|
|
1290
|
+
|
|
1291
|
+
const reference = streamInfo.stream.segmentIndex.get(0);
|
|
1292
|
+
const layout = reference.getTilesLayout();
|
|
1293
|
+
if (layout) {
|
|
1294
|
+
streamInfo.stream.width =
|
|
1295
|
+
Number(resolution.split('x')[0]) * Number(layout.split('x')[0]);
|
|
1296
|
+
streamInfo.stream.height =
|
|
1297
|
+
Number(resolution.split('x')[1]) * Number(layout.split('x')[1]);
|
|
1298
|
+
// TODO: What happens if there are multiple grids, with different
|
|
1299
|
+
// layout sizes, inside this image stream?
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
const bandwidth = tag.getAttributeValue('BANDWIDTH');
|
|
1303
|
+
if (bandwidth) {
|
|
1304
|
+
streamInfo.stream.bandwidth = Number(bandwidth);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1261
1307
|
this.uriToStreamInfosMap_.set(verbatimImagePlaylistUri, streamInfo);
|
|
1262
1308
|
return streamInfo;
|
|
1263
1309
|
}
|
|
@@ -1617,12 +1663,6 @@ shaka.hls.HlsParser = class {
|
|
|
1617
1663
|
}
|
|
1618
1664
|
|
|
1619
1665
|
this.notifySegments_();
|
|
1620
|
-
|
|
1621
|
-
// This asserts that the live edge is being calculated from segment times.
|
|
1622
|
-
// For VOD and event streams, this check should still pass.
|
|
1623
|
-
goog.asserts.assert(
|
|
1624
|
-
!this.presentationTimeline_.usingPresentationStartTime(),
|
|
1625
|
-
'We should not be using the presentation start time in HLS!');
|
|
1626
1666
|
}
|
|
1627
1667
|
|
|
1628
1668
|
/**
|
|
@@ -1795,6 +1835,7 @@ shaka.hls.HlsParser = class {
|
|
|
1795
1835
|
}
|
|
1796
1836
|
|
|
1797
1837
|
let tilesLayout = '';
|
|
1838
|
+
let tileDuration = null;
|
|
1798
1839
|
if (type == shaka.util.ManifestParserUtils.ContentType.IMAGE) {
|
|
1799
1840
|
// By default in HLS the tilesLayout is 1x1
|
|
1800
1841
|
tilesLayout = '1x1';
|
|
@@ -1802,6 +1843,10 @@ shaka.hls.HlsParser = class {
|
|
|
1802
1843
|
shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-TILES');
|
|
1803
1844
|
if (tilesTag) {
|
|
1804
1845
|
tilesLayout = tilesTag.getRequiredAttrValue('LAYOUT');
|
|
1846
|
+
const duration = tilesTag.getAttributeValue('DURATION');
|
|
1847
|
+
if (duration) {
|
|
1848
|
+
tileDuration = Number(duration);
|
|
1849
|
+
}
|
|
1805
1850
|
}
|
|
1806
1851
|
}
|
|
1807
1852
|
|
|
@@ -1817,6 +1862,7 @@ shaka.hls.HlsParser = class {
|
|
|
1817
1862
|
/* appendWindowEnd= */ Infinity,
|
|
1818
1863
|
partialSegmentRefs,
|
|
1819
1864
|
tilesLayout,
|
|
1865
|
+
tileDuration,
|
|
1820
1866
|
);
|
|
1821
1867
|
}
|
|
1822
1868
|
|
|
@@ -2009,9 +2055,6 @@ shaka.hls.HlsParser = class {
|
|
|
2009
2055
|
}
|
|
2010
2056
|
}
|
|
2011
2057
|
|
|
2012
|
-
this.segmentsToNotifyByStream_.push(references);
|
|
2013
|
-
this.notifySegments_();
|
|
2014
|
-
|
|
2015
2058
|
return references;
|
|
2016
2059
|
}
|
|
2017
2060
|
|
|
@@ -2350,6 +2393,12 @@ shaka.hls.HlsParser = class {
|
|
|
2350
2393
|
reader.rewind(1);
|
|
2351
2394
|
};
|
|
2352
2395
|
|
|
2396
|
+
// We will look a few packet-lengths forward to find the first sync byte.
|
|
2397
|
+
// Note that we are using this method on what is already a subset of the
|
|
2398
|
+
// file (the first |shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_| bytes), so
|
|
2399
|
+
// we can't look too far ahead to begin with.
|
|
2400
|
+
let syncByteScanLength = Math.min(reader.getLength() - 188, 5 * 188);
|
|
2401
|
+
|
|
2353
2402
|
// TODO: refactor this while loop for better readability.
|
|
2354
2403
|
// eslint-disable-next-line no-constant-condition
|
|
2355
2404
|
while (true) {
|
|
@@ -2358,8 +2407,16 @@ shaka.hls.HlsParser = class {
|
|
|
2358
2407
|
|
|
2359
2408
|
syncByte = reader.readUint8();
|
|
2360
2409
|
if (syncByte != 0x47) {
|
|
2410
|
+
if (syncByteScanLength > 0) {
|
|
2411
|
+
// This file could have started with a cut-off TS packet. Scan forward
|
|
2412
|
+
// until we find a sync byte.
|
|
2413
|
+
syncByteScanLength -= 1;
|
|
2414
|
+
continue;
|
|
2415
|
+
}
|
|
2361
2416
|
fail();
|
|
2362
2417
|
}
|
|
2418
|
+
// If we've found a sync byte, stop scanning forward for future packets.
|
|
2419
|
+
syncByteScanLength = 0;
|
|
2363
2420
|
|
|
2364
2421
|
const flagsAndPacketId = reader.readUint16();
|
|
2365
2422
|
const packetId = flagsAndPacketId & 0x1fff;
|
|
@@ -86,7 +86,9 @@ shaka.media.ClosedCaptionParser = class {
|
|
|
86
86
|
for (const captionPacket of captionPackets) {
|
|
87
87
|
const uint8ArrayData =
|
|
88
88
|
shaka.util.BufferUtils.toUint8(captionPacket.packet);
|
|
89
|
-
|
|
89
|
+
if (uint8ArrayData.length > 0) {
|
|
90
|
+
this.ceaDecoder_.extract(uint8ArrayData, captionPacket.pts);
|
|
91
|
+
}
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
// Decode and return the parsed captions.
|
|
@@ -75,6 +75,18 @@ shaka.media.ContentWorkarounds = class {
|
|
|
75
75
|
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
76
76
|
});
|
|
77
77
|
})
|
|
78
|
+
.fullBox('avc3', (box) => {
|
|
79
|
+
boxesToModify.push({
|
|
80
|
+
box,
|
|
81
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
82
|
+
});
|
|
83
|
+
})
|
|
84
|
+
.fullBox('ac-3', (box) => {
|
|
85
|
+
boxesToModify.push({
|
|
86
|
+
box,
|
|
87
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCA_,
|
|
88
|
+
});
|
|
89
|
+
})
|
|
78
90
|
.fullBox('ec-3', (box) => {
|
|
79
91
|
boxesToModify.push({
|
|
80
92
|
box,
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -125,6 +125,9 @@ shaka.media.DrmEngine = class {
|
|
|
125
125
|
|
|
126
126
|
/** @const {!shaka.util.Destroyer} */
|
|
127
127
|
this.destroyer_ = new shaka.util.Destroyer(() => this.destroyNow_());
|
|
128
|
+
|
|
129
|
+
/** @private {boolean} */
|
|
130
|
+
this.srcEquals_ = false;
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
/** @override */
|
|
@@ -180,6 +183,7 @@ shaka.media.DrmEngine = class {
|
|
|
180
183
|
this.config_ = null;
|
|
181
184
|
this.onError_ = () => {};
|
|
182
185
|
this.playerInterface_ = null;
|
|
186
|
+
this.srcEquals_ = false;
|
|
183
187
|
}
|
|
184
188
|
|
|
185
189
|
/**
|
|
@@ -193,6 +197,13 @@ shaka.media.DrmEngine = class {
|
|
|
193
197
|
this.config_ = config;
|
|
194
198
|
}
|
|
195
199
|
|
|
200
|
+
/**
|
|
201
|
+
* @param {!boolean} value
|
|
202
|
+
*/
|
|
203
|
+
setSrcEquals(value) {
|
|
204
|
+
this.srcEquals_ = value;
|
|
205
|
+
}
|
|
206
|
+
|
|
196
207
|
/**
|
|
197
208
|
* Initialize the drm engine for storing and deleting stored content.
|
|
198
209
|
*
|
|
@@ -351,7 +362,7 @@ shaka.media.DrmEngine = class {
|
|
|
351
362
|
// We should get the decodingInfo results for the variants after we filling
|
|
352
363
|
// in the drm infos, and before queryMediaKeys_().
|
|
353
364
|
await shaka.util.StreamUtils.getDecodingInfosForVariants(variants,
|
|
354
|
-
this.usePersistentLicenses_);
|
|
365
|
+
this.usePersistentLicenses_, this.srcEquals_);
|
|
355
366
|
|
|
356
367
|
const hasDrmInfo = hadDrmInfo || Object.keys(this.config_.servers).length;
|
|
357
368
|
// An unencrypted content is initialized.
|
|
@@ -539,6 +550,7 @@ shaka.media.DrmEngine = class {
|
|
|
539
550
|
tasks.push(session.remove());
|
|
540
551
|
|
|
541
552
|
await Promise.all(tasks);
|
|
553
|
+
this.activeSessions_.delete(session);
|
|
542
554
|
}
|
|
543
555
|
|
|
544
556
|
/**
|
|
@@ -1813,9 +1825,16 @@ shaka.media.DrmEngine = class {
|
|
|
1813
1825
|
// audio adaptations, so we shouldn't have to worry about checking
|
|
1814
1826
|
// robustness.
|
|
1815
1827
|
if (drm1.keySystem == drm2.keySystem) {
|
|
1828
|
+
/** @type {Array<shaka.extern.InitDataOverride>} */
|
|
1816
1829
|
let initData = [];
|
|
1817
1830
|
initData = initData.concat(drm1.initData || []);
|
|
1818
1831
|
initData = initData.concat(drm2.initData || []);
|
|
1832
|
+
initData = initData.filter((d, i) => {
|
|
1833
|
+
return d.keyId === undefined || i === initData.findIndex((d2) => {
|
|
1834
|
+
return d2.keyId === d.keyId;
|
|
1835
|
+
});
|
|
1836
|
+
});
|
|
1837
|
+
|
|
1819
1838
|
const keyIds = drm1.keyIds && drm2.keyIds ?
|
|
1820
1839
|
new Set([...drm1.keyIds, ...drm2.keyIds]) :
|
|
1821
1840
|
drm1.keyIds || drm2.keyIds;
|
|
@@ -154,8 +154,10 @@ shaka.media.GapJumpingController = class {
|
|
|
154
154
|
}
|
|
155
155
|
// Don't gap jump while paused, so that you don't constantly jump ahead
|
|
156
156
|
// while paused on a livestream. We make an exception for time 0, since we
|
|
157
|
-
// may be _required_ to seek on startup before play can begin
|
|
158
|
-
|
|
157
|
+
// may be _required_ to seek on startup before play can begin, but only if
|
|
158
|
+
// autoplay is enabled.
|
|
159
|
+
if (this.video_.paused && (this.video_.currentTime != 0 ||
|
|
160
|
+
(!this.video_.autoplay && this.video_.currentTime == 0))) {
|
|
159
161
|
return;
|
|
160
162
|
}
|
|
161
163
|
|
|
@@ -215,8 +217,10 @@ shaka.media.GapJumpingController = class {
|
|
|
215
217
|
this.didFireLargeGap_ = true;
|
|
216
218
|
|
|
217
219
|
// Event firing is synchronous.
|
|
218
|
-
const
|
|
219
|
-
'
|
|
220
|
+
const data = new Map()
|
|
221
|
+
.set('currentTime', currentTime)
|
|
222
|
+
.set('gapSize', jumpSize);
|
|
223
|
+
const event = new shaka.util.FakeEvent('largegap', data);
|
|
220
224
|
event.cancelable = true;
|
|
221
225
|
this.onEvent_(event);
|
|
222
226
|
|
|
@@ -48,8 +48,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
48
48
|
* @param {!function(!Array.<shaka.extern.ID3Metadata>, number, ?number)=}
|
|
49
49
|
* onMetadata
|
|
50
50
|
*/
|
|
51
|
-
constructor(video, closedCaptionParser, textDisplayer,
|
|
52
|
-
onMetadata) {
|
|
51
|
+
constructor(video, closedCaptionParser, textDisplayer, onMetadata) {
|
|
53
52
|
/** @private {HTMLMediaElement} */
|
|
54
53
|
this.video_ = video;
|
|
55
54
|
|
package/lib/media/playhead.js
CHANGED
|
@@ -80,7 +80,7 @@ shaka.media.SrcEqualsPlayhead = class {
|
|
|
80
80
|
// We listen for the loaded-data-event so that we know when we can
|
|
81
81
|
// interact with |currentTime|.
|
|
82
82
|
const onLoaded = () => {
|
|
83
|
-
if (this.startTime_ == null) {
|
|
83
|
+
if (this.startTime_ == null || this.startTime_ == 0) {
|
|
84
84
|
this.started_ = true;
|
|
85
85
|
} else {
|
|
86
86
|
// Startup is complete only when the video element acknowledges the
|