@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.
@@ -65,8 +65,4 @@ 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
- },
72
68
  } as const;
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo, useState } from "react";
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
- if (!R.isEmpty(localizations)) {
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
- Math.max(
101
- this.MINIMUM_PAUSE,
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
- const focusId = this.pendingFocusId || Date.now().toString();
157
- this.currentFocusId = focusId;
158
- this.pendingFocusId = null;
115
+ if (this.headingQueue.length > 0) {
116
+ const heading = this.headingQueue.shift()!;
117
+ this.ttsManager?.readText(heading);
159
118
 
160
- this.clearAnnouncement();
119
+ if (this.headingTimeout) {
120
+ clearTimeout(this.headingTimeout);
121
+ }
161
122
 
162
- this.announcementDelayTimeout = setTimeout(() => {
163
- this.executeAnnouncement(textToRead, keyOfLocalizedText, focusId);
164
- }, this.ANNOUNCEMENT_DELAY);
165
- }
123
+ const pauseTime = this.calculateReadingTime(heading);
166
124
 
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);
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: "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,
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-alpha.7163853950",
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-alpha.7163853950",
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
- };