@applicaster/zapp-react-native-utils 13.0.7-alpha.7163853950 → 13.0.7
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/appUtils/accessibilityManager/const.ts +0 -4
- package/appUtils/accessibilityManager/hooks.ts +1 -35
- package/appUtils/accessibilityManager/index.ts +16 -147
- package/appUtils/platform/platformUtils.ts +1 -32
- package/index.d.ts +0 -3
- package/package.json +2 -2
- package/playerUtils/PlayerTTS/PlayerTTS.ts +0 -230
- package/playerUtils/PlayerTTS/index.ts +0 -1
- package/playerUtils/usePlayerTTS.ts +0 -21
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useMemo
|
|
1
|
+
import { useEffect, useMemo } from "react";
|
|
2
2
|
import { AccessibilityManager } from "./index";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -38,37 +38,3 @@ export const useAccessibilityManager = (
|
|
|
38
38
|
|
|
39
39
|
return accessibilityManager;
|
|
40
40
|
};
|
|
41
|
-
|
|
42
|
-
export const useInitialAnnouncementReady = (
|
|
43
|
-
accessibilityManager: AccessibilityManager
|
|
44
|
-
) => {
|
|
45
|
-
const [isReady, setIsReady] = useState(
|
|
46
|
-
accessibilityManager.isInitialPlayerAnnouncementReady
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
const subscription = accessibilityManager
|
|
51
|
-
.getInitialAnnouncementReadyObservable()
|
|
52
|
-
.subscribe(setIsReady);
|
|
53
|
-
|
|
54
|
-
return () => subscription.unsubscribe();
|
|
55
|
-
}, [accessibilityManager]);
|
|
56
|
-
|
|
57
|
-
return isReady;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export const useAnnouncementActive = (
|
|
61
|
-
accessibilityManager: AccessibilityManager
|
|
62
|
-
) => {
|
|
63
|
-
const [isActive, setIsActive] = useState(false);
|
|
64
|
-
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
const subscription = accessibilityManager
|
|
67
|
-
.getTTSStateObservable()
|
|
68
|
-
.subscribe(setIsActive);
|
|
69
|
-
|
|
70
|
-
return () => subscription.unsubscribe();
|
|
71
|
-
}, [accessibilityManager]);
|
|
72
|
-
|
|
73
|
-
return isActive;
|
|
74
|
-
};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as R from "ramda";
|
|
2
1
|
import { BehaviorSubject } from "rxjs";
|
|
3
2
|
import { accessibilityManagerLogger as logger } from "./logger";
|
|
4
3
|
import { TTSManager } from "../platform/platformUtils";
|
|
@@ -9,10 +8,8 @@ import _ from "lodash";
|
|
|
9
8
|
export class AccessibilityManager {
|
|
10
9
|
private static _instance: AccessibilityManager | null = null;
|
|
11
10
|
private headingTimeout: NodeJS.Timeout | null = null;
|
|
12
|
-
private announcementDelayTimeout: NodeJS.Timeout | null = null;
|
|
13
11
|
private WORDS_PER_MINUTE = 160;
|
|
14
12
|
private MINIMUM_PAUSE = 500;
|
|
15
|
-
private ANNOUNCEMENT_DELAY = 500;
|
|
16
13
|
private state$ = new BehaviorSubject<AccessibilityState>({
|
|
17
14
|
screenReaderEnabled: false,
|
|
18
15
|
reduceMotionEnabled: false,
|
|
@@ -28,12 +25,6 @@ export class AccessibilityManager {
|
|
|
28
25
|
private ttsManager = TTSManager.getInstance();
|
|
29
26
|
private localizations: { [key: string]: string } = {};
|
|
30
27
|
private headingQueue: string[] = [];
|
|
31
|
-
private currentFocusId: string | null = null;
|
|
32
|
-
private headingFocusMap: Map<string, string> = new Map();
|
|
33
|
-
private pendingFocusId: string | null = null;
|
|
34
|
-
private isInitialPlayerAnnouncementReady$ = new BehaviorSubject<boolean>(
|
|
35
|
-
false
|
|
36
|
-
);
|
|
37
28
|
|
|
38
29
|
private constructor() {}
|
|
39
30
|
|
|
@@ -45,26 +36,6 @@ export class AccessibilityManager {
|
|
|
45
36
|
return AccessibilityManager._instance;
|
|
46
37
|
}
|
|
47
38
|
|
|
48
|
-
public get isInitialPlayerAnnouncementReady(): boolean {
|
|
49
|
-
return this.isInitialPlayerAnnouncementReady$.getValue();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
public setInitialPlayerAnnouncementReady(): void {
|
|
53
|
-
this.isInitialPlayerAnnouncementReady$.next(true);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
public resetInitialPlayerAnnouncementReady(): void {
|
|
57
|
-
this.isInitialPlayerAnnouncementReady$.next(false);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
public getInitialAnnouncementReadyObservable() {
|
|
61
|
-
return this.isInitialPlayerAnnouncementReady$.asObservable();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
public getTTSStateObservable() {
|
|
65
|
-
return this.ttsManager.getStateAsObservable();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
39
|
/**
|
|
69
40
|
* The method now accepts any object with localizations using a flattened structure
|
|
70
41
|
*
|
|
@@ -75,9 +46,7 @@ export class AccessibilityManager {
|
|
|
75
46
|
* i.e. localizations: [{ en: { accessibility_close_label: "Close", accessibility_close_hint: "Press here to close" } }]
|
|
76
47
|
*/
|
|
77
48
|
public updateLocalizations(localizations: { [key: string]: string }) {
|
|
78
|
-
|
|
79
|
-
this.localizations = localizations;
|
|
80
|
-
}
|
|
49
|
+
this.localizations = localizations;
|
|
81
50
|
}
|
|
82
51
|
|
|
83
52
|
public getState(): AccessibilityState {
|
|
@@ -96,11 +65,9 @@ export class AccessibilityManager {
|
|
|
96
65
|
private calculateReadingTime(text: string): number {
|
|
97
66
|
const words = text.trim().split(/\s+/).length;
|
|
98
67
|
|
|
99
|
-
return (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
(words / this.WORDS_PER_MINUTE) * 60 * 1000
|
|
103
|
-
) + 300
|
|
68
|
+
return Math.max(
|
|
69
|
+
this.MINIMUM_PAUSE,
|
|
70
|
+
(words / this.WORDS_PER_MINUTE) * 60 * 1000
|
|
104
71
|
);
|
|
105
72
|
}
|
|
106
73
|
|
|
@@ -109,20 +76,12 @@ export class AccessibilityManager {
|
|
|
109
76
|
* Each heading will be read once and removed from the queue
|
|
110
77
|
*/
|
|
111
78
|
public addHeading(heading: string) {
|
|
112
|
-
if (!this.pendingFocusId) {
|
|
113
|
-
this.pendingFocusId = Date.now().toString();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
this.headingFocusMap.set(heading, this.pendingFocusId);
|
|
117
79
|
this.headingQueue.push(heading);
|
|
118
80
|
}
|
|
119
81
|
|
|
120
82
|
/**
|
|
121
83
|
* text you want to be read, if you want to use localized text pass keyOfLocalizedText instead
|
|
122
84
|
* keyOfLocalizedText is the key to the localized text
|
|
123
|
-
*
|
|
124
|
-
* Implements a delay mechanism to reduce noise during rapid navigation.
|
|
125
|
-
* Only the most recent announcement will be read after the delay period.
|
|
126
85
|
*/
|
|
127
86
|
public readText({
|
|
128
87
|
text,
|
|
@@ -153,27 +112,19 @@ export class AccessibilityManager {
|
|
|
153
112
|
textToRead = localizedMessage;
|
|
154
113
|
}
|
|
155
114
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
115
|
+
if (this.headingQueue.length > 0) {
|
|
116
|
+
const heading = this.headingQueue.shift()!;
|
|
117
|
+
this.ttsManager?.readText(heading);
|
|
159
118
|
|
|
160
|
-
|
|
119
|
+
if (this.headingTimeout) {
|
|
120
|
+
clearTimeout(this.headingTimeout);
|
|
121
|
+
}
|
|
161
122
|
|
|
162
|
-
|
|
163
|
-
this.executeAnnouncement(textToRead, keyOfLocalizedText, focusId);
|
|
164
|
-
}, this.ANNOUNCEMENT_DELAY);
|
|
165
|
-
}
|
|
123
|
+
const pauseTime = this.calculateReadingTime(heading);
|
|
166
124
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
private executeAnnouncement(
|
|
171
|
-
textToRead: string,
|
|
172
|
-
keyOfLocalizedText?: string,
|
|
173
|
-
focusId?: string
|
|
174
|
-
) {
|
|
175
|
-
if (this.headingQueue.length > 0) {
|
|
176
|
-
this.processHeadingQueue(textToRead, focusId);
|
|
125
|
+
this.headingTimeout = setTimeout(() => {
|
|
126
|
+
this.ttsManager?.readText(textToRead);
|
|
127
|
+
}, pauseTime);
|
|
177
128
|
} else {
|
|
178
129
|
this.ttsManager?.readText(textToRead);
|
|
179
130
|
}
|
|
@@ -185,49 +136,6 @@ export class AccessibilityManager {
|
|
|
185
136
|
});
|
|
186
137
|
}
|
|
187
138
|
|
|
188
|
-
/**
|
|
189
|
-
* Recursively processes all headings in the queue, reading them one by one
|
|
190
|
-
*/
|
|
191
|
-
private processHeadingQueue(textToRead: string, focusId?: string) {
|
|
192
|
-
// If focus has changed, abort this announcement
|
|
193
|
-
if (focusId && this.currentFocusId !== focusId) {
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (this.headingQueue.length === 0) {
|
|
198
|
-
if (focusId && this.currentFocusId === focusId) {
|
|
199
|
-
this.ttsManager?.readText(textToRead);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const heading = this.headingQueue.shift()!;
|
|
206
|
-
|
|
207
|
-
const headingFocusId = this.headingFocusMap.get(heading);
|
|
208
|
-
|
|
209
|
-
if (headingFocusId && headingFocusId !== focusId) {
|
|
210
|
-
// This heading belongs to a previous focus, skip it
|
|
211
|
-
this.headingFocusMap.delete(heading);
|
|
212
|
-
this.processHeadingQueue(textToRead, focusId);
|
|
213
|
-
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
this.ttsManager?.readText(heading);
|
|
218
|
-
this.headingFocusMap.delete(heading); // Clean up after reading
|
|
219
|
-
|
|
220
|
-
if (this.headingTimeout) {
|
|
221
|
-
clearTimeout(this.headingTimeout);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const pauseTime = this.calculateReadingTime(heading);
|
|
225
|
-
|
|
226
|
-
this.headingTimeout = setTimeout(() => {
|
|
227
|
-
this.processHeadingQueue(textToRead, focusId);
|
|
228
|
-
}, pauseTime);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
139
|
public getButtonAccessibilityProps(name: string): AccessibilityProps {
|
|
232
140
|
const buttonName = _.toString(name);
|
|
233
141
|
|
|
@@ -235,15 +143,12 @@ export class AccessibilityManager {
|
|
|
235
143
|
|
|
236
144
|
if (!buttonConfig) {
|
|
237
145
|
return {
|
|
238
|
-
accessible: true,
|
|
239
146
|
accessibilityLabel: buttonName,
|
|
240
147
|
accessibilityHint: `Press button to perform action on ${buttonName}`,
|
|
241
148
|
"aria-label": buttonName,
|
|
242
149
|
"aria-description": `Press button to perform action on ${buttonName}`,
|
|
243
150
|
accessibilityRole: "button" as AccessibilityRole,
|
|
244
151
|
"aria-role": "button",
|
|
245
|
-
role: "button",
|
|
246
|
-
tabindex: 0,
|
|
247
152
|
};
|
|
248
153
|
}
|
|
249
154
|
|
|
@@ -257,52 +162,23 @@ export class AccessibilityManager {
|
|
|
257
162
|
`Press button to perform action on ${buttonName}`;
|
|
258
163
|
|
|
259
164
|
return {
|
|
260
|
-
accessible: true,
|
|
261
165
|
accessibilityLabel: label,
|
|
262
166
|
accessibilityHint: hint,
|
|
263
167
|
"aria-label": label,
|
|
264
168
|
"aria-description": hint,
|
|
265
169
|
accessibilityRole: "button" as AccessibilityRole,
|
|
266
170
|
"aria-role": "button",
|
|
267
|
-
role: "button",
|
|
268
|
-
tabindex: 0,
|
|
269
171
|
};
|
|
270
172
|
}
|
|
271
173
|
|
|
272
174
|
public getInputAccessibilityProps(inputName: string): AccessibilityProps {
|
|
273
175
|
return {
|
|
274
|
-
accessible: true,
|
|
275
176
|
accessibilityLabel: inputName,
|
|
276
177
|
accessibilityHint: `Enter text into ${inputName}`,
|
|
277
178
|
"aria-label": inputName,
|
|
278
179
|
"aria-description": `Enter text into ${inputName}`,
|
|
279
|
-
accessibilityRole: "
|
|
280
|
-
"aria-role": "
|
|
281
|
-
role: "text",
|
|
282
|
-
tabindex: 0,
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Extracts accessibility props from component props and returns them as HTML attributes
|
|
288
|
-
* @param props - Component props containing accessibility properties
|
|
289
|
-
* @returns Object with accessibility HTML attributes
|
|
290
|
-
*/
|
|
291
|
-
public getWebAccessibilityProps(props: any): AccessibilityProps {
|
|
292
|
-
const {
|
|
293
|
-
"aria-label": ariaLabel,
|
|
294
|
-
"aria-description": ariaDescription,
|
|
295
|
-
"aria-role": ariaRole,
|
|
296
|
-
role,
|
|
297
|
-
tabindex,
|
|
298
|
-
} = props;
|
|
299
|
-
|
|
300
|
-
return {
|
|
301
|
-
"aria-label": ariaLabel,
|
|
302
|
-
"aria-description": ariaDescription,
|
|
303
|
-
"aria-role": ariaRole,
|
|
304
|
-
role: role || ariaRole,
|
|
305
|
-
tabindex,
|
|
180
|
+
accessibilityRole: "textbox" as AccessibilityRole,
|
|
181
|
+
"aria-role": "textbox",
|
|
306
182
|
};
|
|
307
183
|
}
|
|
308
184
|
|
|
@@ -320,11 +196,4 @@ export class AccessibilityManager {
|
|
|
320
196
|
|
|
321
197
|
return this.localizations[key];
|
|
322
198
|
}
|
|
323
|
-
|
|
324
|
-
private clearAnnouncement() {
|
|
325
|
-
if (this.announcementDelayTimeout) {
|
|
326
|
-
clearTimeout(this.announcementDelayTimeout);
|
|
327
|
-
this.announcementDelayTimeout = null;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
199
|
}
|
|
@@ -212,16 +212,9 @@ export class TTSManager {
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
readText(text: string) {
|
|
215
|
-
this.ttsState$.next(true);
|
|
216
|
-
|
|
217
215
|
if (isSamsungPlatform() && window.speechSynthesis) {
|
|
218
216
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
219
|
-
|
|
220
|
-
window.speechSynthesis.cancel(); // Cancel previous speech before speaking new text
|
|
221
217
|
window.speechSynthesis.speak(utterance);
|
|
222
|
-
|
|
223
|
-
// Estimate reading time and set inactive when done
|
|
224
|
-
this.scheduleTTSComplete(text);
|
|
225
218
|
}
|
|
226
219
|
|
|
227
220
|
if (isLgPlatform() && window.webOS?.service) {
|
|
@@ -232,47 +225,23 @@ export class TTSManager {
|
|
|
232
225
|
log_debug("There was a failure setting up webOS TTS service", {
|
|
233
226
|
error,
|
|
234
227
|
});
|
|
235
|
-
|
|
236
|
-
this.ttsState$.next(false);
|
|
237
228
|
},
|
|
238
229
|
onSuccess(response: any) {
|
|
239
230
|
log_debug("webOS TTS service is configured successfully", {
|
|
240
231
|
response,
|
|
241
232
|
});
|
|
242
|
-
|
|
243
|
-
// Estimate reading time and set inactive when done
|
|
244
|
-
this.scheduleTTSComplete(text);
|
|
245
233
|
},
|
|
246
234
|
parameters: {
|
|
247
235
|
text,
|
|
248
|
-
clear: true, // Clear any previous speech before speaking new text
|
|
249
236
|
},
|
|
250
237
|
});
|
|
251
238
|
} catch (error) {
|
|
252
239
|
log_debug("webOS TTS service error", { error });
|
|
253
|
-
this.ttsState$.next(false);
|
|
254
240
|
}
|
|
255
241
|
}
|
|
256
242
|
|
|
257
|
-
if (!window.VIZIO?.Chromevox)
|
|
258
|
-
// For platforms without TTS, estimate reading time
|
|
259
|
-
this.scheduleTTSComplete(text);
|
|
260
|
-
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
243
|
+
if (!window.VIZIO?.Chromevox) return;
|
|
263
244
|
|
|
264
245
|
window.VIZIO.Chromevox.play(text);
|
|
265
|
-
// Estimate reading time and set inactive when done
|
|
266
|
-
this.scheduleTTSComplete(text);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
private scheduleTTSComplete(text: string) {
|
|
270
|
-
// Estimate reading time based on word count (160 words per minute)
|
|
271
|
-
const words = text.trim().split(/\s+/).length;
|
|
272
|
-
const readingTime = Math.max(500, (words / 160) * 60 * 1000) + 300;
|
|
273
|
-
|
|
274
|
-
setTimeout(() => {
|
|
275
|
-
this.ttsState$.next(false);
|
|
276
|
-
}, readingTime);
|
|
277
246
|
}
|
|
278
247
|
}
|
package/index.d.ts
CHANGED
|
@@ -139,13 +139,10 @@ declare type AccessibilityState = {
|
|
|
139
139
|
};
|
|
140
140
|
|
|
141
141
|
declare type AccessibilityProps = {
|
|
142
|
-
accessible?: boolean;
|
|
143
142
|
accessibilityLabel?: string;
|
|
144
143
|
accessibilityHint?: string;
|
|
145
144
|
"aria-label"?: string;
|
|
146
145
|
"aria-description"?: string;
|
|
147
146
|
accessibilityRole?: string;
|
|
148
147
|
"aria-role"?: string;
|
|
149
|
-
role?: string;
|
|
150
|
-
tabindex?: number;
|
|
151
148
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "13.0.7
|
|
3
|
+
"version": "13.0.7",
|
|
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": "13.0.7
|
|
30
|
+
"@applicaster/applicaster-types": "13.0.7",
|
|
31
31
|
"buffer": "^5.2.1",
|
|
32
32
|
"camelize": "^1.0.0",
|
|
33
33
|
"dayjs": "^1.11.10",
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import { AccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager";
|
|
2
|
-
import uuidv4 from "uuid/v4";
|
|
3
|
-
import { Player } from "../../appUtils/playerManager/player";
|
|
4
|
-
|
|
5
|
-
enum SEEK_DIRECTION {
|
|
6
|
-
FORWARD = "forward",
|
|
7
|
-
REWIND = "back",
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export class PlayerTTS {
|
|
11
|
-
private player: Player;
|
|
12
|
-
private accessibilityManager: AccessibilityManager;
|
|
13
|
-
private seekStartPosition: number | null = null;
|
|
14
|
-
private isSeeking: boolean = false;
|
|
15
|
-
private listenerId: string;
|
|
16
|
-
private isInitialPlayerOpen: boolean = true;
|
|
17
|
-
|
|
18
|
-
constructor(player: Player, accessibilityManager: AccessibilityManager) {
|
|
19
|
-
this.player = player;
|
|
20
|
-
this.accessibilityManager = accessibilityManager;
|
|
21
|
-
this.listenerId = `player-tts-${uuidv4()}`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
private numberToWords(num: number): string {
|
|
25
|
-
const ones = [
|
|
26
|
-
"",
|
|
27
|
-
"one",
|
|
28
|
-
"two",
|
|
29
|
-
"three",
|
|
30
|
-
"four",
|
|
31
|
-
"five",
|
|
32
|
-
"six",
|
|
33
|
-
"seven",
|
|
34
|
-
"eight",
|
|
35
|
-
"nine",
|
|
36
|
-
"ten",
|
|
37
|
-
"eleven",
|
|
38
|
-
"twelve",
|
|
39
|
-
"thirteen",
|
|
40
|
-
"fourteen",
|
|
41
|
-
"fifteen",
|
|
42
|
-
"sixteen",
|
|
43
|
-
"seventeen",
|
|
44
|
-
"eighteen",
|
|
45
|
-
"nineteen",
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
const tens = [
|
|
49
|
-
"",
|
|
50
|
-
"",
|
|
51
|
-
"twenty",
|
|
52
|
-
"thirty",
|
|
53
|
-
"forty",
|
|
54
|
-
"fifty",
|
|
55
|
-
"sixty",
|
|
56
|
-
"seventy",
|
|
57
|
-
"eighty",
|
|
58
|
-
"ninety",
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
if (num === 0) return "zero";
|
|
62
|
-
if (num < 20) return ones[num];
|
|
63
|
-
|
|
64
|
-
const ten = Math.floor(num / 10);
|
|
65
|
-
const one = num % 10;
|
|
66
|
-
|
|
67
|
-
if (one === 0) return tens[ten];
|
|
68
|
-
|
|
69
|
-
return `${tens[ten]} ${ones[one]}`;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
private secondsToTime(
|
|
73
|
-
seconds: number,
|
|
74
|
-
format: "natural" | "standard" = "natural"
|
|
75
|
-
): string {
|
|
76
|
-
if (seconds < 0) return format === "natural" ? "zero" : "0";
|
|
77
|
-
|
|
78
|
-
const minutes = Math.floor(seconds / 60);
|
|
79
|
-
const remainingSeconds = Math.floor(seconds % 60);
|
|
80
|
-
|
|
81
|
-
if (format === "standard") {
|
|
82
|
-
if (minutes === 0) {
|
|
83
|
-
if (remainingSeconds === 0) return "0";
|
|
84
|
-
|
|
85
|
-
return `${remainingSeconds} second${remainingSeconds !== 1 ? "s" : ""}`;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (remainingSeconds === 0) {
|
|
89
|
-
return `${minutes} minute${minutes !== 1 ? "s" : ""}`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return `${minutes} minute${minutes !== 1 ? "s" : ""} ${remainingSeconds} second${remainingSeconds !== 1 ? "s" : ""}`;
|
|
93
|
-
} else {
|
|
94
|
-
if (minutes === 0) {
|
|
95
|
-
if (remainingSeconds === 0) return "zero";
|
|
96
|
-
|
|
97
|
-
if (remainingSeconds < 10) {
|
|
98
|
-
return `zero o ${this.numberToWords(remainingSeconds)}`;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return `zero ${this.numberToWords(remainingSeconds)}`;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (remainingSeconds === 0) {
|
|
105
|
-
return `${this.numberToWords(minutes)}`;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (remainingSeconds < 10) {
|
|
109
|
-
return `${this.numberToWords(minutes)} o ${this.numberToWords(remainingSeconds)}`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return `${this.numberToWords(minutes)} ${this.numberToWords(remainingSeconds)}`;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private announceLoading = () => {
|
|
117
|
-
this.accessibilityManager.addHeading("Loading");
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
private announcePause = () => {
|
|
121
|
-
if (!this.isSeeking) {
|
|
122
|
-
this.accessibilityManager.addHeading(
|
|
123
|
-
`Paused - ${this.secondsToTime(this.player.playerState.contentPosition, "standard")}`
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
private announceBufferComplete = (event: any) => {
|
|
129
|
-
if (this.isInitialPlayerOpen) {
|
|
130
|
-
const timeRemaining = event.duration - event.currentTime;
|
|
131
|
-
const title = (this.player.entry.title as string) || "";
|
|
132
|
-
const summary = (this.player.entry.summary as string) || "";
|
|
133
|
-
this.accessibilityManager.addHeading(`Playing - ${title}`);
|
|
134
|
-
this.accessibilityManager.addHeading(summary);
|
|
135
|
-
|
|
136
|
-
this.accessibilityManager.addHeading(
|
|
137
|
-
`Playing from ${this.secondsToTime(event.currentTime, "standard")}`
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
this.accessibilityManager.addHeading(
|
|
141
|
-
`${this.secondsToTime(timeRemaining, "standard")} remaining.`
|
|
142
|
-
);
|
|
143
|
-
|
|
144
|
-
this.accessibilityManager.setInitialPlayerAnnouncementReady();
|
|
145
|
-
this.isInitialPlayerOpen = false;
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
private announceResume = () => {
|
|
150
|
-
if (!this.isSeeking && !this.isInitialPlayerOpen) {
|
|
151
|
-
this.accessibilityManager.addHeading(
|
|
152
|
-
`Playing - ${this.secondsToTime(this.player.playerState.contentPosition, "standard")}`
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
private handleVideoProgress = (event: any) => {
|
|
158
|
-
if (event.currentTime > 0) {
|
|
159
|
-
this.seekStartPosition = event.currentTime;
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
private handleSeekComplete = (event: any) => {
|
|
164
|
-
if (this.seekStartPosition !== null) {
|
|
165
|
-
const seekDirection =
|
|
166
|
-
event.currentTime > this.seekStartPosition
|
|
167
|
-
? SEEK_DIRECTION.FORWARD
|
|
168
|
-
: SEEK_DIRECTION.REWIND;
|
|
169
|
-
|
|
170
|
-
const seekAmount = Math.round(
|
|
171
|
-
Math.abs(event.currentTime - this.seekStartPosition)
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
this.accessibilityManager.readText({
|
|
175
|
-
text: `Skipped ${seekDirection} ${this.secondsToTime(seekAmount, "standard")}`,
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
this.seekStartPosition = event.currentTime;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
this.isSeeking = false;
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
private handleSeekStart = () => {
|
|
185
|
-
this.isSeeking = true;
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
private handlePlayerClose = () => {
|
|
189
|
-
this.isInitialPlayerOpen = true;
|
|
190
|
-
this.accessibilityManager.resetInitialPlayerAnnouncementReady();
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
private announceAdBegin = (event: any) => {
|
|
194
|
-
if (event?.ad?.data?.duration) {
|
|
195
|
-
this.accessibilityManager.readText({
|
|
196
|
-
text: `Sponsored. Ends in ${this.secondsToTime(event.ad.data.duration, "standard")}`,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
public init(): () => void {
|
|
202
|
-
if (!this.player) {
|
|
203
|
-
return () => {};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return this.player.addListener({
|
|
207
|
-
id: this.listenerId,
|
|
208
|
-
listener: {
|
|
209
|
-
onBufferStart: this.announceLoading,
|
|
210
|
-
onBufferComplete: this.announceBufferComplete,
|
|
211
|
-
onPlayerResume: this.announceResume,
|
|
212
|
-
onPlayerPause: this.announcePause,
|
|
213
|
-
onVideoProgress: this.handleVideoProgress,
|
|
214
|
-
onPlayerSeekStart: this.handleSeekStart,
|
|
215
|
-
onPlayerSeekComplete: this.handleSeekComplete,
|
|
216
|
-
onPlayerClose: this.handlePlayerClose,
|
|
217
|
-
onAdBegin: this.announceAdBegin,
|
|
218
|
-
},
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
public destroy(): void {
|
|
223
|
-
if (this.player) {
|
|
224
|
-
this.player.removeListener(this.listenerId);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
this.seekStartPosition = null;
|
|
228
|
-
this.handlePlayerClose();
|
|
229
|
-
}
|
|
230
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { PlayerTTS } from "./PlayerTTS";
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer";
|
|
3
|
-
import { useAccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks";
|
|
4
|
-
import { PlayerTTS } from "@applicaster/zapp-react-native-utils/playerUtils/PlayerTTS";
|
|
5
|
-
|
|
6
|
-
export const usePlayerTTS = () => {
|
|
7
|
-
const player = usePlayer();
|
|
8
|
-
const accessibilityManager = useAccessibilityManager({});
|
|
9
|
-
|
|
10
|
-
React.useEffect(() => {
|
|
11
|
-
if (player && accessibilityManager) {
|
|
12
|
-
const playerTTS = new PlayerTTS(player, accessibilityManager);
|
|
13
|
-
const unsubscribe = playerTTS.init();
|
|
14
|
-
|
|
15
|
-
return () => {
|
|
16
|
-
unsubscribe();
|
|
17
|
-
playerTTS.destroy();
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
}, [player, accessibilityManager]);
|
|
21
|
-
};
|