@spatialwalk/avatarkit 1.0.0-beta.46 → 1.0.0-beta.48
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 +13 -0
- package/dist/{StreamingAudioPlayer-BlY4b-HD.js → StreamingAudioPlayer-BCUbWwGa.js} +1 -1
- package/dist/core/AvatarController.d.ts +4 -0
- package/dist/{index-DJMm01Eu.js → index-C0EU_1kt.js} +327 -13
- package/dist/index.js +1 -1
- package/dist/utils/pwa-cache-manager.d.ts +16 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ 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.48] - 2025-01-05
|
|
9
|
+
|
|
10
|
+
### ✨ New Features
|
|
11
|
+
- **PWA Cache Management** - Added automatic PWA cache management for character and template resources to improve loading performance
|
|
12
|
+
|
|
13
|
+
### 🔧 Performance Improvements
|
|
14
|
+
- **Cache Hit Rate Metrics** - Resource downloads now report cache status for analytics
|
|
15
|
+
|
|
16
|
+
## [1.0.0-beta.47] - 2025-12-29
|
|
17
|
+
|
|
18
|
+
### 🐛 Bugfix
|
|
19
|
+
- **Avatar Meta Update** - Fixed issue where cached Avatar instances would return stale character metadata even when the latest metadata was fetched. Now the character metadata is always updated to the latest version when loading an avatar, even if the version number hasn't changed.
|
|
20
|
+
|
|
8
21
|
## [1.0.0-beta.46] - 2025-12-29
|
|
9
22
|
|
|
10
23
|
### 🐛 Bugfix
|
|
@@ -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, e as errorToMessage, l as logEvent, a as logger } from "./index-
|
|
4
|
+
import { A as APP_CONFIG, e as errorToMessage, l as logEvent, a as logger } from "./index-C0EU_1kt.js";
|
|
5
5
|
class StreamingAudioPlayer {
|
|
6
6
|
constructor(options) {
|
|
7
7
|
__publicField(this, "audioContext", null);
|
|
@@ -32,6 +32,10 @@ export declare class AvatarController {
|
|
|
32
32
|
private lastSyncLogTime;
|
|
33
33
|
private lastOutOfBoundsState;
|
|
34
34
|
private isAudioOnlyMode;
|
|
35
|
+
private playbackStuckCheckState;
|
|
36
|
+
private readonly MAX_AUDIO_TIME_ZERO_COUNT;
|
|
37
|
+
private readonly MAX_AUDIO_TIME_STUCK_COUNT;
|
|
38
|
+
private readonly AUDIO_TIME_STUCK_THRESHOLD;
|
|
35
39
|
private hostModeMetrics;
|
|
36
40
|
private readonly audioBytesPerSecond;
|
|
37
41
|
constructor(avatar: Avatar, options?: {
|
|
@@ -13,6 +13,9 @@ class Avatar {
|
|
|
13
13
|
getCharacterMeta() {
|
|
14
14
|
return this.characterMeta;
|
|
15
15
|
}
|
|
16
|
+
updateCharacterMeta(newMeta) {
|
|
17
|
+
this.characterMeta = newMeta;
|
|
18
|
+
}
|
|
16
19
|
getResources() {
|
|
17
20
|
return this.resources;
|
|
18
21
|
}
|
|
@@ -7573,7 +7576,7 @@ const _AnimationPlayer = class _AnimationPlayer {
|
|
|
7573
7576
|
if (this.streamingPlayer) {
|
|
7574
7577
|
return;
|
|
7575
7578
|
}
|
|
7576
|
-
const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-
|
|
7579
|
+
const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-BCUbWwGa.js");
|
|
7577
7580
|
const { AvatarSDK: AvatarSDK2 } = await Promise.resolve().then(() => AvatarSDK$1);
|
|
7578
7581
|
const audioFormat = AvatarSDK2.getAudioFormat();
|
|
7579
7582
|
this.streamingPlayer = new StreamingAudioPlayer({
|
|
@@ -8963,7 +8966,7 @@ class AvatarSDK {
|
|
|
8963
8966
|
}
|
|
8964
8967
|
__publicField(AvatarSDK, "_isInitialized", false);
|
|
8965
8968
|
__publicField(AvatarSDK, "_configuration", null);
|
|
8966
|
-
__publicField(AvatarSDK, "_version", "1.0.0-beta.
|
|
8969
|
+
__publicField(AvatarSDK, "_version", "1.0.0-beta.48");
|
|
8967
8970
|
__publicField(AvatarSDK, "_avatarCore", null);
|
|
8968
8971
|
__publicField(AvatarSDK, "_dynamicSdkConfig", null);
|
|
8969
8972
|
const AvatarSDK$1 = Object.freeze(Object.defineProperty({
|
|
@@ -10632,6 +10635,15 @@ class AvatarController {
|
|
|
10632
10635
|
__publicField(this, "lastSyncLogTime", 0);
|
|
10633
10636
|
__publicField(this, "lastOutOfBoundsState", false);
|
|
10634
10637
|
__publicField(this, "isAudioOnlyMode", false);
|
|
10638
|
+
__publicField(this, "playbackStuckCheckState", {
|
|
10639
|
+
audioTimeZeroCount: 0,
|
|
10640
|
+
lastAudioTime: 0,
|
|
10641
|
+
audioTimeStuckCount: 0,
|
|
10642
|
+
reported: false
|
|
10643
|
+
});
|
|
10644
|
+
__publicField(this, "MAX_AUDIO_TIME_ZERO_COUNT", 60);
|
|
10645
|
+
__publicField(this, "MAX_AUDIO_TIME_STUCK_COUNT", 60);
|
|
10646
|
+
__publicField(this, "AUDIO_TIME_STUCK_THRESHOLD", 1e-3);
|
|
10635
10647
|
__publicField(this, "hostModeMetrics", {
|
|
10636
10648
|
accumulatedBytes: 0,
|
|
10637
10649
|
startTimestamp: 0,
|
|
@@ -11237,6 +11249,68 @@ class AvatarController {
|
|
|
11237
11249
|
this.isPlaying = false;
|
|
11238
11250
|
}
|
|
11239
11251
|
}
|
|
11252
|
+
checkPlaybackStuck(audioTime) {
|
|
11253
|
+
var _a, _b, _c, _d, _e2, _f;
|
|
11254
|
+
const streamingPlayer = (_a = this.animationPlayer) == null ? void 0 : _a.getStreamingPlayer();
|
|
11255
|
+
if (!streamingPlayer) {
|
|
11256
|
+
return false;
|
|
11257
|
+
}
|
|
11258
|
+
const state = this.playbackStuckCheckState;
|
|
11259
|
+
const hasAnimationData = this.currentKeyframes.length > 0;
|
|
11260
|
+
const hasAudioData = ((_b = streamingPlayer.audioChunks) == null ? void 0 : _b.length) > 0;
|
|
11261
|
+
const isNotPaused = this.currentState !== AvatarState.paused;
|
|
11262
|
+
if (!hasAnimationData || !hasAudioData || this.currentState === AvatarState.paused) {
|
|
11263
|
+
state.audioTimeZeroCount = 0;
|
|
11264
|
+
state.audioTimeStuckCount = 0;
|
|
11265
|
+
state.lastAudioTime = 0;
|
|
11266
|
+
state.reported = false;
|
|
11267
|
+
return false;
|
|
11268
|
+
}
|
|
11269
|
+
if (state.reported) {
|
|
11270
|
+
return true;
|
|
11271
|
+
}
|
|
11272
|
+
const audioContextState = ((_c = streamingPlayer.audioContext) == null ? void 0 : _c.state) || "unknown";
|
|
11273
|
+
const isAudioContextSuspended = audioContextState === "suspended";
|
|
11274
|
+
const activeSourcesCount = ((_d = streamingPlayer.activeSources) == null ? void 0 : _d.size) || 0;
|
|
11275
|
+
const scheduledChunks = streamingPlayer.scheduledChunks || 0;
|
|
11276
|
+
const hasScheduledButNoActive = scheduledChunks > 0 && activeSourcesCount === 0;
|
|
11277
|
+
if (audioTime === 0) {
|
|
11278
|
+
state.audioTimeZeroCount++;
|
|
11279
|
+
} else {
|
|
11280
|
+
state.audioTimeZeroCount = 0;
|
|
11281
|
+
}
|
|
11282
|
+
if (Math.abs(audioTime - state.lastAudioTime) < this.AUDIO_TIME_STUCK_THRESHOLD) {
|
|
11283
|
+
state.audioTimeStuckCount++;
|
|
11284
|
+
} else {
|
|
11285
|
+
state.audioTimeStuckCount = 0;
|
|
11286
|
+
}
|
|
11287
|
+
state.lastAudioTime = audioTime;
|
|
11288
|
+
const shouldReport = state.audioTimeZeroCount > this.MAX_AUDIO_TIME_ZERO_COUNT || state.audioTimeStuckCount > this.MAX_AUDIO_TIME_STUCK_COUNT || isAudioContextSuspended && this.isPlaying || hasScheduledButNoActive && this.isPlaying;
|
|
11289
|
+
if (shouldReport && isNotPaused) {
|
|
11290
|
+
state.reported = true;
|
|
11291
|
+
logEvent("character_player", "error", {
|
|
11292
|
+
event: "playback_stuck_after_transition",
|
|
11293
|
+
avatar_id: this.avatar.id,
|
|
11294
|
+
conversationId: ((_e2 = this.networkLayer) == null ? void 0 : _e2.getCurrentConversationId()) || void 0,
|
|
11295
|
+
audioContextState,
|
|
11296
|
+
isAudioContextSuspended,
|
|
11297
|
+
audioTime,
|
|
11298
|
+
audioTimeZero: audioTime === 0,
|
|
11299
|
+
audioTimeZeroCount: state.audioTimeZeroCount,
|
|
11300
|
+
audioTimeStuck: state.audioTimeStuckCount > this.MAX_AUDIO_TIME_STUCK_COUNT,
|
|
11301
|
+
audioTimeStuckCount: state.audioTimeStuckCount,
|
|
11302
|
+
hasScheduledButNoActive,
|
|
11303
|
+
isPlaying: this.isPlaying,
|
|
11304
|
+
currentState: this.currentState,
|
|
11305
|
+
animationFramesCount: this.currentKeyframes.length,
|
|
11306
|
+
audioChunksCount: ((_f = streamingPlayer.audioChunks) == null ? void 0 : _f.length) || 0,
|
|
11307
|
+
scheduledChunks,
|
|
11308
|
+
activeSourcesCount
|
|
11309
|
+
});
|
|
11310
|
+
return true;
|
|
11311
|
+
}
|
|
11312
|
+
return false;
|
|
11313
|
+
}
|
|
11240
11314
|
startPlaybackLoop() {
|
|
11241
11315
|
if (this.playbackLoopId) {
|
|
11242
11316
|
return;
|
|
@@ -11245,14 +11319,23 @@ class AvatarController {
|
|
|
11245
11319
|
const playLoop = async () => {
|
|
11246
11320
|
if (!this.isPlaying || this.currentState === AvatarState.paused || !this.animationPlayer) {
|
|
11247
11321
|
this.playbackLoopId = null;
|
|
11322
|
+
this.playbackStuckCheckState.audioTimeZeroCount = 0;
|
|
11323
|
+
this.playbackStuckCheckState.audioTimeStuckCount = 0;
|
|
11324
|
+
this.playbackStuckCheckState.lastAudioTime = 0;
|
|
11325
|
+
this.playbackStuckCheckState.reported = false;
|
|
11248
11326
|
return;
|
|
11249
11327
|
}
|
|
11250
11328
|
try {
|
|
11251
11329
|
const audioTime = this.animationPlayer.getCurrentTime();
|
|
11330
|
+
this.checkPlaybackStuck(audioTime);
|
|
11252
11331
|
if (audioTime === 0) {
|
|
11253
11332
|
this.playbackLoopId = requestAnimationFrame(playLoop);
|
|
11254
11333
|
return;
|
|
11255
11334
|
}
|
|
11335
|
+
if (audioTime > 0 && Math.abs(audioTime - this.playbackStuckCheckState.lastAudioTime) >= this.AUDIO_TIME_STUCK_THRESHOLD) {
|
|
11336
|
+
this.playbackStuckCheckState.audioTimeZeroCount = 0;
|
|
11337
|
+
this.playbackStuckCheckState.audioTimeStuckCount = 0;
|
|
11338
|
+
}
|
|
11256
11339
|
let frameIndex = Math.floor(audioTime * fps);
|
|
11257
11340
|
if (frameIndex < 0) frameIndex = 0;
|
|
11258
11341
|
let arrayIndex = frameIndex - this.keyframesOffset;
|
|
@@ -11555,13 +11638,197 @@ function errorToMessage(err) {
|
|
|
11555
11638
|
}
|
|
11556
11639
|
return String(err);
|
|
11557
11640
|
}
|
|
11558
|
-
|
|
11641
|
+
const _PwaCacheManager = class _PwaCacheManager {
|
|
11642
|
+
static isSupported() {
|
|
11643
|
+
return typeof caches !== "undefined";
|
|
11644
|
+
}
|
|
11645
|
+
static getCharacterCacheName(characterId) {
|
|
11646
|
+
return `${_PwaCacheManager.CHARACTER_CACHE_PREFIX}${characterId}${_PwaCacheManager.CHARACTER_CACHE_SUFFIX}`;
|
|
11647
|
+
}
|
|
11648
|
+
static async getCharacterResource(characterId, url) {
|
|
11649
|
+
if (!_PwaCacheManager.isSupported()) {
|
|
11650
|
+
return null;
|
|
11651
|
+
}
|
|
11652
|
+
try {
|
|
11653
|
+
const cacheName = _PwaCacheManager.getCharacterCacheName(characterId);
|
|
11654
|
+
const cache = await caches.open(cacheName);
|
|
11655
|
+
const response = await cache.match(url);
|
|
11656
|
+
if (response) {
|
|
11657
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
11658
|
+
logger.log(`[PwaCacheManager] Character resource cache hit: ${url}`);
|
|
11659
|
+
return arrayBuffer;
|
|
11660
|
+
}
|
|
11661
|
+
return null;
|
|
11662
|
+
} catch (error) {
|
|
11663
|
+
logger.warn(`[PwaCacheManager] Failed to get character resource from cache:`, error);
|
|
11664
|
+
return null;
|
|
11665
|
+
}
|
|
11666
|
+
}
|
|
11667
|
+
static async putCharacterResource(characterId, url, data) {
|
|
11668
|
+
if (!_PwaCacheManager.isSupported()) {
|
|
11669
|
+
return;
|
|
11670
|
+
}
|
|
11671
|
+
try {
|
|
11672
|
+
const cacheName = _PwaCacheManager.getCharacterCacheName(characterId);
|
|
11673
|
+
const cache = await caches.open(cacheName);
|
|
11674
|
+
const keys = await cache.keys();
|
|
11675
|
+
if (keys.length >= _PwaCacheManager.MAX_CHARACTER_CACHE_ENTRIES) {
|
|
11676
|
+
const oldestKey = keys[0];
|
|
11677
|
+
await cache.delete(oldestKey);
|
|
11678
|
+
logger.log(`[PwaCacheManager] Character cache full, deleted oldest entry: ${oldestKey.url}`);
|
|
11679
|
+
}
|
|
11680
|
+
await cache.put(url, new Response(data));
|
|
11681
|
+
logger.log(`[PwaCacheManager] Character resource cached: ${url}`);
|
|
11682
|
+
} catch (error) {
|
|
11683
|
+
logger.warn(`[PwaCacheManager] Failed to put character resource to cache:`, error);
|
|
11684
|
+
}
|
|
11685
|
+
}
|
|
11686
|
+
static async getTemplateResource(url) {
|
|
11687
|
+
if (!_PwaCacheManager.isSupported()) {
|
|
11688
|
+
return null;
|
|
11689
|
+
}
|
|
11690
|
+
try {
|
|
11691
|
+
const cache = await caches.open(_PwaCacheManager.TEMPLATE_CACHE_NAME);
|
|
11692
|
+
const response = await cache.match(url);
|
|
11693
|
+
if (response) {
|
|
11694
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
11695
|
+
logger.log(`[PwaCacheManager] Template resource cache hit: ${url}`);
|
|
11696
|
+
return arrayBuffer;
|
|
11697
|
+
}
|
|
11698
|
+
return null;
|
|
11699
|
+
} catch (error) {
|
|
11700
|
+
logger.warn(`[PwaCacheManager] Failed to get template resource from cache:`, error);
|
|
11701
|
+
return null;
|
|
11702
|
+
}
|
|
11703
|
+
}
|
|
11704
|
+
static async putTemplateResource(url, data) {
|
|
11705
|
+
if (!_PwaCacheManager.isSupported()) {
|
|
11706
|
+
return;
|
|
11707
|
+
}
|
|
11708
|
+
try {
|
|
11709
|
+
const cache = await caches.open(_PwaCacheManager.TEMPLATE_CACHE_NAME);
|
|
11710
|
+
await cache.put(url, new Response(data));
|
|
11711
|
+
logger.log(`[PwaCacheManager] Template resource cached: ${url}`);
|
|
11712
|
+
} catch (error) {
|
|
11713
|
+
logger.warn(`[PwaCacheManager] Failed to put template resource to cache:`, error);
|
|
11714
|
+
}
|
|
11715
|
+
}
|
|
11716
|
+
static async clearCharacterCache(characterId) {
|
|
11717
|
+
if (!_PwaCacheManager.isSupported()) {
|
|
11718
|
+
return;
|
|
11719
|
+
}
|
|
11720
|
+
try {
|
|
11721
|
+
const cacheName = _PwaCacheManager.getCharacterCacheName(characterId);
|
|
11722
|
+
await caches.delete(cacheName);
|
|
11723
|
+
logger.log(`[PwaCacheManager] Character cache cleared: ${characterId}`);
|
|
11724
|
+
} catch (error) {
|
|
11725
|
+
logger.warn(`[PwaCacheManager] Failed to clear character cache:`, error);
|
|
11726
|
+
}
|
|
11727
|
+
}
|
|
11728
|
+
static async checkTemplateCacheVersion(newVersion) {
|
|
11729
|
+
if (!_PwaCacheManager.isSupported()) {
|
|
11730
|
+
return false;
|
|
11731
|
+
}
|
|
11732
|
+
try {
|
|
11733
|
+
const storedVersion = localStorage.getItem(_PwaCacheManager.TEMPLATE_VERSION_STORAGE_KEY);
|
|
11734
|
+
if (storedVersion !== newVersion) {
|
|
11735
|
+
await caches.delete(_PwaCacheManager.TEMPLATE_CACHE_NAME);
|
|
11736
|
+
localStorage.setItem(_PwaCacheManager.TEMPLATE_VERSION_STORAGE_KEY, newVersion);
|
|
11737
|
+
logger.log(`[PwaCacheManager] Template cache version changed: ${storedVersion} -> ${newVersion}, cache cleared`);
|
|
11738
|
+
return true;
|
|
11739
|
+
}
|
|
11740
|
+
return false;
|
|
11741
|
+
} catch (error) {
|
|
11742
|
+
logger.warn(`[PwaCacheManager] Failed to check template cache version:`, error);
|
|
11743
|
+
return false;
|
|
11744
|
+
}
|
|
11745
|
+
}
|
|
11746
|
+
};
|
|
11747
|
+
__publicField(_PwaCacheManager, "TEMPLATE_CACHE_VERSION", "v1");
|
|
11748
|
+
__publicField(_PwaCacheManager, "TEMPLATE_CACHE_NAME", `spatialwalk-sdk-template-cache-${_PwaCacheManager.TEMPLATE_CACHE_VERSION}`);
|
|
11749
|
+
__publicField(_PwaCacheManager, "TEMPLATE_VERSION_STORAGE_KEY", "spatialwalk-sdk-template-cache-version");
|
|
11750
|
+
__publicField(_PwaCacheManager, "CHARACTER_CACHE_PREFIX", "spatialwalk-sdk-character-");
|
|
11751
|
+
__publicField(_PwaCacheManager, "CHARACTER_CACHE_SUFFIX", "-cache");
|
|
11752
|
+
__publicField(_PwaCacheManager, "MAX_CHARACTER_CACHE_ENTRIES", 1e3);
|
|
11753
|
+
let PwaCacheManager = _PwaCacheManager;
|
|
11754
|
+
const pwaCacheManager = Object.freeze(Object.defineProperty({
|
|
11755
|
+
__proto__: null,
|
|
11756
|
+
PwaCacheManager
|
|
11757
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
11758
|
+
function getCacheInfo(url, response) {
|
|
11759
|
+
var _a;
|
|
11760
|
+
const resourceTiming = performance.getEntriesByName(url, "resource")[0];
|
|
11761
|
+
const transferSize = (resourceTiming == null ? void 0 : resourceTiming.transferSize) ?? 0;
|
|
11762
|
+
const deliveryType = resourceTiming == null ? void 0 : resourceTiming.deliveryType;
|
|
11763
|
+
const hasServiceWorker = typeof navigator !== "undefined" && ((_a = navigator.serviceWorker) == null ? void 0 : _a.controller) !== null;
|
|
11764
|
+
const isPwaCache = transferSize === 0 && hasServiceWorker && (deliveryType === "cache" || deliveryType === "serviceworker") && (resourceTiming == null ? void 0 : resourceTiming.duration) !== void 0 && resourceTiming.duration > 0;
|
|
11765
|
+
const isBrowserCache = transferSize === 0 && !isPwaCache && (!hasServiceWorker || deliveryType === "cache") && (resourceTiming == null ? void 0 : resourceTiming.duration) !== void 0 && resourceTiming.duration > 0;
|
|
11766
|
+
const cfCacheStatus = response.headers.get("cf-cache-status");
|
|
11767
|
+
const xCache = response.headers.get("x-cache");
|
|
11768
|
+
const xSwiftCacheTime = response.headers.get("x-swift-cache-time");
|
|
11769
|
+
const age = response.headers.get("age");
|
|
11770
|
+
const cdnCacheStatus = cfCacheStatus || xCache || xSwiftCacheTime;
|
|
11771
|
+
const isCdnCache = cdnCacheStatus && (cdnCacheStatus.toLowerCase() === "hit" || cdnCacheStatus.toLowerCase() === "hits" || cdnCacheStatus.toLowerCase().includes("hit")) || age && parseInt(age, 10) > 0;
|
|
11772
|
+
let cacheHit = false;
|
|
11773
|
+
let cacheType = "none";
|
|
11774
|
+
if (isPwaCache) {
|
|
11775
|
+
cacheHit = true;
|
|
11776
|
+
cacheType = "pwa";
|
|
11777
|
+
} else if (isBrowserCache) {
|
|
11778
|
+
cacheHit = true;
|
|
11779
|
+
cacheType = "browser";
|
|
11780
|
+
} else if (isCdnCache) {
|
|
11781
|
+
cacheHit = true;
|
|
11782
|
+
cacheType = "cdn";
|
|
11783
|
+
}
|
|
11784
|
+
return {
|
|
11785
|
+
cacheHit,
|
|
11786
|
+
cacheType,
|
|
11787
|
+
transferSize,
|
|
11788
|
+
cdnCacheStatus: cdnCacheStatus || void 0
|
|
11789
|
+
};
|
|
11790
|
+
}
|
|
11791
|
+
async function downloadResource(url, options) {
|
|
11559
11792
|
try {
|
|
11793
|
+
let cached = null;
|
|
11794
|
+
let pwaCacheSubtype = void 0;
|
|
11795
|
+
if (options == null ? void 0 : options.characterId) {
|
|
11796
|
+
cached = await PwaCacheManager.getCharacterResource(options.characterId, url);
|
|
11797
|
+
if (cached) {
|
|
11798
|
+
pwaCacheSubtype = "character";
|
|
11799
|
+
}
|
|
11800
|
+
} else if ((options == null ? void 0 : options.resourceType) === "template") {
|
|
11801
|
+
cached = await PwaCacheManager.getTemplateResource(url);
|
|
11802
|
+
if (cached) {
|
|
11803
|
+
pwaCacheSubtype = "template";
|
|
11804
|
+
}
|
|
11805
|
+
}
|
|
11806
|
+
if (cached) {
|
|
11807
|
+
const response2 = new Response(cached);
|
|
11808
|
+
await new Promise((resolve2) => setTimeout(resolve2, 0));
|
|
11809
|
+
const cacheInfo2 = getCacheInfo(url, response2);
|
|
11810
|
+
cacheInfo2.cacheHit = true;
|
|
11811
|
+
cacheInfo2.cacheType = "pwa";
|
|
11812
|
+
cacheInfo2.pwaCacheSubtype = pwaCacheSubtype;
|
|
11813
|
+
return { data: cached, cacheInfo: cacheInfo2 };
|
|
11814
|
+
}
|
|
11560
11815
|
const response = await fetch(url);
|
|
11561
11816
|
if (!response.ok) {
|
|
11562
11817
|
throw new Error(`HTTP ${response.status} ${response.statusText}`);
|
|
11563
11818
|
}
|
|
11564
|
-
|
|
11819
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
11820
|
+
if (options == null ? void 0 : options.characterId) {
|
|
11821
|
+
PwaCacheManager.putCharacterResource(options.characterId, url, arrayBuffer).catch((err) => {
|
|
11822
|
+
logger.warn(`[downloadResource] Failed to cache character resource:`, err);
|
|
11823
|
+
});
|
|
11824
|
+
} else if ((options == null ? void 0 : options.resourceType) === "template") {
|
|
11825
|
+
PwaCacheManager.putTemplateResource(url, arrayBuffer).catch((err) => {
|
|
11826
|
+
logger.warn(`[downloadResource] Failed to cache template resource:`, err);
|
|
11827
|
+
});
|
|
11828
|
+
}
|
|
11829
|
+
await new Promise((resolve2) => setTimeout(resolve2, 0));
|
|
11830
|
+
const cacheInfo = getCacheInfo(url, response);
|
|
11831
|
+
return { data: arrayBuffer, cacheInfo };
|
|
11565
11832
|
} catch (err) {
|
|
11566
11833
|
const msg = errorToMessage(err);
|
|
11567
11834
|
throw new Error(`[downloadResource] ${url} → ${msg}`);
|
|
@@ -11574,6 +11841,8 @@ class AvatarDownloader {
|
|
|
11574
11841
|
}
|
|
11575
11842
|
async loadTemplateResources(flameResources, progressCallback = null) {
|
|
11576
11843
|
var _a, _b, _c, _d;
|
|
11844
|
+
const sdkVersion2 = AvatarSDK.version;
|
|
11845
|
+
await PwaCacheManager.checkTemplateCacheVersion(sdkVersion2);
|
|
11577
11846
|
const useApiResources = flameResources && Object.keys(flameResources).length > 0;
|
|
11578
11847
|
if (!useApiResources) {
|
|
11579
11848
|
logger.log("Template resources not provided in CharacterMeta, using global CDN config");
|
|
@@ -11620,15 +11889,26 @@ class AvatarDownloader {
|
|
|
11620
11889
|
}
|
|
11621
11890
|
updateProgress(resourceName, false);
|
|
11622
11891
|
logger.log(`📥 Loading ${key} from API CDN: ${url}`);
|
|
11623
|
-
const buffer = await downloadResource(url);
|
|
11892
|
+
const { data: buffer, cacheInfo } = await downloadResource(url, { resourceType: "template" });
|
|
11624
11893
|
logger.log(`✅ ${key} loaded: ${buffer.byteLength} bytes`);
|
|
11625
11894
|
templateResources[key] = buffer;
|
|
11626
11895
|
updateProgress(resourceName, true);
|
|
11896
|
+
logEvent("resource_download", "info", {
|
|
11897
|
+
resource_type: "template",
|
|
11898
|
+
resource_name: resourceName,
|
|
11899
|
+
cache_hit: cacheInfo.cacheHit,
|
|
11900
|
+
cache_type: cacheInfo.cacheType,
|
|
11901
|
+
pwa_cache_subtype: cacheInfo.pwaCacheSubtype,
|
|
11902
|
+
transfer_size: cacheInfo.transferSize,
|
|
11903
|
+
cdn_cache_status: cacheInfo.cdnCacheStatus
|
|
11904
|
+
});
|
|
11627
11905
|
});
|
|
11628
11906
|
await Promise.all(promises);
|
|
11629
11907
|
return templateResources;
|
|
11630
11908
|
}
|
|
11631
11909
|
async loadGlobalFlameResources(progressCallback = null) {
|
|
11910
|
+
const sdkVersion2 = AvatarSDK.version;
|
|
11911
|
+
await PwaCacheManager.checkTemplateCacheVersion(sdkVersion2);
|
|
11632
11912
|
const startTime = Date.now();
|
|
11633
11913
|
const { cdnBaseUrl, resources } = APP_CONFIG.flame;
|
|
11634
11914
|
const templateFiles = {
|
|
@@ -11669,10 +11949,19 @@ class AvatarDownloader {
|
|
|
11669
11949
|
const promises = Object.entries(templateFiles).map(async ([key, { url, resourceName }]) => {
|
|
11670
11950
|
updateProgress(resourceName, false);
|
|
11671
11951
|
logger.log(`📥 Loading ${key} from global CDN: ${url}`);
|
|
11672
|
-
const buffer = await downloadResource(url);
|
|
11952
|
+
const { data: buffer, cacheInfo } = await downloadResource(url, { resourceType: "template" });
|
|
11673
11953
|
logger.log(`✅ ${key} loaded: ${buffer.byteLength} bytes`);
|
|
11674
11954
|
templateResources[key] = buffer;
|
|
11675
11955
|
updateProgress(resourceName, true);
|
|
11956
|
+
logEvent("resource_download", "info", {
|
|
11957
|
+
resource_type: "template",
|
|
11958
|
+
resource_name: resourceName,
|
|
11959
|
+
cache_hit: cacheInfo.cacheHit,
|
|
11960
|
+
cache_type: cacheInfo.cacheType,
|
|
11961
|
+
pwa_cache_subtype: cacheInfo.pwaCacheSubtype,
|
|
11962
|
+
transfer_size: cacheInfo.transferSize,
|
|
11963
|
+
cdn_cache_status: cacheInfo.cdnCacheStatus
|
|
11964
|
+
});
|
|
11676
11965
|
});
|
|
11677
11966
|
await Promise.all(promises);
|
|
11678
11967
|
const duration = Date.now() - startTime;
|
|
@@ -11694,11 +11983,21 @@ class AvatarDownloader {
|
|
|
11694
11983
|
}
|
|
11695
11984
|
try {
|
|
11696
11985
|
logger.log(`📥 Loading camera info from: ${cameraUrl}`);
|
|
11697
|
-
const
|
|
11698
|
-
|
|
11699
|
-
|
|
11700
|
-
}
|
|
11701
|
-
const
|
|
11986
|
+
const { data: arrayBuffer, cacheInfo } = await downloadResource(cameraUrl, {
|
|
11987
|
+
characterId: characterMeta.characterId ?? void 0,
|
|
11988
|
+
resourceType: "character"
|
|
11989
|
+
});
|
|
11990
|
+
const text = new TextDecoder().decode(arrayBuffer);
|
|
11991
|
+
const cameraSettings = JSON.parse(text);
|
|
11992
|
+
logEvent("resource_download", "info", {
|
|
11993
|
+
resource_type: "camera_settings",
|
|
11994
|
+
resource_name: "camera.json",
|
|
11995
|
+
cache_hit: cacheInfo.cacheHit,
|
|
11996
|
+
cache_type: cacheInfo.cacheType,
|
|
11997
|
+
pwa_cache_subtype: cacheInfo.pwaCacheSubtype,
|
|
11998
|
+
transfer_size: cacheInfo.transferSize,
|
|
11999
|
+
cdn_cache_status: cacheInfo.cdnCacheStatus
|
|
12000
|
+
});
|
|
11702
12001
|
logger.log("✅ Camera info loaded:", cameraSettings);
|
|
11703
12002
|
return cameraSettings;
|
|
11704
12003
|
} catch (error) {
|
|
@@ -11748,7 +12047,10 @@ class AvatarDownloader {
|
|
|
11748
12047
|
const downloadPromises = filesToLoad.map(async ({ key, url, filename, optional }) => {
|
|
11749
12048
|
updateProgress(filename, false);
|
|
11750
12049
|
try {
|
|
11751
|
-
const arrayBuffer = await downloadResource(url
|
|
12050
|
+
const { data: arrayBuffer, cacheInfo } = await downloadResource(url, {
|
|
12051
|
+
characterId: characterMeta.characterId ?? void 0,
|
|
12052
|
+
resourceType: "character"
|
|
12053
|
+
});
|
|
11752
12054
|
if (key === "shape") {
|
|
11753
12055
|
characterData.shape = arrayBuffer;
|
|
11754
12056
|
} else if (key === "pointCloud") {
|
|
@@ -11757,6 +12059,15 @@ class AvatarDownloader {
|
|
|
11757
12059
|
characterData.idleAnimation = arrayBuffer;
|
|
11758
12060
|
}
|
|
11759
12061
|
updateProgress(filename, true);
|
|
12062
|
+
logEvent("resource_download", "info", {
|
|
12063
|
+
resource_type: "character_asset",
|
|
12064
|
+
resource_name: filename,
|
|
12065
|
+
cache_hit: cacheInfo.cacheHit,
|
|
12066
|
+
cache_type: cacheInfo.cacheType,
|
|
12067
|
+
pwa_cache_subtype: cacheInfo.pwaCacheSubtype,
|
|
12068
|
+
transfer_size: cacheInfo.transferSize,
|
|
12069
|
+
cdn_cache_status: cacheInfo.cdnCacheStatus
|
|
12070
|
+
});
|
|
11760
12071
|
return { key, success: true, size: arrayBuffer.byteLength };
|
|
11761
12072
|
} catch (error) {
|
|
11762
12073
|
if (!optional) {
|
|
@@ -11946,11 +12257,14 @@ const _AvatarManager = class _AvatarManager {
|
|
|
11946
12257
|
const cachedVersion = cachedMeta.version;
|
|
11947
12258
|
const newVersion = newCharacterMeta.version;
|
|
11948
12259
|
if (cachedVersion === newVersion) {
|
|
11949
|
-
logger.log(`[AvatarManager] Avatar ${id} found in cache with same version (${cachedVersion}), returning cached`);
|
|
12260
|
+
logger.log(`[AvatarManager] Avatar ${id} found in cache with same version (${cachedVersion}), updating meta and returning cached`);
|
|
12261
|
+
cached.updateCharacterMeta(newCharacterMeta);
|
|
11950
12262
|
return cached;
|
|
11951
12263
|
} else {
|
|
11952
12264
|
logger.log(`[AvatarManager] Avatar ${id} version mismatch: cached=${cachedVersion}, new=${newVersion}, reloading...`);
|
|
11953
12265
|
this.avatarCache.delete(id);
|
|
12266
|
+
const { PwaCacheManager: PwaCacheManager2 } = await Promise.resolve().then(() => pwaCacheManager);
|
|
12267
|
+
await PwaCacheManager2.clearCharacterCache(id);
|
|
11954
12268
|
}
|
|
11955
12269
|
}
|
|
11956
12270
|
logger.log(`[AvatarManager] Queuing avatar download for id: ${id}`);
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class PwaCacheManager {
|
|
2
|
+
private static readonly TEMPLATE_CACHE_VERSION;
|
|
3
|
+
private static readonly TEMPLATE_CACHE_NAME;
|
|
4
|
+
private static readonly TEMPLATE_VERSION_STORAGE_KEY;
|
|
5
|
+
private static readonly CHARACTER_CACHE_PREFIX;
|
|
6
|
+
private static readonly CHARACTER_CACHE_SUFFIX;
|
|
7
|
+
private static readonly MAX_CHARACTER_CACHE_ENTRIES;
|
|
8
|
+
static isSupported(): boolean;
|
|
9
|
+
private static getCharacterCacheName;
|
|
10
|
+
static getCharacterResource(characterId: string, url: string): Promise<ArrayBuffer | null>;
|
|
11
|
+
static putCharacterResource(characterId: string, url: string, data: ArrayBuffer): Promise<void>;
|
|
12
|
+
static getTemplateResource(url: string): Promise<ArrayBuffer | null>;
|
|
13
|
+
static putTemplateResource(url: string, data: ArrayBuffer): Promise<void>;
|
|
14
|
+
static clearCharacterCache(characterId: string): Promise<void>;
|
|
15
|
+
static checkTemplateCacheVersion(newVersion: string): Promise<boolean>;
|
|
16
|
+
}
|
package/package.json
CHANGED