@spatialwalk/avatarkit 1.0.0-beta.91 → 1.0.0-beta.92
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 +6 -0
- package/dist/{StreamingAudioPlayer-CGUA8-w0.js → StreamingAudioPlayer-eQ2RRq5U.js} +1 -1
- package/dist/core/AvatarController.d.ts +2 -1
- package/dist/{index-BYr_FIpm.js → index-C2higMlc.js} +41 -29
- package/dist/index.js +1 -1
- package/package.json +2 -1
- package/dist/demo/src/main.d.ts +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.0-beta.92]
|
|
9
|
+
|
|
10
|
+
### ✨ Features
|
|
11
|
+
|
|
12
|
+
- **Host Mode Animation End Signal** — `yieldFramesData()` now returns a `boolean` indicating whether the server has sent all animation data for the current conversation (`end` flag from protobuf). Non-breaking: callers that ignore the return value are unaffected.
|
|
13
|
+
|
|
8
14
|
## [1.0.0-beta.91] - 2026-03-19
|
|
9
15
|
|
|
10
16
|
### 🐛 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, l as logger, e as errorToMessage, a as logEvent } from "./index-
|
|
4
|
+
import { A as APP_CONFIG, l as logger, e as errorToMessage, a as logEvent } from "./index-C2higMlc.js";
|
|
5
5
|
class StreamingAudioPlayer {
|
|
6
6
|
// Mark if AudioContext is being resumed, avoid concurrent resume requests
|
|
7
7
|
constructor(options) {
|
|
@@ -91,8 +91,9 @@ export declare class AvatarController {
|
|
|
91
91
|
* @param keyframesDataArray - Animation keyframes binary data array (each element is a protobuf encoded Message) or empty array to trigger audio-only mode
|
|
92
92
|
* @param conversationId - Conversation ID (required). If conversationId doesn't match current conversationId, keyframes will be discarded.
|
|
93
93
|
* Use getCurrentConversationId() to get the current conversationId.
|
|
94
|
+
* @returns `true` if the server has sent all animation data for this conversation (end signal received), `false` otherwise.
|
|
94
95
|
*/
|
|
95
|
-
yieldFramesData(keyframesDataArray: (Uint8Array | ArrayBuffer)[], conversationId: string):
|
|
96
|
+
yieldFramesData(keyframesDataArray: (Uint8Array | ArrayBuffer)[], conversationId: string): boolean;
|
|
96
97
|
/**
|
|
97
98
|
* Send animation keyframes (host mode)
|
|
98
99
|
* Stream animation keyframes data after yieldAudioData()
|
|
@@ -9495,7 +9495,7 @@ const _AnimationPlayer = class _AnimationPlayer {
|
|
|
9495
9495
|
if (this.streamingPlayer) {
|
|
9496
9496
|
return;
|
|
9497
9497
|
}
|
|
9498
|
-
const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-
|
|
9498
|
+
const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-eQ2RRq5U.js");
|
|
9499
9499
|
const { AvatarSDK: AvatarSDK2 } = await Promise.resolve().then(() => AvatarSDK$1);
|
|
9500
9500
|
const audioFormat = AvatarSDK2.getAudioFormat();
|
|
9501
9501
|
this.streamingPlayer = new StreamingAudioPlayer({
|
|
@@ -11473,7 +11473,7 @@ class AvatarSDK {
|
|
|
11473
11473
|
__publicField(AvatarSDK, "_initializationState", "uninitialized");
|
|
11474
11474
|
__publicField(AvatarSDK, "_initializingPromise", null);
|
|
11475
11475
|
__publicField(AvatarSDK, "_configuration", null);
|
|
11476
|
-
__publicField(AvatarSDK, "_version", "1.0.0-beta.
|
|
11476
|
+
__publicField(AvatarSDK, "_version", "1.0.0-beta.92");
|
|
11477
11477
|
__publicField(AvatarSDK, "_avatarCore", null);
|
|
11478
11478
|
__publicField(AvatarSDK, "_dynamicSdkConfig", null);
|
|
11479
11479
|
__publicField(AvatarSDK, "_cachedDeviceScore", null);
|
|
@@ -11904,6 +11904,8 @@ class NetworkLayer {
|
|
|
11904
11904
|
__publicField(this, "audioMetrics", this.createAudioMetrics());
|
|
11905
11905
|
__publicField(this, "isFallbackMode", false);
|
|
11906
11906
|
// 连接超时降级模式标记
|
|
11907
|
+
/** @internal 降级原因,供 AvatarController 读取 */
|
|
11908
|
+
__publicField(this, "_fallbackReason", "");
|
|
11907
11909
|
__publicField(this, "isConnecting", false);
|
|
11908
11910
|
// 避免并发连接
|
|
11909
11911
|
// 驱动服务心跳检测相关
|
|
@@ -11943,6 +11945,7 @@ class NetworkLayer {
|
|
|
11943
11945
|
}
|
|
11944
11946
|
(_b = (_a = this.dataController).onConnectionState) == null ? void 0 : _b.call(_a, ConnectionState.connecting);
|
|
11945
11947
|
this.isFallbackMode = false;
|
|
11948
|
+
this._fallbackReason = "";
|
|
11946
11949
|
this.setupWebSocketListeners();
|
|
11947
11950
|
this.isConnecting = true;
|
|
11948
11951
|
const CONNECTION_TIMEOUT_MS = 15e3;
|
|
@@ -11953,6 +11956,7 @@ class NetworkLayer {
|
|
|
11953
11956
|
var _a2, _b2;
|
|
11954
11957
|
if (!this.dataController.connected) {
|
|
11955
11958
|
this.isFallbackMode = true;
|
|
11959
|
+
this._fallbackReason = "connection_timeout";
|
|
11956
11960
|
logger.warn(`[NetworkLayer] WebSocket connection timeout (${CONNECTION_TIMEOUT_MS}ms) - entering fallback mode`);
|
|
11957
11961
|
(_b2 = (_a2 = this.dataController).onConnectionState) == null ? void 0 : _b2.call(_a2, ConnectionState.failed);
|
|
11958
11962
|
}
|
|
@@ -11970,6 +11974,7 @@ class NetworkLayer {
|
|
|
11970
11974
|
}
|
|
11971
11975
|
if (!this.isFallbackMode) {
|
|
11972
11976
|
this.isFallbackMode = true;
|
|
11977
|
+
this._fallbackReason = "connection_failed";
|
|
11973
11978
|
logger.warn("[NetworkLayer] WebSocket connection failed - entering fallback mode");
|
|
11974
11979
|
}
|
|
11975
11980
|
(_d = (_c = this.dataController).onConnectionState) == null ? void 0 : _d.call(_c, ConnectionState.failed);
|
|
@@ -12034,6 +12039,7 @@ class NetworkLayer {
|
|
|
12034
12039
|
*/
|
|
12035
12040
|
disconnect() {
|
|
12036
12041
|
this.isFallbackMode = false;
|
|
12042
|
+
this._fallbackReason = "";
|
|
12037
12043
|
this.isConnecting = false;
|
|
12038
12044
|
this.wsClient.removeAllListeners();
|
|
12039
12045
|
this.wsClient.disconnect();
|
|
@@ -12069,6 +12075,7 @@ class NetworkLayer {
|
|
|
12069
12075
|
}
|
|
12070
12076
|
if (!this.dataController.connected) {
|
|
12071
12077
|
this.isFallbackMode = false;
|
|
12078
|
+
this._fallbackReason = "";
|
|
12072
12079
|
this.dataController.setConnected(true);
|
|
12073
12080
|
(_b = (_a = this.dataController).onConnectionState) == null ? void 0 : _b.call(_a, ConnectionState.connected);
|
|
12074
12081
|
}
|
|
@@ -12665,7 +12672,7 @@ class AvatarController {
|
|
|
12665
12672
|
case AvatarState.idle:
|
|
12666
12673
|
if (this.shouldReportPlaybackStats) {
|
|
12667
12674
|
const stats = this.frameRateMonitor.getPlaybackStats();
|
|
12668
|
-
if (stats.durationMs >= 2e3) {
|
|
12675
|
+
if (stats.durationMs >= 2e3 && stats.frameCount > 0 && Math.random() < 0.3) {
|
|
12669
12676
|
const props = {
|
|
12670
12677
|
avg_fps: Number(stats.avgFps.toFixed(1)),
|
|
12671
12678
|
frame_count: stats.frameCount,
|
|
@@ -12788,7 +12795,7 @@ class AvatarController {
|
|
|
12788
12795
|
* @returns conversationId - Conversation ID for this audio session
|
|
12789
12796
|
*/
|
|
12790
12797
|
send(audioData, end = false) {
|
|
12791
|
-
var _a, _b
|
|
12798
|
+
var _a, _b;
|
|
12792
12799
|
try {
|
|
12793
12800
|
this.checkAudioContextInitialized();
|
|
12794
12801
|
} catch (error) {
|
|
@@ -12799,14 +12806,6 @@ class AvatarController {
|
|
|
12799
12806
|
(_b = this.onError) == null ? void 0 : _b.call(this, new AvatarError("Network layer not available", "NETWORK_LAYER_NOT_AVAILABLE"));
|
|
12800
12807
|
return null;
|
|
12801
12808
|
}
|
|
12802
|
-
if (!this.networkLayer.canSend()) {
|
|
12803
|
-
(_c = this.onError) == null ? void 0 : _c.call(this, new AvatarError("Service not connected", "NOT_CONNECTED"));
|
|
12804
|
-
logEvent("character_manager", "warning", {
|
|
12805
|
-
avatar_id: this.avatar.id,
|
|
12806
|
-
event: "send_not_connected"
|
|
12807
|
-
});
|
|
12808
|
-
return null;
|
|
12809
|
-
}
|
|
12810
12809
|
const networkConversationId = this.networkLayer.getCurrentConversationId();
|
|
12811
12810
|
if (this.reqEnd && this.isPlaying && networkConversationId) {
|
|
12812
12811
|
this.interrupt();
|
|
@@ -12865,12 +12864,7 @@ class AvatarController {
|
|
|
12865
12864
|
this.emit("keyframesUpdate", this.currentKeyframes);
|
|
12866
12865
|
} else {
|
|
12867
12866
|
logger.warn("[AvatarController] Empty animation data in playback - enabling fallback mode");
|
|
12868
|
-
|
|
12869
|
-
avatar_id: this.avatar.id,
|
|
12870
|
-
event: "empty_animation_data_audio_only_fallback",
|
|
12871
|
-
conversationId: this.currentConversationId
|
|
12872
|
-
});
|
|
12873
|
-
this.isFallbackMode = true;
|
|
12867
|
+
this.enableFallbackMode("empty_animation");
|
|
12874
12868
|
}
|
|
12875
12869
|
if (this.pendingAudioChunks.length === 0) {
|
|
12876
12870
|
throw new AvatarError("No audio chunks to play", "NO_AUDIO");
|
|
@@ -12943,14 +12937,16 @@ class AvatarController {
|
|
|
12943
12937
|
* @param keyframesDataArray - Animation keyframes binary data array (each element is a protobuf encoded Message) or empty array to trigger audio-only mode
|
|
12944
12938
|
* @param conversationId - Conversation ID (required). If conversationId doesn't match current conversationId, keyframes will be discarded.
|
|
12945
12939
|
* Use getCurrentConversationId() to get the current conversationId.
|
|
12940
|
+
* @returns `true` if the server has sent all animation data for this conversation (end signal received), `false` otherwise.
|
|
12946
12941
|
*/
|
|
12947
12942
|
yieldFramesData(keyframesDataArray, conversationId) {
|
|
12948
|
-
var _a, _b;
|
|
12943
|
+
var _a, _b, _c;
|
|
12949
12944
|
if (!keyframesDataArray || keyframesDataArray.length === 0) {
|
|
12950
12945
|
this.yieldKeyframes([], conversationId);
|
|
12951
|
-
return;
|
|
12946
|
+
return false;
|
|
12952
12947
|
}
|
|
12953
12948
|
let allKeyframes = [];
|
|
12949
|
+
let isEnd = false;
|
|
12954
12950
|
for (let i2 = 0; i2 < keyframesDataArray.length; i2++) {
|
|
12955
12951
|
const item = keyframesDataArray[i2];
|
|
12956
12952
|
const binaryData = item instanceof ArrayBuffer ? new Uint8Array(item) : item;
|
|
@@ -12966,6 +12962,9 @@ class AvatarController {
|
|
|
12966
12962
|
} else {
|
|
12967
12963
|
logger.warn(`[AvatarController] Message chunk ${i2 + 1} does not contain animation keyframes`);
|
|
12968
12964
|
}
|
|
12965
|
+
if ((_c = message.serverResponseAnimation) == null ? void 0 : _c.end) {
|
|
12966
|
+
isEnd = true;
|
|
12967
|
+
}
|
|
12969
12968
|
} catch (error) {
|
|
12970
12969
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
12971
12970
|
logger.error(`[AvatarController] Failed to decode animation data chunk ${i2 + 1}: ${errorMessage}`);
|
|
@@ -12983,6 +12982,7 @@ class AvatarController {
|
|
|
12983
12982
|
logger.warn(`[AvatarController] No keyframes decoded from ${keyframesDataArray.length} message chunks`);
|
|
12984
12983
|
}
|
|
12985
12984
|
this.yieldKeyframes(allKeyframes, conversationId);
|
|
12985
|
+
return isEnd;
|
|
12986
12986
|
}
|
|
12987
12987
|
/**
|
|
12988
12988
|
* Send animation keyframes (host mode)
|
|
@@ -12997,6 +12997,7 @@ class AvatarController {
|
|
|
12997
12997
|
* Use getCurrentConversationId() to get the current conversationId.
|
|
12998
12998
|
*/
|
|
12999
12999
|
yieldKeyframes(keyframes, conversationId) {
|
|
13000
|
+
var _a;
|
|
13000
13001
|
if (!conversationId || typeof conversationId !== "string") {
|
|
13001
13002
|
logger.error(`[AvatarController] yieldKeyframes requires a valid conversationId. Use getCurrentConversationId() to get the current conversationId.`);
|
|
13002
13003
|
logEvent("character_manager", "error", {
|
|
@@ -13028,12 +13029,8 @@ class AvatarController {
|
|
|
13028
13029
|
}
|
|
13029
13030
|
if (!keyframes || keyframes.length === 0) {
|
|
13030
13031
|
logger.warn("[AvatarController] Empty animation data received - enabling fallback mode");
|
|
13031
|
-
|
|
13032
|
-
|
|
13033
|
-
event: "empty_animation_data_audio_only_fallback",
|
|
13034
|
-
conversationId
|
|
13035
|
-
});
|
|
13036
|
-
this.enableFallbackMode();
|
|
13032
|
+
const reason = ((_a = this.networkLayer) == null ? void 0 : _a._fallbackReason) || "empty_animation";
|
|
13033
|
+
this.enableFallbackMode(reason);
|
|
13037
13034
|
return;
|
|
13038
13035
|
}
|
|
13039
13036
|
const flameKeyframes = keyframes;
|
|
@@ -13049,10 +13046,10 @@ class AvatarController {
|
|
|
13049
13046
|
this.emit("keyframesUpdate", this.currentKeyframes);
|
|
13050
13047
|
if (!this.isPlaying && !this.isStartingPlayback && this.pendingAudioChunks.length > 0 && this.currentKeyframes.length > 0) {
|
|
13051
13048
|
this.startStreamingPlayback().catch((error) => {
|
|
13052
|
-
var
|
|
13049
|
+
var _a2;
|
|
13053
13050
|
this.isStartingPlayback = false;
|
|
13054
13051
|
logger.error("[AvatarController] Failed to auto-start playback:", error);
|
|
13055
|
-
(
|
|
13052
|
+
(_a2 = this.onError) == null ? void 0 : _a2.call(this, new AvatarError("Failed to start playback", "PLAYBACK_START_FAILED"));
|
|
13056
13053
|
});
|
|
13057
13054
|
}
|
|
13058
13055
|
}
|
|
@@ -13704,12 +13701,18 @@ class AvatarController {
|
|
|
13704
13701
|
* Once enabled, subsequent animation data in this session will be ignored
|
|
13705
13702
|
* @internal
|
|
13706
13703
|
*/
|
|
13707
|
-
enableFallbackMode() {
|
|
13704
|
+
enableFallbackMode(reason = "empty_animation") {
|
|
13708
13705
|
if (this.isFallbackMode) {
|
|
13709
13706
|
return;
|
|
13710
13707
|
}
|
|
13711
13708
|
logger.warn("[AvatarController] Enabling fallback mode");
|
|
13712
13709
|
this.isFallbackMode = true;
|
|
13710
|
+
logEvent("fallback_mode_entered", "warning", {
|
|
13711
|
+
avatar_id: this.avatar.id,
|
|
13712
|
+
reason,
|
|
13713
|
+
con_id: idManager.getConnectionId() || "",
|
|
13714
|
+
req_id: this.currentConversationId || ""
|
|
13715
|
+
});
|
|
13713
13716
|
if (this.isPlaying) {
|
|
13714
13717
|
this.stopPlaybackLoop();
|
|
13715
13718
|
this.emit("stopRendering");
|
|
@@ -14479,6 +14482,9 @@ class AvatarDownloader {
|
|
|
14479
14482
|
return { key, success: true, size: arrayBuffer.byteLength };
|
|
14480
14483
|
} catch (error) {
|
|
14481
14484
|
if (error instanceof Error && (error.name === "AbortError" || error.message === "Download cancelled")) {
|
|
14485
|
+
logEvent("download_avatar_assets_cancelled", "info", {
|
|
14486
|
+
avatar_id: characterMeta.characterId ?? "unknown"
|
|
14487
|
+
});
|
|
14482
14488
|
throw error;
|
|
14483
14489
|
}
|
|
14484
14490
|
if (!optional) {
|
|
@@ -14659,6 +14665,12 @@ class AvatarDownloader {
|
|
|
14659
14665
|
});
|
|
14660
14666
|
return response;
|
|
14661
14667
|
} catch (error) {
|
|
14668
|
+
if (error instanceof Error && (error.name === "AbortError" || error.message === "Request cancelled")) {
|
|
14669
|
+
logEvent("fetch_avatar_metadata_cancelled", "info", {
|
|
14670
|
+
avatar_id: characterId ?? "unknown"
|
|
14671
|
+
});
|
|
14672
|
+
throw error;
|
|
14673
|
+
}
|
|
14662
14674
|
logger.errorWithError("Failed to fetch character:", error);
|
|
14663
14675
|
if (error instanceof AvatarError) {
|
|
14664
14676
|
logEvent("fetch_avatar_metadata_failed", "error", {
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spatialwalk/avatarkit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.92",
|
|
5
5
|
"packageManager": "pnpm@10.18.2",
|
|
6
6
|
"description": "AvatarKit SDK - 3D Gaussian Splatting Avatar Rendering SDK",
|
|
7
7
|
"author": "AvatarKit Team",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"dev": "vite build --mode library --watch",
|
|
52
52
|
"demo": "vite --config demo/vite.config.mjs",
|
|
53
53
|
"demo:benchmark": "vite --config benchmark-demo/vite.config.mjs",
|
|
54
|
+
"test:integration": "vite --config tests/integration-runner/vite.config.mjs",
|
|
54
55
|
"clean": "rm -rf dist",
|
|
55
56
|
"typecheck": "tsc --noEmit",
|
|
56
57
|
"test": "cd tests && pnpm test",
|
package/dist/demo/src/main.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|