@applicaster/zapp-react-native-utils 13.0.6 → 13.0.7-alpha.2583030144

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.
@@ -65,4 +65,8 @@ export const BUTTON_ACCESSIBILITY_KEYS = {
65
65
  label: "accessibility_menu_item_label",
66
66
  hint: "accessibility_menu_item_hint",
67
67
  },
68
+ skip_intro: {
69
+ label: "accessibility_skip_intro_label",
70
+ hint: "accessibility_skip_intro_hint",
71
+ },
68
72
  } as const;
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo } from "react";
1
+ import { useEffect, useMemo, useState } from "react";
2
2
  import { AccessibilityManager } from "./index";
3
3
 
4
4
  /**
@@ -38,3 +38,37 @@ 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,3 +1,4 @@
1
+ import * as R from "ramda";
1
2
  import { BehaviorSubject } from "rxjs";
2
3
  import { accessibilityManagerLogger as logger } from "./logger";
3
4
  import { TTSManager } from "../platform/platformUtils";
@@ -8,8 +9,10 @@ import _ from "lodash";
8
9
  export class AccessibilityManager {
9
10
  private static _instance: AccessibilityManager | null = null;
10
11
  private headingTimeout: NodeJS.Timeout | null = null;
12
+ private announcementDelayTimeout: NodeJS.Timeout | null = null;
11
13
  private WORDS_PER_MINUTE = 160;
12
14
  private MINIMUM_PAUSE = 500;
15
+ private ANNOUNCEMENT_DELAY = 500;
13
16
  private state$ = new BehaviorSubject<AccessibilityState>({
14
17
  screenReaderEnabled: false,
15
18
  reduceMotionEnabled: false,
@@ -25,6 +28,12 @@ export class AccessibilityManager {
25
28
  private ttsManager = TTSManager.getInstance();
26
29
  private localizations: { [key: string]: string } = {};
27
30
  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
+ );
28
37
 
29
38
  private constructor() {}
30
39
 
@@ -36,6 +45,26 @@ export class AccessibilityManager {
36
45
  return AccessibilityManager._instance;
37
46
  }
38
47
 
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
+
39
68
  /**
40
69
  * The method now accepts any object with localizations using a flattened structure
41
70
  *
@@ -46,7 +75,9 @@ export class AccessibilityManager {
46
75
  * i.e. localizations: [{ en: { accessibility_close_label: "Close", accessibility_close_hint: "Press here to close" } }]
47
76
  */
48
77
  public updateLocalizations(localizations: { [key: string]: string }) {
49
- this.localizations = localizations;
78
+ if (!R.isEmpty(localizations)) {
79
+ this.localizations = localizations;
80
+ }
50
81
  }
51
82
 
52
83
  public getState(): AccessibilityState {
@@ -65,9 +96,11 @@ export class AccessibilityManager {
65
96
  private calculateReadingTime(text: string): number {
66
97
  const words = text.trim().split(/\s+/).length;
67
98
 
68
- return Math.max(
69
- this.MINIMUM_PAUSE,
70
- (words / this.WORDS_PER_MINUTE) * 60 * 1000
99
+ return (
100
+ Math.max(
101
+ this.MINIMUM_PAUSE,
102
+ (words / this.WORDS_PER_MINUTE) * 60 * 1000
103
+ ) + 300
71
104
  );
72
105
  }
73
106
 
@@ -76,12 +109,20 @@ export class AccessibilityManager {
76
109
  * Each heading will be read once and removed from the queue
77
110
  */
78
111
  public addHeading(heading: string) {
112
+ if (!this.pendingFocusId) {
113
+ this.pendingFocusId = Date.now().toString();
114
+ }
115
+
116
+ this.headingFocusMap.set(heading, this.pendingFocusId);
79
117
  this.headingQueue.push(heading);
80
118
  }
81
119
 
82
120
  /**
83
121
  * text you want to be read, if you want to use localized text pass keyOfLocalizedText instead
84
122
  * 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.
85
126
  */
86
127
  public readText({
87
128
  text,
@@ -112,19 +153,27 @@ export class AccessibilityManager {
112
153
  textToRead = localizedMessage;
113
154
  }
114
155
 
115
- if (this.headingQueue.length > 0) {
116
- const heading = this.headingQueue.shift()!;
117
- this.ttsManager?.readText(heading);
156
+ const focusId = this.pendingFocusId || Date.now().toString();
157
+ this.currentFocusId = focusId;
158
+ this.pendingFocusId = null;
118
159
 
119
- if (this.headingTimeout) {
120
- clearTimeout(this.headingTimeout);
121
- }
160
+ this.clearAnnouncement();
122
161
 
123
- const pauseTime = this.calculateReadingTime(heading);
162
+ this.announcementDelayTimeout = setTimeout(() => {
163
+ this.executeAnnouncement(textToRead, keyOfLocalizedText, focusId);
164
+ }, this.ANNOUNCEMENT_DELAY);
165
+ }
124
166
 
125
- this.headingTimeout = setTimeout(() => {
126
- this.ttsManager?.readText(textToRead);
127
- }, pauseTime);
167
+ /**
168
+ * Internal method to execute the actual announcement after the delay
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);
128
177
  } else {
129
178
  this.ttsManager?.readText(textToRead);
130
179
  }
@@ -136,6 +185,49 @@ export class AccessibilityManager {
136
185
  });
137
186
  }
138
187
 
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
+
139
231
  public getButtonAccessibilityProps(name: string): AccessibilityProps {
140
232
  const buttonName = _.toString(name);
141
233
 
@@ -143,12 +235,15 @@ export class AccessibilityManager {
143
235
 
144
236
  if (!buttonConfig) {
145
237
  return {
238
+ accessible: true,
146
239
  accessibilityLabel: buttonName,
147
240
  accessibilityHint: `Press button to perform action on ${buttonName}`,
148
241
  "aria-label": buttonName,
149
242
  "aria-description": `Press button to perform action on ${buttonName}`,
150
243
  accessibilityRole: "button" as AccessibilityRole,
151
244
  "aria-role": "button",
245
+ role: "button",
246
+ tabindex: 0,
152
247
  };
153
248
  }
154
249
 
@@ -162,23 +257,52 @@ export class AccessibilityManager {
162
257
  `Press button to perform action on ${buttonName}`;
163
258
 
164
259
  return {
260
+ accessible: true,
165
261
  accessibilityLabel: label,
166
262
  accessibilityHint: hint,
167
263
  "aria-label": label,
168
264
  "aria-description": hint,
169
265
  accessibilityRole: "button" as AccessibilityRole,
170
266
  "aria-role": "button",
267
+ role: "button",
268
+ tabindex: 0,
171
269
  };
172
270
  }
173
271
 
174
272
  public getInputAccessibilityProps(inputName: string): AccessibilityProps {
175
273
  return {
274
+ accessible: true,
176
275
  accessibilityLabel: inputName,
177
276
  accessibilityHint: `Enter text into ${inputName}`,
178
277
  "aria-label": inputName,
179
278
  "aria-description": `Enter text into ${inputName}`,
180
- accessibilityRole: "textbox" as AccessibilityRole,
181
- "aria-role": "textbox",
279
+ accessibilityRole: "text" as AccessibilityRole,
280
+ "aria-role": "text",
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,
182
306
  };
183
307
  }
184
308
 
@@ -196,4 +320,11 @@ export class AccessibilityManager {
196
320
 
197
321
  return this.localizations[key];
198
322
  }
323
+
324
+ private clearAnnouncement() {
325
+ if (this.announcementDelayTimeout) {
326
+ clearTimeout(this.announcementDelayTimeout);
327
+ this.announcementDelayTimeout = null;
328
+ }
329
+ }
199
330
  }
@@ -212,9 +212,16 @@ export class TTSManager {
212
212
  }
213
213
 
214
214
  readText(text: string) {
215
+ this.ttsState$.next(true);
216
+
215
217
  if (isSamsungPlatform() && window.speechSynthesis) {
216
218
  const utterance = new SpeechSynthesisUtterance(text);
219
+
220
+ window.speechSynthesis.cancel(); // Cancel previous speech before speaking new text
217
221
  window.speechSynthesis.speak(utterance);
222
+
223
+ // Estimate reading time and set inactive when done
224
+ this.scheduleTTSComplete(text);
218
225
  }
219
226
 
220
227
  if (isLgPlatform() && window.webOS?.service) {
@@ -225,23 +232,47 @@ export class TTSManager {
225
232
  log_debug("There was a failure setting up webOS TTS service", {
226
233
  error,
227
234
  });
235
+
236
+ this.ttsState$.next(false);
228
237
  },
229
238
  onSuccess(response: any) {
230
239
  log_debug("webOS TTS service is configured successfully", {
231
240
  response,
232
241
  });
242
+
243
+ // Estimate reading time and set inactive when done
244
+ this.scheduleTTSComplete(text);
233
245
  },
234
246
  parameters: {
235
247
  text,
248
+ clear: true, // Clear any previous speech before speaking new text
236
249
  },
237
250
  });
238
251
  } catch (error) {
239
252
  log_debug("webOS TTS service error", { error });
253
+ this.ttsState$.next(false);
240
254
  }
241
255
  }
242
256
 
243
- if (!window.VIZIO?.Chromevox) return;
257
+ if (!window.VIZIO?.Chromevox) {
258
+ // For platforms without TTS, estimate reading time
259
+ this.scheduleTTSComplete(text);
260
+
261
+ return;
262
+ }
244
263
 
245
264
  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);
246
277
  }
247
278
  }
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.6",
3
+ "version": "13.0.7-alpha.2583030144",
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.6",
30
+ "@applicaster/applicaster-types": "13.0.7-alpha.2583030144",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",
@@ -0,0 +1,229 @@
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
+ }
229
+ }
@@ -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
+ };