@livekit/agents 1.0.3 → 1.0.4
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/index.cjs +2 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -3
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/tokenize/basic/hyphenator.cjs.map +1 -1
- package/dist/tokenize/basic/hyphenator.js.map +1 -1
- package/dist/utils.cjs +77 -0
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +21 -0
- package/dist/utils.d.ts +21 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +76 -1
- package/dist/utils.js.map +1 -1
- package/dist/voice/agent_activity.cjs +112 -71
- package/dist/voice/agent_activity.cjs.map +1 -1
- package/dist/voice/agent_activity.d.ts.map +1 -1
- package/dist/voice/agent_activity.js +112 -71
- package/dist/voice/agent_activity.js.map +1 -1
- package/dist/voice/avatar/datastream_io.cjs +204 -0
- package/dist/voice/avatar/datastream_io.cjs.map +1 -0
- package/dist/voice/avatar/datastream_io.d.cts +37 -0
- package/dist/voice/avatar/datastream_io.d.ts +37 -0
- package/dist/voice/avatar/datastream_io.d.ts.map +1 -0
- package/dist/voice/avatar/datastream_io.js +188 -0
- package/dist/voice/avatar/datastream_io.js.map +1 -0
- package/dist/{multimodal → voice/avatar}/index.cjs +4 -4
- package/dist/voice/avatar/index.cjs.map +1 -0
- package/dist/voice/avatar/index.d.cts +2 -0
- package/dist/voice/avatar/index.d.ts +2 -0
- package/dist/voice/avatar/index.d.ts.map +1 -0
- package/dist/voice/avatar/index.js +2 -0
- package/dist/voice/avatar/index.js.map +1 -0
- package/dist/voice/index.cjs +2 -0
- package/dist/voice/index.cjs.map +1 -1
- package/dist/voice/index.d.cts +1 -0
- package/dist/voice/index.d.ts +1 -0
- package/dist/voice/index.d.ts.map +1 -1
- package/dist/voice/index.js +1 -0
- package/dist/voice/index.js.map +1 -1
- package/dist/voice/io.cjs.map +1 -1
- package/dist/voice/io.d.cts +1 -1
- package/dist/voice/io.d.ts +1 -1
- package/dist/voice/io.d.ts.map +1 -1
- package/dist/voice/io.js.map +1 -1
- package/dist/voice/room_io/_input.cjs +2 -1
- package/dist/voice/room_io/_input.cjs.map +1 -1
- package/dist/voice/room_io/_input.d.ts.map +1 -1
- package/dist/voice/room_io/_input.js +2 -1
- package/dist/voice/room_io/_input.js.map +1 -1
- package/dist/voice/run_context.cjs +13 -0
- package/dist/voice/run_context.cjs.map +1 -1
- package/dist/voice/run_context.d.cts +10 -0
- package/dist/voice/run_context.d.ts +10 -0
- package/dist/voice/run_context.d.ts.map +1 -1
- package/dist/voice/run_context.js +13 -0
- package/dist/voice/run_context.js.map +1 -1
- package/dist/voice/speech_handle.cjs +152 -30
- package/dist/voice/speech_handle.cjs.map +1 -1
- package/dist/voice/speech_handle.d.cts +67 -16
- package/dist/voice/speech_handle.d.ts +67 -16
- package/dist/voice/speech_handle.d.ts.map +1 -1
- package/dist/voice/speech_handle.js +153 -31
- package/dist/voice/speech_handle.js.map +1 -1
- package/dist/worker.cjs +4 -1
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +4 -1
- package/dist/worker.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +2 -3
- package/src/tokenize/basic/hyphenator.ts +1 -1
- package/src/utils.ts +121 -1
- package/src/voice/agent_activity.ts +128 -78
- package/src/voice/avatar/datastream_io.ts +247 -0
- package/src/voice/avatar/index.ts +4 -0
- package/src/voice/index.ts +2 -0
- package/src/voice/io.ts +1 -1
- package/src/voice/room_io/_input.ts +8 -3
- package/src/voice/run_context.ts +16 -2
- package/src/voice/speech_handle.ts +183 -38
- package/src/worker.ts +5 -1
- package/dist/multimodal/agent_playout.cjs +0 -233
- package/dist/multimodal/agent_playout.cjs.map +0 -1
- package/dist/multimodal/agent_playout.d.cts +0 -34
- package/dist/multimodal/agent_playout.d.ts +0 -34
- package/dist/multimodal/agent_playout.d.ts.map +0 -1
- package/dist/multimodal/agent_playout.js +0 -207
- package/dist/multimodal/agent_playout.js.map +0 -1
- package/dist/multimodal/index.cjs.map +0 -1
- package/dist/multimodal/index.d.cts +0 -2
- package/dist/multimodal/index.d.ts +0 -2
- package/dist/multimodal/index.d.ts.map +0 -1
- package/dist/multimodal/index.js +0 -2
- package/dist/multimodal/index.js.map +0 -1
- package/src/multimodal/agent_playout.ts +0 -266
- package/src/multimodal/index.ts +0 -4
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var datastream_io_exports = {};
|
|
20
|
+
__export(datastream_io_exports, {
|
|
21
|
+
DataStreamAudioOutput: () => DataStreamAudioOutput
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(datastream_io_exports);
|
|
24
|
+
var import_mutex = require("@livekit/mutex");
|
|
25
|
+
var import_rtc_node = require("@livekit/rtc-node");
|
|
26
|
+
var import_log = require("../../log.cjs");
|
|
27
|
+
var import_utils = require("../../utils.cjs");
|
|
28
|
+
var import_io = require("../io.cjs");
|
|
29
|
+
const RPC_CLEAR_BUFFER = "lk.clear_buffer";
|
|
30
|
+
const RPC_PLAYBACK_FINISHED = "lk.playback_finished";
|
|
31
|
+
const AUDIO_STREAM_TOPIC = "lk.audio_stream";
|
|
32
|
+
class DataStreamAudioOutput extends import_io.AudioOutput {
|
|
33
|
+
static _playbackFinishedRpcRegistered = false;
|
|
34
|
+
static _playbackFinishedHandlers = {};
|
|
35
|
+
room;
|
|
36
|
+
destinationIdentity;
|
|
37
|
+
roomConnectedFuture;
|
|
38
|
+
waitRemoteTrack;
|
|
39
|
+
streamWriter;
|
|
40
|
+
pushedDuration = 0;
|
|
41
|
+
started = false;
|
|
42
|
+
lock = new import_mutex.Mutex();
|
|
43
|
+
startTask;
|
|
44
|
+
#logger = (0, import_log.log)();
|
|
45
|
+
constructor(opts) {
|
|
46
|
+
super(opts.sampleRate, void 0);
|
|
47
|
+
const { room, destinationIdentity, sampleRate, waitRemoteTrack } = opts;
|
|
48
|
+
this.room = room;
|
|
49
|
+
this.destinationIdentity = destinationIdentity;
|
|
50
|
+
this.sampleRate = sampleRate;
|
|
51
|
+
this.waitRemoteTrack = waitRemoteTrack;
|
|
52
|
+
const onRoomConnected = async () => {
|
|
53
|
+
if (this.startTask) return;
|
|
54
|
+
await this.roomConnectedFuture.await;
|
|
55
|
+
DataStreamAudioOutput.registerPlaybackFinishedRpc({
|
|
56
|
+
room,
|
|
57
|
+
callerIdentity: this.destinationIdentity,
|
|
58
|
+
handler: (data) => this.handlePlaybackFinished(data)
|
|
59
|
+
});
|
|
60
|
+
this.startTask = import_utils.Task.from(({ signal }) => this._start(signal));
|
|
61
|
+
};
|
|
62
|
+
this.roomConnectedFuture = new import_utils.Future();
|
|
63
|
+
this.room.on(import_rtc_node.RoomEvent.ConnectionStateChanged, (_) => {
|
|
64
|
+
if (room.isConnected && !this.roomConnectedFuture.done) {
|
|
65
|
+
this.roomConnectedFuture.resolve(void 0);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
if (this.room.isConnected) {
|
|
69
|
+
this.roomConnectedFuture.resolve(void 0);
|
|
70
|
+
}
|
|
71
|
+
onRoomConnected();
|
|
72
|
+
}
|
|
73
|
+
async _start(_abortSignal) {
|
|
74
|
+
const unlock = await this.lock.lock();
|
|
75
|
+
try {
|
|
76
|
+
if (this.started) return;
|
|
77
|
+
await this.roomConnectedFuture.await;
|
|
78
|
+
this.#logger.debug(
|
|
79
|
+
{
|
|
80
|
+
identity: this.destinationIdentity
|
|
81
|
+
},
|
|
82
|
+
"waiting for the remote participant"
|
|
83
|
+
);
|
|
84
|
+
await (0, import_utils.waitForParticipant)({
|
|
85
|
+
room: this.room,
|
|
86
|
+
identity: this.destinationIdentity
|
|
87
|
+
});
|
|
88
|
+
if (this.waitRemoteTrack) {
|
|
89
|
+
this.#logger.debug(
|
|
90
|
+
{
|
|
91
|
+
identity: this.destinationIdentity,
|
|
92
|
+
kind: this.waitRemoteTrack
|
|
93
|
+
},
|
|
94
|
+
"waiting for the remote track"
|
|
95
|
+
);
|
|
96
|
+
await (0, import_utils.waitForTrackPublication)({
|
|
97
|
+
room: this.room,
|
|
98
|
+
identity: this.destinationIdentity,
|
|
99
|
+
kind: this.waitRemoteTrack
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
this.#logger.debug(
|
|
103
|
+
{
|
|
104
|
+
identity: this.destinationIdentity
|
|
105
|
+
},
|
|
106
|
+
"remote participant ready"
|
|
107
|
+
);
|
|
108
|
+
this.started = true;
|
|
109
|
+
} finally {
|
|
110
|
+
unlock();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async captureFrame(frame) {
|
|
114
|
+
if (!this.startTask) {
|
|
115
|
+
this.startTask = import_utils.Task.from(({ signal }) => this._start(signal));
|
|
116
|
+
}
|
|
117
|
+
await this.startTask.result;
|
|
118
|
+
await super.captureFrame(frame);
|
|
119
|
+
if (!this.streamWriter) {
|
|
120
|
+
this.streamWriter = await this.room.localParticipant.streamBytes({
|
|
121
|
+
name: (0, import_utils.shortuuid)("AUDIO_"),
|
|
122
|
+
topic: AUDIO_STREAM_TOPIC,
|
|
123
|
+
destinationIdentities: [this.destinationIdentity],
|
|
124
|
+
attributes: {
|
|
125
|
+
sample_rate: frame.sampleRate.toString(),
|
|
126
|
+
num_channels: frame.channels.toString()
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
this.pushedDuration = 0;
|
|
130
|
+
}
|
|
131
|
+
await this.streamWriter.write(new Uint8Array(frame.data.buffer));
|
|
132
|
+
this.pushedDuration += frame.samplesPerChannel / frame.sampleRate;
|
|
133
|
+
}
|
|
134
|
+
flush() {
|
|
135
|
+
super.flush();
|
|
136
|
+
if (this.streamWriter === void 0 || !this.started) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
this.streamWriter.close().finally(() => {
|
|
140
|
+
this.streamWriter = void 0;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
clearBuffer() {
|
|
144
|
+
if (!this.started) return;
|
|
145
|
+
this.room.localParticipant.performRpc({
|
|
146
|
+
destinationIdentity: this.destinationIdentity,
|
|
147
|
+
method: RPC_CLEAR_BUFFER,
|
|
148
|
+
payload: ""
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
handlePlaybackFinished(data) {
|
|
152
|
+
if (data.callerIdentity !== this.destinationIdentity) {
|
|
153
|
+
this.#logger.warn(
|
|
154
|
+
{
|
|
155
|
+
callerIdentity: data.callerIdentity,
|
|
156
|
+
destinationIdentity: this.destinationIdentity
|
|
157
|
+
},
|
|
158
|
+
"playback finished event received from unexpected participant"
|
|
159
|
+
);
|
|
160
|
+
return "reject";
|
|
161
|
+
}
|
|
162
|
+
this.#logger.info(
|
|
163
|
+
{
|
|
164
|
+
callerIdentity: data.callerIdentity
|
|
165
|
+
},
|
|
166
|
+
"playback finished event received"
|
|
167
|
+
);
|
|
168
|
+
const playbackFinishedEvent = JSON.parse(data.payload);
|
|
169
|
+
this.onPlaybackFinished(playbackFinishedEvent);
|
|
170
|
+
return "ok";
|
|
171
|
+
}
|
|
172
|
+
static registerPlaybackFinishedRpc({
|
|
173
|
+
room,
|
|
174
|
+
callerIdentity,
|
|
175
|
+
handler
|
|
176
|
+
}) {
|
|
177
|
+
var _a;
|
|
178
|
+
DataStreamAudioOutput._playbackFinishedHandlers[callerIdentity] = handler;
|
|
179
|
+
if (DataStreamAudioOutput._playbackFinishedRpcRegistered) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const rpcHandler = async (data) => {
|
|
183
|
+
const handler2 = DataStreamAudioOutput._playbackFinishedHandlers[data.callerIdentity];
|
|
184
|
+
if (!handler2) {
|
|
185
|
+
(0, import_log.log)().warn(
|
|
186
|
+
{
|
|
187
|
+
callerIdentity: data.callerIdentity,
|
|
188
|
+
expectedIdentities: Object.keys(DataStreamAudioOutput._playbackFinishedHandlers)
|
|
189
|
+
},
|
|
190
|
+
"playback finished event received from unexpected participant"
|
|
191
|
+
);
|
|
192
|
+
return "reject";
|
|
193
|
+
}
|
|
194
|
+
return handler2(data);
|
|
195
|
+
};
|
|
196
|
+
(_a = room.localParticipant) == null ? void 0 : _a.registerRpcMethod(RPC_PLAYBACK_FINISHED, rpcHandler);
|
|
197
|
+
DataStreamAudioOutput._playbackFinishedRpcRegistered = true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
201
|
+
0 && (module.exports = {
|
|
202
|
+
DataStreamAudioOutput
|
|
203
|
+
});
|
|
204
|
+
//# sourceMappingURL=datastream_io.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/voice/avatar/datastream_io.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { Mutex } from '@livekit/mutex';\nimport {\n type AudioFrame,\n type ByteStreamWriter,\n type Room,\n RoomEvent,\n type RpcInvocationData,\n type TrackKind,\n} from '@livekit/rtc-node';\nimport { log } from '../../log.js';\nimport {\n Future,\n Task,\n shortuuid,\n waitForParticipant,\n waitForTrackPublication,\n} from '../../utils.js';\nimport { AudioOutput, type PlaybackFinishedEvent } from '../io.js';\n\nconst RPC_CLEAR_BUFFER = 'lk.clear_buffer';\nconst RPC_PLAYBACK_FINISHED = 'lk.playback_finished';\nconst AUDIO_STREAM_TOPIC = 'lk.audio_stream';\n\nexport interface DataStreamAudioOutputOptions {\n room: Room;\n destinationIdentity: string;\n sampleRate?: number;\n waitRemoteTrack?: TrackKind;\n}\n\n/**\n * AudioOutput implementation that streams audio to a remote avatar worker using LiveKit DataStream.\n */\nexport class DataStreamAudioOutput extends AudioOutput {\n static _playbackFinishedRpcRegistered: boolean = false;\n static _playbackFinishedHandlers: Record<string, (data: RpcInvocationData) => string> = {};\n\n private room: Room;\n private destinationIdentity: string;\n private roomConnectedFuture: Future<void>;\n private waitRemoteTrack?: TrackKind;\n private streamWriter?: ByteStreamWriter;\n private pushedDuration: number = 0;\n private started: boolean = false;\n private lock = new Mutex();\n private startTask?: Task<void>;\n\n #logger = log();\n\n constructor(opts: DataStreamAudioOutputOptions) {\n super(opts.sampleRate, undefined);\n\n const { room, destinationIdentity, sampleRate, waitRemoteTrack } = opts;\n this.room = room;\n this.destinationIdentity = destinationIdentity;\n this.sampleRate = sampleRate;\n this.waitRemoteTrack = waitRemoteTrack;\n\n const onRoomConnected = async () => {\n if (this.startTask) return;\n\n await this.roomConnectedFuture.await;\n\n // register the rpc method right after the room is connected\n DataStreamAudioOutput.registerPlaybackFinishedRpc({\n room,\n callerIdentity: this.destinationIdentity,\n handler: (data) => this.handlePlaybackFinished(data),\n });\n\n this.startTask = Task.from(({ signal }) => this._start(signal));\n };\n\n this.roomConnectedFuture = new Future<void>();\n\n this.room.on(RoomEvent.ConnectionStateChanged, (_) => {\n if (room.isConnected && !this.roomConnectedFuture.done) {\n this.roomConnectedFuture.resolve(undefined);\n }\n });\n\n if (this.room.isConnected) {\n this.roomConnectedFuture.resolve(undefined);\n }\n\n onRoomConnected();\n }\n\n private async _start(_abortSignal: AbortSignal) {\n const unlock = await this.lock.lock();\n\n try {\n if (this.started) return;\n\n await this.roomConnectedFuture.await;\n\n this.#logger.debug(\n {\n identity: this.destinationIdentity,\n },\n 'waiting for the remote participant',\n );\n\n await waitForParticipant({\n room: this.room,\n identity: this.destinationIdentity,\n });\n\n if (this.waitRemoteTrack) {\n this.#logger.debug(\n {\n identity: this.destinationIdentity,\n kind: this.waitRemoteTrack,\n },\n 'waiting for the remote track',\n );\n\n await waitForTrackPublication({\n room: this.room,\n identity: this.destinationIdentity,\n kind: this.waitRemoteTrack,\n });\n }\n\n this.#logger.debug(\n {\n identity: this.destinationIdentity,\n },\n 'remote participant ready',\n );\n\n this.started = true;\n } finally {\n unlock();\n }\n }\n\n async captureFrame(frame: AudioFrame): Promise<void> {\n if (!this.startTask) {\n this.startTask = Task.from(({ signal }) => this._start(signal));\n }\n\n await this.startTask.result;\n await super.captureFrame(frame);\n\n if (!this.streamWriter) {\n this.streamWriter = await this.room.localParticipant!.streamBytes({\n name: shortuuid('AUDIO_'),\n topic: AUDIO_STREAM_TOPIC,\n destinationIdentities: [this.destinationIdentity],\n attributes: {\n sample_rate: frame.sampleRate.toString(),\n num_channels: frame.channels.toString(),\n },\n });\n this.pushedDuration = 0;\n }\n\n // frame.data is a Int16Array, write accepts a Uint8Array\n await this.streamWriter.write(new Uint8Array(frame.data.buffer));\n this.pushedDuration += frame.samplesPerChannel / frame.sampleRate;\n }\n\n flush(): void {\n super.flush();\n\n if (this.streamWriter === undefined || !this.started) {\n return;\n }\n\n this.streamWriter.close().finally(() => {\n this.streamWriter = undefined;\n });\n }\n\n clearBuffer(): void {\n if (!this.started) return;\n\n this.room.localParticipant!.performRpc({\n destinationIdentity: this.destinationIdentity,\n method: RPC_CLEAR_BUFFER,\n payload: '',\n });\n }\n\n private handlePlaybackFinished(data: RpcInvocationData): string {\n if (data.callerIdentity !== this.destinationIdentity) {\n this.#logger.warn(\n {\n callerIdentity: data.callerIdentity,\n destinationIdentity: this.destinationIdentity,\n },\n 'playback finished event received from unexpected participant',\n );\n return 'reject';\n }\n\n this.#logger.info(\n {\n callerIdentity: data.callerIdentity,\n },\n 'playback finished event received',\n );\n\n const playbackFinishedEvent = JSON.parse(data.payload) as PlaybackFinishedEvent;\n this.onPlaybackFinished(playbackFinishedEvent);\n return 'ok';\n }\n\n static registerPlaybackFinishedRpc({\n room,\n callerIdentity,\n handler,\n }: {\n room: Room;\n callerIdentity: string;\n handler: (data: RpcInvocationData) => string;\n }) {\n DataStreamAudioOutput._playbackFinishedHandlers[callerIdentity] = handler;\n\n if (DataStreamAudioOutput._playbackFinishedRpcRegistered) {\n return;\n }\n\n const rpcHandler = async (data: RpcInvocationData): Promise<string> => {\n const handler = DataStreamAudioOutput._playbackFinishedHandlers[data.callerIdentity];\n if (!handler) {\n log().warn(\n {\n callerIdentity: data.callerIdentity,\n expectedIdentities: Object.keys(DataStreamAudioOutput._playbackFinishedHandlers),\n },\n 'playback finished event received from unexpected participant',\n );\n\n return 'reject';\n }\n return handler(data);\n };\n\n room.localParticipant?.registerRpcMethod(RPC_PLAYBACK_FINISHED, rpcHandler);\n DataStreamAudioOutput._playbackFinishedRpcRegistered = true;\n }\n}\n"],"mappings":"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","names":["handler"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type AudioFrame, type Room, type RpcInvocationData, type TrackKind } from '@livekit/rtc-node';
|
|
2
|
+
import { AudioOutput } from '../io.js';
|
|
3
|
+
export interface DataStreamAudioOutputOptions {
|
|
4
|
+
room: Room;
|
|
5
|
+
destinationIdentity: string;
|
|
6
|
+
sampleRate?: number;
|
|
7
|
+
waitRemoteTrack?: TrackKind;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* AudioOutput implementation that streams audio to a remote avatar worker using LiveKit DataStream.
|
|
11
|
+
*/
|
|
12
|
+
export declare class DataStreamAudioOutput extends AudioOutput {
|
|
13
|
+
#private;
|
|
14
|
+
static _playbackFinishedRpcRegistered: boolean;
|
|
15
|
+
static _playbackFinishedHandlers: Record<string, (data: RpcInvocationData) => string>;
|
|
16
|
+
private room;
|
|
17
|
+
private destinationIdentity;
|
|
18
|
+
private roomConnectedFuture;
|
|
19
|
+
private waitRemoteTrack?;
|
|
20
|
+
private streamWriter?;
|
|
21
|
+
private pushedDuration;
|
|
22
|
+
private started;
|
|
23
|
+
private lock;
|
|
24
|
+
private startTask?;
|
|
25
|
+
constructor(opts: DataStreamAudioOutputOptions);
|
|
26
|
+
private _start;
|
|
27
|
+
captureFrame(frame: AudioFrame): Promise<void>;
|
|
28
|
+
flush(): void;
|
|
29
|
+
clearBuffer(): void;
|
|
30
|
+
private handlePlaybackFinished;
|
|
31
|
+
static registerPlaybackFinishedRpc({ room, callerIdentity, handler, }: {
|
|
32
|
+
room: Room;
|
|
33
|
+
callerIdentity: string;
|
|
34
|
+
handler: (data: RpcInvocationData) => string;
|
|
35
|
+
}): void;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=datastream_io.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type AudioFrame, type Room, type RpcInvocationData, type TrackKind } from '@livekit/rtc-node';
|
|
2
|
+
import { AudioOutput } from '../io.js';
|
|
3
|
+
export interface DataStreamAudioOutputOptions {
|
|
4
|
+
room: Room;
|
|
5
|
+
destinationIdentity: string;
|
|
6
|
+
sampleRate?: number;
|
|
7
|
+
waitRemoteTrack?: TrackKind;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* AudioOutput implementation that streams audio to a remote avatar worker using LiveKit DataStream.
|
|
11
|
+
*/
|
|
12
|
+
export declare class DataStreamAudioOutput extends AudioOutput {
|
|
13
|
+
#private;
|
|
14
|
+
static _playbackFinishedRpcRegistered: boolean;
|
|
15
|
+
static _playbackFinishedHandlers: Record<string, (data: RpcInvocationData) => string>;
|
|
16
|
+
private room;
|
|
17
|
+
private destinationIdentity;
|
|
18
|
+
private roomConnectedFuture;
|
|
19
|
+
private waitRemoteTrack?;
|
|
20
|
+
private streamWriter?;
|
|
21
|
+
private pushedDuration;
|
|
22
|
+
private started;
|
|
23
|
+
private lock;
|
|
24
|
+
private startTask?;
|
|
25
|
+
constructor(opts: DataStreamAudioOutputOptions);
|
|
26
|
+
private _start;
|
|
27
|
+
captureFrame(frame: AudioFrame): Promise<void>;
|
|
28
|
+
flush(): void;
|
|
29
|
+
clearBuffer(): void;
|
|
30
|
+
private handlePlaybackFinished;
|
|
31
|
+
static registerPlaybackFinishedRpc({ room, callerIdentity, handler, }: {
|
|
32
|
+
room: Room;
|
|
33
|
+
callerIdentity: string;
|
|
34
|
+
handler: (data: RpcInvocationData) => string;
|
|
35
|
+
}): void;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=datastream_io.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"datastream_io.d.ts","sourceRoot":"","sources":["../../../src/voice/avatar/datastream_io.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,UAAU,EAEf,KAAK,IAAI,EAET,KAAK,iBAAiB,EACtB,KAAK,SAAS,EACf,MAAM,mBAAmB,CAAC;AAS3B,OAAO,EAAE,WAAW,EAA8B,MAAM,UAAU,CAAC;AAMnE,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,IAAI,CAAC;IACX,mBAAmB,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,SAAS,CAAC;CAC7B;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,WAAW;;IACpD,MAAM,CAAC,8BAA8B,EAAE,OAAO,CAAS;IACvD,MAAM,CAAC,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,MAAM,CAAC,CAAM;IAE3F,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,mBAAmB,CAAe;IAC1C,OAAO,CAAC,eAAe,CAAC,CAAY;IACpC,OAAO,CAAC,YAAY,CAAC,CAAmB;IACxC,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,IAAI,CAAe;IAC3B,OAAO,CAAC,SAAS,CAAC,CAAa;gBAInB,IAAI,EAAE,4BAA4B;YAuChC,MAAM;IAiDd,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BpD,KAAK,IAAI,IAAI;IAYb,WAAW,IAAI,IAAI;IAUnB,OAAO,CAAC,sBAAsB;IAwB9B,MAAM,CAAC,2BAA2B,CAAC,EACjC,IAAI,EACJ,cAAc,EACd,OAAO,GACR,EAAE;QACD,IAAI,EAAE,IAAI,CAAC;QACX,cAAc,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,MAAM,CAAC;KAC9C;CA0BF"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Mutex } from "@livekit/mutex";
|
|
2
|
+
import {
|
|
3
|
+
RoomEvent
|
|
4
|
+
} from "@livekit/rtc-node";
|
|
5
|
+
import { log } from "../../log.js";
|
|
6
|
+
import {
|
|
7
|
+
Future,
|
|
8
|
+
Task,
|
|
9
|
+
shortuuid,
|
|
10
|
+
waitForParticipant,
|
|
11
|
+
waitForTrackPublication
|
|
12
|
+
} from "../../utils.js";
|
|
13
|
+
import { AudioOutput } from "../io.js";
|
|
14
|
+
const RPC_CLEAR_BUFFER = "lk.clear_buffer";
|
|
15
|
+
const RPC_PLAYBACK_FINISHED = "lk.playback_finished";
|
|
16
|
+
const AUDIO_STREAM_TOPIC = "lk.audio_stream";
|
|
17
|
+
class DataStreamAudioOutput extends AudioOutput {
|
|
18
|
+
static _playbackFinishedRpcRegistered = false;
|
|
19
|
+
static _playbackFinishedHandlers = {};
|
|
20
|
+
room;
|
|
21
|
+
destinationIdentity;
|
|
22
|
+
roomConnectedFuture;
|
|
23
|
+
waitRemoteTrack;
|
|
24
|
+
streamWriter;
|
|
25
|
+
pushedDuration = 0;
|
|
26
|
+
started = false;
|
|
27
|
+
lock = new Mutex();
|
|
28
|
+
startTask;
|
|
29
|
+
#logger = log();
|
|
30
|
+
constructor(opts) {
|
|
31
|
+
super(opts.sampleRate, void 0);
|
|
32
|
+
const { room, destinationIdentity, sampleRate, waitRemoteTrack } = opts;
|
|
33
|
+
this.room = room;
|
|
34
|
+
this.destinationIdentity = destinationIdentity;
|
|
35
|
+
this.sampleRate = sampleRate;
|
|
36
|
+
this.waitRemoteTrack = waitRemoteTrack;
|
|
37
|
+
const onRoomConnected = async () => {
|
|
38
|
+
if (this.startTask) return;
|
|
39
|
+
await this.roomConnectedFuture.await;
|
|
40
|
+
DataStreamAudioOutput.registerPlaybackFinishedRpc({
|
|
41
|
+
room,
|
|
42
|
+
callerIdentity: this.destinationIdentity,
|
|
43
|
+
handler: (data) => this.handlePlaybackFinished(data)
|
|
44
|
+
});
|
|
45
|
+
this.startTask = Task.from(({ signal }) => this._start(signal));
|
|
46
|
+
};
|
|
47
|
+
this.roomConnectedFuture = new Future();
|
|
48
|
+
this.room.on(RoomEvent.ConnectionStateChanged, (_) => {
|
|
49
|
+
if (room.isConnected && !this.roomConnectedFuture.done) {
|
|
50
|
+
this.roomConnectedFuture.resolve(void 0);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
if (this.room.isConnected) {
|
|
54
|
+
this.roomConnectedFuture.resolve(void 0);
|
|
55
|
+
}
|
|
56
|
+
onRoomConnected();
|
|
57
|
+
}
|
|
58
|
+
async _start(_abortSignal) {
|
|
59
|
+
const unlock = await this.lock.lock();
|
|
60
|
+
try {
|
|
61
|
+
if (this.started) return;
|
|
62
|
+
await this.roomConnectedFuture.await;
|
|
63
|
+
this.#logger.debug(
|
|
64
|
+
{
|
|
65
|
+
identity: this.destinationIdentity
|
|
66
|
+
},
|
|
67
|
+
"waiting for the remote participant"
|
|
68
|
+
);
|
|
69
|
+
await waitForParticipant({
|
|
70
|
+
room: this.room,
|
|
71
|
+
identity: this.destinationIdentity
|
|
72
|
+
});
|
|
73
|
+
if (this.waitRemoteTrack) {
|
|
74
|
+
this.#logger.debug(
|
|
75
|
+
{
|
|
76
|
+
identity: this.destinationIdentity,
|
|
77
|
+
kind: this.waitRemoteTrack
|
|
78
|
+
},
|
|
79
|
+
"waiting for the remote track"
|
|
80
|
+
);
|
|
81
|
+
await waitForTrackPublication({
|
|
82
|
+
room: this.room,
|
|
83
|
+
identity: this.destinationIdentity,
|
|
84
|
+
kind: this.waitRemoteTrack
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
this.#logger.debug(
|
|
88
|
+
{
|
|
89
|
+
identity: this.destinationIdentity
|
|
90
|
+
},
|
|
91
|
+
"remote participant ready"
|
|
92
|
+
);
|
|
93
|
+
this.started = true;
|
|
94
|
+
} finally {
|
|
95
|
+
unlock();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async captureFrame(frame) {
|
|
99
|
+
if (!this.startTask) {
|
|
100
|
+
this.startTask = Task.from(({ signal }) => this._start(signal));
|
|
101
|
+
}
|
|
102
|
+
await this.startTask.result;
|
|
103
|
+
await super.captureFrame(frame);
|
|
104
|
+
if (!this.streamWriter) {
|
|
105
|
+
this.streamWriter = await this.room.localParticipant.streamBytes({
|
|
106
|
+
name: shortuuid("AUDIO_"),
|
|
107
|
+
topic: AUDIO_STREAM_TOPIC,
|
|
108
|
+
destinationIdentities: [this.destinationIdentity],
|
|
109
|
+
attributes: {
|
|
110
|
+
sample_rate: frame.sampleRate.toString(),
|
|
111
|
+
num_channels: frame.channels.toString()
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
this.pushedDuration = 0;
|
|
115
|
+
}
|
|
116
|
+
await this.streamWriter.write(new Uint8Array(frame.data.buffer));
|
|
117
|
+
this.pushedDuration += frame.samplesPerChannel / frame.sampleRate;
|
|
118
|
+
}
|
|
119
|
+
flush() {
|
|
120
|
+
super.flush();
|
|
121
|
+
if (this.streamWriter === void 0 || !this.started) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
this.streamWriter.close().finally(() => {
|
|
125
|
+
this.streamWriter = void 0;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
clearBuffer() {
|
|
129
|
+
if (!this.started) return;
|
|
130
|
+
this.room.localParticipant.performRpc({
|
|
131
|
+
destinationIdentity: this.destinationIdentity,
|
|
132
|
+
method: RPC_CLEAR_BUFFER,
|
|
133
|
+
payload: ""
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
handlePlaybackFinished(data) {
|
|
137
|
+
if (data.callerIdentity !== this.destinationIdentity) {
|
|
138
|
+
this.#logger.warn(
|
|
139
|
+
{
|
|
140
|
+
callerIdentity: data.callerIdentity,
|
|
141
|
+
destinationIdentity: this.destinationIdentity
|
|
142
|
+
},
|
|
143
|
+
"playback finished event received from unexpected participant"
|
|
144
|
+
);
|
|
145
|
+
return "reject";
|
|
146
|
+
}
|
|
147
|
+
this.#logger.info(
|
|
148
|
+
{
|
|
149
|
+
callerIdentity: data.callerIdentity
|
|
150
|
+
},
|
|
151
|
+
"playback finished event received"
|
|
152
|
+
);
|
|
153
|
+
const playbackFinishedEvent = JSON.parse(data.payload);
|
|
154
|
+
this.onPlaybackFinished(playbackFinishedEvent);
|
|
155
|
+
return "ok";
|
|
156
|
+
}
|
|
157
|
+
static registerPlaybackFinishedRpc({
|
|
158
|
+
room,
|
|
159
|
+
callerIdentity,
|
|
160
|
+
handler
|
|
161
|
+
}) {
|
|
162
|
+
var _a;
|
|
163
|
+
DataStreamAudioOutput._playbackFinishedHandlers[callerIdentity] = handler;
|
|
164
|
+
if (DataStreamAudioOutput._playbackFinishedRpcRegistered) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const rpcHandler = async (data) => {
|
|
168
|
+
const handler2 = DataStreamAudioOutput._playbackFinishedHandlers[data.callerIdentity];
|
|
169
|
+
if (!handler2) {
|
|
170
|
+
log().warn(
|
|
171
|
+
{
|
|
172
|
+
callerIdentity: data.callerIdentity,
|
|
173
|
+
expectedIdentities: Object.keys(DataStreamAudioOutput._playbackFinishedHandlers)
|
|
174
|
+
},
|
|
175
|
+
"playback finished event received from unexpected participant"
|
|
176
|
+
);
|
|
177
|
+
return "reject";
|
|
178
|
+
}
|
|
179
|
+
return handler2(data);
|
|
180
|
+
};
|
|
181
|
+
(_a = room.localParticipant) == null ? void 0 : _a.registerRpcMethod(RPC_PLAYBACK_FINISHED, rpcHandler);
|
|
182
|
+
DataStreamAudioOutput._playbackFinishedRpcRegistered = true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
export {
|
|
186
|
+
DataStreamAudioOutput
|
|
187
|
+
};
|
|
188
|
+
//# sourceMappingURL=datastream_io.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/voice/avatar/datastream_io.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { Mutex } from '@livekit/mutex';\nimport {\n type AudioFrame,\n type ByteStreamWriter,\n type Room,\n RoomEvent,\n type RpcInvocationData,\n type TrackKind,\n} from '@livekit/rtc-node';\nimport { log } from '../../log.js';\nimport {\n Future,\n Task,\n shortuuid,\n waitForParticipant,\n waitForTrackPublication,\n} from '../../utils.js';\nimport { AudioOutput, type PlaybackFinishedEvent } from '../io.js';\n\nconst RPC_CLEAR_BUFFER = 'lk.clear_buffer';\nconst RPC_PLAYBACK_FINISHED = 'lk.playback_finished';\nconst AUDIO_STREAM_TOPIC = 'lk.audio_stream';\n\nexport interface DataStreamAudioOutputOptions {\n room: Room;\n destinationIdentity: string;\n sampleRate?: number;\n waitRemoteTrack?: TrackKind;\n}\n\n/**\n * AudioOutput implementation that streams audio to a remote avatar worker using LiveKit DataStream.\n */\nexport class DataStreamAudioOutput extends AudioOutput {\n static _playbackFinishedRpcRegistered: boolean = false;\n static _playbackFinishedHandlers: Record<string, (data: RpcInvocationData) => string> = {};\n\n private room: Room;\n private destinationIdentity: string;\n private roomConnectedFuture: Future<void>;\n private waitRemoteTrack?: TrackKind;\n private streamWriter?: ByteStreamWriter;\n private pushedDuration: number = 0;\n private started: boolean = false;\n private lock = new Mutex();\n private startTask?: Task<void>;\n\n #logger = log();\n\n constructor(opts: DataStreamAudioOutputOptions) {\n super(opts.sampleRate, undefined);\n\n const { room, destinationIdentity, sampleRate, waitRemoteTrack } = opts;\n this.room = room;\n this.destinationIdentity = destinationIdentity;\n this.sampleRate = sampleRate;\n this.waitRemoteTrack = waitRemoteTrack;\n\n const onRoomConnected = async () => {\n if (this.startTask) return;\n\n await this.roomConnectedFuture.await;\n\n // register the rpc method right after the room is connected\n DataStreamAudioOutput.registerPlaybackFinishedRpc({\n room,\n callerIdentity: this.destinationIdentity,\n handler: (data) => this.handlePlaybackFinished(data),\n });\n\n this.startTask = Task.from(({ signal }) => this._start(signal));\n };\n\n this.roomConnectedFuture = new Future<void>();\n\n this.room.on(RoomEvent.ConnectionStateChanged, (_) => {\n if (room.isConnected && !this.roomConnectedFuture.done) {\n this.roomConnectedFuture.resolve(undefined);\n }\n });\n\n if (this.room.isConnected) {\n this.roomConnectedFuture.resolve(undefined);\n }\n\n onRoomConnected();\n }\n\n private async _start(_abortSignal: AbortSignal) {\n const unlock = await this.lock.lock();\n\n try {\n if (this.started) return;\n\n await this.roomConnectedFuture.await;\n\n this.#logger.debug(\n {\n identity: this.destinationIdentity,\n },\n 'waiting for the remote participant',\n );\n\n await waitForParticipant({\n room: this.room,\n identity: this.destinationIdentity,\n });\n\n if (this.waitRemoteTrack) {\n this.#logger.debug(\n {\n identity: this.destinationIdentity,\n kind: this.waitRemoteTrack,\n },\n 'waiting for the remote track',\n );\n\n await waitForTrackPublication({\n room: this.room,\n identity: this.destinationIdentity,\n kind: this.waitRemoteTrack,\n });\n }\n\n this.#logger.debug(\n {\n identity: this.destinationIdentity,\n },\n 'remote participant ready',\n );\n\n this.started = true;\n } finally {\n unlock();\n }\n }\n\n async captureFrame(frame: AudioFrame): Promise<void> {\n if (!this.startTask) {\n this.startTask = Task.from(({ signal }) => this._start(signal));\n }\n\n await this.startTask.result;\n await super.captureFrame(frame);\n\n if (!this.streamWriter) {\n this.streamWriter = await this.room.localParticipant!.streamBytes({\n name: shortuuid('AUDIO_'),\n topic: AUDIO_STREAM_TOPIC,\n destinationIdentities: [this.destinationIdentity],\n attributes: {\n sample_rate: frame.sampleRate.toString(),\n num_channels: frame.channels.toString(),\n },\n });\n this.pushedDuration = 0;\n }\n\n // frame.data is a Int16Array, write accepts a Uint8Array\n await this.streamWriter.write(new Uint8Array(frame.data.buffer));\n this.pushedDuration += frame.samplesPerChannel / frame.sampleRate;\n }\n\n flush(): void {\n super.flush();\n\n if (this.streamWriter === undefined || !this.started) {\n return;\n }\n\n this.streamWriter.close().finally(() => {\n this.streamWriter = undefined;\n });\n }\n\n clearBuffer(): void {\n if (!this.started) return;\n\n this.room.localParticipant!.performRpc({\n destinationIdentity: this.destinationIdentity,\n method: RPC_CLEAR_BUFFER,\n payload: '',\n });\n }\n\n private handlePlaybackFinished(data: RpcInvocationData): string {\n if (data.callerIdentity !== this.destinationIdentity) {\n this.#logger.warn(\n {\n callerIdentity: data.callerIdentity,\n destinationIdentity: this.destinationIdentity,\n },\n 'playback finished event received from unexpected participant',\n );\n return 'reject';\n }\n\n this.#logger.info(\n {\n callerIdentity: data.callerIdentity,\n },\n 'playback finished event received',\n );\n\n const playbackFinishedEvent = JSON.parse(data.payload) as PlaybackFinishedEvent;\n this.onPlaybackFinished(playbackFinishedEvent);\n return 'ok';\n }\n\n static registerPlaybackFinishedRpc({\n room,\n callerIdentity,\n handler,\n }: {\n room: Room;\n callerIdentity: string;\n handler: (data: RpcInvocationData) => string;\n }) {\n DataStreamAudioOutput._playbackFinishedHandlers[callerIdentity] = handler;\n\n if (DataStreamAudioOutput._playbackFinishedRpcRegistered) {\n return;\n }\n\n const rpcHandler = async (data: RpcInvocationData): Promise<string> => {\n const handler = DataStreamAudioOutput._playbackFinishedHandlers[data.callerIdentity];\n if (!handler) {\n log().warn(\n {\n callerIdentity: data.callerIdentity,\n expectedIdentities: Object.keys(DataStreamAudioOutput._playbackFinishedHandlers),\n },\n 'playback finished event received from unexpected participant',\n );\n\n return 'reject';\n }\n return handler(data);\n };\n\n room.localParticipant?.registerRpcMethod(RPC_PLAYBACK_FINISHED, rpcHandler);\n DataStreamAudioOutput._playbackFinishedRpcRegistered = true;\n }\n}\n"],"mappings":"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","names":["handler"]}
|
|
@@ -13,11 +13,11 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
13
13
|
};
|
|
14
14
|
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
15
15
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
16
|
-
var
|
|
17
|
-
module.exports = __toCommonJS(
|
|
18
|
-
__reExport(
|
|
16
|
+
var avatar_exports = {};
|
|
17
|
+
module.exports = __toCommonJS(avatar_exports);
|
|
18
|
+
__reExport(avatar_exports, require("./datastream_io.cjs"), module.exports);
|
|
19
19
|
// Annotate the CommonJS export names for ESM import in node:
|
|
20
20
|
0 && (module.exports = {
|
|
21
|
-
...require("./
|
|
21
|
+
...require("./datastream_io.cjs")
|
|
22
22
|
});
|
|
23
23
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/voice/avatar/index.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nexport * from './datastream_io.js';\n"],"mappings":";;;;;;;;;;;;;;;AAAA;AAAA;AAGA,2BAAc,+BAHd;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/voice/avatar/index.ts"],"names":[],"mappings":"AAGA,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/voice/avatar/index.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nexport * from './datastream_io.js';\n"],"mappings":"AAGA,cAAc;","names":[]}
|
package/dist/voice/index.cjs
CHANGED
|
@@ -27,6 +27,7 @@ __export(voice_exports, {
|
|
|
27
27
|
module.exports = __toCommonJS(voice_exports);
|
|
28
28
|
var import_agent = require("./agent.cjs");
|
|
29
29
|
var import_agent_session = require("./agent_session.cjs");
|
|
30
|
+
__reExport(voice_exports, require("./avatar/index.cjs"), module.exports);
|
|
30
31
|
__reExport(voice_exports, require("./events.cjs"), module.exports);
|
|
31
32
|
var import_run_context = require("./run_context.cjs");
|
|
32
33
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -35,6 +36,7 @@ var import_run_context = require("./run_context.cjs");
|
|
|
35
36
|
AgentSession,
|
|
36
37
|
RunContext,
|
|
37
38
|
StopResponse,
|
|
39
|
+
...require("./avatar/index.cjs"),
|
|
38
40
|
...require("./events.cjs")
|
|
39
41
|
});
|
|
40
42
|
//# sourceMappingURL=index.cjs.map
|
package/dist/voice/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/voice/index.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nexport { Agent, StopResponse, type AgentOptions, type ModelSettings } from './agent.js';\nexport { AgentSession, type AgentSessionOptions } from './agent_session.js';\nexport * from './events.js';\nexport { RunContext } from './run_context.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,mBAA2E;AAC3E,2BAAuD;
|
|
1
|
+
{"version":3,"sources":["../../src/voice/index.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nexport { Agent, StopResponse, type AgentOptions, type ModelSettings } from './agent.js';\nexport { AgentSession, type AgentSessionOptions } from './agent_session.js';\n\nexport * from './avatar/index.js';\nexport * from './events.js';\nexport { RunContext } from './run_context.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,mBAA2E;AAC3E,2BAAuD;AAEvD,0BAAc,8BANd;AAOA,0BAAc,wBAPd;AAQA,yBAA2B;","names":[]}
|
package/dist/voice/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { Agent, StopResponse, type AgentOptions, type ModelSettings } from './agent.js';
|
|
2
2
|
export { AgentSession, type AgentSessionOptions } from './agent_session.js';
|
|
3
|
+
export * from './avatar/index.js';
|
|
3
4
|
export * from './events.js';
|
|
4
5
|
export { RunContext } from './run_context.js';
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/voice/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { Agent, StopResponse, type AgentOptions, type ModelSettings } from './agent.js';
|
|
2
2
|
export { AgentSession, type AgentSessionOptions } from './agent_session.js';
|
|
3
|
+
export * from './avatar/index.js';
|
|
3
4
|
export * from './events.js';
|
|
4
5
|
export { RunContext } from './run_context.js';
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/voice/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/voice/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE5E,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/voice/index.js
CHANGED
package/dist/voice/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/voice/index.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nexport { Agent, StopResponse, type AgentOptions, type ModelSettings } from './agent.js';\nexport { AgentSession, type AgentSessionOptions } from './agent_session.js';\nexport * from './events.js';\nexport { RunContext } from './run_context.js';\n"],"mappings":"AAGA,SAAS,OAAO,oBAA2D;AAC3E,SAAS,oBAA8C;
|
|
1
|
+
{"version":3,"sources":["../../src/voice/index.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nexport { Agent, StopResponse, type AgentOptions, type ModelSettings } from './agent.js';\nexport { AgentSession, type AgentSessionOptions } from './agent_session.js';\n\nexport * from './avatar/index.js';\nexport * from './events.js';\nexport { RunContext } from './run_context.js';\n"],"mappings":"AAGA,SAAS,OAAO,oBAA2D;AAC3E,SAAS,oBAA8C;AAEvD,cAAc;AACd,cAAc;AACd,SAAS,kBAAkB;","names":[]}
|