shaka-player 4.3.13 → 4.3.14
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/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/.github/ISSUE_TEMPLATE/question.md +7 -0
- package/.github/workflows/release-please.yaml +2 -27
- package/AUTHORS +5 -0
- package/CHANGELOG.md +87 -0
- package/CODE_OF_CONDUCT.md +1 -1
- package/CONTRIBUTORS +6 -0
- package/README.md +2 -0
- package/demo/common/asset.js +0 -1
- package/demo/common/assets.js +0 -1
- package/demo/custom.js +2 -0
- package/dist/demo.compiled.debug.js +1 -1
- package/dist/demo.compiled.debug.map +1 -1
- package/dist/demo.compiled.js +1 -1
- package/dist/demo.compiled.map +1 -1
- package/dist/deps.js +6 -6
- package/dist/locales.js +29 -29
- package/dist/receiver.compiled.debug.map +1 -1
- package/dist/receiver.compiled.map +1 -1
- package/dist/shaka-player.compiled.d.ts +25 -5
- package/dist/shaka-player.compiled.debug.d.ts +25 -5
- package/dist/shaka-player.compiled.debug.externs.js +73 -45
- package/dist/shaka-player.compiled.debug.js +414 -396
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +73 -45
- package/dist/shaka-player.compiled.js +912 -903
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +37 -6
- package/dist/shaka-player.ui.debug.d.ts +37 -6
- package/dist/shaka-player.ui.debug.externs.js +74 -45
- package/dist/shaka-player.ui.debug.js +438 -418
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +74 -45
- package/dist/shaka-player.ui.js +1060 -1051
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/ad_monetization.md +54 -0
- package/docs/tutorials/fairplay.md +2 -2
- package/docs/tutorials/index.json +1 -0
- package/docs/tutorials/plugins.md +4 -0
- package/docs/tutorials/text-displayer.md +15 -0
- package/docs/tutorials/ui-customization.md +29 -0
- package/docs/tutorials/ui.md +4 -1
- package/externs/lcevc.js +6 -0
- package/externs/shaka/manifest_parser.js +8 -0
- package/externs/shaka/player.js +1 -1
- package/externs/shaka/text.js +3 -1
- package/lib/abr/simple_abr_manager.js +1 -1
- package/lib/ads/client_side_ad.js +9 -0
- package/lib/ads/client_side_ad_manager.js +7 -2
- package/lib/ads/server_side_ad_manager.js +4 -0
- package/lib/cast/cast_proxy.js +18 -0
- package/lib/cea/cea608_memory.js +1 -1
- package/lib/cea/cea_decoder.js +11 -1
- package/lib/dash/dash_parser.js +75 -41
- package/lib/dash/mpd_utils.js +39 -27
- package/lib/hls/hls_parser.js +90 -27
- package/lib/hls/hls_utils.js +5 -5
- package/lib/media/content_workarounds.js +30 -0
- package/lib/media/drm_engine.js +16 -12
- package/lib/media/media_source_engine.js +4 -2
- package/lib/media/play_rate_controller.js +1 -0
- package/lib/media/presentation_timeline.js +11 -0
- package/lib/media/quality_observer.js +1 -1
- package/lib/media/segment_reference.js +5 -2
- package/lib/media/streaming_engine.js +50 -30
- package/lib/media/transmuxer.js +1 -1
- package/lib/media/video_wrapper.js +4 -3
- package/lib/offline/offline_manifest_parser.js +5 -0
- package/lib/offline/session_deleter.js +0 -8
- package/lib/offline/storage.js +4 -1
- package/lib/player.js +79 -18
- package/lib/polyfill/media_capabilities.js +23 -3
- package/lib/text/mp4_ttml_parser.js +2 -2
- package/lib/text/simple_text_displayer.js +7 -6
- package/lib/text/srt_text_parser.js +2 -2
- package/lib/text/text_engine.js +3 -2
- package/lib/text/ttml_text_parser.js +33 -5
- package/lib/text/ui_text_displayer.js +81 -15
- package/lib/text/vtt_text_parser.js +15 -2
- package/lib/util/buffer_utils.js +26 -4
- package/lib/util/cmcd_manager.js +10 -0
- package/lib/util/fairplay_utils.js +1 -1
- package/lib/util/manifest_parser_utils.js +14 -8
- package/lib/util/object_utils.js +1 -1
- package/lib/util/periods.js +91 -36
- package/lib/util/platform.js +39 -2
- package/lib/util/player_configuration.js +5 -3
- package/lib/util/stream_utils.js +36 -28
- package/lib/util/xml_utils.js +5 -0
- package/package.json +2 -2
- package/test/cea/cea_decoder_unit.js +40 -0
- package/test/dash/dash_parser_live_unit.js +5 -8
- package/test/hls/hls_parser_unit.js +50 -3
- package/test/media/buffering_observer_unit.js +13 -0
- package/test/media/content_workarounds_unit.js +1 -1
- package/test/media/drm_engine_unit.js +30 -3
- package/test/media/media_source_engine_unit.js +1 -1
- package/test/media/transmuxer_integration.js +2 -2
- package/test/offline/storage_integration.js +3 -0
- package/test/player_integration.js +40 -40
- package/test/player_unit.js +31 -0
- package/test/polyfill/media_capabilities_unit.js +12 -6
- package/test/test/util/fake_drm_engine.js +7 -0
- package/test/test/util/layout_tests.js +2 -1
- package/test/test/util/simple_fakes.js +2 -2
- package/test/test/util/test_scheme.js +3 -0
- package/test/test/util/waiter.js +5 -0
- package/test/text/mp4_ttml_parser_unit.js +4 -4
- package/test/text/simple_text_displayer_unit.js +1 -1
- package/test/text/srt_text_parser_unit.js +1 -1
- package/test/text/text_engine_unit.js +6 -0
- package/test/text/ttml_text_parser_unit.js +49 -18
- package/test/text/ui_text_displayer_unit.js +9 -0
- package/test/util/buffer_utils_unit.js +1 -1
- package/test/util/periods_unit.js +113 -0
- package/third_party/closure-uri/uri.js +1 -1
- package/ui/externs/ui.js +24 -0
- package/ui/language_utils.js +11 -5
- package/ui/play_button.js +20 -0
- package/ui/playback_rate_selection.js +4 -0
- package/ui/presentation_time.js +5 -5
- package/ui/small_play_button.js +2 -2
- package/ui/volume_bar.js +10 -6
- /package/.github/{config.yml → ISSUE_TEMPLATE/config.yml} +0 -0
package/lib/player.js
CHANGED
|
@@ -479,6 +479,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
479
479
|
*/
|
|
480
480
|
this.loadEventManager_ = new shaka.util.EventManager();
|
|
481
481
|
|
|
482
|
+
/**
|
|
483
|
+
* For listeners scoped to the lifetime of the loaded content.
|
|
484
|
+
* @private {shaka.util.EventManager}
|
|
485
|
+
*/
|
|
486
|
+
this.trickPlayEventManager_ = new shaka.util.EventManager();
|
|
487
|
+
|
|
482
488
|
/** @private {shaka.net.NetworkingEngine} */
|
|
483
489
|
this.networkingEngine_ = null;
|
|
484
490
|
|
|
@@ -897,6 +903,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
897
903
|
this.loadEventManager_.release();
|
|
898
904
|
this.loadEventManager_ = null;
|
|
899
905
|
}
|
|
906
|
+
if (this.trickPlayEventManager_) {
|
|
907
|
+
this.trickPlayEventManager_.release();
|
|
908
|
+
this.trickPlayEventManager_ = null;
|
|
909
|
+
}
|
|
900
910
|
|
|
901
911
|
this.abrManagerFactory_ = null;
|
|
902
912
|
this.config_ = null;
|
|
@@ -1555,6 +1565,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1555
1565
|
// be if there was an error and we, by chance, did not have a media element.
|
|
1556
1566
|
if (has.mediaElement) {
|
|
1557
1567
|
this.loadEventManager_.removeAll();
|
|
1568
|
+
this.trickPlayEventManager_.removeAll();
|
|
1558
1569
|
}
|
|
1559
1570
|
|
|
1560
1571
|
// Stop the variant checker timer
|
|
@@ -2012,8 +2023,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2012
2023
|
|
|
2013
2024
|
this.drmEngine_.configure(this.config_.drm);
|
|
2014
2025
|
|
|
2026
|
+
const tracksChanged = shaka.util.StreamUtils.applyRestrictions(
|
|
2027
|
+
this.manifest_.variants, this.config_.restrictions, this.maxHwRes_);
|
|
2028
|
+
if (tracksChanged && this.streamingEngine_) {
|
|
2029
|
+
this.onTracksChanged_();
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
const playableVariants = shaka.util.StreamUtils.getPlayableVariants(
|
|
2033
|
+
this.manifest_.variants);
|
|
2034
|
+
|
|
2015
2035
|
await this.drmEngine_.initForPlayback(
|
|
2016
|
-
|
|
2036
|
+
playableVariants,
|
|
2017
2037
|
this.manifest_.offlineSessionIds);
|
|
2018
2038
|
|
|
2019
2039
|
await this.drmEngine_.attach(has.mediaElement);
|
|
@@ -2192,6 +2212,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2192
2212
|
await Promise.all(createSegmentIndexPromises);
|
|
2193
2213
|
}
|
|
2194
2214
|
|
|
2215
|
+
if (this.parser_ && this.parser_.onInitialVariantChosen) {
|
|
2216
|
+
this.parser_.onInitialVariantChosen(toLazyLoad);
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2195
2219
|
shaka.Player.applyPlayRange_(this.manifest_.presentationTimeline,
|
|
2196
2220
|
this.config_.playRangeStart,
|
|
2197
2221
|
this.config_.playRangeEnd);
|
|
@@ -3705,6 +3729,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3705
3729
|
shaka.log.alwaysWarn('A trick play rate of 0 is unsupported!');
|
|
3706
3730
|
return;
|
|
3707
3731
|
}
|
|
3732
|
+
this.trickPlayEventManager_.removeAll();
|
|
3708
3733
|
|
|
3709
3734
|
if (this.video_.paused) {
|
|
3710
3735
|
// Our fast forward is implemented with playbackRate and needs the video
|
|
@@ -3718,6 +3743,22 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3718
3743
|
this.abrManager_.playbackRateChanged(rate);
|
|
3719
3744
|
this.streamingEngine_.setTrickPlay(Math.abs(rate) > 1);
|
|
3720
3745
|
}
|
|
3746
|
+
if (this.isLive()) {
|
|
3747
|
+
this.trickPlayEventManager_.listen(this.video_, 'timeupdate', () => {
|
|
3748
|
+
const currentTime = this.video_.currentTime;
|
|
3749
|
+
const seekRange = this.seekRange();
|
|
3750
|
+
const safeSeekOffset = this.config_.streaming.safeSeekOffset;
|
|
3751
|
+
if (rate > 0) {
|
|
3752
|
+
if (currentTime >= seekRange.end) {
|
|
3753
|
+
this.cancelTrickPlay();
|
|
3754
|
+
}
|
|
3755
|
+
} else {
|
|
3756
|
+
if (currentTime <= (seekRange.start + safeSeekOffset)) {
|
|
3757
|
+
this.cancelTrickPlay();
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
});
|
|
3761
|
+
}
|
|
3721
3762
|
}
|
|
3722
3763
|
|
|
3723
3764
|
/**
|
|
@@ -3737,6 +3778,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3737
3778
|
this.abrManager_.playbackRateChanged(defaultPlaybackRate);
|
|
3738
3779
|
this.streamingEngine_.setTrickPlay(false);
|
|
3739
3780
|
}
|
|
3781
|
+
this.trickPlayEventManager_.removeAll();
|
|
3740
3782
|
}
|
|
3741
3783
|
|
|
3742
3784
|
/**
|
|
@@ -4635,7 +4677,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4635
4677
|
|
|
4636
4678
|
if (this.isLive()) {
|
|
4637
4679
|
const now = this.getPresentationStartTimeAsDate().valueOf() +
|
|
4638
|
-
|
|
4680
|
+
element.currentTime * 1000;
|
|
4639
4681
|
const latency = (Date.now() - now) / 1000;
|
|
4640
4682
|
this.stats_.setLiveLatency(latency);
|
|
4641
4683
|
}
|
|
@@ -4689,6 +4731,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4689
4731
|
shaka.util.Error.Code.CONTENT_NOT_LOADED);
|
|
4690
4732
|
}
|
|
4691
4733
|
|
|
4734
|
+
if (kind != 'subtitles' && kind != 'captions') {
|
|
4735
|
+
shaka.log.alwaysWarn(
|
|
4736
|
+
'Using a kind value different of `subtitles` or `captions` can ' +
|
|
4737
|
+
'cause unwanted issues.');
|
|
4738
|
+
}
|
|
4739
|
+
|
|
4692
4740
|
if (!mimeType) {
|
|
4693
4741
|
mimeType = await this.getTextMimetype_(uri);
|
|
4694
4742
|
}
|
|
@@ -4707,9 +4755,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4707
4755
|
}
|
|
4708
4756
|
await this.addSrcTrackElement_(uri, language, kind, mimeType, label || '',
|
|
4709
4757
|
adCuePoints);
|
|
4758
|
+
|
|
4759
|
+
const LanguageUtils = shaka.util.LanguageUtils;
|
|
4760
|
+
const languageNormalized = LanguageUtils.normalize(language);
|
|
4761
|
+
|
|
4710
4762
|
const textTracks = this.getTextTracks();
|
|
4711
4763
|
const srcTrack = textTracks.find((t) => {
|
|
4712
|
-
return t.language ==
|
|
4764
|
+
return LanguageUtils.normalize(t.language) == languageNormalized &&
|
|
4713
4765
|
t.label == (label || '') &&
|
|
4714
4766
|
t.kind == kind;
|
|
4715
4767
|
});
|
|
@@ -4871,16 +4923,22 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4871
4923
|
vttOffset: 0,
|
|
4872
4924
|
};
|
|
4873
4925
|
const data = shaka.util.BufferUtils.toUint8(buffer);
|
|
4874
|
-
const cues = TextParser.parseMedia(data, time);
|
|
4926
|
+
const cues = TextParser.parseMedia(data, time, uri);
|
|
4875
4927
|
|
|
4876
4928
|
const references = [];
|
|
4877
4929
|
for (const cue of cues) {
|
|
4878
|
-
|
|
4879
|
-
|
|
4930
|
+
let uris = null;
|
|
4931
|
+
const getUris = () => {
|
|
4932
|
+
if (uris == null) {
|
|
4933
|
+
uris = shaka.util.ManifestParserUtils.resolveUris(
|
|
4934
|
+
[uri], [cue.payload]);
|
|
4935
|
+
}
|
|
4936
|
+
return uris || [];
|
|
4937
|
+
};
|
|
4880
4938
|
const reference = new shaka.media.SegmentReference(
|
|
4881
4939
|
cue.startTime,
|
|
4882
4940
|
cue.endTime,
|
|
4883
|
-
|
|
4941
|
+
getUris,
|
|
4884
4942
|
/* startByte= */ 0,
|
|
4885
4943
|
/* endByte= */ null,
|
|
4886
4944
|
/* initSegmentReference= */ null,
|
|
@@ -4888,8 +4946,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4888
4946
|
/* appendWindowStart= */ 0,
|
|
4889
4947
|
/* appendWindowEnd= */ Infinity,
|
|
4890
4948
|
);
|
|
4891
|
-
if (
|
|
4892
|
-
const spriteInfo =
|
|
4949
|
+
if (cue.payload.includes('#xywh')) {
|
|
4950
|
+
const spriteInfo = cue.payload.split('#xywh=')[1].split(',');
|
|
4893
4951
|
if (spriteInfo.length === 4) {
|
|
4894
4952
|
reference.setThumbnailSprite({
|
|
4895
4953
|
height: parseInt(spriteInfo[3], 10),
|
|
@@ -5129,7 +5187,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5129
5187
|
vttOffset: 0,
|
|
5130
5188
|
};
|
|
5131
5189
|
const data = shaka.util.BufferUtils.toUint8(buffer);
|
|
5132
|
-
const cues = obj.parseMedia(data, time);
|
|
5190
|
+
const cues = obj.parseMedia(data, time, /* uri= */ null);
|
|
5133
5191
|
return shaka.text.WebVttGenerator.convert(cues, adCuePoints);
|
|
5134
5192
|
}
|
|
5135
5193
|
throw new shaka.util.Error(
|
|
@@ -5248,7 +5306,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5248
5306
|
return new shaka.text.UITextDisplayer(
|
|
5249
5307
|
this.video_, this.videoContainer_);
|
|
5250
5308
|
} else {
|
|
5251
|
-
return new shaka.text.SimpleTextDisplayer(
|
|
5309
|
+
return new shaka.text.SimpleTextDisplayer(
|
|
5310
|
+
this.video_, shaka.Player.TextTrackLabel);
|
|
5252
5311
|
}
|
|
5253
5312
|
};
|
|
5254
5313
|
return config;
|
|
@@ -6263,6 +6322,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6263
6322
|
const restrictedStatuses = shaka.Player.restrictedStatuses_;
|
|
6264
6323
|
let tracksChanged = false;
|
|
6265
6324
|
|
|
6325
|
+
goog.asserts.assert(this.drmEngine_, 'drmEngine should be non-null here.');
|
|
6326
|
+
|
|
6266
6327
|
// Only filter tracks for keys if we have some key statuses to look at.
|
|
6267
6328
|
if (keyIds.length) {
|
|
6268
6329
|
for (const variant of this.manifest_.variants) {
|
|
@@ -6278,8 +6339,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6278
6339
|
|
|
6279
6340
|
for (const keyId of stream.keyIds) {
|
|
6280
6341
|
const keyStatus = keyStatusMap[isGlobalStatus ? '00' : keyId];
|
|
6281
|
-
|
|
6282
|
-
|
|
6342
|
+
if (keyStatus || this.drmEngine_.hasManifestInitData()) {
|
|
6343
|
+
variant.allowedByKeySystem = variant.allowedByKeySystem &&
|
|
6344
|
+
!!keyStatus && !restrictedStatuses.includes(keyStatus);
|
|
6345
|
+
}
|
|
6283
6346
|
}
|
|
6284
6347
|
}
|
|
6285
6348
|
|
|
@@ -6291,6 +6354,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6291
6354
|
} // if (keyIds.size)
|
|
6292
6355
|
|
|
6293
6356
|
if (tracksChanged) {
|
|
6357
|
+
this.onTracksChanged_();
|
|
6294
6358
|
const variantsUpdated = this.updateAbrManagerVariants_();
|
|
6295
6359
|
if (!variantsUpdated) {
|
|
6296
6360
|
return;
|
|
@@ -6302,10 +6366,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6302
6366
|
shaka.log.debug('Choosing new streams after key status changed');
|
|
6303
6367
|
this.chooseVariantAndSwitch_();
|
|
6304
6368
|
}
|
|
6305
|
-
|
|
6306
|
-
if (tracksChanged) {
|
|
6307
|
-
this.onTracksChanged_();
|
|
6308
|
-
}
|
|
6309
6369
|
}
|
|
6310
6370
|
|
|
6311
6371
|
/**
|
|
@@ -6971,7 +7031,8 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6971
7031
|
* @define {string} A version number taken from git at compile time.
|
|
6972
7032
|
* @export
|
|
6973
7033
|
*/
|
|
6974
|
-
|
|
7034
|
+
// eslint-disable-next-line no-useless-concat
|
|
7035
|
+
shaka.Player.version = 'v4.3.14' + '-uncompiled'; // x-release-please-version
|
|
6975
7036
|
|
|
6976
7037
|
// Initialize the deprecation system using the version string we just set
|
|
6977
7038
|
// on the player.
|
|
@@ -141,8 +141,15 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
if (audioConfig) {
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
let isSupported;
|
|
145
|
+
if (shaka.util.Platform.isChromecast() &&
|
|
146
|
+
audioConfig.spatialRendering) {
|
|
147
|
+
const extendedType =
|
|
148
|
+
audioConfig.contentType + '; spatialRendering=true';
|
|
149
|
+
isSupported = Capabilities.isTypeSupported(extendedType);
|
|
150
|
+
} else {
|
|
151
|
+
isSupported = Capabilities.isTypeSupported(audioConfig.contentType);
|
|
152
|
+
}
|
|
146
153
|
if (!isSupported) {
|
|
147
154
|
return res;
|
|
148
155
|
}
|
|
@@ -184,10 +191,23 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
184
191
|
const videoCapabilities = [];
|
|
185
192
|
|
|
186
193
|
if (mediaCapkeySystemConfig.audio) {
|
|
187
|
-
|
|
194
|
+
let capability = {
|
|
188
195
|
robustness: mediaCapkeySystemConfig.audio.robustness || '',
|
|
189
196
|
contentType: mediaDecodingConfig.audio.contentType,
|
|
190
197
|
};
|
|
198
|
+
|
|
199
|
+
// Some Tizen devices seem to misreport AC-3 support, but correctly
|
|
200
|
+
// report EC-3 support. So query EC-3 as a fallback for AC-3.
|
|
201
|
+
// See https://github.com/shaka-project/shaka-player/issues/2989 for
|
|
202
|
+
// details.
|
|
203
|
+
if (shaka.util.Platform.isTizen() &&
|
|
204
|
+
mediaDecodingConfig.audio.contentType.includes('codecs="ac-3"')) {
|
|
205
|
+
capability = {
|
|
206
|
+
robustness: capability.robustness,
|
|
207
|
+
contentType: 'audio/mp4; codecs="ec-3"',
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
191
211
|
audioCapabilities.push(capability);
|
|
192
212
|
}
|
|
193
213
|
|
|
@@ -67,7 +67,7 @@ shaka.text.Mp4TtmlParser = class {
|
|
|
67
67
|
* @override
|
|
68
68
|
* @export
|
|
69
69
|
*/
|
|
70
|
-
parseMedia(data, time) {
|
|
70
|
+
parseMedia(data, time, uri) {
|
|
71
71
|
const Mp4Parser = shaka.util.Mp4Parser;
|
|
72
72
|
|
|
73
73
|
let sawMDAT = false;
|
|
@@ -78,7 +78,7 @@ shaka.text.Mp4TtmlParser = class {
|
|
|
78
78
|
sawMDAT = true;
|
|
79
79
|
// Join this to any previous payload, in case the mp4 has multiple
|
|
80
80
|
// mdats.
|
|
81
|
-
payload = payload.concat(this.parser_.parseMedia(data, time));
|
|
81
|
+
payload = payload.concat(this.parser_.parseMedia(data, time, uri));
|
|
82
82
|
}));
|
|
83
83
|
parser.parse(data, /* partialOkay= */ false);
|
|
84
84
|
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @fileoverview
|
|
9
|
-
* @suppress {missingRequire} TODO(b/152540451): this shouldn't be needed
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
11
|
goog.provide('shaka.text.SimpleTextDisplayer');
|
|
@@ -24,8 +23,11 @@ goog.require('shaka.text.Utils');
|
|
|
24
23
|
* @export
|
|
25
24
|
*/
|
|
26
25
|
shaka.text.SimpleTextDisplayer = class {
|
|
27
|
-
/**
|
|
28
|
-
|
|
26
|
+
/**
|
|
27
|
+
* @param {HTMLMediaElement} video
|
|
28
|
+
* @param {string} label
|
|
29
|
+
*/
|
|
30
|
+
constructor(video, label) {
|
|
29
31
|
/** @private {TextTrack} */
|
|
30
32
|
this.textTrack_ = null;
|
|
31
33
|
|
|
@@ -39,7 +41,7 @@ shaka.text.SimpleTextDisplayer = class {
|
|
|
39
41
|
// element.
|
|
40
42
|
track.mode = 'disabled';
|
|
41
43
|
|
|
42
|
-
if (track.label ==
|
|
44
|
+
if (track.label == label) {
|
|
43
45
|
this.textTrack_ = track;
|
|
44
46
|
}
|
|
45
47
|
}
|
|
@@ -49,8 +51,7 @@ shaka.text.SimpleTextDisplayer = class {
|
|
|
49
51
|
// kind to 'subtitles' or 'captions' when creating the TextTrack object.
|
|
50
52
|
// The individual text tracks from the manifest will still have their own
|
|
51
53
|
// kinds which can be displayed in the app's UI.
|
|
52
|
-
this.textTrack_ = video.addTextTrack(
|
|
53
|
-
'subtitles', shaka.Player.TextTrackLabel);
|
|
54
|
+
this.textTrack_ = video.addTextTrack('subtitles', label);
|
|
54
55
|
}
|
|
55
56
|
this.textTrack_.mode = 'hidden';
|
|
56
57
|
}
|
|
@@ -47,7 +47,7 @@ shaka.text.SrtTextParser = class {
|
|
|
47
47
|
* @override
|
|
48
48
|
* @export
|
|
49
49
|
*/
|
|
50
|
-
parseMedia(data, time) {
|
|
50
|
+
parseMedia(data, time, uri) {
|
|
51
51
|
const SrtTextParser = shaka.text.SrtTextParser;
|
|
52
52
|
const BufferUtils = shaka.util.BufferUtils;
|
|
53
53
|
const StringUtils = shaka.util.StringUtils;
|
|
@@ -59,7 +59,7 @@ shaka.text.SrtTextParser = class {
|
|
|
59
59
|
|
|
60
60
|
const newData = BufferUtils.toUint8(StringUtils.toUTF8(vvtText));
|
|
61
61
|
|
|
62
|
-
return this.parser_.parseMedia(newData, time);
|
|
62
|
+
return this.parser_.parseMedia(newData, time, uri);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
package/lib/text/text_engine.js
CHANGED
|
@@ -160,9 +160,10 @@ shaka.text.TextEngine = class {
|
|
|
160
160
|
* @param {BufferSource} buffer
|
|
161
161
|
* @param {?number} startTime relative to the start of the presentation
|
|
162
162
|
* @param {?number} endTime relative to the start of the presentation
|
|
163
|
+
* @param {?string=} uri
|
|
163
164
|
* @return {!Promise}
|
|
164
165
|
*/
|
|
165
|
-
async appendBuffer(buffer, startTime, endTime) {
|
|
166
|
+
async appendBuffer(buffer, startTime, endTime, uri) {
|
|
166
167
|
goog.asserts.assert(
|
|
167
168
|
this.parser_, 'The parser should already be initialized');
|
|
168
169
|
|
|
@@ -192,7 +193,7 @@ shaka.text.TextEngine = class {
|
|
|
192
193
|
|
|
193
194
|
// Parse the buffer and add the new cues.
|
|
194
195
|
const allCues = this.parser_.parseMedia(
|
|
195
|
-
shaka.util.BufferUtils.toUint8(buffer), time);
|
|
196
|
+
shaka.util.BufferUtils.toUint8(buffer), time, uri);
|
|
196
197
|
const cuesToAppend = allCues.filter((cue) => {
|
|
197
198
|
return cue.startTime >= this.appendWindowStart_ &&
|
|
198
199
|
cue.startTime < this.appendWindowEnd_;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
goog.provide('shaka.text.TtmlTextParser');
|
|
8
8
|
|
|
9
9
|
goog.require('goog.asserts');
|
|
10
|
+
goog.require('goog.Uri');
|
|
10
11
|
goog.require('shaka.log');
|
|
11
12
|
goog.require('shaka.text.Cue');
|
|
12
13
|
goog.require('shaka.text.CueRegion');
|
|
@@ -42,7 +43,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
42
43
|
* @override
|
|
43
44
|
* @export
|
|
44
45
|
*/
|
|
45
|
-
parseMedia(data, time) {
|
|
46
|
+
parseMedia(data, time, uri) {
|
|
46
47
|
const TtmlTextParser = shaka.text.TtmlTextParser;
|
|
47
48
|
const XmlUtils = shaka.util.XmlUtils;
|
|
48
49
|
const ttpNs = TtmlTextParser.parameterNs_;
|
|
@@ -139,7 +140,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
139
140
|
body, time, rateInfo, metadataElements, styles,
|
|
140
141
|
regionElements, cueRegions, whitespaceTrim,
|
|
141
142
|
cellResolutionInfo, /* parentCueElement= */ null,
|
|
142
|
-
/* isContent= */ false);
|
|
143
|
+
/* isContent= */ false, uri);
|
|
143
144
|
if (cue) {
|
|
144
145
|
// According to the TTML spec, backgrounds default to transparent.
|
|
145
146
|
// So default the background of the top-level element to transparent.
|
|
@@ -167,12 +168,14 @@ shaka.text.TtmlTextParser = class {
|
|
|
167
168
|
* @param {?{columns: number, rows: number}} cellResolution
|
|
168
169
|
* @param {?Element} parentCueElement
|
|
169
170
|
* @param {boolean} isContent
|
|
171
|
+
* @param {?(string|undefined)} uri
|
|
170
172
|
* @return {shaka.text.Cue}
|
|
171
173
|
* @private
|
|
172
174
|
*/
|
|
173
175
|
static parseCue_(
|
|
174
176
|
cueNode, timeContext, rateInfo, metadataElements, styles, regionElements,
|
|
175
|
-
cueRegions, whitespaceTrim, cellResolution, parentCueElement, isContent
|
|
177
|
+
cueRegions, whitespaceTrim, cellResolution, parentCueElement, isContent,
|
|
178
|
+
uri) {
|
|
176
179
|
/** @type {Element} */
|
|
177
180
|
let cueElement;
|
|
178
181
|
/** @type {Element} */
|
|
@@ -214,7 +217,21 @@ shaka.text.TtmlTextParser = class {
|
|
|
214
217
|
}
|
|
215
218
|
}
|
|
216
219
|
|
|
217
|
-
|
|
220
|
+
let imageUri = null;
|
|
221
|
+
const backgroundImage = shaka.util.XmlUtils.getAttributeNSList(
|
|
222
|
+
cueElement,
|
|
223
|
+
shaka.text.TtmlTextParser.smpteNsList_,
|
|
224
|
+
'backgroundImage');
|
|
225
|
+
if (uri && backgroundImage && !backgroundImage.startsWith('#')) {
|
|
226
|
+
const baseUri = new goog.Uri(uri);
|
|
227
|
+
const relativeUri = new goog.Uri(backgroundImage);
|
|
228
|
+
const newUri = baseUri.resolve(relativeUri).toString();
|
|
229
|
+
if (newUri) {
|
|
230
|
+
imageUri = newUri;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (cueNode.nodeName == 'p' || imageElement || imageUri) {
|
|
218
235
|
isContent = true;
|
|
219
236
|
}
|
|
220
237
|
|
|
@@ -247,6 +264,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
247
264
|
cellResolution,
|
|
248
265
|
cueElement,
|
|
249
266
|
isContent,
|
|
267
|
+
uri,
|
|
250
268
|
);
|
|
251
269
|
|
|
252
270
|
// This node may or may not generate a nested cue.
|
|
@@ -380,6 +398,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
380
398
|
cueElement,
|
|
381
399
|
regionElementForStyle,
|
|
382
400
|
imageElement,
|
|
401
|
+
imageUri,
|
|
383
402
|
styles,
|
|
384
403
|
/** isNested= */ parentIsContent, // "nested in a <div>" doesn't count.
|
|
385
404
|
/** isLeaf= */ (nestedCues.length == 0));
|
|
@@ -464,6 +483,11 @@ shaka.text.TtmlTextParser = class {
|
|
|
464
483
|
region.viewportAnchorX = region.viewportAnchorX * 100 /
|
|
465
484
|
globalWidth;
|
|
466
485
|
}
|
|
486
|
+
} else if (!extent) {
|
|
487
|
+
region.width = 100 - region.viewportAnchorX;
|
|
488
|
+
region.widthUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
489
|
+
region.height = 100 - region.viewportAnchorY;
|
|
490
|
+
region.heightUnits = shaka.text.CueRegion.units.PERCENTAGE;
|
|
467
491
|
}
|
|
468
492
|
|
|
469
493
|
region.viewportAnchorUnits = percentage || globalWidth != null ?
|
|
@@ -482,13 +506,15 @@ shaka.text.TtmlTextParser = class {
|
|
|
482
506
|
* @param {!Element} cueElement
|
|
483
507
|
* @param {Element} region
|
|
484
508
|
* @param {Element} imageElement
|
|
509
|
+
* @param {?string} imageUri
|
|
485
510
|
* @param {!Array.<!Element>} styles
|
|
486
511
|
* @param {boolean} isNested
|
|
487
512
|
* @param {boolean} isLeaf
|
|
488
513
|
* @private
|
|
489
514
|
*/
|
|
490
515
|
static addStyle_(
|
|
491
|
-
cue, cueElement, region, imageElement,
|
|
516
|
+
cue, cueElement, region, imageElement, imageUri, styles,
|
|
517
|
+
isNested, isLeaf) {
|
|
492
518
|
const TtmlTextParser = shaka.text.TtmlTextParser;
|
|
493
519
|
const Cue = shaka.text.Cue;
|
|
494
520
|
|
|
@@ -656,6 +682,8 @@ shaka.text.TtmlTextParser = class {
|
|
|
656
682
|
backgroundImageData) {
|
|
657
683
|
cue.backgroundImage = 'data:image/png;base64,' + backgroundImageData;
|
|
658
684
|
}
|
|
685
|
+
} else if (imageUri) {
|
|
686
|
+
cue.backgroundImage = imageUri;
|
|
659
687
|
}
|
|
660
688
|
|
|
661
689
|
const textOutline = TtmlTextParser.getStyleAttribute_(
|
|
@@ -44,6 +44,9 @@ shaka.text.UITextDisplayer = class {
|
|
|
44
44
|
/** @private {HTMLElement} */
|
|
45
45
|
this.videoContainer_ = videoContainer;
|
|
46
46
|
|
|
47
|
+
/** @private {?number} */
|
|
48
|
+
this.aspectRatio_ = null;
|
|
49
|
+
|
|
47
50
|
/** @type {HTMLElement} */
|
|
48
51
|
this.textContainer_ = shaka.util.Dom.createHTMLElement('div');
|
|
49
52
|
this.textContainer_.classList.add('shaka-text-container');
|
|
@@ -90,6 +93,23 @@ shaka.text.UITextDisplayer = class {
|
|
|
90
93
|
this.updateCaptions_(/* forceUpdate= */ true);
|
|
91
94
|
});
|
|
92
95
|
|
|
96
|
+
// From: https://html.spec.whatwg.org/multipage/media.html#dom-video-videowidth
|
|
97
|
+
// Whenever the natural width or natural height of the video changes
|
|
98
|
+
// (including, for example, because the selected video track was changed),
|
|
99
|
+
// if the element's readyState attribute is not HAVE_NOTHING, the user
|
|
100
|
+
// agent must queue a media element task given the media element to fire an
|
|
101
|
+
// event named resize at the media element.
|
|
102
|
+
this.eventManager_.listen(this.video_, 'resize', () => {
|
|
103
|
+
const element = /** @type {!HTMLVideoElement} */ (this.video_);
|
|
104
|
+
const width = element.videoWidth;
|
|
105
|
+
const height = element.videoHeight;
|
|
106
|
+
if (width && height) {
|
|
107
|
+
this.aspectRatio_ = width / height;
|
|
108
|
+
} else {
|
|
109
|
+
this.aspectRatio_ = null;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
93
113
|
/** @private {ResizeObserver} */
|
|
94
114
|
this.resizeObserver_ = null;
|
|
95
115
|
if ('ResizeObserver' in window) {
|
|
@@ -134,6 +154,11 @@ shaka.text.UITextDisplayer = class {
|
|
|
134
154
|
* @export
|
|
135
155
|
*/
|
|
136
156
|
destroy() {
|
|
157
|
+
// Return resolved promise if destroy() has been called.
|
|
158
|
+
if (!this.textContainer_) {
|
|
159
|
+
return Promise.resolve();
|
|
160
|
+
}
|
|
161
|
+
|
|
137
162
|
// Remove the text container element from the UI.
|
|
138
163
|
this.videoContainer_.removeChild(this.textContainer_);
|
|
139
164
|
this.textContainer_ = null;
|
|
@@ -156,6 +181,8 @@ shaka.text.UITextDisplayer = class {
|
|
|
156
181
|
this.resizeObserver_.disconnect();
|
|
157
182
|
this.resizeObserver_ = null;
|
|
158
183
|
}
|
|
184
|
+
|
|
185
|
+
return Promise.resolve();
|
|
159
186
|
}
|
|
160
187
|
|
|
161
188
|
|
|
@@ -400,6 +427,10 @@ shaka.text.UITextDisplayer = class {
|
|
|
400
427
|
*/
|
|
401
428
|
getRegionElement_(cue) {
|
|
402
429
|
const region = cue.region;
|
|
430
|
+
// from https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#caption-window-size
|
|
431
|
+
// if aspect ratio is 4/3, use that value, otherwise, use the 16:9 value
|
|
432
|
+
const lineWidthMultiple = this.aspectRatio_ === 4/3 ? 2.5 : 1.9;
|
|
433
|
+
const lineHeightMultiple = 5.33;
|
|
403
434
|
|
|
404
435
|
const regionId = this.generateRegionId_(region);
|
|
405
436
|
if (this.regionElements_.has(regionId)) {
|
|
@@ -417,11 +448,33 @@ shaka.text.UITextDisplayer = class {
|
|
|
417
448
|
regionElement.id = 'shaka-text-region---' + regionId;
|
|
418
449
|
regionElement.classList.add('shaka-text-region');
|
|
419
450
|
|
|
420
|
-
regionElement.style.height = region.height + heightUnit;
|
|
421
|
-
regionElement.style.width = region.width + widthUnit;
|
|
422
451
|
regionElement.style.position = 'absolute';
|
|
423
|
-
|
|
424
|
-
|
|
452
|
+
|
|
453
|
+
const linesUnit = shaka.text.CueRegion.units.LINES;
|
|
454
|
+
if (region.heightUnits === linesUnit && region.widthUnits === linesUnit) {
|
|
455
|
+
regionElement.style.height = region.height * lineHeightMultiple + '%';
|
|
456
|
+
regionElement.style.width = region.width * lineWidthMultiple + '%';
|
|
457
|
+
} else {
|
|
458
|
+
regionElement.style.height = region.height + heightUnit;
|
|
459
|
+
regionElement.style.width = region.width + widthUnit;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (region.viewportAnchorUnits === linesUnit) {
|
|
463
|
+
// from https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-708
|
|
464
|
+
let top = region.viewportAnchorY / 75 * 100;
|
|
465
|
+
const windowWidth = this.aspectRatio_ === 4/3 ? 160 : 210;
|
|
466
|
+
let left = region.viewportAnchorX / windowWidth * 100;
|
|
467
|
+
// adjust top and left values based on the region anchor and window size
|
|
468
|
+
top -= region.regionAnchorY * region.height * lineHeightMultiple / 100;
|
|
469
|
+
left -= region.regionAnchorX * region.width * lineWidthMultiple / 100;
|
|
470
|
+
regionElement.style.top = top + '%';
|
|
471
|
+
regionElement.style.left = left + '%';
|
|
472
|
+
} else {
|
|
473
|
+
regionElement.style.top = region.viewportAnchorY -
|
|
474
|
+
region.regionAnchorY * region.height / 100 + viewportAnchorUnit;
|
|
475
|
+
regionElement.style.left = region.viewportAnchorX -
|
|
476
|
+
region.regionAnchorX * region.width / 100 + viewportAnchorUnit;
|
|
477
|
+
}
|
|
425
478
|
|
|
426
479
|
regionElement.style.display = 'flex';
|
|
427
480
|
regionElement.style.flexDirection = 'column';
|
|
@@ -600,6 +653,10 @@ shaka.text.UITextDisplayer = class {
|
|
|
600
653
|
// Only do this for the text itself.
|
|
601
654
|
elem.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
|
|
602
655
|
}
|
|
656
|
+
const fontFamily = inherit((c) => c.fontFamily);
|
|
657
|
+
if (fontFamily) {
|
|
658
|
+
elem.style.fontFamily = fontFamily;
|
|
659
|
+
}
|
|
603
660
|
}
|
|
604
661
|
if (text) {
|
|
605
662
|
elem.textContent = text;
|
|
@@ -653,37 +710,46 @@ shaka.text.UITextDisplayer = class {
|
|
|
653
710
|
// (for vertical growing left) is aligned at the line.
|
|
654
711
|
// TODO: Implement line alignment with line number.
|
|
655
712
|
// TODO: Implement lineAlignment of 'CENTER'.
|
|
656
|
-
|
|
713
|
+
let line = cue.line;
|
|
714
|
+
if (line != null) {
|
|
657
715
|
let lineInterpretation = cue.lineInterpretation;
|
|
658
716
|
// HACK: the current implementation of UITextDisplayer only handled
|
|
659
|
-
// PERCENTAGE,
|
|
660
|
-
|
|
661
|
-
if (cue.line == 0 &&
|
|
662
|
-
lineInterpretation == Cue.lineInterpretation.LINE_NUMBER) {
|
|
717
|
+
// PERCENTAGE, so we need convert LINE_NUMBER to PERCENTAGE
|
|
718
|
+
if (lineInterpretation == Cue.lineInterpretation.LINE_NUMBER) {
|
|
663
719
|
lineInterpretation = Cue.lineInterpretation.PERCENTAGE;
|
|
720
|
+
let maxLines = 16;
|
|
721
|
+
// The maximum number of lines is different if it is a vertical video.
|
|
722
|
+
if (this.aspectRatio_ && this.aspectRatio_ < 1) {
|
|
723
|
+
maxLines = 32;
|
|
724
|
+
}
|
|
725
|
+
if (line < 0) {
|
|
726
|
+
line = 100 + line / maxLines * 100;
|
|
727
|
+
} else {
|
|
728
|
+
line = line / maxLines * 100;
|
|
729
|
+
}
|
|
664
730
|
}
|
|
665
731
|
if (lineInterpretation == Cue.lineInterpretation.PERCENTAGE) {
|
|
666
732
|
style.position = 'absolute';
|
|
667
733
|
if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
|
|
668
734
|
style.width = '100%';
|
|
669
735
|
if (cue.lineAlign == Cue.lineAlign.START) {
|
|
670
|
-
style.top =
|
|
736
|
+
style.top = line + '%';
|
|
671
737
|
} else if (cue.lineAlign == Cue.lineAlign.END) {
|
|
672
|
-
style.bottom = (100 -
|
|
738
|
+
style.bottom = (100 - line) + '%';
|
|
673
739
|
}
|
|
674
740
|
} else if (cue.writingMode == Cue.writingMode.VERTICAL_LEFT_TO_RIGHT) {
|
|
675
741
|
style.height = '100%';
|
|
676
742
|
if (cue.lineAlign == Cue.lineAlign.START) {
|
|
677
|
-
style.left =
|
|
743
|
+
style.left = line + '%';
|
|
678
744
|
} else if (cue.lineAlign == Cue.lineAlign.END) {
|
|
679
|
-
style.right = (100 -
|
|
745
|
+
style.right = (100 - line) + '%';
|
|
680
746
|
}
|
|
681
747
|
} else {
|
|
682
748
|
style.height = '100%';
|
|
683
749
|
if (cue.lineAlign == Cue.lineAlign.START) {
|
|
684
|
-
style.right =
|
|
750
|
+
style.right = line + '%';
|
|
685
751
|
} else if (cue.lineAlign == Cue.lineAlign.END) {
|
|
686
|
-
style.left = (100 -
|
|
752
|
+
style.left = (100 - line) + '%';
|
|
687
753
|
}
|
|
688
754
|
}
|
|
689
755
|
}
|