@mulingai-npm/redis 1.14.2 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/managers/mulingstream-chunk-manager.d.ts +53 -31
- package/dist/managers/mulingstream-chunk-manager.js +155 -224
- package/dist/managers/mulingstream-listener-manager.d.ts +5 -0
- package/dist/managers/mulingstream-listener-manager.js +26 -0
- package/dist/redis-client.d.ts +5 -0
- package/dist/redis-client.js +26 -0
- package/package.json +1 -1
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
import { RedisClient } from '../redis-client';
|
|
2
2
|
export type StepStatus = 'INIT' | 'DISCARDED' | 'READY' | 'USED';
|
|
3
|
-
export type MulingstreamChunkStatus = 'INIT' | 'STARTED' | 'FINISHED' | 'CANCELED' | 'USED';
|
|
4
|
-
export type MulingstreamChunkStep = 'RECEIVED' | 'STT' | 'TRANSLATION' | 'TTS' | 'EMITTED';
|
|
5
3
|
export type SttProvider = 'azure' | 'whisper' | 'google' | 'aws';
|
|
6
4
|
export type MulingstreamChunkData = {
|
|
7
|
-
mulingstreamChunkId: string;
|
|
8
|
-
roomId: string;
|
|
9
5
|
chunkNumber: number;
|
|
10
6
|
language: string;
|
|
11
7
|
sttProviders: SttProvider[];
|
|
12
8
|
targetLanguages: string[];
|
|
13
9
|
finalTranscription: string;
|
|
14
|
-
|
|
15
|
-
mulingstreamChunkStep: MulingstreamChunkStep;
|
|
10
|
+
sttStatus: StepStatus;
|
|
16
11
|
audioChunk: {
|
|
17
12
|
start: number;
|
|
18
13
|
end: number;
|
|
@@ -45,7 +40,31 @@ export type MulingstreamChunkData = {
|
|
|
45
40
|
export declare class MulingstreamChunkManager {
|
|
46
41
|
private redisClient;
|
|
47
42
|
constructor(redisClient: RedisClient);
|
|
48
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Initializes a room in Redis as an empty JSON array.
|
|
45
|
+
* If the key [roomId] already exists, we do NOT overwrite it.
|
|
46
|
+
* Returns true if room was created, false if room already existed.
|
|
47
|
+
*/
|
|
48
|
+
initRoom(roomId: string): Promise<boolean>;
|
|
49
|
+
/**
|
|
50
|
+
* Returns all rooms. This is naive if you store many other keys in Redis,
|
|
51
|
+
* because we search keys for pattern "[*]".
|
|
52
|
+
* Adjust as needed if you have a separate naming prefix.
|
|
53
|
+
*/
|
|
54
|
+
getRooms(): Promise<string[]>;
|
|
55
|
+
/**
|
|
56
|
+
* Returns the entire array of chunks for the given roomId,
|
|
57
|
+
* or null if the room doesn't exist in Redis.
|
|
58
|
+
*/
|
|
59
|
+
getMulingstreamChunksByRoom(roomId: string): Promise<MulingstreamChunkData[] | null>;
|
|
60
|
+
/**
|
|
61
|
+
* Returns the room array "as is" for debugging
|
|
62
|
+
* (same as getMulingstreamChunksByRoom in this example).
|
|
63
|
+
*/
|
|
64
|
+
getRoomById(roomId: string): Promise<MulingstreamChunkData[] | null>;
|
|
65
|
+
/**
|
|
66
|
+
* Adds a new Mulingstream chunk to the array stored at [${roomId}].
|
|
67
|
+
*/
|
|
49
68
|
addMulingstreamChunk(params: {
|
|
50
69
|
roomId: string;
|
|
51
70
|
chunkNumber: number;
|
|
@@ -58,28 +77,31 @@ export declare class MulingstreamChunkManager {
|
|
|
58
77
|
theme: string;
|
|
59
78
|
sttProviders: SttProvider[];
|
|
60
79
|
targetLanguages: string[];
|
|
61
|
-
}): Promise<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
*
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
}): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Given roomId and chunkNumber, return the single chunk from the array
|
|
83
|
+
* or null if not found.
|
|
84
|
+
*/
|
|
85
|
+
getMulingstreamChunkById(roomId: string, chunkNumber: number): Promise<MulingstreamChunkData | null>;
|
|
86
|
+
/**
|
|
87
|
+
* Update STT fields for a given chunk.
|
|
88
|
+
* If transcription or sttStatus is null, skip that field.
|
|
89
|
+
*/
|
|
90
|
+
updateStt(roomId: string, chunkNumber: number, sttProvider: SttProvider, options: {
|
|
91
|
+
transcription?: string;
|
|
92
|
+
sttStatus?: StepStatus;
|
|
93
|
+
}): Promise<boolean>;
|
|
94
|
+
/**
|
|
95
|
+
* Update the final transcription field for a chunk.
|
|
96
|
+
*/
|
|
97
|
+
updateFinalTranscription(roomId: string, chunkNumber: number, transcription: string, sttStatus: StepStatus): Promise<boolean>;
|
|
98
|
+
/**
|
|
99
|
+
* Discards all post-STT steps for a given chunk:
|
|
100
|
+
* sets all translation[].status & tts[].status to "DISCARDED".
|
|
101
|
+
*/
|
|
102
|
+
discardPostStt(roomId: string, chunkNumber: number): Promise<boolean>;
|
|
103
|
+
/**
|
|
104
|
+
* Discards a specific language in both translation and tts for a chunk.
|
|
105
|
+
*/
|
|
106
|
+
discardLanguage(roomId: string, chunkNumber: number, language: string): Promise<boolean>;
|
|
85
107
|
}
|
|
@@ -1,44 +1,70 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MulingstreamChunkManager = void 0;
|
|
4
|
-
const uuid_1 = require("uuid");
|
|
5
4
|
const EXPIRATION = 12 * 60 * 60; // 12 hours in seconds
|
|
5
|
+
const ROOM_ARRAY_LENGTH = 5; // keep only the last 5 elements
|
|
6
6
|
class MulingstreamChunkManager {
|
|
7
7
|
constructor(redisClient) {
|
|
8
8
|
this.redisClient = redisClient;
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Initializes a room in Redis as an empty JSON array.
|
|
12
|
+
* If the key [roomId] already exists, we do NOT overwrite it.
|
|
13
|
+
* Returns true if room was created, false if room already existed.
|
|
14
|
+
*/
|
|
15
|
+
async initRoom(roomId) {
|
|
16
|
+
// Check if the key already exists (JSON.GET)
|
|
17
|
+
const isRoomExisting = await this.redisClient.jsonGet(`[${roomId}]`, '.');
|
|
18
|
+
if (isRoomExisting !== null) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
// Create an empty array at the root
|
|
22
|
+
await this.redisClient.jsonSet(`[${roomId}]`, '.', []);
|
|
23
|
+
await this.redisClient.expire(`[${roomId}]`, EXPIRATION);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Returns all rooms. This is naive if you store many other keys in Redis,
|
|
28
|
+
* because we search keys for pattern "[*]".
|
|
29
|
+
* Adjust as needed if you have a separate naming prefix.
|
|
30
|
+
*/
|
|
31
|
+
async getRooms() {
|
|
32
|
+
// e.g., if we store everything as [foo], [bar], etc., we do:
|
|
33
|
+
const rooms = await this.redisClient.keys('\\[*\\]');
|
|
34
|
+
// This returns the raw keys, e.g. ['[myRoom]', '[anotherRoom]']
|
|
35
|
+
// We might want to strip off the brackets:
|
|
36
|
+
return rooms.map((k) => k.replace(/^\[|\]$/g, ''));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Returns the entire array of chunks for the given roomId,
|
|
40
|
+
* or null if the room doesn't exist in Redis.
|
|
41
|
+
*/
|
|
42
|
+
async getMulingstreamChunksByRoom(roomId) {
|
|
43
|
+
// JSON.GET [roomId] .
|
|
44
|
+
const chunks = await this.redisClient.jsonGet(`[${roomId}]`, '.');
|
|
45
|
+
if (chunks === null)
|
|
46
|
+
return null; // room not found
|
|
47
|
+
return chunks;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Returns the room array "as is" for debugging
|
|
51
|
+
* (same as getMulingstreamChunksByRoom in this example).
|
|
52
|
+
*/
|
|
53
|
+
async getRoomById(roomId) {
|
|
54
|
+
return this.getMulingstreamChunksByRoom(roomId);
|
|
33
55
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// - Applies an expiration (12h).
|
|
56
|
+
/**
|
|
57
|
+
* Adds a new Mulingstream chunk to the array stored at [${roomId}].
|
|
58
|
+
*/
|
|
38
59
|
async addMulingstreamChunk(params) {
|
|
39
60
|
const { roomId, chunkNumber, language, start, end, duration, isFirst, isLast, theme, sttProviders, targetLanguages } = params;
|
|
40
|
-
|
|
41
|
-
|
|
61
|
+
// ensure we only keep the last 5 elements
|
|
62
|
+
const currentLength = await this.redisClient.jsonArrPop(`[${roomId}]`, '.', 0);
|
|
63
|
+
if (typeof currentLength === 'number' && currentLength >= ROOM_ARRAY_LENGTH) {
|
|
64
|
+
// remove the oldest (front of the array)
|
|
65
|
+
await this.redisClient.jsonArrPop(`[${roomId}]`, '.', 0);
|
|
66
|
+
}
|
|
67
|
+
// Build the chunk
|
|
42
68
|
const audioChunk = {
|
|
43
69
|
start,
|
|
44
70
|
end,
|
|
@@ -48,16 +74,15 @@ class MulingstreamChunkManager {
|
|
|
48
74
|
theme,
|
|
49
75
|
processingStart: Date.now()
|
|
50
76
|
};
|
|
51
|
-
// Build
|
|
52
|
-
// For each service in `services`, create { transcription: '', status: 'INIT' }
|
|
77
|
+
// Build stt object
|
|
53
78
|
const stt = {};
|
|
54
|
-
for (const
|
|
55
|
-
stt[
|
|
79
|
+
for (const svc of sttProviders) {
|
|
80
|
+
stt[svc] = {
|
|
56
81
|
transcription: '',
|
|
57
82
|
status: 'INIT'
|
|
58
83
|
};
|
|
59
84
|
}
|
|
60
|
-
// Build translation
|
|
85
|
+
// Build translation and tts objects
|
|
61
86
|
const translation = {};
|
|
62
87
|
const tts = {};
|
|
63
88
|
for (const lang of targetLanguages) {
|
|
@@ -71,217 +96,123 @@ class MulingstreamChunkManager {
|
|
|
71
96
|
isEmitted: false
|
|
72
97
|
};
|
|
73
98
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// Store the data in a Redis hash
|
|
77
|
-
await this.redisClient.hset(`mulingstreamChunk:${mulingstreamChunkId}`, {
|
|
78
|
-
mulingstreamChunkId,
|
|
79
|
-
roomId,
|
|
80
|
-
chunkNumber: chunkNumber.toString(),
|
|
99
|
+
const newChunk = {
|
|
100
|
+
chunkNumber,
|
|
81
101
|
language,
|
|
82
|
-
sttProviders
|
|
83
|
-
targetLanguages
|
|
84
|
-
finalTranscription,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
return mulingstreamChunkId;
|
|
95
|
-
}
|
|
96
|
-
// Retrieves all mulingstream chunks by scanning keys `mulingstreamChunk:*`.
|
|
97
|
-
async getMulingstreamChunks() {
|
|
98
|
-
const keys = await this.redisClient.keys('mulingstreamChunk:*');
|
|
99
|
-
if (!keys || keys.length === 0) {
|
|
100
|
-
return [];
|
|
101
|
-
}
|
|
102
|
-
const results = [];
|
|
103
|
-
for (const key of keys) {
|
|
104
|
-
const data = await this.redisClient.hgetall(key);
|
|
105
|
-
if (data && data.mulingstreamChunkId) {
|
|
106
|
-
results.push(this.parseHashData(data));
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return results;
|
|
110
|
-
}
|
|
111
|
-
// Retrieves a single mulingstream chunk by ID.
|
|
112
|
-
async getMulingstreamChunk(mulingstreamChunkId) {
|
|
113
|
-
const data = await this.redisClient.hgetall(`mulingstreamChunk:${mulingstreamChunkId}`);
|
|
114
|
-
if (!data || !data.mulingstreamChunkId) {
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
return this.parseHashData(data);
|
|
118
|
-
}
|
|
119
|
-
// Retrieves all mulingstream chunks for a specific room by pattern matching keys `mulingstreamChunk:[roomId]-*`.
|
|
120
|
-
async getMulingstreamChunksByRoom(roomId) {
|
|
121
|
-
const pattern = `mulingstreamChunk:[${roomId}]-*`;
|
|
122
|
-
const keys = await this.redisClient.keys(pattern);
|
|
123
|
-
if (!keys || keys.length === 0) {
|
|
124
|
-
return [];
|
|
125
|
-
}
|
|
126
|
-
const results = [];
|
|
127
|
-
for (const key of keys) {
|
|
128
|
-
const data = await this.redisClient.hgetall(key);
|
|
129
|
-
if (!data || !data.mulingstreamChunkId) {
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
results.push(this.parseHashData(data));
|
|
133
|
-
}
|
|
134
|
-
return results;
|
|
102
|
+
sttProviders,
|
|
103
|
+
targetLanguages,
|
|
104
|
+
finalTranscription: '',
|
|
105
|
+
sttStatus: 'INIT',
|
|
106
|
+
audioChunk,
|
|
107
|
+
stt,
|
|
108
|
+
translation,
|
|
109
|
+
tts
|
|
110
|
+
};
|
|
111
|
+
// Append it to the array
|
|
112
|
+
await this.redisClient.jsonArrAppend(`[${roomId}]`, '.', // the root of the JSON document (the array)
|
|
113
|
+
newChunk);
|
|
135
114
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Given roomId and chunkNumber, return the single chunk from the array
|
|
117
|
+
* or null if not found.
|
|
118
|
+
*/
|
|
119
|
+
async getMulingstreamChunkById(roomId, chunkNumber) {
|
|
120
|
+
// Retrieve the entire array
|
|
121
|
+
const chunks = await this.getMulingstreamChunksByRoom(roomId);
|
|
122
|
+
if (!chunks)
|
|
142
123
|
return null;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (!data || !data.mulingstreamChunkId) {
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
return this.parseHashData(data);
|
|
124
|
+
// Find by chunkNumber
|
|
125
|
+
const chunk = chunks.find((c) => c.chunkNumber === chunkNumber);
|
|
126
|
+
return chunk || null;
|
|
150
127
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
await this.redisClient.hset(`mulingstreamChunk:${mulingstreamChunkId}`, {
|
|
159
|
-
mulingstreamChunkStatus,
|
|
160
|
-
mulingstreamChunkStep
|
|
161
|
-
});
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
async updateFinalTranscription(mulingstreamChunkId, finalTranscription) {
|
|
165
|
-
// Fetch the chunk data first
|
|
166
|
-
const data = await this.redisClient.hgetall(`mulingstreamChunk:${mulingstreamChunkId}`);
|
|
167
|
-
if (!data || !data.mulingstreamChunkId) {
|
|
168
|
-
return false; // The chunk may not exist or has expired
|
|
169
|
-
}
|
|
170
|
-
// Update the finalTranscription field
|
|
171
|
-
await this.redisClient.hset(`mulingstreamChunk:${mulingstreamChunkId}`, {
|
|
172
|
-
finalTranscription
|
|
173
|
-
});
|
|
174
|
-
return true;
|
|
175
|
-
}
|
|
176
|
-
// Updates the STT status for a given service in the chunk's STT data.
|
|
177
|
-
async updateSttStatus(mulingstreamChunkId, service, status) {
|
|
178
|
-
const data = await this.redisClient.hgetall(`mulingstreamChunk:${mulingstreamChunkId}`);
|
|
179
|
-
if (!data || !data.mulingstreamChunkId) {
|
|
128
|
+
/**
|
|
129
|
+
* Update STT fields for a given chunk.
|
|
130
|
+
* If transcription or sttStatus is null, skip that field.
|
|
131
|
+
*/
|
|
132
|
+
async updateStt(roomId, chunkNumber, sttProvider, options) {
|
|
133
|
+
const chunks = await this.getMulingstreamChunksByRoom(roomId);
|
|
134
|
+
if (!chunks)
|
|
180
135
|
return false;
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
if (
|
|
184
|
-
return false;
|
|
185
|
-
|
|
186
|
-
stt
|
|
187
|
-
|
|
188
|
-
stt: JSON.stringify(stt)
|
|
189
|
-
});
|
|
190
|
-
return true;
|
|
191
|
-
}
|
|
192
|
-
// Updates the transcription + status for a given service in the chunk's STT data.
|
|
193
|
-
async updateTranscription(mulingstreamChunkId, service, transcription, sttStatus) {
|
|
194
|
-
const data = await this.redisClient.hgetall(`mulingstreamChunk:${mulingstreamChunkId}`);
|
|
195
|
-
if (!data || !data.mulingstreamChunkId) {
|
|
136
|
+
// Find the chunk
|
|
137
|
+
const chunkIndex = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
|
|
138
|
+
if (chunkIndex === -1)
|
|
139
|
+
return false;
|
|
140
|
+
const chunk = chunks[chunkIndex];
|
|
141
|
+
// If this stt provider doesn't exist, ignore
|
|
142
|
+
if (!chunk.stt[sttProvider])
|
|
196
143
|
return false;
|
|
144
|
+
// Update
|
|
145
|
+
if (options.transcription != null) {
|
|
146
|
+
chunk.stt[sttProvider].transcription = options.transcription;
|
|
197
147
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
return false; // service not in stt object
|
|
148
|
+
if (options.sttStatus != null) {
|
|
149
|
+
chunk.stt[sttProvider].status = options.sttStatus;
|
|
201
150
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
await this.redisClient.
|
|
205
|
-
stt: JSON.stringify(stt)
|
|
206
|
-
});
|
|
151
|
+
// Write back
|
|
152
|
+
chunks[chunkIndex] = chunk;
|
|
153
|
+
await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
|
|
207
154
|
return true;
|
|
208
155
|
}
|
|
209
|
-
|
|
210
|
-
*
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const data = await this.redisClient.hgetall(`mulingstreamChunk:${mulingstreamChunkId}`);
|
|
216
|
-
if (!data || !data.mulingstreamChunkId) {
|
|
156
|
+
/**
|
|
157
|
+
* Update the final transcription field for a chunk.
|
|
158
|
+
*/
|
|
159
|
+
async updateFinalTranscription(roomId, chunkNumber, transcription, sttStatus) {
|
|
160
|
+
const chunks = await this.getMulingstreamChunksByRoom(roomId);
|
|
161
|
+
if (!chunks)
|
|
217
162
|
return false;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
translation[lang].status = 'DISCARDED';
|
|
225
|
-
}
|
|
226
|
-
// Set every language status to DISCARDED in tts
|
|
227
|
-
for (const lang of Object.keys(tts)) {
|
|
228
|
-
tts[lang].status = 'DISCARDED';
|
|
229
|
-
}
|
|
163
|
+
// Find the chunk
|
|
164
|
+
const chunkIndex = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
|
|
165
|
+
if (chunkIndex === -1)
|
|
166
|
+
return false;
|
|
167
|
+
chunks[chunkIndex].finalTranscription = transcription;
|
|
168
|
+
chunks[chunkIndex].sttStatus = sttStatus;
|
|
230
169
|
// Write back
|
|
231
|
-
await this.redisClient.
|
|
232
|
-
translation: JSON.stringify(translation),
|
|
233
|
-
tts: JSON.stringify(tts)
|
|
234
|
-
});
|
|
170
|
+
await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
|
|
235
171
|
return true;
|
|
236
172
|
}
|
|
237
|
-
|
|
238
|
-
* Discards all post-
|
|
239
|
-
* all tts[].status to "DISCARDED"
|
|
240
|
-
|
|
241
|
-
async
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (!data || !data.mulingstreamChunkId) {
|
|
173
|
+
/**
|
|
174
|
+
* Discards all post-STT steps for a given chunk:
|
|
175
|
+
* sets all translation[].status & tts[].status to "DISCARDED".
|
|
176
|
+
*/
|
|
177
|
+
async discardPostStt(roomId, chunkNumber) {
|
|
178
|
+
const chunks = await this.getMulingstreamChunksByRoom(roomId);
|
|
179
|
+
if (!chunks)
|
|
245
180
|
return false;
|
|
181
|
+
// Find chunk
|
|
182
|
+
const chunkIndex = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
|
|
183
|
+
if (chunkIndex === -1)
|
|
184
|
+
return false;
|
|
185
|
+
// Discard translation & TTS statuses
|
|
186
|
+
const chunk = chunks[chunkIndex];
|
|
187
|
+
for (const lang of Object.keys(chunk.translation)) {
|
|
188
|
+
chunk.translation[lang].status = 'DISCARDED';
|
|
246
189
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
// Set every language status to DISCARDED in tts
|
|
250
|
-
for (const lang of Object.keys(tts)) {
|
|
251
|
-
tts[lang].status = 'DISCARDED';
|
|
190
|
+
for (const lang of Object.keys(chunk.tts)) {
|
|
191
|
+
chunk.tts[lang].status = 'DISCARDED';
|
|
252
192
|
}
|
|
253
|
-
|
|
254
|
-
await this.redisClient.
|
|
255
|
-
tts: JSON.stringify(tts)
|
|
256
|
-
});
|
|
193
|
+
chunks[chunkIndex] = chunk;
|
|
194
|
+
await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
|
|
257
195
|
return true;
|
|
258
196
|
}
|
|
259
|
-
|
|
260
|
-
* Discards a specific language in both
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const data = await this.redisClient.hgetall(`mulingstreamChunk:${mulingstreamChunkId}`);
|
|
266
|
-
if (!data || !data.mulingstreamChunkId) {
|
|
197
|
+
/**
|
|
198
|
+
* Discards a specific language in both translation and tts for a chunk.
|
|
199
|
+
*/
|
|
200
|
+
async discardLanguage(roomId, chunkNumber, language) {
|
|
201
|
+
const chunks = await this.getMulingstreamChunksByRoom(roomId);
|
|
202
|
+
if (!chunks)
|
|
267
203
|
return false;
|
|
204
|
+
const chunkIndex = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
|
|
205
|
+
if (chunkIndex === -1)
|
|
206
|
+
return false;
|
|
207
|
+
const chunk = chunks[chunkIndex];
|
|
208
|
+
if (chunk.translation[language]) {
|
|
209
|
+
chunk.translation[language].status = 'DISCARDED';
|
|
268
210
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const tts = JSON.parse(data.tts);
|
|
272
|
-
// If the language exists in translation, discard
|
|
273
|
-
if (translation[language]) {
|
|
274
|
-
translation[language].status = 'DISCARDED';
|
|
275
|
-
}
|
|
276
|
-
// If the language exists in tts, discard
|
|
277
|
-
if (tts[language]) {
|
|
278
|
-
tts[language].status = 'DISCARDED';
|
|
211
|
+
if (chunk.tts[language]) {
|
|
212
|
+
chunk.tts[language].status = 'DISCARDED';
|
|
279
213
|
}
|
|
280
|
-
|
|
281
|
-
await this.redisClient.
|
|
282
|
-
translation: JSON.stringify(translation),
|
|
283
|
-
tts: JSON.stringify(tts)
|
|
284
|
-
});
|
|
214
|
+
chunks[chunkIndex] = chunk;
|
|
215
|
+
await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
|
|
285
216
|
return true;
|
|
286
217
|
}
|
|
287
218
|
}
|
|
@@ -21,4 +21,9 @@ export declare class MulingstreamListenerManager {
|
|
|
21
21
|
updateNameLanguage(listenerIdOrToken: string, name: string, language: string): Promise<boolean>;
|
|
22
22
|
updateSocketId(listenerIdOrToken: string, socketId: string): Promise<boolean>;
|
|
23
23
|
getTargetSocketIdsByRoomLanguage(roomId: string, language: string): Promise<string[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Returns an array of unique languages for the given room, from highest frequency to lowest.
|
|
26
|
+
* If a listener has no language (empty string / undefined), it is ignored.
|
|
27
|
+
*/
|
|
28
|
+
getUniqueLanguagesByRoom(roomId: string): Promise<string[]>;
|
|
24
29
|
}
|
|
@@ -139,5 +139,31 @@ class MulingstreamListenerManager {
|
|
|
139
139
|
});
|
|
140
140
|
return filteredListeners.map((listener) => listener.socketId);
|
|
141
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Returns an array of unique languages for the given room, from highest frequency to lowest.
|
|
144
|
+
* If a listener has no language (empty string / undefined), it is ignored.
|
|
145
|
+
*/
|
|
146
|
+
async getUniqueLanguagesByRoom(roomId) {
|
|
147
|
+
// 1) Fetch all listeners for the room.
|
|
148
|
+
// (This includes inactive ones, but feel free to filter out isActive === false if you only want active ones.)
|
|
149
|
+
const listeners = await this.getListenersByRoom(roomId);
|
|
150
|
+
// if I wanted to get only active listeners
|
|
151
|
+
// const listeners = (await this.getListenersByRoom(roomId)).filter(l => l.isActive);
|
|
152
|
+
// 2) Count how many times each language appears.
|
|
153
|
+
const languageCountMap = {};
|
|
154
|
+
for (const listener of listeners) {
|
|
155
|
+
// skip blank/unset language
|
|
156
|
+
if (!listener.language || !listener.language.trim()) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// increment count
|
|
160
|
+
const lang = listener.language;
|
|
161
|
+
languageCountMap[lang] = (languageCountMap[lang] || 0) + 1;
|
|
162
|
+
}
|
|
163
|
+
// 3) Sort by frequency descending: highest count first
|
|
164
|
+
const sortedEntries = Object.entries(languageCountMap).sort(([, countA], [, countB]) => countB - countA);
|
|
165
|
+
// 4) Map back to just the language strings in order
|
|
166
|
+
return sortedEntries.map(([language]) => language);
|
|
167
|
+
}
|
|
142
168
|
}
|
|
143
169
|
exports.MulingstreamListenerManager = MulingstreamListenerManager;
|
package/dist/redis-client.d.ts
CHANGED
|
@@ -19,4 +19,9 @@ export declare class RedisClient {
|
|
|
19
19
|
flushAll(): Promise<string>;
|
|
20
20
|
flushDb(): Promise<string>;
|
|
21
21
|
expire(key: string, seconds: number): Promise<number>;
|
|
22
|
+
jsonSet<T>(key: string, path: string, value: T): Promise<string>;
|
|
23
|
+
jsonGet<T>(key: string, path: string): Promise<T | null>;
|
|
24
|
+
jsonDel(key: string, path: string): Promise<number>;
|
|
25
|
+
jsonArrAppend<T>(key: string, path: string, ...values: T[]): Promise<number>;
|
|
26
|
+
jsonArrPop(key: string, path: string, index?: number): Promise<any>;
|
|
22
27
|
}
|
package/dist/redis-client.js
CHANGED
|
@@ -61,5 +61,31 @@ class RedisClient {
|
|
|
61
61
|
async expire(key, seconds) {
|
|
62
62
|
return this.client.expire(key, seconds);
|
|
63
63
|
}
|
|
64
|
+
async jsonSet(key, path, value) {
|
|
65
|
+
const result = await this.client.call('JSON.SET', key, path, JSON.stringify(value));
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
async jsonGet(key, path) {
|
|
69
|
+
const result = (await this.client.call('JSON.GET', key, path));
|
|
70
|
+
if (!result) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return JSON.parse(result);
|
|
74
|
+
}
|
|
75
|
+
async jsonDel(key, path) {
|
|
76
|
+
const result = await this.client.call('JSON.DEL', key, path);
|
|
77
|
+
return Number(result);
|
|
78
|
+
}
|
|
79
|
+
async jsonArrAppend(key, path, ...values) {
|
|
80
|
+
const stringifiedValues = values.map((v) => JSON.stringify(v));
|
|
81
|
+
const result = await this.client.call('JSON.ARRAPPEND', key, path, ...stringifiedValues);
|
|
82
|
+
return Number(result);
|
|
83
|
+
}
|
|
84
|
+
async jsonArrPop(key, path, index) {
|
|
85
|
+
if (index !== undefined) {
|
|
86
|
+
return this.client.call('JSON.ARRPOP', key, path, index);
|
|
87
|
+
}
|
|
88
|
+
return this.client.call('JSON.ARRPOP', key, path);
|
|
89
|
+
}
|
|
64
90
|
}
|
|
65
91
|
exports.RedisClient = RedisClient;
|