@spatialwalk/avatarkit 1.0.0-beta.101 → 1.0.0-beta.102
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.102]
|
|
9
|
+
|
|
10
|
+
### 🐛 Bugfixes
|
|
11
|
+
|
|
12
|
+
- **Template loading telemetry** — Fixed `template_resources_load_measure` event not being reported when loading the unified template model. Added PWA caching support for the unified model to enable faster subsequent loads.
|
|
13
|
+
|
|
8
14
|
## [1.0.0-beta.101]
|
|
9
15
|
|
|
10
16
|
### ⚡ Performance
|
|
@@ -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-
|
|
4
|
+
import { A as APP_CONFIG, l as logger, e as errorToMessage, a as logEvent } from "./index-DNq7oTVY.js";
|
|
5
5
|
class StreamingAudioPlayer {
|
|
6
6
|
// Mark if AudioContext is being resumed, avoid concurrent resume requests
|
|
7
7
|
constructor(options) {
|
|
@@ -2718,16 +2718,14 @@ const APP_CONFIG = {
|
|
|
2718
2718
|
sampleRate: 16e3
|
|
2719
2719
|
// Audio sample rate (backend requires 16kHz)
|
|
2720
2720
|
},
|
|
2721
|
-
// FLAME
|
|
2722
|
-
//
|
|
2721
|
+
// FLAME unified template model CDN
|
|
2722
|
+
// Single compressed model shared by all characters
|
|
2723
2723
|
flame: {
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
teethNpz: "teeth.npz"
|
|
2730
|
-
}
|
|
2724
|
+
cdn: {
|
|
2725
|
+
default: "https://cdn.spatialwalk.top/public",
|
|
2726
|
+
intl: "https://cdn.spatialwalk.cloud/public"
|
|
2727
|
+
},
|
|
2728
|
+
unifiedModelPath: "base_model.pb.gz"
|
|
2731
2729
|
}
|
|
2732
2730
|
};
|
|
2733
2731
|
var t = "undefined" != typeof window ? window : void 0, i = "undefined" != typeof globalThis ? globalThis : t;
|
|
@@ -9510,7 +9508,7 @@ const _AnimationPlayer = class _AnimationPlayer {
|
|
|
9510
9508
|
if (this.streamingPlayer) {
|
|
9511
9509
|
return;
|
|
9512
9510
|
}
|
|
9513
|
-
const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-
|
|
9511
|
+
const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-tmb18x3O.js");
|
|
9514
9512
|
const { AvatarSDK: AvatarSDK2 } = await Promise.resolve().then(() => AvatarSDK$1);
|
|
9515
9513
|
const audioFormat = AvatarSDK2.getAudioFormat();
|
|
9516
9514
|
this.streamingPlayer = new StreamingAudioPlayer({
|
|
@@ -11312,24 +11310,8 @@ class AvatarSDK {
|
|
|
11312
11310
|
try {
|
|
11313
11311
|
const { AvatarDownloader: AvatarDownloader2 } = await Promise.resolve().then(() => AvatarDownloader$1);
|
|
11314
11312
|
const downloader = new AvatarDownloader2();
|
|
11315
|
-
|
|
11316
|
-
const
|
|
11317
|
-
const unifiedModelUrl = `${cdnBase}/base_model.pb.gz`;
|
|
11318
|
-
try {
|
|
11319
|
-
const response = await fetch(unifiedModelUrl);
|
|
11320
|
-
if (response.ok) {
|
|
11321
|
-
const decompressedStream = response.body.pipeThrough(new DecompressionStream("gzip"));
|
|
11322
|
-
const decompressedResponse = new Response(decompressedStream);
|
|
11323
|
-
const buffer = await decompressedResponse.arrayBuffer();
|
|
11324
|
-
templateResources = { unifiedModel: buffer };
|
|
11325
|
-
logger.log(`[AvatarSDK] Using unified model (${(buffer.byteLength / 1024 / 1024).toFixed(1)} MB)`);
|
|
11326
|
-
} else {
|
|
11327
|
-
logger.log("[AvatarSDK] Unified model not available, falling back to multi-file mode");
|
|
11328
|
-
templateResources = await downloader.loadGlobalFlameResources();
|
|
11329
|
-
}
|
|
11330
|
-
} catch {
|
|
11331
|
-
templateResources = await downloader.loadGlobalFlameResources();
|
|
11332
|
-
}
|
|
11313
|
+
const environment = ((_a = this._configuration) == null ? void 0 : _a.environment) === Environment.intl ? "intl" : "cn";
|
|
11314
|
+
const templateResources = await downloader.loadUnifiedTemplate(environment);
|
|
11333
11315
|
const success = await this._avatarCore.loadTemplateResourcesFromBuffers(templateResources);
|
|
11334
11316
|
if (success) {
|
|
11335
11317
|
logger.log("[AvatarSDK] Template resources initialized successfully");
|
|
@@ -11566,7 +11548,7 @@ class AvatarSDK {
|
|
|
11566
11548
|
__publicField(AvatarSDK, "_initializationState", "uninitialized");
|
|
11567
11549
|
__publicField(AvatarSDK, "_initializingPromise", null);
|
|
11568
11550
|
__publicField(AvatarSDK, "_configuration", null);
|
|
11569
|
-
__publicField(AvatarSDK, "_version", "1.0.0-beta.
|
|
11551
|
+
__publicField(AvatarSDK, "_version", "1.0.0-beta.102");
|
|
11570
11552
|
__publicField(AvatarSDK, "_avatarCore", null);
|
|
11571
11553
|
__publicField(AvatarSDK, "_dynamicSdkConfig", null);
|
|
11572
11554
|
__publicField(AvatarSDK, "_cachedDeviceScore", null);
|
|
@@ -13532,12 +13514,14 @@ class AvatarController {
|
|
|
13532
13514
|
this.notifyConversationState(AvatarState.idle);
|
|
13533
13515
|
this.emit("stopRendering");
|
|
13534
13516
|
if (this.frameStarvationEvents.length > 0) {
|
|
13517
|
+
const hasReqEnd = this.frameStarvationEvents.some((e2) => e2.reqEnd);
|
|
13535
13518
|
logEvent("character_player", "warning", {
|
|
13536
13519
|
event: "frame_starvation",
|
|
13537
13520
|
avatar_id: this.avatar.id,
|
|
13538
13521
|
conversationId: ((_a2 = this.networkLayer) == null ? void 0 : _a2.getCurrentConversationId()) || void 0,
|
|
13539
13522
|
starvation_count: this.frameStarvationEvents.length,
|
|
13540
|
-
starvation_times: this.frameStarvationEvents.map((e2) => Number(e2.audioTime.toFixed(3)))
|
|
13523
|
+
starvation_times: this.frameStarvationEvents.map((e2) => Number(e2.audioTime.toFixed(3))),
|
|
13524
|
+
...hasReqEnd ? { req_end: true } : {}
|
|
13541
13525
|
});
|
|
13542
13526
|
}
|
|
13543
13527
|
this.frameStarvationEvents = [];
|
|
@@ -13691,7 +13675,11 @@ class AvatarController {
|
|
|
13691
13675
|
if (!this.isFrameStarved) {
|
|
13692
13676
|
this.isFrameStarved = true;
|
|
13693
13677
|
const audioTime = ((_a = this.animationPlayer) == null ? void 0 : _a.getCurrentTime()) ?? 0;
|
|
13694
|
-
this.
|
|
13678
|
+
const totalKeyframes = this.currentKeyframes.length + this.keyframesOffset;
|
|
13679
|
+
const frameDiff = frameIndex - totalKeyframes;
|
|
13680
|
+
if (!(this.reqEnd && frameDiff <= 1)) {
|
|
13681
|
+
this.frameStarvationEvents.push({ audioTime, reqEnd: this.reqEnd });
|
|
13682
|
+
}
|
|
13695
13683
|
}
|
|
13696
13684
|
} else {
|
|
13697
13685
|
this.isFrameStarved = false;
|
|
@@ -14395,136 +14383,60 @@ class AvatarDownloader {
|
|
|
14395
14383
|
this.baseAssetsPath = baseAssetsPath;
|
|
14396
14384
|
}
|
|
14397
14385
|
/**
|
|
14398
|
-
* Load template
|
|
14399
|
-
*
|
|
14386
|
+
* Load unified template model (single gzip-compressed file)
|
|
14387
|
+
* Includes PWA cache, retry, integrity check, and telemetry
|
|
14400
14388
|
* @internal
|
|
14401
14389
|
*/
|
|
14402
|
-
async
|
|
14403
|
-
var _a, _b, _c, _d;
|
|
14404
|
-
await PwaCacheManager.checkTemplateCacheVersion();
|
|
14405
|
-
const useApiResources = flameResources && Object.keys(flameResources).length > 0;
|
|
14406
|
-
if (!useApiResources) {
|
|
14407
|
-
logger.log("Template resources not provided in CharacterMeta, using global CDN config");
|
|
14408
|
-
return this.loadGlobalFlameResources(progressCallback);
|
|
14409
|
-
}
|
|
14410
|
-
logger.log("Using template resources from CharacterMeta API");
|
|
14411
|
-
const templateFiles = {
|
|
14412
|
-
flameModel: {
|
|
14413
|
-
url: (_a = flameResources.flameModel) == null ? void 0 : _a.remote,
|
|
14414
|
-
resourceName: "model.pb"
|
|
14415
|
-
},
|
|
14416
|
-
flameTemplate: {
|
|
14417
|
-
url: (_b = flameResources.flameTemplate) == null ? void 0 : _b.remote,
|
|
14418
|
-
resourceName: "flame_template.pb"
|
|
14419
|
-
},
|
|
14420
|
-
teethPb: {
|
|
14421
|
-
url: (_c = flameResources.teethPb) == null ? void 0 : _c.remote,
|
|
14422
|
-
resourceName: "teeth.pb"
|
|
14423
|
-
},
|
|
14424
|
-
teethNpz: {
|
|
14425
|
-
url: (_d = flameResources.teethNpz) == null ? void 0 : _d.remote,
|
|
14426
|
-
resourceName: "teeth.npz"
|
|
14427
|
-
}
|
|
14428
|
-
};
|
|
14429
|
-
const totalFiles = Object.keys(templateFiles).length;
|
|
14430
|
-
let loadedFiles = 0;
|
|
14431
|
-
const updateProgress = (filename, loaded) => {
|
|
14432
|
-
if (progressCallback) {
|
|
14433
|
-
if (loaded)
|
|
14434
|
-
loadedFiles++;
|
|
14435
|
-
progressCallback({
|
|
14436
|
-
stage: "template",
|
|
14437
|
-
filename,
|
|
14438
|
-
loaded: loadedFiles,
|
|
14439
|
-
total: totalFiles,
|
|
14440
|
-
progress: Math.round(loadedFiles / totalFiles * 100)
|
|
14441
|
-
});
|
|
14442
|
-
}
|
|
14443
|
-
};
|
|
14444
|
-
const templateResources = {};
|
|
14445
|
-
const promises = Object.entries(templateFiles).map(async ([key, { url, resourceName }]) => {
|
|
14446
|
-
if (!url) {
|
|
14447
|
-
throw new Error(`[loadTemplateResources] Missing CDN URL for ${key} (${resourceName})`);
|
|
14448
|
-
}
|
|
14449
|
-
updateProgress(resourceName, false);
|
|
14450
|
-
logger.log(`📥 Loading ${key} from API CDN: ${url}`);
|
|
14451
|
-
const { data: buffer } = await downloadResource(url, { resourceType: "template" });
|
|
14452
|
-
logger.log(`✅ ${key} loaded: ${buffer.byteLength} bytes`);
|
|
14453
|
-
templateResources[key] = buffer;
|
|
14454
|
-
updateProgress(resourceName, true);
|
|
14455
|
-
});
|
|
14456
|
-
await Promise.all(promises);
|
|
14457
|
-
return templateResources;
|
|
14458
|
-
}
|
|
14459
|
-
/**
|
|
14460
|
-
* Load global FLAME template resources from CDN
|
|
14461
|
-
* Uses centralized FLAME CDN config (shared across all characters)
|
|
14462
|
-
* @internal 供内部使用(测试、调试等场景),SDK 初始化默认使用本地打包的资源
|
|
14463
|
-
*/
|
|
14464
|
-
async loadGlobalFlameResources(progressCallback = null) {
|
|
14465
|
-
var _a;
|
|
14390
|
+
async loadUnifiedTemplate(environment) {
|
|
14466
14391
|
await PwaCacheManager.checkTemplateCacheVersion();
|
|
14467
14392
|
const startTime = Date.now();
|
|
14468
|
-
const {
|
|
14469
|
-
const
|
|
14470
|
-
|
|
14471
|
-
|
|
14472
|
-
|
|
14473
|
-
|
|
14474
|
-
flameTemplate: {
|
|
14475
|
-
url: `${cdnBaseUrl}/${resources.flameTemplate}`,
|
|
14476
|
-
resourceName: resources.flameTemplate
|
|
14477
|
-
},
|
|
14478
|
-
teethPb: {
|
|
14479
|
-
url: `${cdnBaseUrl}/${resources.teethPb}`,
|
|
14480
|
-
resourceName: resources.teethPb
|
|
14481
|
-
},
|
|
14482
|
-
teethNpz: {
|
|
14483
|
-
url: `${cdnBaseUrl}/${resources.teethNpz}`,
|
|
14484
|
-
resourceName: resources.teethNpz
|
|
14485
|
-
}
|
|
14486
|
-
};
|
|
14487
|
-
const totalFiles = Object.keys(templateFiles).length;
|
|
14488
|
-
let loadedFiles = 0;
|
|
14489
|
-
const updateProgress = (filename, loaded) => {
|
|
14490
|
-
if (progressCallback) {
|
|
14491
|
-
if (loaded)
|
|
14492
|
-
loadedFiles++;
|
|
14493
|
-
progressCallback({
|
|
14494
|
-
stage: "template",
|
|
14495
|
-
filename,
|
|
14496
|
-
loaded: loadedFiles,
|
|
14497
|
-
total: totalFiles,
|
|
14498
|
-
progress: Math.round(loadedFiles / totalFiles * 100)
|
|
14499
|
-
});
|
|
14500
|
-
}
|
|
14501
|
-
};
|
|
14502
|
-
const templateResources = {};
|
|
14503
|
-
const cacheInfos = [];
|
|
14504
|
-
try {
|
|
14505
|
-
const promises = Object.entries(templateFiles).map(async ([key, { url, resourceName }]) => {
|
|
14506
|
-
updateProgress(resourceName, false);
|
|
14507
|
-
logger.log(`📥 Loading ${key} from global CDN: ${url}`);
|
|
14508
|
-
const { data: buffer, cacheInfo } = await downloadResource(url, { resourceType: "template" });
|
|
14509
|
-
logger.log(`✅ ${key} loaded: ${buffer.byteLength} bytes`);
|
|
14510
|
-
templateResources[key] = buffer;
|
|
14511
|
-
cacheInfos.push(cacheInfo);
|
|
14512
|
-
updateProgress(resourceName, true);
|
|
14513
|
-
});
|
|
14514
|
-
await Promise.all(promises);
|
|
14515
|
-
const cacheHit = cacheInfos.length > 0 && cacheInfos.every((info) => info.cacheHit);
|
|
14516
|
-
const cacheType = ((_a = cacheInfos[0]) == null ? void 0 : _a.cacheType) || "none";
|
|
14393
|
+
const { cdn, unifiedModelPath } = APP_CONFIG.flame;
|
|
14394
|
+
const cdnBase = environment === "intl" ? cdn.intl : cdn.default;
|
|
14395
|
+
const url = `${cdnBase}/${unifiedModelPath}`;
|
|
14396
|
+
logger.log(`📥 Loading unified template from: ${url}`);
|
|
14397
|
+
const cached = await PwaCacheManager.getTemplateResource(url);
|
|
14398
|
+
if (cached) {
|
|
14517
14399
|
const duration = Date.now() - startTime;
|
|
14400
|
+
logger.log(`✅ Unified template loaded from cache (${(cached.byteLength / 1024 / 1024).toFixed(1)} MB)`);
|
|
14518
14401
|
logEvent("template_resources_load_measure", "info", {
|
|
14519
14402
|
duration,
|
|
14520
|
-
file_count:
|
|
14521
|
-
cache_hit:
|
|
14522
|
-
cache_type:
|
|
14403
|
+
file_count: 1,
|
|
14404
|
+
cache_hit: true,
|
|
14405
|
+
cache_type: "pwa"
|
|
14523
14406
|
});
|
|
14524
|
-
return
|
|
14525
|
-
}
|
|
14526
|
-
|
|
14407
|
+
return { unifiedModel: cached };
|
|
14408
|
+
}
|
|
14409
|
+
const maxRetries = 3;
|
|
14410
|
+
let lastError = null;
|
|
14411
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
14412
|
+
try {
|
|
14413
|
+
const response = await fetch(url);
|
|
14414
|
+
if (!response.ok) {
|
|
14415
|
+
throw new Error(`HTTP ${response.status} ${response.statusText}`);
|
|
14416
|
+
}
|
|
14417
|
+
const decompressedStream = response.body.pipeThrough(new DecompressionStream("gzip"));
|
|
14418
|
+
const decompressedResponse = new Response(decompressedStream);
|
|
14419
|
+
const buffer = await decompressedResponse.arrayBuffer();
|
|
14420
|
+
logger.log(`✅ Unified template loaded (${(buffer.byteLength / 1024 / 1024).toFixed(1)} MB)`);
|
|
14421
|
+
PwaCacheManager.putTemplateResource(url, buffer).catch((err) => {
|
|
14422
|
+
logger.warn(`[loadUnifiedTemplate] Failed to cache:`, err);
|
|
14423
|
+
});
|
|
14424
|
+
const duration = Date.now() - startTime;
|
|
14425
|
+
logEvent("template_resources_load_measure", "info", {
|
|
14426
|
+
duration,
|
|
14427
|
+
file_count: 1,
|
|
14428
|
+
cache_hit: false,
|
|
14429
|
+
cache_type: "none"
|
|
14430
|
+
});
|
|
14431
|
+
return { unifiedModel: buffer };
|
|
14432
|
+
} catch (err) {
|
|
14433
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
14434
|
+
if (attempt < maxRetries) {
|
|
14435
|
+
logger.warn(`[loadUnifiedTemplate] Attempt ${attempt}/${maxRetries} failed, retrying...`);
|
|
14436
|
+
}
|
|
14437
|
+
}
|
|
14527
14438
|
}
|
|
14439
|
+
throw lastError || new Error(`Failed to download unified template after ${maxRetries} attempts`);
|
|
14528
14440
|
}
|
|
14529
14441
|
/**
|
|
14530
14442
|
* Load camera settings from CharacterMeta (optional)
|
|
@@ -14598,7 +14510,7 @@ class AvatarDownloader {
|
|
|
14598
14510
|
filename,
|
|
14599
14511
|
loaded: loadedFiles,
|
|
14600
14512
|
total: totalFiles,
|
|
14601
|
-
progress:
|
|
14513
|
+
progress: loadedFiles / totalFiles
|
|
14602
14514
|
});
|
|
14603
14515
|
}
|
|
14604
14516
|
};
|
|
@@ -14999,7 +14911,7 @@ const _AvatarManager = class _AvatarManager {
|
|
|
14999
14911
|
if (cachedVersion === newVersion && cachedModelType === requestedModelType) {
|
|
15000
14912
|
logger.log(`[AvatarManager] Avatar ${id} found in cache with same version (${cachedVersion}) and model type (${cachedModelType}), returning cached`);
|
|
15001
14913
|
cached.updateCharacterMeta(characterMeta);
|
|
15002
|
-
this.notifyAllRequests(task, { type: LoadProgress.downloading, progress:
|
|
14914
|
+
this.notifyAllRequests(task, { type: LoadProgress.downloading, progress: 1 });
|
|
15003
14915
|
return cached;
|
|
15004
14916
|
} else {
|
|
15005
14917
|
logger.log(`[AvatarManager] Avatar ${id} cache invalid: version=${cachedVersion}->${newVersion}, modelType=${cachedModelType}->${requestedModelType}, reloading...`);
|
|
@@ -15009,7 +14921,7 @@ const _AvatarManager = class _AvatarManager {
|
|
|
15009
14921
|
}
|
|
15010
14922
|
}
|
|
15011
14923
|
const totalAssets = this.countTotalAssets(characterMeta);
|
|
15012
|
-
const metaProgress =
|
|
14924
|
+
const metaProgress = 1 / (1 + totalAssets);
|
|
15013
14925
|
this.notifyAllRequests(task, { type: LoadProgress.downloading, progress: metaProgress });
|
|
15014
14926
|
if (signal.aborted) {
|
|
15015
14927
|
throw new Error("Task cancelled");
|
|
@@ -15023,7 +14935,7 @@ const _AvatarManager = class _AvatarManager {
|
|
|
15023
14935
|
progressCallback: (info) => {
|
|
15024
14936
|
if (info.loaded > downloadedCount) {
|
|
15025
14937
|
downloadedCount = info.loaded;
|
|
15026
|
-
const progress =
|
|
14938
|
+
const progress = (1 + downloadedCount) / (1 + totalAssets);
|
|
15027
14939
|
this.notifyAllRequests(task, { type: LoadProgress.downloading, progress });
|
|
15028
14940
|
}
|
|
15029
14941
|
}
|
package/dist/index.js
CHANGED
package/package.json
CHANGED