meeting-record 0.0.0-stage → 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.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hoang Vo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,93 @@
1
- # Temporary Holding Version
1
+ # meeting-record for Node.js
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [Full documentation](https://github.com/hoangvvo/meeting-record#readme)
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install meeting-record
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import * as MeetingRecord from "meeting-record";
15
+
16
+ // Request system audio permission to record the whole system mix.
17
+ await MeetingRecord.permissions.request("system-audio");
18
+ // Request microphone permission to record the user voice.
19
+ await MeetingRecord.permissions.request("microphone");
20
+ // Request accessibility permission to read meeting titles and URLs for detection.
21
+ await MeetingRecord.permissions.request("accessibility");
22
+
23
+ // Watch for meetings starting, changing, and ending.
24
+ MeetingRecord.meetings.on("started", (meeting) => {
25
+ console.log("meeting started", meeting);
26
+ });
27
+ MeetingRecord.meetings.on("updated", (meeting) => {
28
+ console.log("meeting updated", meeting);
29
+ });
30
+ MeetingRecord.meetings.on("ended", (meeting) => {
31
+ console.log("meeting ended", meeting);
32
+ });
33
+ MeetingRecord.meetings.watch();
34
+
35
+ // Or scan for active meetings at any time.
36
+ const meeting = MeetingRecord.meetings.scan().find((item) => item.shouldRecord);
37
+ if (!meeting) throw new Error("no active meeting");
38
+
39
+ // Start recording the meeting process and its audio helpers.
40
+ // Use `{ type: "system" }` instead to record the whole system mix.
41
+ const recording = await MeetingRecord.capture.start(
42
+ { type: "process", pid: meeting.pid },
43
+ { microphone: "default" },
44
+ );
45
+
46
+ recording.on("interrupted", () => console.warn("audio device changed; recovering"));
47
+ recording.on("recovered", () => console.log("capture recovered"));
48
+ recording.on("failed", (error) => console.error("capture failed", error));
49
+
50
+ // Process captured application or system audio.
51
+ recording.systemAudio.on("data", (chunk) => {
52
+ console.log(chunk.length, chunk.hostTimeNs, chunk.sampleRate, chunk.channels);
53
+ });
54
+ recording.systemAudio.on("drop", (samples) => console.warn("dropped", samples));
55
+ recording.systemAudio.on("format", (format) => console.log("new format", format));
56
+
57
+ // Process the microphone track if requested.
58
+ recording.microphone?.on("data", (chunk) => {
59
+ console.log(chunk.length, chunk.hostTimeNs, chunk.sampleRate, chunk.channels);
60
+ });
61
+
62
+ await new Promise((resolve) => setTimeout(resolve, 10_000));
63
+ await recording.stopRecording();
64
+
65
+ // Stop watching for meetings when done.
66
+ MeetingRecord.meetings.unwatch();
67
+ ```
68
+
69
+ ## Audio apps
70
+
71
+ `meetings.audioApps()` lists owning applications for a source picker. Pass an entry's `pid` to `capture.start()`; no meeting scan or Accessibility grant is needed.
72
+
73
+ Windows targets include future child processes. macOS targets include current audio helpers; restart capture if helpers are replaced.
74
+
75
+ ## Mixed output
76
+
77
+ ```ts
78
+ const recording = await MeetingRecord.capture.start(
79
+ { type: "system" },
80
+ { microphone: "default", mix: { sampleRate: 24_000 } },
81
+ );
82
+ recording.mixedAudio.pipe(destination);
83
+ recording.mixedAudio.on("level", (rms) => console.log(rms));
84
+ recording.mixedAudio.setMicrophoneMuted(true);
85
+ ```
86
+
87
+ `mixedAudio` is mono s16le. Pause/resume excludes paused time; await its `end` event after stopping to consume the final tail. Raw tracks remain float32. Mixing does not perform echo cancellation.
88
+
89
+ Pass an `AbortSignal` to `capture.start()` to stop capture on cancellation, or to `permissions.request()` to cancel waiting for a grant.
90
+
91
+ ## License
92
+
93
+ [MIT](./LICENSE.md) © Hoang Vo
@@ -0,0 +1,179 @@
1
+ /**
2
+ * meeting-record — system-audio and microphone capture with meeting detection.
3
+ *
4
+ * Wraps the native addon so callers never see helper-process pids, status codes,
5
+ * or the realtime callback. The addon itself is not re-exported.
6
+ */
7
+ import { EventEmitter } from "node:events";
8
+ import { Readable } from "node:stream";
9
+ import { type MixedAudio, type MixedAudioOptions } from "./mixed-audio.js";
10
+ export type { MixedAudio, MixedAudioOptions } from "./mixed-audio.js";
11
+ export type Permission = "system-audio" | "microphone" | "accessibility";
12
+ export type PermissionStatus = "granted" | "denied" | "unknown" | "not-required";
13
+ /**
14
+ * Stable platform identifier; the values never change. No display labels are
15
+ * provided — presentation and localisation belong to the caller.
16
+ */
17
+ export type Platform = "zoom" | "teams" | "meet" | "webex" | "slack" | "discord"
18
+ /** A browser tab on a recognised call URL. */
19
+ | "browser" | "unknown";
20
+ export interface Meeting {
21
+ readonly platform: Platform;
22
+ /** The visible app. Use it in `CaptureTarget` when starting capture. */
23
+ readonly pid: number;
24
+ readonly appName: string;
25
+ /** Requires the accessibility permission; empty string otherwise. */
26
+ readonly title: string;
27
+ /** Requires the accessibility permission; empty string otherwise. */
28
+ readonly url: string;
29
+ readonly isUsingMic: boolean;
30
+ readonly isPlayingAudio: boolean;
31
+ readonly confidence: number;
32
+ /** Prefer this to comparing `confidence`; the weights may change. */
33
+ readonly shouldRecord: boolean;
34
+ }
35
+ export interface AudioProcess {
36
+ readonly pid: number;
37
+ readonly name: string;
38
+ readonly bundleId: string;
39
+ readonly isPlayingAudio: boolean;
40
+ readonly isUsingMic: boolean;
41
+ }
42
+ export type CaptureTarget = {
43
+ /** A detected meeting's app pid, or any audio-producing process pid. */
44
+ type: "process";
45
+ pid: number;
46
+ } | {
47
+ /** All application output. */
48
+ type: "system";
49
+ };
50
+ export type MicrophoneSource = "default";
51
+ export interface CaptureOptions {
52
+ /** Mono mixdown for system audio. Default true. */
53
+ mono?: boolean;
54
+ /** Capture the default microphone as a separate track. Default off. */
55
+ microphone?: MicrophoneSource;
56
+ /** Additional mono s16le output, aligned and resampled to the requested rate. */
57
+ mix?: MixedAudioOptions;
58
+ /** Aborting stops capture, including a pending start. */
59
+ signal?: AbortSignal;
60
+ }
61
+ export declare class MeetingRecordError extends Error {
62
+ readonly code: number;
63
+ constructor(message: string, code: number);
64
+ }
65
+ export declare class UnsupportedOsError extends MeetingRecordError {
66
+ }
67
+ export declare class PermissionError extends MeetingRecordError {
68
+ }
69
+ export declare class AlreadyRunningError extends MeetingRecordError {
70
+ }
71
+ export declare class CaptureError extends MeetingRecordError {
72
+ }
73
+ export declare class InternalError extends MeetingRecordError {
74
+ }
75
+ export declare const permissions: {
76
+ status(permission: Permission): PermissionStatus;
77
+ /**
78
+ * Show the permission prompt and resolve once the user answers.
79
+ *
80
+ * Accessibility opens System Settings and needs an app relaunch, so it resolves
81
+ * as soon as Settings is open.
82
+ */
83
+ request(permission: Permission, { timeoutMs, signal }?: {
84
+ timeoutMs?: number;
85
+ signal?: AbortSignal;
86
+ }): Promise<PermissionStatus>;
87
+ };
88
+ export interface MeetingEvents {
89
+ started: (meeting: Meeting) => void;
90
+ updated: (meeting: Meeting) => void;
91
+ ended: (meeting: Meeting) => void;
92
+ }
93
+ declare class Meetings extends EventEmitter {
94
+ /** Point-in-time scan, highest confidence first. */
95
+ scan(): Meeting[];
96
+ /** Begin emitting `started` / `updated` / `ended`. */
97
+ watch(): void;
98
+ unwatch(): void;
99
+ get watching(): boolean;
100
+ /** Every process doing audio IO. */
101
+ audioProcesses(): AudioProcess[];
102
+ /**
103
+ * Applications doing audio IO, grouped by owning PID, including helpers.
104
+ * Pass `pid` as a process target; no meeting scan or Accessibility grant is needed.
105
+ */
106
+ audioApps(): AudioProcess[];
107
+ }
108
+ export declare const meetings: Meetings & {
109
+ on<E extends keyof MeetingEvents>(event: E, listener: MeetingEvents[E]): Meetings;
110
+ once<E extends keyof MeetingEvents>(event: E, listener: MeetingEvents[E]): Meetings;
111
+ off<E extends keyof MeetingEvents>(event: E, listener: MeetingEvents[E]): Meetings;
112
+ };
113
+ /**
114
+ * Interleaved float32 PCM.
115
+ *
116
+ * The native callback never waits for a slow consumer. If this stream is not read,
117
+ * its preallocated ring buffer drops samples and emits `drop`.
118
+ */
119
+ export type CaptureState = "recording" | "paused" | "stopped";
120
+ export type CaptureHealth = "running" | "recovering" | "failed" | "stopped";
121
+ export interface AudioFormat {
122
+ readonly sampleRate: number;
123
+ readonly channels: number;
124
+ }
125
+ /**
126
+ * A PCM buffer with the capture time of its first frame.
127
+ *
128
+ * `hostTimeNs` is monotonic (not Unix time) and uses the same host clock for the
129
+ * system and microphone tracks, so callers can align or echo-cancel them without
130
+ * relying on callback arrival order.
131
+ */
132
+ export interface AudioChunk extends Buffer {
133
+ readonly hostTimeNs: bigint;
134
+ readonly frameCount: number;
135
+ readonly sampleRate: number;
136
+ readonly channels: number;
137
+ readonly droppedSamples: number;
138
+ }
139
+ export interface AudioTrack extends Readable {
140
+ /** Current negotiated format; a `format` event announces changes. */
141
+ readonly sampleRate: number;
142
+ readonly channels: number;
143
+ /** Interleaved samples discarded because the bounded queue could not accept them. */
144
+ readonly droppedSamples: number;
145
+ on(event: "data", listener: (chunk: AudioChunk) => void): this;
146
+ on(event: "drop", listener: (droppedSamples: number) => void): this;
147
+ /** Emitted before the first chunk in a newly negotiated device format. */
148
+ on(event: "format", listener: (format: AudioFormat) => void): this;
149
+ on(event: string | symbol, listener: (...args: any[]) => void): this;
150
+ }
151
+ export interface CaptureSession extends EventEmitter {
152
+ readonly systemAudio: AudioTrack;
153
+ readonly microphone?: AudioTrack;
154
+ readonly mixedAudio?: MixedAudio;
155
+ readonly state: CaptureState;
156
+ /** Native device health; independent of pause/resume state. */
157
+ readonly health: CaptureHealth;
158
+ /** Set when automatic recovery exhausts its retries. */
159
+ readonly failure?: CaptureError;
160
+ pauseRecording(): void;
161
+ resumeRecording(): void;
162
+ stopRecording(): Promise<void>;
163
+ on(event: "interrupted", listener: () => void): this;
164
+ on(event: "recovered", listener: () => void): this;
165
+ on(event: "failed", listener: (error: CaptureError) => void): this;
166
+ on(event: string | symbol, listener: (...args: any[]) => void): this;
167
+ }
168
+ export interface MixedCaptureSession extends CaptureSession {
169
+ readonly mixedAudio: MixedAudio;
170
+ }
171
+ declare function startCapture(target: CaptureTarget, options: CaptureOptions & {
172
+ mix: MixedAudioOptions;
173
+ }): Promise<MixedCaptureSession>;
174
+ declare function startCapture(target: CaptureTarget, options?: CaptureOptions): Promise<CaptureSession>;
175
+ export declare const capture: {
176
+ start: typeof startCapture;
177
+ readonly running: boolean;
178
+ };
179
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,395 @@
1
+ /**
2
+ * meeting-record — system-audio and microphone capture with meeting detection.
3
+ *
4
+ * Wraps the native addon so callers never see helper-process pids, status codes,
5
+ * or the realtime callback. The addon itself is not re-exported.
6
+ */
7
+ import { EventEmitter } from "node:events";
8
+ import { Readable } from "node:stream";
9
+ import { setTimeout as delay } from "node:timers/promises";
10
+ import { createRequire } from "node:module";
11
+ import { fileURLToPath } from "node:url";
12
+ import { MixedAudioImpl } from "./mixed-audio.js";
13
+ // Native addons cannot be imported under ESM. node-gyp-build selects the
14
+ // bundled binary for the current platform; local builds remain usable in-repo.
15
+ const loadNative = createRequire(import.meta.url)("node-gyp-build");
16
+ const native = loadNative(fileURLToPath(new URL("..", import.meta.url)));
17
+ export class MeetingRecordError extends Error {
18
+ code;
19
+ constructor(message, code) {
20
+ super(message);
21
+ this.code = code;
22
+ this.name = new.target.name;
23
+ }
24
+ }
25
+ export class UnsupportedOsError extends MeetingRecordError {
26
+ }
27
+ export class PermissionError extends MeetingRecordError {
28
+ }
29
+ export class AlreadyRunningError extends MeetingRecordError {
30
+ }
31
+ export class CaptureError extends MeetingRecordError {
32
+ }
33
+ export class InternalError extends MeetingRecordError {
34
+ }
35
+ function errorFromStatus(message, code) {
36
+ switch (code) {
37
+ case -1:
38
+ return new UnsupportedOsError(message, code);
39
+ case -2:
40
+ return new PermissionError(message, code);
41
+ case -3:
42
+ return new AlreadyRunningError(message, code);
43
+ case -4:
44
+ case -5:
45
+ case -6:
46
+ case -7:
47
+ case -8:
48
+ return new CaptureError(message, code);
49
+ default:
50
+ return new InternalError(message, code);
51
+ }
52
+ }
53
+ function asLibraryError(error) {
54
+ if (error instanceof TypeError)
55
+ return error;
56
+ const message = error instanceof Error ? error.message : String(error);
57
+ const code = error?.code;
58
+ return typeof code === "number" ? errorFromStatus(message, code) : new InternalError(message, -9);
59
+ }
60
+ export const permissions = {
61
+ status(permission) {
62
+ if (permission !== "system-audio" &&
63
+ permission !== "microphone" &&
64
+ permission !== "accessibility") {
65
+ throw new TypeError(`unknown permission: ${String(permission)}`);
66
+ }
67
+ if (permission === "accessibility")
68
+ return native.accessibilityPermissionStatus();
69
+ if (permission === "microphone")
70
+ return native.microphonePermissionStatus();
71
+ return native.audioPermissionStatus();
72
+ },
73
+ /**
74
+ * Show the permission prompt and resolve once the user answers.
75
+ *
76
+ * Accessibility opens System Settings and needs an app relaunch, so it resolves
77
+ * as soon as Settings is open.
78
+ */
79
+ async request(permission, { timeoutMs = 120_000, signal } = {}) {
80
+ signal?.throwIfAborted();
81
+ const current = permissions.status(permission);
82
+ if (current === "granted" || current === "not-required")
83
+ return current;
84
+ if (permission === "accessibility") {
85
+ const status = native.requestAccessibilityPermission();
86
+ if (status !== 0)
87
+ throw errorFromStatus("failed to request accessibility permission", status);
88
+ return permissions.status(permission);
89
+ }
90
+ const requestStatus = permission === "microphone"
91
+ ? native.requestMicrophonePermission()
92
+ : native.requestAudioPermission();
93
+ if (requestStatus !== 0) {
94
+ throw errorFromStatus(`failed to request ${permission} permission`, requestStatus);
95
+ }
96
+ // The dialog is modal to the user, not to this process.
97
+ const deadline = Date.now() + timeoutMs;
98
+ while (Date.now() < deadline) {
99
+ await delay(500, undefined, { signal });
100
+ const status = permissions.status(permission);
101
+ if (status === "granted" || status === "denied")
102
+ return status;
103
+ }
104
+ return permissions.status(permission);
105
+ },
106
+ };
107
+ function toMeeting(raw) {
108
+ return raw;
109
+ }
110
+ class Meetings extends EventEmitter {
111
+ /** Point-in-time scan, highest confidence first. */
112
+ scan() {
113
+ return native.scan().map(toMeeting);
114
+ }
115
+ /** Begin emitting `started` / `updated` / `ended`. */
116
+ watch() {
117
+ if (native.isWatching())
118
+ return;
119
+ const status = native.watchStart((event, raw) => {
120
+ this.emit(event, toMeeting(raw));
121
+ });
122
+ if (status !== 0)
123
+ throw errorFromStatus("failed to start watching", status);
124
+ }
125
+ unwatch() {
126
+ if (native.isWatching())
127
+ native.watchStop();
128
+ }
129
+ get watching() {
130
+ return native.isWatching();
131
+ }
132
+ /** Every process doing audio IO. */
133
+ audioProcesses() {
134
+ return native.listAudioProcesses();
135
+ }
136
+ /**
137
+ * Applications doing audio IO, grouped by owning PID, including helpers.
138
+ * Pass `pid` as a process target; no meeting scan or Accessibility grant is needed.
139
+ */
140
+ audioApps() {
141
+ return native.listAudioApps();
142
+ }
143
+ }
144
+ export const meetings = new Meetings();
145
+ class AudioTrackImpl extends Readable {
146
+ #sampleRate;
147
+ #channels;
148
+ #microphone;
149
+ #droppedSamples = 0;
150
+ #consumerReady = true;
151
+ #mixReady = true;
152
+ #mixConsumer;
153
+ #finished = false;
154
+ constructor(sampleRate, channels, microphone) {
155
+ super({ objectMode: false, highWaterMark: 1 << 20 });
156
+ this.#sampleRate = sampleRate;
157
+ this.#channels = channels;
158
+ this.#microphone = microphone;
159
+ }
160
+ /** Required by Readable; data arrives from the native side, not on demand. */
161
+ _read() {
162
+ if (!this.#consumerReady && !this.#finished) {
163
+ this.#consumerReady = true;
164
+ native.captureSetConsumerReady(this.#microphone, this.#mixReady);
165
+ }
166
+ }
167
+ setMixConsumer(consumer) {
168
+ this.#mixConsumer = consumer;
169
+ }
170
+ setMixReady(ready) {
171
+ this.#mixReady = ready;
172
+ if (!this.#finished)
173
+ native.captureSetConsumerReady(this.#microphone, ready && this.#consumerReady);
174
+ }
175
+ get sampleRate() {
176
+ return this.#sampleRate;
177
+ }
178
+ get channels() {
179
+ return this.#channels;
180
+ }
181
+ get droppedSamples() {
182
+ return this.#droppedSamples;
183
+ }
184
+ pushAudio(chunk, info) {
185
+ if (info.dropped > 0) {
186
+ this.#droppedSamples += info.dropped;
187
+ this.emit("drop", info.dropped);
188
+ }
189
+ if (info.sampleRate !== this.#sampleRate || info.channels !== this.#channels) {
190
+ this.#sampleRate = info.sampleRate;
191
+ this.#channels = info.channels;
192
+ this.emit("format", { sampleRate: info.sampleRate, channels: info.channels });
193
+ }
194
+ Object.defineProperties(chunk, {
195
+ hostTimeNs: { value: info.hostTimeNs, enumerable: false },
196
+ frameCount: { value: info.frameCount, enumerable: false },
197
+ sampleRate: { value: info.sampleRate, enumerable: false },
198
+ channels: { value: info.channels, enumerable: false },
199
+ droppedSamples: { value: info.dropped, enumerable: false },
200
+ });
201
+ this.#mixConsumer?.(chunk);
202
+ if (!this.push(chunk) && this.#consumerReady) {
203
+ this.#consumerReady = false;
204
+ native.captureSetConsumerReady(this.#microphone, false);
205
+ }
206
+ }
207
+ finish() {
208
+ if (this.#finished)
209
+ return;
210
+ this.#finished = true;
211
+ this.push(null);
212
+ }
213
+ }
214
+ class CaptureSessionImpl extends EventEmitter {
215
+ #state = "recording";
216
+ #health = "running";
217
+ #failure;
218
+ #healthTimer;
219
+ #systemAudio;
220
+ #microphone;
221
+ mixedAudio;
222
+ #signal;
223
+ #onAbort = () => {
224
+ void this.stopRecording();
225
+ };
226
+ constructor(systemAudio, microphone, mix, signal) {
227
+ super();
228
+ this.#systemAudio = systemAudio;
229
+ this.#microphone = microphone;
230
+ this.#signal = signal;
231
+ signal?.addEventListener("abort", this.#onAbort, { once: true });
232
+ this.mixedAudio = mix ? new MixedAudioImpl(mix.sampleRate, systemAudio, microphone) : undefined;
233
+ this.#healthTimer = setInterval(() => this.#pollHealth(), 250);
234
+ this.#healthTimer.unref();
235
+ }
236
+ get systemAudio() {
237
+ return this.#systemAudio;
238
+ }
239
+ get microphone() {
240
+ return this.#microphone;
241
+ }
242
+ get state() {
243
+ return this.#state;
244
+ }
245
+ get health() {
246
+ return this.#health;
247
+ }
248
+ get failure() {
249
+ return this.#failure;
250
+ }
251
+ pauseRecording() {
252
+ if (this.#state === "stopped")
253
+ return;
254
+ if (this.#state !== "paused") {
255
+ native.capturePause();
256
+ this.#state = "paused";
257
+ this.mixedAudio?.pauseCapture();
258
+ }
259
+ }
260
+ resumeRecording() {
261
+ if (this.#state === "stopped")
262
+ return;
263
+ if (this.#state !== "recording") {
264
+ this.mixedAudio?.resumeCapture();
265
+ native.captureResume();
266
+ this.#state = "recording";
267
+ }
268
+ }
269
+ async stopRecording() {
270
+ if (this.#state === "stopped")
271
+ return;
272
+ clearInterval(this.#healthTimer);
273
+ native.captureStop();
274
+ this.#state = "stopped";
275
+ this.#health = "stopped";
276
+ this.#finishTracks();
277
+ }
278
+ #finishTracks() {
279
+ this.#signal?.removeEventListener("abort", this.#onAbort);
280
+ this.#systemAudio.finish();
281
+ this.#microphone?.finish();
282
+ this.mixedAudio?.finishCapture();
283
+ }
284
+ #pollHealth() {
285
+ if (this.#state === "stopped")
286
+ return;
287
+ const health = native.captureHealthStatus();
288
+ if (health === this.#health)
289
+ return;
290
+ const previous = this.#health;
291
+ this.#health = health;
292
+ if (health === "recovering") {
293
+ this.emit("interrupted");
294
+ }
295
+ else if (health === "running" && previous === "recovering") {
296
+ this.emit("recovered");
297
+ }
298
+ else if (health === "failed") {
299
+ const failure = new CaptureError(native.lastError(), -7);
300
+ this.#failure = failure;
301
+ clearInterval(this.#healthTimer);
302
+ native.captureStop();
303
+ this.#state = "stopped";
304
+ this.#finishTracks();
305
+ this.emit("failed", failure);
306
+ }
307
+ }
308
+ }
309
+ let captureStarting = false;
310
+ async function startCapture(target, options = {}) {
311
+ options.signal?.throwIfAborted();
312
+ if (captureStarting || native.isRunning()) {
313
+ throw new AlreadyRunningError("capture already running", -3);
314
+ }
315
+ captureStarting = true;
316
+ let session;
317
+ const pendingSystemAudio = [];
318
+ const pendingMicrophone = [];
319
+ const receive = (track, pending, chunk, info) => {
320
+ if (!session) {
321
+ pending.push({ chunk, info });
322
+ return;
323
+ }
324
+ if (session.state !== "recording")
325
+ return;
326
+ session[track]?.pushAudio(chunk, info);
327
+ };
328
+ const onSystemAudio = (chunk, info) => {
329
+ receive("systemAudio", pendingSystemAudio, chunk, info);
330
+ };
331
+ const onMicrophone = (chunk, info) => {
332
+ receive("microphone", pendingMicrophone, chunk, info);
333
+ };
334
+ try {
335
+ let nativeTarget;
336
+ if (target.type === "process") {
337
+ if (!Number.isInteger(target.pid) || target.pid <= 0) {
338
+ throw new TypeError("process pid must be a positive integer");
339
+ }
340
+ nativeTarget = { pid: target.pid };
341
+ }
342
+ else if (target.type === "system") {
343
+ nativeTarget = { systemWide: true };
344
+ }
345
+ else {
346
+ throw new TypeError(`unknown capture target: ${target.type}`);
347
+ }
348
+ if (options.mono !== undefined && typeof options.mono !== "boolean") {
349
+ throw new TypeError("mono must be a boolean");
350
+ }
351
+ if (options.microphone !== undefined && options.microphone !== "default") {
352
+ throw new TypeError('microphone must be "default"');
353
+ }
354
+ if (options.mix !== undefined &&
355
+ (!Number.isInteger(options.mix.sampleRate) || options.mix.sampleRate <= 0)) {
356
+ throw new TypeError("mix sampleRate must be a positive integer");
357
+ }
358
+ const formats = await native.captureStart({
359
+ ...nativeTarget,
360
+ mono: options.mono ?? true,
361
+ microphone: options.microphone === "default",
362
+ }, onSystemAudio, onMicrophone);
363
+ if (options.signal?.aborted) {
364
+ native.captureStop();
365
+ options.signal.throwIfAborted();
366
+ }
367
+ const systemAudio = new AudioTrackImpl(formats.systemAudio.sampleRate, formats.systemAudio.channels, false);
368
+ const microphone = formats.microphone
369
+ ? new AudioTrackImpl(formats.microphone.sampleRate, formats.microphone.channels, true)
370
+ : undefined;
371
+ session = new CaptureSessionImpl(systemAudio, microphone, options.mix, options.signal);
372
+ for (const { chunk, info } of pendingSystemAudio) {
373
+ systemAudio.pushAudio(chunk, info);
374
+ }
375
+ for (const { chunk, info } of pendingMicrophone) {
376
+ microphone?.pushAudio(chunk, info);
377
+ }
378
+ return session;
379
+ }
380
+ catch (error) {
381
+ if (options.signal?.aborted)
382
+ throw error;
383
+ throw asLibraryError(error);
384
+ }
385
+ finally {
386
+ captureStarting = false;
387
+ }
388
+ }
389
+ export const capture = {
390
+ start: startCapture,
391
+ get running() {
392
+ return captureStarting || native.captureHealthStatus() !== "stopped";
393
+ },
394
+ };
395
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { Readable } from "node:stream";
2
+ import type { AudioChunk } from "./index.js";
3
+ export interface MixedAudioOptions {
4
+ sampleRate: number;
5
+ }
6
+ /** Mono signed 16-bit little-endian PCM. Await `end` to consume the final tail. */
7
+ export interface MixedAudio extends Readable {
8
+ readonly sampleRate: number;
9
+ readonly channels: 1;
10
+ /** Affects the mixed output only; the raw microphone track stays available. */
11
+ setMicrophoneMuted(muted: boolean): void;
12
+ on(event: "level", listener: (level: number) => void): this;
13
+ on(event: "data", listener: (chunk: Buffer) => void): this;
14
+ on(event: string | symbol, listener: (...args: any[]) => void): this;
15
+ }
16
+ export interface MixSource {
17
+ setMixConsumer(consumer: ((chunk: AudioChunk) => void) | undefined): void;
18
+ setMixReady(ready: boolean): void;
19
+ resume(): unknown;
20
+ }
21
+ export declare class MixedAudioImpl extends Readable implements MixedAudio {
22
+ #private;
23
+ readonly sampleRate: number;
24
+ readonly channels = 1;
25
+ constructor(sampleRate: number, systemAudio: MixSource, microphone?: MixSource);
26
+ _read(): void;
27
+ _destroy(error: Error | null, callback: (error?: Error | null) => void): void;
28
+ setMicrophoneMuted(muted: boolean): void;
29
+ pauseCapture(): void;
30
+ resumeCapture(): void;
31
+ finishCapture(): void;
32
+ }
33
+ //# sourceMappingURL=mixed-audio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mixed-audio.d.ts","sourceRoot":"","sources":["../src/mixed-audio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,mFAAmF;AACnF,MAAM,WAAW,UAAW,SAAQ,QAAQ;IAC1C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrB,+EAA+E;IAC/E,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACzC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC5D,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC3D,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;CACtE;AAED,MAAM,WAAW,SAAS;IACxB,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;IAC1E,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,MAAM,IAAI,OAAO,CAAC;CACnB;AAED,qBAAa,cAAe,SAAQ,QAAS,YAAW,UAAU;;IAChE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,KAAK;gBAaV,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,CAAC,EAAE,SAAS;IAcrE,KAAK,IAAI,IAAI;IAQb,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI;IAWtF,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAIxC,YAAY,IAAI,IAAI;IAQpB,aAAa,IAAI,IAAI;IAOrB,aAAa,IAAI,IAAI;CAmEtB"}
@@ -0,0 +1,137 @@
1
+ import { Readable } from "node:stream";
2
+ import { getLevel, PcmMixer } from "./pcm-mixer.js";
3
+ export class MixedAudioImpl extends Readable {
4
+ sampleRate;
5
+ channels = 1;
6
+ #sources;
7
+ #microphone;
8
+ #mixer;
9
+ #timer;
10
+ #startup = new Map();
11
+ #startupDeadline = 0;
12
+ #startupTimer;
13
+ #blocked = false;
14
+ #pumping = false;
15
+ #paused = false;
16
+ #finished = false;
17
+ constructor(sampleRate, systemAudio, microphone) {
18
+ super({ highWaterMark: sampleRate * 2 });
19
+ this.sampleRate = sampleRate;
20
+ this.#sources = microphone ? [systemAudio, microphone] : [systemAudio];
21
+ this.#microphone = microphone;
22
+ this.#mixer = new PcmMixer(sampleRate, this.#sources);
23
+ for (const source of this.#sources) {
24
+ source.setMixConsumer((chunk) => this.#write(source, chunk));
25
+ source.resume();
26
+ }
27
+ this.#timer = setInterval(() => this.#pump(), 100);
28
+ this.#timer.unref();
29
+ }
30
+ _read() {
31
+ this.#blocked = false;
32
+ this.#pump();
33
+ if (!this.#blocked && !this.#finished) {
34
+ for (const source of this.#sources)
35
+ source.setMixReady(true);
36
+ }
37
+ }
38
+ _destroy(error, callback) {
39
+ clearInterval(this.#timer);
40
+ clearTimeout(this.#startupTimer);
41
+ this.#startup?.clear();
42
+ for (const source of this.#sources) {
43
+ source.setMixConsumer(undefined);
44
+ source.setMixReady(true);
45
+ }
46
+ callback(error);
47
+ }
48
+ setMicrophoneMuted(muted) {
49
+ if (this.#microphone)
50
+ this.#mixer.setMuted(this.#microphone, muted);
51
+ }
52
+ pauseCapture() {
53
+ if (this.#paused || this.#finished || this.destroyed)
54
+ return;
55
+ this.#paused = true;
56
+ this.#flushStartup(true);
57
+ this.#mixer.finishSegment();
58
+ this.#pump();
59
+ }
60
+ resumeCapture() {
61
+ if (!this.#paused || this.#finished || this.destroyed)
62
+ return;
63
+ this.#paused = false;
64
+ this.#mixer.restart();
65
+ this.#startup = new Map();
66
+ }
67
+ finishCapture() {
68
+ if (this.#finished || this.destroyed)
69
+ return;
70
+ clearInterval(this.#timer);
71
+ this.#finished = true;
72
+ this.#flushStartup(true);
73
+ this.#mixer.finishSegment();
74
+ this.#pump();
75
+ }
76
+ #write(source, chunk) {
77
+ if (!this.#startup) {
78
+ this.#mixer.write(source, chunk);
79
+ return;
80
+ }
81
+ if (this.#startup.size === 0) {
82
+ this.#startupDeadline = performance.now() + 100;
83
+ this.#startupTimer = setTimeout(() => {
84
+ this.#flushStartup(true);
85
+ this.#pump();
86
+ }, 100);
87
+ this.#startupTimer.unref();
88
+ }
89
+ const pending = this.#startup.get(source) ?? [];
90
+ pending.push(chunk);
91
+ this.#startup.set(source, pending);
92
+ this.#flushStartup();
93
+ }
94
+ #flushStartup(force = false) {
95
+ if (!this.#startup?.size)
96
+ return;
97
+ if (!force &&
98
+ this.#startup.size < this.#sources.length &&
99
+ performance.now() < this.#startupDeadline) {
100
+ return;
101
+ }
102
+ clearTimeout(this.#startupTimer);
103
+ // Establish the earliest source timestamp while retaining each source's order.
104
+ const pending = [...this.#startup].sort(([, left], [, right]) => {
105
+ if (left[0].hostTimeNs < right[0].hostTimeNs)
106
+ return -1;
107
+ return left[0].hostTimeNs > right[0].hostTimeNs ? 1 : 0;
108
+ });
109
+ this.#startup = undefined;
110
+ for (const [source, chunks] of pending) {
111
+ for (const chunk of chunks)
112
+ this.#mixer.write(source, chunk);
113
+ }
114
+ }
115
+ #pump() {
116
+ if (this.#pumping || this.destroyed)
117
+ return;
118
+ this.#pumping = true;
119
+ this.#flushStartup();
120
+ while (!this.#blocked) {
121
+ const chunk = this.#mixer.read();
122
+ if (!chunk) {
123
+ if (this.#finished)
124
+ this.push(null);
125
+ break;
126
+ }
127
+ if (!this.push(chunk)) {
128
+ this.#blocked = true;
129
+ for (const source of this.#sources)
130
+ source.setMixReady(false);
131
+ }
132
+ this.emit("level", getLevel(chunk));
133
+ }
134
+ this.#pumping = false;
135
+ }
136
+ }
137
+ //# sourceMappingURL=mixed-audio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mixed-audio.js","sourceRoot":"","sources":["../src/mixed-audio.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { AudioChunk } from "./index.js";
2
+ /**
3
+ * Converts interleaved float32 at any rate and channel count into mono at
4
+ * `outputRate`, carrying fractional position across chunks.
5
+ * Downsampling averages each output's input span.
6
+ */
7
+ export declare class MonoResampler {
8
+ #private;
9
+ constructor(outputRate: number);
10
+ resample(chunk: AudioChunk): Float32Array;
11
+ flush(): Float32Array;
12
+ reset(): void;
13
+ }
14
+ //# sourceMappingURL=mono-resampler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mono-resampler.d.ts","sourceRoot":"","sources":["../src/mono-resampler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;GAIG;AACH,qBAAa,aAAa;;gBAYZ,UAAU,EAAE,MAAM;IAI9B,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,YAAY;IAczC,KAAK,IAAI,YAAY;IAcrB,KAAK,IAAI,IAAI;CA+Cd"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Converts interleaved float32 at any rate and channel count into mono at
3
+ * `outputRate`, carrying fractional position across chunks.
4
+ * Downsampling averages each output's input span.
5
+ */
6
+ export class MonoResampler {
7
+ #outputRate;
8
+ #sampleRate = 0;
9
+ #step = 1;
10
+ // Upsampling: the last input frame and the next output's input position,
11
+ // relative to the current chunk (-1 is `#previous`).
12
+ #previous = 0;
13
+ #position = 0;
14
+ // Downsampling: the weighted sum and input span gathered for the next output.
15
+ #sum = 0;
16
+ #covered = 0;
17
+ constructor(outputRate) {
18
+ this.#outputRate = outputRate;
19
+ }
20
+ resample(chunk) {
21
+ if (chunk.sampleRate !== this.#sampleRate) {
22
+ this.#sampleRate = chunk.sampleRate;
23
+ this.#step = chunk.sampleRate / this.#outputRate;
24
+ this.#previous = 0;
25
+ this.#position = 0;
26
+ this.#sum = 0;
27
+ this.#covered = 0;
28
+ }
29
+ const input = downmix(chunk);
30
+ if (this.#step === 1)
31
+ return input;
32
+ return this.#step > 1 ? this.#average(input) : this.#interpolate(input);
33
+ }
34
+ flush() {
35
+ if (this.#step > 1) {
36
+ const output = this.#covered > 0 ? Float32Array.of(this.#sum / this.#covered) : new Float32Array(0);
37
+ this.#sum = 0;
38
+ this.#covered = 0;
39
+ return output;
40
+ }
41
+ const output = new Float32Array(Math.max(0, Math.ceil(-this.#position / this.#step - 1e-9)));
42
+ output.fill(this.#previous);
43
+ this.#position = 0;
44
+ return output;
45
+ }
46
+ reset() {
47
+ this.#sampleRate = 0;
48
+ this.#sum = 0;
49
+ this.#covered = 0;
50
+ this.#position = 0;
51
+ this.#previous = 0;
52
+ }
53
+ #average(input) {
54
+ const step = this.#step;
55
+ const output = new Float32Array(Math.floor((this.#covered + input.length) / step) + 1);
56
+ let written = 0;
57
+ for (const sample of input) {
58
+ let remaining = 1;
59
+ while (remaining > 0) {
60
+ const taken = Math.min(step - this.#covered, remaining);
61
+ this.#sum += sample * taken;
62
+ this.#covered += taken;
63
+ remaining -= taken;
64
+ if (this.#covered >= step - 1e-9) {
65
+ output[written++] = this.#sum / step;
66
+ this.#sum = 0;
67
+ this.#covered = 0;
68
+ }
69
+ }
70
+ }
71
+ return output.subarray(0, written);
72
+ }
73
+ #interpolate(input) {
74
+ const last = input.length - 1;
75
+ const output = new Float32Array(Math.max(0, Math.ceil((last - this.#position) / this.#step)));
76
+ let written = 0;
77
+ // Stops short of the final frame, which still needs its successor.
78
+ while (this.#position < last && written < output.length) {
79
+ const index = Math.floor(this.#position);
80
+ const from = index < 0 ? this.#previous : input[index];
81
+ const to = input[index + 1];
82
+ output[written++] = from + (to - from) * (this.#position - index);
83
+ this.#position += this.#step;
84
+ }
85
+ if (input.length > 0) {
86
+ this.#position -= input.length;
87
+ this.#previous = input[last];
88
+ }
89
+ return output.subarray(0, written);
90
+ }
91
+ }
92
+ function downmix(chunk) {
93
+ const samples = new Float32Array(chunk.buffer, chunk.byteOffset, chunk.byteLength / Float32Array.BYTES_PER_ELEMENT);
94
+ const { channels } = chunk;
95
+ if (channels === 1) {
96
+ return samples;
97
+ }
98
+ const mono = new Float32Array(Math.floor(samples.length / channels));
99
+ for (let frame = 0; frame < mono.length; frame++) {
100
+ let sum = 0;
101
+ for (let channel = 0; channel < channels; channel++) {
102
+ sum += samples[frame * channels + channel];
103
+ }
104
+ mono[frame] = sum / channels;
105
+ }
106
+ return mono;
107
+ }
108
+ //# sourceMappingURL=mono-resampler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mono-resampler.js","sourceRoot":"","sources":["../src/mono-resampler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { AudioChunk } from "./index.js";
2
+ /** Timestamp-aligned mono s16le, with sparse silence between capture packets. */
3
+ export declare class PcmMixer {
4
+ #private;
5
+ constructor(sampleRate: number, sources: readonly object[]);
6
+ write(source: object, chunk: AudioChunk): void;
7
+ setMuted(source: object, muted: boolean): void;
8
+ /** Makes the remaining captured tail readable before pause or stop. */
9
+ finishSegment(): void;
10
+ /** Keeps unread audio, while removing paused time from the next segment. */
11
+ restart(): void;
12
+ read(): Buffer | null;
13
+ }
14
+ export declare function getLevel(pcm: Buffer): number;
15
+ //# sourceMappingURL=pcm-mixer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pcm-mixer.d.ts","sourceRoot":"","sources":["../src/pcm-mixer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAS7C,iFAAiF;AACjF,qBAAa,QAAQ;;gBAYP,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE;IAgB1D,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;IAiC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAI9C,uEAAuE;IACvE,aAAa,IAAI,IAAI;IAOrB,4EAA4E;IAC5E,OAAO,IAAI,IAAI;IASf,IAAI,IAAI,MAAM,GAAG,IAAI;CAoCtB;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO5C"}
@@ -0,0 +1,119 @@
1
+ import { MonoResampler } from "./mono-resampler.js";
2
+ /** Timestamp-aligned mono s16le, with sparse silence between capture packets. */
3
+ export class PcmMixer {
4
+ #sampleRate;
5
+ #blockFrames;
6
+ #inputs;
7
+ #gain;
8
+ #blocks = new Map();
9
+ #origin = null;
10
+ #originFrame = 0;
11
+ #emitted = 0;
12
+ #end = 0;
13
+ #committed = 0;
14
+ constructor(sampleRate, sources) {
15
+ this.#sampleRate = sampleRate;
16
+ this.#blockFrames = Math.max(1, Math.round(sampleRate / 10));
17
+ this.#inputs = new Map(sources.map((source) => [
18
+ source,
19
+ {
20
+ resampler: new MonoResampler(sampleRate),
21
+ cursor: null,
22
+ muted: false,
23
+ },
24
+ ]));
25
+ this.#gain = 1 / Math.sqrt(sources.length);
26
+ }
27
+ write(source, chunk) {
28
+ const input = this.#input(source);
29
+ const samples = input.resampler.resample(chunk);
30
+ this.#origin ??= chunk.hostTimeNs;
31
+ const position = this.#originFrame +
32
+ Math.round((Number(chunk.hostTimeNs - this.#origin) * this.#sampleRate) / 1e9);
33
+ // Absorb up to 20 ms of callback-clock jitter at packet joins.
34
+ if (input.cursor === null || Math.abs(position - input.cursor) > this.#sampleRate / 50) {
35
+ input.cursor = position;
36
+ }
37
+ this.#append(input, samples);
38
+ }
39
+ #append(input, samples) {
40
+ if (input.cursor === null)
41
+ return;
42
+ const start = input.cursor;
43
+ input.cursor += samples.length;
44
+ this.#end = Math.max(this.#end, input.cursor);
45
+ if (input.muted)
46
+ return;
47
+ for (let index = Math.max(0, this.#emitted - start); index < samples.length; index++) {
48
+ const frame = start + index;
49
+ const blockIndex = Math.floor(frame / this.#blockFrames);
50
+ let block = this.#blocks.get(blockIndex);
51
+ if (!block) {
52
+ block = new Float32Array(this.#blockFrames);
53
+ this.#blocks.set(blockIndex, block);
54
+ }
55
+ const offset = frame % this.#blockFrames;
56
+ block[offset] = block[offset] + samples[index] * this.#gain;
57
+ }
58
+ }
59
+ setMuted(source, muted) {
60
+ this.#input(source).muted = muted;
61
+ }
62
+ /** Makes the remaining captured tail readable before pause or stop. */
63
+ finishSegment() {
64
+ for (const input of this.#inputs.values()) {
65
+ this.#append(input, input.resampler.flush());
66
+ }
67
+ this.#committed = this.#end;
68
+ }
69
+ /** Keeps unread audio, while removing paused time from the next segment. */
70
+ restart() {
71
+ this.#origin = null;
72
+ this.#originFrame = this.#end;
73
+ for (const input of this.#inputs.values()) {
74
+ input.cursor = null;
75
+ input.resampler.reset();
76
+ }
77
+ }
78
+ read() {
79
+ const cursors = [...this.#inputs.values()]
80
+ .map((input) => input.cursor)
81
+ .filter((cursor) => cursor !== null);
82
+ let ready = this.#committed;
83
+ if (cursors.length > 0) {
84
+ const lead = Math.max(...cursors);
85
+ // A track more than half a second behind contributes silence.
86
+ ready = Math.max(ready, Math.min(...cursors.filter((cursor) => lead - cursor <= this.#sampleRate / 2)));
87
+ }
88
+ const end = Math.min(ready, this.#emitted + this.#blockFrames);
89
+ if (end <= this.#emitted)
90
+ return null;
91
+ const output = Buffer.allocUnsafe((end - this.#emitted) * 2);
92
+ for (let frame = this.#emitted; frame < end; frame++) {
93
+ const block = this.#blocks.get(Math.floor(frame / this.#blockFrames));
94
+ const sample = Math.max(-1, Math.min(1, block?.[frame % this.#blockFrames] ?? 0));
95
+ output.writeInt16LE(Math.round(sample < 0 ? sample * 0x8000 : sample * 0x7fff), (frame - this.#emitted) * 2);
96
+ }
97
+ for (const blockIndex of this.#blocks.keys()) {
98
+ if ((blockIndex + 1) * this.#blockFrames <= end)
99
+ this.#blocks.delete(blockIndex);
100
+ }
101
+ this.#emitted = end;
102
+ return output;
103
+ }
104
+ #input(source) {
105
+ const input = this.#inputs.get(source);
106
+ if (!input)
107
+ throw new Error("Not a source of this mixer");
108
+ return input;
109
+ }
110
+ }
111
+ export function getLevel(pcm) {
112
+ let sum = 0;
113
+ for (let offset = 0; offset < pcm.length; offset += 2) {
114
+ const sample = pcm.readInt16LE(offset) / 0x8000;
115
+ sum += sample * sample;
116
+ }
117
+ return Math.sqrt(sum / (pcm.length / 2));
118
+ }
119
+ //# sourceMappingURL=pcm-mixer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pcm-mixer.js","sourceRoot":"","sources":["../src/pcm-mixer.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,53 @@
1
1
  {
2
2
  "name": "meeting-record",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "System-audio and microphone capture with meeting detection for macOS and Windows",
5
+ "bugs": "https://github.com/hoangvvo/meeting-record/issues",
6
+ "license": "MIT",
7
+ "author": {
8
+ "name": "Hoang Vo",
9
+ "url": "https://hoangvvo.com"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/hoangvvo/meeting-record.git",
14
+ "directory": "node"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "prebuilds"
19
+ ],
20
+ "os": [
21
+ "darwin",
22
+ "win32"
23
+ ],
24
+ "cpu": [
25
+ "arm64",
26
+ "x64"
27
+ ],
28
+ "type": "module",
29
+ "main": "dist/index.js",
30
+ "types": "dist/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "default": "./dist/index.js"
35
+ }
36
+ },
37
+ "scripts": {
38
+ "build:native": "node ../scripts/build-native.mjs",
39
+ "build:addon": "node-gyp rebuild",
40
+ "build:prebuild": "npm run build:native && prebuildify --napi",
41
+ "build:ts": "tsc -p tsconfig.json",
42
+ "build": "npm run build:native && npm run build:addon && npm run build:ts",
43
+ "prepack": "npm run build:ts",
44
+ "test": "node --test"
45
+ },
46
+ "dependencies": {
47
+ "node-gyp-build": "^4.8.4"
48
+ },
49
+ "engines": {
50
+ "node": ">=22.13.0 <23 || >=24"
51
+ },
52
+ "gypfile": false
53
+ }