@mulingai-npm/redis 2.12.2 → 2.12.4

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.
@@ -1,486 +1,486 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MulingstreamChunkManager = void 0;
4
- const EXPIRATION = 12 * 60 * 60; // 12 hours in seconds
5
- const ROOM_ARRAY_LENGTH = 50; // keep only the last 5 elements
6
- class MulingstreamChunkManager {
7
- constructor(redisClient) {
8
- this.redisClient = redisClient;
9
- }
10
- getTimeout(audioStart, audioEnd) {
11
- const audioChunkLength = audioEnd - audioStart;
12
- if (audioChunkLength < 1000) {
13
- return 1500;
14
- }
15
- else if (audioChunkLength >= 1000 && audioChunkLength < 3000) {
16
- return 3000;
17
- }
18
- else if (audioChunkLength >= 3000 && audioChunkLength < 5000) {
19
- return 7000;
20
- }
21
- else if (audioChunkLength >= 5000 && audioChunkLength < 8000) {
22
- return 10000;
23
- }
24
- else if (audioChunkLength >= 8000 && audioChunkLength < 12000) {
25
- return 15000;
26
- }
27
- else if (audioChunkLength >= 12000 && audioChunkLength < 20000) {
28
- return 20000;
29
- }
30
- else {
31
- return 30000;
32
- }
33
- }
34
- /**
35
- * Initializes a room in Redis as an empty JSON array.
36
- * If the key [roomId] already exists, we do NOT overwrite it.
37
- * Returns true if room was created, false if room already existed.
38
- */
39
- async initRoom(roomId) {
40
- // Check if the key already exists (JSON.GET)
41
- const isRoomExisting = await this.redisClient.jsonGet(`[${roomId}]`, '.');
42
- if (isRoomExisting !== null) {
43
- return false;
44
- }
45
- // Create an empty array at the root
46
- await this.redisClient.jsonSet(`[${roomId}]`, '.', []);
47
- await this.redisClient.expire(`[${roomId}]`, EXPIRATION);
48
- return true;
49
- }
50
- /**
51
- * Returns all rooms. This is naive if you store many other keys in Redis,
52
- * because we search keys for pattern "[*]".
53
- * Adjust as needed if you have a separate naming prefix.
54
- */
55
- async getRooms() {
56
- // e.g., if we store everything as [foo], [bar], etc., we do:
57
- const rooms = await this.redisClient.keys('\\[*\\]');
58
- // This returns the raw keys, e.g. ['[myRoom]', '[anotherRoom]']
59
- // We might want to strip off the brackets:
60
- return rooms.map((k) => k.replace(/^\[|\]$/g, ''));
61
- }
62
- /**
63
- * Returns the entire array of chunks for the given roomId,
64
- * or null if the room doesn't exist in Redis.
65
- */
66
- async getMulingstreamChunksByRoom(roomId) {
67
- // JSON.GET [roomId] .
68
- const chunks = await this.redisClient.jsonGet(`[${roomId}]`, '.');
69
- if (chunks === null) {
70
- return null;
71
- }
72
- return chunks;
73
- }
74
- async getRoomById(roomId) {
75
- return this.getMulingstreamChunksByRoom(roomId);
76
- }
77
- async addMulingstreamChunk(params) {
78
- var _a;
79
- const { roomId, chunkNumber, language, start, end, duration, isFirst, isLast, theme, sttProviders, targetLanguages, shortCodeTargetLanguages } = params;
80
- /* ---------- build chunk skeleton ---------- */
81
- const audioChunk = {
82
- start,
83
- end,
84
- duration,
85
- isFirst,
86
- isLast,
87
- theme,
88
- processingStart: Date.now()
89
- };
90
- const stt = {};
91
- for (const p of sttProviders) {
92
- stt[p] = { transcription: '', status: 'INIT' };
93
- }
94
- const translation = {};
95
- const tts = {};
96
- for (const lang of shortCodeTargetLanguages) {
97
- translation[lang] = { translation: '', status: 'INIT' };
98
- tts[lang] = { ttsAudioPath: '', status: 'INIT', isEmitted: false, totalCheck: 0 };
99
- }
100
- const newChunk = {
101
- roomId,
102
- chunkNumber,
103
- language,
104
- sttProviders,
105
- targetLanguages,
106
- shortCodeTargetLanguages,
107
- finalTranscription: '',
108
- sttStatus: 'INIT',
109
- createdAt: Date.now(),
110
- audioChunk,
111
- stt,
112
- translation,
113
- tts
114
- };
115
- /* ---------- pull existing room array ---------- */
116
- let chunks = (_a = (await this.redisClient.jsonGet(`[${roomId}]`, '.'))) !== null && _a !== void 0 ? _a : [];
117
- /* ---------- reset the room if this is the first chunk ---------- */
118
- if (chunkNumber === 1) {
119
- chunks = []; // start fresh
120
- }
121
- /* ---------- insert / replace ---------- */
122
- const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
123
- if (idx !== -1) {
124
- chunks[idx] = newChunk;
125
- }
126
- else {
127
- chunks.push(newChunk);
128
- if (chunks.length > ROOM_ARRAY_LENGTH)
129
- chunks.shift();
130
- }
131
- /* ---------- persist ---------- */
132
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
133
- await this.redisClient.expire(`[${roomId}]`, EXPIRATION);
134
- }
135
- /**
136
- * Given roomId and chunkNumber, return the single chunk from the array
137
- * or null if not found.
138
- */
139
- async getMulingstreamChunkById(roomId, chunkNumber) {
140
- // Retrieve the entire array
141
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
142
- if (!chunks)
143
- return null;
144
- // Find by chunkNumber
145
- const chunk = chunks.find((c) => c.chunkNumber === chunkNumber);
146
- return chunk || null;
147
- }
148
- /**
149
- * Update STT fields for a given chunk.
150
- * If transcription or sttStatus is null, skip that field.
151
- */
152
- async updateStt(roomId, chunkNumber, sttProvider, options) {
153
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
154
- if (!chunks)
155
- return false;
156
- // Find the chunk
157
- const chunkIndex = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
158
- if (chunkIndex === -1)
159
- return false;
160
- const chunk = chunks[chunkIndex];
161
- // If this stt provider doesn't exist, ignore
162
- if (!chunk.stt[sttProvider])
163
- return false;
164
- // Update
165
- if (options.transcription != null) {
166
- chunk.stt[sttProvider].transcription = options.transcription;
167
- }
168
- if (options.sttStatus != null) {
169
- chunk.stt[sttProvider].status = options.sttStatus;
170
- }
171
- // Write back
172
- chunks[chunkIndex] = chunk;
173
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
174
- return true;
175
- }
176
- async updateSttObject(roomId, chunkNumber, newStt) {
177
- // Fetch all chunks in the room
178
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
179
- if (!chunks) {
180
- return false;
181
- }
182
- // Locate the target chunk
183
- const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
184
- if (idx === -1) {
185
- return false;
186
- }
187
- // Replace the stt object
188
- chunks[idx].stt = newStt;
189
- // Persist back to Redis
190
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
191
- return true;
192
- }
193
- async discardStt(roomId, chunkNumber) {
194
- // Fetch all chunks in this room
195
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
196
- if (!chunks) {
197
- return false;
198
- }
199
- // Locate the desired chunk
200
- const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
201
- if (idx === -1) {
202
- return false;
203
- }
204
- const chunk = chunks[idx];
205
- // Reset every STT provider entry
206
- for (const provider of Object.keys(chunk.stt)) {
207
- chunk.stt[provider].transcription = '';
208
- chunk.stt[provider].status = 'DISCARDED';
209
- }
210
- // Reset aggregate STT fields
211
- chunk.finalTranscription = '';
212
- chunk.sttStatus = 'DISCARDED';
213
- // Persist back to Redis
214
- chunks[idx] = chunk;
215
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
216
- return true;
217
- }
218
- async updateFinalTranscription(roomId, chunkNumber, options) {
219
- // 1) fetch room’s chunks
220
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
221
- if (!chunks)
222
- return null;
223
- // 2) locate the chunk
224
- const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
225
- if (idx === -1)
226
- return null;
227
- // 3) apply updates only when the key is present
228
- if (options.transcription !== undefined) {
229
- chunks[idx].finalTranscription = options.transcription;
230
- }
231
- if (options.sttStatus !== undefined) {
232
- chunks[idx].sttStatus = options.sttStatus;
233
- }
234
- // 4) persist and return
235
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
236
- return chunks[idx];
237
- }
238
- /**
239
- * Discards all post-STT steps for a given chunk:
240
- * sets all translation[].status & tts[].status to "DISCARDED".
241
- */
242
- async discardPostStt(roomId, chunkNumber) {
243
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
244
- if (!chunks)
245
- return false;
246
- // Find chunk
247
- const chunkIndex = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
248
- if (chunkIndex === -1)
249
- return false;
250
- // Discard translation & TTS statuses
251
- const chunk = chunks[chunkIndex];
252
- for (const lang of Object.keys(chunk.translation)) {
253
- chunk.translation[lang].status = 'DISCARDED';
254
- }
255
- for (const lang of Object.keys(chunk.tts)) {
256
- chunk.tts[lang].status = 'DISCARDED';
257
- }
258
- chunks[chunkIndex] = chunk;
259
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
260
- return true;
261
- }
262
- /**
263
- * Discards a specific language in both translation and tts for a chunk.
264
- */
265
- async discardLanguage(roomId, chunkNumber, language) {
266
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
267
- if (!chunks)
268
- return false;
269
- const chunkIndex = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
270
- if (chunkIndex === -1)
271
- return false;
272
- const chunk = chunks[chunkIndex];
273
- if (chunk.translation[language]) {
274
- chunk.translation[language].status = 'DISCARDED';
275
- }
276
- if (chunk.tts[language]) {
277
- chunk.tts[language].status = 'DISCARDED';
278
- }
279
- chunks[chunkIndex] = chunk;
280
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
281
- return true;
282
- }
283
- async discardLanguages(roomId, chunkNumber, options) {
284
- // 1) fetch room’s chunks
285
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
286
- if (!chunks)
287
- return null;
288
- // 2) locate the chunk
289
- const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
290
- if (idx === -1)
291
- return null;
292
- const chunk = chunks[idx];
293
- // 3) discard translation languages still in INIT
294
- if (options.translation) {
295
- for (const lang of options.translation) {
296
- const entry = chunk.translation[lang];
297
- if (entry && entry.status === 'INIT') {
298
- entry.status = 'DISCARDED';
299
- }
300
- }
301
- }
302
- // 4) discard TTS languages still in INIT
303
- if (options.tts) {
304
- for (const lang of options.tts) {
305
- const entry = chunk.tts[lang];
306
- if (entry && entry.status === 'INIT') {
307
- entry.status = 'DISCARDED';
308
- }
309
- }
310
- }
311
- // 5) persist and return
312
- chunks[idx] = chunk;
313
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
314
- return chunk;
315
- }
316
- async updateTranslation(roomId, chunkNumber, language, options) {
317
- // 1) fetch the room’s chunks
318
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
319
- if (!chunks)
320
- return null;
321
- // 2) locate the chunk
322
- const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
323
- if (idx === -1)
324
- return null;
325
- const chunk = chunks[idx];
326
- // 3) ensure the requested language exists
327
- if (!chunk.translation[language])
328
- return null;
329
- // 4) apply updates only for provided keys
330
- if (options.translation !== undefined) {
331
- chunk.translation[language].translation = options.translation;
332
- }
333
- if (options.status !== undefined) {
334
- chunk.translation[language].status = options.status;
335
- }
336
- // 5) persist and return
337
- chunks[idx] = chunk;
338
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
339
- return chunk;
340
- }
341
- async updateTranslationInBulk(roomId, chunkNumber, translations, status = 'READY') {
342
- // 1) fetch the room array
343
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
344
- if (!chunks) {
345
- return null;
346
- }
347
- // 2) locate the target chunk
348
- const chunkIdx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
349
- if (chunkIdx === -1) {
350
- return null;
351
- }
352
- const chunk = chunks[chunkIdx];
353
- // 3) apply updates only for the languages provided
354
- for (const [lang, translationText] of Object.entries(translations)) {
355
- if (!chunk.translation[lang]) {
356
- continue;
357
- }
358
- chunk.translation[lang].translation = translationText;
359
- chunk.translation[lang].status = status;
360
- }
361
- // 4) write the full room array back to Redis
362
- chunks[chunkIdx] = chunk;
363
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
364
- return chunk;
365
- }
366
- async updateTts(roomId, chunkNumber, language, options) {
367
- // 1) fetch the room’s chunks
368
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
369
- if (!chunks)
370
- return null;
371
- // 2) locate the chunk
372
- const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
373
- if (idx === -1)
374
- return null;
375
- const chunk = chunks[idx];
376
- // 3) ensure the language exists in the TTS map
377
- if (!chunk.tts[language])
378
- return null;
379
- // 4) apply only the provided keys
380
- if (options.ttsAudioPath !== undefined) {
381
- chunk.tts[language].ttsAudioPath = options.ttsAudioPath;
382
- }
383
- if (options.status !== undefined) {
384
- chunk.tts[language].status = options.status;
385
- }
386
- if (options.isEmitted !== undefined) {
387
- chunk.tts[language].isEmitted = options.isEmitted;
388
- }
389
- if (options.totalCheck !== undefined) {
390
- chunk.tts[language].totalCheck = options.totalCheck;
391
- }
392
- // 5) persist and return the updated chunk
393
- chunks[idx] = chunk;
394
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
395
- return chunk;
396
- }
397
- async increaseTotalCheck(roomId, chunkNumber, language) {
398
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
399
- if (!chunks) {
400
- return null;
401
- }
402
- const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
403
- if (idx === -1) {
404
- return null;
405
- }
406
- const chunk = chunks[idx];
407
- if (!chunk.tts[language]) {
408
- return null;
409
- }
410
- chunk.tts[language].totalCheck += 1;
411
- chunks[idx] = chunk;
412
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
413
- return chunk;
414
- }
415
- async areTranslationsProcessed(roomId, chunkNumber) {
416
- const chunk = await this.getMulingstreamChunkById(roomId, chunkNumber);
417
- if (!chunk) {
418
- return false;
419
- }
420
- return Object.values(chunk.translation).every((t) => t.status !== 'INIT');
421
- }
422
- async getAllReadyTts(roomId, language) {
423
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
424
- if (!(chunks === null || chunks === void 0 ? void 0 : chunks.length))
425
- return [];
426
- // chunks are received in consecutive order already, no need for sorting for now (maybe in the future we have more speaker in a room, so we may need sorting)
427
- // chunks.sort((a, b) => a.chunkNumber - b.chunkNumber);
428
- /* ---- isolate the tail where the language exists ---- */
429
- let lastLangIdx = -1;
430
- for (let i = chunks.length - 1; i >= 0; i--) {
431
- if (chunks[i].tts[language]) {
432
- lastLangIdx = i;
433
- break;
434
- }
435
- }
436
- if (lastLangIdx === -1)
437
- return [];
438
- let firstLangIdx = lastLangIdx;
439
- for (let i = lastLangIdx - 1; i >= 0; i--) {
440
- if (!chunks[i].tts[language])
441
- break;
442
- firstLangIdx = i;
443
- }
444
- const window = chunks.slice(firstLangIdx, lastLangIdx + 1);
445
- /* ---- find last USED / DISCARDED in that window ---- */
446
- let lastDone = -1;
447
- for (let i = window.length - 1; i >= 0; i--) {
448
- const st = window[i].tts[language].status;
449
- if (st === 'USED' || st === 'DISCARDED') {
450
- lastDone = i;
451
- break;
452
- }
453
- }
454
- const ready = [];
455
- let blocked = false;
456
- const now = Date.now();
457
- for (let i = lastDone + 1; i < window.length; i++) {
458
- const entry = window[i].tts[language];
459
- const audioChunk = window[i].audioChunk;
460
- if (entry.status === 'INIT') {
461
- const timeout = this.getTimeout(audioChunk.start, audioChunk.end);
462
- console.log('AUDIO LENGTH: ', audioChunk.end - audioChunk.start, ' TIMEOUT BASED ON AUDIO LENGTH: ', timeout, ' SINCE CREATED UNTIL NOW: ', now - window[i].createdAt);
463
- if (now - window[i].createdAt > timeout) {
464
- entry.status = 'DISCARDED';
465
- }
466
- else {
467
- blocked = true;
468
- }
469
- continue;
470
- }
471
- if (entry.status === 'READY') {
472
- if (blocked)
473
- break; // earlier INIT still pending
474
- ready.push(window[i]);
475
- continue;
476
- }
477
- break; // unexpected USED / DISCARDED
478
- }
479
- // persist any DISCARDED changes we made
480
- if (ready.length === 0 || blocked) {
481
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
482
- }
483
- return ready;
484
- }
485
- }
486
- exports.MulingstreamChunkManager = MulingstreamChunkManager;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MulingstreamChunkManager = void 0;
4
+ const EXPIRATION = 12 * 60 * 60; // 12 hours in seconds
5
+ const ROOM_ARRAY_LENGTH = 50; // keep only the last 5 elements
6
+ class MulingstreamChunkManager {
7
+ constructor(redisClient) {
8
+ this.redisClient = redisClient;
9
+ }
10
+ getTimeout(audioStart, audioEnd) {
11
+ const audioChunkLength = audioEnd - audioStart;
12
+ if (audioChunkLength < 1000) {
13
+ return 1500;
14
+ }
15
+ else if (audioChunkLength >= 1000 && audioChunkLength < 3000) {
16
+ return 3000;
17
+ }
18
+ else if (audioChunkLength >= 3000 && audioChunkLength < 5000) {
19
+ return 7000;
20
+ }
21
+ else if (audioChunkLength >= 5000 && audioChunkLength < 8000) {
22
+ return 10000;
23
+ }
24
+ else if (audioChunkLength >= 8000 && audioChunkLength < 12000) {
25
+ return 15000;
26
+ }
27
+ else if (audioChunkLength >= 12000 && audioChunkLength < 20000) {
28
+ return 20000;
29
+ }
30
+ else {
31
+ return 30000;
32
+ }
33
+ }
34
+ /**
35
+ * Initializes a room in Redis as an empty JSON array.
36
+ * If the key [roomId] already exists, we do NOT overwrite it.
37
+ * Returns true if room was created, false if room already existed.
38
+ */
39
+ async initRoom(roomId) {
40
+ // Check if the key already exists (JSON.GET)
41
+ const isRoomExisting = await this.redisClient.jsonGet(`[${roomId}]`, '.');
42
+ if (isRoomExisting !== null) {
43
+ return false;
44
+ }
45
+ // Create an empty array at the root
46
+ await this.redisClient.jsonSet(`[${roomId}]`, '.', []);
47
+ await this.redisClient.expire(`[${roomId}]`, EXPIRATION);
48
+ return true;
49
+ }
50
+ /**
51
+ * Returns all rooms. This is naive if you store many other keys in Redis,
52
+ * because we search keys for pattern "[*]".
53
+ * Adjust as needed if you have a separate naming prefix.
54
+ */
55
+ async getRooms() {
56
+ // e.g., if we store everything as [foo], [bar], etc., we do:
57
+ const rooms = await this.redisClient.keys('\\[*\\]');
58
+ // This returns the raw keys, e.g. ['[myRoom]', '[anotherRoom]']
59
+ // We might want to strip off the brackets:
60
+ return rooms.map((k) => k.replace(/^\[|\]$/g, ''));
61
+ }
62
+ /**
63
+ * Returns the entire array of chunks for the given roomId,
64
+ * or null if the room doesn't exist in Redis.
65
+ */
66
+ async getMulingstreamChunksByRoom(roomId) {
67
+ // JSON.GET [roomId] .
68
+ const chunks = await this.redisClient.jsonGet(`[${roomId}]`, '.');
69
+ if (chunks === null) {
70
+ return null;
71
+ }
72
+ return chunks;
73
+ }
74
+ async getRoomById(roomId) {
75
+ return this.getMulingstreamChunksByRoom(roomId);
76
+ }
77
+ async addMulingstreamChunk(params) {
78
+ var _a;
79
+ const { roomId, chunkNumber, language, start, end, duration, isFirst, isLast, theme, sttProviders, targetLanguages, shortCodeTargetLanguages } = params;
80
+ /* ---------- build chunk skeleton ---------- */
81
+ const audioChunk = {
82
+ start,
83
+ end,
84
+ duration,
85
+ isFirst,
86
+ isLast,
87
+ theme,
88
+ processingStart: Date.now()
89
+ };
90
+ const stt = {};
91
+ for (const p of sttProviders) {
92
+ stt[p] = { transcription: '', status: 'INIT' };
93
+ }
94
+ const translation = {};
95
+ const tts = {};
96
+ for (const lang of shortCodeTargetLanguages) {
97
+ translation[lang] = { translation: '', status: 'INIT' };
98
+ tts[lang] = { ttsAudioPath: '', status: 'INIT', isEmitted: false, totalCheck: 0 };
99
+ }
100
+ const newChunk = {
101
+ roomId,
102
+ chunkNumber,
103
+ language,
104
+ sttProviders,
105
+ targetLanguages,
106
+ shortCodeTargetLanguages,
107
+ finalTranscription: '',
108
+ sttStatus: 'INIT',
109
+ createdAt: Date.now(),
110
+ audioChunk,
111
+ stt,
112
+ translation,
113
+ tts
114
+ };
115
+ /* ---------- pull existing room array ---------- */
116
+ let chunks = (_a = (await this.redisClient.jsonGet(`[${roomId}]`, '.'))) !== null && _a !== void 0 ? _a : [];
117
+ /* ---------- reset the room if this is the first chunk ---------- */
118
+ if (chunkNumber === 1) {
119
+ chunks = []; // start fresh
120
+ }
121
+ /* ---------- insert / replace ---------- */
122
+ const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
123
+ if (idx !== -1) {
124
+ chunks[idx] = newChunk;
125
+ }
126
+ else {
127
+ chunks.push(newChunk);
128
+ if (chunks.length > ROOM_ARRAY_LENGTH)
129
+ chunks.shift();
130
+ }
131
+ /* ---------- persist ---------- */
132
+ await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
133
+ await this.redisClient.expire(`[${roomId}]`, EXPIRATION);
134
+ }
135
+ /**
136
+ * Given roomId and chunkNumber, return the single chunk from the array
137
+ * or null if not found.
138
+ */
139
+ async getMulingstreamChunkById(roomId, chunkNumber) {
140
+ // Retrieve the entire array
141
+ const chunks = await this.getMulingstreamChunksByRoom(roomId);
142
+ if (!chunks)
143
+ return null;
144
+ // Find by chunkNumber
145
+ const chunk = chunks.find((c) => c.chunkNumber === chunkNumber);
146
+ return chunk || null;
147
+ }
148
+ /**
149
+ * Update STT fields for a given chunk.
150
+ * If transcription or sttStatus is null, skip that field.
151
+ */
152
+ async updateStt(roomId, chunkNumber, sttProvider, options) {
153
+ const chunks = await this.getMulingstreamChunksByRoom(roomId);
154
+ if (!chunks)
155
+ return false;
156
+ // Find the chunk
157
+ const chunkIndex = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
158
+ if (chunkIndex === -1)
159
+ return false;
160
+ const chunk = chunks[chunkIndex];
161
+ // If this stt provider doesn't exist, ignore
162
+ if (!chunk.stt[sttProvider])
163
+ return false;
164
+ // Update
165
+ if (options.transcription != null) {
166
+ chunk.stt[sttProvider].transcription = options.transcription;
167
+ }
168
+ if (options.sttStatus != null) {
169
+ chunk.stt[sttProvider].status = options.sttStatus;
170
+ }
171
+ // Write back
172
+ chunks[chunkIndex] = chunk;
173
+ await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
174
+ return true;
175
+ }
176
+ async updateSttObject(roomId, chunkNumber, newStt) {
177
+ // Fetch all chunks in the room
178
+ const chunks = await this.getMulingstreamChunksByRoom(roomId);
179
+ if (!chunks) {
180
+ return false;
181
+ }
182
+ // Locate the target chunk
183
+ const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
184
+ if (idx === -1) {
185
+ return false;
186
+ }
187
+ // Replace the stt object
188
+ chunks[idx].stt = newStt;
189
+ // Persist back to Redis
190
+ await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
191
+ return true;
192
+ }
193
+ async discardStt(roomId, chunkNumber) {
194
+ // Fetch all chunks in this room
195
+ const chunks = await this.getMulingstreamChunksByRoom(roomId);
196
+ if (!chunks) {
197
+ return false;
198
+ }
199
+ // Locate the desired chunk
200
+ const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
201
+ if (idx === -1) {
202
+ return false;
203
+ }
204
+ const chunk = chunks[idx];
205
+ // Reset every STT provider entry
206
+ for (const provider of Object.keys(chunk.stt)) {
207
+ chunk.stt[provider].transcription = '';
208
+ chunk.stt[provider].status = 'DISCARDED';
209
+ }
210
+ // Reset aggregate STT fields
211
+ chunk.finalTranscription = '';
212
+ chunk.sttStatus = 'DISCARDED';
213
+ // Persist back to Redis
214
+ chunks[idx] = chunk;
215
+ await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
216
+ return true;
217
+ }
218
+ async updateFinalTranscription(roomId, chunkNumber, options) {
219
+ // 1) fetch room’s chunks
220
+ const chunks = await this.getMulingstreamChunksByRoom(roomId);
221
+ if (!chunks)
222
+ return null;
223
+ // 2) locate the chunk
224
+ const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
225
+ if (idx === -1)
226
+ return null;
227
+ // 3) apply updates only when the key is present
228
+ if (options.transcription !== undefined) {
229
+ chunks[idx].finalTranscription = options.transcription;
230
+ }
231
+ if (options.sttStatus !== undefined) {
232
+ chunks[idx].sttStatus = options.sttStatus;
233
+ }
234
+ // 4) persist and return
235
+ await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
236
+ return chunks[idx];
237
+ }
238
+ /**
239
+ * Discards all post-STT steps for a given chunk:
240
+ * sets all translation[].status & tts[].status to "DISCARDED".
241
+ */
242
+ async discardPostStt(roomId, chunkNumber) {
243
+ const chunks = await this.getMulingstreamChunksByRoom(roomId);
244
+ if (!chunks)
245
+ return false;
246
+ // Find chunk
247
+ const chunkIndex = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
248
+ if (chunkIndex === -1)
249
+ return false;
250
+ // Discard translation & TTS statuses
251
+ const chunk = chunks[chunkIndex];
252
+ for (const lang of Object.keys(chunk.translation)) {
253
+ chunk.translation[lang].status = 'DISCARDED';
254
+ }
255
+ for (const lang of Object.keys(chunk.tts)) {
256
+ chunk.tts[lang].status = 'DISCARDED';
257
+ }
258
+ chunks[chunkIndex] = chunk;
259
+ await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
260
+ return true;
261
+ }
262
+ /**
263
+ * Discards a specific language in both translation and tts for a chunk.
264
+ */
265
+ async discardLanguage(roomId, chunkNumber, language) {
266
+ const chunks = await this.getMulingstreamChunksByRoom(roomId);
267
+ if (!chunks)
268
+ return false;
269
+ const chunkIndex = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
270
+ if (chunkIndex === -1)
271
+ return false;
272
+ const chunk = chunks[chunkIndex];
273
+ if (chunk.translation[language]) {
274
+ chunk.translation[language].status = 'DISCARDED';
275
+ }
276
+ if (chunk.tts[language]) {
277
+ chunk.tts[language].status = 'DISCARDED';
278
+ }
279
+ chunks[chunkIndex] = chunk;
280
+ await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
281
+ return true;
282
+ }
283
+ async discardLanguages(roomId, chunkNumber, options) {
284
+ // 1) fetch room’s chunks
285
+ const chunks = await this.getMulingstreamChunksByRoom(roomId);
286
+ if (!chunks)
287
+ return null;
288
+ // 2) locate the chunk
289
+ const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
290
+ if (idx === -1)
291
+ return null;
292
+ const chunk = chunks[idx];
293
+ // 3) discard translation languages still in INIT
294
+ if (options.translation) {
295
+ for (const lang of options.translation) {
296
+ const entry = chunk.translation[lang];
297
+ if (entry && entry.status === 'INIT') {
298
+ entry.status = 'DISCARDED';
299
+ }
300
+ }
301
+ }
302
+ // 4) discard TTS languages still in INIT
303
+ if (options.tts) {
304
+ for (const lang of options.tts) {
305
+ const entry = chunk.tts[lang];
306
+ if (entry && entry.status === 'INIT') {
307
+ entry.status = 'DISCARDED';
308
+ }
309
+ }
310
+ }
311
+ // 5) persist and return
312
+ chunks[idx] = chunk;
313
+ await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
314
+ return chunk;
315
+ }
316
+ async updateTranslation(roomId, chunkNumber, language, options) {
317
+ // 1) fetch the room’s chunks
318
+ const chunks = await this.getMulingstreamChunksByRoom(roomId);
319
+ if (!chunks)
320
+ return null;
321
+ // 2) locate the chunk
322
+ const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
323
+ if (idx === -1)
324
+ return null;
325
+ const chunk = chunks[idx];
326
+ // 3) ensure the requested language exists
327
+ if (!chunk.translation[language])
328
+ return null;
329
+ // 4) apply updates only for provided keys
330
+ if (options.translation !== undefined) {
331
+ chunk.translation[language].translation = options.translation;
332
+ }
333
+ if (options.status !== undefined) {
334
+ chunk.translation[language].status = options.status;
335
+ }
336
+ // 5) persist and return
337
+ chunks[idx] = chunk;
338
+ await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
339
+ return chunk;
340
+ }
341
+ async updateTranslationInBulk(roomId, chunkNumber, translations, status = 'READY') {
342
+ // 1) fetch the room array
343
+ const chunks = await this.getMulingstreamChunksByRoom(roomId);
344
+ if (!chunks) {
345
+ return null;
346
+ }
347
+ // 2) locate the target chunk
348
+ const chunkIdx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
349
+ if (chunkIdx === -1) {
350
+ return null;
351
+ }
352
+ const chunk = chunks[chunkIdx];
353
+ // 3) apply updates only for the languages provided
354
+ for (const [lang, translationText] of Object.entries(translations)) {
355
+ if (!chunk.translation[lang]) {
356
+ continue;
357
+ }
358
+ chunk.translation[lang].translation = translationText;
359
+ chunk.translation[lang].status = status;
360
+ }
361
+ // 4) write the full room array back to Redis
362
+ chunks[chunkIdx] = chunk;
363
+ await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
364
+ return chunk;
365
+ }
366
+ async updateTts(roomId, chunkNumber, language, options) {
367
+ // 1) fetch the room’s chunks
368
+ const chunks = await this.getMulingstreamChunksByRoom(roomId);
369
+ if (!chunks)
370
+ return null;
371
+ // 2) locate the chunk
372
+ const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
373
+ if (idx === -1)
374
+ return null;
375
+ const chunk = chunks[idx];
376
+ // 3) ensure the language exists in the TTS map
377
+ if (!chunk.tts[language])
378
+ return null;
379
+ // 4) apply only the provided keys
380
+ if (options.ttsAudioPath !== undefined) {
381
+ chunk.tts[language].ttsAudioPath = options.ttsAudioPath;
382
+ }
383
+ if (options.status !== undefined) {
384
+ chunk.tts[language].status = options.status;
385
+ }
386
+ if (options.isEmitted !== undefined) {
387
+ chunk.tts[language].isEmitted = options.isEmitted;
388
+ }
389
+ if (options.totalCheck !== undefined) {
390
+ chunk.tts[language].totalCheck = options.totalCheck;
391
+ }
392
+ // 5) persist and return the updated chunk
393
+ chunks[idx] = chunk;
394
+ await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
395
+ return chunk;
396
+ }
397
+ async increaseTotalCheck(roomId, chunkNumber, language) {
398
+ const chunks = await this.getMulingstreamChunksByRoom(roomId);
399
+ if (!chunks) {
400
+ return null;
401
+ }
402
+ const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
403
+ if (idx === -1) {
404
+ return null;
405
+ }
406
+ const chunk = chunks[idx];
407
+ if (!chunk.tts[language]) {
408
+ return null;
409
+ }
410
+ chunk.tts[language].totalCheck += 1;
411
+ chunks[idx] = chunk;
412
+ await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
413
+ return chunk;
414
+ }
415
+ async areTranslationsProcessed(roomId, chunkNumber) {
416
+ const chunk = await this.getMulingstreamChunkById(roomId, chunkNumber);
417
+ if (!chunk) {
418
+ return false;
419
+ }
420
+ return Object.values(chunk.translation).every((t) => t.status !== 'INIT');
421
+ }
422
+ async getAllReadyTts(roomId, language) {
423
+ const chunks = await this.getMulingstreamChunksByRoom(roomId);
424
+ if (!(chunks === null || chunks === void 0 ? void 0 : chunks.length))
425
+ return [];
426
+ // chunks are received in consecutive order already, no need for sorting for now (maybe in the future we have more speaker in a room, so we may need sorting)
427
+ // chunks.sort((a, b) => a.chunkNumber - b.chunkNumber);
428
+ /* ---- isolate the tail where the language exists ---- */
429
+ let lastLangIdx = -1;
430
+ for (let i = chunks.length - 1; i >= 0; i--) {
431
+ if (chunks[i].tts[language]) {
432
+ lastLangIdx = i;
433
+ break;
434
+ }
435
+ }
436
+ if (lastLangIdx === -1)
437
+ return [];
438
+ let firstLangIdx = lastLangIdx;
439
+ for (let i = lastLangIdx - 1; i >= 0; i--) {
440
+ if (!chunks[i].tts[language])
441
+ break;
442
+ firstLangIdx = i;
443
+ }
444
+ const window = chunks.slice(firstLangIdx, lastLangIdx + 1);
445
+ /* ---- find last USED / DISCARDED in that window ---- */
446
+ let lastDone = -1;
447
+ for (let i = window.length - 1; i >= 0; i--) {
448
+ const st = window[i].tts[language].status;
449
+ if (st === 'USED' || st === 'DISCARDED') {
450
+ lastDone = i;
451
+ break;
452
+ }
453
+ }
454
+ const ready = [];
455
+ let blocked = false;
456
+ const now = Date.now();
457
+ for (let i = lastDone + 1; i < window.length; i++) {
458
+ const entry = window[i].tts[language];
459
+ const audioChunk = window[i].audioChunk;
460
+ if (entry.status === 'INIT') {
461
+ const timeout = this.getTimeout(audioChunk.start, audioChunk.end);
462
+ console.log('AUDIO LENGTH: ', audioChunk.end - audioChunk.start, ' TIMEOUT BASED ON AUDIO LENGTH: ', timeout, ' SINCE CREATED UNTIL NOW: ', now - window[i].createdAt);
463
+ if (now - window[i].createdAt > timeout) {
464
+ entry.status = 'DISCARDED';
465
+ }
466
+ else {
467
+ blocked = true;
468
+ }
469
+ continue;
470
+ }
471
+ if (entry.status === 'READY') {
472
+ if (blocked)
473
+ break; // earlier INIT still pending
474
+ ready.push(window[i]);
475
+ continue;
476
+ }
477
+ break; // unexpected USED / DISCARDED
478
+ }
479
+ // persist any DISCARDED changes we made
480
+ if (ready.length === 0 || blocked) {
481
+ await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
482
+ }
483
+ return ready;
484
+ }
485
+ }
486
+ exports.MulingstreamChunkManager = MulingstreamChunkManager;