@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.
- package/dist/enums/redis-database.d.ts +5 -5
- package/dist/enums/redis-database.js +9 -9
- package/dist/managers/mulingstream-chunk-manager.d.ts +129 -129
- package/dist/managers/mulingstream-chunk-manager.js +486 -486
- package/dist/managers/mulingstream-listener-manager.d.ts +29 -29
- package/dist/managers/mulingstream-listener-manager.js +169 -169
- package/dist/managers/mulingstream-speaker-manager.d.ts +37 -37
- package/dist/managers/mulingstream-speaker-manager.js +225 -225
- package/dist/redis-client.d.ts +28 -28
- package/dist/redis-client.js +96 -96
- package/dist/utils/finders.d.ts +1 -1
- package/dist/utils/finders.js +18 -18
- package/package.json +34 -34
|
@@ -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;
|