@mulingai-npm/redis 3.40.28 → 3.40.29
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.
|
@@ -122,13 +122,27 @@ export declare class MulingstreamChunkManager {
|
|
|
122
122
|
}): Promise<MulingstreamChunkData>;
|
|
123
123
|
private getChunkId;
|
|
124
124
|
getMulingstreamChunkById(roomId: string, n: number): Promise<MulingstreamChunkData>;
|
|
125
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Partial HSET — touches only `finalTranscription`. See setBufferStatus for the
|
|
127
|
+
* race rationale (whole-chunk read-modify-write would clobber concurrent updates).
|
|
128
|
+
*/
|
|
126
129
|
updateFinalTranscription(roomId: string, n: number, transcription: string): Promise<MulingstreamChunkData | null>;
|
|
127
130
|
/**
|
|
128
131
|
* Update chunk with LLM-sanitized transcription from SmartTranslate.
|
|
129
132
|
* If the sanitized text differs from the original, sets transcriptionSource to 'llm'
|
|
130
133
|
* and updates finalTranscription to the sanitized version.
|
|
131
134
|
* Returns the chunk (caller can compare original vs sanitized to decide on TRANSCRIPTION_CORRECTED).
|
|
135
|
+
*
|
|
136
|
+
* IMPORTANT — partial HSET, NOT withChunk(). Reasoning identical to setBufferStatus:
|
|
137
|
+
* handleTtsService runs in parallel and writes `tts.<lang>.status = READY`. The
|
|
138
|
+
* read-modify-write of withChunk would clobber that READY back to INIT (we read
|
|
139
|
+
* tts before TTS callback fires, then write the entire chunk back later). Result:
|
|
140
|
+
* emitChunk's `if (status !== READY) return;` drops the audio publish, listeners
|
|
141
|
+
* see text but never get the audio URL.
|
|
142
|
+
*
|
|
143
|
+
* This bug surfaced 2026-05-01: same root cause as the earlier setBufferStatus
|
|
144
|
+
* race; updateLlmTranscription wasn't fixed at the same time so it kept producing
|
|
145
|
+
* silent audio for any chunk where wasSanitized=true (i.e. every LLM-routed chunk).
|
|
132
146
|
*/
|
|
133
147
|
updateLlmTranscription(roomId: string, n: number, opts: {
|
|
134
148
|
llmTranscription: string;
|
|
@@ -139,16 +153,37 @@ export declare class MulingstreamChunkManager {
|
|
|
139
153
|
chunk: MulingstreamChunkData | null;
|
|
140
154
|
wasChanged: boolean;
|
|
141
155
|
}>;
|
|
156
|
+
/**
|
|
157
|
+
* Partial HSET — reads only `translation` and `tts` fields, modifies in place,
|
|
158
|
+
* writes back only those two. See setBufferStatus for the race rationale.
|
|
159
|
+
*/
|
|
142
160
|
discardLanguage(roomId: string, n: number, lang: string): Promise<MulingstreamChunkData | null>;
|
|
161
|
+
/**
|
|
162
|
+
* Partial HSET — same race-safe pattern as discardLanguage. Only writes the
|
|
163
|
+
* fields actually mutated by the caller (translation and/or tts).
|
|
164
|
+
*/
|
|
143
165
|
discardLanguages(roomId: string, n: number, opt: {
|
|
144
166
|
translation?: string[];
|
|
145
167
|
tts?: string[];
|
|
146
168
|
}): Promise<MulingstreamChunkData | null>;
|
|
169
|
+
/**
|
|
170
|
+
* Partial HSET — only `translation` is read and rewritten. Concurrent updateTts
|
|
171
|
+
* calls won't be clobbered.
|
|
172
|
+
*/
|
|
147
173
|
updateTranslation(roomId: string, n: number, lang: string, opt: {
|
|
148
174
|
translation?: string;
|
|
149
175
|
status?: StepStatus;
|
|
150
176
|
}): Promise<MulingstreamChunkData | null>;
|
|
177
|
+
/**
|
|
178
|
+
* Partial HSET — only `translation` is read and rewritten. This is the hot path
|
|
179
|
+
* that races with handleTtsService's updateTts and was the root cause of the
|
|
180
|
+
* "audio LISTENER_FEED never arrives" bug fixed 2026-05-01.
|
|
181
|
+
*/
|
|
151
182
|
updateTranslationInBulk(roomId: string, n: number, dict: Record<string, string>, status?: StepStatus, hintsPerLanguage?: Record<string, TranslationHint[]>): Promise<MulingstreamChunkData | null>;
|
|
183
|
+
/**
|
|
184
|
+
* Partial HSET — only `tts` is read and rewritten. Same race-rationale as
|
|
185
|
+
* updateTranslationInBulk: avoids clobbering concurrent translation writes.
|
|
186
|
+
*/
|
|
152
187
|
updateTts(roomId: string, n: number, lang: string, opt: {
|
|
153
188
|
ttsAudioPath?: string;
|
|
154
189
|
status?: StepStatus;
|
|
@@ -178,11 +213,13 @@ export declare class MulingstreamChunkManager {
|
|
|
178
213
|
*/
|
|
179
214
|
isChunkComplete(roomId: string, n: number): Promise<boolean>;
|
|
180
215
|
/**
|
|
181
|
-
* Mark a chunk as complete (all TTS finished for all target languages)
|
|
216
|
+
* Mark a chunk as complete (all TTS finished for all target languages).
|
|
217
|
+
* Partial HSET — touches only `isComplete`.
|
|
182
218
|
*/
|
|
183
219
|
markChunkComplete(roomId: string, n: number): Promise<MulingstreamChunkData | null>;
|
|
184
220
|
/**
|
|
185
|
-
* Mark a chunk as saved to database
|
|
221
|
+
* Mark a chunk as saved to database.
|
|
222
|
+
* Partial HSET — touches only `isSaved`.
|
|
186
223
|
*/
|
|
187
224
|
markChunkSaved(roomId: string, n: number): Promise<MulingstreamChunkData | null>;
|
|
188
225
|
/**
|
|
@@ -215,143 +215,231 @@ class MulingstreamChunkManager {
|
|
|
215
215
|
const raw = await this.redisClient.hgetall(this.chunkHashKey(cid));
|
|
216
216
|
return raw.chunkId ? this.hashToChunk(raw) : null;
|
|
217
217
|
}
|
|
218
|
-
|
|
218
|
+
// NOTE — `withChunk` was removed 2026-05-01. Its read-modify-write pattern
|
|
219
|
+
// raced with concurrent updates to other fields and clobbered them on writeback.
|
|
220
|
+
// All mutators now use partial HSETs that read and write only the fields they own.
|
|
221
|
+
// If you need a new mutator, follow the pattern of `updateTts` / `updateTranslationInBulk`.
|
|
222
|
+
/**
|
|
223
|
+
* Partial HSET — touches only `finalTranscription`. See setBufferStatus for the
|
|
224
|
+
* race rationale (whole-chunk read-modify-write would clobber concurrent updates).
|
|
225
|
+
*/
|
|
226
|
+
async updateFinalTranscription(roomId, n, transcription) {
|
|
219
227
|
const cid = await this.getChunkId(roomId, n);
|
|
220
228
|
if (!cid)
|
|
221
229
|
return null;
|
|
222
230
|
const key = this.chunkHashKey(cid);
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const p = this.redisClient.pipeline();
|
|
229
|
-
const updateHash = {
|
|
230
|
-
finalTranscription: chunk.finalTranscription,
|
|
231
|
-
translation: this.serialize(chunk.translation),
|
|
232
|
-
tts: this.serialize(chunk.tts),
|
|
233
|
-
streamingChunk: this.serialize(chunk.streamingChunk),
|
|
234
|
-
isComplete: String(chunk.isComplete),
|
|
235
|
-
isSaved: String(chunk.isSaved),
|
|
236
|
-
transcriptionSource: chunk.transcriptionSource || 'stt'
|
|
237
|
-
};
|
|
238
|
-
// SmartTranslate fields (only set if populated)
|
|
239
|
-
if (chunk.llmTranscription !== undefined)
|
|
240
|
-
updateHash.llmTranscription = chunk.llmTranscription;
|
|
241
|
-
if (chunk.llmWords !== undefined)
|
|
242
|
-
updateHash.llmWords = this.serialize(chunk.llmWords);
|
|
243
|
-
if (chunk.routeUsed !== undefined)
|
|
244
|
-
updateHash.routeUsed = chunk.routeUsed;
|
|
245
|
-
if (chunk.sttHistory !== undefined)
|
|
246
|
-
updateHash.sttHistory = this.serialize(chunk.sttHistory);
|
|
247
|
-
// SmartTranslate buffering (Section 22)
|
|
248
|
-
if (chunk.bufferStatus !== undefined)
|
|
249
|
-
updateHash.bufferStatus = chunk.bufferStatus;
|
|
250
|
-
if (chunk.pendingText !== undefined)
|
|
251
|
-
updateHash.pendingText = chunk.pendingText;
|
|
252
|
-
if (chunk.consumedChunkNumbers !== undefined)
|
|
253
|
-
updateHash.consumedChunkNumbers = this.serialize(chunk.consumedChunkNumbers);
|
|
254
|
-
p.hset(key, updateHash);
|
|
255
|
-
p.expire(key, EXPIRATION);
|
|
256
|
-
await p.exec();
|
|
257
|
-
return chunk;
|
|
258
|
-
}
|
|
259
|
-
async updateFinalTranscription(roomId, n, transcription) {
|
|
260
|
-
return this.withChunk(roomId, n, (c) => {
|
|
261
|
-
c.finalTranscription = transcription;
|
|
262
|
-
});
|
|
231
|
+
const pipe = this.redisClient.pipeline();
|
|
232
|
+
pipe.hset(key, { finalTranscription: transcription });
|
|
233
|
+
pipe.expire(key, EXPIRATION);
|
|
234
|
+
await pipe.exec();
|
|
235
|
+
return this.getMulingstreamChunkById(roomId, n);
|
|
263
236
|
}
|
|
264
237
|
/**
|
|
265
238
|
* Update chunk with LLM-sanitized transcription from SmartTranslate.
|
|
266
239
|
* If the sanitized text differs from the original, sets transcriptionSource to 'llm'
|
|
267
240
|
* and updates finalTranscription to the sanitized version.
|
|
268
241
|
* Returns the chunk (caller can compare original vs sanitized to decide on TRANSCRIPTION_CORRECTED).
|
|
242
|
+
*
|
|
243
|
+
* IMPORTANT — partial HSET, NOT withChunk(). Reasoning identical to setBufferStatus:
|
|
244
|
+
* handleTtsService runs in parallel and writes `tts.<lang>.status = READY`. The
|
|
245
|
+
* read-modify-write of withChunk would clobber that READY back to INIT (we read
|
|
246
|
+
* tts before TTS callback fires, then write the entire chunk back later). Result:
|
|
247
|
+
* emitChunk's `if (status !== READY) return;` drops the audio publish, listeners
|
|
248
|
+
* see text but never get the audio URL.
|
|
249
|
+
*
|
|
250
|
+
* This bug surfaced 2026-05-01: same root cause as the earlier setBufferStatus
|
|
251
|
+
* race; updateLlmTranscription wasn't fixed at the same time so it kept producing
|
|
252
|
+
* silent audio for any chunk where wasSanitized=true (i.e. every LLM-routed chunk).
|
|
269
253
|
*/
|
|
270
254
|
async updateLlmTranscription(roomId, n, opts) {
|
|
255
|
+
const cid = await this.getChunkId(roomId, n);
|
|
256
|
+
if (!cid)
|
|
257
|
+
return { chunk: null, wasChanged: false };
|
|
258
|
+
const key = this.chunkHashKey(cid);
|
|
259
|
+
// Read just the fields we need to compute the diff/append, NOT the whole chunk.
|
|
260
|
+
// We don't need tts/translation/streamingChunk here — they're untouched by this op.
|
|
261
|
+
const [originalTextRaw, sttHistoryRaw] = await Promise.all([
|
|
262
|
+
this.redisClient.hget(key, 'finalTranscription'),
|
|
263
|
+
this.redisClient.hget(key, 'sttHistory')
|
|
264
|
+
]);
|
|
265
|
+
const originalText = originalTextRaw !== null && originalTextRaw !== void 0 ? originalTextRaw : '';
|
|
266
|
+
const existingSttHistory = sttHistoryRaw ? this.deserialize(sttHistoryRaw) : [];
|
|
267
|
+
const update = {
|
|
268
|
+
llmTranscription: opts.llmTranscription,
|
|
269
|
+
routeUsed: opts.routeUsed
|
|
270
|
+
};
|
|
271
|
+
if (opts.llmWords !== undefined)
|
|
272
|
+
update.llmWords = this.serialize(opts.llmWords);
|
|
271
273
|
let wasChanged = false;
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
c.sttHistory = c.sttHistory.slice(-3);
|
|
290
|
-
}
|
|
291
|
-
});
|
|
274
|
+
if (opts.routeUsed === 'LLM' && opts.llmTranscription !== originalText) {
|
|
275
|
+
update.finalTranscription = opts.llmTranscription;
|
|
276
|
+
update.transcriptionSource = 'llm';
|
|
277
|
+
wasChanged = true;
|
|
278
|
+
}
|
|
279
|
+
if (opts.sttHistoryEntry) {
|
|
280
|
+
const next = [...existingSttHistory, opts.sttHistoryEntry];
|
|
281
|
+
if (next.length > 3)
|
|
282
|
+
next.splice(0, next.length - 3);
|
|
283
|
+
update.sttHistory = this.serialize(next);
|
|
284
|
+
}
|
|
285
|
+
const pipe = this.redisClient.pipeline();
|
|
286
|
+
pipe.hset(key, update);
|
|
287
|
+
pipe.expire(key, EXPIRATION);
|
|
288
|
+
await pipe.exec();
|
|
289
|
+
// Re-read the full chunk so callers can use the up-to-date object.
|
|
290
|
+
const chunk = await this.getMulingstreamChunkById(roomId, n);
|
|
292
291
|
return { chunk, wasChanged };
|
|
293
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Partial HSET — reads only `translation` and `tts` fields, modifies in place,
|
|
295
|
+
* writes back only those two. See setBufferStatus for the race rationale.
|
|
296
|
+
*/
|
|
294
297
|
async discardLanguage(roomId, n, lang) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
298
|
+
var _a, _b;
|
|
299
|
+
const cid = await this.getChunkId(roomId, n);
|
|
300
|
+
if (!cid)
|
|
301
|
+
return null;
|
|
302
|
+
const key = this.chunkHashKey(cid);
|
|
303
|
+
const [translationRaw, ttsRaw] = await Promise.all([
|
|
304
|
+
this.redisClient.hget(key, 'translation'),
|
|
305
|
+
this.redisClient.hget(key, 'tts')
|
|
306
|
+
]);
|
|
307
|
+
const translation = (_a = this.deserialize(translationRaw !== null && translationRaw !== void 0 ? translationRaw : '')) !== null && _a !== void 0 ? _a : {};
|
|
308
|
+
const tts = (_b = this.deserialize(ttsRaw !== null && ttsRaw !== void 0 ? ttsRaw : '')) !== null && _b !== void 0 ? _b : {};
|
|
309
|
+
if (translation[lang])
|
|
310
|
+
translation[lang].status = 'DISCARDED';
|
|
311
|
+
if (tts[lang])
|
|
312
|
+
tts[lang].status = 'DISCARDED';
|
|
313
|
+
const pipe = this.redisClient.pipeline();
|
|
314
|
+
pipe.hset(key, { translation: this.serialize(translation), tts: this.serialize(tts) });
|
|
315
|
+
pipe.expire(key, EXPIRATION);
|
|
316
|
+
await pipe.exec();
|
|
317
|
+
return this.getMulingstreamChunkById(roomId, n);
|
|
301
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Partial HSET — same race-safe pattern as discardLanguage. Only writes the
|
|
321
|
+
* fields actually mutated by the caller (translation and/or tts).
|
|
322
|
+
*/
|
|
302
323
|
async discardLanguages(roomId, n, opt) {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
324
|
+
var _a, _b, _c, _d;
|
|
325
|
+
const cid = await this.getChunkId(roomId, n);
|
|
326
|
+
if (!cid)
|
|
327
|
+
return null;
|
|
328
|
+
const key = this.chunkHashKey(cid);
|
|
329
|
+
const wantTranslation = !!((_a = opt.translation) === null || _a === void 0 ? void 0 : _a.length);
|
|
330
|
+
const wantTts = !!((_b = opt.tts) === null || _b === void 0 ? void 0 : _b.length);
|
|
331
|
+
if (!wantTranslation && !wantTts)
|
|
332
|
+
return this.getMulingstreamChunkById(roomId, n);
|
|
333
|
+
const [translationRaw, ttsRaw] = await Promise.all([
|
|
334
|
+
wantTranslation ? this.redisClient.hget(key, 'translation') : Promise.resolve(null),
|
|
335
|
+
wantTts ? this.redisClient.hget(key, 'tts') : Promise.resolve(null)
|
|
336
|
+
]);
|
|
337
|
+
const update = {};
|
|
338
|
+
if (wantTranslation) {
|
|
339
|
+
const translation = (_c = this.deserialize(translationRaw !== null && translationRaw !== void 0 ? translationRaw : '')) !== null && _c !== void 0 ? _c : {};
|
|
340
|
+
opt.translation.forEach((l) => {
|
|
341
|
+
const e = translation[l];
|
|
307
342
|
if (e && e.status === 'INIT')
|
|
308
343
|
e.status = 'DISCARDED';
|
|
309
344
|
});
|
|
310
|
-
|
|
311
|
-
|
|
345
|
+
update.translation = this.serialize(translation);
|
|
346
|
+
}
|
|
347
|
+
if (wantTts) {
|
|
348
|
+
const tts = (_d = this.deserialize(ttsRaw !== null && ttsRaw !== void 0 ? ttsRaw : '')) !== null && _d !== void 0 ? _d : {};
|
|
349
|
+
opt.tts.forEach((l) => {
|
|
350
|
+
const e = tts[l];
|
|
312
351
|
if (e && e.status === 'INIT')
|
|
313
352
|
e.status = 'DISCARDED';
|
|
314
353
|
});
|
|
315
|
-
|
|
354
|
+
update.tts = this.serialize(tts);
|
|
355
|
+
}
|
|
356
|
+
const pipe = this.redisClient.pipeline();
|
|
357
|
+
pipe.hset(key, update);
|
|
358
|
+
pipe.expire(key, EXPIRATION);
|
|
359
|
+
await pipe.exec();
|
|
360
|
+
return this.getMulingstreamChunkById(roomId, n);
|
|
316
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Partial HSET — only `translation` is read and rewritten. Concurrent updateTts
|
|
364
|
+
* calls won't be clobbered.
|
|
365
|
+
*/
|
|
317
366
|
async updateTranslation(roomId, n, lang, opt) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
367
|
+
var _a;
|
|
368
|
+
const cid = await this.getChunkId(roomId, n);
|
|
369
|
+
if (!cid)
|
|
370
|
+
return null;
|
|
371
|
+
const key = this.chunkHashKey(cid);
|
|
372
|
+
const translationRaw = await this.redisClient.hget(key, 'translation');
|
|
373
|
+
const translation = (_a = this.deserialize(translationRaw !== null && translationRaw !== void 0 ? translationRaw : '')) !== null && _a !== void 0 ? _a : {};
|
|
374
|
+
const e = translation[lang];
|
|
375
|
+
if (!e)
|
|
376
|
+
return this.getMulingstreamChunkById(roomId, n);
|
|
377
|
+
if (opt.translation !== undefined)
|
|
378
|
+
e.translation = opt.translation;
|
|
379
|
+
if (opt.status !== undefined)
|
|
380
|
+
e.status = opt.status;
|
|
381
|
+
const pipe = this.redisClient.pipeline();
|
|
382
|
+
pipe.hset(key, { translation: this.serialize(translation) });
|
|
383
|
+
pipe.expire(key, EXPIRATION);
|
|
384
|
+
await pipe.exec();
|
|
385
|
+
return this.getMulingstreamChunkById(roomId, n);
|
|
327
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Partial HSET — only `translation` is read and rewritten. This is the hot path
|
|
389
|
+
* that races with handleTtsService's updateTts and was the root cause of the
|
|
390
|
+
* "audio LISTENER_FEED never arrives" bug fixed 2026-05-01.
|
|
391
|
+
*/
|
|
328
392
|
async updateTranslationInBulk(roomId, n, dict, status = 'READY', hintsPerLanguage) {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
393
|
+
var _a;
|
|
394
|
+
const cid = await this.getChunkId(roomId, n);
|
|
395
|
+
if (!cid)
|
|
396
|
+
return null;
|
|
397
|
+
const key = this.chunkHashKey(cid);
|
|
398
|
+
const translationRaw = await this.redisClient.hget(key, 'translation');
|
|
399
|
+
const translation = (_a = this.deserialize(translationRaw !== null && translationRaw !== void 0 ? translationRaw : '')) !== null && _a !== void 0 ? _a : {};
|
|
400
|
+
Object.entries(dict).forEach(([l, txt]) => {
|
|
401
|
+
if (!translation[l])
|
|
402
|
+
return;
|
|
403
|
+
translation[l].translation = txt;
|
|
404
|
+
translation[l].status = status;
|
|
405
|
+
if (hintsPerLanguage && hintsPerLanguage[l]) {
|
|
406
|
+
translation[l].hints = hintsPerLanguage[l];
|
|
407
|
+
}
|
|
339
408
|
});
|
|
409
|
+
const pipe = this.redisClient.pipeline();
|
|
410
|
+
pipe.hset(key, { translation: this.serialize(translation) });
|
|
411
|
+
pipe.expire(key, EXPIRATION);
|
|
412
|
+
await pipe.exec();
|
|
413
|
+
return this.getMulingstreamChunkById(roomId, n);
|
|
340
414
|
}
|
|
415
|
+
/**
|
|
416
|
+
* Partial HSET — only `tts` is read and rewritten. Same race-rationale as
|
|
417
|
+
* updateTranslationInBulk: avoids clobbering concurrent translation writes.
|
|
418
|
+
*/
|
|
341
419
|
async updateTts(roomId, n, lang, opt) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
420
|
+
var _a;
|
|
421
|
+
const cid = await this.getChunkId(roomId, n);
|
|
422
|
+
if (!cid)
|
|
423
|
+
return null;
|
|
424
|
+
const key = this.chunkHashKey(cid);
|
|
425
|
+
const ttsRaw = await this.redisClient.hget(key, 'tts');
|
|
426
|
+
const tts = (_a = this.deserialize(ttsRaw !== null && ttsRaw !== void 0 ? ttsRaw : '')) !== null && _a !== void 0 ? _a : {};
|
|
427
|
+
const e = tts[lang];
|
|
428
|
+
if (!e)
|
|
429
|
+
return this.getMulingstreamChunkById(roomId, n);
|
|
430
|
+
if (opt.ttsAudioPath !== undefined)
|
|
431
|
+
e.ttsAudioPath = opt.ttsAudioPath;
|
|
432
|
+
if (opt.status !== undefined)
|
|
433
|
+
e.status = opt.status;
|
|
434
|
+
if (opt.isEmitted !== undefined)
|
|
435
|
+
e.isEmitted = opt.isEmitted;
|
|
436
|
+
if (opt.duration !== undefined)
|
|
437
|
+
e.duration = opt.duration;
|
|
438
|
+
const pipe = this.redisClient.pipeline();
|
|
439
|
+
pipe.hset(key, { tts: this.serialize(tts) });
|
|
440
|
+
pipe.expire(key, EXPIRATION);
|
|
441
|
+
await pipe.exec();
|
|
442
|
+
return this.getMulingstreamChunkById(roomId, n);
|
|
355
443
|
}
|
|
356
444
|
/**
|
|
357
445
|
* Update SmartTranslate buffering metadata on a chunk (Section 22).
|
|
@@ -402,20 +490,34 @@ class MulingstreamChunkManager {
|
|
|
402
490
|
return Object.values(c.tts).every((t) => t.status !== 'INIT');
|
|
403
491
|
}
|
|
404
492
|
/**
|
|
405
|
-
* Mark a chunk as complete (all TTS finished for all target languages)
|
|
493
|
+
* Mark a chunk as complete (all TTS finished for all target languages).
|
|
494
|
+
* Partial HSET — touches only `isComplete`.
|
|
406
495
|
*/
|
|
407
496
|
async markChunkComplete(roomId, n) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
497
|
+
const cid = await this.getChunkId(roomId, n);
|
|
498
|
+
if (!cid)
|
|
499
|
+
return null;
|
|
500
|
+
const key = this.chunkHashKey(cid);
|
|
501
|
+
const pipe = this.redisClient.pipeline();
|
|
502
|
+
pipe.hset(key, { isComplete: 'true' });
|
|
503
|
+
pipe.expire(key, EXPIRATION);
|
|
504
|
+
await pipe.exec();
|
|
505
|
+
return this.getMulingstreamChunkById(roomId, n);
|
|
411
506
|
}
|
|
412
507
|
/**
|
|
413
|
-
* Mark a chunk as saved to database
|
|
508
|
+
* Mark a chunk as saved to database.
|
|
509
|
+
* Partial HSET — touches only `isSaved`.
|
|
414
510
|
*/
|
|
415
511
|
async markChunkSaved(roomId, n) {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
512
|
+
const cid = await this.getChunkId(roomId, n);
|
|
513
|
+
if (!cid)
|
|
514
|
+
return null;
|
|
515
|
+
const key = this.chunkHashKey(cid);
|
|
516
|
+
const pipe = this.redisClient.pipeline();
|
|
517
|
+
pipe.hset(key, { isSaved: 'true' });
|
|
518
|
+
pipe.expire(key, EXPIRATION);
|
|
519
|
+
await pipe.exec();
|
|
520
|
+
return this.getMulingstreamChunkById(roomId, n);
|
|
419
521
|
}
|
|
420
522
|
/**
|
|
421
523
|
* Get all complete but unsaved chunks for a room
|