@spatialwalk/avatarkit 1.0.0-beta.3 → 1.0.0-beta.31
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/CHANGELOG.md +441 -0
- package/README.md +328 -138
- package/dist/StreamingAudioPlayer-B07iPxK4.js +398 -0
- package/dist/animation/AnimationWebSocketClient.d.ts +6 -24
- package/dist/animation/utils/eventEmitter.d.ts +0 -4
- package/dist/animation/utils/flameConverter.d.ts +3 -11
- package/dist/audio/AnimationPlayer.d.ts +5 -29
- package/dist/audio/StreamingAudioPlayer.d.ts +8 -66
- package/dist/avatar_core_wasm-i0Ocpx6q.js +2693 -0
- package/dist/avatar_core_wasm.wasm +0 -0
- package/dist/config/app-config.d.ts +4 -13
- package/dist/config/constants.d.ts +18 -11
- package/dist/config/sdk-config-loader.d.ts +2 -9
- package/dist/core/Avatar.d.ts +0 -15
- package/dist/core/AvatarController.d.ts +49 -109
- package/dist/core/AvatarDownloader.d.ts +0 -95
- package/dist/core/AvatarManager.d.ts +7 -18
- package/dist/core/AvatarSDK.d.ts +21 -0
- package/dist/core/AvatarView.d.ts +25 -119
- package/dist/core/NetworkLayer.d.ts +1 -0
- package/dist/generated/driveningress/v1/driveningress.d.ts +1 -12
- package/dist/generated/driveningress/v2/driveningress.d.ts +0 -3
- package/dist/generated/google/protobuf/struct.d.ts +5 -39
- package/dist/generated/google/protobuf/timestamp.d.ts +1 -103
- package/dist/index-CCBBCJi2.js +7915 -0
- package/dist/index.d.ts +1 -6
- package/dist/index.js +17 -17
- package/dist/renderer/RenderSystem.d.ts +1 -77
- package/dist/renderer/covariance.d.ts +0 -12
- package/dist/renderer/renderer.d.ts +0 -1
- package/dist/renderer/sortSplats.d.ts +0 -11
- package/dist/renderer/webgl/reorderData.d.ts +0 -13
- package/dist/renderer/webgl/webglRenderer.d.ts +3 -40
- package/dist/renderer/webgpu/webgpuRenderer.d.ts +3 -28
- package/dist/types/character-settings.d.ts +0 -5
- package/dist/types/character.d.ts +3 -21
- package/dist/types/index.d.ts +38 -18
- package/dist/utils/animation-interpolation.d.ts +3 -13
- package/dist/utils/client-id.d.ts +1 -0
- package/dist/utils/cls-tracker.d.ts +11 -0
- package/dist/utils/conversationId.d.ts +1 -0
- package/dist/utils/error-utils.d.ts +1 -25
- package/dist/utils/heartbeat-manager.d.ts +18 -0
- package/dist/utils/id-manager.d.ts +37 -0
- package/dist/utils/logger.d.ts +5 -11
- package/dist/utils/usage-tracker.d.ts +5 -0
- package/dist/vanilla/vite.config.d.ts +2 -0
- package/dist/wasm/avatarCoreAdapter.d.ts +11 -97
- package/dist/wasm/avatarCoreMemory.d.ts +5 -54
- package/package.json +10 -4
- package/dist/StreamingAudioPlayer-BeLlDiwE.js +0 -288
- package/dist/StreamingAudioPlayer-BeLlDiwE.js.map +0 -1
- package/dist/animation/AnimationWebSocketClient.d.ts.map +0 -1
- package/dist/animation/utils/eventEmitter.d.ts.map +0 -1
- package/dist/animation/utils/flameConverter.d.ts.map +0 -1
- package/dist/audio/AnimationPlayer.d.ts.map +0 -1
- package/dist/audio/StreamingAudioPlayer.d.ts.map +0 -1
- package/dist/avatar_core_wasm-DmkU6dYn.js +0 -1666
- package/dist/avatar_core_wasm-DmkU6dYn.js.map +0 -1
- package/dist/config/app-config.d.ts.map +0 -1
- package/dist/config/constants.d.ts.map +0 -1
- package/dist/config/sdk-config-loader.d.ts.map +0 -1
- package/dist/core/Avatar.d.ts.map +0 -1
- package/dist/core/AvatarController.d.ts.map +0 -1
- package/dist/core/AvatarDownloader.d.ts.map +0 -1
- package/dist/core/AvatarKit.d.ts +0 -60
- package/dist/core/AvatarKit.d.ts.map +0 -1
- package/dist/core/AvatarManager.d.ts.map +0 -1
- package/dist/core/AvatarView.d.ts.map +0 -1
- package/dist/generated/driveningress/v1/driveningress.d.ts.map +0 -1
- package/dist/generated/driveningress/v2/driveningress.d.ts.map +0 -1
- package/dist/generated/google/protobuf/struct.d.ts.map +0 -1
- package/dist/generated/google/protobuf/timestamp.d.ts.map +0 -1
- package/dist/index-NmYXWJnL.js +0 -9712
- package/dist/index-NmYXWJnL.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/renderer/RenderSystem.d.ts.map +0 -1
- package/dist/renderer/covariance.d.ts.map +0 -1
- package/dist/renderer/renderer.d.ts.map +0 -1
- package/dist/renderer/sortSplats.d.ts.map +0 -1
- package/dist/renderer/webgl/reorderData.d.ts.map +0 -1
- package/dist/renderer/webgl/webglRenderer.d.ts.map +0 -1
- package/dist/renderer/webgpu/webgpuRenderer.d.ts.map +0 -1
- package/dist/types/character-settings.d.ts.map +0 -1
- package/dist/types/character.d.ts.map +0 -1
- package/dist/types/index.d.ts.map +0 -1
- package/dist/utils/animation-interpolation.d.ts.map +0 -1
- package/dist/utils/error-utils.d.ts.map +0 -1
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/posthog-tracker.d.ts +0 -82
- package/dist/utils/posthog-tracker.d.ts.map +0 -1
- package/dist/utils/reqId.d.ts +0 -20
- package/dist/utils/reqId.d.ts.map +0 -1
- package/dist/wasm/avatarCoreAdapter.d.ts.map +0 -1
- package/dist/wasm/avatarCoreMemory.d.ts.map +0 -1
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
import { A as APP_CONFIG, e as errorToMessage, l as logEvent, a as logger } from "./index-CCBBCJi2.js";
|
|
5
|
+
class StreamingAudioPlayer {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
__publicField(this, "audioContext", null);
|
|
8
|
+
__publicField(this, "sampleRate");
|
|
9
|
+
__publicField(this, "channelCount");
|
|
10
|
+
__publicField(this, "debug");
|
|
11
|
+
__publicField(this, "sessionId");
|
|
12
|
+
__publicField(this, "sessionStartTime", 0);
|
|
13
|
+
__publicField(this, "pausedTimeOffset", 0);
|
|
14
|
+
__publicField(this, "pausedAt", 0);
|
|
15
|
+
__publicField(this, "pausedAudioContextTime", 0);
|
|
16
|
+
__publicField(this, "scheduledTime", 0);
|
|
17
|
+
__publicField(this, "isPlaying", false);
|
|
18
|
+
__publicField(this, "isPaused", false);
|
|
19
|
+
__publicField(this, "autoStartEnabled", true);
|
|
20
|
+
__publicField(this, "audioChunks", []);
|
|
21
|
+
__publicField(this, "scheduledChunks", 0);
|
|
22
|
+
__publicField(this, "activeSources", /* @__PURE__ */ new Set());
|
|
23
|
+
__publicField(this, "gainNode", null);
|
|
24
|
+
__publicField(this, "volume", 1);
|
|
25
|
+
__publicField(this, "onEndedCallback");
|
|
26
|
+
this.sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
27
|
+
this.sampleRate = (options == null ? void 0 : options.sampleRate) ?? APP_CONFIG.audio.sampleRate;
|
|
28
|
+
this.channelCount = (options == null ? void 0 : options.channelCount) ?? 1;
|
|
29
|
+
this.debug = (options == null ? void 0 : options.debug) ?? false;
|
|
30
|
+
}
|
|
31
|
+
async initialize() {
|
|
32
|
+
if (this.audioContext) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
this.audioContext = new AudioContext({
|
|
37
|
+
sampleRate: this.sampleRate
|
|
38
|
+
});
|
|
39
|
+
this.gainNode = this.audioContext.createGain();
|
|
40
|
+
this.gainNode.gain.value = this.volume;
|
|
41
|
+
this.gainNode.connect(this.audioContext.destination);
|
|
42
|
+
if (this.audioContext.state === "suspended") {
|
|
43
|
+
await this.audioContext.resume();
|
|
44
|
+
}
|
|
45
|
+
this.log("AudioContext initialized", {
|
|
46
|
+
sessionId: this.sessionId,
|
|
47
|
+
sampleRate: this.audioContext.sampleRate,
|
|
48
|
+
state: this.audioContext.state
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
const message = errorToMessage(error);
|
|
52
|
+
logEvent("activeAudioSessionFailed", "warning", {
|
|
53
|
+
sessionId: this.sessionId,
|
|
54
|
+
reason: message
|
|
55
|
+
});
|
|
56
|
+
logger.error("Failed to initialize AudioContext:", message);
|
|
57
|
+
throw error instanceof Error ? error : new Error(message);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
addChunk(pcmData, isLast = false) {
|
|
61
|
+
if (!this.audioContext) {
|
|
62
|
+
logger.error("AudioContext not initialized");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
this.audioChunks.push({ data: pcmData, isLast });
|
|
66
|
+
this.log(`Added chunk ${this.audioChunks.length}`, {
|
|
67
|
+
size: pcmData.length,
|
|
68
|
+
totalChunks: this.audioChunks.length,
|
|
69
|
+
isLast,
|
|
70
|
+
isPlaying: this.isPlaying,
|
|
71
|
+
scheduledChunks: this.scheduledChunks
|
|
72
|
+
});
|
|
73
|
+
if (!this.isPlaying && this.autoStartEnabled && this.audioChunks.length > 0) {
|
|
74
|
+
this.log("[StreamingAudioPlayer] Auto-starting playback from addChunk");
|
|
75
|
+
this.startPlayback();
|
|
76
|
+
} else if (this.isPlaying && !this.isPaused) {
|
|
77
|
+
this.log("[StreamingAudioPlayer] Already playing, scheduling next chunk");
|
|
78
|
+
this.scheduleNextChunk();
|
|
79
|
+
} else {
|
|
80
|
+
this.log("[StreamingAudioPlayer] Not playing and no chunks, waiting for more chunks");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async startNewSession(audioChunks) {
|
|
84
|
+
this.stop();
|
|
85
|
+
this.sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
86
|
+
this.audioChunks = [];
|
|
87
|
+
this.scheduledChunks = 0;
|
|
88
|
+
this.pausedTimeOffset = 0;
|
|
89
|
+
this.pausedAt = 0;
|
|
90
|
+
this.pausedAudioContextTime = 0;
|
|
91
|
+
this.log("Starting new session", {
|
|
92
|
+
chunks: audioChunks.length
|
|
93
|
+
});
|
|
94
|
+
for (const chunk of audioChunks) {
|
|
95
|
+
this.addChunk(chunk.data, chunk.isLast);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
startPlayback() {
|
|
99
|
+
if (!this.audioContext) {
|
|
100
|
+
this.log("[StreamingAudioPlayer] Cannot start playback: AudioContext not initialized");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (this.isPlaying) {
|
|
104
|
+
this.log("[StreamingAudioPlayer] Cannot start playback: Already playing");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
this.isPlaying = true;
|
|
108
|
+
this.sessionStartTime = this.audioContext.currentTime;
|
|
109
|
+
this.scheduledTime = this.sessionStartTime;
|
|
110
|
+
this.log("[StreamingAudioPlayer] Starting playback", {
|
|
111
|
+
sessionStartTime: this.sessionStartTime,
|
|
112
|
+
bufferedChunks: this.audioChunks.length,
|
|
113
|
+
scheduledChunks: this.scheduledChunks,
|
|
114
|
+
activeSources: this.activeSources.size
|
|
115
|
+
});
|
|
116
|
+
this.scheduleAllChunks();
|
|
117
|
+
}
|
|
118
|
+
scheduleAllChunks() {
|
|
119
|
+
while (this.scheduledChunks < this.audioChunks.length) {
|
|
120
|
+
this.scheduleNextChunk();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
scheduleNextChunk() {
|
|
124
|
+
if (!this.audioContext) {
|
|
125
|
+
this.log("[StreamingAudioPlayer] Cannot schedule chunk: AudioContext not initialized");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!this.isPlaying || this.isPaused) {
|
|
129
|
+
this.log("[StreamingAudioPlayer] Cannot schedule chunk: Not playing or paused");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const chunkIndex = this.scheduledChunks;
|
|
133
|
+
if (chunkIndex >= this.audioChunks.length) {
|
|
134
|
+
this.log(`[StreamingAudioPlayer] No more chunks to schedule (chunkIndex: ${chunkIndex}, totalChunks: ${this.audioChunks.length})`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const chunk = this.audioChunks[chunkIndex];
|
|
138
|
+
if (chunk.data.length === 0 && !chunk.isLast) {
|
|
139
|
+
this.scheduledChunks++;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const pcmData = chunk.data;
|
|
143
|
+
const isLast = chunk.isLast;
|
|
144
|
+
const audioBuffer = this.pcmToAudioBuffer(pcmData);
|
|
145
|
+
if (!audioBuffer) {
|
|
146
|
+
const errorMessage = "Failed to create AudioBuffer from PCM data";
|
|
147
|
+
logger.error(errorMessage);
|
|
148
|
+
logEvent("character_player", "error", {
|
|
149
|
+
sessionId: this.sessionId,
|
|
150
|
+
event: "audio_buffer_creation_failed"
|
|
151
|
+
});
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
const source = this.audioContext.createBufferSource();
|
|
156
|
+
source.buffer = audioBuffer;
|
|
157
|
+
source.connect(this.gainNode);
|
|
158
|
+
source.start(this.scheduledTime);
|
|
159
|
+
this.activeSources.add(source);
|
|
160
|
+
source.onended = () => {
|
|
161
|
+
this.activeSources.delete(source);
|
|
162
|
+
if (isLast && this.activeSources.size === 0) {
|
|
163
|
+
this.log("Last audio chunk ended, marking playback as ended");
|
|
164
|
+
this.markEnded();
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
this.scheduledTime += audioBuffer.duration;
|
|
168
|
+
this.scheduledChunks++;
|
|
169
|
+
this.log(`[StreamingAudioPlayer] Scheduled chunk ${chunkIndex + 1}/${this.audioChunks.length}`, {
|
|
170
|
+
startTime: this.scheduledTime - audioBuffer.duration,
|
|
171
|
+
duration: audioBuffer.duration,
|
|
172
|
+
nextScheduleTime: this.scheduledTime,
|
|
173
|
+
isLast,
|
|
174
|
+
activeSources: this.activeSources.size
|
|
175
|
+
});
|
|
176
|
+
} catch (err) {
|
|
177
|
+
logger.errorWithError("Failed to schedule audio chunk:", err);
|
|
178
|
+
logEvent("character_player", "error", {
|
|
179
|
+
sessionId: this.sessionId,
|
|
180
|
+
event: "schedule_chunk_failed",
|
|
181
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
pcmToAudioBuffer(pcmData) {
|
|
186
|
+
if (!this.audioContext) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
if (pcmData.length === 0) {
|
|
190
|
+
const silenceDuration = 0.01;
|
|
191
|
+
const numSamples2 = Math.floor(this.sampleRate * silenceDuration);
|
|
192
|
+
const audioBuffer2 = this.audioContext.createBuffer(
|
|
193
|
+
this.channelCount,
|
|
194
|
+
numSamples2,
|
|
195
|
+
this.sampleRate
|
|
196
|
+
);
|
|
197
|
+
for (let channel = 0; channel < this.channelCount; channel++) {
|
|
198
|
+
const channelData = audioBuffer2.getChannelData(channel);
|
|
199
|
+
channelData.fill(0);
|
|
200
|
+
}
|
|
201
|
+
return audioBuffer2;
|
|
202
|
+
}
|
|
203
|
+
const alignedData = new Uint8Array(pcmData);
|
|
204
|
+
const int16Array = new Int16Array(alignedData.buffer, 0, alignedData.length / 2);
|
|
205
|
+
const numSamples = int16Array.length / this.channelCount;
|
|
206
|
+
const audioBuffer = this.audioContext.createBuffer(
|
|
207
|
+
this.channelCount,
|
|
208
|
+
numSamples,
|
|
209
|
+
this.sampleRate
|
|
210
|
+
);
|
|
211
|
+
for (let channel = 0; channel < this.channelCount; channel++) {
|
|
212
|
+
const channelData = audioBuffer.getChannelData(channel);
|
|
213
|
+
for (let i = 0; i < numSamples; i++) {
|
|
214
|
+
const sampleIndex = i * this.channelCount + channel;
|
|
215
|
+
channelData[i] = int16Array[sampleIndex] / 32768;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return audioBuffer;
|
|
219
|
+
}
|
|
220
|
+
getCurrentTime() {
|
|
221
|
+
if (!this.audioContext || !this.isPlaying) {
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
if (this.isPaused) {
|
|
225
|
+
return this.pausedAt;
|
|
226
|
+
}
|
|
227
|
+
const currentAudioTime = this.audioContext.currentTime;
|
|
228
|
+
const elapsed = currentAudioTime - this.sessionStartTime - this.pausedTimeOffset;
|
|
229
|
+
return Math.max(0, elapsed);
|
|
230
|
+
}
|
|
231
|
+
pause() {
|
|
232
|
+
if (!this.isPlaying || this.isPaused || !this.audioContext) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
this.pausedAt = this.getCurrentTime();
|
|
236
|
+
this.pausedAudioContextTime = this.audioContext.currentTime;
|
|
237
|
+
this.isPaused = true;
|
|
238
|
+
if (this.audioContext.state === "running") {
|
|
239
|
+
this.audioContext.suspend().catch((err) => {
|
|
240
|
+
logger.errorWithError("Failed to suspend AudioContext:", err);
|
|
241
|
+
this.isPaused = false;
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
this.log("Playback paused", {
|
|
245
|
+
pausedAt: this.pausedAt,
|
|
246
|
+
pausedAudioContextTime: this.pausedAudioContextTime,
|
|
247
|
+
audioContextState: this.audioContext.state
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
async resume() {
|
|
251
|
+
if (!this.isPaused || !this.audioContext || !this.isPlaying) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (this.audioContext.state === "suspended") {
|
|
255
|
+
try {
|
|
256
|
+
await this.audioContext.resume();
|
|
257
|
+
} catch (err) {
|
|
258
|
+
logger.errorWithError("Failed to resume AudioContext:", err);
|
|
259
|
+
throw err;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const currentAudioTime = this.audioContext.currentTime;
|
|
263
|
+
this.sessionStartTime = this.pausedAudioContextTime - this.pausedAt - this.pausedTimeOffset;
|
|
264
|
+
this.isPaused = false;
|
|
265
|
+
if (this.scheduledChunks < this.audioChunks.length) {
|
|
266
|
+
this.scheduleAllChunks();
|
|
267
|
+
}
|
|
268
|
+
this.log("Playback resumed", {
|
|
269
|
+
pausedAt: this.pausedAt,
|
|
270
|
+
pausedAudioContextTime: this.pausedAudioContextTime,
|
|
271
|
+
currentAudioContextTime: currentAudioTime,
|
|
272
|
+
adjustedSessionStartTime: this.sessionStartTime,
|
|
273
|
+
audioContextState: this.audioContext.state
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
stop() {
|
|
277
|
+
if (!this.audioContext) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (this.isPaused && this.audioContext.state === "suspended") {
|
|
281
|
+
this.audioContext.resume().catch(() => {
|
|
282
|
+
});
|
|
283
|
+
this.isPaused = false;
|
|
284
|
+
}
|
|
285
|
+
this.isPlaying = false;
|
|
286
|
+
this.isPaused = false;
|
|
287
|
+
this.sessionStartTime = 0;
|
|
288
|
+
this.scheduledTime = 0;
|
|
289
|
+
for (const source of this.activeSources) {
|
|
290
|
+
source.onended = null;
|
|
291
|
+
try {
|
|
292
|
+
source.stop(0);
|
|
293
|
+
} catch {
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
source.disconnect();
|
|
297
|
+
} catch {
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
this.activeSources.clear();
|
|
301
|
+
this.audioChunks = [];
|
|
302
|
+
this.scheduledChunks = 0;
|
|
303
|
+
this.log("[StreamingAudioPlayer] Playback stopped, state reset");
|
|
304
|
+
}
|
|
305
|
+
setAutoStart(enabled) {
|
|
306
|
+
this.autoStartEnabled = enabled;
|
|
307
|
+
this.log(`Auto-start ${enabled ? "enabled" : "disabled"}`);
|
|
308
|
+
}
|
|
309
|
+
play() {
|
|
310
|
+
if (this.isPlaying) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
this.autoStartEnabled = true;
|
|
314
|
+
this.startPlayback();
|
|
315
|
+
}
|
|
316
|
+
markEnded() {
|
|
317
|
+
var _a;
|
|
318
|
+
this.log("Playback ended");
|
|
319
|
+
this.isPlaying = false;
|
|
320
|
+
(_a = this.onEndedCallback) == null ? void 0 : _a.call(this);
|
|
321
|
+
}
|
|
322
|
+
onEnded(callback) {
|
|
323
|
+
this.onEndedCallback = callback;
|
|
324
|
+
}
|
|
325
|
+
isPlayingNow() {
|
|
326
|
+
return this.isPlaying && !this.isPaused;
|
|
327
|
+
}
|
|
328
|
+
getBufferedDuration() {
|
|
329
|
+
if (!this.audioContext) {
|
|
330
|
+
return 0;
|
|
331
|
+
}
|
|
332
|
+
let totalSamples = 0;
|
|
333
|
+
for (const chunk of this.audioChunks) {
|
|
334
|
+
totalSamples += chunk.data.length / 2 / this.channelCount;
|
|
335
|
+
}
|
|
336
|
+
return totalSamples / this.sampleRate;
|
|
337
|
+
}
|
|
338
|
+
getRemainingDuration() {
|
|
339
|
+
const total = this.getBufferedDuration();
|
|
340
|
+
const played = this.getCurrentTime();
|
|
341
|
+
return Math.max(0, total - played);
|
|
342
|
+
}
|
|
343
|
+
dispose() {
|
|
344
|
+
this.stop();
|
|
345
|
+
if (this.audioContext) {
|
|
346
|
+
this.audioContext.close();
|
|
347
|
+
this.audioContext = null;
|
|
348
|
+
this.gainNode = null;
|
|
349
|
+
}
|
|
350
|
+
this.audioChunks = [];
|
|
351
|
+
this.scheduledChunks = 0;
|
|
352
|
+
this.sessionStartTime = 0;
|
|
353
|
+
this.pausedTimeOffset = 0;
|
|
354
|
+
this.pausedAt = 0;
|
|
355
|
+
this.pausedAudioContextTime = 0;
|
|
356
|
+
this.scheduledTime = 0;
|
|
357
|
+
this.onEndedCallback = void 0;
|
|
358
|
+
this.log("StreamingAudioPlayer disposed");
|
|
359
|
+
}
|
|
360
|
+
flush(options) {
|
|
361
|
+
const hard = (options == null ? void 0 : options.hard) === true;
|
|
362
|
+
if (hard) {
|
|
363
|
+
this.stop();
|
|
364
|
+
this.audioChunks = [];
|
|
365
|
+
this.scheduledChunks = 0;
|
|
366
|
+
this.sessionStartTime = 0;
|
|
367
|
+
this.pausedAt = 0;
|
|
368
|
+
this.scheduledTime = 0;
|
|
369
|
+
this.log("Flushed (hard)");
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (this.scheduledChunks < this.audioChunks.length) {
|
|
373
|
+
this.audioChunks.splice(this.scheduledChunks);
|
|
374
|
+
}
|
|
375
|
+
this.log("Flushed (soft)", { remainingScheduled: this.scheduledChunks });
|
|
376
|
+
}
|
|
377
|
+
setVolume(volume) {
|
|
378
|
+
if (volume < 0 || volume > 1) {
|
|
379
|
+
logger.warn(`[StreamingAudioPlayer] Volume out of range: ${volume}, clamping to [0, 1]`);
|
|
380
|
+
volume = Math.max(0, Math.min(1, volume));
|
|
381
|
+
}
|
|
382
|
+
this.volume = volume;
|
|
383
|
+
if (this.gainNode) {
|
|
384
|
+
this.gainNode.gain.value = volume;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
getVolume() {
|
|
388
|
+
return this.volume;
|
|
389
|
+
}
|
|
390
|
+
log(message, data) {
|
|
391
|
+
if (this.debug) {
|
|
392
|
+
logger.log(`[StreamingAudioPlayer] ${message}`, data || "");
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
export {
|
|
397
|
+
StreamingAudioPlayer
|
|
398
|
+
};
|
|
@@ -2,14 +2,16 @@ import { EventEmitter } from './utils/eventEmitter';
|
|
|
2
2
|
export interface AnimationWebSocketClientOptions {
|
|
3
3
|
wsUrl: string;
|
|
4
4
|
reconnectAttempts?: number;
|
|
5
|
-
debug?: boolean;
|
|
6
5
|
jwtToken?: string;
|
|
6
|
+
appId?: string;
|
|
7
|
+
clientId?: string;
|
|
7
8
|
}
|
|
8
9
|
export declare class AnimationWebSocketClient extends EventEmitter {
|
|
9
10
|
private wsUrl;
|
|
10
11
|
private reconnectAttempts;
|
|
11
|
-
private debug;
|
|
12
12
|
private jwtToken?;
|
|
13
|
+
private appId?;
|
|
14
|
+
private clientId?;
|
|
13
15
|
private ws;
|
|
14
16
|
private currentCharacterId;
|
|
15
17
|
private currentRetryCount;
|
|
@@ -17,34 +19,14 @@ export declare class AnimationWebSocketClient extends EventEmitter {
|
|
|
17
19
|
private isManuallyDisconnected;
|
|
18
20
|
private reconnectTimer;
|
|
19
21
|
constructor(options: AnimationWebSocketClientOptions);
|
|
20
|
-
/**
|
|
21
|
-
* 连接WebSocket
|
|
22
|
-
*/
|
|
23
22
|
connect(characterId: string): Promise<void>;
|
|
24
|
-
/**
|
|
25
|
-
* 断开连接
|
|
26
|
-
*/
|
|
27
23
|
disconnect(): void;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*/
|
|
31
|
-
sendAudioData(reqId: string, audioData: ArrayBuffer, end: boolean): boolean;
|
|
32
|
-
/**
|
|
33
|
-
* 生成请求ID
|
|
34
|
-
* 使用统一的 ReqID 生成规则:YYYYMMDDHHmmss_nanoid
|
|
35
|
-
*/
|
|
36
|
-
generateReqId(): string;
|
|
37
|
-
/**
|
|
38
|
-
* 获取连接状态
|
|
39
|
-
*/
|
|
24
|
+
sendAudioData(conversationId: string, audioData: ArrayBuffer, end: boolean): boolean;
|
|
25
|
+
generateConversationId(): string;
|
|
40
26
|
isConnected(): boolean;
|
|
41
|
-
/**
|
|
42
|
-
* 获取当前角色ID
|
|
43
|
-
*/
|
|
44
27
|
getCurrentCharacterId(): string;
|
|
45
28
|
private buildWebSocketUrl;
|
|
46
29
|
private connectWebSocket;
|
|
47
30
|
private handleMessage;
|
|
48
31
|
private scheduleReconnect;
|
|
49
32
|
}
|
|
50
|
-
//# sourceMappingURL=AnimationWebSocketClient.d.ts.map
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple Event Emitter
|
|
3
|
-
*/
|
|
4
1
|
type EventHandler = (...args: any[]) => void;
|
|
5
2
|
export declare class EventEmitter {
|
|
6
3
|
private events;
|
|
@@ -10,4 +7,3 @@ export declare class EventEmitter {
|
|
|
10
7
|
removeAllListeners(event?: string): void;
|
|
11
8
|
}
|
|
12
9
|
export {};
|
|
13
|
-
//# sourceMappingURL=eventEmitter.d.ts.map
|
|
@@ -10,17 +10,9 @@ export interface FlameParams {
|
|
|
10
10
|
eyelid?: number[];
|
|
11
11
|
has_eyelid?: boolean;
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
* Convert proto Flame to WASM FlameParams format
|
|
15
|
-
*/
|
|
13
|
+
|
|
16
14
|
export declare function convertProtoFlameToWasmParams(protoFlame: Flame): FlameParams;
|
|
17
|
-
|
|
18
|
-
* Convert WASM FlameParams to proto Flame format
|
|
19
|
-
* Used for transition animation from idle to speaking
|
|
20
|
-
*/
|
|
15
|
+
|
|
21
16
|
export declare function convertWasmParamsToProtoFlame(wasmParams: FlameParams): Flame;
|
|
22
|
-
|
|
23
|
-
* Create a neutral proto Flame (zero pose)
|
|
24
|
-
*/
|
|
17
|
+
|
|
25
18
|
export declare function createNeutralFlameProto(): Flame;
|
|
26
|
-
//# sourceMappingURL=flameConverter.d.ts.map
|
|
@@ -7,47 +7,23 @@ export declare class AnimationPlayer {
|
|
|
7
7
|
private onEndedCallback?;
|
|
8
8
|
private static audioUnlocked;
|
|
9
9
|
private useStreaming;
|
|
10
|
-
/**
|
|
11
|
-
* 解锁音频上下文(Safari 自动播放策略)
|
|
12
|
-
* 必须在用户交互事件(如 click)中调用
|
|
13
|
-
*/
|
|
14
10
|
static unlockAudioContext(): Promise<void>;
|
|
15
|
-
/**
|
|
16
|
-
* Initialize with HTMLAudioElement (traditional way)
|
|
17
|
-
*/
|
|
18
11
|
initialize(audioUrl: string, onEnded?: () => void): Promise<void>;
|
|
19
|
-
/**
|
|
20
|
-
* Initialize with StreamingAudioPlayer (streaming way)
|
|
21
|
-
* @deprecated 使用 prepareStreamingPlayer() 代替
|
|
22
|
-
*/
|
|
23
12
|
initializeStreaming(streamingPlayer: StreamingAudioPlayer, onEnded?: () => void): Promise<void>;
|
|
24
|
-
/**
|
|
25
|
-
* 检查流式播放器是否已准备好
|
|
26
|
-
*/
|
|
27
13
|
isStreamingReady(): boolean;
|
|
28
|
-
|
|
29
|
-
* 创建并初始化流式播放器
|
|
30
|
-
* 在服务连接建立时调用
|
|
31
|
-
*/
|
|
14
|
+
getStreamingPlayer(): StreamingAudioPlayer | null;
|
|
32
15
|
createAndInitializeStreamingPlayer(): Promise<void>;
|
|
33
|
-
/**
|
|
34
|
-
* 准备流式播放器(如果未创建则创建并初始化)
|
|
35
|
-
* 停止之前的播放并更新结束回调
|
|
36
|
-
*/
|
|
37
16
|
prepareStreamingPlayer(onEnded?: () => void): Promise<void>;
|
|
38
17
|
private setupEventListeners;
|
|
39
18
|
play(): Promise<void>;
|
|
40
19
|
stop(): void;
|
|
41
20
|
isPlaying(): boolean;
|
|
42
21
|
getCurrentFrameIndex(): number;
|
|
43
|
-
/**
|
|
44
|
-
* Get current playback time
|
|
45
|
-
*/
|
|
46
22
|
getCurrentTime(): number;
|
|
47
|
-
/**
|
|
48
|
-
* 添加音频块(仅用于流式播放)
|
|
49
|
-
*/
|
|
50
23
|
addAudioChunk(audio: Uint8Array, isLast?: boolean): void;
|
|
24
|
+
pause(): void;
|
|
25
|
+
resume(): Promise<void>;
|
|
26
|
+
setVolume(volume: number): void;
|
|
27
|
+
getVolume(): number;
|
|
51
28
|
dispose(): void;
|
|
52
29
|
}
|
|
53
|
-
//# sourceMappingURL=AnimationPlayer.d.ts.map
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Streaming Audio Player
|
|
3
|
-
* Implements real-time audio playback using Web Audio API
|
|
4
|
-
* Supports dynamic PCM chunk addition without Workers
|
|
5
|
-
*/
|
|
6
1
|
export interface StreamingAudioPlayerOptions {
|
|
7
2
|
sampleRate?: number;
|
|
8
3
|
channelCount?: number;
|
|
@@ -17,97 +12,44 @@ export declare class StreamingAudioPlayer {
|
|
|
17
12
|
private sessionStartTime;
|
|
18
13
|
private pausedTimeOffset;
|
|
19
14
|
private pausedAt;
|
|
15
|
+
private pausedAudioContextTime;
|
|
20
16
|
private scheduledTime;
|
|
21
17
|
private isPlaying;
|
|
22
18
|
private isPaused;
|
|
19
|
+
private autoStartEnabled;
|
|
23
20
|
private audioChunks;
|
|
24
21
|
private scheduledChunks;
|
|
25
22
|
private activeSources;
|
|
23
|
+
private gainNode;
|
|
24
|
+
private volume;
|
|
26
25
|
private onEndedCallback?;
|
|
27
26
|
constructor(options?: StreamingAudioPlayerOptions);
|
|
28
|
-
/**
|
|
29
|
-
* Initialize audio context (create and ensure it's ready)
|
|
30
|
-
*/
|
|
31
27
|
initialize(): Promise<void>;
|
|
32
|
-
/**
|
|
33
|
-
* Add audio chunk (16-bit PCM)
|
|
34
|
-
*/
|
|
35
28
|
addChunk(pcmData: Uint8Array, isLast?: boolean): void;
|
|
36
|
-
/**
|
|
37
|
-
* Start new session (stop current and start fresh)
|
|
38
|
-
*/
|
|
39
29
|
startNewSession(audioChunks: Array<{
|
|
40
30
|
data: Uint8Array;
|
|
41
31
|
isLast: boolean;
|
|
42
32
|
}>): Promise<void>;
|
|
43
|
-
/**
|
|
44
|
-
* Start playback
|
|
45
|
-
*/
|
|
46
33
|
private startPlayback;
|
|
47
|
-
/**
|
|
48
|
-
* Schedule all pending chunks
|
|
49
|
-
*/
|
|
50
34
|
private scheduleAllChunks;
|
|
51
|
-
/**
|
|
52
|
-
* Schedule next audio chunk
|
|
53
|
-
*/
|
|
54
35
|
private scheduleNextChunk;
|
|
55
|
-
/**
|
|
56
|
-
* Convert PCM data to AudioBuffer
|
|
57
|
-
* Input: 16-bit PCM (int16), Output: AudioBuffer (float32 [-1, 1])
|
|
58
|
-
*/
|
|
59
36
|
private pcmToAudioBuffer;
|
|
60
|
-
/**
|
|
61
|
-
* Get current playback time (seconds)
|
|
62
|
-
*/
|
|
63
37
|
getCurrentTime(): number;
|
|
64
|
-
/**
|
|
65
|
-
* Pause playback
|
|
66
|
-
*/
|
|
67
38
|
pause(): void;
|
|
68
|
-
/**
|
|
69
|
-
* Resume playback
|
|
70
|
-
*/
|
|
71
39
|
resume(): Promise<void>;
|
|
72
|
-
/**
|
|
73
|
-
* Stop playback
|
|
74
|
-
*/
|
|
75
40
|
stop(): void;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
*/
|
|
41
|
+
setAutoStart(enabled: boolean): void;
|
|
42
|
+
play(): void;
|
|
79
43
|
markEnded(): void;
|
|
80
|
-
/**
|
|
81
|
-
* Set ended callback
|
|
82
|
-
*/
|
|
83
44
|
onEnded(callback: () => void): void;
|
|
84
|
-
/**
|
|
85
|
-
* Check if playing
|
|
86
|
-
*/
|
|
87
45
|
isPlayingNow(): boolean;
|
|
88
|
-
/**
|
|
89
|
-
* Get total duration of buffered audio
|
|
90
|
-
*/
|
|
91
46
|
getBufferedDuration(): number;
|
|
92
|
-
/**
|
|
93
|
-
* Get remaining duration (buffered - played) in seconds
|
|
94
|
-
*/
|
|
95
47
|
getRemainingDuration(): number;
|
|
96
|
-
/**
|
|
97
|
-
* Dispose and cleanup
|
|
98
|
-
*/
|
|
99
48
|
dispose(): void;
|
|
100
|
-
/**
|
|
101
|
-
* Flush buffered audio
|
|
102
|
-
* - hard: stops all playing sources and clears all chunks
|
|
103
|
-
* - soft (default): clears UNSCHEDULED chunks only
|
|
104
|
-
*/
|
|
105
49
|
flush(options?: {
|
|
106
50
|
hard?: boolean;
|
|
107
51
|
}): void;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
*/
|
|
52
|
+
setVolume(volume: number): void;
|
|
53
|
+
getVolume(): number;
|
|
111
54
|
private log;
|
|
112
55
|
}
|
|
113
|
-
//# sourceMappingURL=StreamingAudioPlayer.d.ts.map
|