@spatialwalk/avatarkit 1.0.0-beta.101 → 1.0.0-beta.103
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 +12 -0
- package/dist/{StreamingAudioPlayer-BULgPjpe.js → StreamingAudioPlayer-CxtOBEsC.js} +1 -1
- package/dist/core/AvatarView.d.ts +17 -14
- package/dist/{index-C0A1HA8M.js → index-BqX6ubaV.js} +95 -158
- package/dist/index.js +1 -1
- package/dist/types/index.d.ts +0 -20
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ 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.103]
|
|
9
|
+
|
|
10
|
+
### 🔧 Internal
|
|
11
|
+
|
|
12
|
+
- Optimize internal type exports
|
|
13
|
+
|
|
14
|
+
## [1.0.0-beta.102]
|
|
15
|
+
|
|
16
|
+
### 🐛 Bugfixes
|
|
17
|
+
|
|
18
|
+
- **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.
|
|
19
|
+
|
|
8
20
|
## [1.0.0-beta.101]
|
|
9
21
|
|
|
10
22
|
### ⚡ 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-BqX6ubaV.js";
|
|
5
5
|
class StreamingAudioPlayer {
|
|
6
6
|
// Mark if AudioContext is being resumed, avoid concurrent resume requests
|
|
7
7
|
constructor(options) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CameraConfig
|
|
1
|
+
import { CameraConfig } from '../types';
|
|
2
2
|
import { Avatar } from './Avatar';
|
|
3
3
|
import { AvatarController } from './AvatarController';
|
|
4
4
|
export declare class AvatarView {
|
|
@@ -57,19 +57,6 @@ export declare class AvatarView {
|
|
|
57
57
|
* 更新相机配置
|
|
58
58
|
*/
|
|
59
59
|
updateCameraConfig(cameraConfig: CameraConfig): void;
|
|
60
|
-
/**
|
|
61
|
-
* Render specified keyframe (pure rendering mode, no audio-animation synchronization)
|
|
62
|
-
* Suitable for scenarios where external application controls audio playback and animation playback
|
|
63
|
-
* @param keyframeData - Keyframe data
|
|
64
|
-
* @param enableIdleRendering - Whether to enable idle loop rendering. true: Enable idle rendering and return immediately (skip this keyframe); false: Disable idle rendering and process keyframe (default)
|
|
65
|
-
*/
|
|
66
|
-
renderFlame(keyframeData: KeyframeData, enableIdleRendering?: boolean): Promise<void>;
|
|
67
|
-
generateTransition(options: {
|
|
68
|
-
from?: KeyframeData;
|
|
69
|
-
to?: KeyframeData;
|
|
70
|
-
frameCount: number;
|
|
71
|
-
useLinear?: boolean;
|
|
72
|
-
}): Promise<KeyframeData[]>;
|
|
73
60
|
/**
|
|
74
61
|
* Render a single animation frame from raw protobuf data.
|
|
75
62
|
*
|
|
@@ -107,6 +94,22 @@ export declare class AvatarView {
|
|
|
107
94
|
* Start idle animation (stop pure rendering mode, resume idle loop).
|
|
108
95
|
*/
|
|
109
96
|
startIdle(): void;
|
|
97
|
+
/**
|
|
98
|
+
* Generate transition frames from protobuf data.
|
|
99
|
+
*
|
|
100
|
+
* Decodes the protobuf, extracts the target keyframe, and generates
|
|
101
|
+
* transition frames from the current idle position to the target.
|
|
102
|
+
* The caller is responsible for playing the returned frames at the desired cadence.
|
|
103
|
+
*
|
|
104
|
+
* @param data - Raw protobuf bytes containing the target frame
|
|
105
|
+
* @param frameCount - Number of transition frames to generate
|
|
106
|
+
* @param options - Additional options
|
|
107
|
+
* @param options.useLinear - Use linear interpolation (default: true)
|
|
108
|
+
* @returns Array of opaque keyframe data for sequential playback
|
|
109
|
+
*/
|
|
110
|
+
generateTransitionFromProtobuf(data: ArrayBuffer | Uint8Array, frameCount: number, options?: {
|
|
111
|
+
useLinear?: boolean;
|
|
112
|
+
}): Promise<unknown[]>;
|
|
110
113
|
/**
|
|
111
114
|
* Cancel any in-progress frame sequence playback.
|
|
112
115
|
* Called by renderFromProtobuf when streaming frames arrive during transition.
|
|
@@ -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-CxtOBEsC.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.103");
|
|
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
|
}
|
|
@@ -18042,9 +17954,9 @@ class AvatarView {
|
|
|
18042
17954
|
}
|
|
18043
17955
|
/**
|
|
18044
17956
|
* Render specified keyframe (pure rendering mode, no audio-animation synchronization)
|
|
18045
|
-
* Suitable for scenarios where external application controls audio playback and animation playback
|
|
18046
17957
|
* @param keyframeData - Keyframe data
|
|
18047
|
-
* @param enableIdleRendering - Whether to enable idle loop rendering
|
|
17958
|
+
* @param enableIdleRendering - Whether to enable idle loop rendering
|
|
17959
|
+
* @internal
|
|
18048
17960
|
*/
|
|
18049
17961
|
async renderFlame(keyframeData, enableIdleRendering) {
|
|
18050
17962
|
if (!this.isInitialized || !this.renderSystem) {
|
|
@@ -18075,6 +17987,7 @@ class AvatarView {
|
|
|
18075
17987
|
throw error;
|
|
18076
17988
|
}
|
|
18077
17989
|
}
|
|
17990
|
+
/** @internal */
|
|
18078
17991
|
async generateTransition(options) {
|
|
18079
17992
|
if (!this.isInitialized) {
|
|
18080
17993
|
throw new Error("AvatarView not initialized");
|
|
@@ -18186,6 +18099,30 @@ class AvatarView {
|
|
|
18186
18099
|
startIdle() {
|
|
18187
18100
|
this.renderFlame(void 0, true);
|
|
18188
18101
|
}
|
|
18102
|
+
/**
|
|
18103
|
+
* Generate transition frames from protobuf data.
|
|
18104
|
+
*
|
|
18105
|
+
* Decodes the protobuf, extracts the target keyframe, and generates
|
|
18106
|
+
* transition frames from the current idle position to the target.
|
|
18107
|
+
* The caller is responsible for playing the returned frames at the desired cadence.
|
|
18108
|
+
*
|
|
18109
|
+
* @param data - Raw protobuf bytes containing the target frame
|
|
18110
|
+
* @param frameCount - Number of transition frames to generate
|
|
18111
|
+
* @param options - Additional options
|
|
18112
|
+
* @param options.useLinear - Use linear interpolation (default: true)
|
|
18113
|
+
* @returns Array of opaque keyframe data for sequential playback
|
|
18114
|
+
*/
|
|
18115
|
+
async generateTransitionFromProtobuf(data, frameCount, options) {
|
|
18116
|
+
const keyframes = decodeAnimationKeyframes(data);
|
|
18117
|
+
if (!keyframes || keyframes.length === 0) {
|
|
18118
|
+
return [];
|
|
18119
|
+
}
|
|
18120
|
+
return this.generateTransition({
|
|
18121
|
+
to: keyframes[0],
|
|
18122
|
+
frameCount,
|
|
18123
|
+
useLinear: (options == null ? void 0 : options.useLinear) ?? true
|
|
18124
|
+
});
|
|
18125
|
+
}
|
|
18189
18126
|
/**
|
|
18190
18127
|
* Cancel any in-progress frame sequence playback.
|
|
18191
18128
|
* Called by renderFromProtobuf when streaming frames arrive during transition.
|
package/dist/index.js
CHANGED
package/dist/types/index.d.ts
CHANGED
|
@@ -119,26 +119,6 @@ export interface CameraConfig {
|
|
|
119
119
|
up?: [number, number, number];
|
|
120
120
|
aspect?: number;
|
|
121
121
|
}
|
|
122
|
-
/**
|
|
123
|
-
* Animation keyframe data
|
|
124
|
-
* Used to represent avatar pose and expression parameters
|
|
125
|
-
*/
|
|
126
|
-
export interface KeyframeData {
|
|
127
|
-
/** Translation [x, y, z] */
|
|
128
|
-
translation: number[];
|
|
129
|
-
/** Rotation [x, y, z] (Euler angles, in radians) */
|
|
130
|
-
rotation: number[];
|
|
131
|
-
/** Neck pose [x, y, z] (in radians) */
|
|
132
|
-
neckPose: number[];
|
|
133
|
-
/** Jaw pose [x, y, z] (in radians) */
|
|
134
|
-
jawPose: number[];
|
|
135
|
-
/** Eye pose [x, y, z, x, y, z] (3 parameters for each eye) */
|
|
136
|
-
eyePose: number[];
|
|
137
|
-
/** Eyelid [x, y] */
|
|
138
|
-
eyeLid: number[];
|
|
139
|
-
/** Expression parameters array */
|
|
140
|
-
expression: number[];
|
|
141
|
-
}
|
|
142
122
|
/**
|
|
143
123
|
* Post-processing parameter configuration
|
|
144
124
|
* Used to overlay in real-time on animation parameters returned by the server
|
package/package.json
CHANGED