@applicaster/zapp-react-native-utils 13.0.8-alpha.1940580104 → 13.0.8-alpha.4487353250

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "13.0.8-alpha.1940580104",
3
+ "version": "13.0.8-alpha.4487353250",
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.8-alpha.1940580104",
30
+ "@applicaster/applicaster-types": "13.0.8-alpha.4487353250",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",
@@ -1,12 +1,40 @@
1
- import { AccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager";
2
1
  import uuidv4 from "uuid/v4";
3
- import { Player } from "../../appUtils/playerManager/player";
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
+ });
4
10
 
5
11
  enum SEEK_DIRECTION {
6
12
  FORWARD = "forward",
7
13
  REWIND = "back",
8
14
  }
9
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
+
10
38
  export class PlayerTTS {
11
39
  private player: Player;
12
40
  private accessibilityManager: AccessibilityManager;
@@ -14,11 +42,20 @@ export class PlayerTTS {
14
42
  private isSeeking: boolean = false;
15
43
  private listenerId: string;
16
44
  private isInitialPlayerOpen: boolean = true;
45
+ private isPrerollActive: boolean = false;
46
+ private hasPrerollAds: boolean = false; // Track if preroll ads are expected
17
47
 
18
48
  constructor(player: Player, accessibilityManager: AccessibilityManager) {
19
49
  this.player = player;
20
50
  this.accessibilityManager = accessibilityManager;
21
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
+ });
22
59
  }
23
60
 
24
61
  private numberToWords(num: number): string {
@@ -64,9 +101,7 @@ export class PlayerTTS {
64
101
  const ten = Math.floor(num / 10);
65
102
  const one = num % 10;
66
103
 
67
- if (one === 0) return tens[ten];
68
-
69
- return `${tens[ten]} ${ones[one]}`;
104
+ return one === 0 ? tens[ten] : `${tens[ten]} ${ones[one]}`;
70
105
  }
71
106
 
72
107
  private secondsToTime(
@@ -79,17 +114,19 @@ export class PlayerTTS {
79
114
  const remainingSeconds = Math.floor(seconds % 60);
80
115
 
81
116
  if (format === "standard") {
82
- if (minutes === 0) {
83
- if (remainingSeconds === 0) return "0";
117
+ const parts = [];
84
118
 
85
- return `${remainingSeconds} second${remainingSeconds !== 1 ? "s" : ""}`;
119
+ if (minutes > 0) {
120
+ parts.push(`${minutes} minute${minutes !== 1 ? "s" : ""}`);
86
121
  }
87
122
 
88
- if (remainingSeconds === 0) {
89
- return `${minutes} minute${minutes !== 1 ? "s" : ""}`;
123
+ if (remainingSeconds > 0) {
124
+ parts.push(
125
+ `${remainingSeconds} second${remainingSeconds !== 1 ? "s" : ""}`
126
+ );
90
127
  }
91
128
 
92
- return `${minutes} minute${minutes !== 1 ? "s" : ""} ${remainingSeconds} second${remainingSeconds !== 1 ? "s" : ""}`;
129
+ return parts.length > 0 ? parts.join(" ") : "0";
93
130
  } else {
94
131
  if (minutes === 0) {
95
132
  if (remainingSeconds === 0) return "zero";
@@ -113,10 +150,6 @@ export class PlayerTTS {
113
150
  }
114
151
  }
115
152
 
116
- private announceLoading = () => {
117
- this.accessibilityManager.addHeading("Loading");
118
- };
119
-
120
153
  private announcePause = () => {
121
154
  if (!this.isSeeking) {
122
155
  this.accessibilityManager.addHeading(
@@ -125,29 +158,84 @@ export class PlayerTTS {
125
158
  }
126
159
  };
127
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
+
128
205
  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);
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
+ });
135
212
 
136
- this.accessibilityManager.addHeading(
137
- `Playing from ${this.secondsToTime(event.currentTime, "standard")}`
138
- );
213
+ return;
214
+ }
139
215
 
140
- this.accessibilityManager.addHeading(
141
- `${this.secondsToTime(timeRemaining, "standard")} remaining.`
142
- );
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
+ });
143
223
 
144
- this.accessibilityManager.setInitialPlayerAnnouncementReady();
145
- this.isInitialPlayerOpen = false;
224
+ this.announceContentStart({
225
+ currentTime: event.currentTime,
226
+ duration: event.duration,
227
+ });
146
228
  }
147
229
  };
148
230
 
149
231
  private announceResume = () => {
150
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
+
151
239
  this.accessibilityManager.addHeading(
152
240
  `Playing - ${this.secondsToTime(this.player.playerState.contentPosition, "standard")}`
153
241
  );
@@ -171,6 +259,13 @@ export class PlayerTTS {
171
259
  Math.abs(event.currentTime - this.seekStartPosition)
172
260
  );
173
261
 
262
+ log_debug("Seek completed", {
263
+ seekDirection,
264
+ seekAmount,
265
+ fromPosition: this.seekStartPosition,
266
+ toPosition: event.currentTime,
267
+ });
268
+
174
269
  this.accessibilityManager.readText({
175
270
  text: `Skipped ${seekDirection} ${this.secondsToTime(seekAmount, "standard")}`,
176
271
  });
@@ -182,15 +277,24 @@ export class PlayerTTS {
182
277
  };
183
278
 
184
279
  private handleSeekStart = () => {
280
+ log_debug("Seek started");
185
281
  this.isSeeking = true;
186
282
  };
187
283
 
188
284
  private handlePlayerClose = () => {
285
+ log_debug("Player closed - resetting state");
189
286
  this.isInitialPlayerOpen = true;
190
287
  this.accessibilityManager.resetInitialPlayerAnnouncementReady();
191
288
  };
192
289
 
193
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
+
194
298
  if (event?.ad?.data?.duration) {
195
299
  this.accessibilityManager.readText({
196
300
  text: `Sponsored. Ends in ${this.secondsToTime(event.ad.data.duration, "standard")}`,
@@ -198,15 +302,34 @@ export class PlayerTTS {
198
302
  }
199
303
  };
200
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
+
201
319
  public init(): () => void {
202
320
  if (!this.player) {
321
+ log_error("Failed to initialize PlayerTTS - no player provided");
322
+
203
323
  return () => {};
204
324
  }
205
325
 
326
+ log_debug("Initializing PlayerTTS listeners", {
327
+ listenerId: this.listenerId,
328
+ });
329
+
206
330
  return this.player.addListener({
207
331
  id: this.listenerId,
208
332
  listener: {
209
- onBufferStart: this.announceLoading,
210
333
  onBufferComplete: this.announceBufferComplete,
211
334
  onPlayerResume: this.announceResume,
212
335
  onPlayerPause: this.announcePause,
@@ -215,11 +338,17 @@ export class PlayerTTS {
215
338
  onPlayerSeekComplete: this.handleSeekComplete,
216
339
  onPlayerClose: this.handlePlayerClose,
217
340
  onAdBegin: this.announceAdBegin,
341
+ onAdEnd: this.handleAdEnd,
342
+ onAdBreakEnd: this.handleAdEnd,
218
343
  },
219
344
  });
220
345
  }
221
346
 
222
347
  public destroy(): void {
348
+ log_debug("Destroying PlayerTTS", {
349
+ listenerId: this.listenerId,
350
+ });
351
+
223
352
  if (this.player) {
224
353
  this.player.removeListener(this.listenerId);
225
354
  }