@applicaster/zapp-react-native-utils 14.0.0-rc.9 → 14.0.0-rc.91
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/AnalyticPlayerListener.ts +5 -2
- package/analyticsUtils/AnalyticsEvents/helper.ts +81 -0
- package/analyticsUtils/AnalyticsEvents/sendHeaderClickEvent.ts +1 -1
- package/analyticsUtils/AnalyticsEvents/sendMenuClickEvent.ts +2 -1
- package/analyticsUtils/AnalyticsEvents/sendOnClickEvent.ts +14 -4
- package/analyticsUtils/__tests__/analyticsUtils.test.js +3 -0
- package/analyticsUtils/events.ts +8 -0
- package/analyticsUtils/index.tsx +3 -4
- package/analyticsUtils/manager.ts +1 -1
- package/analyticsUtils/playerAnalyticsTracker.ts +2 -1
- package/appUtils/HooksManager/Hook.ts +4 -4
- package/appUtils/HooksManager/index.ts +11 -1
- package/appUtils/accessibilityManager/const.ts +13 -0
- package/appUtils/accessibilityManager/hooks.ts +35 -1
- package/appUtils/accessibilityManager/index.ts +154 -30
- package/appUtils/accessibilityManager/utils.ts +24 -0
- package/appUtils/contextKeysManager/contextResolver.ts +42 -1
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +8 -0
- package/appUtils/focusManager/__tests__/focusManager.test.js +1 -1
- package/appUtils/focusManager/events.ts +2 -0
- package/appUtils/focusManager/index.ios.ts +27 -0
- package/appUtils/focusManager/index.ts +86 -11
- package/appUtils/focusManager/treeDataStructure/Tree/index.js +1 -1
- package/appUtils/focusManagerAux/utils/index.ts +112 -3
- package/appUtils/focusManagerAux/utils/utils.ios.ts +35 -0
- package/appUtils/platform/platformUtils.ts +33 -3
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +91 -16
- package/appUtils/playerManager/OverlayObserver/utils.ts +32 -20
- package/appUtils/playerManager/conts.ts +21 -0
- package/appUtils/playerManager/useChapterMarker.tsx +0 -1
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +16 -0
- package/arrayUtils/__tests__/allTruthy.test.ts +24 -0
- package/arrayUtils/__tests__/anyThruthy.test.ts +24 -0
- package/arrayUtils/__tests__/isEmptyArray.test.ts +63 -0
- package/arrayUtils/__tests__/isFilledArray.test.ts +1 -1
- package/arrayUtils/index.ts +13 -3
- package/audioPlayerUtils/__tests__/getArtworkImage.test.ts +144 -0
- package/audioPlayerUtils/__tests__/getBackgroundImage.test.ts +72 -0
- package/audioPlayerUtils/__tests__/getImageFromEntry.test.ts +110 -0
- package/audioPlayerUtils/assets/index.ts +2 -0
- package/audioPlayerUtils/index.ts +242 -0
- package/componentsUtils/__tests__/isTabsScreen.test.ts +38 -0
- package/componentsUtils/index.ts +4 -1
- package/conf/player/__tests__/selectors.test.ts +34 -0
- package/conf/player/selectors.ts +10 -0
- package/configurationUtils/__tests__/configurationUtils.test.js +0 -31
- package/configurationUtils/__tests__/getMediaItems.test.ts +65 -0
- package/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +34 -0
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +546 -0
- package/configurationUtils/index.ts +64 -35
- package/configurationUtils/manifestKeyParser.ts +57 -32
- package/focusManager/FocusManager.ts +104 -20
- package/focusManager/Tree.ts +25 -21
- package/focusManager/__tests__/FocusManager.test.ts +50 -8
- package/focusManager/aux/index.ts +98 -0
- package/focusManager/utils.ts +12 -6
- package/index.d.ts +1 -10
- package/manifestUtils/_internals/getDefaultConfiguration.js +28 -0
- package/manifestUtils/{_internals.js → _internals/index.js} +2 -25
- package/manifestUtils/createConfig.js +4 -1
- package/manifestUtils/defaultManifestConfigurations/player.js +2764 -1539
- package/manifestUtils/index.js +4 -0
- package/manifestUtils/keys.js +33 -0
- package/manifestUtils/progressBar/__tests__/mobileProgressBar.test.js +0 -30
- package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +6 -0
- package/manifestUtils/sharedConfiguration/screenPicker/utils.js +1 -0
- package/navigationUtils/__tests__/mapContentTypesToRivers.test.ts +130 -0
- package/navigationUtils/index.ts +26 -21
- package/package.json +2 -3
- package/playerUtils/PlayerTTS/PlayerTTS.ts +359 -0
- package/playerUtils/PlayerTTS/index.ts +1 -0
- package/playerUtils/__tests__/configurationUtils.test.ts +1 -65
- package/playerUtils/__tests__/getPlayerActionButtons.test.ts +54 -0
- package/playerUtils/_internals/__tests__/utils.test.ts +71 -0
- package/playerUtils/_internals/index.ts +1 -0
- package/playerUtils/_internals/utils.ts +31 -0
- package/playerUtils/configurationUtils.ts +0 -44
- package/playerUtils/getPlayerActionButtons.ts +17 -0
- package/playerUtils/index.ts +53 -0
- package/playerUtils/usePlayerTTS.ts +21 -0
- package/playerUtils/useValidatePlayerConfig.tsx +22 -19
- package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +15 -14
- 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 +47 -90
- package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +71 -31
- package/reactHooks/feed/index.ts +2 -0
- package/reactHooks/feed/useBatchLoading.ts +17 -10
- package/reactHooks/feed/useFeedLoader.tsx +36 -43
- package/reactHooks/feed/useInflatedUrl.ts +23 -29
- package/reactHooks/feed/useLoadPipesDataDispatch.ts +63 -0
- package/reactHooks/feed/usePipesCacheReset.ts +3 -3
- package/reactHooks/flatList/useSequentialRenderItem.tsx +3 -3
- package/reactHooks/layout/__tests__/index.test.tsx +3 -1
- package/reactHooks/layout/index.ts +1 -1
- package/reactHooks/layout/isTablet/index.ts +12 -5
- package/reactHooks/layout/useDimensions/__tests__/useDimensions.test.ts +34 -36
- package/reactHooks/layout/useDimensions/useDimensions.ts +2 -3
- package/reactHooks/layout/useLayoutVersion.ts +5 -5
- package/reactHooks/navigation/index.ts +7 -5
- package/reactHooks/navigation/useIsScreenActive.ts +9 -5
- package/reactHooks/navigation/useRoute.ts +7 -2
- package/reactHooks/navigation/useScreenStateStore.ts +8 -0
- package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +4 -0
- package/reactHooks/screen/useScreenContext.ts +1 -1
- package/reactHooks/state/__tests__/ZStoreProvider.test.tsx +2 -1
- package/reactHooks/state/index.ts +1 -1
- package/reactHooks/state/useHomeRiver.ts +4 -2
- package/reactHooks/state/useRivers.ts +7 -8
- package/riverComponetsMeasurementProvider/index.tsx +1 -1
- package/screenPickerUtils/index.ts +13 -0
- package/services/js2native.ts +1 -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/testUtils/index.tsx +7 -8
- package/time/BackgroundTimer.ts +6 -4
- package/utils/__tests__/endsWith.test.ts +30 -0
- package/utils/__tests__/find.test.ts +36 -0
- package/utils/__tests__/mapAccum.test.ts +73 -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 +38 -1
- package/utils/mapAccum.ts +23 -0
- 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
- package/playerUtils/configurationGenerator.ts +0 -2572
|
@@ -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";
|
|
@@ -1,72 +1,8 @@
|
|
|
1
1
|
import * as utils from "../configurationUtils";
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
const configuration = {
|
|
6
|
-
general: { fields: [{ key: "general_1", initial_value: true }] },
|
|
7
|
-
styles: { fields: [{ key: "styles_1", initial_value: true }] },
|
|
8
|
-
localizations: {
|
|
9
|
-
fields: [{ key: "localizations_1", initial_value: true, label: "label" }],
|
|
10
|
-
},
|
|
11
|
-
custom_configuration_fields: [
|
|
12
|
-
{ key: "custom_configuration_fields_1", initial_value: true },
|
|
13
|
-
{ key: "custom_configuration_fields_2", initial_value: "black" },
|
|
14
|
-
{
|
|
15
|
-
group: true,
|
|
16
|
-
fields: [{ key: "custom_configuration_fields_3", initial_value: "red" }],
|
|
17
|
-
},
|
|
18
|
-
],
|
|
19
|
-
};
|
|
3
|
+
const { parseLanguageTracks } = utils;
|
|
20
4
|
|
|
21
5
|
describe("utilities", () => {
|
|
22
|
-
describe("modifyDefaultConfigValues", () => {
|
|
23
|
-
it("should not modify the configuration if a map key doesn't exist in configuration ", function () {
|
|
24
|
-
const res = modifyDefaultConfigValues(configuration, {
|
|
25
|
-
custom_configuration_fields: {
|
|
26
|
-
non_existing_key: { initial_value: "true" },
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
expect(res).toEqual(configuration);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("should modify the key from mapping", function () {
|
|
34
|
-
const currentResult = modifyDefaultConfigValues(configuration, {
|
|
35
|
-
styles: { styles_1: { initial_value: false } },
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
expect(currentResult.styles.fields[0].initial_value).toBe(false);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("should add extra keys the key from mapping", function () {
|
|
42
|
-
const currentResult = modifyDefaultConfigValues(configuration, {
|
|
43
|
-
styles: { styles_1: { extra_key: false } },
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
expect(currentResult.styles.fields[0].extra_key).toBeDefined();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("should not remove existing keys that aren't being overwritten", function () {
|
|
50
|
-
const currentResult = modifyDefaultConfigValues(configuration, {
|
|
51
|
-
localizations: { localizations_1: { initial_value: false } },
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
expect(currentResult.localizations.fields[0].label).toBeDefined();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("should modify values in groups as well", function () {
|
|
58
|
-
const currentResult = modifyDefaultConfigValues(configuration, {
|
|
59
|
-
custom_configuration_fields: {
|
|
60
|
-
custom_configuration_fields_3: { initial_value: "blue" },
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
expect(
|
|
65
|
-
currentResult.custom_configuration_fields[2].fields[0].initial_value
|
|
66
|
-
).toBe("blue");
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
6
|
describe("parseLanguageTracks", () => {
|
|
71
7
|
const textTrack = { index: 0, id: "text-0" };
|
|
72
8
|
const audioTrack = { index: 0, id: "audio-0" };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getPlayerActionButtons } from "../getPlayerActionButtons";
|
|
2
|
+
import { selectActionButtons } from "../../conf/player/selectors";
|
|
3
|
+
|
|
4
|
+
jest.mock("../../conf/player/selectors", () => ({
|
|
5
|
+
selectActionButtons: jest.fn(),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
describe("getPlayerActionButtons", () => {
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
jest.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns an empty array if selectActionButtons returns undefined", () => {
|
|
14
|
+
(selectActionButtons as jest.Mock).mockReturnValue(undefined);
|
|
15
|
+
const result = getPlayerActionButtons({});
|
|
16
|
+
expect(result).toEqual([]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("returns an empty array if selectActionButtons returns null", () => {
|
|
20
|
+
(selectActionButtons as jest.Mock).mockReturnValue(null);
|
|
21
|
+
const result = getPlayerActionButtons({});
|
|
22
|
+
expect(result).toEqual([]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("returns an empty array if selectActionButtons returns empty string", () => {
|
|
26
|
+
(selectActionButtons as jest.Mock).mockReturnValue("");
|
|
27
|
+
const result = getPlayerActionButtons({});
|
|
28
|
+
expect(result).toEqual([]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("returns the first two trimmed action buttons", () => {
|
|
32
|
+
(selectActionButtons as jest.Mock).mockReturnValue(" play , pause , stop ");
|
|
33
|
+
const result = getPlayerActionButtons({});
|
|
34
|
+
expect(result).toEqual(["play", "pause"]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("returns only one button if only one is present", () => {
|
|
38
|
+
(selectActionButtons as jest.Mock).mockReturnValue(" play ");
|
|
39
|
+
const result = getPlayerActionButtons({});
|
|
40
|
+
expect(result).toEqual(["play"]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("trims whitespace from button names", () => {
|
|
44
|
+
(selectActionButtons as jest.Mock).mockReturnValue(" play , pause ");
|
|
45
|
+
const result = getPlayerActionButtons({});
|
|
46
|
+
expect(result).toEqual(["play", "pause"]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("returns an empty array if selectActionButtons returns only commas", () => {
|
|
50
|
+
(selectActionButtons as jest.Mock).mockReturnValue(" , , ");
|
|
51
|
+
const result = getPlayerActionButtons({});
|
|
52
|
+
expect(result).toEqual(["", ""]);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { getAllFields, getConfigurationDiff } from "../utils";
|
|
2
|
+
|
|
3
|
+
describe("getAllFields", () => {
|
|
4
|
+
it("should return all field keys from flat configs", () => {
|
|
5
|
+
const config1 = {
|
|
6
|
+
fields: [{ key: "foo" }, { key: "bar" }],
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const config2 = {
|
|
10
|
+
fields: [{ key: "baz" }],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
expect(getAllFields(config1, config2)).toEqual(["foo", "bar", "baz"]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should handle grouped fields", () => {
|
|
17
|
+
const config = {
|
|
18
|
+
fields: [
|
|
19
|
+
{
|
|
20
|
+
group: true,
|
|
21
|
+
fields: [{ key: "grouped1" }, { key: "grouped2" }],
|
|
22
|
+
},
|
|
23
|
+
{ key: "single" },
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
expect(getAllFields(config)).toEqual(["grouped1", "grouped2", "single"]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should filter out fields without a key", () => {
|
|
31
|
+
const config = {
|
|
32
|
+
fields: [{ key: "foo" }, { notAKey: "bar" }],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
expect(getAllFields(config)).toEqual(["foo"]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should return an empty array if no fields are present", () => {
|
|
39
|
+
expect(getAllFields({})).toEqual([]);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("getConfigurationDiff", () => {
|
|
44
|
+
it("should return keys in defaultConfig not present in config", () => {
|
|
45
|
+
const defaultConfig = ["foo", "bar", "baz"];
|
|
46
|
+
const config = { foo: 1, baz: 2 };
|
|
47
|
+
|
|
48
|
+
expect(getConfigurationDiff(defaultConfig, config)).toEqual(["bar"]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should return all keys if config is empty", () => {
|
|
52
|
+
const defaultConfig = ["foo", "bar"];
|
|
53
|
+
const config = {};
|
|
54
|
+
|
|
55
|
+
expect(getConfigurationDiff(defaultConfig, config)).toEqual(["foo", "bar"]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should return an empty array if all keys are present", () => {
|
|
59
|
+
const defaultConfig = ["foo"];
|
|
60
|
+
const config = { foo: 1 };
|
|
61
|
+
|
|
62
|
+
expect(getConfigurationDiff(defaultConfig, config)).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should return defaultConfig if config has no matching keys", () => {
|
|
66
|
+
const defaultConfig = ["foo", "bar"];
|
|
67
|
+
const config = { baz: 1 };
|
|
68
|
+
|
|
69
|
+
expect(getConfigurationDiff(defaultConfig, config)).toEqual(["foo", "bar"]);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./utils";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
flatMap,
|
|
3
|
+
get,
|
|
4
|
+
flatten,
|
|
5
|
+
difference,
|
|
6
|
+
} from "@applicaster/zapp-react-native-utils/utils";
|
|
7
|
+
|
|
8
|
+
const extractFields = (field: any) => {
|
|
9
|
+
if (field.group === true) {
|
|
10
|
+
return field.fields;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return field;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const getAllFields = (...configs: any[]) => {
|
|
17
|
+
const allFields = flatMap(configs, (config) => get(config, "fields", []));
|
|
18
|
+
|
|
19
|
+
const processedFields = flatten(allFields.map(extractFields))
|
|
20
|
+
.map((field) => get(field, "key"))
|
|
21
|
+
.filter(Boolean);
|
|
22
|
+
|
|
23
|
+
return processedFields;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const getConfigurationDiff = (
|
|
27
|
+
defaultConfig,
|
|
28
|
+
config: Record<string, any>
|
|
29
|
+
) => {
|
|
30
|
+
return difference(defaultConfig, Object.keys(config));
|
|
31
|
+
};
|
|
@@ -1,50 +1,6 @@
|
|
|
1
1
|
import { parseJsonIfNeeded } from "../functionUtils";
|
|
2
2
|
import * as R from "ramda";
|
|
3
3
|
|
|
4
|
-
import { getNativeName as nativeNameUtil } from "../localizationUtils/localeLanguage";
|
|
5
|
-
|
|
6
|
-
export const modifyDefaultConfigValues = (
|
|
7
|
-
configuration: ConfigurationKeys,
|
|
8
|
-
mapping: ConfigValuesMapping
|
|
9
|
-
): DefaultConfiguration => {
|
|
10
|
-
return R.mapObjIndexed((value, key) => {
|
|
11
|
-
const isFieldlessKey = key === "custom_configuration_fields";
|
|
12
|
-
const keyMapping = mapping[key];
|
|
13
|
-
const fields = value?.fields || value;
|
|
14
|
-
|
|
15
|
-
if (!keyMapping) {
|
|
16
|
-
return value;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const mapper = (obj) => {
|
|
20
|
-
if (obj.fields) {
|
|
21
|
-
return R.mergeLeft({ fields: R.map(mapper)(obj.fields) })(obj);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return R.mergeLeft(keyMapping?.[obj.key])(obj);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const mappedFields = R.map(mapper)(fields);
|
|
28
|
-
|
|
29
|
-
return R.unless(() => isFieldlessKey, R.objOf("fields"))(mappedFields);
|
|
30
|
-
})(configuration);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export function nativeName(localeCode) {
|
|
34
|
-
try {
|
|
35
|
-
const {
|
|
36
|
-
getNativeName,
|
|
37
|
-
} = require("@applicaster/zapp-react-native-utils/localizationUtils/localeLanguage");
|
|
38
|
-
|
|
39
|
-
return getNativeName(localeCode);
|
|
40
|
-
} catch (error) {
|
|
41
|
-
// eslint-disable-next-line no-console
|
|
42
|
-
console.warn("Could not load localeLanguage utils from QB", error);
|
|
43
|
-
|
|
44
|
-
return nativeNameUtil(localeCode);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
4
|
const setTrackType = R.curry(
|
|
49
5
|
(
|
|
50
6
|
type: QuickBrickPlayer.TrackType,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { map, take, trim } from "../utils";
|
|
2
|
+
import { selectActionButtons } from "../conf/player/selectors";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns the first two action buttons from the configuration.
|
|
6
|
+
* @param {Object} configuration - The player configuration object.
|
|
7
|
+
* @returns {Array} An array containing the first two action buttons.
|
|
8
|
+
*/
|
|
9
|
+
export const getPlayerActionButtons = (configuration: any) => {
|
|
10
|
+
const buttonsString = selectActionButtons(configuration);
|
|
11
|
+
|
|
12
|
+
if (!buttonsString) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return take(2, map(buttonsString.split(","), trim));
|
|
17
|
+
};
|