shaka-player 4.12.9 → 4.13.0
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/AUTHORS +1 -0
- package/CONTRIBUTORS +4 -0
- package/README.md +22 -21
- package/cspell.config.yaml +18 -0
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/deps.js +29 -24
- package/dist/locales.js +36 -36
- package/dist/shaka-player.compiled.d.ts +131 -36
- package/dist/shaka-player.compiled.debug.d.ts +131 -36
- package/dist/shaka-player.compiled.debug.externs.js +355 -107
- package/dist/shaka-player.compiled.debug.js +799 -742
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +355 -107
- package/dist/shaka-player.compiled.js +1456 -1423
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +165 -38
- package/dist/shaka-player.ui.debug.d.ts +165 -38
- package/dist/shaka-player.ui.debug.externs.js +394 -113
- package/dist/shaka-player.ui.debug.js +808 -738
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +394 -113
- package/dist/shaka-player.ui.js +1671 -1632
- package/dist/shaka-player.ui.map +4 -4
- package/eslint.config.mjs +343 -0
- package/externs/airplay.js +1 -1
- package/externs/audiotrack.js +1 -1
- package/externs/awesomplete.js +1 -1
- package/externs/cast-sdk-v2.js +2 -2
- package/externs/cmcd.js +16 -1
- package/externs/device_sensor_event.js +1 -1
- package/externs/ima.js +8 -8
- package/externs/isoboxer.js +31 -31
- package/externs/jwk_set.js +1 -1
- package/externs/lcevc.js +8 -8
- package/externs/mediakeys.js +1 -1
- package/externs/mediasession.js +3 -2
- package/externs/mediatailor.js +18 -18
- package/externs/pictureinpicture.js +41 -1
- package/externs/playstation.js +2 -2
- package/externs/prefixed_eme.js +1 -1
- package/externs/shaka/abortable.js +4 -4
- package/externs/shaka/abr_manager.js +1 -1
- package/externs/shaka/ads.js +45 -6
- package/externs/shaka/cea.js +2 -2
- package/externs/shaka/codecs.js +4 -4
- package/externs/shaka/drm_info.js +6 -6
- package/externs/shaka/error.js +5 -5
- package/externs/shaka/manifest.js +51 -56
- package/externs/shaka/manifest_parser.js +3 -9
- package/externs/shaka/net.js +23 -20
- package/externs/shaka/offline.js +37 -37
- package/externs/shaka/offline_compat_v1.js +10 -10
- package/externs/shaka/offline_compat_v2.js +10 -10
- package/externs/shaka/player.js +137 -108
- package/externs/shaka/text.js +3 -3
- package/externs/shaka/transmuxer.js +1 -1
- package/externs/videotrack.js +1 -1
- package/lib/abr/ewma.js +1 -1
- package/lib/abr/simple_abr_manager.js +70 -22
- package/lib/ads/ad_manager.js +43 -18
- package/lib/ads/ad_utils.js +160 -58
- package/lib/ads/ads_stats.js +1 -1
- package/lib/ads/client_side_ad_manager.js +2 -2
- package/lib/ads/interstitial_ad.js +16 -11
- package/lib/ads/interstitial_ad_manager.js +419 -72
- package/lib/ads/interstitial_static_ad.js +299 -0
- package/lib/ads/media_tailor_ad.js +1 -0
- package/lib/ads/media_tailor_ad_manager.js +37 -36
- package/lib/ads/server_side_ad_manager.js +7 -6
- package/lib/cast/cast_receiver.js +5 -3
- package/lib/cast/cast_sender.js +2 -2
- package/lib/cast/cast_utils.js +20 -13
- package/lib/cea/cea608_data_channel.js +53 -53
- package/lib/cea/cea708_service.js +34 -24
- package/lib/cea/cea708_window.js +5 -2
- package/lib/cea/cea_decoder.js +5 -7
- package/lib/cea/mp4_cea_parser.js +1 -1
- package/lib/cea/sei_processor.js +1 -1
- package/lib/dash/content_protection.js +62 -192
- package/lib/dash/dash_parser.js +98 -108
- package/lib/dash/mpd_utils.js +14 -13
- package/lib/dash/segment_base.js +5 -5
- package/lib/dash/segment_list.js +10 -10
- package/lib/dash/segment_template.js +14 -14
- package/lib/debug/log.js +2 -2
- package/lib/dependencies/all.js +2 -1
- package/lib/{media → drm}/drm_engine.js +274 -143
- package/lib/{util → drm}/drm_utils.js +20 -11
- package/lib/drm/playready.js +233 -0
- package/lib/hls/hls_classes.js +14 -14
- package/lib/hls/hls_parser.js +257 -196
- package/lib/hls/hls_utils.js +14 -14
- package/lib/hls/manifest_text_parser.js +10 -10
- package/lib/media/adaptation_set.js +6 -5
- package/lib/media/adaptation_set_criteria.js +51 -379
- package/lib/media/buffering_observer.js +5 -4
- package/lib/media/closed_caption_parser.js +2 -2
- package/lib/media/content_workarounds.js +2 -2
- package/lib/media/example_based_criteria.js +69 -0
- package/lib/media/manifest_filterer.js +8 -8
- package/lib/media/manifest_parser.js +2 -2
- package/lib/media/media_source_capabilities.js +20 -1
- package/lib/media/media_source_engine.js +71 -26
- package/lib/media/mp4_segment_index_parser.js +5 -5
- package/lib/media/playhead.js +2 -8
- package/lib/media/playhead_observer.js +1 -1
- package/lib/media/preference_based_criteria.js +333 -0
- package/lib/media/preload_manager.js +53 -37
- package/lib/media/presentation_timeline.js +2 -2
- package/lib/media/quality_observer.js +35 -35
- package/lib/media/region_observer.js +2 -2
- package/lib/media/region_timeline.js +3 -3
- package/lib/media/segment_index.js +14 -14
- package/lib/media/segment_prefetch.js +17 -13
- package/lib/media/segment_reference.js +7 -7
- package/lib/media/segment_utils.js +9 -9
- package/lib/media/streaming_engine.js +77 -38
- package/lib/media/time_ranges_utils.js +1 -1
- package/lib/media/video_wrapper.js +1 -1
- package/lib/media/webm_segment_index_parser.js +4 -4
- package/lib/mss/content_protection.js +17 -222
- package/lib/mss/mss_parser.js +23 -24
- package/lib/net/data_uri_plugin.js +1 -0
- package/lib/net/http_fetch_plugin.js +8 -7
- package/lib/net/http_plugin_utils.js +5 -2
- package/lib/net/http_xhr_plugin.js +4 -3
- package/lib/net/networking_engine.js +72 -17
- package/lib/net/networking_utils.js +5 -2
- package/lib/offline/download_info.js +5 -1
- package/lib/offline/download_manager.js +15 -4
- package/lib/offline/download_progress_estimator.js +1 -1
- package/lib/offline/indexeddb/base_storage_cell.js +9 -9
- package/lib/offline/indexeddb/db_connection.js +1 -1
- package/lib/offline/indexeddb/db_operation.js +2 -2
- package/lib/offline/indexeddb/eme_session_storage_cell.js +1 -1
- package/lib/offline/indexeddb/storage_mechanism.js +1 -1
- package/lib/offline/indexeddb/v1_storage_cell.js +3 -3
- package/lib/offline/indexeddb/v2_storage_cell.js +2 -2
- package/lib/offline/manifest_converter.js +11 -12
- package/lib/offline/offline_scheme.js +8 -4
- package/lib/offline/session_deleter.js +11 -11
- package/lib/offline/storage.js +137 -88
- package/lib/offline/storage_muxer.js +8 -8
- package/lib/offline/stored_content_utils.js +3 -3
- package/lib/offline/stream_bandwidth_estimator.js +1 -1
- package/lib/player.js +270 -207
- package/lib/polyfill/all.js +1 -1
- package/lib/polyfill/media_capabilities.js +5 -5
- package/lib/polyfill/patchedmediakeys_apple.js +6 -6
- package/lib/polyfill/patchedmediakeys_webkit.js +8 -8
- package/lib/polyfill/video_play_promise.js +0 -1
- package/lib/text/cue.js +3 -2
- package/lib/text/lrc_text_parser.js +1 -1
- package/lib/text/mp4_ttml_parser.js +1 -1
- package/lib/text/mp4_vtt_parser.js +3 -3
- package/lib/text/sbv_text_parser.js +1 -1
- package/lib/text/ssa_text_parser.js +3 -1
- package/lib/text/stub_text_displayer.js +5 -5
- package/lib/text/text_engine.js +5 -4
- package/lib/text/text_utils.js +4 -4
- package/lib/text/ttml_text_parser.js +21 -20
- package/lib/text/ui_text_displayer.js +13 -13
- package/lib/text/vtt_text_parser.js +10 -10
- package/lib/text/web_vtt_generator.js +2 -2
- package/lib/transmuxer/aac_transmuxer.js +2 -2
- package/lib/transmuxer/ac3_transmuxer.js +2 -2
- package/lib/transmuxer/adts.js +2 -0
- package/lib/transmuxer/ec3.js +3 -3
- package/lib/transmuxer/ec3_transmuxer.js +2 -2
- package/lib/transmuxer/h264.js +5 -5
- package/lib/transmuxer/h265.js +3 -3
- package/lib/transmuxer/mp3_transmuxer.js +2 -2
- package/lib/transmuxer/mpeg_audio.js +6 -6
- package/lib/transmuxer/mss_transmuxer.js +2 -0
- package/lib/transmuxer/transmuxer_engine.js +1 -1
- package/lib/transmuxer/ts_transmuxer.js +10 -10
- package/lib/util/abortable_operation.js +10 -10
- package/lib/util/array_utils.js +6 -6
- package/lib/util/cmcd_manager.js +149 -18
- package/lib/util/cmsd_manager.js +11 -11
- package/lib/util/config_utils.js +3 -3
- package/lib/util/content_steering_manager.js +17 -17
- package/lib/util/data_view_reader.js +3 -3
- package/lib/util/ebml_parser.js +1 -1
- package/lib/util/error.js +11 -6
- package/lib/util/event_manager.js +1 -1
- package/lib/util/exp_golomb.js +1 -1
- package/lib/util/fairplay_utils.js +6 -1
- package/lib/util/fake_event.js +3 -2
- package/lib/util/fake_event_target.js +1 -1
- package/lib/util/functional.js +8 -8
- package/lib/util/id3_utils.js +1 -1
- package/lib/util/iterables.js +6 -6
- package/lib/util/language_utils.js +17 -17
- package/lib/util/manifest_parser_utils.js +13 -13
- package/lib/util/map_utils.js +6 -6
- package/lib/util/media_ready_state_utils.js +1 -1
- package/lib/util/mime_utils.js +15 -4
- package/lib/util/mp4_box_parsers.js +5 -5
- package/lib/util/mp4_generator.js +31 -30
- package/lib/util/mp4_parser.js +2 -2
- package/lib/util/multi_map.js +5 -5
- package/lib/util/mutex.js +1 -1
- package/lib/util/networking.js +1 -1
- package/lib/util/object_utils.js +5 -1
- package/lib/util/operation_manager.js +1 -1
- package/lib/util/periods.js +24 -24
- package/lib/util/platform.js +8 -4
- package/lib/util/player_configuration.js +50 -17
- package/lib/util/pssh.js +34 -6
- package/lib/util/public_promise.js +2 -2
- package/lib/util/state_history.js +2 -2
- package/lib/util/stream_utils.js +138 -120
- package/lib/util/string_utils.js +4 -3
- package/lib/util/switch_history.js +2 -2
- package/lib/util/tXml.js +22 -17
- package/lib/util/text_parser.js +2 -2
- package/lib/util/timer.js +1 -1
- package/lib/util/ts_parser.js +22 -22
- package/lib/util/xml_utils.js +1 -0
- package/package.json +7 -4
- package/project-words.txt +485 -0
- package/shaka-player.uncompiled.js +1 -1
- package/third_party/closure-uri/uri.js +2 -2
- package/third_party/closure-uri/utils.js +2 -2
- package/third_party/language-mapping-list/language-mapping-list.js +1 -1
- package/ui/ad_statistics_button.js +9 -5
- package/ui/airplay_button.js +1 -0
- package/ui/audio_language_selection.js +2 -1
- package/ui/context_menu.js +2 -2
- package/ui/controls.js +7 -7
- package/ui/externs/ui.js +29 -21
- package/ui/externs/watermark.js +64 -0
- package/ui/fast_forward_button.js +1 -1
- package/ui/gl_matrix/matrix_4x4.js +11 -5
- package/ui/hidden_fast_forward_button.js +1 -1
- package/ui/hidden_seek_button.js +1 -1
- package/ui/language_utils.js +27 -26
- package/ui/less/buttons.less +2 -2
- package/ui/localization.js +21 -21
- package/ui/loop_button.js +2 -2
- package/ui/overflow_menu.js +2 -2
- package/ui/playback_rate_selection.js +1 -1
- package/ui/presentation_time.js +4 -1
- package/ui/range_element.js +2 -2
- package/ui/resolution_selection.js +42 -6
- package/ui/rewind_button.js +1 -1
- package/ui/seek_bar.js +5 -5
- package/ui/statistics_button.js +8 -4
- package/ui/text_selection.js +3 -2
- package/ui/ui.js +32 -2
- package/ui/vr_manager.js +3 -3
- package/ui/vr_utils.js +14 -14
- package/ui/vr_webgl.js +5 -5
- package/ui/watermark.js +313 -0
- package/.eslintignore +0 -4
- package/.eslintrc.js +0 -310
- package/lib/polyfill/patchedmediakeys_nop.js +0 -148
package/lib/polyfill/all.js
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
goog.provide('shaka.polyfill.MediaCapabilities');
|
|
8
8
|
|
|
9
9
|
goog.require('shaka.log');
|
|
10
|
+
goog.require('shaka.drm.DrmUtils');
|
|
10
11
|
goog.require('shaka.media.Capabilities');
|
|
11
12
|
goog.require('shaka.polyfill');
|
|
12
|
-
goog.require('shaka.util.DrmUtils');
|
|
13
13
|
goog.require('shaka.util.MimeUtils');
|
|
14
14
|
goog.require('shaka.util.Platform');
|
|
15
15
|
|
|
@@ -89,7 +89,7 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* @param {!MediaDecodingConfiguration} mediaDecodingConfig
|
|
92
|
-
* @return {!Promise
|
|
92
|
+
* @return {!Promise<!MediaCapabilitiesDecodingInfo>}
|
|
93
93
|
* @private
|
|
94
94
|
*/
|
|
95
95
|
static async decodingInfo_(mediaDecodingConfig) {
|
|
@@ -291,14 +291,14 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
291
291
|
/** @type {MediaKeySystemAccess} */
|
|
292
292
|
let keySystemAccess = null;
|
|
293
293
|
try {
|
|
294
|
-
if (shaka.
|
|
294
|
+
if (shaka.drm.DrmUtils.hasMediaKeySystemAccess(
|
|
295
295
|
videoCodec, audioCodec, keySystem)) {
|
|
296
|
-
keySystemAccess = shaka.
|
|
296
|
+
keySystemAccess = shaka.drm.DrmUtils.getMediaKeySystemAccess(
|
|
297
297
|
videoCodec, audioCodec, keySystem);
|
|
298
298
|
} else {
|
|
299
299
|
keySystemAccess = await navigator.requestMediaKeySystemAccess(
|
|
300
300
|
mcapKeySystemConfig.keySystem, [mediaKeySystemConfig]);
|
|
301
|
-
shaka.
|
|
301
|
+
shaka.drm.DrmUtils.setMediaKeySystemAccess(
|
|
302
302
|
videoCodec, audioCodec, keySystem, keySystemAccess);
|
|
303
303
|
}
|
|
304
304
|
} catch (e) {
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
goog.provide('shaka.polyfill.PatchedMediaKeysApple');
|
|
8
8
|
|
|
9
9
|
goog.require('goog.asserts');
|
|
10
|
+
goog.require('shaka.drm.DrmUtils');
|
|
10
11
|
goog.require('shaka.log');
|
|
11
12
|
goog.require('shaka.polyfill');
|
|
12
13
|
goog.require('shaka.util.BufferUtils');
|
|
13
|
-
goog.require('shaka.util.DrmUtils');
|
|
14
14
|
goog.require('shaka.util.EventManager');
|
|
15
15
|
goog.require('shaka.util.FakeEvent');
|
|
16
16
|
goog.require('shaka.util.FakeEventTarget');
|
|
@@ -124,8 +124,8 @@ shaka.polyfill.PatchedMediaKeysApple = class {
|
|
|
124
124
|
*
|
|
125
125
|
* @this {!Navigator}
|
|
126
126
|
* @param {string} keySystem
|
|
127
|
-
* @param {!Array
|
|
128
|
-
* @return {!Promise
|
|
127
|
+
* @param {!Array<!MediaKeySystemConfiguration>} supportedConfigurations
|
|
128
|
+
* @return {!Promise<!MediaKeySystemAccess>}
|
|
129
129
|
*/
|
|
130
130
|
static requestMediaKeySystemAccess(keySystem, supportedConfigurations) {
|
|
131
131
|
shaka.log.debug('PatchedMediaKeysApple.requestMediaKeySystemAccess');
|
|
@@ -245,7 +245,7 @@ shaka.polyfill.PatchedMediaKeysApple = class {
|
|
|
245
245
|
shaka.polyfill.PatchedMediaKeysApple.MediaKeySystemAccess = class {
|
|
246
246
|
/**
|
|
247
247
|
* @param {string} keySystem
|
|
248
|
-
* @param {!Array
|
|
248
|
+
* @param {!Array<!MediaKeySystemConfiguration>} supportedConfigurations
|
|
249
249
|
*/
|
|
250
250
|
constructor(keySystem, supportedConfigurations) {
|
|
251
251
|
shaka.log.debug('PatchedMediaKeysApple.MediaKeySystemAccess');
|
|
@@ -746,7 +746,7 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap = class {
|
|
|
746
746
|
/** @override */
|
|
747
747
|
forEach(fn) {
|
|
748
748
|
if (this.status_) {
|
|
749
|
-
fn(this.status_, shaka.
|
|
749
|
+
fn(this.status_, shaka.drm.DrmUtils.DUMMY_KEY_ID.value());
|
|
750
750
|
}
|
|
751
751
|
}
|
|
752
752
|
|
|
@@ -760,7 +760,7 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap = class {
|
|
|
760
760
|
|
|
761
761
|
/** @override */
|
|
762
762
|
has(keyId) {
|
|
763
|
-
const fakeKeyId = shaka.
|
|
763
|
+
const fakeKeyId = shaka.drm.DrmUtils.DUMMY_KEY_ID.value();
|
|
764
764
|
if (this.status_ && shaka.util.BufferUtils.equal(keyId, fakeKeyId)) {
|
|
765
765
|
return true;
|
|
766
766
|
}
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
goog.provide('shaka.polyfill.PatchedMediaKeysWebkit');
|
|
8
8
|
|
|
9
9
|
goog.require('goog.asserts');
|
|
10
|
+
goog.require('shaka.drm.DrmUtils');
|
|
10
11
|
goog.require('shaka.log');
|
|
11
12
|
goog.require('shaka.polyfill');
|
|
12
13
|
goog.require('shaka.util.BufferUtils');
|
|
13
|
-
goog.require('shaka.util.DrmUtils');
|
|
14
14
|
goog.require('shaka.util.EventManager');
|
|
15
15
|
goog.require('shaka.util.FakeEvent');
|
|
16
16
|
goog.require('shaka.util.FakeEventTarget');
|
|
@@ -97,8 +97,8 @@ shaka.polyfill.PatchedMediaKeysWebkit = class {
|
|
|
97
97
|
*
|
|
98
98
|
* @this {!Navigator}
|
|
99
99
|
* @param {string} keySystem
|
|
100
|
-
* @param {!Array
|
|
101
|
-
* @return {!Promise
|
|
100
|
+
* @param {!Array<!MediaKeySystemConfiguration>} supportedConfigurations
|
|
101
|
+
* @return {!Promise<!MediaKeySystemAccess>}
|
|
102
102
|
*/
|
|
103
103
|
static requestMediaKeySystemAccess(keySystem, supportedConfigurations) {
|
|
104
104
|
shaka.log.debug('PatchedMediaKeysWebkit.requestMediaKeySystemAccess');
|
|
@@ -185,7 +185,7 @@ shaka.polyfill.PatchedMediaKeysWebkit = class {
|
|
|
185
185
|
shaka.polyfill.PatchedMediaKeysWebkit.MediaKeySystemAccess = class {
|
|
186
186
|
/**
|
|
187
187
|
* @param {string} keySystem
|
|
188
|
-
* @param {!Array
|
|
188
|
+
* @param {!Array<!MediaKeySystemConfiguration>} supportedConfigurations
|
|
189
189
|
*/
|
|
190
190
|
constructor(keySystem, supportedConfigurations) {
|
|
191
191
|
shaka.log.debug('PatchedMediaKeysWebkit.MediaKeySystemAccess');
|
|
@@ -340,12 +340,12 @@ shaka.polyfill.PatchedMediaKeysWebkit.MediaKeys = class {
|
|
|
340
340
|
this.eventManager_ = new shaka.util.EventManager();
|
|
341
341
|
|
|
342
342
|
/**
|
|
343
|
-
* @private {Array
|
|
343
|
+
* @private {Array<!shaka.polyfill.PatchedMediaKeysWebkit.MediaKeySession>}
|
|
344
344
|
*/
|
|
345
345
|
this.newSessions_ = [];
|
|
346
346
|
|
|
347
347
|
/**
|
|
348
|
-
* @private {!Map
|
|
348
|
+
* @private {!Map<string,
|
|
349
349
|
* !shaka.polyfill.PatchedMediaKeysWebkit.MediaKeySession>}
|
|
350
350
|
*/
|
|
351
351
|
this.sessionMap_ = new Map();
|
|
@@ -924,7 +924,7 @@ shaka.polyfill.PatchedMediaKeysWebkit.MediaKeyStatusMap = class {
|
|
|
924
924
|
/** @override */
|
|
925
925
|
forEach(fn) {
|
|
926
926
|
if (this.status_) {
|
|
927
|
-
fn(this.status_, shaka.
|
|
927
|
+
fn(this.status_, shaka.drm.DrmUtils.DUMMY_KEY_ID.value());
|
|
928
928
|
}
|
|
929
929
|
}
|
|
930
930
|
|
|
@@ -938,7 +938,7 @@ shaka.polyfill.PatchedMediaKeysWebkit.MediaKeyStatusMap = class {
|
|
|
938
938
|
|
|
939
939
|
/** @override */
|
|
940
940
|
has(keyId) {
|
|
941
|
-
const fakeKeyId = shaka.
|
|
941
|
+
const fakeKeyId = shaka.drm.DrmUtils.DUMMY_KEY_ID.value();
|
|
942
942
|
if (this.status_ && shaka.util.BufferUtils.equal(keyId, fakeKeyId)) {
|
|
943
943
|
return true;
|
|
944
944
|
}
|
|
@@ -27,7 +27,6 @@ shaka.polyfill.VideoPlayPromise = class {
|
|
|
27
27
|
const originalPlay = HTMLMediaElement.prototype.play;
|
|
28
28
|
// eslint-disable-next-line no-restricted-syntax
|
|
29
29
|
HTMLMediaElement.prototype.play = function() {
|
|
30
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
31
30
|
const p = originalPlay.apply(this);
|
|
32
31
|
if (p) {
|
|
33
32
|
// This browser is returning a Promise from play().
|
package/lib/text/cue.js
CHANGED
|
@@ -236,7 +236,7 @@ shaka.text.Cue = class {
|
|
|
236
236
|
/**
|
|
237
237
|
* Text decoration. A combination of underline, overline
|
|
238
238
|
* and line through. Empty array means no decoration.
|
|
239
|
-
* @type {!Array
|
|
239
|
+
* @type {!Array<!shaka.text.Cue.textDecoration>}
|
|
240
240
|
* @export
|
|
241
241
|
*/
|
|
242
242
|
this.textDecoration = [];
|
|
@@ -280,7 +280,7 @@ shaka.text.Cue = class {
|
|
|
280
280
|
* Nested cues, which should be laid out horizontally in one block.
|
|
281
281
|
* Top-level cues are blocks, and nested cues are inline elements.
|
|
282
282
|
* Cues can be nested arbitrarily deeply.
|
|
283
|
-
* @type {!Array
|
|
283
|
+
* @type {!Array<!shaka.text.Cue>}
|
|
284
284
|
* @export
|
|
285
285
|
*/
|
|
286
286
|
this.nestedCues = [];
|
|
@@ -764,6 +764,7 @@ shaka.text.Cue = class {
|
|
|
764
764
|
/**
|
|
765
765
|
* @param {string} value
|
|
766
766
|
* @param {string} defaultValue
|
|
767
|
+
* @return {string}
|
|
767
768
|
* @private
|
|
768
769
|
*/
|
|
769
770
|
static getOrDefault_(value, defaultValue) {
|
|
@@ -58,7 +58,7 @@ shaka.text.LrcTextParser = class {
|
|
|
58
58
|
/** @type {shaka.text.Cue} */
|
|
59
59
|
let prevCue = null;
|
|
60
60
|
|
|
61
|
-
/** @type {!Array
|
|
61
|
+
/** @type {!Array<!shaka.text.Cue>} */
|
|
62
62
|
const cues = [];
|
|
63
63
|
const lines = str.split(/\r?\n/);
|
|
64
64
|
for (const line of lines) {
|
|
@@ -122,11 +122,11 @@ shaka.text.Mp4VttParser = class {
|
|
|
122
122
|
const Mp4Parser = shaka.util.Mp4Parser;
|
|
123
123
|
|
|
124
124
|
let baseTime = 0;
|
|
125
|
-
/** @type {!Array
|
|
125
|
+
/** @type {!Array<shaka.util.ParsedTRUNSample>} */
|
|
126
126
|
let presentations = [];
|
|
127
127
|
/** @type {!Uint8Array} */
|
|
128
128
|
let rawPayload;
|
|
129
|
-
/** @type {!Array
|
|
129
|
+
/** @type {!Array<shaka.text.Cue>} */
|
|
130
130
|
const cues = [];
|
|
131
131
|
|
|
132
132
|
let sawTFDT = false;
|
|
@@ -258,7 +258,7 @@ shaka.text.Mp4VttParser = class {
|
|
|
258
258
|
'MDAT which contain VTT cues and non-VTT data are not currently ' +
|
|
259
259
|
'supported!');
|
|
260
260
|
|
|
261
|
-
return /** @type {!Array
|
|
261
|
+
return /** @type {!Array<!shaka.text.Cue>} */ (
|
|
262
262
|
cues.filter(shaka.util.Functional.isNotNull));
|
|
263
263
|
}
|
|
264
264
|
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
// cspell:ignore AABBGGRR HAABBGGRR
|
|
8
|
+
|
|
7
9
|
goog.provide('shaka.text.SsaTextParser');
|
|
8
10
|
|
|
9
11
|
goog.require('goog.asserts');
|
|
@@ -128,7 +130,7 @@ shaka.text.SsaTextParser = class {
|
|
|
128
130
|
}
|
|
129
131
|
|
|
130
132
|
// Process cues
|
|
131
|
-
/** @type {!Array
|
|
133
|
+
/** @type {!Array<!shaka.text.Cue>} */
|
|
132
134
|
const cues = [];
|
|
133
135
|
|
|
134
136
|
// Used to be able to iterate over the event parameters.
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
goog.provide('shaka.text.StubTextDisplayer');
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* A stub text displayer plugin that does nothing
|
|
11
|
-
*
|
|
12
|
-
* @implements {shaka.extern.TextDisplayer}
|
|
13
|
-
* @export
|
|
14
|
-
*/
|
|
10
|
+
* A stub text displayer plugin that does nothing
|
|
11
|
+
*
|
|
12
|
+
* @implements {shaka.extern.TextDisplayer}
|
|
13
|
+
* @export
|
|
14
|
+
*/
|
|
15
15
|
shaka.text.StubTextDisplayer = class {
|
|
16
16
|
/**
|
|
17
17
|
* @override
|
package/lib/text/text_engine.js
CHANGED
|
@@ -63,7 +63,8 @@ shaka.text.TextEngine = class {
|
|
|
63
63
|
* re-fetching the video segments they were embedded in.
|
|
64
64
|
* Structure of closed caption map:
|
|
65
65
|
* closed caption id -> {start and end time -> cues}
|
|
66
|
-
* @private {!Map
|
|
66
|
+
* @private {!Map<string, !Map<string, !Array<shaka.text.Cue>>>}
|
|
67
|
+
*/
|
|
67
68
|
this.closedCaptionsMap_ = new Map();
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -355,7 +356,7 @@ shaka.text.TextEngine = class {
|
|
|
355
356
|
const captionsMap = this.closedCaptionsMap_.get(id);
|
|
356
357
|
if (captionsMap) {
|
|
357
358
|
for (const startAndEndTime of captionsMap.keys()) {
|
|
358
|
-
/** @type {Array
|
|
359
|
+
/** @type {Array<!shaka.text.Cue>} */
|
|
359
360
|
const cues = captionsMap.get(startAndEndTime)
|
|
360
361
|
.filter((c) => c.endTime <= bufferEndTime);
|
|
361
362
|
if (cues) {
|
|
@@ -391,7 +392,7 @@ shaka.text.TextEngine = class {
|
|
|
391
392
|
storeAndAppendClosedCaptions(
|
|
392
393
|
closedCaptions, startTime, endTime, videoTimestampOffset) {
|
|
393
394
|
const startAndEndTime = startTime + ' ' + endTime;
|
|
394
|
-
/** @type {!Map
|
|
395
|
+
/** @type {!Map<string, !Map<string, !Array<!shaka.text.Cue>>>} */
|
|
395
396
|
const captionsMap = new Map();
|
|
396
397
|
|
|
397
398
|
for (const caption of closedCaptions) {
|
|
@@ -468,5 +469,5 @@ shaka.text.TextEngine = class {
|
|
|
468
469
|
}
|
|
469
470
|
};
|
|
470
471
|
|
|
471
|
-
/** @private {!Object
|
|
472
|
+
/** @private {!Object<string, !shaka.extern.TextParserPlugin>} */
|
|
472
473
|
shaka.text.TextEngine.parserMap_ = {};
|
package/lib/text/text_utils.js
CHANGED
|
@@ -148,9 +148,9 @@ shaka.text.Utils = class {
|
|
|
148
148
|
* this, we only call flattenPayload() starting at elements marked as
|
|
149
149
|
* isContainer = false.
|
|
150
150
|
*
|
|
151
|
-
* @param {!Array
|
|
151
|
+
* @param {!Array<!shaka.text.Cue>} cues
|
|
152
152
|
* @param {?shaka.text.Cue=} parentCue
|
|
153
|
-
* @return {!Array
|
|
153
|
+
* @return {!Array<!shaka.text.Cue>}
|
|
154
154
|
*/
|
|
155
155
|
static getCuesToFlatten(cues, parentCue) {
|
|
156
156
|
const result = [];
|
|
@@ -170,8 +170,8 @@ shaka.text.Utils = class {
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
/**
|
|
173
|
-
* @param {!Array
|
|
174
|
-
* @return {!Array
|
|
173
|
+
* @param {!Array<!shaka.text.Cue>} cues
|
|
174
|
+
* @return {!Array<!shaka.text.Cue>}
|
|
175
175
|
*/
|
|
176
176
|
static removeDuplicates(cues) {
|
|
177
177
|
const uniqueCues = [];
|
|
@@ -169,16 +169,16 @@ shaka.text.TtmlTextParser = class {
|
|
|
169
169
|
* @param {!shaka.extern.xml.Node} cueNode
|
|
170
170
|
* @param {shaka.extern.TextParser.TimeContext} timeContext
|
|
171
171
|
* @param {!shaka.text.TtmlTextParser.RateInfo_} rateInfo
|
|
172
|
-
* @param {!Array
|
|
173
|
-
* @param {!Array
|
|
174
|
-
* @param {!Array
|
|
175
|
-
* @param {!Array
|
|
172
|
+
* @param {!Array<!shaka.extern.xml.Node>} metadataElements
|
|
173
|
+
* @param {!Array<!shaka.extern.xml.Node>} styles
|
|
174
|
+
* @param {!Array<!shaka.extern.xml.Node>} regionElements
|
|
175
|
+
* @param {!Array<!shaka.text.CueRegion>} cueRegions
|
|
176
176
|
* @param {boolean} collapseMultipleSpaces
|
|
177
177
|
* @param {?{columns: number, rows: number}} cellResolution
|
|
178
178
|
* @param {?shaka.extern.xml.Node} parentCueElement
|
|
179
179
|
* @param {boolean} isContent
|
|
180
180
|
* @param {?(string|undefined)} uri
|
|
181
|
-
* @param {!Array
|
|
181
|
+
* @param {!Array<string>} images
|
|
182
182
|
* @return {shaka.text.Cue}
|
|
183
183
|
* @private
|
|
184
184
|
*/
|
|
@@ -430,7 +430,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
430
430
|
* Parses an Element into a TextTrackCue or VTTCue.
|
|
431
431
|
*
|
|
432
432
|
* @param {!shaka.extern.xml.Node} regionElement
|
|
433
|
-
* @param {!Array
|
|
433
|
+
* @param {!Array<!shaka.extern.xml.Node>} styles
|
|
434
434
|
* Defined in the top of tt element and used principally for images.
|
|
435
435
|
* @param {?string} globalExtent
|
|
436
436
|
* @return {shaka.text.CueRegion}
|
|
@@ -545,7 +545,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
545
545
|
* @param {shaka.extern.xml.Node} region
|
|
546
546
|
* @param {shaka.extern.xml.Node} imageElement
|
|
547
547
|
* @param {?string} imageUri
|
|
548
|
-
* @param {!Array
|
|
548
|
+
* @param {!Array<!shaka.extern.xml.Node>} styles
|
|
549
549
|
* @param {boolean} isNested
|
|
550
550
|
* @param {boolean} isLeaf
|
|
551
551
|
* @private
|
|
@@ -567,7 +567,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
567
567
|
cue.direction = Cue.direction.HORIZONTAL_RIGHT_TO_LEFT;
|
|
568
568
|
}
|
|
569
569
|
|
|
570
|
-
// Direction attribute specifies one-
|
|
570
|
+
// Direction attribute specifies one-dimensional writing direction
|
|
571
571
|
// (left to right or right to left). Writing mode specifies that
|
|
572
572
|
// plus whether text is vertical or horizontal.
|
|
573
573
|
// They should not contradict each other. If they do, we give
|
|
@@ -598,7 +598,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
598
598
|
cue.textAlign = Cue.textAlign[align.toUpperCase()];
|
|
599
599
|
} else {
|
|
600
600
|
// Default value is START in the TTML spec: https://bit.ly/32OGmvo
|
|
601
|
-
// But to make the subtitle render
|
|
601
|
+
// But to make the subtitle render consistent with other players and the
|
|
602
602
|
// shaka.text.Cue we use CENTER
|
|
603
603
|
cue.textAlign = Cue.textAlign.CENTER;
|
|
604
604
|
}
|
|
@@ -654,6 +654,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
654
654
|
cue.fontFamily = 'sans-serif';
|
|
655
655
|
break;
|
|
656
656
|
case 'monospaceSansSerif':
|
|
657
|
+
// cspell: disable-next-line
|
|
657
658
|
cue.fontFamily = 'Consolas,monospace';
|
|
658
659
|
break;
|
|
659
660
|
case 'proportionalSerif':
|
|
@@ -860,7 +861,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
860
861
|
*
|
|
861
862
|
* @param {!shaka.extern.xml.Node} cueElement
|
|
862
863
|
* @param {shaka.extern.xml.Node} region
|
|
863
|
-
* @param {!Array
|
|
864
|
+
* @param {!Array<!shaka.extern.xml.Node>} styles
|
|
864
865
|
* @param {string} attribute
|
|
865
866
|
* @param {boolean=} shouldInheritRegionStyles
|
|
866
867
|
* @return {?string}
|
|
@@ -889,7 +890,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
889
890
|
* and returns the value if the attribute was found.
|
|
890
891
|
*
|
|
891
892
|
* @param {shaka.extern.xml.Node} region
|
|
892
|
-
* @param {!Array
|
|
893
|
+
* @param {!Array<!shaka.extern.xml.Node>} styles
|
|
893
894
|
* @param {string} attribute
|
|
894
895
|
* @return {?string}
|
|
895
896
|
* @private
|
|
@@ -916,7 +917,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
916
917
|
* if the attribute was found.
|
|
917
918
|
*
|
|
918
919
|
* @param {!shaka.extern.xml.Node} cueElement
|
|
919
|
-
* @param {!Array
|
|
920
|
+
* @param {!Array<!shaka.extern.xml.Node>} styles
|
|
920
921
|
* @param {string} attribute
|
|
921
922
|
* @return {?string}
|
|
922
923
|
* @private
|
|
@@ -944,7 +945,7 @@ shaka.text.TtmlTextParser = class {
|
|
|
944
945
|
* styles inherit from.
|
|
945
946
|
*
|
|
946
947
|
* @param {!shaka.extern.xml.Node} element
|
|
947
|
-
* @param {!Array
|
|
948
|
+
* @param {!Array<!shaka.extern.xml.Node>} styles
|
|
948
949
|
* @param {string} attribute
|
|
949
950
|
* @return {?string}
|
|
950
951
|
* @private
|
|
@@ -999,10 +1000,10 @@ shaka.text.TtmlTextParser = class {
|
|
|
999
1000
|
*
|
|
1000
1001
|
* @param {shaka.extern.xml.Node} element
|
|
1001
1002
|
* @param {string} attributeName
|
|
1002
|
-
* @param {!Array
|
|
1003
|
+
* @param {!Array<shaka.extern.xml.Node>} collection
|
|
1003
1004
|
* @param {string} prefixName
|
|
1004
1005
|
* @param {string=} nsName
|
|
1005
|
-
* @return {!Array
|
|
1006
|
+
* @return {!Array<!shaka.extern.xml.Node>}
|
|
1006
1007
|
* @private
|
|
1007
1008
|
*/
|
|
1008
1009
|
static getElementsFromCollection_(
|
|
@@ -1401,7 +1402,7 @@ shaka.text.TtmlTextParser.timeHMSFormat_ =
|
|
|
1401
1402
|
|
|
1402
1403
|
/**
|
|
1403
1404
|
* @const
|
|
1404
|
-
* @private {!Object
|
|
1405
|
+
* @private {!Object<string, shaka.text.Cue.lineAlign>}
|
|
1405
1406
|
*/
|
|
1406
1407
|
shaka.text.TtmlTextParser.textAlignToLineAlign_ = {
|
|
1407
1408
|
'left': shaka.text.Cue.lineAlign.START,
|
|
@@ -1413,7 +1414,7 @@ shaka.text.TtmlTextParser.textAlignToLineAlign_ = {
|
|
|
1413
1414
|
|
|
1414
1415
|
/**
|
|
1415
1416
|
* @const
|
|
1416
|
-
* @private {!Object
|
|
1417
|
+
* @private {!Object<string, shaka.text.Cue.positionAlign>}
|
|
1417
1418
|
*/
|
|
1418
1419
|
shaka.text.TtmlTextParser.textAlignToPositionAlign_ = {
|
|
1419
1420
|
'left': shaka.text.Cue.positionAlign.LEFT,
|
|
@@ -1426,7 +1427,7 @@ shaka.text.TtmlTextParser.textAlignToPositionAlign_ = {
|
|
|
1426
1427
|
* document, not just "ttp:", so we use this with getAttributeNS() to ensure
|
|
1427
1428
|
* that we support arbitrary namespace names.
|
|
1428
1429
|
*
|
|
1429
|
-
* @const {!Array
|
|
1430
|
+
* @const {!Array<string>}
|
|
1430
1431
|
* @private
|
|
1431
1432
|
*/
|
|
1432
1433
|
shaka.text.TtmlTextParser.parameterNs_ = [
|
|
@@ -1439,7 +1440,7 @@ shaka.text.TtmlTextParser.parameterNs_ = [
|
|
|
1439
1440
|
* document, not just "tts:", so we use this with getAttributeNS() to ensure
|
|
1440
1441
|
* that we support arbitrary namespace names.
|
|
1441
1442
|
*
|
|
1442
|
-
* @const {!Array
|
|
1443
|
+
* @const {!Array<string>}
|
|
1443
1444
|
* @private
|
|
1444
1445
|
*/
|
|
1445
1446
|
shaka.text.TtmlTextParser.styleNs_ = [
|
|
@@ -1459,7 +1460,7 @@ shaka.text.TtmlTextParser.styleEbuttsNs_ = 'urn:ebu:tt:style';
|
|
|
1459
1460
|
|
|
1460
1461
|
/**
|
|
1461
1462
|
* The supported namespace URLs for SMPTE fields.
|
|
1462
|
-
* @const {!Array
|
|
1463
|
+
* @const {!Array<string>}
|
|
1463
1464
|
* @private
|
|
1464
1465
|
*/
|
|
1465
1466
|
shaka.text.TtmlTextParser.smpteNsList_ = [
|
|
@@ -43,7 +43,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
43
43
|
/** @private {boolean} */
|
|
44
44
|
this.isTextVisible_ = false;
|
|
45
45
|
|
|
46
|
-
/** @private {!Array
|
|
46
|
+
/** @private {!Array<!shaka.text.Cue>} */
|
|
47
47
|
this.cues_ = [];
|
|
48
48
|
|
|
49
49
|
/** @private {HTMLMediaElement} */
|
|
@@ -82,7 +82,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
82
82
|
/**
|
|
83
83
|
* Maps cues to cue elements. Specifically points out the wrapper element of
|
|
84
84
|
* the cue (e.g. the HTML element to put nested cues inside).
|
|
85
|
-
* @private {Map
|
|
85
|
+
* @private {Map<!shaka.text.Cue, !{
|
|
86
86
|
* cueElement: !HTMLElement,
|
|
87
87
|
* regionElement: HTMLElement,
|
|
88
88
|
* wrapper: !HTMLElement
|
|
@@ -127,7 +127,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
127
127
|
this.resizeObserver_.observe(this.textContainer_);
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
/** @private {Map
|
|
130
|
+
/** @private {Map<string, !HTMLElement>} */
|
|
131
131
|
this.regionElements_ = new Map();
|
|
132
132
|
}
|
|
133
133
|
|
|
@@ -148,7 +148,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
148
148
|
* @export
|
|
149
149
|
*/
|
|
150
150
|
append(cues) {
|
|
151
|
-
// Clone the cues list for
|
|
151
|
+
// Clone the cues list for performance optimization. We can avoid the cues
|
|
152
152
|
// list growing during the comparisons for duplicate cues.
|
|
153
153
|
// See: https://github.com/shaka-project/shaka-player/issues/3018
|
|
154
154
|
const cuesList = [...this.cues_];
|
|
@@ -279,10 +279,10 @@ shaka.text.UITextDisplayer = class {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
/**
|
|
282
|
-
* @param {!Array
|
|
282
|
+
* @param {!Array<!shaka.text.Cue>} cues
|
|
283
283
|
* @param {!HTMLElement} container
|
|
284
284
|
* @param {number} currentTime
|
|
285
|
-
* @param {!Array
|
|
285
|
+
* @param {!Array<!shaka.text.Cue>} parents
|
|
286
286
|
* @private
|
|
287
287
|
*/
|
|
288
288
|
updateCuesRecursive_(cues, container, currentTime, parents) {
|
|
@@ -294,14 +294,14 @@ shaka.text.UITextDisplayer = class {
|
|
|
294
294
|
* Some of these elements may be added back again, if their corresponding
|
|
295
295
|
* cue is in toPlant.
|
|
296
296
|
* These elements are only removed if updateDOM is true.
|
|
297
|
-
* @type {!Array
|
|
297
|
+
* @type {!Array<!HTMLElement>}
|
|
298
298
|
*/
|
|
299
299
|
const toUproot = [];
|
|
300
300
|
/**
|
|
301
301
|
* The cues whose corresponding elements should be in the DOM.
|
|
302
302
|
* Some of these might be new, some might have been displayed beforehand.
|
|
303
303
|
* These will only be added if updateDOM is true.
|
|
304
|
-
* @type {!Array
|
|
304
|
+
* @type {!Array<!shaka.text.Cue>}
|
|
305
305
|
*/
|
|
306
306
|
const toPlant = [];
|
|
307
307
|
for (const cue of cues) {
|
|
@@ -560,7 +560,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
560
560
|
* Creates the object for a cue.
|
|
561
561
|
*
|
|
562
562
|
* @param {!shaka.text.Cue} cue
|
|
563
|
-
* @param {!Array
|
|
563
|
+
* @param {!Array<!shaka.text.Cue>} parents
|
|
564
564
|
* @private
|
|
565
565
|
*/
|
|
566
566
|
createCue_(cue, parents) {
|
|
@@ -641,7 +641,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
641
641
|
/**
|
|
642
642
|
* @param {!HTMLElement} cueElement
|
|
643
643
|
* @param {!shaka.text.Cue} cue
|
|
644
|
-
* @param {!Array
|
|
644
|
+
* @param {!Array<!shaka.text.Cue>} parents
|
|
645
645
|
* @param {boolean} hasWrapper
|
|
646
646
|
* @private
|
|
647
647
|
*/
|
|
@@ -933,7 +933,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
933
933
|
* @param {HTMLElement} videoContainer
|
|
934
934
|
* @return {string}
|
|
935
935
|
* @private
|
|
936
|
-
|
|
936
|
+
*/
|
|
937
937
|
static convertLengthValue_(lengthValue, cue, videoContainer) {
|
|
938
938
|
const lengthValueInfo =
|
|
939
939
|
shaka.text.UITextDisplayer.getLengthValueInfo_(lengthValue);
|
|
@@ -965,7 +965,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
965
965
|
* @return {string}
|
|
966
966
|
*
|
|
967
967
|
* @private
|
|
968
|
-
|
|
968
|
+
*/
|
|
969
969
|
static getAbsoluteLengthInPixels_(value, cue, videoContainer) {
|
|
970
970
|
const containerHeight = videoContainer.clientHeight;
|
|
971
971
|
|
|
@@ -977,7 +977,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
977
977
|
* it is assumed to be inherited from the parent. This returns null if the
|
|
978
978
|
* value isn't found.
|
|
979
979
|
*
|
|
980
|
-
* @param {!Array
|
|
980
|
+
* @param {!Array<!shaka.text.Cue>} parents
|
|
981
981
|
* @param {function(!shaka.text.Cue):?T} cb
|
|
982
982
|
* @return {?T}
|
|
983
983
|
* @template T
|