@spatialwalk/avatarkit 1.0.0-beta.98 → 1.0.0-beta.99

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,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.99]
9
+
10
+ ### ✨ Features
11
+
12
+ - **Playback diagnostics** — Added telemetry events for audio buffer stall and frame starvation detection, improving visibility into playback interruption issues.
13
+
8
14
  ## [1.0.0-beta.98]
9
15
 
10
16
  ### 🐛 Bugfixes
package/README.md CHANGED
@@ -716,8 +716,8 @@ const conversationId = avatarView.controller.getCurrentConversationId()
716
716
  // Returns: Current conversationId for the active audio session, or null if no active session
717
717
 
718
718
  // Volume control (affects only avatar audio player, not system volume)
719
- avatarView.controller.volume = 0.5 // Set volume to 50% (0.0 to 1.0)
720
- const currentVolume = avatarView.controller.volume // Get current volume (0.0 to 1.0)
719
+ avatarView.controller.setVolume(0.5) // Set volume to 50% (0.0 to 1.0)
720
+ const currentVolume = avatarView.controller.getVolume() // Get current volume (0.0 to 1.0)
721
721
 
722
722
  // Set event callbacks
723
723
  avatarView.controller.onConnectionState = (state: ConnectionState) => {} // SDK mode only
@@ -745,7 +745,7 @@ avatarView.avatarTransform = { x: 0.5, y: 0, scale: 2.0 } // Right half, double
745
745
  **Important Notes:**
746
746
  - `start()` and `close()` are only available in SDK mode
747
747
  - `yieldAudioData()` and `yieldFramesData()` are only available in Host mode
748
- - `pause()`, `resume()`, `interrupt()`, `clear()`, `getCurrentConversationId()`, and `volume` property are available in both modes
748
+ - `pause()`, `resume()`, `interrupt()`, `clear()`, `getCurrentConversationId()`, `setVolume()`, and `getVolume()` are available in both modes
749
749
  - The playback mode is determined when creating `AvatarView` and cannot be changed
750
750
 
751
751
  ## 🔧 Configuration
@@ -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-DfE9mnfH.js";
4
+ import { A as APP_CONFIG, l as logger, e as errorToMessage, a as logEvent } from "./index-CnV_uOSq.js";
5
5
  class StreamingAudioPlayer {
6
6
  // Mark if AudioContext is being resumed, avoid concurrent resume requests
7
7
  constructor(options) {
@@ -305,6 +305,11 @@ class StreamingAudioPlayer {
305
305
  const lastChunk = this.audioChunks[this.scheduledChunks - 1];
306
306
  if (lastChunk && !lastChunk.isLast) {
307
307
  this.log("All audio chunks ended but end=false, pausing and setting autoContinue");
308
+ logEvent("character_player", "warning", {
309
+ event: "audio_buffer_stall",
310
+ scheduled_chunks: this.scheduledChunks,
311
+ audio_time: this.getCurrentTime()
312
+ });
308
313
  this.autoContinue = true;
309
314
  this.pause();
310
315
  } else if (isLast) {
@@ -31,6 +31,8 @@ export declare class AvatarController {
31
31
  private lastSyncLogTime;
32
32
  private lastOutOfBoundsState;
33
33
  private isFallbackMode;
34
+ private frameStarvationEvents;
35
+ private isFrameStarved;
34
36
  private playbackStuckCheckState;
35
37
  private readonly MAX_AUDIO_TIME_ZERO_COUNT;
36
38
  private readonly MAX_AUDIO_TIME_STUCK_COUNT;
@@ -138,12 +140,14 @@ export declare class AvatarController {
138
140
  */
139
141
  setPostProcessingConfig(config: PostProcessingConfig | null): void;
140
142
  /**
141
- * Audio playback volume (0.0 - 1.0)
142
- *
143
- * @example
144
- * avatarView.controller.volume = 0.5
145
- * const current = avatarView.controller.volume
143
+ * Set audio playback volume
144
+ * Note: This only controls the avatar audio player volume, not the system volume
145
+ * @param volume Volume value, range from 0.0 to 1.0 (0.0 = mute, 1.0 = max volume)
146
+ */
147
+ setVolume(volume: number): void;
148
+ /**
149
+ * Get current audio playback volume
150
+ * @returns Current volume value (0.0 - 1.0)
146
151
  */
147
- get volume(): number;
148
- set volume(value: number);
152
+ getVolume(): number;
149
153
  }
@@ -9510,7 +9510,7 @@ const _AnimationPlayer = class _AnimationPlayer {
9510
9510
  if (this.streamingPlayer) {
9511
9511
  return;
9512
9512
  }
9513
- const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-DEELveJ_.js");
9513
+ const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-CLmftu1c.js");
9514
9514
  const { AvatarSDK: AvatarSDK2 } = await Promise.resolve().then(() => AvatarSDK$1);
9515
9515
  const audioFormat = AvatarSDK2.getAudioFormat();
9516
9516
  this.streamingPlayer = new StreamingAudioPlayer({
@@ -11531,7 +11531,7 @@ class AvatarSDK {
11531
11531
  __publicField(AvatarSDK, "_initializationState", "uninitialized");
11532
11532
  __publicField(AvatarSDK, "_initializingPromise", null);
11533
11533
  __publicField(AvatarSDK, "_configuration", null);
11534
- __publicField(AvatarSDK, "_version", "1.0.0-beta.97");
11534
+ __publicField(AvatarSDK, "_version", "1.0.0-beta.99");
11535
11535
  __publicField(AvatarSDK, "_avatarCore", null);
11536
11536
  __publicField(AvatarSDK, "_dynamicSdkConfig", null);
11537
11537
  __publicField(AvatarSDK, "_cachedDeviceScore", null);
@@ -12626,6 +12626,9 @@ class AvatarController {
12626
12626
  // ========== Fallback Mode ==========
12627
12627
  __publicField(this, "isFallbackMode", false);
12628
12628
  // 降级模式标志
12629
+ // ========== Frame Starvation Tracking ==========
12630
+ __publicField(this, "frameStarvationEvents", []);
12631
+ __publicField(this, "isFrameStarved", false);
12629
12632
  // ========== Playback Stuck Detection ==========
12630
12633
  __publicField(this, "playbackStuckCheckState", {
12631
12634
  audioTimeZeroCount: 0,
@@ -13426,29 +13429,31 @@ class AvatarController {
13426
13429
  }
13427
13430
  }
13428
13431
  /**
13429
- * Audio playback volume (0.0 - 1.0)
13430
- *
13431
- * @example
13432
- * avatarView.controller.volume = 0.5
13433
- * const current = avatarView.controller.volume
13432
+ * Set audio playback volume
13433
+ * Note: This only controls the avatar audio player volume, not the system volume
13434
+ * @param volume Volume value, range from 0.0 to 1.0 (0.0 = mute, 1.0 = max volume)
13434
13435
  */
13435
- get volume() {
13436
- var _a;
13437
- return ((_a = this.animationPlayer) == null ? void 0 : _a.getVolume()) ?? 1;
13438
- }
13439
- set volume(value) {
13436
+ setVolume(volume) {
13440
13437
  var _a;
13441
- if (value < 0 || value > 1) {
13442
- logger.warn(`[AvatarController] Volume out of range: ${value}, clamping to [0, 1]`);
13443
- value = Math.max(0, Math.min(1, value));
13438
+ if (volume < 0 || volume > 1) {
13439
+ logger.warn(`[AvatarController] Volume out of range: ${volume}, clamping to [0, 1]`);
13440
+ volume = Math.max(0, Math.min(1, volume));
13444
13441
  }
13445
- (_a = this.animationPlayer) == null ? void 0 : _a.setVolume(value);
13442
+ (_a = this.animationPlayer) == null ? void 0 : _a.setVolume(volume);
13446
13443
  logEvent("character_player", "info", {
13447
13444
  avatar_id: this.avatar.id,
13448
13445
  event: "volume_changed",
13449
- volume: value
13446
+ volume
13450
13447
  });
13451
13448
  }
13449
+ /**
13450
+ * Get current audio playback volume
13451
+ * @returns Current volume value (0.0 - 1.0)
13452
+ */
13453
+ getVolume() {
13454
+ var _a;
13455
+ return ((_a = this.animationPlayer) == null ? void 0 : _a.getVolume()) ?? 1;
13456
+ }
13452
13457
  /**
13453
13458
  * Provide interface for AvatarView to register internal events
13454
13459
  * @internal
@@ -13493,11 +13498,27 @@ class AvatarController {
13493
13498
  throw new AvatarError("Animation player not initialized", ErrorCode.animationPlayerNotInitialized);
13494
13499
  }
13495
13500
  await this.animationPlayer.prepareStreamingPlayer(() => {
13496
- var _a2;
13501
+ var _a2, _b2;
13497
13502
  this.isPlaying = false;
13498
13503
  this.currentState = AvatarState.idle;
13499
13504
  this.notifyConversationState(AvatarState.idle);
13500
13505
  this.emit("stopRendering");
13506
+ if (this.frameStarvationEvents.length > 0) {
13507
+ logEvent("character_player", "warning", {
13508
+ event: "frame_starvation",
13509
+ avatar_id: this.avatar.id,
13510
+ conversationId: ((_a2 = this.networkLayer) == null ? void 0 : _a2.getCurrentConversationId()) || void 0,
13511
+ starvation_count: this.frameStarvationEvents.length,
13512
+ starvation_times: this.frameStarvationEvents.map((e2) => Number(e2.audioTime.toFixed(3)))
13513
+ });
13514
+ }
13515
+ this.frameStarvationEvents = [];
13516
+ this.isFrameStarved = false;
13517
+ logEvent("character_player", "info", {
13518
+ avatar_id: this.avatar.id,
13519
+ event: "playback_ended",
13520
+ conversationId: ((_b2 = this.networkLayer) == null ? void 0 : _b2.getCurrentConversationId()) || void 0
13521
+ });
13501
13522
  this.clearPlaybackData();
13502
13523
  if (this.networkLayer) {
13503
13524
  this.networkLayer.resetConversationId();
@@ -13506,11 +13527,6 @@ class AvatarController {
13506
13527
  }
13507
13528
  this.reqEnd = false;
13508
13529
  this.isFallbackMode = false;
13509
- logEvent("character_player", "info", {
13510
- avatar_id: this.avatar.id,
13511
- event: "playback_ended",
13512
- conversationId: ((_a2 = this.networkLayer) == null ? void 0 : _a2.getCurrentConversationId()) || void 0
13513
- });
13514
13530
  });
13515
13531
  this.emit("startRendering");
13516
13532
  const streamingPlayer = this.animationPlayer.getStreamingPlayer();
@@ -13628,6 +13644,7 @@ class AvatarController {
13628
13644
  * @internal
13629
13645
  */
13630
13646
  async computeAndRenderFrame(frameIndex, loopGeneration) {
13647
+ var _a;
13631
13648
  let arrayIndex = frameIndex - this.keyframesOffset;
13632
13649
  if (arrayIndex < 0) {
13633
13650
  arrayIndex = 0;
@@ -13643,8 +13660,16 @@ class AvatarController {
13643
13660
  this.lastSyncLogTime = now;
13644
13661
  this.lastOutOfBoundsState = isOutOfBounds;
13645
13662
  }
13646
- } else if (isOutOfBounds !== this.lastOutOfBoundsState) {
13647
- this.lastOutOfBoundsState = isOutOfBounds;
13663
+ if (!this.isFrameStarved) {
13664
+ this.isFrameStarved = true;
13665
+ const audioTime = ((_a = this.animationPlayer) == null ? void 0 : _a.getCurrentTime()) ?? 0;
13666
+ this.frameStarvationEvents.push({ audioTime });
13667
+ }
13668
+ } else {
13669
+ this.isFrameStarved = false;
13670
+ if (isOutOfBounds !== this.lastOutOfBoundsState) {
13671
+ this.lastOutOfBoundsState = isOutOfBounds;
13672
+ }
13648
13673
  }
13649
13674
  if (this.currentKeyframes.length > this.MAX_KEYFRAMES) {
13650
13675
  const framesToKeep = this.KEYFRAMES_CLEANUP_THRESHOLD;
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-DfE9mnfH.js";
1
+ import { b, c, m, f, d, j, g, C, i, D, E, k, h, L, R, n } from "./index-CnV_uOSq.js";
2
2
  export {
3
3
  b as Avatar,
4
4
  c as AvatarController,
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.98",
4
+ "version": "1.0.0-beta.99",
5
5
  "packageManager": "pnpm@10.18.2",
6
6
  "description": "AvatarKit SDK - 3D Gaussian Splatting Avatar Rendering SDK",
7
7
  "author": "AvatarKit Team",