hci-atrium 0.1.0
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/LICENSE +21 -0
- package/README.md +182 -0
- package/dist/atrium.d.ts +522 -0
- package/dist/atrium.d.ts.map +1 -0
- package/dist/atrium.js +806 -0
- package/dist/atrium.js.map +1 -0
- package/dist/auth.d.ts +85 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +369 -0
- package/dist/auth.js.map +1 -0
- package/dist/by.d.ts +84 -0
- package/dist/by.d.ts.map +1 -0
- package/dist/by.js +87 -0
- package/dist/by.js.map +1 -0
- package/dist/decider.d.ts +40 -0
- package/dist/decider.d.ts.map +1 -0
- package/dist/decider.js +76 -0
- package/dist/decider.js.map +1 -0
- package/dist/discovery.d.ts +15 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +38 -0
- package/dist/discovery.js.map +1 -0
- package/dist/env.d.ts +11 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +28 -0
- package/dist/env.js.map +1 -0
- package/dist/errors.d.ts +67 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +86 -0
- package/dist/errors.js.map +1 -0
- package/dist/files.d.ts +7 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +2 -0
- package/dist/files.js.map +1 -0
- package/dist/http.d.ts +11 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +21 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/models.d.ts +456 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +487 -0
- package/dist/models.js.map +1 -0
- package/dist/pkce.d.ts +16 -0
- package/dist/pkce.d.ts.map +1 -0
- package/dist/pkce.js +40 -0
- package/dist/pkce.js.map +1 -0
- package/dist/react/index.d.ts +37 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +89 -0
- package/dist/react/index.js.map +1 -0
- package/dist/rooms/armed-set.d.ts +33 -0
- package/dist/rooms/armed-set.d.ts.map +1 -0
- package/dist/rooms/armed-set.js +65 -0
- package/dist/rooms/armed-set.js.map +1 -0
- package/dist/rooms/brokers.d.ts +62 -0
- package/dist/rooms/brokers.d.ts.map +1 -0
- package/dist/rooms/brokers.js +109 -0
- package/dist/rooms/brokers.js.map +1 -0
- package/dist/rooms/codec.d.ts +100 -0
- package/dist/rooms/codec.d.ts.map +1 -0
- package/dist/rooms/codec.js +101 -0
- package/dist/rooms/codec.js.map +1 -0
- package/dist/rooms/index.d.ts +40 -0
- package/dist/rooms/index.d.ts.map +1 -0
- package/dist/rooms/index.js +45 -0
- package/dist/rooms/index.js.map +1 -0
- package/dist/rooms/offer-capture.d.ts +96 -0
- package/dist/rooms/offer-capture.d.ts.map +1 -0
- package/dist/rooms/offer-capture.js +338 -0
- package/dist/rooms/offer-capture.js.map +1 -0
- package/dist/rooms/offer-transcription.d.ts +63 -0
- package/dist/rooms/offer-transcription.d.ts.map +1 -0
- package/dist/rooms/offer-transcription.js +231 -0
- package/dist/rooms/offer-transcription.js.map +1 -0
- package/dist/rooms/participant.d.ts +35 -0
- package/dist/rooms/participant.d.ts.map +1 -0
- package/dist/rooms/participant.js +53 -0
- package/dist/rooms/participant.js.map +1 -0
- package/dist/rooms/recorder.d.ts +74 -0
- package/dist/rooms/recorder.d.ts.map +1 -0
- package/dist/rooms/recorder.js +157 -0
- package/dist/rooms/recorder.js.map +1 -0
- package/dist/rooms/room.d.ts +542 -0
- package/dist/rooms/room.d.ts.map +1 -0
- package/dist/rooms/room.js +1357 -0
- package/dist/rooms/room.js.map +1 -0
- package/dist/rooms/snippet.d.ts +48 -0
- package/dist/rooms/snippet.d.ts.map +1 -0
- package/dist/rooms/snippet.js +70 -0
- package/dist/rooms/snippet.js.map +1 -0
- package/dist/rooms/sse.d.ts +102 -0
- package/dist/rooms/sse.d.ts.map +1 -0
- package/dist/rooms/sse.js +347 -0
- package/dist/rooms/sse.js.map +1 -0
- package/dist/rooms/standing-source.d.ts +190 -0
- package/dist/rooms/standing-source.d.ts.map +1 -0
- package/dist/rooms/standing-source.js +431 -0
- package/dist/rooms/standing-source.js.map +1 -0
- package/dist/rooms/transcriber.d.ts +81 -0
- package/dist/rooms/transcriber.d.ts.map +1 -0
- package/dist/rooms/transcriber.js +111 -0
- package/dist/rooms/transcriber.js.map +1 -0
- package/dist/rooms/transport-shared.d.ts +28 -0
- package/dist/rooms/transport-shared.d.ts.map +1 -0
- package/dist/rooms/transport-shared.js +42 -0
- package/dist/rooms/transport-shared.js.map +1 -0
- package/dist/rooms/transport.d.ts +161 -0
- package/dist/rooms/transport.d.ts.map +1 -0
- package/dist/rooms/transport.js +258 -0
- package/dist/rooms/transport.js.map +1 -0
- package/dist/shaping/assets.d.ts +42 -0
- package/dist/shaping/assets.d.ts.map +1 -0
- package/dist/shaping/assets.js +77 -0
- package/dist/shaping/assets.js.map +1 -0
- package/dist/shaping/chat.d.ts +39 -0
- package/dist/shaping/chat.d.ts.map +1 -0
- package/dist/shaping/chat.js +62 -0
- package/dist/shaping/chat.js.map +1 -0
- package/dist/shaping/index.d.ts +14 -0
- package/dist/shaping/index.d.ts.map +1 -0
- package/dist/shaping/index.js +13 -0
- package/dist/shaping/index.js.map +1 -0
- package/dist/shaping/library.d.ts +84 -0
- package/dist/shaping/library.d.ts.map +1 -0
- package/dist/shaping/library.js +288 -0
- package/dist/shaping/library.js.map +1 -0
- package/dist/shaping/request.d.ts +47 -0
- package/dist/shaping/request.d.ts.map +1 -0
- package/dist/shaping/request.js +25 -0
- package/dist/shaping/request.js.map +1 -0
- package/dist/shaping/rooms.d.ts +128 -0
- package/dist/shaping/rooms.d.ts.map +1 -0
- package/dist/shaping/rooms.js +330 -0
- package/dist/shaping/rooms.js.map +1 -0
- package/dist/shaping/sources.d.ts +29 -0
- package/dist/shaping/sources.d.ts.map +1 -0
- package/dist/shaping/sources.js +51 -0
- package/dist/shaping/sources.js.map +1 -0
- package/dist/shaping/structured.d.ts +47 -0
- package/dist/shaping/structured.d.ts.map +1 -0
- package/dist/shaping/structured.js +115 -0
- package/dist/shaping/structured.js.map +1 -0
- package/dist/storage.d.ts +36 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +86 -0
- package/dist/storage.js.map +1 -0
- package/dist/transcribe-stream.d.ts +81 -0
- package/dist/transcribe-stream.d.ts.map +1 -0
- package/dist/transcribe-stream.js +253 -0
- package/dist/transcribe-stream.js.map +1 -0
- package/dist/transcribe.d.ts +57 -0
- package/dist/transcribe.d.ts.map +1 -0
- package/dist/transcribe.js +267 -0
- package/dist/transcribe.js.map +1 -0
- package/dist/types.d.ts +20 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared harness for the two peer-toggleable standing offers (``offerTranscription`` /
|
|
3
|
+
* ``offerAudioCapture``) — the TypeScript port of Python's ``_StandingSource`` (aio.py). Everything
|
|
4
|
+
* the multi-mic scoping needs that isn't specific to *what* a source's frames do (transcribe vs
|
|
5
|
+
* record):
|
|
6
|
+
*
|
|
7
|
+
* * the {@link ArmedSet} and the ``maxSources`` cap admission ({@link StandingSource.tryArm}) plus
|
|
8
|
+
* warn-once cap logging;
|
|
9
|
+
* * the ``active`` roster + {@link StandingSource.status}, built on the **armed set** (not on audio
|
|
10
|
+
* flow — a source appears the moment it is armed; an all-arm enumerates every currently-connected
|
|
11
|
+
* ``capture-source`` from the presence roster, kept current on join/leave);
|
|
12
|
+
* * broadcast dedup + fire-and-forget spawn, over the REST ``/signal`` POST (the WS inbound path
|
|
13
|
+
* forces ``ephemeral: false``, so an ephemeral ``*.available`` broadcast must ride REST);
|
|
14
|
+
* * the departed-source grace bookkeeping and the **single** periodic sweeper (one timer — not a
|
|
15
|
+
* timer per departure) that finalizes/expires grace;
|
|
16
|
+
* * arm **migration** on a same-name reconnect so a per-source-armed device that drops and returns
|
|
17
|
+
* within grace stays admitted;
|
|
18
|
+
* * the generic scoped ``start`` / ``stopRequest`` shells, dispatching to per-offer hooks.
|
|
19
|
+
*
|
|
20
|
+
* Subclasses provide ``signal`` / ``logName`` / ``identityPrefix`` and override the hooks:
|
|
21
|
+
* {@link StandingSource.run}, {@link StandingSource.stop}, {@link StandingSource.stopSource},
|
|
22
|
+
* {@link StandingSource.beforeArm}, {@link StandingSource.onEngage}, {@link StandingSource.activeExtra},
|
|
23
|
+
* {@link StandingSource.onPresenceSource}, {@link StandingSource.onGraceExpired}.
|
|
24
|
+
*/
|
|
25
|
+
import { ArmedSet } from "./armed-set.js";
|
|
26
|
+
import { mergeRoles } from "./participant.js";
|
|
27
|
+
import { randomId } from "./room.js";
|
|
28
|
+
import { AUDIO_SNIPPET_CONSUMER_ROLE, CAPTURE_SOURCE_ROLE } from "./snippet.js";
|
|
29
|
+
import { RoomSocket } from "./transport.js";
|
|
30
|
+
/** Max clips buffered per source before an offered transcription's demux drops the *oldest* (live
|
|
31
|
+
* captions favour recency; a stalled engine must not grow memory without bound). */
|
|
32
|
+
export const OFFER_CLIP_QUEUE_MAX = 8;
|
|
33
|
+
/** Safety bound on the pre-demux frame queue — only BINARY frames are dropped (oldest-first) past
|
|
34
|
+
* this; control/presence/end frames are never dropped (they carry roster + teardown state). Generous
|
|
35
|
+
* because the demux normally drains it at once; it exists to cap a pathological producer flood. */
|
|
36
|
+
export const OFFER_EVENT_QUEUE_MAX = 256;
|
|
37
|
+
/** Consecutive failed recording-creates for one capture source before it is dropped. */
|
|
38
|
+
export const CAPTURE_CREATE_MAX_FAILURES = 3;
|
|
39
|
+
/** Default cap on concurrently-active sources per offer (multi-mic safety). */
|
|
40
|
+
export const OFFER_MAX_SOURCES = 8;
|
|
41
|
+
/** Grace window (seconds) an offered-capture recording stays open after its source departs, so a
|
|
42
|
+
* same-name reconnect within it resumes the very same file. Elapsed with no return → it finalizes. */
|
|
43
|
+
export const SOURCE_GRACE_SECONDS = 120;
|
|
44
|
+
/** How often the single grace sweeper wakes to scan departure timestamps (coarse — grace is minutes). */
|
|
45
|
+
export const GRACE_SWEEP_SECONDS = 5;
|
|
46
|
+
/** A minimal async mutex — serializes state transitions (start / stop / grace-finalize / disengage),
|
|
47
|
+
* the port of the offers' ``asyncio.Lock``. */
|
|
48
|
+
export class Mutex {
|
|
49
|
+
tail = Promise.resolve();
|
|
50
|
+
run(fn) {
|
|
51
|
+
const result = this.tail.then(fn);
|
|
52
|
+
this.tail = result.then(() => { }, () => { });
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// A monotonic clock in ms — the mirror of Python's ``time.monotonic()`` (the grace machinery's clock).
|
|
57
|
+
// Sub-millisecond resolution (unlike ``Date.now()``'s integer ms), so ``now - left`` is > 0 the instant
|
|
58
|
+
// after a departure even with grace 0 (an integer-ms clock could read equal in the same tick and never
|
|
59
|
+
// expire the source), and it's immune to wall-clock/NTP jumps. All grace timestamps use THIS, never
|
|
60
|
+
// ``Date.now()``, so the comparisons stay consistent.
|
|
61
|
+
function monotonicMs() {
|
|
62
|
+
const perf = globalThis.performance;
|
|
63
|
+
return perf?.now ? perf.now() : Date.now();
|
|
64
|
+
}
|
|
65
|
+
function announcedName(announce) {
|
|
66
|
+
const name = announce?.name;
|
|
67
|
+
return typeof name === "string" ? name : null;
|
|
68
|
+
}
|
|
69
|
+
function announcedRoles(announce) {
|
|
70
|
+
return mergeRoles(announce ?? {});
|
|
71
|
+
}
|
|
72
|
+
export class StandingSource {
|
|
73
|
+
signal = "standing.available";
|
|
74
|
+
logName = "standing offer";
|
|
75
|
+
identityPrefix = "standing-offer";
|
|
76
|
+
deps;
|
|
77
|
+
maxSources;
|
|
78
|
+
armed = new ArmedSet();
|
|
79
|
+
lock = new Mutex();
|
|
80
|
+
/** Grace window (seconds) — a per-instance override of {@link SOURCE_GRACE_SECONDS} (tests set 0). */
|
|
81
|
+
graceSeconds = SOURCE_GRACE_SECONDS;
|
|
82
|
+
/** Sweeper interval (seconds) — overridable for tests. */
|
|
83
|
+
sweepSeconds = GRACE_SWEEP_SECONDS;
|
|
84
|
+
ws = null;
|
|
85
|
+
runPromise = null;
|
|
86
|
+
settled = false;
|
|
87
|
+
err = null;
|
|
88
|
+
/** Set when the offer wound down because the room rotated its connect code (a controlled end, not
|
|
89
|
+
* an error — {@link error} stays ``null``). */
|
|
90
|
+
stoppedReason = null;
|
|
91
|
+
/** True once the frame stream ended via a ``room.code_rotated`` advance-signal / close. */
|
|
92
|
+
codeRotated = false;
|
|
93
|
+
/** Connection ids refused admission by the source cap — warned once each. */
|
|
94
|
+
cappedWarned = new Set();
|
|
95
|
+
/** Background broadcast promises spun off the (sync) presence hook — settled at teardown. */
|
|
96
|
+
bg = [];
|
|
97
|
+
/** The last ``(available, active)`` broadcast — dedupes the ``*.available`` signal. */
|
|
98
|
+
lastBroadcast = null;
|
|
99
|
+
/** connection id → last-known announced name, kept across a leave for the grace window. */
|
|
100
|
+
sourceNames = new Map();
|
|
101
|
+
/** departed connection id → leave time (ms) — the grace window a same-name return resumes within. */
|
|
102
|
+
departed = new Map();
|
|
103
|
+
/** The offer's OWN point-in-time roster (name + roles), seeded from the ready snapshot and folded
|
|
104
|
+
* in loop order by {@link onPresence} — the port of ``RoomWebSocket``'s own ``_names`` / ``_roles``.
|
|
105
|
+
* Read (not the transport's live roster) so a burst of presence + binary resolves each frame's
|
|
106
|
+
* source against the roster *as it was when that frame was processed*. */
|
|
107
|
+
roster = new Map();
|
|
108
|
+
rosterSeeded = false;
|
|
109
|
+
eventQueue = [];
|
|
110
|
+
// Count of BINARY frames currently in eventQueue — the drop-oldest bound is on binary only.
|
|
111
|
+
binaryQueued = 0;
|
|
112
|
+
eventWake = null;
|
|
113
|
+
sweepTimer = null;
|
|
114
|
+
constructor(deps, maxSources = OFFER_MAX_SOURCES) {
|
|
115
|
+
this.deps = deps;
|
|
116
|
+
this.maxSources = maxSources;
|
|
117
|
+
}
|
|
118
|
+
// --- state ---------------------------------------------------------------------------------------
|
|
119
|
+
/** True while the offer is engaged (the snippet subscription loop runs). */
|
|
120
|
+
get alive() {
|
|
121
|
+
return this.runPromise !== null && !this.settled;
|
|
122
|
+
}
|
|
123
|
+
/** The error that ended the offer for good (``null`` while armed or after ``stop()``). */
|
|
124
|
+
get error() {
|
|
125
|
+
return this.err;
|
|
126
|
+
}
|
|
127
|
+
/** Availability as it rides ``<service>.status`` / ``<service>.available``. The default
|
|
128
|
+
* (transcription) is available unless the loop died on a terminal error; capture overrides to also
|
|
129
|
+
* require an active recorder. */
|
|
130
|
+
available() {
|
|
131
|
+
return !(this.err !== null && !this.alive);
|
|
132
|
+
}
|
|
133
|
+
isDeparted(cid) {
|
|
134
|
+
const left = this.departed.get(cid);
|
|
135
|
+
return left !== undefined && monotonicMs() - left <= this.graceSeconds * 1000;
|
|
136
|
+
}
|
|
137
|
+
// --- the active roster (the ARMED set, not the audio-flow set) -----------------------------------
|
|
138
|
+
nameFor(cid) {
|
|
139
|
+
return this.roster.get(cid)?.name ?? null;
|
|
140
|
+
}
|
|
141
|
+
connectionIdsWithRole(role) {
|
|
142
|
+
const out = [];
|
|
143
|
+
for (const [cid, entry] of this.roster)
|
|
144
|
+
if (entry.roles.includes(role))
|
|
145
|
+
out.push(cid);
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
armedActive() {
|
|
149
|
+
const ids = new Set([...this.armed.explicit].filter((c) => !this.isDeparted(c)));
|
|
150
|
+
if (this.armed.allArmed && this.ws) {
|
|
151
|
+
for (const cid of this.connectionIdsWithRole(CAPTURE_SOURCE_ROLE)) {
|
|
152
|
+
if (this.armed.isArmed(cid))
|
|
153
|
+
ids.add(cid);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return [...ids].sort().map((cid) => ({
|
|
157
|
+
source: cid,
|
|
158
|
+
name: this.nameFor(cid) ?? this.sourceNames.get(cid) ?? null,
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
/** Per-offer additions to the armed roster (the honesty entry for a v1 id-less source). */
|
|
162
|
+
activeExtra() {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
active() {
|
|
166
|
+
return [...this.armedActive(), ...this.activeExtra()];
|
|
167
|
+
}
|
|
168
|
+
/** The ``<service>.status`` reply payload (sans the outer ``ok``). */
|
|
169
|
+
status() {
|
|
170
|
+
return { available: this.available(), active: this.active() };
|
|
171
|
+
}
|
|
172
|
+
// --- broadcast -----------------------------------------------------------------------------------
|
|
173
|
+
async broadcast() {
|
|
174
|
+
const available = this.available();
|
|
175
|
+
const active = this.active();
|
|
176
|
+
const snapshot = JSON.stringify([available, active.map((a) => [a.source, a.name])]);
|
|
177
|
+
if (snapshot === this.lastBroadcast)
|
|
178
|
+
return;
|
|
179
|
+
this.lastBroadcast = snapshot;
|
|
180
|
+
try {
|
|
181
|
+
await this.deps.publishSignal(this.signal, { available, active }, { ephemeral: true });
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// best-effort — a teardown broadcast may race the transport
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
clearLastBroadcast() {
|
|
188
|
+
this.lastBroadcast = null;
|
|
189
|
+
}
|
|
190
|
+
spawnBroadcast() {
|
|
191
|
+
this.bg.push(this.broadcast());
|
|
192
|
+
}
|
|
193
|
+
// --- cap admission -------------------------------------------------------------------------------
|
|
194
|
+
tryArm(source, armAll) {
|
|
195
|
+
if (!armAll &&
|
|
196
|
+
source !== null &&
|
|
197
|
+
!this.armed.explicit.has(source) &&
|
|
198
|
+
this.armed.explicit.size >= this.maxSources) {
|
|
199
|
+
throw new Error(`too many active sources (max ${this.maxSources})`);
|
|
200
|
+
}
|
|
201
|
+
if (armAll || source === null)
|
|
202
|
+
this.armed.armAll();
|
|
203
|
+
else
|
|
204
|
+
this.armed.arm(source);
|
|
205
|
+
}
|
|
206
|
+
warnCapped(cid) {
|
|
207
|
+
if (this.cappedWarned.has(cid))
|
|
208
|
+
return;
|
|
209
|
+
this.cappedWarned.add(cid);
|
|
210
|
+
console.warn(`Atrium ${this.logName} for room ${this.deps.roomId}: too many active sources ` +
|
|
211
|
+
`(max ${this.maxSources}) — dropping frames from ${JSON.stringify(cid)}`);
|
|
212
|
+
}
|
|
213
|
+
// --- the shared WS setup + grace sweeper ---------------------------------------------------------
|
|
214
|
+
/** Open the snippet-subscription WebSocket, seed the departed-name roster, and launch the single
|
|
215
|
+
* grace sweeper. Each ``run`` calls this at its top. */
|
|
216
|
+
async openWs() {
|
|
217
|
+
const socket = new RoomSocket({
|
|
218
|
+
roomId: this.deps.roomId,
|
|
219
|
+
code: this.deps.getCode(),
|
|
220
|
+
baseUrl: this.deps.baseUrl,
|
|
221
|
+
identity: `${this.identityPrefix}:${randomId().slice(0, 8)}`,
|
|
222
|
+
announce: { role: AUDIO_SNIPPET_CONSUMER_ROLE },
|
|
223
|
+
reconnect: this.deps.reconnect,
|
|
224
|
+
WebSocketCtor: this.deps.WebSocketCtor,
|
|
225
|
+
// Seed the offer's own roster from the ready snapshot (fires during the ``ready`` message,
|
|
226
|
+
// before any later frame) — so a synchronous burst can't move the transport's roster ahead of
|
|
227
|
+
// this seed. Folding thereafter is the loop's job (below).
|
|
228
|
+
onReady: () => this.seedRoster(socket),
|
|
229
|
+
onBinary: (data) => this.pushEvent({ kind: "binary", data }),
|
|
230
|
+
onSignal: (sig) => this.foldPresence(sig),
|
|
231
|
+
onEnd: (reason) => {
|
|
232
|
+
if (reason === "code-rotated")
|
|
233
|
+
this.codeRotated = true;
|
|
234
|
+
this.pushEvent({ kind: "end" });
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
this.ws = socket;
|
|
238
|
+
await socket.connect();
|
|
239
|
+
this.startSweeper();
|
|
240
|
+
return socket;
|
|
241
|
+
}
|
|
242
|
+
seedRoster(socket) {
|
|
243
|
+
if (this.rosterSeeded)
|
|
244
|
+
return;
|
|
245
|
+
this.rosterSeeded = true;
|
|
246
|
+
for (const p of socket.participants()) {
|
|
247
|
+
this.roster.set(p.connectionId, {
|
|
248
|
+
name: announcedName(p.announce),
|
|
249
|
+
roles: announcedRoles(p.announce),
|
|
250
|
+
});
|
|
251
|
+
this.sourceNames.set(p.connectionId, announcedName(p.announce));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
foldPresence(sig) {
|
|
255
|
+
const payload = sig.payload;
|
|
256
|
+
const cid = payload?.connection_id;
|
|
257
|
+
if (typeof cid !== "string")
|
|
258
|
+
return;
|
|
259
|
+
if (sig.type === "presence.join") {
|
|
260
|
+
this.pushEvent({
|
|
261
|
+
kind: "presence",
|
|
262
|
+
event: "join",
|
|
263
|
+
cid,
|
|
264
|
+
announce: payload?.announce,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
else if (sig.type === "presence.leave") {
|
|
268
|
+
this.pushEvent({ kind: "presence", event: "leave", cid });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
pushEvent(ev) {
|
|
272
|
+
if (ev.kind === "binary") {
|
|
273
|
+
if (this.binaryQueued >= OFFER_EVENT_QUEUE_MAX) {
|
|
274
|
+
// Drop the OLDEST binary frame (recency over completeness) — never a control/presence/end
|
|
275
|
+
// frame (those carry roster + teardown state the loop must see).
|
|
276
|
+
const idx = this.eventQueue.findIndex((e) => e.kind === "binary");
|
|
277
|
+
if (idx >= 0) {
|
|
278
|
+
this.eventQueue.splice(idx, 1);
|
|
279
|
+
this.binaryQueued -= 1;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
this.binaryQueued += 1;
|
|
283
|
+
}
|
|
284
|
+
this.eventQueue.push(ev);
|
|
285
|
+
this.eventWake?.();
|
|
286
|
+
this.eventWake = null;
|
|
287
|
+
}
|
|
288
|
+
/** Yield the snippet-consumer socket's binary frames while folding ``presence.join`` /
|
|
289
|
+
* ``presence.leave`` into the live roster + arm migration (the port of Python's
|
|
290
|
+
* ``RoomWebSocket.frames(on_presence=...)``). Ends when the socket ends. */
|
|
291
|
+
async *frames() {
|
|
292
|
+
while (true) {
|
|
293
|
+
while (this.eventQueue.length > 0) {
|
|
294
|
+
const item = this.eventQueue.shift();
|
|
295
|
+
if (item.kind === "binary") {
|
|
296
|
+
this.binaryQueued -= 1;
|
|
297
|
+
yield item.data;
|
|
298
|
+
}
|
|
299
|
+
else if (item.kind === "presence") {
|
|
300
|
+
this.onPresence(item.event, item.cid, item.event === "join" ? item.announce : undefined);
|
|
301
|
+
}
|
|
302
|
+
else
|
|
303
|
+
return; // end sentinel
|
|
304
|
+
}
|
|
305
|
+
await new Promise((resolve) => {
|
|
306
|
+
this.eventWake = resolve;
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
startSweeper() {
|
|
311
|
+
if (this.sweepTimer !== null)
|
|
312
|
+
return;
|
|
313
|
+
this.sweepTimer = setInterval(() => void this.sweepOnce(), this.sweepSeconds * 1000);
|
|
314
|
+
this.sweepTimer.unref?.();
|
|
315
|
+
}
|
|
316
|
+
stopSweeper() {
|
|
317
|
+
if (this.sweepTimer !== null) {
|
|
318
|
+
clearInterval(this.sweepTimer);
|
|
319
|
+
this.sweepTimer = null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/** One grace pass: finalize/expire each source whose grace window elapsed with no return. Public
|
|
323
|
+
* so a test can drive a pass directly (mirror of Python's ``_sweep_once``). */
|
|
324
|
+
async sweepOnce() {
|
|
325
|
+
const now = monotonicMs();
|
|
326
|
+
const graceMs = this.graceSeconds * 1000;
|
|
327
|
+
const expired = [...this.departed]
|
|
328
|
+
.filter(([, left]) => now - left > graceMs)
|
|
329
|
+
.map(([cid]) => cid);
|
|
330
|
+
for (const cid of expired) {
|
|
331
|
+
await this.onGraceExpired(cid);
|
|
332
|
+
this.departed.delete(cid);
|
|
333
|
+
this.sourceNames.delete(cid);
|
|
334
|
+
this.armed.explicit.delete(cid); // a departed per-source arm that never came back
|
|
335
|
+
}
|
|
336
|
+
if (expired.length > 0)
|
|
337
|
+
await this.broadcast();
|
|
338
|
+
}
|
|
339
|
+
/** Per-offer grace-expiry action (capture finalizes the departed recording; default: none). */
|
|
340
|
+
async onGraceExpired(_cid) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
// --- the presence hook (name tracking + arm migration + re-broadcast) ----------------------------
|
|
344
|
+
onPresence(event, cid, announce) {
|
|
345
|
+
if (event === "join") {
|
|
346
|
+
const name = announcedName(announce);
|
|
347
|
+
this.roster.set(cid, { name, roles: announcedRoles(announce) });
|
|
348
|
+
this.sourceNames.set(cid, name);
|
|
349
|
+
this.migrateArmOnRejoin(cid, name);
|
|
350
|
+
this.departed.delete(cid); // a same-id return is live again
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
// Drop the roster entry (so an all-arm stops enumerating it) but keep sourceNames for the grace
|
|
354
|
+
// window — a departed source still resolves a human label + arm-migration match.
|
|
355
|
+
this.roster.delete(cid);
|
|
356
|
+
this.departed.set(cid, monotonicMs());
|
|
357
|
+
}
|
|
358
|
+
this.cappedWarned.delete(cid);
|
|
359
|
+
this.onPresenceSource(event, cid);
|
|
360
|
+
this.spawnBroadcast();
|
|
361
|
+
}
|
|
362
|
+
migrateArmOnRejoin(newCid, name) {
|
|
363
|
+
if (!name)
|
|
364
|
+
return;
|
|
365
|
+
for (const oldCid of [...this.armed.explicit]) {
|
|
366
|
+
if (oldCid === newCid)
|
|
367
|
+
continue;
|
|
368
|
+
if (this.isDeparted(oldCid) && this.sourceNames.get(oldCid) === name) {
|
|
369
|
+
this.armed.migrate(oldCid, newCid);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/** Per-offer presence bookkeeping (transcription reaps the source's engine on leave; capture
|
|
375
|
+
* leans on the shared grace machinery, so it's a no-op). Default: none. */
|
|
376
|
+
onPresenceSource(_event, _cid) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
// --- the generic scoped start / stop shells ------------------------------------------------------
|
|
380
|
+
/** Arm a source (idempotent) and launch the frame loop on the first arm. Scoped to ``source``
|
|
381
|
+
* unless ``all`` (or an unscoped ``source=null`` — the v1 rule). Refuses a new per-source arm past
|
|
382
|
+
* the cap; a per-offer {@link beforeArm} may refuse for another reason (capture: no recorder).
|
|
383
|
+
* Broadcasts the armed-set change immediately, so a device sees itself active before a frame. */
|
|
384
|
+
async start({ source = null, all = false, } = {}) {
|
|
385
|
+
await this.lock.run(async () => {
|
|
386
|
+
// Refuse before arming/engaging if the handle is SSE-latched — binary audio can't ride SSE, so
|
|
387
|
+
// opening the snippet socket would spin a doomed WS reconnect. The `<service>.start` handler
|
|
388
|
+
// turns this into a clean `{ error }` reply (like the "no active recorder" refusal).
|
|
389
|
+
this.deps.requireWsAudio?.();
|
|
390
|
+
const ctx = await this.beforeArm(all);
|
|
391
|
+
this.tryArm(source, all);
|
|
392
|
+
if (this.runPromise === null || this.settled) {
|
|
393
|
+
this.onEngage(ctx);
|
|
394
|
+
this.err = null;
|
|
395
|
+
this.settled = false;
|
|
396
|
+
this.runPromise = this.run().finally(() => {
|
|
397
|
+
this.settled = true;
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
await this.broadcast();
|
|
402
|
+
}
|
|
403
|
+
/** Per-offer pre-arm check, returning a context passed to {@link onEngage} on first launch. */
|
|
404
|
+
async beforeArm(_armAll) {
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
/** Per-offer first-launch setup under the lock (capture binds the recorder; else none). */
|
|
408
|
+
onEngage(_ctx) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
/** The wire ``<service>.stop``. ``all`` (or an unscoped call) is the full teardown; otherwise
|
|
412
|
+
* disarm ``source`` alone, leaving the rest. */
|
|
413
|
+
async stopRequest({ source = null, all = false, } = {}) {
|
|
414
|
+
if (all || source === null) {
|
|
415
|
+
await this.stop();
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
await this.stopSource(source);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (typeof Symbol.dispose === "symbol") {
|
|
422
|
+
StandingSource.prototype[Symbol.dispose] = function () {
|
|
423
|
+
void this.stop();
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
if (typeof Symbol.asyncDispose === "symbol") {
|
|
427
|
+
StandingSource.prototype[Symbol.asyncDispose] = async function () {
|
|
428
|
+
await this.stop();
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
//# sourceMappingURL=standing-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standing-source.js","sourceRoot":"","sources":["../../src/rooms/standing-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A running transcription of a room's live audio into ``transcript.*`` signals — the handle
|
|
3
|
+
* {@link ./room.Room.transcribe} returns. The TypeScript port of Python's ``_AsyncTranscriber`` +
|
|
4
|
+
* ``_publish_room_transcription`` (aio.py).
|
|
5
|
+
*
|
|
6
|
+
* It consumes the room's shared ``audio.snippet`` stream, pushes it through the client's shared
|
|
7
|
+
* transcription engine (on *your* credentials/compute), and publishes back:
|
|
8
|
+
*
|
|
9
|
+
* * ``transcript.partial`` — ``{ text }``, **ephemeral** (a live caption the recorder skips).
|
|
10
|
+
* Wholesale-replaced — render the latest, don't append.
|
|
11
|
+
* * ``transcript.final`` — ``{ text, start, end, seq }``, durable (a {@link ./recorder.Recorder}
|
|
12
|
+
* bridge persists it — no new recording API). ``seq`` is a per-handle counter from 0; the signal's
|
|
13
|
+
* ``source`` is ``"transcriber"``.
|
|
14
|
+
*
|
|
15
|
+
* A **side effect**, like {@link ./room.Room.record}: it starts in the background and is stopped when
|
|
16
|
+
* the room closes, or via ``await handle.stop()`` / ``await using``. A fatal error (a rejected
|
|
17
|
+
* credential, the snippet stream refused) ends it and is held on {@link Transcriber.error}.
|
|
18
|
+
*/
|
|
19
|
+
import type { Transcript } from "../models.js";
|
|
20
|
+
/** The signal-level ``source`` every ``transcript.*`` message carries. */
|
|
21
|
+
export declare const TRANSCRIBER_SOURCE = "transcriber";
|
|
22
|
+
/** What a {@link Transcriber} is wired with (filled by {@link ./room.Room.transcribe}). */
|
|
23
|
+
export interface TranscriberDeps {
|
|
24
|
+
model?: string;
|
|
25
|
+
language?: string;
|
|
26
|
+
/** The room's raw snippet bytes (a dedicated consumer socket); ends when ``signal`` aborts. */
|
|
27
|
+
snippetBytes(signal: AbortSignal): AsyncIterable<ArrayBuffer>;
|
|
28
|
+
/** The client's shared transcription engine over those bytes. */
|
|
29
|
+
engine(clips: AsyncIterable<ArrayBuffer>, opts: {
|
|
30
|
+
model?: string;
|
|
31
|
+
language?: string;
|
|
32
|
+
signal?: AbortSignal;
|
|
33
|
+
}): AsyncIterable<Transcript>;
|
|
34
|
+
/** Publish one ``transcript.*`` signal back into the room (as ``source: "transcriber"``). */
|
|
35
|
+
publish(type: string, payload: Record<string, unknown>, opts: {
|
|
36
|
+
ephemeral?: boolean;
|
|
37
|
+
}): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
export declare class Transcriber {
|
|
40
|
+
private readonly deps;
|
|
41
|
+
private readonly abort;
|
|
42
|
+
private loop;
|
|
43
|
+
private settled;
|
|
44
|
+
private stopped;
|
|
45
|
+
private _error;
|
|
46
|
+
constructor(deps: TranscriberDeps);
|
|
47
|
+
/** True while the transcription loop runs. */
|
|
48
|
+
get alive(): boolean;
|
|
49
|
+
/** The error that ended the transcription for good (``null`` while alive or after a clean stop). */
|
|
50
|
+
get error(): Error | null;
|
|
51
|
+
/** Begin transcribing (idempotent). */
|
|
52
|
+
start(): void;
|
|
53
|
+
private run;
|
|
54
|
+
/** Stop transcribing (idempotent) — aborts the loop, closing the snippet stream and the engine. */
|
|
55
|
+
stop(): Promise<void>;
|
|
56
|
+
[Symbol.asyncDispose]: () => Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
/** The one body behind {@link Transcriber} and every per-source engine of
|
|
59
|
+
* {@link ./offer-transcription.OfferedTranscription} — the shared port of Python's
|
|
60
|
+
* ``_publish_room_transcription``. Runs the shared transcription ``engine`` over ``clips`` and
|
|
61
|
+
* publishes each result: ``transcript.final`` (``{ text, start, end, seq, …attribution }``, durable —
|
|
62
|
+
* ``seq`` counts finals from 0) and ``transcript.partial`` (``{ text, …attribution }``, ephemeral live
|
|
63
|
+
* caption). ``attribution`` adds the producer's ``source`` / ``source_name`` for the offer's
|
|
64
|
+
* per-source publishes; the plain in-room transcriber leaves it empty (byte-identical to its former
|
|
65
|
+
* inline body). */
|
|
66
|
+
export declare function publishRoomTranscripts(opts: {
|
|
67
|
+
clips: AsyncIterable<ArrayBuffer>;
|
|
68
|
+
engine: (clips: AsyncIterable<ArrayBuffer>, o: {
|
|
69
|
+
model?: string;
|
|
70
|
+
language?: string;
|
|
71
|
+
signal?: AbortSignal;
|
|
72
|
+
}) => AsyncIterable<Transcript>;
|
|
73
|
+
publish: (type: string, payload: Record<string, unknown>, o: {
|
|
74
|
+
ephemeral?: boolean;
|
|
75
|
+
}) => Promise<void>;
|
|
76
|
+
model?: string;
|
|
77
|
+
language?: string;
|
|
78
|
+
signal?: AbortSignal;
|
|
79
|
+
attribution?: Record<string, unknown>;
|
|
80
|
+
}): Promise<void>;
|
|
81
|
+
//# sourceMappingURL=transcriber.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcriber.d.ts","sourceRoot":"","sources":["../../src/rooms/transcriber.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,0EAA0E;AAC1E,eAAO,MAAM,kBAAkB,gBAAgB,CAAA;AAE/C,2FAA2F;AAC3F,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+FAA+F;IAC/F,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,aAAa,CAAC,WAAW,CAAC,CAAA;IAC7D,iEAAiE;IACjE,MAAM,CACJ,KAAK,EAAE,aAAa,CAAC,WAAW,CAAC,EACjC,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAChE,aAAa,CAAC,UAAU,CAAC,CAAA;IAC5B,6FAA6F;IAC7F,OAAO,CACL,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,IAAI,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,IAAI,CAAC,CAAA;CACjB;AAED,qBAAa,WAAW;IAOV,OAAO,CAAC,QAAQ,CAAC,IAAI;IANjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwB;IAC9C,OAAO,CAAC,IAAI,CAA6B;IACzC,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,MAAM,CAAqB;gBAEN,IAAI,EAAE,eAAe;IAElD,8CAA8C;IAC9C,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,oGAAoG;IACpG,IAAI,KAAK,IAAI,KAAK,GAAG,IAAI,CAExB;IAED,uCAAuC;IACvC,KAAK,IAAI,IAAI;YAIC,GAAG;IAqBjB,mGAAmG;IAC7F,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAOnB,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACnD;AAQD;;;;;;;mBAOmB;AACnB,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,KAAK,EAAE,aAAa,CAAC,WAAW,CAAC,CAAA;IACjC,MAAM,EAAE,CACN,KAAK,EAAE,aAAa,CAAC,WAAW,CAAC,EACjC,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,KAC3D,aAAa,CAAC,UAAU,CAAC,CAAA;IAC9B,OAAO,EAAE,CACP,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,KACvB,OAAO,CAAC,IAAI,CAAC,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACtC,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BhB"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A running transcription of a room's live audio into ``transcript.*`` signals — the handle
|
|
3
|
+
* {@link ./room.Room.transcribe} returns. The TypeScript port of Python's ``_AsyncTranscriber`` +
|
|
4
|
+
* ``_publish_room_transcription`` (aio.py).
|
|
5
|
+
*
|
|
6
|
+
* It consumes the room's shared ``audio.snippet`` stream, pushes it through the client's shared
|
|
7
|
+
* transcription engine (on *your* credentials/compute), and publishes back:
|
|
8
|
+
*
|
|
9
|
+
* * ``transcript.partial`` — ``{ text }``, **ephemeral** (a live caption the recorder skips).
|
|
10
|
+
* Wholesale-replaced — render the latest, don't append.
|
|
11
|
+
* * ``transcript.final`` — ``{ text, start, end, seq }``, durable (a {@link ./recorder.Recorder}
|
|
12
|
+
* bridge persists it — no new recording API). ``seq`` is a per-handle counter from 0; the signal's
|
|
13
|
+
* ``source`` is ``"transcriber"``.
|
|
14
|
+
*
|
|
15
|
+
* A **side effect**, like {@link ./room.Room.record}: it starts in the background and is stopped when
|
|
16
|
+
* the room closes, or via ``await handle.stop()`` / ``await using``. A fatal error (a rejected
|
|
17
|
+
* credential, the snippet stream refused) ends it and is held on {@link Transcriber.error}.
|
|
18
|
+
*/
|
|
19
|
+
import { asError } from "../http.js";
|
|
20
|
+
/** The signal-level ``source`` every ``transcript.*`` message carries. */
|
|
21
|
+
export const TRANSCRIBER_SOURCE = "transcriber";
|
|
22
|
+
export class Transcriber {
|
|
23
|
+
deps;
|
|
24
|
+
abort = new AbortController();
|
|
25
|
+
loop = null;
|
|
26
|
+
settled = false;
|
|
27
|
+
stopped = false;
|
|
28
|
+
_error = null;
|
|
29
|
+
constructor(deps) {
|
|
30
|
+
this.deps = deps;
|
|
31
|
+
}
|
|
32
|
+
/** True while the transcription loop runs. */
|
|
33
|
+
get alive() {
|
|
34
|
+
return this.loop !== null && !this.settled;
|
|
35
|
+
}
|
|
36
|
+
/** The error that ended the transcription for good (``null`` while alive or after a clean stop). */
|
|
37
|
+
get error() {
|
|
38
|
+
return this._error;
|
|
39
|
+
}
|
|
40
|
+
/** Begin transcribing (idempotent). */
|
|
41
|
+
start() {
|
|
42
|
+
if (!this.loop)
|
|
43
|
+
this.loop = this.run();
|
|
44
|
+
}
|
|
45
|
+
async run() {
|
|
46
|
+
try {
|
|
47
|
+
await publishRoomTranscripts({
|
|
48
|
+
clips: this.deps.snippetBytes(this.abort.signal),
|
|
49
|
+
engine: this.deps.engine,
|
|
50
|
+
publish: this.deps.publish,
|
|
51
|
+
model: this.deps.model,
|
|
52
|
+
language: this.deps.language,
|
|
53
|
+
signal: this.abort.signal,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
// A deliberate stop() aborts the engine cleanly; only an unexpected failure is terminal.
|
|
58
|
+
if (!this.stopped) {
|
|
59
|
+
this._error = asError(err);
|
|
60
|
+
console.error("Atrium transcriber died:", err);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
this.settled = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Stop transcribing (idempotent) — aborts the loop, closing the snippet stream and the engine. */
|
|
68
|
+
async stop() {
|
|
69
|
+
this.stopped = true;
|
|
70
|
+
this.abort.abort();
|
|
71
|
+
if (this.loop)
|
|
72
|
+
await this.loop.catch(() => { });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (typeof Symbol.asyncDispose === "symbol") {
|
|
76
|
+
Transcriber.prototype[Symbol.asyncDispose] = async function () {
|
|
77
|
+
await this.stop();
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** The one body behind {@link Transcriber} and every per-source engine of
|
|
81
|
+
* {@link ./offer-transcription.OfferedTranscription} — the shared port of Python's
|
|
82
|
+
* ``_publish_room_transcription``. Runs the shared transcription ``engine`` over ``clips`` and
|
|
83
|
+
* publishes each result: ``transcript.final`` (``{ text, start, end, seq, …attribution }``, durable —
|
|
84
|
+
* ``seq`` counts finals from 0) and ``transcript.partial`` (``{ text, …attribution }``, ephemeral live
|
|
85
|
+
* caption). ``attribution`` adds the producer's ``source`` / ``source_name`` for the offer's
|
|
86
|
+
* per-source publishes; the plain in-room transcriber leaves it empty (byte-identical to its former
|
|
87
|
+
* inline body). */
|
|
88
|
+
export async function publishRoomTranscripts(opts) {
|
|
89
|
+
let seq = 0;
|
|
90
|
+
const attribution = opts.attribution ?? {};
|
|
91
|
+
for await (const said of opts.engine(opts.clips, {
|
|
92
|
+
model: opts.model,
|
|
93
|
+
language: opts.language,
|
|
94
|
+
signal: opts.signal,
|
|
95
|
+
})) {
|
|
96
|
+
if (said.final) {
|
|
97
|
+
await opts.publish("transcript.final", {
|
|
98
|
+
text: said.text,
|
|
99
|
+
start: said.raw.start ?? null,
|
|
100
|
+
end: said.raw.end ?? null,
|
|
101
|
+
seq,
|
|
102
|
+
...attribution,
|
|
103
|
+
}, {});
|
|
104
|
+
seq += 1;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
await opts.publish("transcript.partial", { text: said.text, ...attribution }, { ephemeral: true });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=transcriber.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcriber.js","sourceRoot":"","sources":["../../src/rooms/transcriber.ts"],"names":[],"mappings":"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"}
|