@mulingai-npm/redis 3.36.1 → 3.37.1

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.
@@ -37,9 +37,10 @@ export declare class ChunkSequencerManager {
37
37
  private waitingRoomKey;
38
38
  /**
39
39
  * Get the next expected chunk number for a room/language
40
- * Returns 1 if not initialized yet (first chunk)
40
+ * If not initialized, uses fallbackChunk (defaults to the incoming chunk number).
41
+ * This handles sessions where chunk numbers don't start at 1 (they increment globally).
41
42
  */
42
- getExpectedChunk(roomId: string, language: string): Promise<number>;
43
+ getExpectedChunk(roomId: string, language: string, fallbackChunk?: number): Promise<number>;
43
44
  /**
44
45
  * Set the expected chunk number
45
46
  */
@@ -28,16 +28,17 @@ class ChunkSequencerManager {
28
28
  }
29
29
  /**
30
30
  * Get the next expected chunk number for a room/language
31
- * Returns 1 if not initialized yet (first chunk)
31
+ * If not initialized, uses fallbackChunk (defaults to the incoming chunk number).
32
+ * This handles sessions where chunk numbers don't start at 1 (they increment globally).
32
33
  */
33
- async getExpectedChunk(roomId, language) {
34
+ async getExpectedChunk(roomId, language, fallbackChunk) {
34
35
  const key = this.expectedChunkKey(roomId, language);
35
36
  const value = await this.redisClient.get(key);
36
37
  if (!value) {
37
- // Initialize to 1 for first chunk
38
- await this.redisClient.set(key, '1');
38
+ const initial = fallbackChunk !== null && fallbackChunk !== void 0 ? fallbackChunk : 1;
39
+ await this.redisClient.set(key, initial.toString());
39
40
  await this.redisClient.expire(key, 3600); // 1 hour TTL
40
- return 1;
41
+ return initial;
41
42
  }
42
43
  return parseInt(value, 10);
43
44
  }
@@ -9,6 +9,7 @@ export type MulingstreamListenerData = {
9
9
  lastHeartbeat: number;
10
10
  language?: string;
11
11
  color?: string;
12
+ isListening?: boolean;
12
13
  };
13
14
  export declare class MulingstreamListenerManager {
14
15
  private redisClient;
@@ -29,4 +30,21 @@ export declare class MulingstreamListenerManager {
29
30
  languageBreakdown: Record<string, number>;
30
31
  }>;
31
32
  getListenerBySocketId(socketId: string): Promise<MulingstreamListenerData | null>;
33
+ /**
34
+ * Update the isListening state for a listener (audio play/pause).
35
+ */
36
+ setListeningState(listenerIdOrToken: string, isListening: boolean): Promise<MulingstreamListenerData | null>;
37
+ /**
38
+ * Returns unique languages that have at least one listener with isListening=true.
39
+ * Used by pipeline to determine which languages need TTS generation.
40
+ */
41
+ getLanguagesWithActiveListeners(roomId: string): Promise<string[]>;
42
+ /**
43
+ * Returns socket IDs for listeners of a language who have isListening=true (audio on).
44
+ */
45
+ getListeningSocketIdsByRoomLanguage(roomId: string, language: string): Promise<string[]>;
46
+ /**
47
+ * Returns socket IDs for listeners of a language who have isListening=false (audio off, reading text only).
48
+ */
49
+ getNonListeningSocketIdsByRoomLanguage(roomId: string, language: string): Promise<string[]>;
32
50
  }
@@ -45,7 +45,8 @@ class MulingstreamListenerManager {
45
45
  firstJoined: parseInt(data.firstJoined, 10),
46
46
  lastHeartbeat: parseInt(data.lastHeartbeat, 10),
47
47
  language: data.language || '',
48
- color: data.color || ''
48
+ color: data.color || '',
49
+ isListening: data.isListening === 'true'
49
50
  };
50
51
  }
51
52
  // Creates a new listener.
@@ -69,7 +70,8 @@ class MulingstreamListenerManager {
69
70
  firstJoined: listenerData.firstJoined.toString(),
70
71
  lastHeartbeat: listenerData.lastHeartbeat.toString(),
71
72
  language: (_c = listenerData.language) !== null && _c !== void 0 ? _c : '',
72
- color
73
+ color,
74
+ isListening: 'false'
73
75
  });
74
76
  // expire listener
75
77
  await this.redisClient.expire(`listener:${listenerId}`, EXPIRATION);
@@ -235,5 +237,56 @@ class MulingstreamListenerManager {
235
237
  }
236
238
  return null;
237
239
  }
240
+ /**
241
+ * Update the isListening state for a listener (audio play/pause).
242
+ */
243
+ async setListeningState(listenerIdOrToken, isListening) {
244
+ const listener = await this.getListener(listenerIdOrToken);
245
+ if (!listener) {
246
+ return null;
247
+ }
248
+ await this.redisClient.hset(`listener:${listener.listenerId}`, {
249
+ isListening: isListening.toString()
250
+ });
251
+ return { ...listener, isListening };
252
+ }
253
+ /**
254
+ * Returns unique languages that have at least one listener with isListening=true.
255
+ * Used by pipeline to determine which languages need TTS generation.
256
+ */
257
+ async getLanguagesWithActiveListeners(roomId) {
258
+ const listeners = await this.getListenersByRoom(roomId);
259
+ const languageCountMap = {};
260
+ for (const listener of listeners) {
261
+ if (!listener.language || !listener.language.trim())
262
+ continue;
263
+ if (!listener.isListening)
264
+ continue;
265
+ const lang = listener.language;
266
+ languageCountMap[lang] = (languageCountMap[lang] || 0) + 1;
267
+ }
268
+ const sortedEntries = Object.entries(languageCountMap).sort(([, countA], [, countB]) => countB - countA);
269
+ return sortedEntries.map(([language]) => language);
270
+ }
271
+ /**
272
+ * Returns socket IDs for listeners of a language who have isListening=true (audio on).
273
+ */
274
+ async getListeningSocketIdsByRoomLanguage(roomId, language) {
275
+ const listeners = await this.getListenersByRoom(roomId);
276
+ return listeners
277
+ .filter(l => l.language === language && l.isListening)
278
+ .map(l => l.socketId)
279
+ .filter((id) => !!id);
280
+ }
281
+ /**
282
+ * Returns socket IDs for listeners of a language who have isListening=false (audio off, reading text only).
283
+ */
284
+ async getNonListeningSocketIdsByRoomLanguage(roomId, language) {
285
+ const listeners = await this.getListenersByRoom(roomId);
286
+ return listeners
287
+ .filter(l => l.language === language && !l.isListening)
288
+ .map(l => l.socketId)
289
+ .filter((id) => !!id);
290
+ }
238
291
  }
239
292
  exports.MulingstreamListenerManager = MulingstreamListenerManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mulingai-npm/redis",
3
- "version": "3.36.1",
3
+ "version": "3.37.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "repository": {