@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-C0A1HA8M.js";
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 global template resource CDN
2722
- // These resources are base templates shared by all characters
2721
+ // FLAME unified template model CDN
2722
+ // Single compressed model shared by all characters
2723
2723
  flame: {
2724
- cdnBaseUrl: "https://cdn.spatialwalk.top/public",
2725
- resources: {
2726
- flameModel: "model.pb",
2727
- flameTemplate: "fm/template.pb",
2728
- teethPb: "fm/teeth.pb",
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-BULgPjpe.js");
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
- let templateResources;
11316
- const cdnBase = ((_a = this._configuration) == null ? void 0 : _a.environment) === Environment.intl ? "https://cdn.spatialwalk.cloud/public" : "https://cdn.spatialwalk.top/public";
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.101");
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.frameStarvationEvents.push({ audioTime });
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 resources from CharacterMeta flame CDN URLs
14399
- * Falls back to global CDN config if not provided by API
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 loadTemplateResources(flameResources, progressCallback = null) {
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 { cdnBaseUrl, resources } = APP_CONFIG.flame;
14469
- const templateFiles = {
14470
- flameModel: {
14471
- url: `${cdnBaseUrl}/${resources.flameModel}`,
14472
- resourceName: resources.flameModel
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: totalFiles,
14521
- cache_hit: cacheHit,
14522
- cache_type: cacheType
14403
+ file_count: 1,
14404
+ cache_hit: true,
14405
+ cache_type: "pwa"
14523
14406
  });
14524
- return templateResources;
14525
- } catch (error) {
14526
- throw error;
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: Math.round(loadedFiles / totalFiles * 100)
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: 100 });
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 = Math.round(1 / (1 + totalAssets) * 100);
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 = Math.round((1 + downloadedCount) / (1 + totalAssets) * 100);
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
@@ -1,4 +1,4 @@
1
- import { b, c, m, f, d, j, g, C, i, D, E, k, h, L, R, n } from "./index-C0A1HA8M.js";
1
+ import { b, c, m, f, d, j, g, C, i, D, E, k, h, L, R, n } from "./index-DNq7oTVY.js";
2
2
  export {
3
3
  b as Avatar,
4
4
  c as AvatarController,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@spatialwalk/avatarkit",
3
3
  "type": "module",
4
- "version": "1.0.0-beta.101",
4
+ "version": "1.0.0-beta.102",
5
5
  "packageManager": "pnpm@10.18.2",
6
6
  "description": "AvatarKit SDK - 3D Gaussian Splatting Avatar Rendering SDK",
7
7
  "author": "AvatarKit Team",