@mulingai-npm/redis 3.40.54 → 3.40.55
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.
|
@@ -89,13 +89,6 @@ export declare class MulingstreamListenerManager {
|
|
|
89
89
|
* for why that single threshold could not be lowered and had to be split.
|
|
90
90
|
*/
|
|
91
91
|
getUniqueLanguagesByRoom(roomId: string): Promise<string[]>;
|
|
92
|
-
private guestLanguagesKey;
|
|
93
|
-
/** Record that a guest, not the host, put this language in the room. */
|
|
94
|
-
markGuestLanguage(roomId: string, language: string): Promise<void>;
|
|
95
|
-
/** Forget a guest language, once it has been released from the room. */
|
|
96
|
-
unmarkGuestLanguage(roomId: string, language: string): Promise<void>;
|
|
97
|
-
/** Every language in this room that a guest added. */
|
|
98
|
-
getGuestLanguages(roomId: string): Promise<string[]>;
|
|
99
92
|
/**
|
|
100
93
|
* How many PRESENT listeners in this room are on a language.
|
|
101
94
|
*
|
|
@@ -140,14 +133,35 @@ export declare class MulingstreamListenerManager {
|
|
|
140
133
|
*
|
|
141
134
|
* Church passes turn on this number rather than on how many people joined. A
|
|
142
135
|
* pass is spent only when translation reached a human being, so somebody
|
|
143
|
-
* sitting on the join screen
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
136
|
+
* sitting on the join screen must not burn a church's pass.
|
|
137
|
+
*
|
|
138
|
+
* ─── RECEIVING MEANS AUDIO **OR** TEXT ──────────────────────────────────
|
|
139
|
+
*
|
|
140
|
+
* Corrected 2026-08-19. This used to require `isListening`, the audio
|
|
141
|
+
* playback flag, so a guest who joined, chose their language and sat reading
|
|
142
|
+
* the translated text counted for nothing: the pass clock stayed at zero
|
|
143
|
+
* until they pressed Listening. David found it on a phone, and the counting
|
|
144
|
+
* spec had said otherwise all along, in as many words: "the listener session
|
|
145
|
+
* is open AND we are delivering translated output to it, audio or text".
|
|
146
|
+
*
|
|
147
|
+
* The reading is the product. A deaf guest, a guest in a quiet room, a guest
|
|
148
|
+
* with no headphones and a guest who simply prefers to read are all being
|
|
149
|
+
* served, all costing us translation on every chunk, and none of them are
|
|
150
|
+
* pressing an audio button. Charging only for audio would have meant a
|
|
151
|
+
* church whose congregation reads never spends a pass, which sounds generous
|
|
152
|
+
* until you notice we are paying for every word of it.
|
|
153
|
+
*
|
|
154
|
+
* So the test is PRESENCE, and nothing else. Presence already means joined,
|
|
155
|
+
* in a language, and with a heartbeat inside the last ninety seconds, which
|
|
156
|
+
* is exactly the population `consumersFor` is translating for. Whether they
|
|
157
|
+
* also chose to hear it is not our business, and the spec is explicit that we
|
|
158
|
+
* do not police attention: a phone in a pocket with audio playing is
|
|
159
|
+
* listening, and a phone in a hand with text on it is reading.
|
|
160
|
+
*
|
|
161
|
+
* `isListening` keeps its real job, which is a different question: whether to
|
|
162
|
+
* SYNTHESISE SPEECH. See getLanguagesWithActiveListeners. Speech is the most
|
|
163
|
+
* expensive thing we produce and nobody should pay to generate audio no one
|
|
164
|
+
* is playing. Text is already produced for the room either way.
|
|
151
165
|
*/
|
|
152
166
|
getReceivingCount(roomId: string): Promise<number>;
|
|
153
167
|
private capKey;
|
|
@@ -263,48 +263,6 @@ class MulingstreamListenerManager {
|
|
|
263
263
|
// 4) Map back to just the language strings in order
|
|
264
264
|
return sortedEntries.map(([language]) => language);
|
|
265
265
|
}
|
|
266
|
-
/*
|
|
267
|
-
* GUEST ADDED LANGUAGES
|
|
268
|
-
*
|
|
269
|
-
* Which of a room's target languages a guest put there, as opposed to the
|
|
270
|
-
* host. The distinction is the whole reason this exists: a guest-added
|
|
271
|
-
* language is released the moment nobody is on it, and the host's own
|
|
272
|
-
* language never is, however empty the room gets. Without the distinction a
|
|
273
|
-
* church that pinned Danish and had every guest wander off would find its room
|
|
274
|
-
* translating nothing.
|
|
275
|
-
*
|
|
276
|
-
* It also stops a single guest eating the room. Changing language seven times
|
|
277
|
-
* would otherwise leave seven languages standing, every one of them translated
|
|
278
|
-
* and synthesised for the rest of the service on a counted pass, and no slot
|
|
279
|
-
* left for the person who actually needed one.
|
|
280
|
-
*
|
|
281
|
-
* A set rather than a column: it is per-session bookkeeping, not a property of
|
|
282
|
-
* the room, and it should disappear on its own if a process dies mid-service.
|
|
283
|
-
* The TTL is the listener expiry for exactly that reason.
|
|
284
|
-
*/
|
|
285
|
-
guestLanguagesKey(roomId) {
|
|
286
|
-
return `room:${roomId}:guest-languages`;
|
|
287
|
-
}
|
|
288
|
-
/** Record that a guest, not the host, put this language in the room. */
|
|
289
|
-
async markGuestLanguage(roomId, language) {
|
|
290
|
-
if (!roomId || !language)
|
|
291
|
-
return;
|
|
292
|
-
const key = this.guestLanguagesKey(roomId);
|
|
293
|
-
await this.redisClient.sadd(key, language);
|
|
294
|
-
await this.redisClient.expire(key, EXPIRATION);
|
|
295
|
-
}
|
|
296
|
-
/** Forget a guest language, once it has been released from the room. */
|
|
297
|
-
async unmarkGuestLanguage(roomId, language) {
|
|
298
|
-
if (!roomId || !language)
|
|
299
|
-
return;
|
|
300
|
-
await this.redisClient.srem(this.guestLanguagesKey(roomId), language);
|
|
301
|
-
}
|
|
302
|
-
/** Every language in this room that a guest added. */
|
|
303
|
-
async getGuestLanguages(roomId) {
|
|
304
|
-
if (!roomId)
|
|
305
|
-
return [];
|
|
306
|
-
return (await this.redisClient.smembers(this.guestLanguagesKey(roomId))) || [];
|
|
307
|
-
}
|
|
308
266
|
/**
|
|
309
267
|
* How many PRESENT listeners in this room are on a language.
|
|
310
268
|
*
|
|
@@ -396,25 +354,40 @@ class MulingstreamListenerManager {
|
|
|
396
354
|
*
|
|
397
355
|
* Church passes turn on this number rather than on how many people joined. A
|
|
398
356
|
* pass is spent only when translation reached a human being, so somebody
|
|
399
|
-
* sitting on the join screen
|
|
400
|
-
*
|
|
357
|
+
* sitting on the join screen must not burn a church's pass.
|
|
358
|
+
*
|
|
359
|
+
* ─── RECEIVING MEANS AUDIO **OR** TEXT ──────────────────────────────────
|
|
360
|
+
*
|
|
361
|
+
* Corrected 2026-08-19. This used to require `isListening`, the audio
|
|
362
|
+
* playback flag, so a guest who joined, chose their language and sat reading
|
|
363
|
+
* the translated text counted for nothing: the pass clock stayed at zero
|
|
364
|
+
* until they pressed Listening. David found it on a phone, and the counting
|
|
365
|
+
* spec had said otherwise all along, in as many words: "the listener session
|
|
366
|
+
* is open AND we are delivering translated output to it, audio or text".
|
|
367
|
+
*
|
|
368
|
+
* The reading is the product. A deaf guest, a guest in a quiet room, a guest
|
|
369
|
+
* with no headphones and a guest who simply prefers to read are all being
|
|
370
|
+
* served, all costing us translation on every chunk, and none of them are
|
|
371
|
+
* pressing an audio button. Charging only for audio would have meant a
|
|
372
|
+
* church whose congregation reads never spends a pass, which sounds generous
|
|
373
|
+
* until you notice we are paying for every word of it.
|
|
374
|
+
*
|
|
375
|
+
* So the test is PRESENCE, and nothing else. Presence already means joined,
|
|
376
|
+
* in a language, and with a heartbeat inside the last ninety seconds, which
|
|
377
|
+
* is exactly the population `consumersFor` is translating for. Whether they
|
|
378
|
+
* also chose to hear it is not our business, and the spec is explicit that we
|
|
379
|
+
* do not police attention: a phone in a pocket with audio playing is
|
|
380
|
+
* listening, and a phone in a hand with text on it is reading.
|
|
401
381
|
*
|
|
402
|
-
* `isListening`
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
* still count.
|
|
382
|
+
* `isListening` keeps its real job, which is a different question: whether to
|
|
383
|
+
* SYNTHESISE SPEECH. See getLanguagesWithActiveListeners. Speech is the most
|
|
384
|
+
* expensive thing we produce and nobody should pay to generate audio no one
|
|
385
|
+
* is playing. Text is already produced for the room either way.
|
|
407
386
|
*/
|
|
408
387
|
async getReceivingCount(roomId) {
|
|
409
388
|
const now = Date.now();
|
|
410
389
|
const listeners = await this.getListenersByRoom(roomId);
|
|
411
|
-
return listeners.filter(
|
|
412
|
-
// Present AND playing audio. Presence is the half that was missing:
|
|
413
|
-
// a listener who closed their phone kept the isListening flag they had
|
|
414
|
-
// when they left, so they went on being counted as receiving until the
|
|
415
|
-
// sweep removed them, and went on pushing a church's pass toward being
|
|
416
|
-
// spent. Nobody ever pauses audio on the way out of a building.
|
|
417
|
-
(l) => (0, listener_presence_1.isListenerPresent)(l.lastHeartbeat, l.isAway === true, now) && l.isListening !== false).length;
|
|
390
|
+
return listeners.filter((l) => (0, listener_presence_1.isListenerPresent)(l.lastHeartbeat, l.isAway === true, now)).length;
|
|
418
391
|
}
|
|
419
392
|
// ─── Per-room listener capacity (design B, 2026-07-09) ────────────────────
|
|
420
393
|
// The cap value is the room owner's plan `max_audience`, resolved ONCE by the
|