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 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rooms/index.ts"],"names":[],"mappings":"AAAA;qEACqE;AAKrE,OAAO,EAAE,IAAI,EAAiB,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAA;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EACL,KAAK,gBAAgB,EACrB,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,uBAAuB,EACvB,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,UAAU,EACV,YAAY,EACZ,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,GACb,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAChG,OAAO,EAAE,oBAAoB,EAAE,KAAK,sBAAsB,EAAE,MAAM,0BAA0B,CAAA;AAC5F,OAAO,EACL,UAAU,EACV,KAAK,WAAW,EAChB,KAAK,oBAAoB,EACzB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,QAAQ,EAAE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAA;AACjE,OAAO,EACL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,IAAI,EACJ,KAAK,eAAe,EACpB,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,iBAAiB,EACtB,KAAK,WAAW,GACjB,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,EACnB,yBAAyB,EACzB,qBAAqB,EACrB,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,KAAK,SAAS,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAC7E,OAAO,EACL,KAAK,YAAY,EACjB,iBAAiB,EACjB,KAAK,SAAS,EACd,cAAc,GACf,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACxF,YAAY,EACV,aAAa,EACb,UAAU,EACV,aAAa,EACb,WAAW,EACX,aAAa,EACb,aAAa,GACd,MAAM,gBAAgB,CAAA;AAEvB,8FAA8F;AAC9F,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,iGAAiG;IACjG,SAAS,CAAC,EAAE,iBAAiB,CAAA;IAC7B,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,+EAA+E;IAC/E,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBtF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** The live rooms surface — the {@link Room} handle, its audio codec, and the loginless
|
|
2
|
+
* {@link joinRoom} entry (no client needed, just a connect code). */
|
|
3
|
+
import { DEFAULT_BASE_URL, readEnv } from "../env.js";
|
|
4
|
+
import { raiseForStatus } from "../errors.js";
|
|
5
|
+
import { httpFetch } from "../http.js";
|
|
6
|
+
import { Room } from "./room.js";
|
|
7
|
+
export { ArmedSet } from "./armed-set.js";
|
|
8
|
+
export { BrokerError, CAPTURE_OFFER_ROLE, CHAT_BROKER_ROLE, DEFAULT_CHAT_MAX_TOKENS, DEFAULT_MAX_ANCHORS, EMBED_BROKER_ROLE, parseAnchorDescriptor, TRANSCRIPTION_OFFER_ROLE, validateChatMessages, } from "./brokers.js";
|
|
9
|
+
export { AUDIO_ROLE, AUDIO_SIGNAL, decodeAudioFrame, encodeAudioFrame, isAudioFrame, } from "./codec.js";
|
|
10
|
+
export { OfferedCapture } from "./offer-capture.js";
|
|
11
|
+
export { OfferedTranscription } from "./offer-transcription.js";
|
|
12
|
+
export { mergeRoles, parseParticipant, participantMatches, } from "./participant.js";
|
|
13
|
+
export { Recorder } from "./recorder.js";
|
|
14
|
+
export { Room, } from "./room.js";
|
|
15
|
+
export { AUDIO_SNIPPET_CONSUMER_ROLE, AUDIO_SNIPPET_MAGIC, AUDIO_SNIPPET_SOURCE_ROLE, AUDIO_SNIPPET_VERSION, AUDIO_SNIPPET_VERSION_2, CAPTURE_SOURCE_ROLE, decodeAudioSnippetFrame, isAudioSnippetFrame, } from "./snippet.js";
|
|
16
|
+
export { RoomEventStream, readObserveRoster } from "./sse.js";
|
|
17
|
+
export { OFFER_MAX_SOURCES, StandingSource, } from "./standing-source.js";
|
|
18
|
+
export { TRANSCRIBER_SOURCE, Transcriber } from "./transcriber.js";
|
|
19
|
+
/**
|
|
20
|
+
* Join a room by its connect code — **no account, no client**. Resolves the code to a room, then
|
|
21
|
+
* returns a live {@link Room} handle that can ``send`` / ``on`` / ``request`` / play / consume audio
|
|
22
|
+
* signals (but not manage the room — that needs the owner). Mirrors the Python SDK's ``join_room``.
|
|
23
|
+
*/
|
|
24
|
+
export async function joinRoom(code, opts = {}) {
|
|
25
|
+
const baseUrl = (opts.baseUrl ?? readEnv("ATRIUM_BASE_URL") ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
26
|
+
const res = await httpFetch(`${baseUrl}/v1/rooms/resolve?code=${encodeURIComponent(code)}`);
|
|
27
|
+
if (!res.ok)
|
|
28
|
+
await raiseForStatus(res);
|
|
29
|
+
const data = (await res.json());
|
|
30
|
+
const deps = {
|
|
31
|
+
id: String(data.id),
|
|
32
|
+
connectCode: data.connect_code ?? code,
|
|
33
|
+
baseUrl,
|
|
34
|
+
identity: opts.identity,
|
|
35
|
+
announce: opts.announce,
|
|
36
|
+
requestTimeout: opts.requestTimeout,
|
|
37
|
+
reconnect: opts.reconnect,
|
|
38
|
+
transport: opts.transport,
|
|
39
|
+
WebSocketCtor: opts.WebSocketCtor,
|
|
40
|
+
fetchImpl: opts.fetchImpl,
|
|
41
|
+
signal: opts.signal,
|
|
42
|
+
};
|
|
43
|
+
return new Room(deps);
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rooms/index.ts"],"names":[],"mappings":"AAAA;qEACqE;AAErE,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,EAAE,IAAI,EAAyC,MAAM,WAAW,CAAA;AAIvE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAEL,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,uBAAuB,EACvB,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,UAAU,EACV,YAAY,EAOZ,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,GACb,MAAM,YAAY,CAAA;AACnB,OAAO,EAA+C,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAChG,OAAO,EAAE,oBAAoB,EAA+B,MAAM,0BAA0B,CAAA;AAC5F,OAAO,EACL,UAAU,EAGV,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,QAAQ,EAA2B,MAAM,eAAe,CAAA;AACjE,OAAO,EAIL,IAAI,GAML,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,EACnB,yBAAyB,EACzB,qBAAqB,EACrB,uBAAuB,EAEvB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAkB,eAAe,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAC7E,OAAO,EAEL,iBAAiB,EAEjB,cAAc,GACf,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAwB,MAAM,kBAAkB,CAAA;AA0BxF;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY,EAAE,OAAwB,EAAE;IACrE,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,iBAAiB,CAAC,IAAI,gBAAgB,CAAC,CAAC,OAAO,CACtF,MAAM,EACN,EAAE,CACH,CAAA;IACD,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,OAAO,0BAA0B,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC3F,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,cAAc,CAAC,GAAG,CAAC,CAAA;IACtC,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA2C,CAAA;IACzE,MAAM,IAAI,GAAa;QACrB,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACnB,WAAW,EAAE,IAAI,CAAC,YAAY,IAAI,IAAI;QACtC,OAAO;QACP,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAA;IACD,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,CAAA;AACvB,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The audio-capture standing service behind {@link ./room.Room.offerAudioCapture} — **session-less**:
|
|
3
|
+
* it writes into the **active recorder's** session (``room.record(into)`` is the single durable
|
|
4
|
+
* binding). The TypeScript port of Python's ``_AsyncOfferedCapture`` (aio.py). Engaged by a
|
|
5
|
+
* ``capture.start`` request, disengaged (finalizing every open recording) by ``capture.stop``, the
|
|
6
|
+
* room's close, or the bound recorder stopping.
|
|
7
|
+
*
|
|
8
|
+
* On ``capture.start`` it binds the **first** active recorder's session, subscribes the room's
|
|
9
|
+
* ``audio.snippet`` stream, and bridges each armed source's frames into one session recording per
|
|
10
|
+
* source via the chunk-append recording API. Collision-safe keys: name-else-cid, a same-name
|
|
11
|
+
* reconnect within grace resumes the file, two simultaneous same-name producers get their own
|
|
12
|
+
* (cid-suffixed) files. Exactly one recorder feeds audio — a second warns and is not double-written.
|
|
13
|
+
*/
|
|
14
|
+
import type { ShapedRequest } from "../shaping/index.js";
|
|
15
|
+
import { type ActiveSource, type OfferDeps, StandingSource } from "./standing-source.js";
|
|
16
|
+
/** The recorder a capture offer binds to — membership + liveness + the session its clips write into.
|
|
17
|
+
* A {@link ./recorder.Recorder} satisfies this structurally (``alive`` + ``into``). */
|
|
18
|
+
export interface CaptureRecorder {
|
|
19
|
+
readonly alive: boolean;
|
|
20
|
+
readonly into: {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** What the capture offer is wired with — the base {@link OfferDeps} plus the recorder seam + the
|
|
25
|
+
* authenticated REST caller for the chunked recording API. */
|
|
26
|
+
export interface CaptureOfferDeps extends OfferDeps {
|
|
27
|
+
/** Perform a shaped REST request on the host's credential (the recording create/chunk/finalize). */
|
|
28
|
+
request(req: ShapedRequest): Promise<unknown>;
|
|
29
|
+
/** The recorders currently active on the room (membership + liveness) — availability + binding. */
|
|
30
|
+
activeRecorders(): CaptureRecorder[];
|
|
31
|
+
/** Subscribe to the room's recorder-set changes; returns an unsubscribe. */
|
|
32
|
+
onRecordersChanged(listener: () => void | Promise<void>): () => void;
|
|
33
|
+
}
|
|
34
|
+
export declare class OfferedCapture extends StandingSource {
|
|
35
|
+
protected readonly signal = "capture.available";
|
|
36
|
+
protected readonly logName = "capture offer";
|
|
37
|
+
protected readonly identityPrefix = "capture-offer";
|
|
38
|
+
private readonly captureDeps;
|
|
39
|
+
private readonly unsubscribeRecorders;
|
|
40
|
+
/** The recorder capture is writing under (bound at ``capture.start``); its session is derived from
|
|
41
|
+
* it, so the two can never drift. */
|
|
42
|
+
private boundRecorder;
|
|
43
|
+
/** source key (announced name when present, else connection id) → open recording id. */
|
|
44
|
+
private readonly recordings;
|
|
45
|
+
/** source connection id → the recording key it resolved to. */
|
|
46
|
+
private readonly sourceKey;
|
|
47
|
+
/** recording key → the connection id currently owning it. */
|
|
48
|
+
private readonly keyOwner;
|
|
49
|
+
/** v1 (id-less) recording key → its source_name (surfaces the null-source ``active`` entry). */
|
|
50
|
+
private readonly v1Open;
|
|
51
|
+
/** source key → consecutive failed recording-creates (reset by a success). */
|
|
52
|
+
private readonly createFailures;
|
|
53
|
+
/** source keys dropped after hitting the create-failure ceiling — later clips ignored. */
|
|
54
|
+
private readonly dead;
|
|
55
|
+
/** True once the "second recorder" warning fired for the current engagement (warn once). */
|
|
56
|
+
private warnedMulti;
|
|
57
|
+
constructor(deps: CaptureOfferDeps, opts?: {
|
|
58
|
+
maxSources?: number;
|
|
59
|
+
});
|
|
60
|
+
private get sessionId();
|
|
61
|
+
/** True ⇔ a recorder is active on the room **and** the bridge isn't dead from a terminal error. */
|
|
62
|
+
available(): boolean;
|
|
63
|
+
/** A v1 (id-less) source being recorded under the legacy all-arm shows as a null-source entry. */
|
|
64
|
+
protected activeExtra(): ActiveSource[];
|
|
65
|
+
/** Re-derive availability after the room's recorder set changed. If the bound recorder stopped,
|
|
66
|
+
* disengage; if a second recorder appeared while engaged, warn once (no double-write). */
|
|
67
|
+
private onRecordersChanged;
|
|
68
|
+
protected beforeArm(_armAll: boolean): Promise<CaptureRecorder[]>;
|
|
69
|
+
protected onEngage(ctx: unknown): void;
|
|
70
|
+
protected run(): Promise<void>;
|
|
71
|
+
/** Append one clip to its source's recording, opening the recording on the first clip. Only armed
|
|
72
|
+
* sources are bridged; a transient chunk failure is logged and skipped; a 401/403 propagates. */
|
|
73
|
+
private bridge;
|
|
74
|
+
/** Resolve (and memoize) the recording key for a source's first clip: RESUME a same-name key whose
|
|
75
|
+
* owner departed within grace; COLLIDE a same-name key owned by a different live connection to a
|
|
76
|
+
* cid-suffixed key. Returns ``null`` (warning once) if a new source would exceed the cap. */
|
|
77
|
+
private decideKey;
|
|
78
|
+
/** Create a source's recording, applying the create-failure circuit breaker. Returns the new
|
|
79
|
+
* recording id, or ``null`` if the create failed. A 401/403 propagates. */
|
|
80
|
+
private openRecording;
|
|
81
|
+
protected stopSource(cid: string): Promise<void>;
|
|
82
|
+
protected onGraceExpired(cid: string): Promise<void>;
|
|
83
|
+
stop(): Promise<void>;
|
|
84
|
+
/** Stop watching the recorder seam — call when the room is torn down for good. Named ``detach`` (not
|
|
85
|
+
* ``dispose``) so it isn't mistaken for the ``Symbol.dispose`` protocol: disposing an offer maps to
|
|
86
|
+
* {@link stop} (wind down + finalize), while ``detach`` only unsubscribes the recorder-changed hook. */
|
|
87
|
+
detach(): void;
|
|
88
|
+
private disengage;
|
|
89
|
+
/** The teardown body — the caller must already hold the lock. Cancel-before-clear: close the frame
|
|
90
|
+
* loop and await it *before* clearing the binding, so no straggling {@link bridge} runs against a
|
|
91
|
+
* half-cleared state. Skips the self-teardown when invoked from within the frame loop. */
|
|
92
|
+
private disengageLocked;
|
|
93
|
+
private finalizeOne;
|
|
94
|
+
private finalizeAll;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=offer-capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offer-capture.d.ts","sourceRoot":"","sources":["../../src/rooms/offer-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAQxD,OAAO,EACL,KAAK,YAAY,EAGjB,KAAK,SAAS,EACd,cAAc,EACf,MAAM,sBAAsB,CAAA;AAE7B;uFACuF;AACvF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CACvC;AAED;8DAC8D;AAC9D,MAAM,WAAW,gBAAiB,SAAQ,SAAS;IACjD,oGAAoG;IACpG,OAAO,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7C,mGAAmG;IACnG,eAAe,IAAI,eAAe,EAAE,CAAA;IACpC,4EAA4E;IAC5E,kBAAkB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,CAAA;CACrE;AAMD,qBAAa,cAAe,SAAQ,cAAc;IAChD,SAAS,CAAC,QAAQ,CAAC,MAAM,uBAAsB;IAC/C,SAAS,CAAC,QAAQ,CAAC,OAAO,mBAAkB;IAC5C,SAAS,CAAC,QAAQ,CAAC,cAAc,mBAAkB;IAEnD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkB;IAC9C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAY;IACjD;yCACqC;IACrC,OAAO,CAAC,aAAa,CAA+B;IACpD,wFAAwF;IACxF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA4B;IACvD,+DAA+D;IAC/D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IACrD,gGAAgG;IAChG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAC1D,8EAA8E;IAC9E,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA4B;IAC3D,0FAA0F;IAC1F,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoB;IACzC,4FAA4F;IAC5F,OAAO,CAAC,WAAW,CAAQ;gBAEf,IAAI,EAAE,gBAAgB,EAAE,IAAI,GAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAO;IAMtE,OAAO,KAAK,SAAS,GAEpB;IAED,mGAAmG;IAC1F,SAAS,IAAI,OAAO;IAK7B,kGAAkG;cAC/E,WAAW,IAAI,YAAY,EAAE;IAIhD;8FAC0F;YAC5E,kBAAkB;cAkBP,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAQ7D,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI;cAa/B,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IA6BpC;qGACiG;YACnF,MAAM;IAoDpB;;iGAE6F;IAC7F,OAAO,CAAC,SAAS;IAoBjB;+EAC2E;YAC7D,aAAa;cAiCX,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;cAoB7B,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc7D,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B;;4GAEwG;IACxG,MAAM,IAAI,IAAI;YAIA,SAAS;IAIvB;;8FAE0F;YAC5E,eAAe;YA2Bf,WAAW;YAUX,WAAW;CAW1B"}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The audio-capture standing service behind {@link ./room.Room.offerAudioCapture} — **session-less**:
|
|
3
|
+
* it writes into the **active recorder's** session (``room.record(into)`` is the single durable
|
|
4
|
+
* binding). The TypeScript port of Python's ``_AsyncOfferedCapture`` (aio.py). Engaged by a
|
|
5
|
+
* ``capture.start`` request, disengaged (finalizing every open recording) by ``capture.stop``, the
|
|
6
|
+
* room's close, or the bound recorder stopping.
|
|
7
|
+
*
|
|
8
|
+
* On ``capture.start`` it binds the **first** active recorder's session, subscribes the room's
|
|
9
|
+
* ``audio.snippet`` stream, and bridges each armed source's frames into one session recording per
|
|
10
|
+
* source via the chunk-append recording API. Collision-safe keys: name-else-cid, a same-name
|
|
11
|
+
* reconnect within grace resumes the file, two simultaneous same-name producers get their own
|
|
12
|
+
* (cid-suffixed) files. Exactly one recorder feeds audio — a second warns and is not double-written.
|
|
13
|
+
*/
|
|
14
|
+
import { APIError } from "../errors.js";
|
|
15
|
+
import { asError } from "../http.js";
|
|
16
|
+
import { buildRecordingChunk, buildRecordingCreate, buildRecordingFinalize, } from "../shaping/rooms.js";
|
|
17
|
+
import { decodeAudioSnippetFrame } from "./snippet.js";
|
|
18
|
+
import { CAPTURE_CREATE_MAX_FAILURES, OFFER_MAX_SOURCES, StandingSource, } from "./standing-source.js";
|
|
19
|
+
function isAuthError(err) {
|
|
20
|
+
return err instanceof APIError && (err.status === 401 || err.status === 403);
|
|
21
|
+
}
|
|
22
|
+
export class OfferedCapture extends StandingSource {
|
|
23
|
+
signal = "capture.available";
|
|
24
|
+
logName = "capture offer";
|
|
25
|
+
identityPrefix = "capture-offer";
|
|
26
|
+
captureDeps;
|
|
27
|
+
unsubscribeRecorders;
|
|
28
|
+
/** The recorder capture is writing under (bound at ``capture.start``); its session is derived from
|
|
29
|
+
* it, so the two can never drift. */
|
|
30
|
+
boundRecorder = null;
|
|
31
|
+
/** source key (announced name when present, else connection id) → open recording id. */
|
|
32
|
+
recordings = new Map();
|
|
33
|
+
/** source connection id → the recording key it resolved to. */
|
|
34
|
+
sourceKey = new Map();
|
|
35
|
+
/** recording key → the connection id currently owning it. */
|
|
36
|
+
keyOwner = new Map();
|
|
37
|
+
/** v1 (id-less) recording key → its source_name (surfaces the null-source ``active`` entry). */
|
|
38
|
+
v1Open = new Map();
|
|
39
|
+
/** source key → consecutive failed recording-creates (reset by a success). */
|
|
40
|
+
createFailures = new Map();
|
|
41
|
+
/** source keys dropped after hitting the create-failure ceiling — later clips ignored. */
|
|
42
|
+
dead = new Set();
|
|
43
|
+
/** True once the "second recorder" warning fired for the current engagement (warn once). */
|
|
44
|
+
warnedMulti = false;
|
|
45
|
+
constructor(deps, opts = {}) {
|
|
46
|
+
super(deps, opts.maxSources ?? OFFER_MAX_SOURCES);
|
|
47
|
+
this.captureDeps = deps;
|
|
48
|
+
this.unsubscribeRecorders = deps.onRecordersChanged(() => this.onRecordersChanged());
|
|
49
|
+
}
|
|
50
|
+
get sessionId() {
|
|
51
|
+
return this.boundRecorder?.into.id ?? null;
|
|
52
|
+
}
|
|
53
|
+
/** True ⇔ a recorder is active on the room **and** the bridge isn't dead from a terminal error. */
|
|
54
|
+
available() {
|
|
55
|
+
if (this.err !== null && !this.alive)
|
|
56
|
+
return false;
|
|
57
|
+
return this.captureDeps.activeRecorders().length > 0;
|
|
58
|
+
}
|
|
59
|
+
/** A v1 (id-less) source being recorded under the legacy all-arm shows as a null-source entry. */
|
|
60
|
+
activeExtra() {
|
|
61
|
+
return [...this.v1Open.values()].map((name) => ({ source: null, name }));
|
|
62
|
+
}
|
|
63
|
+
/** Re-derive availability after the room's recorder set changed. If the bound recorder stopped,
|
|
64
|
+
* disengage; if a second recorder appeared while engaged, warn once (no double-write). */
|
|
65
|
+
async onRecordersChanged() {
|
|
66
|
+
await this.lock.run(async () => {
|
|
67
|
+
if (this.boundRecorder !== null) {
|
|
68
|
+
const active = this.captureDeps.activeRecorders();
|
|
69
|
+
if (!active.includes(this.boundRecorder)) {
|
|
70
|
+
await this.disengageLocked({});
|
|
71
|
+
}
|
|
72
|
+
else if (active.length > 1 && !this.warnedMulti) {
|
|
73
|
+
this.warnedMulti = true;
|
|
74
|
+
console.warn(`Atrium capture offer for room ${this.deps.roomId}: audio capture continues into the ` +
|
|
75
|
+
"first recorder's session (a second recorder started)");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
await this.broadcast();
|
|
80
|
+
}
|
|
81
|
+
async beforeArm(_armAll) {
|
|
82
|
+
const active = this.captureDeps.activeRecorders();
|
|
83
|
+
if (active.length === 0) {
|
|
84
|
+
throw new Error("no active recorder — call room.record(into=session) first");
|
|
85
|
+
}
|
|
86
|
+
return active;
|
|
87
|
+
}
|
|
88
|
+
onEngage(ctx) {
|
|
89
|
+
const active = ctx;
|
|
90
|
+
this.warnedMulti = active.length > 1;
|
|
91
|
+
if (this.warnedMulti) {
|
|
92
|
+
console.warn(`Atrium capture offer for room ${this.deps.roomId}: audio capture continues into the first ` +
|
|
93
|
+
"recorder's session (multiple recorders active)");
|
|
94
|
+
}
|
|
95
|
+
this.boundRecorder = active[0] ?? null;
|
|
96
|
+
this.stoppedReason = null;
|
|
97
|
+
}
|
|
98
|
+
async run() {
|
|
99
|
+
const ws = await this.openWs();
|
|
100
|
+
let threw = false;
|
|
101
|
+
try {
|
|
102
|
+
for await (const raw of this.frames()) {
|
|
103
|
+
const snippet = decodeAudioSnippetFrame(raw);
|
|
104
|
+
if (!snippet)
|
|
105
|
+
continue; // a foreign / DSP binary frame — not ours
|
|
106
|
+
await this.bridge(snippet, snippet.source !== null ? this.nameFor(snippet.source) : null);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
threw = true;
|
|
111
|
+
this.err = asError(err);
|
|
112
|
+
console.error(`Atrium capture offer for room ${this.deps.roomId} died:`, err);
|
|
113
|
+
// Funnel through the single disengage path so we finalize what we opened *and* clear the
|
|
114
|
+
// binding. It's the running loop, so disengage skips the self-teardown wait.
|
|
115
|
+
await this.disengage({ fromRun: true });
|
|
116
|
+
}
|
|
117
|
+
if (!threw && this.codeRotated) {
|
|
118
|
+
console.info(`Atrium capture offer for room ${this.deps.roomId}: room code rotated; finalizing`);
|
|
119
|
+
await this.disengage({ reason: "code-rotated", fromRun: true });
|
|
120
|
+
}
|
|
121
|
+
// Natural frame-exhaustion (no rotation, no error) deliberately does NOT finalize — the recording
|
|
122
|
+
// stays open for an explicit capture.stop. Just stop the sweeper + close the socket.
|
|
123
|
+
this.stopSweeper();
|
|
124
|
+
ws.close();
|
|
125
|
+
}
|
|
126
|
+
/** Append one clip to its source's recording, opening the recording on the first clip. Only armed
|
|
127
|
+
* sources are bridged; a transient chunk failure is logged and skipped; a 401/403 propagates. */
|
|
128
|
+
async bridge(snippet, sourceName) {
|
|
129
|
+
const sessionId = this.sessionId;
|
|
130
|
+
if (sessionId === null)
|
|
131
|
+
return; // straggler after disengage (binding cleared)
|
|
132
|
+
const cid = snippet.source;
|
|
133
|
+
if (!this.armed.isArmed(cid))
|
|
134
|
+
return; // unarmed source — dropped at the demux
|
|
135
|
+
const isV1 = cid === null;
|
|
136
|
+
let resumed = false;
|
|
137
|
+
let key = isV1 ? null : (this.sourceKey.get(cid) ?? null);
|
|
138
|
+
if (key === null) {
|
|
139
|
+
if (isV1) {
|
|
140
|
+
key = sourceName || "default"; // a v1 frame has no id to key by — coalesce by name else a fixed key
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
key = this.decideKey(cid, sourceName);
|
|
144
|
+
if (key === null)
|
|
145
|
+
return; // cap exceeded — dropped (warned once)
|
|
146
|
+
resumed = this.recordings.has(key);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (this.dead.has(key))
|
|
150
|
+
return;
|
|
151
|
+
let rid = this.recordings.get(key);
|
|
152
|
+
if (rid === undefined) {
|
|
153
|
+
const created = await this.openRecording(sessionId, key, sourceName, snippet.mimeType);
|
|
154
|
+
if (created === null)
|
|
155
|
+
return; // create failed (transient, or this source hit its ceiling)
|
|
156
|
+
// Re-check after the create's await: a per-source stop / disengage may have landed. If so, this
|
|
157
|
+
// recording is an orphan the stop path can't see — finalize it now rather than register it.
|
|
158
|
+
if (!this.armed.isArmed(cid) ||
|
|
159
|
+
(!isV1 && this.sourceKey.get(cid) !== key) ||
|
|
160
|
+
this.boundRecorder === null) {
|
|
161
|
+
await this.finalizeOne(created, sessionId);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
rid = created;
|
|
165
|
+
this.recordings.set(key, rid);
|
|
166
|
+
if (isV1)
|
|
167
|
+
this.v1Open.set(key, sourceName);
|
|
168
|
+
this.spawnBroadcast(); // a new source is now active (v1 surfaces the null entry)
|
|
169
|
+
}
|
|
170
|
+
else if (resumed) {
|
|
171
|
+
this.spawnBroadcast(); // a departed source resumed → active again
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
await this.captureDeps.request(buildRecordingChunk(sessionId, rid, snippet.data, snippet.mimeType));
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
if (isAuthError(err))
|
|
178
|
+
throw err;
|
|
179
|
+
console.warn(`Atrium capture: chunk upload for recording ${rid} failed — continuing with next clip:`, err);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** Resolve (and memoize) the recording key for a source's first clip: RESUME a same-name key whose
|
|
183
|
+
* owner departed within grace; COLLIDE a same-name key owned by a different live connection to a
|
|
184
|
+
* cid-suffixed key. Returns ``null`` (warning once) if a new source would exceed the cap. */
|
|
185
|
+
decideKey(cid, sourceName) {
|
|
186
|
+
const candidate = sourceName || cid;
|
|
187
|
+
const owner = this.keyOwner.get(candidate);
|
|
188
|
+
let chosen;
|
|
189
|
+
if (owner === undefined || owner === cid)
|
|
190
|
+
chosen = candidate;
|
|
191
|
+
else if (this.isDeparted(owner))
|
|
192
|
+
chosen = candidate; // a same-name return within grace resumes
|
|
193
|
+
else
|
|
194
|
+
chosen = `${candidate}#${cid}`; // a different LIVE owner → own file (no cross-write)
|
|
195
|
+
if (!this.recordings.has(chosen) && this.recordings.size >= this.maxSources) {
|
|
196
|
+
this.warnCapped(cid);
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
const prior = this.keyOwner.get(chosen);
|
|
200
|
+
if (prior !== undefined && prior !== cid)
|
|
201
|
+
this.departed.delete(prior);
|
|
202
|
+
this.sourceKey.set(cid, chosen);
|
|
203
|
+
this.keyOwner.set(chosen, cid);
|
|
204
|
+
this.departed.delete(cid);
|
|
205
|
+
return chosen;
|
|
206
|
+
}
|
|
207
|
+
/** Create a source's recording, applying the create-failure circuit breaker. Returns the new
|
|
208
|
+
* recording id, or ``null`` if the create failed. A 401/403 propagates. */
|
|
209
|
+
async openRecording(sessionId, key, sourceName, contentType) {
|
|
210
|
+
let created;
|
|
211
|
+
try {
|
|
212
|
+
created = await this.captureDeps.request(buildRecordingCreate(sessionId, { sourceName, contentType }));
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
if (isAuthError(err))
|
|
216
|
+
throw err;
|
|
217
|
+
const fails = (this.createFailures.get(key) ?? 0) + 1;
|
|
218
|
+
this.createFailures.set(key, fails);
|
|
219
|
+
if (fails >= CAPTURE_CREATE_MAX_FAILURES) {
|
|
220
|
+
this.dead.add(key);
|
|
221
|
+
this.createFailures.delete(key);
|
|
222
|
+
this.err = asError(err);
|
|
223
|
+
console.error(`Atrium capture: giving up on source ${JSON.stringify(key)} after ${fails} failed ` +
|
|
224
|
+
"recording-creates:", err);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
console.warn("Atrium capture: could not open a recording — dropping this clip:", err);
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
this.createFailures.delete(key); // a success clears the streak
|
|
232
|
+
return String(created.id);
|
|
233
|
+
}
|
|
234
|
+
async stopSource(cid) {
|
|
235
|
+
await this.lock.run(async () => {
|
|
236
|
+
this.armed.disarm(cid);
|
|
237
|
+
const key = this.sourceKey.get(cid);
|
|
238
|
+
this.sourceKey.delete(cid);
|
|
239
|
+
let rid;
|
|
240
|
+
if (key !== undefined && this.keyOwner.get(key) === cid) {
|
|
241
|
+
this.keyOwner.delete(key);
|
|
242
|
+
rid = this.recordings.get(key);
|
|
243
|
+
this.recordings.delete(key);
|
|
244
|
+
this.createFailures.delete(key);
|
|
245
|
+
this.dead.delete(key);
|
|
246
|
+
}
|
|
247
|
+
this.departed.delete(cid);
|
|
248
|
+
this.cappedWarned.delete(cid);
|
|
249
|
+
if (rid !== undefined)
|
|
250
|
+
await this.finalizeOne(rid);
|
|
251
|
+
});
|
|
252
|
+
await this.broadcast();
|
|
253
|
+
}
|
|
254
|
+
async onGraceExpired(cid) {
|
|
255
|
+
await this.lock.run(async () => {
|
|
256
|
+
const key = this.sourceKey.get(cid);
|
|
257
|
+
if (key === undefined || this.keyOwner.get(key) !== cid)
|
|
258
|
+
return; // no open recording / re-owned
|
|
259
|
+
const rid = this.recordings.get(key);
|
|
260
|
+
this.recordings.delete(key);
|
|
261
|
+
this.keyOwner.delete(key);
|
|
262
|
+
for (const [c, k] of [...this.sourceKey])
|
|
263
|
+
if (k === key)
|
|
264
|
+
this.sourceKey.delete(c);
|
|
265
|
+
this.createFailures.delete(key);
|
|
266
|
+
this.dead.delete(key);
|
|
267
|
+
if (rid !== undefined)
|
|
268
|
+
await this.finalizeOne(rid);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
async stop() {
|
|
272
|
+
await this.disengage({});
|
|
273
|
+
}
|
|
274
|
+
/** Stop watching the recorder seam — call when the room is torn down for good. Named ``detach`` (not
|
|
275
|
+
* ``dispose``) so it isn't mistaken for the ``Symbol.dispose`` protocol: disposing an offer maps to
|
|
276
|
+
* {@link stop} (wind down + finalize), while ``detach`` only unsubscribes the recorder-changed hook. */
|
|
277
|
+
detach() {
|
|
278
|
+
this.unsubscribeRecorders();
|
|
279
|
+
}
|
|
280
|
+
async disengage(opts) {
|
|
281
|
+
await this.lock.run(() => this.disengageLocked(opts));
|
|
282
|
+
}
|
|
283
|
+
/** The teardown body — the caller must already hold the lock. Cancel-before-clear: close the frame
|
|
284
|
+
* loop and await it *before* clearing the binding, so no straggling {@link bridge} runs against a
|
|
285
|
+
* half-cleared state. Skips the self-teardown when invoked from within the frame loop. */
|
|
286
|
+
async disengageLocked(opts) {
|
|
287
|
+
if (opts.reason !== undefined)
|
|
288
|
+
this.stoppedReason = opts.reason;
|
|
289
|
+
if (!opts.fromRun) {
|
|
290
|
+
this.ws?.close(); // ends the frame loop (onEnd → end sentinel)
|
|
291
|
+
const running = this.runPromise;
|
|
292
|
+
this.runPromise = null;
|
|
293
|
+
if (running)
|
|
294
|
+
await running.catch(() => { });
|
|
295
|
+
}
|
|
296
|
+
this.stopSweeper();
|
|
297
|
+
await Promise.allSettled(this.bg);
|
|
298
|
+
this.bg.length = 0;
|
|
299
|
+
await this.finalizeAll(); // still bound → recordings finalize
|
|
300
|
+
this.boundRecorder = null; // clears the derived session — after finalize
|
|
301
|
+
this.armed.clear();
|
|
302
|
+
this.sourceKey.clear();
|
|
303
|
+
this.keyOwner.clear();
|
|
304
|
+
this.v1Open.clear();
|
|
305
|
+
this.departed.clear();
|
|
306
|
+
this.sourceNames.clear();
|
|
307
|
+
this.createFailures.clear();
|
|
308
|
+
this.dead.clear();
|
|
309
|
+
this.cappedWarned.clear();
|
|
310
|
+
this.warnedMulti = false;
|
|
311
|
+
this.clearLastBroadcast();
|
|
312
|
+
this.ws = null;
|
|
313
|
+
}
|
|
314
|
+
async finalizeOne(rid, sessionId) {
|
|
315
|
+
const sid = sessionId ?? this.sessionId;
|
|
316
|
+
if (sid === null)
|
|
317
|
+
return;
|
|
318
|
+
try {
|
|
319
|
+
await this.captureDeps.request(buildRecordingFinalize(sid, rid));
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
console.warn(`Atrium capture: finalizing recording ${rid} failed:`, err);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async finalizeAll() {
|
|
326
|
+
const sid = this.sessionId;
|
|
327
|
+
if (sid === null) {
|
|
328
|
+
this.recordings.clear();
|
|
329
|
+
this.v1Open.clear();
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
for (const rid of [...this.recordings.values()])
|
|
333
|
+
await this.finalizeOne(rid, sid);
|
|
334
|
+
this.recordings.clear();
|
|
335
|
+
this.v1Open.clear();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
//# sourceMappingURL=offer-capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offer-capture.js","sourceRoot":"","sources":["../../src/rooms/offer-capture.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transcription standing service behind {@link ./room.Room.offerTranscription} — armed by a
|
|
3
|
+
* ``transcription.start`` request, wound down by ``transcription.stop`` or the room's close. The
|
|
4
|
+
* TypeScript port of Python's ``_AsyncOfferedTranscription`` + ``_publish_room_transcription`` (aio.py).
|
|
5
|
+
*
|
|
6
|
+
* Engage is **sender-scoped** (see {@link ./armed-set.ArmedSet}): only armed sources are transcribed
|
|
7
|
+
* — an unarmed source's frames are dropped at the demux (no engine spawns). While engaged it
|
|
8
|
+
* subscribes the room's ``audio.snippet`` stream, demuxes frames per source, and runs one
|
|
9
|
+
* transcription engine per source (the same engine as {@link ./room.Room.transcribe}), stamping each
|
|
10
|
+
* ``transcript.*`` signal's payload with the source's connection id and roster-resolved
|
|
11
|
+
* ``source_name``. Up to ``maxSources`` sources; a departed source's engine is reaped promptly.
|
|
12
|
+
*/
|
|
13
|
+
import type { Transcript } from "../models.js";
|
|
14
|
+
import { type ActiveSource, type OfferDeps, StandingSource } from "./standing-source.js";
|
|
15
|
+
/** What the transcription offer is wired with — the base {@link OfferDeps} plus the shared engine. */
|
|
16
|
+
export interface TranscriptionOfferDeps extends OfferDeps {
|
|
17
|
+
/** The client's shared live-transcription engine (streaming when the backend offers it, per-clip
|
|
18
|
+
* batch otherwise) — one input iterable per source. */
|
|
19
|
+
engine(clips: AsyncIterable<ArrayBuffer>, opts: {
|
|
20
|
+
model?: string;
|
|
21
|
+
language?: string;
|
|
22
|
+
signal?: AbortSignal;
|
|
23
|
+
}): AsyncIterable<Transcript>;
|
|
24
|
+
}
|
|
25
|
+
export declare class OfferedTranscription extends StandingSource {
|
|
26
|
+
protected readonly signal = "transcription.available";
|
|
27
|
+
protected readonly logName = "transcription offer";
|
|
28
|
+
protected readonly identityPrefix = "transcription-offer";
|
|
29
|
+
private readonly engineFn;
|
|
30
|
+
private readonly model?;
|
|
31
|
+
private readonly language?;
|
|
32
|
+
private readonly queues;
|
|
33
|
+
private readonly engines;
|
|
34
|
+
private readonly aborts;
|
|
35
|
+
private reaped;
|
|
36
|
+
private readonly stalled;
|
|
37
|
+
constructor(deps: TranscriptionOfferDeps, opts?: {
|
|
38
|
+
model?: string;
|
|
39
|
+
language?: string;
|
|
40
|
+
maxSources?: number;
|
|
41
|
+
});
|
|
42
|
+
/** A v1 (id-less) source being transcribed under an all-arm surfaces as a null-source entry. */
|
|
43
|
+
protected activeExtra(): ActiveSource[];
|
|
44
|
+
protected run(): Promise<void>;
|
|
45
|
+
/** Route one clip to its source's engine — dropping it if the source is unarmed or past the cap,
|
|
46
|
+
* and spawning the engine on a source's first clip. */
|
|
47
|
+
private admit;
|
|
48
|
+
private spawnEngine;
|
|
49
|
+
/** Run the engine over one source's clip queue and publish each result — the shared
|
|
50
|
+
* {@link publishRoomTranscripts} with per-source attribution, published over the REST ``/signal``
|
|
51
|
+
* POST (so the ephemeral partial hint survives) as ``source: "transcriber"``. */
|
|
52
|
+
private publishTranscription;
|
|
53
|
+
/** Hand one clip to a source's engine, dropping the oldest queued clip if the engine has stalled.
|
|
54
|
+
* Warns once per source per stall episode. */
|
|
55
|
+
private enqueue;
|
|
56
|
+
/** On leave, cancel that source's engine immediately (mirror of Python's ``engine.cancel()``):
|
|
57
|
+
* abort its signal and drop its buffered clips, so no captions publish for a source that has left
|
|
58
|
+
* (consent-relevant). The reaped engine is awaited at teardown (the base handles the rest). */
|
|
59
|
+
protected onPresenceSource(event: "join" | "leave", cid: string): void;
|
|
60
|
+
protected stopSource(source: string): Promise<void>;
|
|
61
|
+
stop(): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=offer-transcription.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offer-transcription.d.ts","sourceRoot":"","sources":["../../src/rooms/offer-transcription.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,OAAO,EACL,KAAK,YAAY,EAGjB,KAAK,SAAS,EACd,cAAc,EACf,MAAM,sBAAsB,CAAA;AAG7B,sGAAsG;AACtG,MAAM,WAAW,sBAAuB,SAAQ,SAAS;IACvD;2DACuD;IACvD,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;CAC7B;AA6CD,qBAAa,oBAAqB,SAAQ,cAAc;IACtD,SAAS,CAAC,QAAQ,CAAC,MAAM,6BAA4B;IACrD,SAAS,CAAC,QAAQ,CAAC,OAAO,yBAAwB;IAClD,SAAS,CAAC,QAAQ,CAAC,cAAc,yBAAwB;IAEzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAQ;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsC;IAC7D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0C;IAGlE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4C;IACnE,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2B;gBAGjD,IAAI,EAAE,sBAAsB,EAC5B,IAAI,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAO;IAQvE,gGAAgG;cAC7E,WAAW,IAAI,YAAY,EAAE;cAIhC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IA8BpC;2DACuD;IACvD,OAAO,CAAC,KAAK;IAmBb,OAAO,CAAC,WAAW;IAanB;;qFAEiF;IACjF,OAAO,CAAC,oBAAoB;IAqB5B;kDAC8C;IAC9C,OAAO,CAAC,OAAO;IAcf;;mGAE+F;cAC5E,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;cAmB/D,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBnD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAU5B"}
|