@spatialwalk/avatarkit 1.0.0-beta.91 → 1.0.0-beta.93

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,24 @@ 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.93]
9
+
10
+ ### 🐛 Bugfixes
11
+
12
+ - **WebGPU Tab Switch Rendering Fix** — Fixed avatar rendering corruption (split/misaligned) when switching back to a tab. Root cause: WebGPU context was not reconfigured after canvas resize, causing surface size and screenSize mismatch in the shader.
13
+
14
+ ### ✨ Features
15
+
16
+ - **WebSocket Close Code Handling** — Server-defined custom close codes (4010 auth failure, 4001 insufficient balance, 4002 session timeout, 4003 concurrent limit) are now properly recognized and mapped to `ErrorCode`. Non-recoverable errors (4010/4001/4003) no longer trigger reconnection attempts.
17
+ - **Error Transparency** — All non-normal WebSocket close events are now propagated to `onError` callback and reported to PostHog telemetry. Previously, some close codes (e.g. 1006) were only logged internally without notifying the application.
18
+ - **New ErrorCode Values** — Added `insufficientBalance`, `sessionTimeout`, `concurrentLimitExceeded` to the `ErrorCode` enum.
19
+
20
+ ## [1.0.0-beta.92]
21
+
22
+ ### ✨ Features
23
+
24
+ - **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.
25
+
8
26
  ## [1.0.0-beta.91] - 2026-03-19
9
27
 
10
28
  ### 🐛 Bugfixes
package/README.md CHANGED
@@ -234,9 +234,10 @@ const configuration: Configuration = {
234
234
  // - LogLevel.error: Only error logs
235
235
  // - LogLevel.warning: Warning and error logs
236
236
  // - LogLevel.all: All logs (info, warning, error)
237
- audioFormat: { // Optional, default is { channelCount: 1, sampleRate: 16000 }
237
+ audioFormat: { // Default is { channelCount: 1, sampleRate: 16000 }
238
238
  channelCount: 1, // Fixed to 1 (mono)
239
239
  sampleRate: 16000 // Supported: 8000, 16000, 22050, 24000, 32000, 44100, 48000 Hz
240
+ // ⚠️ Must match your actual audio sample rate. Mismatched sample rate will cause playback issues.
240
241
  }
241
242
  // characterApiBaseUrl: 'https://custom-api.example.com' // Optional, internal debug config, can be ignored
242
243
  }
@@ -269,9 +270,18 @@ button.addEventListener('click', async () => {
269
270
  await avatarView.controller.initializeAudioContext()
270
271
 
271
272
  // 5. Start real-time communication (SDK mode only)
273
+ // Note: start() initiates the WebSocket connection asynchronously.
274
+ // Wait for onConnectionState === 'connected' before calling send().
272
275
  await avatarView.controller.start()
273
-
274
- // 6. Send audio data (SDK mode, must be mono PCM16 format matching configured sample rate)
276
+
277
+ // 6. Wait for connection to be ready
278
+ await new Promise<void>((resolve) => {
279
+ avatarView.controller.onConnectionState = (state) => {
280
+ if (state === ConnectionState.connected) resolve()
281
+ }
282
+ })
283
+
284
+ // 7. Send audio data (SDK mode, must be mono PCM16 format matching configured sample rate)
275
285
  // audioData: ArrayBuffer or Uint8Array containing PCM16 audio samples
276
286
  // - PCM files: Can be directly read as ArrayBuffer
277
287
  // - WAV files: Extract PCM data from WAV format (may require resampling)
@@ -592,21 +602,25 @@ avatarView.transform = { x, y, scale }
592
602
  avatarView.dispose()
593
603
  ```
594
604
 
595
- **Avatar Switching Example:**
605
+ **Switching Avatars:**
606
+
607
+ To switch avatars, dispose the old view and create a new one. Do NOT attempt to reuse or reset an existing AvatarView.
608
+ - `AvatarSDK.initialize()` and session token do not need to be called again.
609
+ - The old AvatarView's internal state is fully cleaned up by `dispose()`.
596
610
 
597
611
  ```typescript
598
- // To switch avatars, simply dispose the old view and create a new one
612
+ // 1. Dispose old avatar
599
613
  if (currentAvatarView) {
600
614
  currentAvatarView.dispose()
601
615
  }
602
616
 
603
- // Load new avatar
604
- const newAvatar = await avatarManager.load('new-character-id')
617
+ // 2. Load new avatar (SDK is already initialized, token is still valid)
618
+ const newAvatar = await AvatarManager.shared.load('new-character-id')
605
619
 
606
- // Create new AvatarView
620
+ // 3. Create new AvatarView
607
621
  currentAvatarView = new AvatarView(newAvatar, container)
608
622
 
609
- // SDK mode: start connection (will throw error if not in SDK mode)
623
+ // 4. Start connection if SDK mode
610
624
  await currentAvatarView.controller.start()
611
625
  ```
612
626
 
@@ -634,7 +648,7 @@ button.addEventListener('click', async () => {
634
648
  const conversationId = avatarView.controller.send(audioData: ArrayBuffer, end: boolean)
635
649
  // Returns: conversationId - Conversation ID for this conversation session
636
650
  // end: false (default) - Continue sending audio data for current conversation
637
- // end: true - Mark the end of current conversation round. After end=true, sending new audio data will interrupt any ongoing playback from the previous conversation round
651
+ // end: true - Mark the end of audio input for current conversation round. The avatar will continue playing remaining animation until finished, then automatically return to idle (notified via onConversationState). After end=true, sending new audio data will interrupt any ongoing playback from the previous conversation round
638
652
  })
639
653
 
640
654
  // Close service
@@ -706,7 +720,7 @@ const currentVolume = avatarView.controller.getVolume() // Get current volume (
706
720
  // Set event callbacks
707
721
  avatarView.controller.onConnectionState = (state: ConnectionState) => {} // SDK mode only
708
722
  avatarView.controller.onConversationState = (state: ConversationState) => {}
709
- avatarView.controller.onError = (error: Error) => {} // Usually AvatarError (includes code for SDK/server errors)
723
+ avatarView.controller.onError = (error: AvatarError) => {} // Includes error.code for specific error type
710
724
  ```
711
725
 
712
726
  #### Avatar Transform Methods
@@ -866,23 +880,41 @@ try {
866
880
  ```typescript
867
881
  import { AvatarError } from '@spatialwalk/avatarkit'
868
882
 
869
- avatarView.controller.onError = (error: Error) => {
870
- if (error instanceof AvatarError) {
871
- console.error('AvatarController error:', error.message, error.code)
872
- return
873
- }
874
-
875
- console.error('AvatarController unknown error:', error)
883
+ avatarView.controller.onError = (error: AvatarError) => {
884
+ console.error('Error:', error.code, error.message)
876
885
  }
877
886
  ```
878
887
 
879
- In SDK mode, server `MESSAGE_SERVER_ERROR` is forwarded to `onError` as `AvatarError`:
880
- - `error.message`: server-returned error message
881
- - `error.code` mapping:
882
- - `401` -> `sessionTokenExpired`
883
- - `400` -> `sessionTokenInvalid`
884
- - `404` -> `avatarIDUnrecognized`
885
- - other HTTP status -> original status code string (for example, `"500"`)
888
+ `error.code` values (from `ErrorCode` enum):
889
+
890
+ | Code | Description | Trigger |
891
+ |------|-------------|---------|
892
+ | **Authentication & Authorization** | | |
893
+ | `appIDUnrecognized` | App ID not recognized | Reserved |
894
+ | `sessionTokenInvalid` | Token invalid or appId mismatch | WebSocket close code 4010 |
895
+ | `sessionTokenExpired` | Token expired | WebSocket close code 4010 |
896
+ | `insufficientBalance` | Insufficient balance | WebSocket close code 4001 |
897
+ | `concurrentLimitExceeded` | Concurrent connection limit exceeded | WebSocket close code 4003 |
898
+ | **Resource Loading** | | |
899
+ | `avatarIDUnrecognized` | Avatar ID not found | Server error |
900
+ | `failedToFetchAvatarMetadata` | Metadata fetch failed | Network/server error |
901
+ | `failedToDownloadAvatarAssets` | Asset download failed | Network/server error |
902
+ | **Connection** | | |
903
+ | `websocketError` | WebSocket handshake or network error | Connection failure |
904
+ | `websocketClosedAbnormally` | Connection closed abnormally | Close code 1006 |
905
+ | `websocketClosedUnexpected` | Unexpected close code | Unknown close code |
906
+ | `sessionTimeout` | Session timeout | WebSocket close code 4002 |
907
+ | `connectionInProgress` | Connection already in progress | Duplicate `start()` call |
908
+ | **Playback** | | |
909
+ | `networkLayerNotAvailable` | Network layer not available | `send()` in host mode |
910
+ | `playbackStartFailed` | Failed to start playback | Internal error |
911
+ | `playbackInitFailed` | Playback initialization failed | Internal error |
912
+ | `audioOnlyInitFailed` | Audio-only playback init failed | Fallback mode error |
913
+ | `noAudio` | No audio data to play | Empty audio input |
914
+ | `audioContextNotInitialized` | Audio context not initialized | `send()` before `initializeAudioContext()` |
915
+ | `animationPlayerNotInitialized` | Animation player not initialized | Internal error |
916
+ | **Server** | | |
917
+ | `serverError` | Server-side error | Server MESSAGE_SERVER_ERROR |
886
918
 
887
919
  ## 🔄 Resource Management
888
920
 
@@ -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-BYr_FIpm.js";
4
+ import { A as APP_CONFIG, l as logger, e as errorToMessage, a as logEvent } from "./index-CDywZ8iv.js";
5
5
  class StreamingAudioPlayer {
6
6
  // Mark if AudioContext is being resumed, avoid concurrent resume requests
7
7
  constructor(options) {