@spatius/avatarkit 1.0.0 → 1.1.0-beta.1

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,27 @@ 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.1.0-beta.1] - 2026-06-08
9
+
10
+ First 1.1 pre-release. Includes breaking changes — see below.
11
+
12
+ ### Added
13
+ - Render quality tiers (`standard` / `high` / `ultra`), selectable at init via the `renderQuality` configuration option or at runtime via `setRenderQuality()`.
14
+ - Render output resolution cap, to bound rendering cost on high-DPI displays.
15
+ - `Configuration.region` is now part of the public API, so consumers can target non-default regions (e.g. `cn`).
16
+
17
+ ### Changed
18
+ - Unified audio playback path with automatic recovery after a fallback.
19
+ - Smoother RTC-mode speak→idle transition with bezier end interpolation.
20
+
21
+ ### Fixed
22
+ - Fixed unfriendly avatar-load error messages that could expose internal request details.
23
+ - Fixed `Configuration.region` being stripped from the published type declarations, which prevented TypeScript consumers from setting the region.
24
+
25
+ ### Breaking Changes
26
+ - `DrivingServiceMode` values renamed from `sdk` / `host` to `direct` / `backend`.
27
+ - Removed the `eyefocus` option (no longer supported in `CharacterSettings` or post-processing configuration).
28
+
8
29
  ## [1.0.0] - 2026-05-17
9
30
 
10
31
  First stable release.
package/README.md CHANGED
@@ -55,7 +55,7 @@ We provide complete example code and best practices to help you quickly integrat
55
55
 
56
56
  **The demo repository includes:**
57
57
  - ✅ Complete integration examples
58
- - ✅ Usage examples for both SDK mode and Host mode
58
+ - ✅ Usage examples for both Direct mode and Backend mode
59
59
  - ✅ Audio processing examples (PCM16, WAV, MP3, etc.)
60
60
  - ✅ Vite configuration examples
61
61
  - ✅ Next.js configuration examples
@@ -224,9 +224,9 @@ import {
224
224
  // 1. Initialize SDK
225
225
 
226
226
  const configuration: Configuration = {
227
- drivingServiceMode: DrivingServiceMode.sdk, // Optional, 'sdk' is default
228
- // - DrivingServiceMode.sdk: SDK mode - SDK handles network communication
229
- // - DrivingServiceMode.host: Host mode - Host app provides audio and animation data
227
+ drivingServiceMode: DrivingServiceMode.direct, // Optional, `direct` is default
228
+ // - DrivingServiceMode.direct: Direct mode - SDK handles network communication
229
+ // - DrivingServiceMode.backend: Backend mode - Host app provides audio and animation data
230
230
  logLevel: LogLevel.off, // Optional, 'off' is default
231
231
  // - LogLevel.off: Disable all logs
232
232
  // - LogLevel.error: Only error logs
@@ -254,8 +254,8 @@ const avatar = await avatarManager.load('character-id', (progress) => {
254
254
 
255
255
  // 3. Create view (automatically creates Canvas and AvatarController)
256
256
  // The playback mode is determined by drivingServiceMode in AvatarSDK configuration
257
- // - DrivingServiceMode.sdk: SDK mode - SDK handles network communication
258
- // - DrivingServiceMode.host: Host mode - Host app provides audio and animation data
257
+ // - DrivingServiceMode.direct: Direct mode - SDK handles network communication
258
+ // - DrivingServiceMode.backend: Backend mode - Host app provides audio and animation data
259
259
  const container = document.getElementById('avatar-container')
260
260
  const avatarView = new AvatarView(avatar, container)
261
261
 
@@ -266,7 +266,7 @@ button.addEventListener('click', async () => {
266
266
  // Initialize audio context - MUST be in user gesture context
267
267
  await avatarView.controller.initializeAudioContext()
268
268
 
269
- // 5. Start real-time communication (SDK mode only)
269
+ // 5. Start real-time communication (Direct mode only)
270
270
  // Note: start() initiates the WebSocket connection asynchronously.
271
271
  // Wait for onConnectionState === 'connected' before calling send().
272
272
  await avatarView.controller.start()
@@ -278,7 +278,7 @@ button.addEventListener('click', async () => {
278
278
  }
279
279
  })
280
280
 
281
- // 7. Send audio data (SDK mode, must be mono PCM16 format matching configured sample rate)
281
+ // 7. Send audio data (Direct mode, must be mono PCM16 format matching configured sample rate)
282
282
  // audioData: ArrayBuffer or Uint8Array containing PCM16 (S16LE) audio samples
283
283
  // ⚠️ Byte length MUST be even (2 bytes per sample). Odd-length data will cause server-side
284
284
  // validation error and WebSocket disconnect.
@@ -295,9 +295,9 @@ button.addEventListener('click', async () => {
295
295
 
296
296
  ```typescript
297
297
 
298
- // 1-3. Same as SDK mode (initialize SDK, load avatar)
298
+ // 1-3. Same as Direct mode (initialize SDK, load avatar)
299
299
 
300
- // 3. Create view with Host mode
300
+ // 3. Create view with Backend mode
301
301
  const container = document.getElementById('avatar-container')
302
302
  const avatarView = new AvatarView(avatar, container)
303
303
 
@@ -317,8 +317,8 @@ button.addEventListener('click', async () => {
317
317
  ### Complete Examples
318
318
 
319
319
  This SDK supports two usage modes:
320
- - SDK mode: Real-time audio input with automatic animation data reception
321
- - Host mode: Custom data sources with manual audio/animation data management
320
+ - Direct mode: Real-time audio input with automatic animation data reception
321
+ - Backend mode: Custom data sources with manual audio/animation data management
322
322
 
323
323
  ## 🏗️ Architecture Overview
324
324
 
@@ -334,14 +334,14 @@ This SDK supports two usage modes:
334
334
  The SDK supports two playback modes, configured in `AvatarSDK.initialize()`:
335
335
 
336
336
  #### 1. SDK Mode (Default)
337
- - Configured via `drivingServiceMode: DrivingServiceMode.sdk` in `AvatarSDK.initialize()`
337
+ - Configured via `drivingServiceMode: DrivingServiceMode.direct` in `AvatarSDK.initialize()`
338
338
  - SDK handles network communication automatically
339
339
  - Send audio data via `AvatarController.send()`
340
340
  - SDK receives animation data from backend and synchronizes playback
341
341
  - Best for: Real-time audio input scenarios
342
342
 
343
343
  #### 2. Host Mode
344
- - Configured via `drivingServiceMode: DrivingServiceMode.host` in `AvatarSDK.initialize()`
344
+ - Configured via `drivingServiceMode: DrivingServiceMode.backend` in `AvatarSDK.initialize()`
345
345
  - Host application manages its own network/data fetching
346
346
  - Host application provides both audio and animation data
347
347
  - SDK only handles synchronized playback
@@ -618,13 +618,13 @@ const newAvatar = await AvatarManager.shared.load('new-character-id')
618
618
  // 3. Create new AvatarView
619
619
  currentAvatarView = new AvatarView(newAvatar, container)
620
620
 
621
- // 4. Start connection if SDK mode
621
+ // 4. Start connection if Direct mode
622
622
  await currentAvatarView.controller.start()
623
623
  ```
624
624
 
625
625
  ### AvatarController
626
626
 
627
- Audio/animation playback controller, manages synchronized playback of audio and animation. Automatically handles network communication in SDK mode.
627
+ Audio/animation playback controller, manages synchronized playback of audio and animation. Automatically handles network communication in Direct mode.
628
628
 
629
629
  **Two Usage Patterns:**
630
630
 
@@ -708,7 +708,7 @@ avatarView.controller.setVolume(0.5) // Set volume to 50% (0.0 to 1.0)
708
708
  const currentVolume = avatarView.controller.getVolume() // Get current volume (0.0 to 1.0)
709
709
 
710
710
  // Set event callbacks
711
- avatarView.controller.onConnectionState = (state: ConnectionState) => {} // SDK mode only
711
+ avatarView.controller.onConnectionState = (state: ConnectionState) => {} // Direct mode only
712
712
  avatarView.controller.onConversationState = (state: ConversationState) => {}
713
713
  avatarView.controller.onError = (error: AvatarError) => {} // Includes error.code for specific error type
714
714
  ```
@@ -731,8 +731,8 @@ avatarView.avatarTransform = { x: 0.5, y: 0, scale: 2.0 } // Right half, double
731
731
  ```
732
732
 
733
733
  **Important Notes:**
734
- - `start()` and `close()` are only available in SDK mode
735
- - `yieldAudioData()` and `yieldFramesData()` are only available in Host mode
734
+ - `start()` and `close()` are only available in Direct mode
735
+ - `yieldAudioData()` and `yieldFramesData()` are only available in Backend mode
736
736
  - `pause()`, `resume()`, `interrupt()`, `setVolume()`, and `getVolume()` are available in both modes
737
737
  - The playback mode is determined when creating `AvatarView` and cannot be changed
738
738
 
@@ -742,7 +742,7 @@ avatarView.avatarTransform = { x: 0.5, y: 0, scale: 2.0 } // Right half, double
742
742
 
743
743
  ```typescript
744
744
  interface Configuration {
745
- drivingServiceMode?: DrivingServiceMode // Optional, default is 'sdk' (SDK mode)
745
+ drivingServiceMode?: DrivingServiceMode // Optional, default is `direct` (Direct mode)
746
746
  logLevel?: LogLevel // Optional, default is 'off' (no logs)
747
747
  audioFormat?: AudioFormat // Optional, default is { channelCount: 1, sampleRate: 16000 }
748
748
  }
@@ -770,8 +770,8 @@ enum LogLevel {
770
770
 
771
771
  **Description:**
772
772
  - `drivingServiceMode`: Specifies the driving service mode
773
- - `DrivingServiceMode.sdk` (default): SDK mode - SDK handles network communication automatically
774
- - `DrivingServiceMode.host`: Host mode - Host application provides audio and animation data
773
+ - `DrivingServiceMode.direct` (default): Direct mode - SDK handles network communication automatically
774
+ - `DrivingServiceMode.backend`: Backend mode - Host application provides audio and animation data
775
775
  - `logLevel`: Controls the verbosity of SDK logs
776
776
  - `LogLevel.off` (default): Disable all logs
777
777
  - `LogLevel.error`: Only error logs
@@ -885,7 +885,7 @@ avatarView.controller.onError = (error: AvatarError) => {
885
885
  | `sessionTimeout` | Session timeout | WebSocket close code 4002 |
886
886
  | `connectionInProgress` | Connection already in progress | Duplicate `start()` call |
887
887
  | **Playback** | | |
888
- | `networkLayerNotAvailable` | Network layer not available | `send()` in host mode |
888
+ | `networkLayerNotAvailable` | Network layer not available | `send()` in backend mode |
889
889
  | `playbackStartFailed` | Failed to start playback | Internal error |
890
890
  | `playbackInitFailed` | Playback initialization failed | Internal error |
891
891
  | `audioOnlyInitFailed` | Audio-only playback init failed | Fallback mode error |
@@ -931,12 +931,12 @@ avatarView.dispose()
931
931
 
932
932
  **⚠️ Important Notes:**
933
933
  - `dispose()` automatically cleans up all resources, including:
934
- - Network connections (SDK mode)
934
+ - Network connections (Direct mode)
935
935
  - Playback data and animation resources (both modes)
936
936
  - Render system and canvas elements
937
937
  - All event listeners and callbacks
938
938
  - Not properly calling `dispose()` may cause resource leaks and rendering errors
939
- - If you need to manually close connections before disposing, you can call `avatarView.controller.close()` (SDK mode) first, but it's not required as `dispose()` handles this automatically
939
+ - If you need to manually close connections before disposing, you can call `avatarView.controller.close()` (Direct mode) first, but it's not required as `dispose()` handles this automatically
940
940
 
941
941
  ### Memory Optimization
942
942
 
@@ -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-zTpDp_Pv.js";
4
+ import { A as APP_CONFIG, l as logger, e as errorToMessage, a as logEvent } from "./index-CVQn5uEB.js";
5
5
  class StreamingAudioPlayer {
6
6
  // Mark if AudioContext is being resumed, avoid concurrent resume requests
7
7
  constructor(options) {
@@ -87,8 +87,8 @@ class StreamingAudioPlayer {
87
87
  });
88
88
  } catch (error) {
89
89
  const message = errorToMessage(error);
90
- logEvent("activeAudioSessionFailed", "warning", {
91
- sessionId: this.sessionId,
90
+ logEvent("audio_session_init_failed", "warning", {
91
+ session_id: this.sessionId,
92
92
  reason: message
93
93
  });
94
94
  logger.error("Failed to initialize AudioContext:", message);
@@ -144,7 +144,7 @@ class StreamingAudioPlayer {
144
144
  } catch (err) {
145
145
  logger.errorWithError("[StreamingAudioPlayer] Failed to resume AudioContext:", err);
146
146
  logEvent("audio_context_resume_failed", "error", {
147
- sessionId: this.sessionId,
147
+ session_id: this.sessionId,
148
148
  reason: err instanceof Error ? err.message : String(err)
149
149
  });
150
150
  } finally {
@@ -281,7 +281,7 @@ class StreamingAudioPlayer {
281
281
  const errorMessage = "Failed to create AudioBuffer from PCM data";
282
282
  logger.error(errorMessage);
283
283
  logEvent("audio_buffer_creation_failed", "error", {
284
- sessionId: this.sessionId
284
+ session_id: this.sessionId
285
285
  });
286
286
  return;
287
287
  }
@@ -328,7 +328,7 @@ class StreamingAudioPlayer {
328
328
  } catch (err) {
329
329
  logger.errorWithError("Failed to schedule audio chunk:", err);
330
330
  logEvent("schedule_chunk_failed", "error", {
331
- sessionId: this.sessionId,
331
+ session_id: this.sessionId,
332
332
  reason: err instanceof Error ? err.message : String(err)
333
333
  });
334
334
  }
@@ -22,9 +22,7 @@ export declare class AvatarController {
22
22
  set frameRateMonitorEnabled(value: boolean);
23
23
  private renderCallback?;
24
24
  private characterHandle;
25
- private characterId;
26
25
  private postProcessingConfig;
27
- private audioMonitorLoopId;
28
26
  private lastRenderedFrameIndex;
29
27
  private keyframesOffset;
30
28
  private readonly MAX_KEYFRAMES;
@@ -1,4 +1,4 @@
1
- import { Configuration } from '../types';
1
+ import { Configuration, RenderQuality } from '../types';
2
2
  export declare class AvatarSDK {
3
3
  private static _initializationState;
4
4
  private static _initializingPromise;
@@ -7,6 +7,13 @@ export declare class AvatarSDK {
7
7
  private static _avatarCore;
8
8
  private static _cachedDeviceScore;
9
9
  private static _rendererBackend;
10
+ /** Render output resolution cap. Disabled by default; when enabled and the
11
+ * canvas backing height (css × DPR) exceeds maxHeight, the canvas is sized
12
+ * to maxHeight (aspect-preserving) and the browser scales it back up to
13
+ * the CSS box. Training material is 1080p so heights above ~1080-1440
14
+ * add no real visual information. */
15
+ static renderResolutionCapEnabled: boolean;
16
+ static renderResolutionMaxHeight: number;
10
17
  /**
11
18
  * Initialize SDK
12
19
  * @param appId Application ID to be included in both HTTP Headers and WebSocket Headers
@@ -27,6 +34,14 @@ export declare class AvatarSDK {
27
34
  static setUserId(userId: string): void;
28
35
  static get appId(): string | null;
29
36
  static get configuration(): Configuration | null;
37
+ /**
38
+ * Update the global render quality tier. Takes effect on the next rendered
39
+ * frame across all active AvatarView instances. Throws if SDK isn't initialized.
40
+ */
41
+ static setRenderQuality(quality: RenderQuality): void;
42
+ /** Toggle the render output resolution cap. Takes effect on the next layout
43
+ * pass across all active AvatarView instances. */
44
+ static setRenderResolutionCap(enabled: boolean, maxHeight?: number): void;
30
45
  static get sessionToken(): string | null;
31
46
  static get userId(): string | null;
32
47
  static get version(): string;
@@ -16,7 +16,7 @@ export declare class AvatarView {
16
16
  private frameCount;
17
17
  private lastFpsUpdate;
18
18
  private currentFPS;
19
- private currentPlayingFrame;
19
+ private currentFrame;
20
20
  private cachedIdleFirstFrame;
21
21
  private characterHandle;
22
22
  private characterId;
@@ -41,6 +41,20 @@ export declare class AvatarView {
41
41
  * Get controller (public interface)
42
42
  */
43
43
  get controller(): AvatarController;
44
+ /**
45
+ * Current native render surface size in pixels (canvas backing buffer
46
+ * size, post-DPR).
47
+ *
48
+ * Differs from the canvas CSS size (`offsetWidth / offsetHeight`): this
49
+ * reflects the actual pixel buffer the WebGPU renderer is targeting after
50
+ * the most recent resize has been applied.
51
+ *
52
+ * Returns `{ width: 0, height: 0 }` before the canvas has been initialized.
53
+ */
54
+ get renderSize(): {
55
+ width: number;
56
+ height: number;
57
+ };
44
58
  private _exportBitmapResolve;
45
59
  /**
46
60
  * Exports the current rendering as a Blob (PNG).
@@ -126,7 +140,17 @@ export declare class AvatarView {
126
140
  * @param options.useLinear - Use linear interpolation (default: true)
127
141
  * @returns Array of opaque keyframe data for sequential playback
128
142
  */
129
- generateTransitionFromProtobuf(data: ArrayBuffer | Uint8Array, frameCount: number, options?: {
143
+ generateTransitionToFrame(data: ArrayBuffer | Uint8Array, frameCount: number, options?: {
144
+ useLinear?: boolean;
145
+ }): Promise<unknown[]>;
146
+ /**
147
+ * RTC mode: generate `frameCount` interpolated frames from the most recently
148
+ * rendered frame back to the idle loop start. Caller plays them at 25fps
149
+ * via `renderFrame`, then hands control back with
150
+ * `renderFrame(undefined, true)`. Used for speaking → idle and disconnect
151
+ * soft transitions.
152
+ */
153
+ generateTransitionToIdle(frameCount: number, options?: {
130
154
  useLinear?: boolean;
131
155
  }): Promise<unknown[]>;
132
156
  /**
@@ -134,6 +158,12 @@ export declare class AvatarView {
134
158
  * Called by renderFromProtobuf when streaming frames arrive during transition.
135
159
  */
136
160
  cancelFrameSequence(): void;
161
+ /** 计算 canvas backing-store 像素尺寸. 默认 css × dpr;
162
+ * AvatarSDK.setRenderResolutionCap 启用且高度超过阈值时,
163
+ * 按比例缩到阈值, css 尺寸不变 (浏览器自动拉伸). */
164
+ private computeCappedBacking;
165
+ /** Registry hook: AvatarSDK.setRenderResolutionCap 调用时重算 backing size. */
166
+ applyResolutionCapFromSdk(): void;
137
167
  /**
138
168
  * Pause rendering loop
139
169
  *
@@ -0,0 +1,9 @@
1
+ import { AvatarView } from './AvatarView';
2
+ declare class AvatarViewRegistry {
3
+ private entries;
4
+ register(view: AvatarView): void;
5
+ unregister(view: AvatarView): void;
6
+ applyResolutionCapToAll(): void;
7
+ }
8
+ export declare const avatarViewRegistry: AvatarViewRegistry;
9
+ export {};