@applicaster/zapp-react-native-utils 13.0.6 → 13.0.7-alpha.2991546311
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 +4 -0
- package/appUtils/accessibilityManager/index.ts +120 -15
- package/appUtils/platform/platformUtils.ts +3 -0
- package/index.d.ts +3 -0
- package/package.json +2 -2
- package/playerUtils/PlayerTTS/PlayerTTS.ts +213 -0
- package/playerUtils/PlayerTTS/index.ts +1 -0
- package/playerUtils/usePlayerTTS.ts +21 -0
|
@@ -8,8 +8,10 @@ import _ from "lodash";
|
|
|
8
8
|
export class AccessibilityManager {
|
|
9
9
|
private static _instance: AccessibilityManager | null = null;
|
|
10
10
|
private headingTimeout: NodeJS.Timeout | null = null;
|
|
11
|
+
private announcementDelayTimeout: NodeJS.Timeout | null = null;
|
|
11
12
|
private WORDS_PER_MINUTE = 160;
|
|
12
13
|
private MINIMUM_PAUSE = 500;
|
|
14
|
+
private ANNOUNCEMENT_DELAY = 500;
|
|
13
15
|
private state$ = new BehaviorSubject<AccessibilityState>({
|
|
14
16
|
screenReaderEnabled: false,
|
|
15
17
|
reduceMotionEnabled: false,
|
|
@@ -25,6 +27,9 @@ export class AccessibilityManager {
|
|
|
25
27
|
private ttsManager = TTSManager.getInstance();
|
|
26
28
|
private localizations: { [key: string]: string } = {};
|
|
27
29
|
private headingQueue: string[] = [];
|
|
30
|
+
private currentFocusId: string | null = null;
|
|
31
|
+
private headingFocusMap: Map<string, string> = new Map();
|
|
32
|
+
private pendingFocusId: string | null = null;
|
|
28
33
|
|
|
29
34
|
private constructor() {}
|
|
30
35
|
|
|
@@ -65,9 +70,11 @@ export class AccessibilityManager {
|
|
|
65
70
|
private calculateReadingTime(text: string): number {
|
|
66
71
|
const words = text.trim().split(/\s+/).length;
|
|
67
72
|
|
|
68
|
-
return
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
return (
|
|
74
|
+
Math.max(
|
|
75
|
+
this.MINIMUM_PAUSE,
|
|
76
|
+
(words / this.WORDS_PER_MINUTE) * 60 * 1000
|
|
77
|
+
) + 300
|
|
71
78
|
);
|
|
72
79
|
}
|
|
73
80
|
|
|
@@ -76,12 +83,20 @@ export class AccessibilityManager {
|
|
|
76
83
|
* Each heading will be read once and removed from the queue
|
|
77
84
|
*/
|
|
78
85
|
public addHeading(heading: string) {
|
|
86
|
+
if (!this.pendingFocusId) {
|
|
87
|
+
this.pendingFocusId = Date.now().toString();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.headingFocusMap.set(heading, this.pendingFocusId);
|
|
79
91
|
this.headingQueue.push(heading);
|
|
80
92
|
}
|
|
81
93
|
|
|
82
94
|
/**
|
|
83
95
|
* text you want to be read, if you want to use localized text pass keyOfLocalizedText instead
|
|
84
96
|
* keyOfLocalizedText is the key to the localized text
|
|
97
|
+
*
|
|
98
|
+
* Implements a delay mechanism to reduce noise during rapid navigation.
|
|
99
|
+
* Only the most recent announcement will be read after the delay period.
|
|
85
100
|
*/
|
|
86
101
|
public readText({
|
|
87
102
|
text,
|
|
@@ -112,19 +127,27 @@ export class AccessibilityManager {
|
|
|
112
127
|
textToRead = localizedMessage;
|
|
113
128
|
}
|
|
114
129
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
130
|
+
const focusId = this.pendingFocusId || Date.now().toString();
|
|
131
|
+
this.currentFocusId = focusId;
|
|
132
|
+
this.pendingFocusId = null;
|
|
118
133
|
|
|
119
|
-
|
|
120
|
-
clearTimeout(this.headingTimeout);
|
|
121
|
-
}
|
|
134
|
+
this.clearAnnouncement();
|
|
122
135
|
|
|
123
|
-
|
|
136
|
+
this.announcementDelayTimeout = setTimeout(() => {
|
|
137
|
+
this.executeAnnouncement(textToRead, keyOfLocalizedText, focusId);
|
|
138
|
+
}, this.ANNOUNCEMENT_DELAY);
|
|
139
|
+
}
|
|
124
140
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Internal method to execute the actual announcement after the delay
|
|
143
|
+
*/
|
|
144
|
+
private executeAnnouncement(
|
|
145
|
+
textToRead: string,
|
|
146
|
+
keyOfLocalizedText?: string,
|
|
147
|
+
focusId?: string
|
|
148
|
+
) {
|
|
149
|
+
if (this.headingQueue.length > 0) {
|
|
150
|
+
this.processHeadingQueue(textToRead, focusId);
|
|
128
151
|
} else {
|
|
129
152
|
this.ttsManager?.readText(textToRead);
|
|
130
153
|
}
|
|
@@ -136,6 +159,49 @@ export class AccessibilityManager {
|
|
|
136
159
|
});
|
|
137
160
|
}
|
|
138
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Recursively processes all headings in the queue, reading them one by one
|
|
164
|
+
*/
|
|
165
|
+
private processHeadingQueue(textToRead: string, focusId?: string) {
|
|
166
|
+
// If focus has changed, abort this announcement
|
|
167
|
+
if (focusId && this.currentFocusId !== focusId) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (this.headingQueue.length === 0) {
|
|
172
|
+
if (focusId && this.currentFocusId === focusId) {
|
|
173
|
+
this.ttsManager?.readText(textToRead);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const heading = this.headingQueue.shift()!;
|
|
180
|
+
|
|
181
|
+
const headingFocusId = this.headingFocusMap.get(heading);
|
|
182
|
+
|
|
183
|
+
if (headingFocusId && headingFocusId !== focusId) {
|
|
184
|
+
// This heading belongs to a previous focus, skip it
|
|
185
|
+
this.headingFocusMap.delete(heading);
|
|
186
|
+
this.processHeadingQueue(textToRead, focusId);
|
|
187
|
+
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this.ttsManager?.readText(heading);
|
|
192
|
+
this.headingFocusMap.delete(heading); // Clean up after reading
|
|
193
|
+
|
|
194
|
+
if (this.headingTimeout) {
|
|
195
|
+
clearTimeout(this.headingTimeout);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const pauseTime = this.calculateReadingTime(heading);
|
|
199
|
+
|
|
200
|
+
this.headingTimeout = setTimeout(() => {
|
|
201
|
+
this.processHeadingQueue(textToRead, focusId);
|
|
202
|
+
}, pauseTime);
|
|
203
|
+
}
|
|
204
|
+
|
|
139
205
|
public getButtonAccessibilityProps(name: string): AccessibilityProps {
|
|
140
206
|
const buttonName = _.toString(name);
|
|
141
207
|
|
|
@@ -143,12 +209,15 @@ export class AccessibilityManager {
|
|
|
143
209
|
|
|
144
210
|
if (!buttonConfig) {
|
|
145
211
|
return {
|
|
212
|
+
accessible: true,
|
|
146
213
|
accessibilityLabel: buttonName,
|
|
147
214
|
accessibilityHint: `Press button to perform action on ${buttonName}`,
|
|
148
215
|
"aria-label": buttonName,
|
|
149
216
|
"aria-description": `Press button to perform action on ${buttonName}`,
|
|
150
217
|
accessibilityRole: "button" as AccessibilityRole,
|
|
151
218
|
"aria-role": "button",
|
|
219
|
+
role: "button",
|
|
220
|
+
tabindex: 0,
|
|
152
221
|
};
|
|
153
222
|
}
|
|
154
223
|
|
|
@@ -162,23 +231,52 @@ export class AccessibilityManager {
|
|
|
162
231
|
`Press button to perform action on ${buttonName}`;
|
|
163
232
|
|
|
164
233
|
return {
|
|
234
|
+
accessible: true,
|
|
165
235
|
accessibilityLabel: label,
|
|
166
236
|
accessibilityHint: hint,
|
|
167
237
|
"aria-label": label,
|
|
168
238
|
"aria-description": hint,
|
|
169
239
|
accessibilityRole: "button" as AccessibilityRole,
|
|
170
240
|
"aria-role": "button",
|
|
241
|
+
role: "button",
|
|
242
|
+
tabindex: 0,
|
|
171
243
|
};
|
|
172
244
|
}
|
|
173
245
|
|
|
174
246
|
public getInputAccessibilityProps(inputName: string): AccessibilityProps {
|
|
175
247
|
return {
|
|
248
|
+
accessible: true,
|
|
176
249
|
accessibilityLabel: inputName,
|
|
177
250
|
accessibilityHint: `Enter text into ${inputName}`,
|
|
178
251
|
"aria-label": inputName,
|
|
179
252
|
"aria-description": `Enter text into ${inputName}`,
|
|
180
|
-
accessibilityRole: "
|
|
181
|
-
"aria-role": "
|
|
253
|
+
accessibilityRole: "text" as AccessibilityRole,
|
|
254
|
+
"aria-role": "text",
|
|
255
|
+
role: "text",
|
|
256
|
+
tabindex: 0,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Extracts accessibility props from component props and returns them as HTML attributes
|
|
262
|
+
* @param props - Component props containing accessibility properties
|
|
263
|
+
* @returns Object with accessibility HTML attributes
|
|
264
|
+
*/
|
|
265
|
+
public getWebAccessibilityProps(props: any): AccessibilityProps {
|
|
266
|
+
const {
|
|
267
|
+
"aria-label": ariaLabel,
|
|
268
|
+
"aria-description": ariaDescription,
|
|
269
|
+
"aria-role": ariaRole,
|
|
270
|
+
role,
|
|
271
|
+
tabindex,
|
|
272
|
+
} = props;
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
"aria-label": ariaLabel,
|
|
276
|
+
"aria-description": ariaDescription,
|
|
277
|
+
"aria-role": ariaRole,
|
|
278
|
+
role: role || ariaRole,
|
|
279
|
+
tabindex,
|
|
182
280
|
};
|
|
183
281
|
}
|
|
184
282
|
|
|
@@ -196,4 +294,11 @@ export class AccessibilityManager {
|
|
|
196
294
|
|
|
197
295
|
return this.localizations[key];
|
|
198
296
|
}
|
|
297
|
+
|
|
298
|
+
private clearAnnouncement() {
|
|
299
|
+
if (this.announcementDelayTimeout) {
|
|
300
|
+
clearTimeout(this.announcementDelayTimeout);
|
|
301
|
+
this.announcementDelayTimeout = null;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
199
304
|
}
|
|
@@ -214,6 +214,8 @@ export class TTSManager {
|
|
|
214
214
|
readText(text: string) {
|
|
215
215
|
if (isSamsungPlatform() && window.speechSynthesis) {
|
|
216
216
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
217
|
+
|
|
218
|
+
window.speechSynthesis.cancel(); // Cancel previous speech before speaking new text
|
|
217
219
|
window.speechSynthesis.speak(utterance);
|
|
218
220
|
}
|
|
219
221
|
|
|
@@ -233,6 +235,7 @@ export class TTSManager {
|
|
|
233
235
|
},
|
|
234
236
|
parameters: {
|
|
235
237
|
text,
|
|
238
|
+
clear: true, // Clear any pending speech before speaking new text
|
|
236
239
|
},
|
|
237
240
|
});
|
|
238
241
|
} catch (error) {
|
package/index.d.ts
CHANGED
|
@@ -139,10 +139,13 @@ declare type AccessibilityState = {
|
|
|
139
139
|
};
|
|
140
140
|
|
|
141
141
|
declare type AccessibilityProps = {
|
|
142
|
+
accessible?: boolean;
|
|
142
143
|
accessibilityLabel?: string;
|
|
143
144
|
accessibilityHint?: string;
|
|
144
145
|
"aria-label"?: string;
|
|
145
146
|
"aria-description"?: string;
|
|
146
147
|
accessibilityRole?: string;
|
|
147
148
|
"aria-role"?: string;
|
|
149
|
+
role?: string;
|
|
150
|
+
tabindex?: number;
|
|
148
151
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "13.0.
|
|
3
|
+
"version": "13.0.7-alpha.2991546311",
|
|
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.
|
|
30
|
+
"@applicaster/applicaster-types": "13.0.7-alpha.2991546311",
|
|
31
31
|
"buffer": "^5.2.1",
|
|
32
32
|
"camelize": "^1.0.0",
|
|
33
33
|
"dayjs": "^1.11.10",
|
|
@@ -0,0 +1,213 @@
|
|
|
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.isInitialPlayerOpen = false;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
private announceResume = () => {
|
|
149
|
+
if (!this.isSeeking && !this.isInitialPlayerOpen) {
|
|
150
|
+
this.accessibilityManager.addHeading(
|
|
151
|
+
`Playing - ${this.secondsToTime(this.player.playerState.contentPosition, "standard")}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
private handleVideoProgress = (event: any) => {
|
|
157
|
+
if (event.currentTime > 0) {
|
|
158
|
+
this.seekStartPosition = event.currentTime;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
private handleSeekComplete = (event: any) => {
|
|
163
|
+
if (this.seekStartPosition !== null) {
|
|
164
|
+
const seekDirection =
|
|
165
|
+
event.currentTime > this.seekStartPosition
|
|
166
|
+
? SEEK_DIRECTION.FORWARD
|
|
167
|
+
: SEEK_DIRECTION.REWIND;
|
|
168
|
+
|
|
169
|
+
const seekAmount = Math.round(
|
|
170
|
+
Math.abs(event.currentTime - this.seekStartPosition)
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
this.accessibilityManager.readText({
|
|
174
|
+
text: `Skipped ${seekDirection} ${this.secondsToTime(seekAmount, "standard")}`,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
this.seekStartPosition = event.currentTime;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
this.isSeeking = false;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
private handleSeekStart = () => {
|
|
184
|
+
this.isSeeking = true;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
public init(): () => void {
|
|
188
|
+
if (!this.player) {
|
|
189
|
+
return () => {};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return this.player.addListener({
|
|
193
|
+
id: this.listenerId,
|
|
194
|
+
listener: {
|
|
195
|
+
onBufferStart: this.announceLoading,
|
|
196
|
+
onBufferComplete: this.announceBufferComplete,
|
|
197
|
+
onPlayerResume: this.announceResume,
|
|
198
|
+
onPlayerPause: this.announcePause,
|
|
199
|
+
onVideoProgress: this.handleVideoProgress,
|
|
200
|
+
onPlayerSeekStart: this.handleSeekStart,
|
|
201
|
+
onPlayerSeekComplete: this.handleSeekComplete,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
public destroy(): void {
|
|
207
|
+
if (this.player) {
|
|
208
|
+
this.player.removeListener(this.listenerId);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.seekStartPosition = null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PlayerTTS } from "./PlayerTTS";
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
};
|