@spatius/avatarkit 1.3.1-beta.6 → 1.3.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 +27 -0
- package/dist/{AvatarDownloader-Dfi7wtgQ.js → AvatarDownloader-DyhenPKd.js} +98 -43
- package/dist/{AvatarSDK-CnZqa_X4.js → AvatarSDK-DgPvHU4L.js} +162 -2560
- package/dist/{OpusCodec-D9DcUV65.js → OpusCodec-CXU7ysrU.js} +43 -9
- package/dist/{OpusDecoderProxy-Ar_dFXRB.js → OpusDecoderProxy-Cq8Z_MZY.js} +19 -6
- package/dist/{OpusEncoderProxy-9YIZ7nIt.js → OpusEncoderProxy-CzY-VckE.js} +42 -7
- package/dist/{StreamingAudioPlayer-D4q7c1U0.js → StreamingAudioPlayer-CIJmBp3z.js} +2 -2
- package/dist/assets/{AvatarDownloader-D5GePpJ2.js → AvatarDownloader-G5FO1bVl.js} +96 -41
- package/dist/assets/{AvatarSDK-DR8dNylh.js → AvatarSDK-C_Anluwp.js} +149 -12
- package/dist/assets/{OpusDecoderWorker.worker-B7R93nev.js → OpusDecoderWorker.worker-C2T0afkj.js} +21 -5
- package/dist/assets/{OpusEncoderWorker.worker-BYWIV5xu.js → OpusEncoderWorker.worker-DLYO9vPK.js} +53 -11
- package/dist/assets/{logger-D8rDVim7.js → logger-YPooEAbA.js} +451 -132
- package/dist/core/AvatarController.d.ts +10 -0
- package/dist/{error-utils-CqKOrHrY.js → error-utils-BogEllAd.js} +1 -1
- package/dist/index.js +279 -46
- package/dist/internal-telemetry.d.ts +45 -20
- package/dist/internal-telemetry.js +27 -2
- package/dist/{logger-fAU8oahT.js → logger-BOpQ7u0w.js} +468 -132
- package/dist/otel-trace-Ct4T48nC.js +2565 -0
- package/dist/{pwa-cache-manager-CwXaM3xR.js → pwa-cache-manager-C7NbU52Q.js} +1 -1
- package/dist/types/index.d.ts +30 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ 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.3.1] - 2026-08-07
|
|
9
|
+
|
|
10
|
+
Rolls up everything from the 1.3.1 beta line.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `DrivingServiceMode.rtc`. Use it **only** when driving the avatar through the companion RTC SDK, `@spatius/avatarkit-rtc`; on `direct` / `backend` nothing changes. It is a telemetry dimension and unlocks no API — declaring it lets RTC-driven sessions be told apart from plain `backend` traffic on the dashboards.
|
|
14
|
+
- Opus audio support. `AudioFormat.inputAudioFormat` (`'pcm'` default, or `'opus'`) declares the format the host feeds into the SDK via `send`/`yieldAudioData`; `'opus'` input is decoded back to PCM16 for local rendering and forwarded upstream as-is in direct mode. `AudioFormat.opusBitrate` (default `48000`) tunes the target bitrate. Only effective in direct mode.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- **The direct-mode uplink now compresses audio to Opus by default** (`AudioFormat.opusUplinkEnabled`, previously off). It cuts the upload to roughly 1/8 at the cost of client-side encoding, which matters most on the mobile networks where stalls actually happen. Pass `opusUplinkEnabled: false` to keep the raw PCM uplink. Unchanged for host mode (no uplink) and for `'opus'` input (already compressed).
|
|
18
|
+
- The `region` configuration now defaults to automatic selection: when left unset, the SDK picks the closest serving region at initialization. Passing an explicit `region` continues to force that region, unchanged. If automatic selection can't be reached, the SDK falls back to a default region and continues initializing.
|
|
19
|
+
- Opus uplink encoding streams: each second of audio is sent as soon as it is encoded, instead of waiting for the whole chunk to finish. When a host hands over a long utterance in one call, the first packet used to wait out the entire encode; playback now starts noticeably sooner.
|
|
20
|
+
- If the configured `sampleRate` is not one of Opus's supported rates, `initialize` logs a warning and falls back to a raw PCM uplink instead of silently failing to build the encoder.
|
|
21
|
+
- `initialize` with a missing `appId` now fails fast (throws) with an updated message pointing to https://app.spatius.ai/ to obtain an app ID.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- Fixed playback failing to start when the first audio arrived slightly after the animation frames (both host and direct modes) — playback now begins correctly instead of stalling.
|
|
25
|
+
- Fixed an intermittent burst of harsh noise at the start of Opus-input playback: on a fast start the first audio could be played before the Opus decoder finished initializing. Opus input is now always decoded before playback, so no raw Opus is ever played as PCM.
|
|
26
|
+
- Fixed the idle animation freezing (and a delayed start) in direct mode with Opus input when animation frames arrived before the decoded audio: playback now waits for both audio and animation to be ready, and the avatar keeps looping idle smoothly until the round actually begins.
|
|
27
|
+
- Fixed playback getting stuck when audio was sent while the connection was still being established and the session later reconnected: the audio clock could freeze, leaving the avatar frozen on a single frame instead of animating in sync with the audio.
|
|
28
|
+
- Fixed the avatar not returning to idle after a playback round finished in that same send-then-connect scenario; it now correctly transitions back to idle when playback ends.
|
|
29
|
+
|
|
30
|
+
## [1.3.1-beta.7] - 2026-08-02
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- Internal telemetry changes only; no changes to the public API or runtime behavior.
|
|
34
|
+
|
|
8
35
|
## [1.3.1-beta.6] - 2026-08-01
|
|
9
36
|
|
|
10
37
|
### Changed
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as __exportAll } from "./rolldown-runtime-B-1-B7_t.js";
|
|
2
|
-
import { t as AvatarSDK } from "./AvatarSDK-
|
|
3
|
-
import {
|
|
4
|
-
import { n as APP_CONFIG, r as getFlameCdnBase, t as errorToMessage } from "./error-utils-
|
|
5
|
-
import { t as PwaCacheManager } from "./pwa-cache-manager-
|
|
2
|
+
import { t as AvatarSDK } from "./AvatarSDK-DgPvHU4L.js";
|
|
3
|
+
import { S as recordHttpClientDuration, c as logEvent, kt as ErrorCode, l as logMetric, t as logger, v as hostOf, xt as AvatarError, yt as generateTraceId } from "./logger-BOpQ7u0w.js";
|
|
4
|
+
import { n as APP_CONFIG, r as getFlameCdnBase, t as errorToMessage } from "./error-utils-BogEllAd.js";
|
|
5
|
+
import { t as PwaCacheManager } from "./pwa-cache-manager-C7NbU52Q.js";
|
|
6
6
|
//#region core/AvatarDownloader.ts
|
|
7
7
|
var AvatarDownloader_exports = /* @__PURE__ */ __exportAll({ AvatarDownloader: () => AvatarDownloader });
|
|
8
8
|
/**
|
|
@@ -47,20 +47,46 @@ function getCacheInfo(url, response) {
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* @param url 资源 URL
|
|
59
|
-
* @param options 下载选项
|
|
60
|
-
* @returns ArrayBuffer and cache information
|
|
50
|
+
* 把整次角色加载的资产下载汇总成**一条** Transaction。
|
|
51
|
+
*
|
|
52
|
+
* 成败取「最差的一片」:任一片 transport error 即整次记 transport error;否则取
|
|
53
|
+
* 状态码最大的那个(4xx/5xx 会盖过 200)。这样后端按状态码切出的成功率,含义是
|
|
54
|
+
* 「这次加载的资产是否全部拿到」——与用户实际体验一致,一片挂了角色就出不来。
|
|
55
|
+
*
|
|
56
|
+
* duration 传整体耗时而非单片,与 `download_avatar_assets_latency` 同口径。
|
|
61
57
|
*/
|
|
58
|
+
function recordAssetTransaction(outcomes, durationMs, operation = ASSET_OPERATION) {
|
|
59
|
+
const real = outcomes.filter((o) => !o.skipped);
|
|
60
|
+
if (real.length === 0) {
|
|
61
|
+
recordHttpClientDuration({
|
|
62
|
+
operation,
|
|
63
|
+
method: "GET",
|
|
64
|
+
durationMs,
|
|
65
|
+
statusCode: 200,
|
|
66
|
+
cacheHit: true
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
recordHttpClientDuration({
|
|
71
|
+
operation,
|
|
72
|
+
method: "GET",
|
|
73
|
+
durationMs,
|
|
74
|
+
statusCode: real.some((o) => o.statusCode === void 0) ? void 0 : real.reduce((max, o) => Math.max(max, o.statusCode ?? 0), 0),
|
|
75
|
+
serverAddress: real[0].host,
|
|
76
|
+
cacheHit: false
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Transaction 的 `operation` 取值。必须低基数:资产 URL 带 avatar id 与文件名,
|
|
81
|
+
* 直接当维度会让时间序列随角色数无限增长。要定位「哪个文件挂了」用
|
|
82
|
+
* `download_avatar_assets_failed` log,它带 resource/url/error。
|
|
83
|
+
*/
|
|
84
|
+
var ASSET_OPERATION = "/assets/character";
|
|
85
|
+
var TEMPLATE_OPERATION = "/assets/template";
|
|
62
86
|
async function downloadResource(url, options) {
|
|
63
|
-
const { signal, characterId, resourceType, maxRetries = 3 } = options || {};
|
|
87
|
+
const { signal, characterId, resourceType, maxRetries = 3, outcomes } = options || {};
|
|
88
|
+
const outcomeSlotIndex = outcomes ? outcomes.length : -1;
|
|
89
|
+
if (outcomes) outcomes.push({ host: hostOf(url) });
|
|
64
90
|
if (signal?.aborted) throw new Error("Download cancelled");
|
|
65
91
|
try {
|
|
66
92
|
let cached = null;
|
|
@@ -79,6 +105,10 @@ async function downloadResource(url, options) {
|
|
|
79
105
|
cacheInfo.cacheHit = true;
|
|
80
106
|
cacheInfo.cacheType = "pwa";
|
|
81
107
|
cacheInfo.pwaCacheSubtype = pwaCacheSubtype;
|
|
108
|
+
if (outcomes && outcomeSlotIndex >= 0) outcomes[outcomeSlotIndex] = {
|
|
109
|
+
host: "",
|
|
110
|
+
skipped: true
|
|
111
|
+
};
|
|
82
112
|
return {
|
|
83
113
|
data: cached,
|
|
84
114
|
cacheInfo
|
|
@@ -87,8 +117,11 @@ async function downloadResource(url, options) {
|
|
|
87
117
|
let lastError = null;
|
|
88
118
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
89
119
|
if (signal?.aborted) throw new Error("Download cancelled");
|
|
120
|
+
const outcomeSlot = { host: hostOf(url) };
|
|
121
|
+
if (outcomes) outcomes[outcomeSlotIndex] = outcomeSlot;
|
|
90
122
|
try {
|
|
91
123
|
const response = await fetch(url, { signal });
|
|
124
|
+
outcomeSlot.statusCode = response.status;
|
|
92
125
|
if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText}`);
|
|
93
126
|
const arrayBuffer = await response.arrayBuffer();
|
|
94
127
|
const contentLength = response.headers.get("content-length");
|
|
@@ -146,32 +179,41 @@ var AvatarDownloader = class {
|
|
|
146
179
|
cache_hit: true,
|
|
147
180
|
cache_type: "pwa"
|
|
148
181
|
});
|
|
182
|
+
recordAssetTransaction([], duration, TEMPLATE_OPERATION);
|
|
149
183
|
return { unifiedModel: cached };
|
|
150
184
|
}
|
|
151
185
|
const maxRetries = 3;
|
|
152
186
|
let lastError = null;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
187
|
+
const templateOutcomes = [];
|
|
188
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
189
|
+
const outcomeSlot = { host: hostOf(url) };
|
|
190
|
+
templateOutcomes[0] = outcomeSlot;
|
|
191
|
+
try {
|
|
192
|
+
const response = await fetch(url);
|
|
193
|
+
outcomeSlot.statusCode = response.status;
|
|
194
|
+
if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText}`);
|
|
195
|
+
let buffer;
|
|
196
|
+
if (APP_CONFIG.flame.unifiedModelPath.endsWith(".gz")) {
|
|
197
|
+
const decompressedStream = response.body.pipeThrough(new DecompressionStream("gzip"));
|
|
198
|
+
buffer = await new Response(decompressedStream).arrayBuffer();
|
|
199
|
+
} else buffer = await response.arrayBuffer();
|
|
200
|
+
logger.log(`✅ Unified template loaded (${(buffer.byteLength / 1024 / 1024).toFixed(1)} MB)`);
|
|
201
|
+
PwaCacheManager.putTemplateResource(url, buffer).catch((err) => {
|
|
202
|
+
logger.warn(`[loadUnifiedTemplate] Failed to cache:`, err);
|
|
203
|
+
});
|
|
204
|
+
logMetric("template_resources_load_measure", Date.now() - startTime, {
|
|
205
|
+
file_count: 1,
|
|
206
|
+
cache_hit: false,
|
|
207
|
+
cache_type: "none"
|
|
208
|
+
});
|
|
209
|
+
recordAssetTransaction(templateOutcomes, Date.now() - startTime, TEMPLATE_OPERATION);
|
|
210
|
+
return { unifiedModel: buffer };
|
|
211
|
+
} catch (err) {
|
|
212
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
213
|
+
if (attempt < maxRetries) logger.warn(`[loadUnifiedTemplate] Attempt ${attempt}/${maxRetries} failed, retrying...`);
|
|
214
|
+
}
|
|
174
215
|
}
|
|
216
|
+
recordAssetTransaction(templateOutcomes, Date.now() - startTime, TEMPLATE_OPERATION);
|
|
175
217
|
throw lastError || /* @__PURE__ */ new Error(`Failed to download unified template after ${maxRetries} attempts`);
|
|
176
218
|
}
|
|
177
219
|
/**
|
|
@@ -252,6 +294,8 @@ var AvatarDownloader = class {
|
|
|
252
294
|
};
|
|
253
295
|
const characterData = {};
|
|
254
296
|
const cacheInfos = [];
|
|
297
|
+
/** 各片的 HTTP 结果,收尾时汇总成一条 Transaction(见 recordAssetTransaction)。 */
|
|
298
|
+
const httpOutcomes = [];
|
|
255
299
|
const parallelStartTime = Date.now();
|
|
256
300
|
const downloadPromises = filesToLoad.map(async ({ key, url, filename, optional }) => {
|
|
257
301
|
updateProgress(filename, false);
|
|
@@ -259,7 +303,8 @@ var AvatarDownloader = class {
|
|
|
259
303
|
const { data: arrayBuffer, cacheInfo } = await downloadResource(url, {
|
|
260
304
|
signal,
|
|
261
305
|
characterId: characterMeta.characterId ?? void 0,
|
|
262
|
-
resourceType: "character"
|
|
306
|
+
resourceType: "character",
|
|
307
|
+
outcomes: httpOutcomes
|
|
263
308
|
});
|
|
264
309
|
if (key === "shape") characterData.shape = arrayBuffer;
|
|
265
310
|
else if (key === "pointCloud") characterData.pointCloud = arrayBuffer;
|
|
@@ -297,7 +342,11 @@ var AvatarDownloader = class {
|
|
|
297
342
|
};
|
|
298
343
|
}
|
|
299
344
|
});
|
|
300
|
-
|
|
345
|
+
try {
|
|
346
|
+
await Promise.all(downloadPromises);
|
|
347
|
+
} finally {
|
|
348
|
+
recordAssetTransaction(httpOutcomes, Date.now() - totalStartTime);
|
|
349
|
+
}
|
|
301
350
|
const parallelDuration = Date.now() - parallelStartTime;
|
|
302
351
|
const totalDuration = Date.now() - totalStartTime;
|
|
303
352
|
if (!characterData.shape || !characterData.pointCloud) {
|
|
@@ -324,7 +373,11 @@ var AvatarDownloader = class {
|
|
|
324
373
|
parallel_duration: parallelDuration,
|
|
325
374
|
total_size: totalSize
|
|
326
375
|
});
|
|
327
|
-
return
|
|
376
|
+
return {
|
|
377
|
+
data: characterData,
|
|
378
|
+
cacheHit,
|
|
379
|
+
cacheType
|
|
380
|
+
};
|
|
328
381
|
}
|
|
329
382
|
/**
|
|
330
383
|
* Preload all resources (template + character data + camera info + settings)
|
|
@@ -333,7 +386,7 @@ var AvatarDownloader = class {
|
|
|
333
386
|
async preloadResources(characterMeta, options) {
|
|
334
387
|
const { progressCallback = null, signal, useCompressedModel = false } = options || {};
|
|
335
388
|
if (signal?.aborted) throw new Error("Preload cancelled");
|
|
336
|
-
const [
|
|
389
|
+
const [characterResult, preloadCameraSettings] = await Promise.all([this.loadCharacterData(characterMeta, {
|
|
337
390
|
signal,
|
|
338
391
|
useCompressedModel,
|
|
339
392
|
progressCallback: (info) => {
|
|
@@ -344,9 +397,11 @@ var AvatarDownloader = class {
|
|
|
344
397
|
}
|
|
345
398
|
}), this.loadCameraSettings(characterMeta, { signal })]);
|
|
346
399
|
return {
|
|
347
|
-
characterData,
|
|
400
|
+
characterData: characterResult.data,
|
|
348
401
|
preloadCameraSettings,
|
|
349
|
-
characterSettings: characterMeta.characterSettings
|
|
402
|
+
characterSettings: characterMeta.characterSettings,
|
|
403
|
+
cacheHit: characterResult.cacheHit,
|
|
404
|
+
cacheType: characterResult.cacheType
|
|
350
405
|
};
|
|
351
406
|
}
|
|
352
407
|
/**
|