@spatialwalk/avatarkit 1.0.0-beta.57 → 1.0.0-beta.59
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 +11 -0
- package/dist/{StreamingAudioPlayer-Co3xec_k.js → StreamingAudioPlayer-BbUBoPqm.js} +85 -5
- package/dist/audio/StreamingAudioPlayer.d.ts +3 -0
- package/dist/avatar_core_wasm.wasm +0 -0
- package/dist/config/sdk-config-loader.d.ts +2 -0
- package/dist/core/AvatarView.d.ts +3 -1
- package/dist/{index-cfYSlUmQ.js → index-B0MJ_hCJ.js} +58 -29
- package/dist/index.js +1 -1
- package/package.json +12 -13
- package/dist/docs/render-flame-api-example.d.ts +0 -28
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.0-beta.59] - 2026-01-14
|
|
6
|
+
|
|
7
|
+
### 🐛 Bugfixes
|
|
8
|
+
- **AudioContext Auto-Resume** - Fixed issue where audio playback would get stuck after external applications request recording permissions. AudioContext now automatically resumes when suspended during playback
|
|
9
|
+
- **PostHog Instance Initialization** - Fixed PostHog named instance initialization to avoid conflicts with external PostHog instances
|
|
10
|
+
|
|
11
|
+
## [1.0.0-beta.58] - 2026-01-14
|
|
12
|
+
|
|
13
|
+
### ✨ New Features
|
|
14
|
+
- **Pure Rendering Mode APIs** - Added `renderFlame()` and `generateTransitionFromIdle()` methods to `AvatarView` for external-controlled rendering without audio synchronization
|
|
15
|
+
|
|
5
16
|
## [1.0.0-beta.57] - 2026-01-09
|
|
6
17
|
|
|
7
18
|
### 🐛 Bugfixes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
-
import { A as APP_CONFIG,
|
|
4
|
+
import { A as APP_CONFIG, l as logger, e as errorToMessage, a as logEvent } from "./index-B0MJ_hCJ.js";
|
|
5
5
|
class StreamingAudioPlayer {
|
|
6
6
|
constructor(options) {
|
|
7
7
|
__publicField(this, "audioContext", null);
|
|
@@ -27,6 +27,8 @@ class StreamingAudioPlayer {
|
|
|
27
27
|
__publicField(this, "gainNode", null);
|
|
28
28
|
__publicField(this, "volume", 1);
|
|
29
29
|
__publicField(this, "onEndedCallback");
|
|
30
|
+
__publicField(this, "stateChangeHandler");
|
|
31
|
+
__publicField(this, "isResuming", false);
|
|
30
32
|
this.sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
31
33
|
this.sampleRate = (options == null ? void 0 : options.sampleRate) ?? APP_CONFIG.audio.sampleRate;
|
|
32
34
|
this.channelCount = (options == null ? void 0 : options.channelCount) ?? 1;
|
|
@@ -46,6 +48,15 @@ class StreamingAudioPlayer {
|
|
|
46
48
|
if (this.audioContext.state === "suspended") {
|
|
47
49
|
await this.audioContext.resume();
|
|
48
50
|
}
|
|
51
|
+
this.stateChangeHandler = (event) => {
|
|
52
|
+
const context = event.target;
|
|
53
|
+
if (context.state === "suspended" && this.isPlaying && !this.isPaused) {
|
|
54
|
+
this.ensureAudioContextRunning().catch((err) => {
|
|
55
|
+
logger.errorWithError("[StreamingAudioPlayer] Failed to auto-resume AudioContext after external suspend:", err);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
this.audioContext.addEventListener("statechange", this.stateChangeHandler);
|
|
49
60
|
this.log("AudioContext initialized", {
|
|
50
61
|
sessionId: this.sessionId,
|
|
51
62
|
sampleRate: this.audioContext.sampleRate,
|
|
@@ -61,11 +72,64 @@ class StreamingAudioPlayer {
|
|
|
61
72
|
throw error instanceof Error ? error : new Error(message);
|
|
62
73
|
}
|
|
63
74
|
}
|
|
75
|
+
async ensureAudioContextRunning() {
|
|
76
|
+
if (!this.audioContext) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const state = this.audioContext.state;
|
|
80
|
+
if (state === "running") {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (state === "closed") {
|
|
84
|
+
this.log("AudioContext is closed, cannot resume", {
|
|
85
|
+
sessionId: this.sessionId,
|
|
86
|
+
state
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (state === "suspended" && this.isPlaying && !this.isPaused) {
|
|
91
|
+
if (this.isResuming) {
|
|
92
|
+
this.log("AudioContext resume already in progress, skipping duplicate request", {
|
|
93
|
+
sessionId: this.sessionId,
|
|
94
|
+
state
|
|
95
|
+
});
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.isResuming = true;
|
|
99
|
+
try {
|
|
100
|
+
this.log("AudioContext is suspended during playback, resuming...", {
|
|
101
|
+
sessionId: this.sessionId,
|
|
102
|
+
state,
|
|
103
|
+
isPlaying: this.isPlaying,
|
|
104
|
+
isPaused: this.isPaused
|
|
105
|
+
});
|
|
106
|
+
await this.audioContext.resume();
|
|
107
|
+
this.log("AudioContext resumed successfully", {
|
|
108
|
+
sessionId: this.sessionId,
|
|
109
|
+
state: this.audioContext.state
|
|
110
|
+
});
|
|
111
|
+
} catch (err) {
|
|
112
|
+
logger.errorWithError("[StreamingAudioPlayer] Failed to resume AudioContext:", err);
|
|
113
|
+
logEvent("character_player", "error", {
|
|
114
|
+
sessionId: this.sessionId,
|
|
115
|
+
event: "audio_context_resume_failed",
|
|
116
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
117
|
+
});
|
|
118
|
+
} finally {
|
|
119
|
+
this.isResuming = false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
64
123
|
addChunk(pcmData, isLast = false) {
|
|
65
124
|
if (!this.audioContext) {
|
|
66
125
|
logger.error("AudioContext not initialized");
|
|
67
126
|
return;
|
|
68
127
|
}
|
|
128
|
+
if (this.isPlaying && !this.isPaused && this.audioContext.state === "suspended") {
|
|
129
|
+
this.ensureAudioContextRunning().catch((err) => {
|
|
130
|
+
logger.errorWithError("[StreamingAudioPlayer] Failed to ensure AudioContext running in addChunk:", err);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
69
133
|
this.audioChunks.push({ data: pcmData, isLast });
|
|
70
134
|
this.log(`Added chunk ${this.audioChunks.length}`, {
|
|
71
135
|
size: pcmData.length,
|
|
@@ -83,7 +147,9 @@ class StreamingAudioPlayer {
|
|
|
83
147
|
}
|
|
84
148
|
if (!this.isPlaying && this.autoStartEnabled && this.audioChunks.length > 0) {
|
|
85
149
|
this.log("[StreamingAudioPlayer] Auto-starting playback from addChunk");
|
|
86
|
-
this.startPlayback()
|
|
150
|
+
this.startPlayback().catch((err) => {
|
|
151
|
+
logger.errorWithError("[StreamingAudioPlayer] Failed to start playback from addChunk:", err);
|
|
152
|
+
});
|
|
87
153
|
} else if (this.isPlaying && !this.isPaused) {
|
|
88
154
|
this.log("[StreamingAudioPlayer] Already playing, scheduling next chunk");
|
|
89
155
|
this.scheduleNextChunk();
|
|
@@ -107,7 +173,7 @@ class StreamingAudioPlayer {
|
|
|
107
173
|
this.addChunk(chunk.data, chunk.isLast);
|
|
108
174
|
}
|
|
109
175
|
}
|
|
110
|
-
startPlayback() {
|
|
176
|
+
async startPlayback() {
|
|
111
177
|
if (!this.audioContext) {
|
|
112
178
|
this.log("[StreamingAudioPlayer] Cannot start playback: AudioContext not initialized");
|
|
113
179
|
return;
|
|
@@ -116,6 +182,7 @@ class StreamingAudioPlayer {
|
|
|
116
182
|
this.log("[StreamingAudioPlayer] Cannot start playback: Already playing");
|
|
117
183
|
return;
|
|
118
184
|
}
|
|
185
|
+
await this.ensureAudioContextRunning();
|
|
119
186
|
this.isPlaying = true;
|
|
120
187
|
this.sessionStartTime = this.audioContext.currentTime;
|
|
121
188
|
this.scheduledTime = this.sessionStartTime;
|
|
@@ -126,7 +193,8 @@ class StreamingAudioPlayer {
|
|
|
126
193
|
sessionStartTime: this.sessionStartTime,
|
|
127
194
|
bufferedChunks: this.audioChunks.length,
|
|
128
195
|
scheduledChunks: this.scheduledChunks,
|
|
129
|
-
activeSources: this.activeSources.size
|
|
196
|
+
activeSources: this.activeSources.size,
|
|
197
|
+
audioContextState: this.audioContext.state
|
|
130
198
|
});
|
|
131
199
|
this.scheduleAllChunks();
|
|
132
200
|
}
|
|
@@ -144,6 +212,11 @@ class StreamingAudioPlayer {
|
|
|
144
212
|
this.log("[StreamingAudioPlayer] Cannot schedule chunk: Not playing or paused");
|
|
145
213
|
return;
|
|
146
214
|
}
|
|
215
|
+
if (this.audioContext.state === "suspended") {
|
|
216
|
+
this.ensureAudioContextRunning().catch((err) => {
|
|
217
|
+
logger.errorWithError("[StreamingAudioPlayer] Failed to ensure AudioContext running in scheduleNextChunk:", err);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
147
220
|
const chunkIndex = this.scheduledChunks;
|
|
148
221
|
if (chunkIndex >= this.audioChunks.length) {
|
|
149
222
|
this.log(`[StreamingAudioPlayer] No more chunks to schedule (chunkIndex: ${chunkIndex}, totalChunks: ${this.audioChunks.length})`);
|
|
@@ -345,6 +418,7 @@ class StreamingAudioPlayer {
|
|
|
345
418
|
}
|
|
346
419
|
this.isPlaying = false;
|
|
347
420
|
this.isPaused = false;
|
|
421
|
+
this.isResuming = false;
|
|
348
422
|
this.sessionStartTime = 0;
|
|
349
423
|
this.scheduledTime = 0;
|
|
350
424
|
for (const source of this.activeSources) {
|
|
@@ -373,7 +447,9 @@ class StreamingAudioPlayer {
|
|
|
373
447
|
return;
|
|
374
448
|
}
|
|
375
449
|
this.autoStartEnabled = true;
|
|
376
|
-
this.startPlayback()
|
|
450
|
+
this.startPlayback().catch((err) => {
|
|
451
|
+
logger.errorWithError("[StreamingAudioPlayer] Failed to start playback from play():", err);
|
|
452
|
+
});
|
|
377
453
|
}
|
|
378
454
|
markEnded() {
|
|
379
455
|
var _a;
|
|
@@ -389,6 +465,10 @@ class StreamingAudioPlayer {
|
|
|
389
465
|
}
|
|
390
466
|
dispose() {
|
|
391
467
|
this.stop();
|
|
468
|
+
if (this.audioContext && this.stateChangeHandler) {
|
|
469
|
+
this.audioContext.removeEventListener("statechange", this.stateChangeHandler);
|
|
470
|
+
this.stateChangeHandler = void 0;
|
|
471
|
+
}
|
|
392
472
|
if (this.audioContext) {
|
|
393
473
|
this.audioContext.close();
|
|
394
474
|
this.audioContext = null;
|
|
@@ -27,8 +27,11 @@ export declare class StreamingAudioPlayer {
|
|
|
27
27
|
private gainNode;
|
|
28
28
|
private volume;
|
|
29
29
|
private onEndedCallback?;
|
|
30
|
+
private stateChangeHandler?;
|
|
31
|
+
private isResuming;
|
|
30
32
|
constructor(options?: StreamingAudioPlayerOptions);
|
|
31
33
|
initialize(): Promise<void>;
|
|
34
|
+
private ensureAudioContextRunning;
|
|
32
35
|
addChunk(pcmData: Uint8Array, isLast?: boolean): void;
|
|
33
36
|
startNewSession(audioChunks: Array<{
|
|
34
37
|
data: Uint8Array;
|
|
File without changes
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Environment } from '../types';
|
|
2
2
|
|
|
3
|
+
export declare const DEFAULT_SDK_CONFIG: Partial<Record<Environment, string>>;
|
|
4
|
+
|
|
3
5
|
export declare function fetchSdkConfig(version: string): Promise<Partial<Record<Environment, string>>>;
|
|
4
6
|
|
|
5
7
|
export declare function clearSdkConfigCache(): void;
|
|
@@ -27,6 +27,7 @@ export declare class AvatarView {
|
|
|
27
27
|
private idleCurrentFrameIndex;
|
|
28
28
|
private characterHandle;
|
|
29
29
|
private characterId;
|
|
30
|
+
private isPureRenderingMode;
|
|
30
31
|
private alignFlamePair;
|
|
31
32
|
private generateAndAlignTransitionFrames;
|
|
32
33
|
private getCachedIdleFirstFrame;
|
|
@@ -57,7 +58,8 @@ export declare class AvatarView {
|
|
|
57
58
|
private startRealtimeRendering;
|
|
58
59
|
private stopRealtimeRendering;
|
|
59
60
|
dispose(): void;
|
|
60
|
-
renderFlame(flame: Flame): Promise<void>;
|
|
61
|
+
renderFlame(flame: Flame, enableIdleRendering?: boolean): Promise<void>;
|
|
62
|
+
generateTransitionFromIdle(toFlame: Flame, frameCount: number): Promise<Flame[]>;
|
|
61
63
|
private rerenderCurrentFrameWithNewCamera;
|
|
62
64
|
private handleResize;
|
|
63
65
|
get transform(): {
|
|
@@ -7397,19 +7397,7 @@ let isInitialized = false;
|
|
|
7397
7397
|
const SDK_POSTHOG_INSTANCE_NAME = "spatialwalk-posthog";
|
|
7398
7398
|
let sdkPosthogInstance = null;
|
|
7399
7399
|
const eventQueue = [];
|
|
7400
|
-
const FILTERED_HOSTNAMES = ["localhost", "127.0.0.1", "0.0.0.0"];
|
|
7401
|
-
function shouldFilterHostname() {
|
|
7402
|
-
if (typeof window === "undefined") {
|
|
7403
|
-
return false;
|
|
7404
|
-
}
|
|
7405
|
-
const hostname = window.location.hostname;
|
|
7406
|
-
return FILTERED_HOSTNAMES.includes(hostname);
|
|
7407
|
-
}
|
|
7408
7400
|
function initializePostHog(environment, version) {
|
|
7409
|
-
if (shouldFilterHostname()) {
|
|
7410
|
-
logger.log(`[PostHog] Tracking disabled due to filtered hostname: ${window.location.hostname}`);
|
|
7411
|
-
return;
|
|
7412
|
-
}
|
|
7413
7401
|
const { host, apiKey, disableCompression } = getPostHogConfig(environment);
|
|
7414
7402
|
if (!apiKey) {
|
|
7415
7403
|
logger.warn(`[PostHog] API Key not configured for environment: ${environment}, tracking disabled`);
|
|
@@ -7425,7 +7413,6 @@ function initializePostHog(environment, version) {
|
|
|
7425
7413
|
const logContext = idManager.getLogContext();
|
|
7426
7414
|
Vo.init(apiKey, {
|
|
7427
7415
|
api_host: host,
|
|
7428
|
-
name: SDK_POSTHOG_INSTANCE_NAME,
|
|
7429
7416
|
person_profiles: "identified_only",
|
|
7430
7417
|
capture_pageview: false,
|
|
7431
7418
|
capture_pageleave: false,
|
|
@@ -7455,7 +7442,7 @@ function initializePostHog(environment, version) {
|
|
|
7455
7442
|
}
|
|
7456
7443
|
flushEventQueue();
|
|
7457
7444
|
}
|
|
7458
|
-
});
|
|
7445
|
+
}, SDK_POSTHOG_INSTANCE_NAME);
|
|
7459
7446
|
} catch (error) {
|
|
7460
7447
|
logger.warn("[PostHog] Failed to initialize:", error instanceof Error ? error.message : String(error));
|
|
7461
7448
|
}
|
|
@@ -7505,9 +7492,6 @@ function flushEventQueue() {
|
|
|
7505
7492
|
}, 0);
|
|
7506
7493
|
}
|
|
7507
7494
|
function trackEvent(event, level = "info", contents = {}) {
|
|
7508
|
-
if (shouldFilterHostname()) {
|
|
7509
|
-
return;
|
|
7510
|
-
}
|
|
7511
7495
|
const instance = getSdkPosthogInstance();
|
|
7512
7496
|
if (!instance) {
|
|
7513
7497
|
eventQueue.push({ event, level, contents });
|
|
@@ -7639,7 +7623,7 @@ const _AnimationPlayer = class _AnimationPlayer {
|
|
|
7639
7623
|
if (this.streamingPlayer) {
|
|
7640
7624
|
return;
|
|
7641
7625
|
}
|
|
7642
|
-
const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-
|
|
7626
|
+
const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-BbUBoPqm.js");
|
|
7643
7627
|
const { AvatarSDK: AvatarSDK2 } = await Promise.resolve().then(() => AvatarSDK$1);
|
|
7644
7628
|
const audioFormat = AvatarSDK2.getAudioFormat();
|
|
7645
7629
|
this.streamingPlayer = new StreamingAudioPlayer({
|
|
@@ -7786,6 +7770,10 @@ const _AnimationPlayer = class _AnimationPlayer {
|
|
|
7786
7770
|
};
|
|
7787
7771
|
__publicField(_AnimationPlayer, "audioUnlocked", false);
|
|
7788
7772
|
let AnimationPlayer = _AnimationPlayer;
|
|
7773
|
+
const DEFAULT_SDK_CONFIG = {
|
|
7774
|
+
[Environment.cn]: "https://api.open.spatialwalk.top",
|
|
7775
|
+
[Environment.intl]: "https://api.intl.spatialwalk.cloud"
|
|
7776
|
+
};
|
|
7789
7777
|
const configCache = {
|
|
7790
7778
|
config: null,
|
|
7791
7779
|
promise: null
|
|
@@ -7824,10 +7812,7 @@ async function fetchSdkConfig(version) {
|
|
|
7824
7812
|
configCache.config = config;
|
|
7825
7813
|
logger.log(`[SdkConfigLoader] SDK config fetched successfully:`, config);
|
|
7826
7814
|
} catch (error) {
|
|
7827
|
-
configCache.config = {
|
|
7828
|
-
[Environment.cn]: "https://api.open.spatialwalk.top",
|
|
7829
|
-
[Environment.intl]: "https://api.intl.spatialwalk.cloud"
|
|
7830
|
-
};
|
|
7815
|
+
configCache.config = { ...DEFAULT_SDK_CONFIG };
|
|
7831
7816
|
logger.log("[SdkConfigLoader] Using default SDK config:", configCache.config);
|
|
7832
7817
|
} finally {
|
|
7833
7818
|
configCache.promise = null;
|
|
@@ -8887,6 +8872,7 @@ class AvatarSDK {
|
|
|
8887
8872
|
idManager.setAppId(appId);
|
|
8888
8873
|
logger.log(`[AvatarSDK] Client ID: ${idManager.getClientId()}`);
|
|
8889
8874
|
initializePostHog(configuration.environment, this._version);
|
|
8875
|
+
this._dynamicSdkConfig = { ...DEFAULT_SDK_CONFIG };
|
|
8890
8876
|
this._fetchSdkConfig().catch((err) => {
|
|
8891
8877
|
const message = err instanceof Error ? err.message : String(err);
|
|
8892
8878
|
logger.warn("Failed to fetch SDK config from remote, using defaults:", message);
|
|
@@ -9062,7 +9048,7 @@ class AvatarSDK {
|
|
|
9062
9048
|
}
|
|
9063
9049
|
__publicField(AvatarSDK, "_isInitialized", false);
|
|
9064
9050
|
__publicField(AvatarSDK, "_configuration", null);
|
|
9065
|
-
__publicField(AvatarSDK, "_version", "1.0.0-beta.
|
|
9051
|
+
__publicField(AvatarSDK, "_version", "1.0.0-beta.59");
|
|
9066
9052
|
__publicField(AvatarSDK, "_avatarCore", null);
|
|
9067
9053
|
__publicField(AvatarSDK, "_dynamicSdkConfig", null);
|
|
9068
9054
|
const AvatarSDK$1 = Object.freeze(Object.defineProperty({
|
|
@@ -11419,7 +11405,6 @@ class AvatarController {
|
|
|
11419
11405
|
return true;
|
|
11420
11406
|
}
|
|
11421
11407
|
const audioContextState = ((_c = streamingPlayer.audioContext) == null ? void 0 : _c.state) || "unknown";
|
|
11422
|
-
const isAudioContextSuspended = audioContextState === "suspended";
|
|
11423
11408
|
const activeSourcesCount = ((_d = streamingPlayer.activeSources) == null ? void 0 : _d.size) || 0;
|
|
11424
11409
|
const scheduledChunks = streamingPlayer.scheduledChunks || 0;
|
|
11425
11410
|
const hasScheduledButNoActive = scheduledChunks > 0 && activeSourcesCount === 0;
|
|
@@ -11434,7 +11419,7 @@ class AvatarController {
|
|
|
11434
11419
|
state.audioTimeStuckCount = 0;
|
|
11435
11420
|
}
|
|
11436
11421
|
state.lastAudioTime = audioTime;
|
|
11437
|
-
const shouldReport = state.audioTimeZeroCount > this.MAX_AUDIO_TIME_ZERO_COUNT || state.audioTimeStuckCount > this.MAX_AUDIO_TIME_STUCK_COUNT ||
|
|
11422
|
+
const shouldReport = state.audioTimeZeroCount > this.MAX_AUDIO_TIME_ZERO_COUNT || state.audioTimeStuckCount > this.MAX_AUDIO_TIME_STUCK_COUNT || hasScheduledButNoActive && this.isPlaying;
|
|
11438
11423
|
if (shouldReport && isNotPaused) {
|
|
11439
11424
|
state.reported = true;
|
|
11440
11425
|
logEvent("character_player", "error", {
|
|
@@ -11442,7 +11427,6 @@ class AvatarController {
|
|
|
11442
11427
|
avatar_id: this.avatar.id,
|
|
11443
11428
|
conversationId: ((_e2 = this.networkLayer) == null ? void 0 : _e2.getCurrentConversationId()) || void 0,
|
|
11444
11429
|
audioContextState,
|
|
11445
|
-
isAudioContextSuspended,
|
|
11446
11430
|
audioTime,
|
|
11447
11431
|
audioTimeZero: audioTime === 0,
|
|
11448
11432
|
audioTimeZeroCount: state.audioTimeZeroCount,
|
|
@@ -13990,6 +13974,7 @@ class AvatarView {
|
|
|
13990
13974
|
__publicField(this, "idleCurrentFrameIndex", 0);
|
|
13991
13975
|
__publicField(this, "characterHandle", null);
|
|
13992
13976
|
__publicField(this, "characterId");
|
|
13977
|
+
__publicField(this, "isPureRenderingMode", false);
|
|
13993
13978
|
this.avatar = avatar;
|
|
13994
13979
|
this.characterId = `${avatar.id}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
13995
13980
|
if (!AvatarSDK.configuration) {
|
|
@@ -14335,6 +14320,10 @@ class AvatarView {
|
|
|
14335
14320
|
return;
|
|
14336
14321
|
}
|
|
14337
14322
|
lastTime = currentTime;
|
|
14323
|
+
if (this.isPureRenderingMode) {
|
|
14324
|
+
this.idleAnimationLoopId = requestAnimationFrame(renderFrame);
|
|
14325
|
+
return;
|
|
14326
|
+
}
|
|
14338
14327
|
const avatarCore = AvatarSDK.getAvatarCore();
|
|
14339
14328
|
if (!avatarCore) {
|
|
14340
14329
|
return;
|
|
@@ -14345,6 +14334,9 @@ class AvatarView {
|
|
|
14345
14334
|
if (this.renderingState !== "idle") {
|
|
14346
14335
|
return;
|
|
14347
14336
|
}
|
|
14337
|
+
if (this.isPureRenderingMode) {
|
|
14338
|
+
return;
|
|
14339
|
+
}
|
|
14348
14340
|
this.renderSystem.loadSplatsFromPackedData(splatData);
|
|
14349
14341
|
this.renderSystem.renderFrame();
|
|
14350
14342
|
}
|
|
@@ -14683,10 +14675,15 @@ class AvatarView {
|
|
|
14683
14675
|
}
|
|
14684
14676
|
}
|
|
14685
14677
|
}
|
|
14686
|
-
async renderFlame(flame) {
|
|
14678
|
+
async renderFlame(flame, enableIdleRendering) {
|
|
14687
14679
|
if (!this.isInitialized || !this.renderSystem) {
|
|
14688
14680
|
throw new Error("AvatarView not initialized");
|
|
14689
14681
|
}
|
|
14682
|
+
if (enableIdleRendering === true) {
|
|
14683
|
+
this.isPureRenderingMode = false;
|
|
14684
|
+
return;
|
|
14685
|
+
}
|
|
14686
|
+
this.isPureRenderingMode = true;
|
|
14690
14687
|
try {
|
|
14691
14688
|
const processedFlame = this.avatarController.applyPostProcessingToFlame(flame);
|
|
14692
14689
|
const wasmParams = convertProtoFlameToWasmParams(processedFlame);
|
|
@@ -14707,6 +14704,38 @@ class AvatarView {
|
|
|
14707
14704
|
throw error;
|
|
14708
14705
|
}
|
|
14709
14706
|
}
|
|
14707
|
+
async generateTransitionFromIdle(toFlame, frameCount) {
|
|
14708
|
+
if (!this.isInitialized) {
|
|
14709
|
+
throw new Error("AvatarView not initialized");
|
|
14710
|
+
}
|
|
14711
|
+
if (frameCount <= 0) {
|
|
14712
|
+
throw new Error("Frame count must be greater than 0");
|
|
14713
|
+
}
|
|
14714
|
+
const avatarCore = AvatarSDK.getAvatarCore();
|
|
14715
|
+
if (!avatarCore) {
|
|
14716
|
+
throw new Error("AvatarCore not available");
|
|
14717
|
+
}
|
|
14718
|
+
try {
|
|
14719
|
+
const idleParams = await avatarCore.getCurrentFrameParams(this.idleCurrentFrameIndex, this.characterId);
|
|
14720
|
+
const idleFrameProto = convertWasmParamsToProtoFlame(idleParams);
|
|
14721
|
+
const toFlameWithPostProcessing = this.avatarController.applyPostProcessingToFlame(toFlame);
|
|
14722
|
+
const aligned = this.alignFlamePair(idleFrameProto, toFlameWithPostProcessing);
|
|
14723
|
+
const fps = APP_CONFIG.animation.fps;
|
|
14724
|
+
const durationMs = frameCount / fps * 1e3;
|
|
14725
|
+
const transitionFrames = generateTransitionFrames(
|
|
14726
|
+
aligned.from,
|
|
14727
|
+
aligned.to,
|
|
14728
|
+
durationMs,
|
|
14729
|
+
fps
|
|
14730
|
+
);
|
|
14731
|
+
transitionFrames[0] = aligned.from;
|
|
14732
|
+
transitionFrames[transitionFrames.length - 1] = aligned.to;
|
|
14733
|
+
return transitionFrames;
|
|
14734
|
+
} catch (error) {
|
|
14735
|
+
logger.error("[AvatarView] Failed to generate transition from idle:", error instanceof Error ? error.message : String(error));
|
|
14736
|
+
throw error;
|
|
14737
|
+
}
|
|
14738
|
+
}
|
|
14710
14739
|
async rerenderCurrentFrameWithNewCamera() {
|
|
14711
14740
|
if (this.avatarController.state !== AvatarState.paused || this.renderingState !== "speaking" || !this.renderSystem) {
|
|
14712
14741
|
return;
|
|
@@ -14755,7 +14784,7 @@ export {
|
|
|
14755
14784
|
LogLevel$1 as L,
|
|
14756
14785
|
ResourceType as R,
|
|
14757
14786
|
SPAvatarError as S,
|
|
14758
|
-
|
|
14787
|
+
logEvent as a,
|
|
14759
14788
|
Avatar as b,
|
|
14760
14789
|
AvatarController as c,
|
|
14761
14790
|
AvatarSDK as d,
|
|
@@ -14766,6 +14795,6 @@ export {
|
|
|
14766
14795
|
ConversationState as i,
|
|
14767
14796
|
AvatarState as j,
|
|
14768
14797
|
ErrorCode as k,
|
|
14769
|
-
|
|
14798
|
+
logger as l,
|
|
14770
14799
|
extractResourceUrls as m
|
|
14771
14800
|
};
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spatialwalk/avatarkit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
5
|
-
"packageManager": "pnpm@10.18.2",
|
|
4
|
+
"version": "1.0.0-beta.59",
|
|
6
5
|
"description": "SPAvatar SDK - 3D Gaussian Splatting Avatar Rendering SDK",
|
|
7
6
|
"author": "SPAvatar Team",
|
|
8
7
|
"license": "MIT",
|
|
@@ -32,16 +31,6 @@
|
|
|
32
31
|
"CHANGELOG.md",
|
|
33
32
|
"dist"
|
|
34
33
|
],
|
|
35
|
-
"scripts": {
|
|
36
|
-
"build": "SDK_BUILD=true vite build --mode library",
|
|
37
|
-
"dev": "vite build --mode library --watch",
|
|
38
|
-
"clean": "rm -rf dist",
|
|
39
|
-
"typecheck": "tsc --noEmit",
|
|
40
|
-
"test": "cd tests && pnpm test",
|
|
41
|
-
"test:watch": "cd tests && pnpm run test:watch",
|
|
42
|
-
"test:e2e": "cd tests && pnpm run test:e2e",
|
|
43
|
-
"test:perf": "cd tests && pnpm run test:perf"
|
|
44
|
-
},
|
|
45
34
|
"peerDependencies": {
|
|
46
35
|
"@webgpu/types": "*"
|
|
47
36
|
},
|
|
@@ -58,5 +47,15 @@
|
|
|
58
47
|
"typescript": "^5.0.0",
|
|
59
48
|
"vite": "^5.0.0",
|
|
60
49
|
"vite-plugin-dts": "^4.5.4"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "SDK_BUILD=true vite build --mode library",
|
|
53
|
+
"dev": "vite build --mode library --watch",
|
|
54
|
+
"clean": "rm -rf dist",
|
|
55
|
+
"typecheck": "tsc --noEmit",
|
|
56
|
+
"test": "cd tests && pnpm test",
|
|
57
|
+
"test:watch": "cd tests && pnpm run test:watch",
|
|
58
|
+
"test:e2e": "cd tests && pnpm run test:e2e",
|
|
59
|
+
"test:perf": "cd tests && pnpm run test:perf"
|
|
61
60
|
}
|
|
62
|
-
}
|
|
61
|
+
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { AvatarView, Flame } from '@spatialwalk/avatarkit';
|
|
2
|
-
declare function initializeSDK(): Promise<void>;
|
|
3
|
-
declare function createAvatarView(characterId: string, container: HTMLElement): Promise<AvatarView>;
|
|
4
|
-
declare function fetchFlameDataFromServer(characterId: string, audioData: ArrayBuffer): Promise<Flame[]>;
|
|
5
|
-
declare class FlameRenderer {
|
|
6
|
-
private avatarView;
|
|
7
|
-
private keyframes;
|
|
8
|
-
private currentFrameIndex;
|
|
9
|
-
private animationFrameId;
|
|
10
|
-
private isPlaying;
|
|
11
|
-
private fps;
|
|
12
|
-
private frameInterval;
|
|
13
|
-
private lastRenderTime;
|
|
14
|
-
constructor(avatarView: AvatarView);
|
|
15
|
-
setKeyframes(keyframes: Flame[]): void;
|
|
16
|
-
start(): void;
|
|
17
|
-
stop(): void;
|
|
18
|
-
renderFrame(frameIndex: number): Promise<void>;
|
|
19
|
-
private renderLoop;
|
|
20
|
-
setFPS(fps: number): void;
|
|
21
|
-
seek(frameIndex: number): void;
|
|
22
|
-
getCurrentFrameIndex(): number;
|
|
23
|
-
getTotalFrames(): number;
|
|
24
|
-
}
|
|
25
|
-
declare function main(): Promise<void>;
|
|
26
|
-
declare function fetchAudioData(): Promise<ArrayBuffer>;
|
|
27
|
-
|
|
28
|
-
export { initializeSDK, createAvatarView, fetchFlameDataFromServer, FlameRenderer, main, fetchAudioData };
|