@spatius/avatarkit 1.3.1-beta.2 → 1.3.1-beta.4

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.
@@ -7,6 +7,23 @@ export declare class AvatarController {
7
7
  private isStartingPlayback;
8
8
  private currentConversationId;
9
9
  private reqEnd;
10
+ private inputOpusDecoderProxy;
11
+ /**
12
+ * Serializes the async decode→buffer path so PCM reaches the playback buffer in
13
+ * FEED ORDER. Critical because OggOpusDecoder (inside the proxy) is stateful — it
14
+ * carries a partial Ogg page across calls — so out-of-order decodes corrupt the
15
+ * stream. Each send()/yieldAudioData() chains onto this; the worker is FIFO, but
16
+ * awaiting also keeps addAudioChunkToBuffer(pcm) calls from interleaving.
17
+ * Mirrors AnimationWebSocketClient.opusSendChain on the uplink.
18
+ */
19
+ private inputDecodeChain;
20
+ /**
21
+ * Brings host-provided Opus into the single shape the rest of the pipeline
22
+ * expects: an Ogg stream. Streaming TTS providers hand over bare Opus packets
23
+ * rather than Ogg, so those get muxed here — once, before the send path splits,
24
+ * so local playback and the uplink both get valid Ogg.
25
+ */
26
+ private readonly opusInputNormalizer;
10
27
  onConnectionState: ((state: ConnectionState) => void) | null;
11
28
  onConversationState: ((state: ConversationState) => void) | null;
12
29
  onError: ((error: AvatarError) => void) | null;
@@ -51,6 +68,8 @@ export declare class AvatarController {
51
68
  private playbackEndReason;
52
69
  private receivedAudioBytes;
53
70
  private receivedAnimationFrames;
71
+ private playbackStartedAt;
72
+ private playbackEndedAt;
54
73
  /**
55
74
  * Whether this round's final animation batch (ServerResponseAnimation.end) has arrived.
56
75
  * Frame starvation is only possible BEFORE this — once all frames are in, any remaining
@@ -67,8 +86,27 @@ export declare class AvatarController {
67
86
  private readonly MAX_AUDIO_TIME_ZERO_COUNT;
68
87
  private readonly MAX_AUDIO_TIME_STUCK_COUNT;
69
88
  private readonly AUDIO_TIME_STUCK_THRESHOLD;
70
- private hostModeMetrics;
71
- private readonly audioBytesPerSecond;
89
+ private latencyMarks;
90
+ /**
91
+ * Per-call trace records for the current round, in call order. Unlike
92
+ * latencyMarks (which aggregates arrival into per-second/per-group instants),
93
+ * these are one entry per actual API action, so each becomes a short span:
94
+ * how long the CALL itself took, plus what it carried (audio seconds sent /
95
+ * frames received). Reset per conversation alongside latencyMarks.
96
+ */
97
+ private traceActions;
98
+ /**
99
+ * Highest Ogg granule forwarded so far this round, for Opus input. The granule
100
+ * accumulates across a stream, so a send's audio seconds is its own end granule
101
+ * minus this. Reset per conversation. -1 = nothing forwarded yet.
102
+ */
103
+ private lastOpusGranule;
104
+ /**
105
+ * 音频字节率(bytes/s),按 **实际配置的采样率** 动态计算,而不是写死 16kHz。
106
+ * SDK 支持 8k/16k/22.05k/24k/32k/44.1k/48k,写死会让非 16k 的时长统计整体缩放出错。
107
+ * Opus 输入已被 AvatarSDK.getAudioFormat() 归一化成 48000,正是解码后 PCM 的真实采样率。
108
+ */
109
+ private get audioBytesPerSecond();
72
110
  constructor(avatar: Avatar, options?: {
73
111
  playbackMode?: DrivingServiceMode;
74
112
  });
@@ -94,6 +132,19 @@ export declare class AvatarController {
94
132
  * })
95
133
  */
96
134
  initializeAudioContext(): Promise<void>;
135
+ /**
136
+ * Create the input Opus decoder when the configured input format is 'opus'.
137
+ * No-op for PCM input. Idempotent. The decoder turns host-provided Ogg Opus
138
+ * into PCM16 for the local audio pipeline.
139
+ */
140
+ private ensureInputOpusDecoder;
141
+ /**
142
+ * Normalize host-provided audio into PCM16. If the SDK is configured for Opus
143
+ * input, decode the Ogg Opus bytes; otherwise pass PCM through untouched. All
144
+ * downstream code (playback buffer, byte/duration stats, upstream) then works
145
+ * on PCM exactly as before.
146
+ */
147
+ private normalizeInputAudioToPcm;
97
148
  /**
98
149
  * Start service (SDK mode only)
99
150
  */
@@ -27,8 +27,6 @@ export declare class AvatarView {
27
27
  private activeAnimationState;
28
28
  private isPureRenderingMode;
29
29
  private _renderingEnabled;
30
- private avatarActiveTimer;
31
- private readonly AVATAR_ACTIVE_INTERVAL;
32
30
  /**
33
31
  * Constructor
34
32
  * Creates a unified AvatarController, internally composes network layer based on configuration
@@ -0,0 +1,85 @@
1
+ import { mt as isDebugMode } from "./logger-C3fw-NWP.js";
2
+ //#region config/app-config.ts
3
+ var GLOBAL_FLAME_CDN_BASE = "https://cdn.spatialwalk.cloud/public";
4
+ var CN_FLAME_CDN_BASE = "https://cdn.spatialwalk.top/public";
5
+ function getFlameCdnBase(region) {
6
+ return region.startsWith("cn-") ? CN_FLAME_CDN_BASE : GLOBAL_FLAME_CDN_BASE;
7
+ }
8
+ var APP_CONFIG = {
9
+ testEnv: false,
10
+ get debug() {
11
+ return isDebugMode();
12
+ },
13
+ rendering: {
14
+ /**
15
+ * Sort mode
16
+ * - 'balance': Performance first, sort first frame then reuse (default)
17
+ * - 'quality': Quality first, re-sort every frame
18
+ */
19
+ sortMode: "balance" },
20
+ camera: {
21
+ position: [
22
+ -.02,
23
+ -.013,
24
+ 1.5
25
+ ],
26
+ target: [
27
+ 0,
28
+ 0,
29
+ 0
30
+ ],
31
+ fov: 22,
32
+ near: .01,
33
+ far: 100
34
+ },
35
+ animation: { fps: 25 },
36
+ audio: { sampleRate: 16e3 },
37
+ avatar: {
38
+ baseAssetsPath: "",
39
+ wasmPath: "./wasm/avatar_core_wasm.js"
40
+ },
41
+ flame: {
42
+ cdnBase: GLOBAL_FLAME_CDN_BASE,
43
+ unifiedModelPath: "unified_model_body.pb.gz"
44
+ },
45
+ wasm: {
46
+ logLevel: "basic",
47
+ enableValidation: false,
48
+ enablePerformanceMetrics: true
49
+ }
50
+ };
51
+ //#endregion
52
+ //#region utils/error-utils.ts
53
+ /**
54
+ * Error Utility Functions
55
+ * Provides consistent error handling and formatting across the application
56
+ */
57
+ /**
58
+ * Convert unknown error to a readable error message string
59
+ *
60
+ * @param err - Unknown error object (could be Error, string, object, etc.)
61
+ * @returns Formatted error message string
62
+ *
63
+ * @example
64
+ * try {
65
+ * riskyOperation()
66
+ * } catch (err) {
67
+ * const message = errorToMessage(err)
68
+ * logger.error('Operation failed:', message)
69
+ * }
70
+ */
71
+ function errorToMessage(err) {
72
+ if (err instanceof Error) return err.message;
73
+ if (typeof err === "string") return err;
74
+ if (typeof err === "object" && err !== null) {
75
+ if ("message" in err && typeof err.message === "string") return err.message;
76
+ try {
77
+ return JSON.stringify(err);
78
+ } catch {
79
+ return String(err);
80
+ }
81
+ }
82
+ return String(err);
83
+ }
84
+ //#endregion
85
+ export { APP_CONFIG as n, getFlameCdnBase as r, errorToMessage as t };