@spatialwalk/avatarkit 1.0.0-beta.90 → 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 CHANGED
@@ -5,6 +5,18 @@ 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
+
14
+ ## [1.0.0-beta.91] - 2026-03-19
15
+
16
+ ### 🐛 Bugfixes
17
+
18
+ - **Inline CPU Benchmark Worker** — Consumers using Vite could not build: `Could not resolve entry module "public/assets/cpu-benchmark-worker-*.js"`. Root cause: `new URL()` worker pattern in Vite library mode compiles to absolute path that downstream bundlers cannot resolve. Fixed by replacing with inline Blob URL worker.
19
+
8
20
  ## [1.0.0-beta.90] - 2026-03-18
9
21
 
10
22
  ### 🐛 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-jdCd5L22.js";
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) {
File without changes
@@ -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): void;
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-GS4x7i1m.js");
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({
@@ -11054,24 +11054,44 @@ async function gpuBenchmark() {
11054
11054
  return 0;
11055
11055
  }
11056
11056
  }
11057
+ const workerCode = `
11058
+ const N = 128;
11059
+ const A = new Float32Array(N * N);
11060
+ const B = new Float32Array(N * N);
11061
+ const C = new Float32Array(N * N);
11062
+ for (let i = 0; i < A.length; i++) { A[i] = (i % 100) / 100; B[i] = ((i * 7) % 100) / 100; }
11063
+ function bench() {
11064
+ for (let i = 0; i < N; i++)
11065
+ for (let j = 0; j < N; j++) {
11066
+ let s = 0;
11067
+ for (let k = 0; k < N; k++) s += A[i * N + k] * B[k * N + j];
11068
+ C[i * N + j] = s;
11069
+ }
11070
+ }
11071
+ self.onmessage = () => {
11072
+ const w = performance.now() + 1000;
11073
+ while (performance.now() < w) bench();
11074
+ let it = 0;
11075
+ const m = performance.now() + 1000;
11076
+ while (performance.now() < m) { bench(); it++; }
11077
+ self.postMessage({ cpuScore: it });
11078
+ };
11079
+ `;
11057
11080
  function cpuBenchmark() {
11058
11081
  return new Promise((resolve2) => {
11059
11082
  try {
11060
- const worker = new Worker(
11061
- new URL(
11062
- /* @vite-ignore */
11063
- "/assets/cpu-benchmark-worker-C6iFEUSO.js",
11064
- import.meta.url
11065
- ),
11066
- { type: "module" }
11067
- );
11083
+ const blob = new Blob([workerCode], { type: "application/javascript" });
11084
+ const url = URL.createObjectURL(blob);
11085
+ const worker = new Worker(url, { type: "classic" });
11068
11086
  worker.onmessage = (e2) => {
11069
11087
  resolve2(e2.data.cpuScore);
11070
11088
  worker.terminate();
11089
+ URL.revokeObjectURL(url);
11071
11090
  };
11072
11091
  worker.onerror = () => {
11073
11092
  resolve2(0);
11074
11093
  worker.terminate();
11094
+ URL.revokeObjectURL(url);
11075
11095
  };
11076
11096
  worker.postMessage("start");
11077
11097
  } catch {
@@ -11453,7 +11473,7 @@ class AvatarSDK {
11453
11473
  __publicField(AvatarSDK, "_initializationState", "uninitialized");
11454
11474
  __publicField(AvatarSDK, "_initializingPromise", null);
11455
11475
  __publicField(AvatarSDK, "_configuration", null);
11456
- __publicField(AvatarSDK, "_version", "1.0.0-beta.90");
11476
+ __publicField(AvatarSDK, "_version", "1.0.0-beta.92");
11457
11477
  __publicField(AvatarSDK, "_avatarCore", null);
11458
11478
  __publicField(AvatarSDK, "_dynamicSdkConfig", null);
11459
11479
  __publicField(AvatarSDK, "_cachedDeviceScore", null);
@@ -11884,6 +11904,8 @@ class NetworkLayer {
11884
11904
  __publicField(this, "audioMetrics", this.createAudioMetrics());
11885
11905
  __publicField(this, "isFallbackMode", false);
11886
11906
  // 连接超时降级模式标记
11907
+ /** @internal 降级原因,供 AvatarController 读取 */
11908
+ __publicField(this, "_fallbackReason", "");
11887
11909
  __publicField(this, "isConnecting", false);
11888
11910
  // 避免并发连接
11889
11911
  // 驱动服务心跳检测相关
@@ -11923,6 +11945,7 @@ class NetworkLayer {
11923
11945
  }
11924
11946
  (_b = (_a = this.dataController).onConnectionState) == null ? void 0 : _b.call(_a, ConnectionState.connecting);
11925
11947
  this.isFallbackMode = false;
11948
+ this._fallbackReason = "";
11926
11949
  this.setupWebSocketListeners();
11927
11950
  this.isConnecting = true;
11928
11951
  const CONNECTION_TIMEOUT_MS = 15e3;
@@ -11933,6 +11956,7 @@ class NetworkLayer {
11933
11956
  var _a2, _b2;
11934
11957
  if (!this.dataController.connected) {
11935
11958
  this.isFallbackMode = true;
11959
+ this._fallbackReason = "connection_timeout";
11936
11960
  logger.warn(`[NetworkLayer] WebSocket connection timeout (${CONNECTION_TIMEOUT_MS}ms) - entering fallback mode`);
11937
11961
  (_b2 = (_a2 = this.dataController).onConnectionState) == null ? void 0 : _b2.call(_a2, ConnectionState.failed);
11938
11962
  }
@@ -11950,6 +11974,7 @@ class NetworkLayer {
11950
11974
  }
11951
11975
  if (!this.isFallbackMode) {
11952
11976
  this.isFallbackMode = true;
11977
+ this._fallbackReason = "connection_failed";
11953
11978
  logger.warn("[NetworkLayer] WebSocket connection failed - entering fallback mode");
11954
11979
  }
11955
11980
  (_d = (_c = this.dataController).onConnectionState) == null ? void 0 : _d.call(_c, ConnectionState.failed);
@@ -12014,6 +12039,7 @@ class NetworkLayer {
12014
12039
  */
12015
12040
  disconnect() {
12016
12041
  this.isFallbackMode = false;
12042
+ this._fallbackReason = "";
12017
12043
  this.isConnecting = false;
12018
12044
  this.wsClient.removeAllListeners();
12019
12045
  this.wsClient.disconnect();
@@ -12049,6 +12075,7 @@ class NetworkLayer {
12049
12075
  }
12050
12076
  if (!this.dataController.connected) {
12051
12077
  this.isFallbackMode = false;
12078
+ this._fallbackReason = "";
12052
12079
  this.dataController.setConnected(true);
12053
12080
  (_b = (_a = this.dataController).onConnectionState) == null ? void 0 : _b.call(_a, ConnectionState.connected);
12054
12081
  }
@@ -12645,7 +12672,7 @@ class AvatarController {
12645
12672
  case AvatarState.idle:
12646
12673
  if (this.shouldReportPlaybackStats) {
12647
12674
  const stats = this.frameRateMonitor.getPlaybackStats();
12648
- if (stats.durationMs >= 2e3) {
12675
+ if (stats.durationMs >= 2e3 && stats.frameCount > 0 && Math.random() < 0.3) {
12649
12676
  const props = {
12650
12677
  avg_fps: Number(stats.avgFps.toFixed(1)),
12651
12678
  frame_count: stats.frameCount,
@@ -12768,7 +12795,7 @@ class AvatarController {
12768
12795
  * @returns conversationId - Conversation ID for this audio session
12769
12796
  */
12770
12797
  send(audioData, end = false) {
12771
- var _a, _b, _c;
12798
+ var _a, _b;
12772
12799
  try {
12773
12800
  this.checkAudioContextInitialized();
12774
12801
  } catch (error) {
@@ -12779,14 +12806,6 @@ class AvatarController {
12779
12806
  (_b = this.onError) == null ? void 0 : _b.call(this, new AvatarError("Network layer not available", "NETWORK_LAYER_NOT_AVAILABLE"));
12780
12807
  return null;
12781
12808
  }
12782
- if (!this.networkLayer.canSend()) {
12783
- (_c = this.onError) == null ? void 0 : _c.call(this, new AvatarError("Service not connected", "NOT_CONNECTED"));
12784
- logEvent("character_manager", "warning", {
12785
- avatar_id: this.avatar.id,
12786
- event: "send_not_connected"
12787
- });
12788
- return null;
12789
- }
12790
12809
  const networkConversationId = this.networkLayer.getCurrentConversationId();
12791
12810
  if (this.reqEnd && this.isPlaying && networkConversationId) {
12792
12811
  this.interrupt();
@@ -12845,12 +12864,7 @@ class AvatarController {
12845
12864
  this.emit("keyframesUpdate", this.currentKeyframes);
12846
12865
  } else {
12847
12866
  logger.warn("[AvatarController] Empty animation data in playback - enabling fallback mode");
12848
- logEvent("character_manager", "info", {
12849
- avatar_id: this.avatar.id,
12850
- event: "empty_animation_data_audio_only_fallback",
12851
- conversationId: this.currentConversationId
12852
- });
12853
- this.isFallbackMode = true;
12867
+ this.enableFallbackMode("empty_animation");
12854
12868
  }
12855
12869
  if (this.pendingAudioChunks.length === 0) {
12856
12870
  throw new AvatarError("No audio chunks to play", "NO_AUDIO");
@@ -12923,14 +12937,16 @@ class AvatarController {
12923
12937
  * @param keyframesDataArray - Animation keyframes binary data array (each element is a protobuf encoded Message) or empty array to trigger audio-only mode
12924
12938
  * @param conversationId - Conversation ID (required). If conversationId doesn't match current conversationId, keyframes will be discarded.
12925
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.
12926
12941
  */
12927
12942
  yieldFramesData(keyframesDataArray, conversationId) {
12928
- var _a, _b;
12943
+ var _a, _b, _c;
12929
12944
  if (!keyframesDataArray || keyframesDataArray.length === 0) {
12930
12945
  this.yieldKeyframes([], conversationId);
12931
- return;
12946
+ return false;
12932
12947
  }
12933
12948
  let allKeyframes = [];
12949
+ let isEnd = false;
12934
12950
  for (let i2 = 0; i2 < keyframesDataArray.length; i2++) {
12935
12951
  const item = keyframesDataArray[i2];
12936
12952
  const binaryData = item instanceof ArrayBuffer ? new Uint8Array(item) : item;
@@ -12946,6 +12962,9 @@ class AvatarController {
12946
12962
  } else {
12947
12963
  logger.warn(`[AvatarController] Message chunk ${i2 + 1} does not contain animation keyframes`);
12948
12964
  }
12965
+ if ((_c = message.serverResponseAnimation) == null ? void 0 : _c.end) {
12966
+ isEnd = true;
12967
+ }
12949
12968
  } catch (error) {
12950
12969
  const errorMessage = error instanceof Error ? error.message : String(error);
12951
12970
  logger.error(`[AvatarController] Failed to decode animation data chunk ${i2 + 1}: ${errorMessage}`);
@@ -12963,6 +12982,7 @@ class AvatarController {
12963
12982
  logger.warn(`[AvatarController] No keyframes decoded from ${keyframesDataArray.length} message chunks`);
12964
12983
  }
12965
12984
  this.yieldKeyframes(allKeyframes, conversationId);
12985
+ return isEnd;
12966
12986
  }
12967
12987
  /**
12968
12988
  * Send animation keyframes (host mode)
@@ -12977,6 +12997,7 @@ class AvatarController {
12977
12997
  * Use getCurrentConversationId() to get the current conversationId.
12978
12998
  */
12979
12999
  yieldKeyframes(keyframes, conversationId) {
13000
+ var _a;
12980
13001
  if (!conversationId || typeof conversationId !== "string") {
12981
13002
  logger.error(`[AvatarController] yieldKeyframes requires a valid conversationId. Use getCurrentConversationId() to get the current conversationId.`);
12982
13003
  logEvent("character_manager", "error", {
@@ -13008,12 +13029,8 @@ class AvatarController {
13008
13029
  }
13009
13030
  if (!keyframes || keyframes.length === 0) {
13010
13031
  logger.warn("[AvatarController] Empty animation data received - enabling fallback mode");
13011
- logEvent("character_manager", "info", {
13012
- avatar_id: this.avatar.id,
13013
- event: "empty_animation_data_audio_only_fallback",
13014
- conversationId
13015
- });
13016
- this.enableFallbackMode();
13032
+ const reason = ((_a = this.networkLayer) == null ? void 0 : _a._fallbackReason) || "empty_animation";
13033
+ this.enableFallbackMode(reason);
13017
13034
  return;
13018
13035
  }
13019
13036
  const flameKeyframes = keyframes;
@@ -13029,10 +13046,10 @@ class AvatarController {
13029
13046
  this.emit("keyframesUpdate", this.currentKeyframes);
13030
13047
  if (!this.isPlaying && !this.isStartingPlayback && this.pendingAudioChunks.length > 0 && this.currentKeyframes.length > 0) {
13031
13048
  this.startStreamingPlayback().catch((error) => {
13032
- var _a;
13049
+ var _a2;
13033
13050
  this.isStartingPlayback = false;
13034
13051
  logger.error("[AvatarController] Failed to auto-start playback:", error);
13035
- (_a = this.onError) == null ? void 0 : _a.call(this, new AvatarError("Failed to start playback", "PLAYBACK_START_FAILED"));
13052
+ (_a2 = this.onError) == null ? void 0 : _a2.call(this, new AvatarError("Failed to start playback", "PLAYBACK_START_FAILED"));
13036
13053
  });
13037
13054
  }
13038
13055
  }
@@ -13684,12 +13701,18 @@ class AvatarController {
13684
13701
  * Once enabled, subsequent animation data in this session will be ignored
13685
13702
  * @internal
13686
13703
  */
13687
- enableFallbackMode() {
13704
+ enableFallbackMode(reason = "empty_animation") {
13688
13705
  if (this.isFallbackMode) {
13689
13706
  return;
13690
13707
  }
13691
13708
  logger.warn("[AvatarController] Enabling fallback mode");
13692
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
+ });
13693
13716
  if (this.isPlaying) {
13694
13717
  this.stopPlaybackLoop();
13695
13718
  this.emit("stopRendering");
@@ -14459,6 +14482,9 @@ class AvatarDownloader {
14459
14482
  return { key, success: true, size: arrayBuffer.byteLength };
14460
14483
  } catch (error) {
14461
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
+ });
14462
14488
  throw error;
14463
14489
  }
14464
14490
  if (!optional) {
@@ -14639,6 +14665,12 @@ class AvatarDownloader {
14639
14665
  });
14640
14666
  return response;
14641
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
+ }
14642
14674
  logger.errorWithError("Failed to fetch character:", error);
14643
14675
  if (error instanceof AvatarError) {
14644
14676
  logEvent("fetch_avatar_metadata_failed", "error", {
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { b, c, m, f, d, j, g, C, i, D, E, k, h, L, R, n } from "./index-jdCd5L22.js";
1
+ import { b, c, m, f, d, j, g, C, i, D, E, k, h, L, R, n } from "./index-C2higMlc.js";
2
2
  export {
3
3
  b as Avatar,
4
4
  c as AvatarController,
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@spatialwalk/avatarkit",
3
3
  "type": "module",
4
- "version": "1.0.0-beta.90",
4
+ "version": "1.0.0-beta.92",
5
+ "packageManager": "pnpm@10.18.2",
5
6
  "description": "AvatarKit SDK - 3D Gaussian Splatting Avatar Rendering SDK",
6
7
  "author": "AvatarKit Team",
7
8
  "license": "MIT",
@@ -43,6 +44,21 @@
43
44
  "next.js",
44
45
  "next.d.ts"
45
46
  ],
47
+ "scripts": {
48
+ "build": "SDK_BUILD=true vite build --mode library && npm run build:vite-plugin && npm run build:next-plugin",
49
+ "build:vite-plugin": "tsc vite.ts --outDir . --module esnext --target es2020 --moduleResolution bundler --esModuleInterop --skipLibCheck --declaration --declarationMap",
50
+ "build:next-plugin": "tsc next.ts --outDir . --module esnext --target es2020 --moduleResolution bundler --esModuleInterop --skipLibCheck --declaration --declarationMap",
51
+ "dev": "vite build --mode library --watch",
52
+ "demo": "vite --config demo/vite.config.mjs",
53
+ "demo:benchmark": "vite --config benchmark-demo/vite.config.mjs",
54
+ "test:integration": "vite --config tests/integration-runner/vite.config.mjs",
55
+ "clean": "rm -rf dist",
56
+ "typecheck": "tsc --noEmit",
57
+ "test": "cd tests && pnpm test",
58
+ "test:watch": "cd tests && pnpm run test:watch",
59
+ "test:e2e": "cd tests && pnpm run test:e2e",
60
+ "test:perf": "cd tests && pnpm run test:perf"
61
+ },
46
62
  "peerDependencies": {
47
63
  "@webgpu/types": "*",
48
64
  "vite": "^5.0.0",
@@ -69,19 +85,5 @@
69
85
  "typescript": "^5.0.0",
70
86
  "vite": "^5.0.0",
71
87
  "vite-plugin-dts": "^4.5.4"
72
- },
73
- "scripts": {
74
- "build": "SDK_BUILD=true vite build --mode library && npm run build:vite-plugin && npm run build:next-plugin",
75
- "build:vite-plugin": "tsc vite.ts --outDir . --module esnext --target es2020 --moduleResolution bundler --esModuleInterop --skipLibCheck --declaration --declarationMap",
76
- "build:next-plugin": "tsc next.ts --outDir . --module esnext --target es2020 --moduleResolution bundler --esModuleInterop --skipLibCheck --declaration --declarationMap",
77
- "dev": "vite build --mode library --watch",
78
- "demo": "vite --config demo/vite.config.mjs",
79
- "demo:benchmark": "vite --config benchmark-demo/vite.config.mjs",
80
- "clean": "rm -rf dist",
81
- "typecheck": "tsc --noEmit",
82
- "test": "cd tests && pnpm test",
83
- "test:watch": "cd tests && pnpm run test:watch",
84
- "test:e2e": "cd tests && pnpm run test:e2e",
85
- "test:perf": "cd tests && pnpm run test:perf"
86
88
  }
87
- }
89
+ }
@@ -1,36 +0,0 @@
1
- (function() {
2
- "use strict";
3
- const MATRIX_SIZE = 128;
4
- const A = new Float32Array(MATRIX_SIZE * MATRIX_SIZE);
5
- const B = new Float32Array(MATRIX_SIZE * MATRIX_SIZE);
6
- const C = new Float32Array(MATRIX_SIZE * MATRIX_SIZE);
7
- for (let i = 0; i < A.length; i++) {
8
- A[i] = i % 100 / 100;
9
- B[i] = i * 7 % 100 / 100;
10
- }
11
- function benchmarkMatrixMultiply() {
12
- const N = MATRIX_SIZE;
13
- for (let i = 0; i < N; i++) {
14
- for (let j = 0; j < N; j++) {
15
- let sum = 0;
16
- for (let k = 0; k < N; k++) {
17
- sum += A[i * N + k] * B[k * N + j];
18
- }
19
- C[i * N + j] = sum;
20
- }
21
- }
22
- }
23
- self.onmessage = () => {
24
- const warmupEnd = performance.now() + 1e3;
25
- while (performance.now() < warmupEnd) {
26
- benchmarkMatrixMultiply();
27
- }
28
- let iterations = 0;
29
- const measureEnd = performance.now() + 1e3;
30
- while (performance.now() < measureEnd) {
31
- benchmarkMatrixMultiply();
32
- iterations++;
33
- }
34
- self.postMessage({ cpuScore: iterations });
35
- };
36
- })();
@@ -1 +0,0 @@
1
- export {};