@applicaster/zapp-react-native-utils 14.0.0-alpha.4385101226 → 14.0.0-alpha.4520122433
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/actionsExecutor/ActionExecutorContext.tsx +60 -84
- package/actionsExecutor/ScreenActions.ts +164 -0
- package/actionsExecutor/StorageActions.ts +110 -0
- package/actionsExecutor/feedDecorator.ts +171 -0
- package/actionsExecutor/screenResolver.ts +11 -0
- package/analyticsUtils/AnalyticsEvents/helper.ts +1 -1
- package/analyticsUtils/__tests__/analyticsUtils.test.js +0 -11
- package/analyticsUtils/playerAnalyticsTracker.ts +2 -1
- package/appUtils/accessibilityManager/const.ts +13 -0
- package/appUtils/accessibilityManager/hooks.ts +35 -1
- package/appUtils/accessibilityManager/index.ts +151 -30
- package/appUtils/accessibilityManager/utils.ts +24 -0
- package/appUtils/contextKeysManager/contextResolver.ts +42 -1
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +7 -0
- package/appUtils/focusManager/__tests__/focusManager.test.js +1 -1
- package/appUtils/focusManager/events.ts +2 -0
- package/appUtils/focusManager/index.ios.ts +10 -0
- package/appUtils/focusManager/index.ts +86 -11
- package/appUtils/focusManagerAux/utils/index.ts +94 -3
- package/appUtils/platform/platformUtils.ts +31 -1
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +0 -1
- package/configurationUtils/index.ts +1 -1
- package/focusManager/FocusManager.ts +78 -4
- package/focusManager/aux/index.ts +98 -0
- package/focusManager/utils.ts +12 -6
- package/index.d.ts +1 -1
- package/manifestUtils/defaultManifestConfigurations/player.js +188 -2
- package/manifestUtils/index.js +4 -0
- package/manifestUtils/keys.js +12 -0
- package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +6 -0
- package/navigationUtils/index.ts +1 -1
- package/package.json +2 -2
- package/playerUtils/PlayerTTS/PlayerTTS.ts +359 -0
- package/playerUtils/PlayerTTS/index.ts +1 -0
- package/playerUtils/getPlayerActionButtons.ts +1 -1
- package/playerUtils/usePlayerTTS.ts +21 -0
- package/reactHooks/cell-click/__tests__/index.test.js +3 -0
- package/reactHooks/cell-click/index.ts +8 -1
- package/reactHooks/debugging/__tests__/index.test.js +0 -1
- package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +8 -2
- package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +71 -31
- package/reactHooks/feed/index.ts +2 -0
- package/reactHooks/feed/useBatchLoading.ts +14 -9
- package/reactHooks/feed/useFeedLoader.tsx +36 -38
- package/reactHooks/feed/useLoadPipesDataDispatch.ts +63 -0
- package/reactHooks/navigation/useRoute.ts +7 -2
- package/reactHooks/navigation/useScreenStateStore.ts +8 -0
- package/reactHooks/state/index.ts +1 -1
- package/reactHooks/state/useHomeRiver.ts +4 -2
- package/screenPickerUtils/index.ts +13 -0
- package/storage/ScreenSingleValueProvider.ts +204 -0
- package/storage/ScreenStateMultiSelectProvider.ts +293 -0
- package/storage/StorageMultiSelectProvider.ts +192 -0
- package/storage/StorageSingleSelectProvider.ts +108 -0
- package/utils/__tests__/endsWith.test.ts +30 -0
- package/utils/__tests__/find.test.ts +36 -0
- package/utils/__tests__/omit.test.ts +19 -0
- package/utils/__tests__/path.test.ts +33 -0
- package/utils/__tests__/pathOr.test.ts +37 -0
- package/utils/__tests__/startsWith.test.ts +30 -0
- package/utils/__tests__/take.test.ts +40 -0
- package/utils/endsWith.ts +9 -0
- package/utils/find.ts +3 -0
- package/utils/index.ts +17 -1
- package/utils/omit.ts +5 -0
- package/utils/path.ts +5 -0
- package/utils/pathOr.ts +5 -0
- package/utils/startsWith.ts +9 -0
- package/utils/take.ts +5 -0
|
@@ -208,7 +208,7 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
208
208
|
{
|
|
209
209
|
key: "accessibility_forward_label",
|
|
210
210
|
label: "Accessibility forward label",
|
|
211
|
-
initial_value: "
|
|
211
|
+
initial_value: "Fast forward button",
|
|
212
212
|
label_tooltip: "Label for forward button accessibility",
|
|
213
213
|
type: "text_input",
|
|
214
214
|
},
|
|
@@ -292,7 +292,7 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
292
292
|
{
|
|
293
293
|
key: "accessibility_back_label",
|
|
294
294
|
label: "Accessibility back label",
|
|
295
|
-
initial_value: "
|
|
295
|
+
initial_value: "Exit player button",
|
|
296
296
|
label_tooltip: "Label for back button accessibility",
|
|
297
297
|
type: "text_input",
|
|
298
298
|
},
|
|
@@ -317,6 +317,20 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
317
317
|
label_tooltip: "Hint for fullscreen button accessibility",
|
|
318
318
|
type: "text_input",
|
|
319
319
|
},
|
|
320
|
+
{
|
|
321
|
+
key: "accessibility_skip_intro_label",
|
|
322
|
+
label: "Accessibility skip intro label",
|
|
323
|
+
initial_value: "Skip intro - button",
|
|
324
|
+
label_tooltip: "Label for skip intro button accessibility",
|
|
325
|
+
type: "text_input",
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
key: "accessibility_skip_intro_hint",
|
|
329
|
+
label: "Accessibility skip intro hint",
|
|
330
|
+
initial_value: "Press to skip intro",
|
|
331
|
+
label_tooltip: "Hint for skip intro button accessibility",
|
|
332
|
+
type: "text_input",
|
|
333
|
+
},
|
|
320
334
|
],
|
|
321
335
|
};
|
|
322
336
|
|
|
@@ -351,6 +365,86 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
351
365
|
],
|
|
352
366
|
},
|
|
353
367
|
]),
|
|
368
|
+
fieldsGroup(
|
|
369
|
+
"Partial Player (Roku only)",
|
|
370
|
+
"This section allows you to configure width and height of video player in Partial Player",
|
|
371
|
+
[
|
|
372
|
+
{
|
|
373
|
+
key: "video_theater_width",
|
|
374
|
+
label: "Width of player",
|
|
375
|
+
type: "number_input",
|
|
376
|
+
initial_value: 1420,
|
|
377
|
+
placeholder: "1420",
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
key: "video_theater_height",
|
|
381
|
+
label: "Height of player",
|
|
382
|
+
type: "number_input",
|
|
383
|
+
initial_value: 900,
|
|
384
|
+
placeholder: "900",
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
key: "full_screen_button_offset_x",
|
|
388
|
+
label: "Fullscreen button X",
|
|
389
|
+
type: "number_input",
|
|
390
|
+
initial_value: 160,
|
|
391
|
+
placeholder: "160",
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
key: "full_screen_button_offset_y",
|
|
395
|
+
label: "Fullscreen button Y",
|
|
396
|
+
type: "number_input",
|
|
397
|
+
initial_value: 160,
|
|
398
|
+
placeholder: "160",
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
key: "full_screen_button_w",
|
|
402
|
+
label: "Fullscreen button width",
|
|
403
|
+
type: "number_input",
|
|
404
|
+
initial_value: 120,
|
|
405
|
+
placeholder: "120",
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
key: "full_screen_button_h",
|
|
409
|
+
label: "Fullscreen button height",
|
|
410
|
+
type: "number_input",
|
|
411
|
+
initial_value: 120,
|
|
412
|
+
placeholder: "120",
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
key: "full_screen_button_background_color",
|
|
416
|
+
type: "color_picker",
|
|
417
|
+
label: "Fullscreen Button background color",
|
|
418
|
+
initial_value: "#00000000",
|
|
419
|
+
placeholder: "color",
|
|
420
|
+
label_tooltip: "Pick Color",
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
key: "full_screen_button_background_url",
|
|
424
|
+
type: "text_input",
|
|
425
|
+
label: "Fullscreen Button background URL",
|
|
426
|
+
initial_value: "pkg:/images/tv_fullscreen.png",
|
|
427
|
+
placeholder: "",
|
|
428
|
+
label_tooltip: "",
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
key: "full_screen_button_highlighted_background_color",
|
|
432
|
+
type: "color_picker",
|
|
433
|
+
label: "Fullscreen Button highlighted background color",
|
|
434
|
+
initial_value: "#00000000",
|
|
435
|
+
placeholder: "color",
|
|
436
|
+
label_tooltip: "Pick Color",
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
key: "full_screen_button_highlighted_background_url",
|
|
440
|
+
type: "text_input",
|
|
441
|
+
label: "Fullscreen Button highlighted URL",
|
|
442
|
+
initial_value: "pkg:/images/tv_fullscreen.png",
|
|
443
|
+
placeholder: "",
|
|
444
|
+
label_tooltip: "",
|
|
445
|
+
},
|
|
446
|
+
]
|
|
447
|
+
),
|
|
354
448
|
fieldsGroup(
|
|
355
449
|
"Skip Button",
|
|
356
450
|
"This section allows you to configure the skip button styles for tv",
|
|
@@ -482,6 +576,20 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
482
576
|
key: "skip_button_style_text_android_font_size",
|
|
483
577
|
initial_value: 24,
|
|
484
578
|
},
|
|
579
|
+
{
|
|
580
|
+
type: "roku_font_selector",
|
|
581
|
+
label_tooltip: "",
|
|
582
|
+
label: "Roku Font Family",
|
|
583
|
+
key: "skip_button_style_text_roku_font_family",
|
|
584
|
+
initial_value: "Ubuntu-Bold",
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
type: "number_input",
|
|
588
|
+
label_tooltip: "",
|
|
589
|
+
label: "Roku Font Size",
|
|
590
|
+
key: "skip_button_style_text_roku_font_size",
|
|
591
|
+
initial_value: 24,
|
|
592
|
+
},
|
|
485
593
|
{
|
|
486
594
|
type: "select",
|
|
487
595
|
options: [
|
|
@@ -2890,6 +2998,18 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
2890
2998
|
key: "android_tv_audio_player_title_font_size",
|
|
2891
2999
|
initial_value: 40,
|
|
2892
3000
|
},
|
|
3001
|
+
{
|
|
3002
|
+
type: "roku_font_selector",
|
|
3003
|
+
label: "Roku Font Family",
|
|
3004
|
+
key: "roku_audio_player_title_font_family",
|
|
3005
|
+
initial_value: "Ubuntu-Bold",
|
|
3006
|
+
},
|
|
3007
|
+
{
|
|
3008
|
+
type: "number_input",
|
|
3009
|
+
label: "Roku Font Size",
|
|
3010
|
+
key: "roku_audio_player_title_font_size",
|
|
3011
|
+
initial_value: 40,
|
|
3012
|
+
},
|
|
2893
3013
|
]
|
|
2894
3014
|
),
|
|
2895
3015
|
// Audio Player Summary Font Family
|
|
@@ -2963,6 +3083,18 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
2963
3083
|
key: "android_tv_audio_player_summary_font_size",
|
|
2964
3084
|
initial_value: 22,
|
|
2965
3085
|
},
|
|
3086
|
+
{
|
|
3087
|
+
type: "roku_font_selector",
|
|
3088
|
+
label: "Roku Font Family",
|
|
3089
|
+
key: "roku_audio_player_summary_font_family",
|
|
3090
|
+
initial_value: "Ubuntu-Medium",
|
|
3091
|
+
},
|
|
3092
|
+
{
|
|
3093
|
+
type: "number_input",
|
|
3094
|
+
label: "Roku Font Size",
|
|
3095
|
+
key: "roku_audio_player_summary_font_size",
|
|
3096
|
+
initial_value: 22,
|
|
3097
|
+
},
|
|
2966
3098
|
]
|
|
2967
3099
|
),
|
|
2968
3100
|
fieldsGroup(
|
|
@@ -3330,6 +3462,60 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
3330
3462
|
},
|
|
3331
3463
|
]
|
|
3332
3464
|
),
|
|
3465
|
+
fieldsGroup(
|
|
3466
|
+
"Roku only",
|
|
3467
|
+
"This section allows you to configure RAF - Roku Ad Framework settings",
|
|
3468
|
+
[
|
|
3469
|
+
{
|
|
3470
|
+
key: "raf_enabled",
|
|
3471
|
+
type: "switch",
|
|
3472
|
+
initial_value: false,
|
|
3473
|
+
},
|
|
3474
|
+
{
|
|
3475
|
+
key: "raf_url",
|
|
3476
|
+
type: "text_input",
|
|
3477
|
+
label: "Ad URL",
|
|
3478
|
+
label_tooltip:
|
|
3479
|
+
"Publisher's ad URL. The default is the Roku Ad Server with a single pre-roll placeholder, with revenue split ad sharing by default. TO GET PAID A URL MUST BE PASSED IN HERE. Note: If you are putting ads in child targetted content then your ad url will have to use the ROKU_ADS_KIDS_CONTENT macro value, as per the docs here: developer.roku.com/docs/developer-program/advertising/integrating-roku-advertising-framework.md#url-parameter-macros",
|
|
3480
|
+
},
|
|
3481
|
+
{
|
|
3482
|
+
key: "genre",
|
|
3483
|
+
type: "text_input",
|
|
3484
|
+
label: "Roku Genre",
|
|
3485
|
+
initial_value: "Entertainment",
|
|
3486
|
+
label_tooltip:
|
|
3487
|
+
"Choose value from Roku genre tags, from developer.roku.com/en-gb/docs/developer-program/advertising/integrating-roku-advertising-framework.md",
|
|
3488
|
+
},
|
|
3489
|
+
{
|
|
3490
|
+
key: "nielsen_enabled",
|
|
3491
|
+
type: "checkbox",
|
|
3492
|
+
initial_value: false,
|
|
3493
|
+
label_tooltip:
|
|
3494
|
+
"Required only for apps launched in the US market, See developer.roku.com/en-gb/docs/developer-program/advertising/integrating-roku-advertising-framework.md for details on configuration",
|
|
3495
|
+
},
|
|
3496
|
+
{
|
|
3497
|
+
key: "nielsen_app_id",
|
|
3498
|
+
type: "text_input",
|
|
3499
|
+
initial_value: "",
|
|
3500
|
+
label_tooltip:
|
|
3501
|
+
"id of your app for nielsen leave blank if you don't have a specific id (that is almost always the case)",
|
|
3502
|
+
},
|
|
3503
|
+
{
|
|
3504
|
+
key: "nielsen_genre",
|
|
3505
|
+
type: "text_input",
|
|
3506
|
+
initial_value: "General",
|
|
3507
|
+
label_tooltip:
|
|
3508
|
+
"genre from developer.roku.com/en-gb/docs/developer-program/advertising/integrating-roku-advertising-framework.md#nielsen-dar-genre-tags",
|
|
3509
|
+
},
|
|
3510
|
+
{
|
|
3511
|
+
key: "is_kids_content",
|
|
3512
|
+
type: "checkbox",
|
|
3513
|
+
initial_value: false,
|
|
3514
|
+
label_tooltip:
|
|
3515
|
+
"If your content is directed at kids, this must be checked. See developer.roku.com/docs/developer-program/advertising/raf-api.md#setcontentgenregenres-as-string-kidscontent-as-boolean for more info. Also note your ad urls will have to use the ROKU_ADS_KIDS_CONTENT macro value, as per the docs here: developer.roku.com/docs/developer-program/advertising/integrating-roku-advertising-framework.md#url-parameter-macros",
|
|
3516
|
+
},
|
|
3517
|
+
]
|
|
3518
|
+
),
|
|
3333
3519
|
fieldsGroup(
|
|
3334
3520
|
"Audio Tracks",
|
|
3335
3521
|
"This section allows you to configure default audio track behavior for videos with multiple audio tracks",
|
package/manifestUtils/index.js
CHANGED
|
@@ -29,6 +29,9 @@ const {
|
|
|
29
29
|
getUpdatedSecondaryImageKeys,
|
|
30
30
|
} = require("./secondaryImage");
|
|
31
31
|
|
|
32
|
+
const DEFAULT_GRADIENT_IMAGE =
|
|
33
|
+
"data:image/png;base64,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";
|
|
34
|
+
|
|
32
35
|
module.exports = {
|
|
33
36
|
fontKey,
|
|
34
37
|
fontKeyTV,
|
|
@@ -47,5 +50,6 @@ module.exports = {
|
|
|
47
50
|
tvProgressBar,
|
|
48
51
|
secondaryImage,
|
|
49
52
|
getUpdatedSecondaryImageKeys,
|
|
53
|
+
DEFAULT_GRADIENT_IMAGE,
|
|
50
54
|
compact,
|
|
51
55
|
};
|
package/manifestUtils/keys.js
CHANGED
|
@@ -485,6 +485,18 @@ const TV_MENU_LABEL_FIELDS = [
|
|
|
485
485
|
type: ZAPPIFEST_FIELDS.number_input,
|
|
486
486
|
suffix: "LG letter spacing",
|
|
487
487
|
},
|
|
488
|
+
{
|
|
489
|
+
type: ZAPPIFEST_FIELDS.font_selector.roku,
|
|
490
|
+
suffix: "Roku font family",
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
type: ZAPPIFEST_FIELDS.number_input,
|
|
494
|
+
suffix: "Roku font size",
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
type: ZAPPIFEST_FIELDS.number_input,
|
|
498
|
+
suffix: "Roku line height",
|
|
499
|
+
},
|
|
488
500
|
{
|
|
489
501
|
type: ZAPPIFEST_FIELDS.select,
|
|
490
502
|
suffix: "text transform",
|
|
@@ -424,6 +424,12 @@ const titleFields = [
|
|
|
424
424
|
key: "vizio_font_family",
|
|
425
425
|
initial_value: fontFamily,
|
|
426
426
|
},
|
|
427
|
+
{
|
|
428
|
+
type: "roku_font_selector",
|
|
429
|
+
label: "Roku TV Font Family",
|
|
430
|
+
key: "roku_font_family",
|
|
431
|
+
initial_value: fontFamily,
|
|
432
|
+
},
|
|
427
433
|
...generateFontConfiguration(),
|
|
428
434
|
// text transform
|
|
429
435
|
{
|
package/navigationUtils/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "14.0.0-alpha.
|
|
3
|
+
"version": "14.0.0-alpha.4520122433",
|
|
4
4
|
"description": "Applicaster Zapp React Native utilities package",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@applicaster/applicaster-types": "14.0.0-alpha.
|
|
30
|
+
"@applicaster/applicaster-types": "14.0.0-alpha.4520122433",
|
|
31
31
|
"buffer": "^5.2.1",
|
|
32
32
|
"camelize": "^1.0.0",
|
|
33
33
|
"dayjs": "^1.11.10",
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import uuidv4 from "uuid/v4";
|
|
2
|
+
import { AccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager";
|
|
3
|
+
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
4
|
+
import { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
|
|
5
|
+
|
|
6
|
+
const { log_debug, log_error } = createLogger({
|
|
7
|
+
subsystem: "Player",
|
|
8
|
+
category: "PlayerTTS",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
enum SEEK_DIRECTION {
|
|
12
|
+
FORWARD = "forward",
|
|
13
|
+
REWIND = "back",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const hasPrerollAds = (entry: ZappEntry): boolean => {
|
|
17
|
+
const videoAds = entry?.extensions?.video_ads;
|
|
18
|
+
|
|
19
|
+
if (!videoAds) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// If it's a string (VMAP URL), assume it might have preroll
|
|
24
|
+
if (typeof videoAds === "string") {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// If it's an array, check for preroll offset
|
|
29
|
+
if (Array.isArray(videoAds)) {
|
|
30
|
+
return videoAds.some(
|
|
31
|
+
(ad: ZappVideoAdExtension) => ad.offset === "preroll" || ad.offset === 0
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return false;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export class PlayerTTS {
|
|
39
|
+
private player: Player;
|
|
40
|
+
private accessibilityManager: AccessibilityManager;
|
|
41
|
+
private seekStartPosition: number | null = null;
|
|
42
|
+
private isSeeking: boolean = false;
|
|
43
|
+
private listenerId: string;
|
|
44
|
+
private isInitialPlayerOpen: boolean = true;
|
|
45
|
+
private isPrerollActive: boolean = false;
|
|
46
|
+
private hasPrerollAds: boolean = false; // Track if preroll ads are expected
|
|
47
|
+
|
|
48
|
+
constructor(player: Player, accessibilityManager: AccessibilityManager) {
|
|
49
|
+
this.player = player;
|
|
50
|
+
this.accessibilityManager = accessibilityManager;
|
|
51
|
+
this.listenerId = `player-tts-${uuidv4()}`;
|
|
52
|
+
this.hasPrerollAds = hasPrerollAds(player.entry);
|
|
53
|
+
|
|
54
|
+
log_debug("PlayerTTS initialized", {
|
|
55
|
+
hasPrerollAds: this.hasPrerollAds,
|
|
56
|
+
listenerId: this.listenerId,
|
|
57
|
+
entryTitle: player.entry.title,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private numberToWords(num: number): string {
|
|
62
|
+
const ones = [
|
|
63
|
+
"",
|
|
64
|
+
"one",
|
|
65
|
+
"two",
|
|
66
|
+
"three",
|
|
67
|
+
"four",
|
|
68
|
+
"five",
|
|
69
|
+
"six",
|
|
70
|
+
"seven",
|
|
71
|
+
"eight",
|
|
72
|
+
"nine",
|
|
73
|
+
"ten",
|
|
74
|
+
"eleven",
|
|
75
|
+
"twelve",
|
|
76
|
+
"thirteen",
|
|
77
|
+
"fourteen",
|
|
78
|
+
"fifteen",
|
|
79
|
+
"sixteen",
|
|
80
|
+
"seventeen",
|
|
81
|
+
"eighteen",
|
|
82
|
+
"nineteen",
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const tens = [
|
|
86
|
+
"",
|
|
87
|
+
"",
|
|
88
|
+
"twenty",
|
|
89
|
+
"thirty",
|
|
90
|
+
"forty",
|
|
91
|
+
"fifty",
|
|
92
|
+
"sixty",
|
|
93
|
+
"seventy",
|
|
94
|
+
"eighty",
|
|
95
|
+
"ninety",
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
if (num === 0) return "zero";
|
|
99
|
+
if (num < 20) return ones[num];
|
|
100
|
+
|
|
101
|
+
const ten = Math.floor(num / 10);
|
|
102
|
+
const one = num % 10;
|
|
103
|
+
|
|
104
|
+
return one === 0 ? tens[ten] : `${tens[ten]} ${ones[one]}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private secondsToTime(
|
|
108
|
+
seconds: number,
|
|
109
|
+
format: "natural" | "standard" = "natural"
|
|
110
|
+
): string {
|
|
111
|
+
if (seconds < 0) return format === "natural" ? "zero" : "0";
|
|
112
|
+
|
|
113
|
+
const minutes = Math.floor(seconds / 60);
|
|
114
|
+
const remainingSeconds = Math.floor(seconds % 60);
|
|
115
|
+
|
|
116
|
+
if (format === "standard") {
|
|
117
|
+
const parts = [];
|
|
118
|
+
|
|
119
|
+
if (minutes > 0) {
|
|
120
|
+
parts.push(`${minutes} minute${minutes !== 1 ? "s" : ""}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (remainingSeconds > 0) {
|
|
124
|
+
parts.push(
|
|
125
|
+
`${remainingSeconds} second${remainingSeconds !== 1 ? "s" : ""}`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return parts.length > 0 ? parts.join(" ") : "0";
|
|
130
|
+
} else {
|
|
131
|
+
if (minutes === 0) {
|
|
132
|
+
if (remainingSeconds === 0) return "zero";
|
|
133
|
+
|
|
134
|
+
if (remainingSeconds < 10) {
|
|
135
|
+
return `zero o ${this.numberToWords(remainingSeconds)}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return `zero ${this.numberToWords(remainingSeconds)}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (remainingSeconds === 0) {
|
|
142
|
+
return `${this.numberToWords(minutes)}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (remainingSeconds < 10) {
|
|
146
|
+
return `${this.numberToWords(minutes)} o ${this.numberToWords(remainingSeconds)}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return `${this.numberToWords(minutes)} ${this.numberToWords(remainingSeconds)}`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private announcePause = () => {
|
|
154
|
+
if (!this.isSeeking) {
|
|
155
|
+
this.accessibilityManager.addHeading(
|
|
156
|
+
`Paused - ${this.secondsToTime(this.player.playerState.contentPosition, "standard")}`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
private announceContentStart(
|
|
162
|
+
options: {
|
|
163
|
+
currentTime?: number;
|
|
164
|
+
duration?: number;
|
|
165
|
+
useReadText?: boolean;
|
|
166
|
+
} = {}
|
|
167
|
+
): void {
|
|
168
|
+
const { currentTime, duration, useReadText = false } = options;
|
|
169
|
+
const state = this.player.playerState;
|
|
170
|
+
|
|
171
|
+
const timeRemaining =
|
|
172
|
+
(duration || state?.contentDuration || 0) -
|
|
173
|
+
(currentTime || state?.contentPosition || 0);
|
|
174
|
+
|
|
175
|
+
const title = (this.player.entry.title as string) || "";
|
|
176
|
+
const summary = (this.player.entry.summary as string) || "";
|
|
177
|
+
|
|
178
|
+
log_debug("Announcing content start", {
|
|
179
|
+
title,
|
|
180
|
+
currentTime: currentTime || state?.contentPosition || 0,
|
|
181
|
+
duration: duration || state?.contentDuration || 0,
|
|
182
|
+
timeRemaining,
|
|
183
|
+
useReadText,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
this.accessibilityManager.addHeading(`Playing - ${title}`);
|
|
187
|
+
if (summary) this.accessibilityManager.addHeading(summary);
|
|
188
|
+
|
|
189
|
+
this.accessibilityManager.addHeading(
|
|
190
|
+
`Playing from ${this.secondsToTime(currentTime || state?.contentPosition || 0, "standard")}`
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const remainingText = `${this.secondsToTime(Math.max(0, Math.floor(timeRemaining)), "standard")} remaining.`;
|
|
194
|
+
|
|
195
|
+
if (useReadText) {
|
|
196
|
+
this.accessibilityManager.readText({ text: remainingText });
|
|
197
|
+
} else {
|
|
198
|
+
this.accessibilityManager.addHeading(remainingText);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
this.accessibilityManager.setInitialPlayerAnnouncementReady();
|
|
202
|
+
this.isInitialPlayerOpen = false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private announceBufferComplete = (event: any) => {
|
|
206
|
+
// If preroll ads are expected, wait for them to finish before announcing content
|
|
207
|
+
if (this.hasPrerollAds && this.isInitialPlayerOpen) {
|
|
208
|
+
log_debug("Waiting for preroll ads to finish", {
|
|
209
|
+
hasPrerollAds: this.hasPrerollAds,
|
|
210
|
+
isInitialPlayerOpen: this.isInitialPlayerOpen,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Gate content announcement until preroll finishes
|
|
217
|
+
if (this.isInitialPlayerOpen && !this.isPrerollActive) {
|
|
218
|
+
log_debug("Buffer complete - announcing content", {
|
|
219
|
+
currentTime: event.currentTime,
|
|
220
|
+
duration: event.duration,
|
|
221
|
+
isPrerollActive: this.isPrerollActive,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
this.announceContentStart({
|
|
225
|
+
currentTime: event.currentTime,
|
|
226
|
+
duration: event.duration,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
private announceResume = () => {
|
|
232
|
+
if (!this.isSeeking && !this.isInitialPlayerOpen) {
|
|
233
|
+
log_debug("Player resumed", {
|
|
234
|
+
contentPosition: this.player.playerState.contentPosition,
|
|
235
|
+
isSeeking: this.isSeeking,
|
|
236
|
+
isInitialPlayerOpen: this.isInitialPlayerOpen,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
this.accessibilityManager.addHeading(
|
|
240
|
+
`Playing - ${this.secondsToTime(this.player.playerState.contentPosition, "standard")}`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
private handleVideoProgress = (event: any) => {
|
|
246
|
+
if (event.currentTime > 0) {
|
|
247
|
+
this.seekStartPosition = event.currentTime;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
private handleSeekComplete = (event: any) => {
|
|
252
|
+
if (this.seekStartPosition !== null) {
|
|
253
|
+
const seekDirection =
|
|
254
|
+
event.currentTime > this.seekStartPosition
|
|
255
|
+
? SEEK_DIRECTION.FORWARD
|
|
256
|
+
: SEEK_DIRECTION.REWIND;
|
|
257
|
+
|
|
258
|
+
const seekAmount = Math.round(
|
|
259
|
+
Math.abs(event.currentTime - this.seekStartPosition)
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
log_debug("Seek completed", {
|
|
263
|
+
seekDirection,
|
|
264
|
+
seekAmount,
|
|
265
|
+
fromPosition: this.seekStartPosition,
|
|
266
|
+
toPosition: event.currentTime,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
this.accessibilityManager.readText({
|
|
270
|
+
text: `Skipped ${seekDirection} ${this.secondsToTime(seekAmount, "standard")}`,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
this.seekStartPosition = event.currentTime;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
this.isSeeking = false;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
private handleSeekStart = () => {
|
|
280
|
+
log_debug("Seek started");
|
|
281
|
+
this.isSeeking = true;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
private handlePlayerClose = () => {
|
|
285
|
+
log_debug("Player closed - resetting state");
|
|
286
|
+
this.isInitialPlayerOpen = true;
|
|
287
|
+
this.accessibilityManager.resetInitialPlayerAnnouncementReady();
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
private announceAdBegin = (event: any) => {
|
|
291
|
+
this.isPrerollActive = true;
|
|
292
|
+
|
|
293
|
+
log_debug("Ad started", {
|
|
294
|
+
adDuration: event?.ad?.data?.duration,
|
|
295
|
+
isPrerollActive: this.isPrerollActive,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
if (event?.ad?.data?.duration) {
|
|
299
|
+
this.accessibilityManager.readText({
|
|
300
|
+
text: `Sponsored. Ends in ${this.secondsToTime(event.ad.data.duration, "standard")}`,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
private handleAdEnd = (_event: any) => {
|
|
306
|
+
this.isPrerollActive = false;
|
|
307
|
+
|
|
308
|
+
log_debug("Ad ended", {
|
|
309
|
+
isPrerollActive: this.isPrerollActive,
|
|
310
|
+
isInitialPlayerOpen: this.isInitialPlayerOpen,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// If initial entry still pending, trigger content announcement using latest player state
|
|
314
|
+
if (this.isInitialPlayerOpen) {
|
|
315
|
+
this.announceContentStart({ useReadText: true });
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
public init(): () => void {
|
|
320
|
+
if (!this.player) {
|
|
321
|
+
log_error("Failed to initialize PlayerTTS - no player provided");
|
|
322
|
+
|
|
323
|
+
return () => {};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
log_debug("Initializing PlayerTTS listeners", {
|
|
327
|
+
listenerId: this.listenerId,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
return this.player.addListener({
|
|
331
|
+
id: this.listenerId,
|
|
332
|
+
listener: {
|
|
333
|
+
onBufferComplete: this.announceBufferComplete,
|
|
334
|
+
onPlayerResume: this.announceResume,
|
|
335
|
+
onPlayerPause: this.announcePause,
|
|
336
|
+
onVideoProgress: this.handleVideoProgress,
|
|
337
|
+
onPlayerSeekStart: this.handleSeekStart,
|
|
338
|
+
onPlayerSeekComplete: this.handleSeekComplete,
|
|
339
|
+
onPlayerClose: this.handlePlayerClose,
|
|
340
|
+
onAdBegin: this.announceAdBegin,
|
|
341
|
+
onAdEnd: this.handleAdEnd,
|
|
342
|
+
onAdBreakEnd: this.handleAdEnd,
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
public destroy(): void {
|
|
348
|
+
log_debug("Destroying PlayerTTS", {
|
|
349
|
+
listenerId: this.listenerId,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (this.player) {
|
|
353
|
+
this.player.removeListener(this.listenerId);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
this.seekStartPosition = null;
|
|
357
|
+
this.handlePlayerClose();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PlayerTTS } from "./PlayerTTS";
|