@livekit/rtc-node 0.13.30 → 0.13.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audio_source.cjs +38 -19
- package/dist/audio_source.cjs.map +1 -1
- package/dist/audio_source.d.cts +12 -4
- package/dist/audio_source.d.ts +12 -4
- package/dist/audio_source.d.ts.map +1 -1
- package/dist/audio_source.js +38 -19
- package/dist/audio_source.js.map +1 -1
- package/dist/{audio_stream-B4hGVcKz.d.cts → audio_stream-4M8XlO4-.d.cts} +25 -0
- package/dist/{audio_stream-DEG1JKge.d.ts → audio_stream-CTyN1Ldy.d.ts} +25 -0
- package/dist/audio_stream.cjs +31 -18
- package/dist/audio_stream.cjs.map +1 -1
- package/dist/audio_stream.d.cts +1 -1
- package/dist/audio_stream.d.ts +1 -1
- package/dist/audio_stream.d.ts.map +1 -1
- package/dist/audio_stream.js +31 -18
- package/dist/audio_stream.js.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/participant.d.cts +1 -1
- package/dist/participant.d.ts +1 -1
- package/dist/room.cjs +1 -0
- package/dist/room.cjs.map +1 -1
- package/dist/room.d.cts +1 -1
- package/dist/room.d.ts +1 -1
- package/dist/room.d.ts.map +1 -1
- package/dist/room.js +1 -0
- package/dist/room.js.map +1 -1
- package/dist/track.cjs +16 -0
- package/dist/track.cjs.map +1 -1
- package/dist/track.d.cts +1 -1
- package/dist/track.d.ts +1 -1
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +16 -0
- package/dist/track.js.map +1 -1
- package/dist/track_publication.d.cts +1 -1
- package/dist/track_publication.d.ts +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/video_stream.d.cts +1 -1
- package/dist/video_stream.d.ts +1 -1
- package/package.json +3 -3
- package/src/audio_source.test.ts +92 -0
- package/src/audio_source.ts +42 -18
- package/src/audio_stream.ts +37 -24
- package/src/audio_stream_room_lifecycle.test.ts +50 -3
- package/src/room.ts +4 -0
- package/src/tests/e2e.test.ts +137 -16
- package/src/track.ts +17 -0
- package/src/version.ts +1 -1
package/dist/audio_source.cjs
CHANGED
|
@@ -26,13 +26,36 @@ var import_ffi_client = require("./ffi_client.cjs");
|
|
|
26
26
|
class AudioSource {
|
|
27
27
|
constructor(sampleRate, numChannels, queueSize = 1e3) {
|
|
28
28
|
/** @internal */
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
this.
|
|
29
|
+
this.promise = void 0;
|
|
30
|
+
/** @internal */
|
|
31
|
+
this.resolvePromise = void 0;
|
|
32
32
|
/** @internal */
|
|
33
33
|
this.timeout = void 0;
|
|
34
34
|
/** @internal */
|
|
35
35
|
this.closed = false;
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the pending waitForPlayout() promise (if any) and reset the queue
|
|
38
|
+
* bookkeeping. Mirrors python-sdks' AudioSource._release_waiter: the promise is
|
|
39
|
+
* discarded here and lazily re-created by the next captureFrame, so a later
|
|
40
|
+
* waitForPlayout() can never consume a stale resolution and report playout
|
|
41
|
+
* complete while audio is still queued.
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
this.releaseWaiter = () => {
|
|
45
|
+
var _a;
|
|
46
|
+
if (!this.promise) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
(_a = this.resolvePromise) == null ? void 0 : _a.call(this);
|
|
50
|
+
this.lastCapture = 0;
|
|
51
|
+
this.currentQueueSize = 0;
|
|
52
|
+
this.promise = void 0;
|
|
53
|
+
this.resolvePromise = void 0;
|
|
54
|
+
if (this.timeout) {
|
|
55
|
+
clearTimeout(this.timeout);
|
|
56
|
+
this.timeout = void 0;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
36
59
|
this.sampleRate = sampleRate;
|
|
37
60
|
this.numChannels = numChannels;
|
|
38
61
|
this.queueSize = queueSize;
|
|
@@ -69,22 +92,13 @@ class AudioSource {
|
|
|
69
92
|
value: req
|
|
70
93
|
}
|
|
71
94
|
});
|
|
72
|
-
this.
|
|
73
|
-
this.release();
|
|
74
|
-
}
|
|
75
|
-
/** @internal */
|
|
76
|
-
async newPromise() {
|
|
77
|
-
return new Promise((resolve) => {
|
|
78
|
-
this.release = resolve;
|
|
79
|
-
});
|
|
95
|
+
this.releaseWaiter();
|
|
80
96
|
}
|
|
81
97
|
async waitForPlayout() {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
this.timeout = void 0;
|
|
87
|
-
});
|
|
98
|
+
if (!this.promise) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
await this.promise;
|
|
88
102
|
}
|
|
89
103
|
async captureFrame(frame) {
|
|
90
104
|
if (this.closed) {
|
|
@@ -101,7 +115,12 @@ class AudioSource {
|
|
|
101
115
|
if (this.timeout) {
|
|
102
116
|
clearTimeout(this.timeout);
|
|
103
117
|
}
|
|
104
|
-
|
|
118
|
+
if (!this.promise) {
|
|
119
|
+
this.promise = new Promise((resolve) => {
|
|
120
|
+
this.resolvePromise = resolve;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
this.timeout = setTimeout(this.releaseWaiter, this.currentQueueSize);
|
|
105
124
|
const req = new import_rtc_ffi_bindings.CaptureAudioFrameRequest({
|
|
106
125
|
sourceHandle: this.ffiHandle.handle,
|
|
107
126
|
buffer: frame.protoInfo()
|
|
@@ -121,7 +140,7 @@ class AudioSource {
|
|
|
121
140
|
clearTimeout(this.timeout);
|
|
122
141
|
this.timeout = void 0;
|
|
123
142
|
}
|
|
124
|
-
this.
|
|
143
|
+
this.releaseWaiter();
|
|
125
144
|
this.ffiHandle.dispose();
|
|
126
145
|
this.closed = true;
|
|
127
146
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/audio_source.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type {\n AudioSourceInfo,\n CaptureAudioFrameCallback,\n CaptureAudioFrameResponse,\n ClearAudioBufferResponse,\n NewAudioSourceResponse,\n} from '@livekit/rtc-ffi-bindings';\nimport {\n AudioSourceType,\n CaptureAudioFrameRequest,\n ClearAudioBufferRequest,\n FfiHandle,\n NewAudioSourceRequest,\n} from '@livekit/rtc-ffi-bindings';\nimport type { AudioFrame } from './audio_frame.js';\nimport { FfiClient } from './ffi_client.js';\n\nexport class AudioSource {\n /** @internal */\n info: AudioSourceInfo;\n /** @internal */\n ffiHandle: FfiHandle;\n /** @internal */\n lastCapture: number;\n /** @internal */\n currentQueueSize: number;\n /** @internal */\n
|
|
1
|
+
{"version":3,"sources":["../src/audio_source.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type {\n AudioSourceInfo,\n CaptureAudioFrameCallback,\n CaptureAudioFrameResponse,\n ClearAudioBufferResponse,\n NewAudioSourceResponse,\n} from '@livekit/rtc-ffi-bindings';\nimport {\n AudioSourceType,\n CaptureAudioFrameRequest,\n ClearAudioBufferRequest,\n FfiHandle,\n NewAudioSourceRequest,\n} from '@livekit/rtc-ffi-bindings';\nimport type { AudioFrame } from './audio_frame.js';\nimport { FfiClient } from './ffi_client.js';\n\nexport class AudioSource {\n /** @internal */\n info: AudioSourceInfo;\n /** @internal */\n ffiHandle: FfiHandle;\n /** @internal */\n lastCapture: number;\n /** @internal */\n currentQueueSize: number;\n /** @internal */\n promise?: Promise<void> = undefined;\n /** @internal */\n resolvePromise?: () => void = undefined;\n /** @internal */\n timeout?: ReturnType<typeof setTimeout> = undefined;\n /** @internal */\n closed = false;\n\n sampleRate: number;\n numChannels: number;\n queueSize: number;\n\n constructor(sampleRate: number, numChannels: number, queueSize = 1000) {\n this.sampleRate = sampleRate;\n this.numChannels = numChannels;\n this.queueSize = queueSize;\n\n this.lastCapture = 0;\n this.currentQueueSize = 0;\n\n const req = new NewAudioSourceRequest({\n type: AudioSourceType.AUDIO_SOURCE_NATIVE,\n sampleRate: sampleRate,\n numChannels: numChannels,\n queueSizeMs: queueSize,\n });\n\n const res = FfiClient.instance.request<NewAudioSourceResponse>({\n message: {\n case: 'newAudioSource',\n value: req,\n },\n });\n\n this.info = res.source!.info!;\n this.ffiHandle = new FfiHandle(res.source!.handle!.id!);\n }\n\n get queuedDuration(): number {\n return Math.max(\n this.currentQueueSize - Number(process.hrtime.bigint() / BigInt(1000000)) + this.lastCapture,\n 0,\n );\n }\n\n clearQueue() {\n const req = new ClearAudioBufferRequest({\n sourceHandle: this.ffiHandle.handle,\n });\n\n FfiClient.instance.request<ClearAudioBufferResponse>({\n message: {\n case: 'clearAudioBuffer',\n value: req,\n },\n });\n\n this.releaseWaiter();\n }\n\n /**\n * Resolve the pending waitForPlayout() promise (if any) and reset the queue\n * bookkeeping. Mirrors python-sdks' AudioSource._release_waiter: the promise is\n * discarded here and lazily re-created by the next captureFrame, so a later\n * waitForPlayout() can never consume a stale resolution and report playout\n * complete while audio is still queued.\n * @internal\n */\n releaseWaiter = () => {\n if (!this.promise) {\n return;\n }\n\n this.resolvePromise?.();\n this.lastCapture = 0;\n this.currentQueueSize = 0;\n this.promise = undefined;\n this.resolvePromise = undefined;\n // cancel the drain timer (e.g. when released early by clearQueue), otherwise\n // it would fire later and release the waiter of a subsequent segment\n if (this.timeout) {\n clearTimeout(this.timeout);\n this.timeout = undefined;\n }\n };\n\n async waitForPlayout() {\n if (!this.promise) {\n return;\n }\n\n await this.promise;\n }\n\n async captureFrame(frame: AudioFrame) {\n if (this.closed) {\n throw new Error('AudioSource is closed');\n }\n\n if (frame.samplesPerChannel === 0) {\n return;\n }\n\n const now = Number(process.hrtime.bigint() / BigInt(1000000));\n const elapsed = this.lastCapture === 0 ? 0 : now - this.lastCapture;\n const frameDurationMs = (frame.samplesPerChannel / frame.sampleRate) * 1000;\n this.currentQueueSize = Math.max(this.currentQueueSize - elapsed, 0) + frameDurationMs;\n\n this.lastCapture = now;\n\n if (this.timeout) {\n clearTimeout(this.timeout);\n }\n\n if (!this.promise) {\n this.promise = new Promise<void>((resolve) => {\n this.resolvePromise = resolve;\n });\n }\n\n this.timeout = setTimeout(this.releaseWaiter, this.currentQueueSize);\n\n const req = new CaptureAudioFrameRequest({\n sourceHandle: this.ffiHandle.handle,\n buffer: frame.protoInfo(),\n });\n\n const res = FfiClient.instance.request<CaptureAudioFrameResponse>({\n message: { case: 'captureAudioFrame', value: req },\n });\n\n const cb = await FfiClient.instance.waitFor<CaptureAudioFrameCallback>((ev) => {\n return ev.message.case == 'captureAudioFrame' && ev.message.value.asyncId == res.asyncId;\n });\n\n if (cb.error) {\n throw new Error(cb.error);\n }\n }\n\n async close() {\n // Clear any pending playout timeout so its callback doesn't fire after\n // the handle is disposed, which would reference freed native state.\n if (this.timeout) {\n clearTimeout(this.timeout);\n this.timeout = undefined;\n }\n // Resolve any pending waitForPlayout() promise so callers don't hang.\n this.releaseWaiter();\n this.ffiHandle.dispose();\n this.closed = true;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/audio_source.d.cts
CHANGED
|
@@ -11,8 +11,9 @@ declare class AudioSource {
|
|
|
11
11
|
/** @internal */
|
|
12
12
|
currentQueueSize: number;
|
|
13
13
|
/** @internal */
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
promise?: Promise<void>;
|
|
15
|
+
/** @internal */
|
|
16
|
+
resolvePromise?: () => void;
|
|
16
17
|
/** @internal */
|
|
17
18
|
timeout?: ReturnType<typeof setTimeout>;
|
|
18
19
|
/** @internal */
|
|
@@ -23,8 +24,15 @@ declare class AudioSource {
|
|
|
23
24
|
constructor(sampleRate: number, numChannels: number, queueSize?: number);
|
|
24
25
|
get queuedDuration(): number;
|
|
25
26
|
clearQueue(): void;
|
|
26
|
-
/**
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the pending waitForPlayout() promise (if any) and reset the queue
|
|
29
|
+
* bookkeeping. Mirrors python-sdks' AudioSource._release_waiter: the promise is
|
|
30
|
+
* discarded here and lazily re-created by the next captureFrame, so a later
|
|
31
|
+
* waitForPlayout() can never consume a stale resolution and report playout
|
|
32
|
+
* complete while audio is still queued.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
releaseWaiter: () => void;
|
|
28
36
|
waitForPlayout(): Promise<void>;
|
|
29
37
|
captureFrame(frame: AudioFrame): Promise<void>;
|
|
30
38
|
close(): Promise<void>;
|
package/dist/audio_source.d.ts
CHANGED
|
@@ -11,8 +11,9 @@ declare class AudioSource {
|
|
|
11
11
|
/** @internal */
|
|
12
12
|
currentQueueSize: number;
|
|
13
13
|
/** @internal */
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
promise?: Promise<void>;
|
|
15
|
+
/** @internal */
|
|
16
|
+
resolvePromise?: () => void;
|
|
16
17
|
/** @internal */
|
|
17
18
|
timeout?: ReturnType<typeof setTimeout>;
|
|
18
19
|
/** @internal */
|
|
@@ -23,8 +24,15 @@ declare class AudioSource {
|
|
|
23
24
|
constructor(sampleRate: number, numChannels: number, queueSize?: number);
|
|
24
25
|
get queuedDuration(): number;
|
|
25
26
|
clearQueue(): void;
|
|
26
|
-
/**
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the pending waitForPlayout() promise (if any) and reset the queue
|
|
29
|
+
* bookkeeping. Mirrors python-sdks' AudioSource._release_waiter: the promise is
|
|
30
|
+
* discarded here and lazily re-created by the next captureFrame, so a later
|
|
31
|
+
* waitForPlayout() can never consume a stale resolution and report playout
|
|
32
|
+
* complete while audio is still queued.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
releaseWaiter: () => void;
|
|
28
36
|
waitForPlayout(): Promise<void>;
|
|
29
37
|
captureFrame(frame: AudioFrame): Promise<void>;
|
|
30
38
|
close(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audio_source.d.ts","sourceRoot":"","sources":["../src/audio_source.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EAKhB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAIL,SAAS,EAEV,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,qBAAa,WAAW;IACtB,gBAAgB;IAChB,IAAI,EAAE,eAAe,CAAC;IACtB,gBAAgB;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,gBAAgB;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB;IAChB,OAAO,
|
|
1
|
+
{"version":3,"file":"audio_source.d.ts","sourceRoot":"","sources":["../src/audio_source.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EAKhB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAIL,SAAS,EAEV,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,qBAAa,WAAW;IACtB,gBAAgB;IAChB,IAAI,EAAE,eAAe,CAAC;IACtB,gBAAgB;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,gBAAgB;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAa;IACpC,gBAAgB;IAChB,cAAc,CAAC,EAAE,MAAM,IAAI,CAAa;IACxC,gBAAgB;IAChB,OAAO,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAa;IACpD,gBAAgB;IAChB,MAAM,UAAS;IAEf,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;gBAEN,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,SAAO;IA0BrE,IAAI,cAAc,IAAI,MAAM,CAK3B;IAED,UAAU;IAeV;;;;;;;OAOG;IACH,aAAa,aAgBX;IAEI,cAAc;IAQd,YAAY,CAAC,KAAK,EAAE,UAAU;IA8C9B,KAAK;CAYZ"}
|
package/dist/audio_source.js
CHANGED
|
@@ -9,13 +9,36 @@ import { FfiClient } from "./ffi_client.js";
|
|
|
9
9
|
class AudioSource {
|
|
10
10
|
constructor(sampleRate, numChannels, queueSize = 1e3) {
|
|
11
11
|
/** @internal */
|
|
12
|
-
this.
|
|
13
|
-
|
|
14
|
-
this.
|
|
12
|
+
this.promise = void 0;
|
|
13
|
+
/** @internal */
|
|
14
|
+
this.resolvePromise = void 0;
|
|
15
15
|
/** @internal */
|
|
16
16
|
this.timeout = void 0;
|
|
17
17
|
/** @internal */
|
|
18
18
|
this.closed = false;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the pending waitForPlayout() promise (if any) and reset the queue
|
|
21
|
+
* bookkeeping. Mirrors python-sdks' AudioSource._release_waiter: the promise is
|
|
22
|
+
* discarded here and lazily re-created by the next captureFrame, so a later
|
|
23
|
+
* waitForPlayout() can never consume a stale resolution and report playout
|
|
24
|
+
* complete while audio is still queued.
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
this.releaseWaiter = () => {
|
|
28
|
+
var _a;
|
|
29
|
+
if (!this.promise) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
(_a = this.resolvePromise) == null ? void 0 : _a.call(this);
|
|
33
|
+
this.lastCapture = 0;
|
|
34
|
+
this.currentQueueSize = 0;
|
|
35
|
+
this.promise = void 0;
|
|
36
|
+
this.resolvePromise = void 0;
|
|
37
|
+
if (this.timeout) {
|
|
38
|
+
clearTimeout(this.timeout);
|
|
39
|
+
this.timeout = void 0;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
19
42
|
this.sampleRate = sampleRate;
|
|
20
43
|
this.numChannels = numChannels;
|
|
21
44
|
this.queueSize = queueSize;
|
|
@@ -52,22 +75,13 @@ class AudioSource {
|
|
|
52
75
|
value: req
|
|
53
76
|
}
|
|
54
77
|
});
|
|
55
|
-
this.
|
|
56
|
-
this.release();
|
|
57
|
-
}
|
|
58
|
-
/** @internal */
|
|
59
|
-
async newPromise() {
|
|
60
|
-
return new Promise((resolve) => {
|
|
61
|
-
this.release = resolve;
|
|
62
|
-
});
|
|
78
|
+
this.releaseWaiter();
|
|
63
79
|
}
|
|
64
80
|
async waitForPlayout() {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this.timeout = void 0;
|
|
70
|
-
});
|
|
81
|
+
if (!this.promise) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
await this.promise;
|
|
71
85
|
}
|
|
72
86
|
async captureFrame(frame) {
|
|
73
87
|
if (this.closed) {
|
|
@@ -84,7 +98,12 @@ class AudioSource {
|
|
|
84
98
|
if (this.timeout) {
|
|
85
99
|
clearTimeout(this.timeout);
|
|
86
100
|
}
|
|
87
|
-
|
|
101
|
+
if (!this.promise) {
|
|
102
|
+
this.promise = new Promise((resolve) => {
|
|
103
|
+
this.resolvePromise = resolve;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
this.timeout = setTimeout(this.releaseWaiter, this.currentQueueSize);
|
|
88
107
|
const req = new CaptureAudioFrameRequest({
|
|
89
108
|
sourceHandle: this.ffiHandle.handle,
|
|
90
109
|
buffer: frame.protoInfo()
|
|
@@ -104,7 +123,7 @@ class AudioSource {
|
|
|
104
123
|
clearTimeout(this.timeout);
|
|
105
124
|
this.timeout = void 0;
|
|
106
125
|
}
|
|
107
|
-
this.
|
|
126
|
+
this.releaseWaiter();
|
|
108
127
|
this.ffiHandle.dispose();
|
|
109
128
|
this.closed = true;
|
|
110
129
|
}
|
package/dist/audio_source.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/audio_source.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type {\n AudioSourceInfo,\n CaptureAudioFrameCallback,\n CaptureAudioFrameResponse,\n ClearAudioBufferResponse,\n NewAudioSourceResponse,\n} from '@livekit/rtc-ffi-bindings';\nimport {\n AudioSourceType,\n CaptureAudioFrameRequest,\n ClearAudioBufferRequest,\n FfiHandle,\n NewAudioSourceRequest,\n} from '@livekit/rtc-ffi-bindings';\nimport type { AudioFrame } from './audio_frame.js';\nimport { FfiClient } from './ffi_client.js';\n\nexport class AudioSource {\n /** @internal */\n info: AudioSourceInfo;\n /** @internal */\n ffiHandle: FfiHandle;\n /** @internal */\n lastCapture: number;\n /** @internal */\n currentQueueSize: number;\n /** @internal */\n
|
|
1
|
+
{"version":3,"sources":["../src/audio_source.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type {\n AudioSourceInfo,\n CaptureAudioFrameCallback,\n CaptureAudioFrameResponse,\n ClearAudioBufferResponse,\n NewAudioSourceResponse,\n} from '@livekit/rtc-ffi-bindings';\nimport {\n AudioSourceType,\n CaptureAudioFrameRequest,\n ClearAudioBufferRequest,\n FfiHandle,\n NewAudioSourceRequest,\n} from '@livekit/rtc-ffi-bindings';\nimport type { AudioFrame } from './audio_frame.js';\nimport { FfiClient } from './ffi_client.js';\n\nexport class AudioSource {\n /** @internal */\n info: AudioSourceInfo;\n /** @internal */\n ffiHandle: FfiHandle;\n /** @internal */\n lastCapture: number;\n /** @internal */\n currentQueueSize: number;\n /** @internal */\n promise?: Promise<void> = undefined;\n /** @internal */\n resolvePromise?: () => void = undefined;\n /** @internal */\n timeout?: ReturnType<typeof setTimeout> = undefined;\n /** @internal */\n closed = false;\n\n sampleRate: number;\n numChannels: number;\n queueSize: number;\n\n constructor(sampleRate: number, numChannels: number, queueSize = 1000) {\n this.sampleRate = sampleRate;\n this.numChannels = numChannels;\n this.queueSize = queueSize;\n\n this.lastCapture = 0;\n this.currentQueueSize = 0;\n\n const req = new NewAudioSourceRequest({\n type: AudioSourceType.AUDIO_SOURCE_NATIVE,\n sampleRate: sampleRate,\n numChannels: numChannels,\n queueSizeMs: queueSize,\n });\n\n const res = FfiClient.instance.request<NewAudioSourceResponse>({\n message: {\n case: 'newAudioSource',\n value: req,\n },\n });\n\n this.info = res.source!.info!;\n this.ffiHandle = new FfiHandle(res.source!.handle!.id!);\n }\n\n get queuedDuration(): number {\n return Math.max(\n this.currentQueueSize - Number(process.hrtime.bigint() / BigInt(1000000)) + this.lastCapture,\n 0,\n );\n }\n\n clearQueue() {\n const req = new ClearAudioBufferRequest({\n sourceHandle: this.ffiHandle.handle,\n });\n\n FfiClient.instance.request<ClearAudioBufferResponse>({\n message: {\n case: 'clearAudioBuffer',\n value: req,\n },\n });\n\n this.releaseWaiter();\n }\n\n /**\n * Resolve the pending waitForPlayout() promise (if any) and reset the queue\n * bookkeeping. Mirrors python-sdks' AudioSource._release_waiter: the promise is\n * discarded here and lazily re-created by the next captureFrame, so a later\n * waitForPlayout() can never consume a stale resolution and report playout\n * complete while audio is still queued.\n * @internal\n */\n releaseWaiter = () => {\n if (!this.promise) {\n return;\n }\n\n this.resolvePromise?.();\n this.lastCapture = 0;\n this.currentQueueSize = 0;\n this.promise = undefined;\n this.resolvePromise = undefined;\n // cancel the drain timer (e.g. when released early by clearQueue), otherwise\n // it would fire later and release the waiter of a subsequent segment\n if (this.timeout) {\n clearTimeout(this.timeout);\n this.timeout = undefined;\n }\n };\n\n async waitForPlayout() {\n if (!this.promise) {\n return;\n }\n\n await this.promise;\n }\n\n async captureFrame(frame: AudioFrame) {\n if (this.closed) {\n throw new Error('AudioSource is closed');\n }\n\n if (frame.samplesPerChannel === 0) {\n return;\n }\n\n const now = Number(process.hrtime.bigint() / BigInt(1000000));\n const elapsed = this.lastCapture === 0 ? 0 : now - this.lastCapture;\n const frameDurationMs = (frame.samplesPerChannel / frame.sampleRate) * 1000;\n this.currentQueueSize = Math.max(this.currentQueueSize - elapsed, 0) + frameDurationMs;\n\n this.lastCapture = now;\n\n if (this.timeout) {\n clearTimeout(this.timeout);\n }\n\n if (!this.promise) {\n this.promise = new Promise<void>((resolve) => {\n this.resolvePromise = resolve;\n });\n }\n\n this.timeout = setTimeout(this.releaseWaiter, this.currentQueueSize);\n\n const req = new CaptureAudioFrameRequest({\n sourceHandle: this.ffiHandle.handle,\n buffer: frame.protoInfo(),\n });\n\n const res = FfiClient.instance.request<CaptureAudioFrameResponse>({\n message: { case: 'captureAudioFrame', value: req },\n });\n\n const cb = await FfiClient.instance.waitFor<CaptureAudioFrameCallback>((ev) => {\n return ev.message.case == 'captureAudioFrame' && ev.message.value.asyncId == res.asyncId;\n });\n\n if (cb.error) {\n throw new Error(cb.error);\n }\n }\n\n async close() {\n // Clear any pending playout timeout so its callback doesn't fire after\n // the handle is disposed, which would reference freed native state.\n if (this.timeout) {\n clearTimeout(this.timeout);\n this.timeout = undefined;\n }\n // Resolve any pending waitForPlayout() promise so callers don't hang.\n this.releaseWaiter();\n this.ffiHandle.dispose();\n this.closed = true;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -420,6 +420,17 @@ declare abstract class Track {
|
|
|
420
420
|
registerAudioStream(stream: AudioStreamSource): void;
|
|
421
421
|
/** @internal */
|
|
422
422
|
unregisterAudioStream(stream: AudioStreamSource): void;
|
|
423
|
+
/**
|
|
424
|
+
* End every {@link AudioStream} attached to this track.
|
|
425
|
+
*
|
|
426
|
+
* @remarks
|
|
427
|
+
* The FFI keeps feeding an audio stream until the stream's own handle is
|
|
428
|
+
* dropped, so streams on a track that is no longer subscribed would otherwise
|
|
429
|
+
* go on delivering audio for the lifetime of the process.
|
|
430
|
+
*
|
|
431
|
+
* @internal
|
|
432
|
+
*/
|
|
433
|
+
closeAudioStreams(): void;
|
|
423
434
|
private iterateStreams;
|
|
424
435
|
private pushProcessorMetadataToStream;
|
|
425
436
|
get sid(): string | undefined;
|
|
@@ -486,6 +497,20 @@ declare class AudioStreamSource implements UnderlyingSource<AudioFrame> {
|
|
|
486
497
|
get processor(): FrameProcessor<AudioFrame> | null;
|
|
487
498
|
private onEvent;
|
|
488
499
|
start(controller: ReadableStreamDefaultController<AudioFrame>): void;
|
|
500
|
+
/**
|
|
501
|
+
* Detach from the FFI and release resources: on `eos`, on `cancel()`, or
|
|
502
|
+
* because the track went away (e.g. it was unsubscribed — which never
|
|
503
|
+
* produces an `eos`, so without this the stream would keep delivering
|
|
504
|
+
* frames). Already-buffered frames stay readable and the consumer sees `done`
|
|
505
|
+
* after draining them.
|
|
506
|
+
*
|
|
507
|
+
* @remarks
|
|
508
|
+
* Idempotent, so `eos` arriving after a `cancel()` (or vice versa) doesn't
|
|
509
|
+
* double-dispose the handle while buffered frames are still queued.
|
|
510
|
+
*
|
|
511
|
+
* @internal
|
|
512
|
+
*/
|
|
513
|
+
teardown(): void;
|
|
489
514
|
cancel(): void;
|
|
490
515
|
}
|
|
491
516
|
declare class AudioStream extends ReadableStream<AudioFrame> {
|
|
@@ -420,6 +420,17 @@ declare abstract class Track {
|
|
|
420
420
|
registerAudioStream(stream: AudioStreamSource): void;
|
|
421
421
|
/** @internal */
|
|
422
422
|
unregisterAudioStream(stream: AudioStreamSource): void;
|
|
423
|
+
/**
|
|
424
|
+
* End every {@link AudioStream} attached to this track.
|
|
425
|
+
*
|
|
426
|
+
* @remarks
|
|
427
|
+
* The FFI keeps feeding an audio stream until the stream's own handle is
|
|
428
|
+
* dropped, so streams on a track that is no longer subscribed would otherwise
|
|
429
|
+
* go on delivering audio for the lifetime of the process.
|
|
430
|
+
*
|
|
431
|
+
* @internal
|
|
432
|
+
*/
|
|
433
|
+
closeAudioStreams(): void;
|
|
423
434
|
private iterateStreams;
|
|
424
435
|
private pushProcessorMetadataToStream;
|
|
425
436
|
get sid(): string | undefined;
|
|
@@ -486,6 +497,20 @@ declare class AudioStreamSource implements UnderlyingSource<AudioFrame> {
|
|
|
486
497
|
get processor(): FrameProcessor<AudioFrame> | null;
|
|
487
498
|
private onEvent;
|
|
488
499
|
start(controller: ReadableStreamDefaultController<AudioFrame>): void;
|
|
500
|
+
/**
|
|
501
|
+
* Detach from the FFI and release resources: on `eos`, on `cancel()`, or
|
|
502
|
+
* because the track went away (e.g. it was unsubscribed — which never
|
|
503
|
+
* produces an `eos`, so without this the stream would keep delivering
|
|
504
|
+
* frames). Already-buffered frames stay readable and the consumer sees `done`
|
|
505
|
+
* after draining them.
|
|
506
|
+
*
|
|
507
|
+
* @remarks
|
|
508
|
+
* Idempotent, so `eos` arriving after a `cancel()` (or vice versa) doesn't
|
|
509
|
+
* double-dispose the handle while buffered frames are still queued.
|
|
510
|
+
*
|
|
511
|
+
* @internal
|
|
512
|
+
*/
|
|
513
|
+
teardown(): void;
|
|
489
514
|
cancel(): void;
|
|
490
515
|
}
|
|
491
516
|
declare class AudioStream extends ReadableStream<AudioFrame> {
|
package/dist/audio_stream.cjs
CHANGED
|
@@ -53,16 +53,7 @@ class AudioStreamSource {
|
|
|
53
53
|
this.controller.enqueue(frame);
|
|
54
54
|
break;
|
|
55
55
|
case "eos":
|
|
56
|
-
|
|
57
|
-
this.controller.close();
|
|
58
|
-
if (!this.disposed) {
|
|
59
|
-
this.disposed = true;
|
|
60
|
-
this.track.unregisterAudioStream(this);
|
|
61
|
-
this.ffiHandle.dispose();
|
|
62
|
-
if (this.frameProcessor && this.autoCloseProcessor) {
|
|
63
|
-
this.frameProcessor.close();
|
|
64
|
-
}
|
|
65
|
-
}
|
|
56
|
+
this.teardown();
|
|
66
57
|
break;
|
|
67
58
|
}
|
|
68
59
|
};
|
|
@@ -109,16 +100,38 @@ class AudioStreamSource {
|
|
|
109
100
|
start(controller) {
|
|
110
101
|
this.controller = controller;
|
|
111
102
|
}
|
|
112
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Detach from the FFI and release resources: on `eos`, on `cancel()`, or
|
|
105
|
+
* because the track went away (e.g. it was unsubscribed — which never
|
|
106
|
+
* produces an `eos`, so without this the stream would keep delivering
|
|
107
|
+
* frames). Already-buffered frames stay readable and the consumer sees `done`
|
|
108
|
+
* after draining them.
|
|
109
|
+
*
|
|
110
|
+
* @remarks
|
|
111
|
+
* Idempotent, so `eos` arriving after a `cancel()` (or vice versa) doesn't
|
|
112
|
+
* double-dispose the handle while buffered frames are still queued.
|
|
113
|
+
*
|
|
114
|
+
* @internal
|
|
115
|
+
*/
|
|
116
|
+
teardown() {
|
|
117
|
+
var _a;
|
|
113
118
|
import_ffi_client.FfiClient.instance.off(import_ffi_client.FfiClientEvent.FfiEvent, this.onEvent);
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
this.track.unregisterAudioStream(this);
|
|
117
|
-
this.ffiHandle.dispose();
|
|
118
|
-
if (this.frameProcessor && this.autoCloseProcessor) {
|
|
119
|
-
this.frameProcessor.close();
|
|
120
|
-
}
|
|
119
|
+
if (this.disposed) {
|
|
120
|
+
return;
|
|
121
121
|
}
|
|
122
|
+
this.disposed = true;
|
|
123
|
+
this.track.unregisterAudioStream(this);
|
|
124
|
+
this.ffiHandle.dispose();
|
|
125
|
+
if (this.frameProcessor && this.autoCloseProcessor) {
|
|
126
|
+
this.frameProcessor.close();
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
(_a = this.controller) == null ? void 0 : _a.close();
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
cancel() {
|
|
134
|
+
this.teardown();
|
|
122
135
|
}
|
|
123
136
|
}
|
|
124
137
|
class AudioStream extends ReadableStream {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/audio_stream.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { NewAudioStreamResponse } from '@livekit/rtc-ffi-bindings';\nimport { AudioStreamType, NewAudioStreamRequest } from '@livekit/rtc-ffi-bindings';\nimport type { UnderlyingSource } from 'node:stream/web';\nimport { AudioFrame } from './audio_frame.js';\nimport type { FfiEvent } from './ffi_client.js';\nimport { FfiClient, FfiClientEvent, FfiHandle } from './ffi_client.js';\nimport { type FrameProcessor, isFrameProcessor } from './frame_processor.js';\nimport { log } from './log.js';\nimport type { Track } from './track.js';\n\nexport interface AudioStreamOptions {\n noiseCancellation?: NoiseCancellationOptions | FrameProcessor<AudioFrame>;\n /**\n * When the audio stream closes, whether to run the {@link FrameProcessor}'s\n * `close()` method. If `false`, the processor is left open so it can be\n * reused with another {@link AudioStream}. Only relevant when\n * `noiseCancellation` is a {@link FrameProcessor}. Defaults to `true`.\n */\n autoCloseNoiseCancellation?: boolean;\n sampleRate?: number;\n numChannels?: number;\n frameSizeMs?: number;\n}\n\nexport interface NoiseCancellationOptions {\n moduleId: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n options: Record<string, any>;\n}\n\n/** @internal */\nexport class AudioStreamSource implements UnderlyingSource<AudioFrame> {\n private controller?: ReadableStreamDefaultController<AudioFrame>;\n private ffiHandle: FfiHandle;\n private disposed = false;\n private sampleRate: number;\n private numChannels: number;\n private legacyNcOptions?: NoiseCancellationOptions;\n private frameProcessor: FrameProcessor<AudioFrame> | null = null;\n private autoCloseProcessor = true;\n private frameSizeMs?: number;\n private track: Track;\n\n constructor(\n track: Track,\n sampleRateOrOptions?: number | AudioStreamOptions,\n numChannels?: number,\n ) {\n this.track = track;\n if (sampleRateOrOptions !== undefined && typeof sampleRateOrOptions !== 'number') {\n this.sampleRate = sampleRateOrOptions.sampleRate ?? 48000;\n this.numChannels = sampleRateOrOptions.numChannels ?? 1;\n if (isFrameProcessor(sampleRateOrOptions.noiseCancellation)) {\n this.frameProcessor = sampleRateOrOptions.noiseCancellation;\n this.autoCloseProcessor = sampleRateOrOptions.autoCloseNoiseCancellation ?? true;\n } else {\n this.legacyNcOptions = sampleRateOrOptions.noiseCancellation;\n }\n this.frameSizeMs = sampleRateOrOptions.frameSizeMs;\n } else {\n this.sampleRate = (sampleRateOrOptions as number) ?? 48000;\n this.numChannels = numChannels ?? 1;\n }\n\n const req = new NewAudioStreamRequest({\n type: AudioStreamType.AUDIO_STREAM_NATIVE,\n trackHandle: track.ffi_handle.handle,\n sampleRate: this.sampleRate,\n numChannels: this.numChannels,\n frameSizeMs: this.frameSizeMs,\n ...(this.legacyNcOptions\n ? {\n audioFilterModuleId: this.legacyNcOptions.moduleId,\n audioFilterOptions: JSON.stringify(this.legacyNcOptions.options),\n }\n : {}),\n });\n\n const res = FfiClient.instance.request<NewAudioStreamResponse>({\n message: {\n case: 'newAudioStream',\n value: req,\n },\n });\n\n this.ffiHandle = new FfiHandle(res.stream!.handle!.id!);\n\n FfiClient.instance.on(FfiClientEvent.FfiEvent, this.onEvent);\n track.registerAudioStream(this);\n }\n\n /** @internal */\n get processor(): FrameProcessor<AudioFrame> | null {\n return this.frameProcessor;\n }\n\n private onEvent = (ev: FfiEvent) => {\n if (!this.controller) {\n throw new Error('Stream controller not initialized');\n }\n\n if (\n ev.message.case != 'audioStreamEvent' ||\n ev.message.value.streamHandle != this.ffiHandle.handle\n ) {\n return;\n }\n\n const streamEvent = ev.message.value.message;\n switch (streamEvent.case) {\n case 'frameReceived':\n let frame = AudioFrame.fromOwnedInfo(streamEvent.value.frame!);\n if (this.frameProcessor && this.frameProcessor.isEnabled()) {\n try {\n frame = this.frameProcessor.process(frame);\n } catch (err: unknown) {\n log.warn(`Frame processing failed, passing through original frame: ${err}`);\n }\n }\n this.controller.enqueue(frame);\n break;\n case 'eos':\n
|
|
1
|
+
{"version":3,"sources":["../src/audio_stream.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { NewAudioStreamResponse } from '@livekit/rtc-ffi-bindings';\nimport { AudioStreamType, NewAudioStreamRequest } from '@livekit/rtc-ffi-bindings';\nimport type { UnderlyingSource } from 'node:stream/web';\nimport { AudioFrame } from './audio_frame.js';\nimport type { FfiEvent } from './ffi_client.js';\nimport { FfiClient, FfiClientEvent, FfiHandle } from './ffi_client.js';\nimport { type FrameProcessor, isFrameProcessor } from './frame_processor.js';\nimport { log } from './log.js';\nimport type { Track } from './track.js';\n\nexport interface AudioStreamOptions {\n noiseCancellation?: NoiseCancellationOptions | FrameProcessor<AudioFrame>;\n /**\n * When the audio stream closes, whether to run the {@link FrameProcessor}'s\n * `close()` method. If `false`, the processor is left open so it can be\n * reused with another {@link AudioStream}. Only relevant when\n * `noiseCancellation` is a {@link FrameProcessor}. Defaults to `true`.\n */\n autoCloseNoiseCancellation?: boolean;\n sampleRate?: number;\n numChannels?: number;\n frameSizeMs?: number;\n}\n\nexport interface NoiseCancellationOptions {\n moduleId: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n options: Record<string, any>;\n}\n\n/** @internal */\nexport class AudioStreamSource implements UnderlyingSource<AudioFrame> {\n private controller?: ReadableStreamDefaultController<AudioFrame>;\n private ffiHandle: FfiHandle;\n private disposed = false;\n private sampleRate: number;\n private numChannels: number;\n private legacyNcOptions?: NoiseCancellationOptions;\n private frameProcessor: FrameProcessor<AudioFrame> | null = null;\n private autoCloseProcessor = true;\n private frameSizeMs?: number;\n private track: Track;\n\n constructor(\n track: Track,\n sampleRateOrOptions?: number | AudioStreamOptions,\n numChannels?: number,\n ) {\n this.track = track;\n if (sampleRateOrOptions !== undefined && typeof sampleRateOrOptions !== 'number') {\n this.sampleRate = sampleRateOrOptions.sampleRate ?? 48000;\n this.numChannels = sampleRateOrOptions.numChannels ?? 1;\n if (isFrameProcessor(sampleRateOrOptions.noiseCancellation)) {\n this.frameProcessor = sampleRateOrOptions.noiseCancellation;\n this.autoCloseProcessor = sampleRateOrOptions.autoCloseNoiseCancellation ?? true;\n } else {\n this.legacyNcOptions = sampleRateOrOptions.noiseCancellation;\n }\n this.frameSizeMs = sampleRateOrOptions.frameSizeMs;\n } else {\n this.sampleRate = (sampleRateOrOptions as number) ?? 48000;\n this.numChannels = numChannels ?? 1;\n }\n\n const req = new NewAudioStreamRequest({\n type: AudioStreamType.AUDIO_STREAM_NATIVE,\n trackHandle: track.ffi_handle.handle,\n sampleRate: this.sampleRate,\n numChannels: this.numChannels,\n frameSizeMs: this.frameSizeMs,\n ...(this.legacyNcOptions\n ? {\n audioFilterModuleId: this.legacyNcOptions.moduleId,\n audioFilterOptions: JSON.stringify(this.legacyNcOptions.options),\n }\n : {}),\n });\n\n const res = FfiClient.instance.request<NewAudioStreamResponse>({\n message: {\n case: 'newAudioStream',\n value: req,\n },\n });\n\n this.ffiHandle = new FfiHandle(res.stream!.handle!.id!);\n\n FfiClient.instance.on(FfiClientEvent.FfiEvent, this.onEvent);\n track.registerAudioStream(this);\n }\n\n /** @internal */\n get processor(): FrameProcessor<AudioFrame> | null {\n return this.frameProcessor;\n }\n\n private onEvent = (ev: FfiEvent) => {\n if (!this.controller) {\n throw new Error('Stream controller not initialized');\n }\n\n if (\n ev.message.case != 'audioStreamEvent' ||\n ev.message.value.streamHandle != this.ffiHandle.handle\n ) {\n return;\n }\n\n const streamEvent = ev.message.value.message;\n switch (streamEvent.case) {\n case 'frameReceived':\n let frame = AudioFrame.fromOwnedInfo(streamEvent.value.frame!);\n if (this.frameProcessor && this.frameProcessor.isEnabled()) {\n try {\n frame = this.frameProcessor.process(frame);\n } catch (err: unknown) {\n log.warn(`Frame processing failed, passing through original frame: ${err}`);\n }\n }\n this.controller.enqueue(frame);\n break;\n case 'eos':\n // Disposes the native handle so the FD is released on stream end, not\n // just when cancel() is called explicitly by the consumer.\n this.teardown();\n break;\n }\n };\n\n start(controller: ReadableStreamDefaultController<AudioFrame>) {\n this.controller = controller;\n }\n\n /**\n * Detach from the FFI and release resources: on `eos`, on `cancel()`, or\n * because the track went away (e.g. it was unsubscribed — which never\n * produces an `eos`, so without this the stream would keep delivering\n * frames). Already-buffered frames stay readable and the consumer sees `done`\n * after draining them.\n *\n * @remarks\n * Idempotent, so `eos` arriving after a `cancel()` (or vice versa) doesn't\n * double-dispose the handle while buffered frames are still queued.\n *\n * @internal\n */\n teardown() {\n FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);\n if (this.disposed) {\n return;\n }\n this.disposed = true;\n this.track.unregisterAudioStream(this);\n this.ffiHandle.dispose();\n // Close the frame processor on every teardown path so resources are\n // released regardless of how the stream ended.\n if (this.frameProcessor && this.autoCloseProcessor) {\n this.frameProcessor.close();\n }\n try {\n this.controller?.close();\n } catch {\n // Already closed — e.g. cancel(), where the consumer has torn the\n // ReadableStream down before the underlying source is notified.\n }\n }\n\n cancel() {\n this.teardown();\n }\n}\n\nexport class AudioStream extends ReadableStream<AudioFrame> {\n constructor(track: Track);\n constructor(track: Track, sampleRate: number);\n constructor(track: Track, sampleRate: number, numChannels: number);\n constructor(track: Track, options: AudioStreamOptions);\n constructor(\n track: Track,\n sampleRateOrOptions?: number | AudioStreamOptions,\n numChannels?: number,\n ) {\n super(new AudioStreamSource(track, sampleRateOrOptions, numChannels));\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/audio_stream.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import 'node:stream/web';
|
|
2
2
|
import './audio_frame.cjs';
|
|
3
3
|
import './frame_processor.cjs';
|
|
4
|
-
export { A as AudioStream, r as AudioStreamOptions, s as AudioStreamSource, N as NoiseCancellationOptions } from './audio_stream-
|
|
4
|
+
export { A as AudioStream, r as AudioStreamOptions, s as AudioStreamSource, N as NoiseCancellationOptions } from './audio_stream-4M8XlO4-.cjs';
|
|
5
5
|
import '@livekit/rtc-ffi-bindings';
|
|
6
6
|
import './video_frame.cjs';
|
|
7
7
|
import './audio_source.cjs';
|
package/dist/audio_stream.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import 'node:stream/web';
|
|
2
2
|
import './audio_frame.js';
|
|
3
3
|
import './frame_processor.js';
|
|
4
|
-
export { A as AudioStream, r as AudioStreamOptions, s as AudioStreamSource, N as NoiseCancellationOptions } from './audio_stream-
|
|
4
|
+
export { A as AudioStream, r as AudioStreamOptions, s as AudioStreamSource, N as NoiseCancellationOptions } from './audio_stream-CTyN1Ldy.js';
|
|
5
5
|
import '@livekit/rtc-ffi-bindings';
|
|
6
6
|
import './video_frame.js';
|
|
7
7
|
import './audio_source.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audio_stream.d.ts","sourceRoot":"","sources":["../src/audio_stream.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EAAE,KAAK,cAAc,EAAoB,MAAM,sBAAsB,CAAC;AAE7E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,CAAC,EAAE,wBAAwB,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IAC1E;;;;;OAKG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IAEjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B;AAED,gBAAgB;AAChB,qBAAa,iBAAkB,YAAW,gBAAgB,CAAC,UAAU,CAAC;IACpE,OAAO,CAAC,UAAU,CAAC,CAA8C;IACjE,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAC,CAA2B;IACnD,OAAO,CAAC,cAAc,CAA2C;IACjE,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,KAAK,CAAQ;gBAGnB,KAAK,EAAE,KAAK,EACZ,mBAAmB,CAAC,EAAE,MAAM,GAAG,kBAAkB,EACjD,WAAW,CAAC,EAAE,MAAM;IA6CtB,gBAAgB;IAChB,IAAI,SAAS,IAAI,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAEjD;IAED,OAAO,CAAC,OAAO,
|
|
1
|
+
{"version":3,"file":"audio_stream.d.ts","sourceRoot":"","sources":["../src/audio_stream.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EAAE,KAAK,cAAc,EAAoB,MAAM,sBAAsB,CAAC;AAE7E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,CAAC,EAAE,wBAAwB,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IAC1E;;;;;OAKG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IAEjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B;AAED,gBAAgB;AAChB,qBAAa,iBAAkB,YAAW,gBAAgB,CAAC,UAAU,CAAC;IACpE,OAAO,CAAC,UAAU,CAAC,CAA8C;IACjE,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAC,CAA2B;IACnD,OAAO,CAAC,cAAc,CAA2C;IACjE,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,KAAK,CAAQ;gBAGnB,KAAK,EAAE,KAAK,EACZ,mBAAmB,CAAC,EAAE,MAAM,GAAG,kBAAkB,EACjD,WAAW,CAAC,EAAE,MAAM;IA6CtB,gBAAgB;IAChB,IAAI,SAAS,IAAI,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAEjD;IAED,OAAO,CAAC,OAAO,CA+Bb;IAEF,KAAK,CAAC,UAAU,EAAE,+BAA+B,CAAC,UAAU,CAAC;IAI7D;;;;;;;;;;;;OAYG;IACH,QAAQ;IAqBR,MAAM;CAGP;AAED,qBAAa,WAAY,SAAQ,cAAc,CAAC,UAAU,CAAC;gBAC7C,KAAK,EAAE,KAAK;gBACZ,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM;gBAChC,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;gBACrD,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,kBAAkB;CAQtD"}
|
package/dist/audio_stream.js
CHANGED
|
@@ -29,16 +29,7 @@ class AudioStreamSource {
|
|
|
29
29
|
this.controller.enqueue(frame);
|
|
30
30
|
break;
|
|
31
31
|
case "eos":
|
|
32
|
-
|
|
33
|
-
this.controller.close();
|
|
34
|
-
if (!this.disposed) {
|
|
35
|
-
this.disposed = true;
|
|
36
|
-
this.track.unregisterAudioStream(this);
|
|
37
|
-
this.ffiHandle.dispose();
|
|
38
|
-
if (this.frameProcessor && this.autoCloseProcessor) {
|
|
39
|
-
this.frameProcessor.close();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
32
|
+
this.teardown();
|
|
42
33
|
break;
|
|
43
34
|
}
|
|
44
35
|
};
|
|
@@ -85,16 +76,38 @@ class AudioStreamSource {
|
|
|
85
76
|
start(controller) {
|
|
86
77
|
this.controller = controller;
|
|
87
78
|
}
|
|
88
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Detach from the FFI and release resources: on `eos`, on `cancel()`, or
|
|
81
|
+
* because the track went away (e.g. it was unsubscribed — which never
|
|
82
|
+
* produces an `eos`, so without this the stream would keep delivering
|
|
83
|
+
* frames). Already-buffered frames stay readable and the consumer sees `done`
|
|
84
|
+
* after draining them.
|
|
85
|
+
*
|
|
86
|
+
* @remarks
|
|
87
|
+
* Idempotent, so `eos` arriving after a `cancel()` (or vice versa) doesn't
|
|
88
|
+
* double-dispose the handle while buffered frames are still queued.
|
|
89
|
+
*
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
teardown() {
|
|
93
|
+
var _a;
|
|
89
94
|
FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
this.track.unregisterAudioStream(this);
|
|
93
|
-
this.ffiHandle.dispose();
|
|
94
|
-
if (this.frameProcessor && this.autoCloseProcessor) {
|
|
95
|
-
this.frameProcessor.close();
|
|
96
|
-
}
|
|
95
|
+
if (this.disposed) {
|
|
96
|
+
return;
|
|
97
97
|
}
|
|
98
|
+
this.disposed = true;
|
|
99
|
+
this.track.unregisterAudioStream(this);
|
|
100
|
+
this.ffiHandle.dispose();
|
|
101
|
+
if (this.frameProcessor && this.autoCloseProcessor) {
|
|
102
|
+
this.frameProcessor.close();
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
(_a = this.controller) == null ? void 0 : _a.close();
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
cancel() {
|
|
110
|
+
this.teardown();
|
|
98
111
|
}
|
|
99
112
|
}
|
|
100
113
|
class AudioStream extends ReadableStream {
|