@spatialwalk/avatarkit 1.0.0-beta.85 → 1.0.0-beta.86
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 +4 -21
- package/dist/{StreamingAudioPlayer-By5EueKJ.js → StreamingAudioPlayer-pfCQ3Bn7.js} +1 -1
- package/dist/core/AvatarController.d.ts +0 -1
- package/dist/core/AvatarView.d.ts +5 -3
- package/dist/{index-vzFN-bug.js → index-Z9AXSJw3.js} +555 -329
- package/dist/index.js +1 -1
- package/package.json +3 -7
package/CHANGELOG.md
CHANGED
|
@@ -5,31 +5,14 @@ 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.
|
|
8
|
+
## [1.0.0-beta.86] - 2026-03-03
|
|
9
9
|
|
|
10
10
|
### 🐛 Bugfixes
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
11
|
+
- **Transition Continuity** - Fixed start transition source frame selection to use the currently displayed idle frame, reducing first-frame jump.
|
|
12
|
+
- **Transition Robustness** - Added frame index clamping for realtime callback and speaking->idle transition source lookup, preventing end-transition fallback jumps caused by out-of-range indices.
|
|
13
13
|
|
|
14
14
|
### ✅ Tests
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
## [1.0.0-beta.84] - 2026-03-03
|
|
18
|
-
|
|
19
|
-
### 🐛 Bugfixes
|
|
20
|
-
- **End-Transition Continuity** - Fixed the visual jump after conversation end transition by resetting idle frame cursor when returning to `Idle` state
|
|
21
|
-
|
|
22
|
-
## [1.0.0-beta.83] - 2026-03-03
|
|
23
|
-
|
|
24
|
-
### 🐛 Bugfixes
|
|
25
|
-
- **Playback Startup Sync** - Improved startup sequencing for SDK mode playback to reduce transition/audio desync at conversation start
|
|
26
|
-
- **Initialization Guard** - Added explicit `appId` validation in `AvatarSDK.initialize()` to fail fast when `appId` is empty
|
|
27
|
-
|
|
28
|
-
### 🔧 Improvements
|
|
29
|
-
- **Server Error Propagation** - `onError` now consistently receives `AvatarError` with server message and mapped error code for `MESSAGE_SERVER_ERROR` in SDK mode
|
|
30
|
-
|
|
31
|
-
### 📚 Documentation
|
|
32
|
-
- **README Error Callback** - Added detailed `onError` callback behavior and server error code mapping documentation
|
|
15
|
+
- **State Machine Coverage** - Updated `AvatarView` rendering state tests to align with current dual-transition flow, and added boundary tests for start/end transition frame selection.
|
|
33
16
|
|
|
34
17
|
## [1.0.0-beta.82] - 2026-02-12
|
|
35
18
|
|
|
@@ -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-Z9AXSJw3.js";
|
|
5
5
|
class StreamingAudioPlayer {
|
|
6
6
|
// Mark if AudioContext is being resumed, avoid concurrent resume requests
|
|
7
7
|
constructor(options) {
|
|
@@ -11,7 +11,6 @@ export declare class AvatarController {
|
|
|
11
11
|
onError: ((error: Error) => void) | null;
|
|
12
12
|
private eventListeners;
|
|
13
13
|
private renderCallback?;
|
|
14
|
-
private getCurrentIdleFrameCallback?;
|
|
15
14
|
private characterHandle;
|
|
16
15
|
private characterId;
|
|
17
16
|
private postProcessingConfig;
|
|
@@ -13,14 +13,16 @@ export declare class AvatarView {
|
|
|
13
13
|
private currentKeyframes;
|
|
14
14
|
private lastRenderedFrameIndex;
|
|
15
15
|
private lastRealtimeProtoFrame;
|
|
16
|
-
private
|
|
17
|
-
private
|
|
18
|
-
private isConversationActive;
|
|
16
|
+
private idleAnimationLoopId;
|
|
17
|
+
private realtimeAnimationLoopId;
|
|
19
18
|
private resizeObserver;
|
|
20
19
|
private onWindowResize;
|
|
21
20
|
private frameCount;
|
|
22
21
|
private lastFpsUpdate;
|
|
23
22
|
private currentFPS;
|
|
23
|
+
private transitionKeyframes;
|
|
24
|
+
private transitionStartTime;
|
|
25
|
+
private readonly startTransitionDurationMs;
|
|
24
26
|
private readonly endTransitionDurationMs;
|
|
25
27
|
private cachedIdleFirstFrame;
|
|
26
28
|
private idleCurrentFrameIndex;
|
|
@@ -2648,6 +2648,32 @@ function isObject(value) {
|
|
|
2648
2648
|
function isSet(value) {
|
|
2649
2649
|
return value !== null && value !== void 0;
|
|
2650
2650
|
}
|
|
2651
|
+
function convertProtoFlameToWasmParams(protoFlame) {
|
|
2652
|
+
var _a;
|
|
2653
|
+
return {
|
|
2654
|
+
translation: protoFlame.translation || [0, 0, 0],
|
|
2655
|
+
rotation: protoFlame.rotation || [0, 0, 0],
|
|
2656
|
+
neck_pose: protoFlame.neckPose || [0, 0, 0],
|
|
2657
|
+
jaw_pose: protoFlame.jawPose || [0, 0, 0],
|
|
2658
|
+
eyes_pose: protoFlame.eyePose || [0, 0, 0, 0, 0, 0],
|
|
2659
|
+
eyelid: protoFlame.eyeLid || [0, 0],
|
|
2660
|
+
expr_params: protoFlame.expression || [],
|
|
2661
|
+
shape_params: [],
|
|
2662
|
+
// Realtime doesn't provide shape params, use default
|
|
2663
|
+
has_eyelid: (((_a = protoFlame.eyeLid) == null ? void 0 : _a.length) || 0) > 0
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
function convertWasmParamsToProtoFlame(wasmParams) {
|
|
2667
|
+
return {
|
|
2668
|
+
translation: wasmParams.translation || [0, 0, 0],
|
|
2669
|
+
rotation: wasmParams.rotation || [0, 0, 0],
|
|
2670
|
+
neckPose: wasmParams.neck_pose || [0, 0, 0],
|
|
2671
|
+
jawPose: wasmParams.jaw_pose || [0, 0, 0],
|
|
2672
|
+
eyePose: wasmParams.eyes_pose || [0, 0, 0, 0, 0, 0],
|
|
2673
|
+
eyeLid: wasmParams.eyelid || [0, 0],
|
|
2674
|
+
expression: wasmParams.expr_params || []
|
|
2675
|
+
};
|
|
2676
|
+
}
|
|
2651
2677
|
const POSTHOG_HOST_INTL = "https://i.spatialwalk.ai";
|
|
2652
2678
|
const POSTHOG_API_KEY_INTL = "phc_IFTLa6Z6VhTaNvsxB7klvG2JeNwcSpnnwz8YvZRC96Q";
|
|
2653
2679
|
function getPostHogConfig(_environment) {
|
|
@@ -2700,32 +2726,6 @@ const APP_CONFIG = {
|
|
|
2700
2726
|
}
|
|
2701
2727
|
}
|
|
2702
2728
|
};
|
|
2703
|
-
function convertProtoFlameToWasmParams(protoFlame) {
|
|
2704
|
-
var _a;
|
|
2705
|
-
return {
|
|
2706
|
-
translation: protoFlame.translation || [0, 0, 0],
|
|
2707
|
-
rotation: protoFlame.rotation || [0, 0, 0],
|
|
2708
|
-
neck_pose: protoFlame.neckPose || [0, 0, 0],
|
|
2709
|
-
jaw_pose: protoFlame.jawPose || [0, 0, 0],
|
|
2710
|
-
eyes_pose: protoFlame.eyePose || [0, 0, 0, 0, 0, 0],
|
|
2711
|
-
eyelid: protoFlame.eyeLid || [0, 0],
|
|
2712
|
-
expr_params: protoFlame.expression || [],
|
|
2713
|
-
shape_params: [],
|
|
2714
|
-
// Realtime doesn't provide shape params, use default
|
|
2715
|
-
has_eyelid: (((_a = protoFlame.eyeLid) == null ? void 0 : _a.length) || 0) > 0
|
|
2716
|
-
};
|
|
2717
|
-
}
|
|
2718
|
-
function convertWasmParamsToProtoFlame(wasmParams) {
|
|
2719
|
-
return {
|
|
2720
|
-
translation: wasmParams.translation || [0, 0, 0],
|
|
2721
|
-
rotation: wasmParams.rotation || [0, 0, 0],
|
|
2722
|
-
neckPose: wasmParams.neck_pose || [0, 0, 0],
|
|
2723
|
-
jawPose: wasmParams.jaw_pose || [0, 0, 0],
|
|
2724
|
-
eyePose: wasmParams.eyes_pose || [0, 0, 0, 0, 0, 0],
|
|
2725
|
-
eyeLid: wasmParams.eyelid || [0, 0],
|
|
2726
|
-
expression: wasmParams.expr_params || []
|
|
2727
|
-
};
|
|
2728
|
-
}
|
|
2729
2729
|
var t = "undefined" != typeof window ? window : void 0, i = "undefined" != typeof globalThis ? globalThis : t;
|
|
2730
2730
|
"undefined" == typeof self && (i.self = i), "undefined" == typeof File && (i.File = function() {
|
|
2731
2731
|
});
|
|
@@ -9491,7 +9491,7 @@ const _AnimationPlayer = class _AnimationPlayer {
|
|
|
9491
9491
|
if (this.streamingPlayer) {
|
|
9492
9492
|
return;
|
|
9493
9493
|
}
|
|
9494
|
-
const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-
|
|
9494
|
+
const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-pfCQ3Bn7.js");
|
|
9495
9495
|
const { AvatarSDK: AvatarSDK2 } = await Promise.resolve().then(() => AvatarSDK$1);
|
|
9496
9496
|
const audioFormat = AvatarSDK2.getAudioFormat();
|
|
9497
9497
|
this.streamingPlayer = new StreamingAudioPlayer({
|
|
@@ -11220,7 +11220,7 @@ class AvatarSDK {
|
|
|
11220
11220
|
}
|
|
11221
11221
|
__publicField(AvatarSDK, "_isInitialized", false);
|
|
11222
11222
|
__publicField(AvatarSDK, "_configuration", null);
|
|
11223
|
-
__publicField(AvatarSDK, "_version", "1.0.0-beta.
|
|
11223
|
+
__publicField(AvatarSDK, "_version", "1.0.0-beta.86");
|
|
11224
11224
|
__publicField(AvatarSDK, "_avatarCore", null);
|
|
11225
11225
|
__publicField(AvatarSDK, "_dynamicSdkConfig", null);
|
|
11226
11226
|
const AvatarSDK$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
@@ -12072,117 +12072,6 @@ class NetworkLayer {
|
|
|
12072
12072
|
this.visibilityChangeHandler = null;
|
|
12073
12073
|
}
|
|
12074
12074
|
}
|
|
12075
|
-
function lerpArrays(from, to2, progress) {
|
|
12076
|
-
const length = Math.min(from.length, to2.length);
|
|
12077
|
-
const result2 = Array.from({ length });
|
|
12078
|
-
for (let i2 = 0; i2 < length; i2++) {
|
|
12079
|
-
result2[i2] = from[i2] + (to2[i2] - from[i2]) * progress;
|
|
12080
|
-
}
|
|
12081
|
-
return result2;
|
|
12082
|
-
}
|
|
12083
|
-
const clamp01 = (x2) => Math.max(0, Math.min(1, x2));
|
|
12084
|
-
function linearLerp(from, to2, progress) {
|
|
12085
|
-
return {
|
|
12086
|
-
translation: lerpArrays(from.translation || [0, 0, 0], to2.translation || [0, 0, 0], progress),
|
|
12087
|
-
rotation: lerpArrays(from.rotation || [0, 0, 0], to2.rotation || [0, 0, 0], progress),
|
|
12088
|
-
neckPose: lerpArrays(from.neckPose || [0, 0, 0], to2.neckPose || [0, 0, 0], progress),
|
|
12089
|
-
jawPose: lerpArrays(from.jawPose || [0, 0, 0], to2.jawPose || [0, 0, 0], progress),
|
|
12090
|
-
eyePose: lerpArrays(from.eyePose || [0, 0, 0, 0, 0, 0], to2.eyePose || [0, 0, 0, 0, 0, 0], progress),
|
|
12091
|
-
eyeLid: (() => {
|
|
12092
|
-
const fromEyelid = from.eyeLid;
|
|
12093
|
-
const toEyelid = to2.eyeLid;
|
|
12094
|
-
if ((fromEyelid == null ? void 0 : fromEyelid.length) && (toEyelid == null ? void 0 : toEyelid.length))
|
|
12095
|
-
return lerpArrays(fromEyelid, toEyelid, progress);
|
|
12096
|
-
return fromEyelid || toEyelid || [];
|
|
12097
|
-
})(),
|
|
12098
|
-
expression: lerpArrays(from.expression || [], to2.expression || [], progress)
|
|
12099
|
-
};
|
|
12100
|
-
}
|
|
12101
|
-
function generateTransitionFramesLinear(from, to2, durationMs, fps = FLAME_FRAME_RATE) {
|
|
12102
|
-
const steps = Math.max(1, Math.floor(durationMs / 1e3 * fps));
|
|
12103
|
-
const frames = Array.from({ length: steps });
|
|
12104
|
-
if (steps === 1) {
|
|
12105
|
-
frames[0] = to2;
|
|
12106
|
-
return frames;
|
|
12107
|
-
}
|
|
12108
|
-
for (let i2 = 0; i2 < steps; i2++) {
|
|
12109
|
-
const progress = i2 / (steps - 1);
|
|
12110
|
-
frames[i2] = linearLerp(from, to2, progress);
|
|
12111
|
-
}
|
|
12112
|
-
frames[0] = from;
|
|
12113
|
-
frames[frames.length - 1] = to2;
|
|
12114
|
-
return frames;
|
|
12115
|
-
}
|
|
12116
|
-
function createBezierEasing(x1, y1, x2, y2) {
|
|
12117
|
-
const cx = 3 * x1;
|
|
12118
|
-
const bx = 3 * (x2 - x1) - cx;
|
|
12119
|
-
const ax = 1 - cx - bx;
|
|
12120
|
-
const cy = 3 * y1;
|
|
12121
|
-
const by = 3 * (y2 - y1) - cy;
|
|
12122
|
-
const ay = 1 - cy - by;
|
|
12123
|
-
const sampleCurveX = (t2) => ((ax * t2 + bx) * t2 + cx) * t2;
|
|
12124
|
-
const sampleCurveY = (t2) => ((ay * t2 + by) * t2 + cy) * t2;
|
|
12125
|
-
const sampleCurveDerivativeX = (t2) => (3 * ax * t2 + 2 * bx) * t2 + cx;
|
|
12126
|
-
const solveCurveX = (x3) => {
|
|
12127
|
-
let t2 = x3;
|
|
12128
|
-
for (let i2 = 0; i2 < 8; i2++) {
|
|
12129
|
-
const error = sampleCurveX(t2) - x3;
|
|
12130
|
-
if (Math.abs(error) < 1e-6) break;
|
|
12131
|
-
const d2 = sampleCurveDerivativeX(t2);
|
|
12132
|
-
if (Math.abs(d2) < 1e-6) break;
|
|
12133
|
-
t2 -= error / d2;
|
|
12134
|
-
}
|
|
12135
|
-
return t2;
|
|
12136
|
-
};
|
|
12137
|
-
return (x3) => {
|
|
12138
|
-
if (x3 <= 0) return 0;
|
|
12139
|
-
if (x3 >= 1) return 1;
|
|
12140
|
-
return sampleCurveY(solveCurveX(x3));
|
|
12141
|
-
};
|
|
12142
|
-
}
|
|
12143
|
-
const BEZIER_CURVES = {
|
|
12144
|
-
jaw: createBezierEasing(...BEZIER_CURVES$1.jaw),
|
|
12145
|
-
expression: createBezierEasing(...BEZIER_CURVES$1.expression),
|
|
12146
|
-
eye: createBezierEasing(...BEZIER_CURVES$1.eye),
|
|
12147
|
-
neck: createBezierEasing(...BEZIER_CURVES$1.neck),
|
|
12148
|
-
global: createBezierEasing(...BEZIER_CURVES$1.global)
|
|
12149
|
-
};
|
|
12150
|
-
function bezierLerp(from, to2, progress) {
|
|
12151
|
-
const getT = (key) => {
|
|
12152
|
-
const scaledProgress = clamp01(progress * TIME_SCALE[key]);
|
|
12153
|
-
return BEZIER_CURVES[key](scaledProgress);
|
|
12154
|
-
};
|
|
12155
|
-
return {
|
|
12156
|
-
translation: lerpArrays(from.translation || [0, 0, 0], to2.translation || [0, 0, 0], getT("global")),
|
|
12157
|
-
rotation: lerpArrays(from.rotation || [0, 0, 0], to2.rotation || [0, 0, 0], getT("global")),
|
|
12158
|
-
neckPose: lerpArrays(from.neckPose || [0, 0, 0], to2.neckPose || [0, 0, 0], getT("neck")),
|
|
12159
|
-
jawPose: lerpArrays(from.jawPose || [0, 0, 0], to2.jawPose || [0, 0, 0], getT("jaw")),
|
|
12160
|
-
eyePose: lerpArrays(from.eyePose || [0, 0, 0, 0, 0, 0], to2.eyePose || [0, 0, 0, 0, 0, 0], getT("eye")),
|
|
12161
|
-
eyeLid: (() => {
|
|
12162
|
-
const fromEyelid = from.eyeLid;
|
|
12163
|
-
const toEyelid = to2.eyeLid;
|
|
12164
|
-
if ((fromEyelid == null ? void 0 : fromEyelid.length) && (toEyelid == null ? void 0 : toEyelid.length))
|
|
12165
|
-
return lerpArrays(fromEyelid, toEyelid, getT("eye"));
|
|
12166
|
-
return fromEyelid || toEyelid || [];
|
|
12167
|
-
})(),
|
|
12168
|
-
expression: lerpArrays(from.expression || [], to2.expression || [], getT("expression"))
|
|
12169
|
-
};
|
|
12170
|
-
}
|
|
12171
|
-
function generateTransitionFrames(from, to2, durationMs, fps = FLAME_FRAME_RATE) {
|
|
12172
|
-
const steps = Math.max(1, Math.floor(durationMs / 1e3 * fps));
|
|
12173
|
-
const frames = Array.from({ length: steps });
|
|
12174
|
-
if (steps === 1) {
|
|
12175
|
-
frames[0] = to2;
|
|
12176
|
-
return frames;
|
|
12177
|
-
}
|
|
12178
|
-
for (let i2 = 0; i2 < steps; i2++) {
|
|
12179
|
-
const progress = i2 / (steps - 1);
|
|
12180
|
-
frames[i2] = bezierLerp(from, to2, progress);
|
|
12181
|
-
}
|
|
12182
|
-
frames[0] = from;
|
|
12183
|
-
frames[frames.length - 1] = to2;
|
|
12184
|
-
return frames;
|
|
12185
|
-
}
|
|
12186
12075
|
class AvatarController {
|
|
12187
12076
|
// 16kHz * 2 bytes per sample
|
|
12188
12077
|
constructor(avatar, options) {
|
|
@@ -12217,7 +12106,6 @@ class AvatarController {
|
|
|
12217
12106
|
__publicField(this, "eventListeners", /* @__PURE__ */ new Map());
|
|
12218
12107
|
// ========== Callbacks ==========
|
|
12219
12108
|
__publicField(this, "renderCallback");
|
|
12220
|
-
__publicField(this, "getCurrentIdleFrameCallback");
|
|
12221
12109
|
__publicField(this, "characterHandle", null);
|
|
12222
12110
|
// Character handle for multi-character support
|
|
12223
12111
|
__publicField(this, "characterId", null);
|
|
@@ -13004,7 +12892,6 @@ class AvatarController {
|
|
|
13004
12892
|
* @internal
|
|
13005
12893
|
*/
|
|
13006
12894
|
setupInternalEventListeners(callbacks) {
|
|
13007
|
-
this.getCurrentIdleFrameCallback = callbacks.getCurrentIdleFrame;
|
|
13008
12895
|
if (callbacks.onKeyframesUpdate) {
|
|
13009
12896
|
this.registerEventListener("keyframesUpdate", callbacks.onKeyframesUpdate);
|
|
13010
12897
|
}
|
|
@@ -13024,7 +12911,7 @@ class AvatarController {
|
|
|
13024
12911
|
* @internal
|
|
13025
12912
|
*/
|
|
13026
12913
|
async startStreamingPlaybackInternal() {
|
|
13027
|
-
var _a, _b, _c
|
|
12914
|
+
var _a, _b, _c;
|
|
13028
12915
|
this.checkAudioContextInitialized();
|
|
13029
12916
|
if (this.isPlaying) {
|
|
13030
12917
|
this.isStartingPlayback = false;
|
|
@@ -13063,30 +12950,19 @@ class AvatarController {
|
|
|
13063
12950
|
conversationId: ((_b2 = this.networkLayer) == null ? void 0 : _b2.getCurrentConversationId()) || void 0
|
|
13064
12951
|
});
|
|
13065
12952
|
});
|
|
13066
|
-
const transitionFrames = await this.generateStartTransitionFrames();
|
|
13067
|
-
if (transitionFrames.length > 0) {
|
|
13068
|
-
this.currentKeyframes.unshift(...transitionFrames);
|
|
13069
|
-
}
|
|
13070
12953
|
this.emit("startRendering");
|
|
13071
|
-
const silenceDurationS = START_TRANSITION_DURATION_MS / 1e3;
|
|
13072
|
-
const silenceChunk = this.createSilencePCMChunk(silenceDurationS);
|
|
13073
|
-
const audioChunksWithSilence = [
|
|
13074
|
-
{ data: silenceChunk, isLast: false },
|
|
13075
|
-
...this.pendingAudioChunks
|
|
13076
|
-
];
|
|
13077
12954
|
const streamingPlayer = this.animationPlayer.getStreamingPlayer();
|
|
13078
12955
|
if (streamingPlayer) {
|
|
13079
12956
|
streamingPlayer.setAutoStart(false);
|
|
13080
|
-
|
|
12957
|
+
}
|
|
12958
|
+
if (streamingPlayer) {
|
|
12959
|
+
await streamingPlayer.startNewSession(this.pendingAudioChunks);
|
|
13081
12960
|
}
|
|
13082
12961
|
this.pendingAudioChunks = [];
|
|
13083
12962
|
this.isPlaying = true;
|
|
13084
12963
|
this.currentState = AvatarState.playing;
|
|
13085
12964
|
(_a = this.onConversationState) == null ? void 0 : _a.call(this, this.mapToConversationState(AvatarState.playing));
|
|
13086
12965
|
this.startPlaybackLoop();
|
|
13087
|
-
if (streamingPlayer) {
|
|
13088
|
-
streamingPlayer.play();
|
|
13089
|
-
}
|
|
13090
12966
|
this.isStartingPlayback = false;
|
|
13091
12967
|
logEvent("character_player", "info", {
|
|
13092
12968
|
avatar_id: this.avatar.id,
|
|
@@ -13096,52 +12972,11 @@ class AvatarController {
|
|
|
13096
12972
|
} catch (error) {
|
|
13097
12973
|
const message = error instanceof Error ? error.message : String(error);
|
|
13098
12974
|
logger.error("[AvatarController] Failed to start streaming playback:", message);
|
|
13099
|
-
this.stopPlaybackLoop();
|
|
13100
12975
|
(_c = this.onError) == null ? void 0 : _c.call(this, new AvatarError("Failed to start streaming playback", "INIT_FAILED"));
|
|
13101
|
-
this.emit("stopRendering");
|
|
13102
12976
|
this.isPlaying = false;
|
|
13103
|
-
this.currentState = AvatarState.idle;
|
|
13104
|
-
(_d = this.onConversationState) == null ? void 0 : _d.call(this, this.mapToConversationState(AvatarState.idle));
|
|
13105
12977
|
this.isStartingPlayback = false;
|
|
13106
12978
|
}
|
|
13107
12979
|
}
|
|
13108
|
-
/**
|
|
13109
|
-
* Generate start-transition frames (idle → first conversation keyframe)
|
|
13110
|
-
* @returns transition frames array, or empty if generation fails
|
|
13111
|
-
* @internal
|
|
13112
|
-
*/
|
|
13113
|
-
async generateStartTransitionFrames() {
|
|
13114
|
-
try {
|
|
13115
|
-
const toFrame = this.currentKeyframes[0];
|
|
13116
|
-
if (!toFrame) return [];
|
|
13117
|
-
const toFrameWithPP = this.applyPostProcessingToFlame(toFrame);
|
|
13118
|
-
let fromFrame = null;
|
|
13119
|
-
if (this.getCurrentIdleFrameCallback) {
|
|
13120
|
-
fromFrame = await this.getCurrentIdleFrameCallback();
|
|
13121
|
-
}
|
|
13122
|
-
const avatarCore = AvatarSDK.getAvatarCore();
|
|
13123
|
-
if (!fromFrame && avatarCore && this.characterId) {
|
|
13124
|
-
const idleParams = await avatarCore.getCurrentFrameParams(0, this.characterId);
|
|
13125
|
-
fromFrame = convertWasmParamsToProtoFlame(idleParams);
|
|
13126
|
-
}
|
|
13127
|
-
if (!fromFrame) return [];
|
|
13128
|
-
return generateTransitionFramesLinear(fromFrame, toFrameWithPP, START_TRANSITION_DURATION_MS);
|
|
13129
|
-
} catch (e2) {
|
|
13130
|
-
logger.warn("[AvatarController] Failed to generate start transition:", e2 instanceof Error ? e2.message : String(e2));
|
|
13131
|
-
return [];
|
|
13132
|
-
}
|
|
13133
|
-
}
|
|
13134
|
-
/**
|
|
13135
|
-
* Create a silence PCM chunk (16-bit mono, 16kHz)
|
|
13136
|
-
* @internal
|
|
13137
|
-
*/
|
|
13138
|
-
createSilencePCMChunk(durationS) {
|
|
13139
|
-
const sampleRate = APP_CONFIG.audio.sampleRate;
|
|
13140
|
-
const channelCount = 1;
|
|
13141
|
-
const bytesPerSample = 2;
|
|
13142
|
-
const totalBytes = Math.round(sampleRate * channelCount * bytesPerSample * durationS);
|
|
13143
|
-
return new Uint8Array(totalBytes);
|
|
13144
|
-
}
|
|
13145
12980
|
/**
|
|
13146
12981
|
* Playback loop: Calculate animation frame based on audio time, notify render layer to render
|
|
13147
12982
|
*/
|
|
@@ -16248,6 +16083,117 @@ class RenderSystem {
|
|
|
16248
16083
|
m2[15] = 1;
|
|
16249
16084
|
}
|
|
16250
16085
|
}
|
|
16086
|
+
function lerpArrays(from, to2, progress) {
|
|
16087
|
+
const length = Math.min(from.length, to2.length);
|
|
16088
|
+
const result2 = Array.from({ length });
|
|
16089
|
+
for (let i2 = 0; i2 < length; i2++) {
|
|
16090
|
+
result2[i2] = from[i2] + (to2[i2] - from[i2]) * progress;
|
|
16091
|
+
}
|
|
16092
|
+
return result2;
|
|
16093
|
+
}
|
|
16094
|
+
const clamp01 = (x2) => Math.max(0, Math.min(1, x2));
|
|
16095
|
+
function linearLerp(from, to2, progress) {
|
|
16096
|
+
return {
|
|
16097
|
+
translation: lerpArrays(from.translation || [0, 0, 0], to2.translation || [0, 0, 0], progress),
|
|
16098
|
+
rotation: lerpArrays(from.rotation || [0, 0, 0], to2.rotation || [0, 0, 0], progress),
|
|
16099
|
+
neckPose: lerpArrays(from.neckPose || [0, 0, 0], to2.neckPose || [0, 0, 0], progress),
|
|
16100
|
+
jawPose: lerpArrays(from.jawPose || [0, 0, 0], to2.jawPose || [0, 0, 0], progress),
|
|
16101
|
+
eyePose: lerpArrays(from.eyePose || [0, 0, 0, 0, 0, 0], to2.eyePose || [0, 0, 0, 0, 0, 0], progress),
|
|
16102
|
+
eyeLid: (() => {
|
|
16103
|
+
const fromEyelid = from.eyeLid;
|
|
16104
|
+
const toEyelid = to2.eyeLid;
|
|
16105
|
+
if ((fromEyelid == null ? void 0 : fromEyelid.length) && (toEyelid == null ? void 0 : toEyelid.length))
|
|
16106
|
+
return lerpArrays(fromEyelid, toEyelid, progress);
|
|
16107
|
+
return fromEyelid || toEyelid || [];
|
|
16108
|
+
})(),
|
|
16109
|
+
expression: lerpArrays(from.expression || [], to2.expression || [], progress)
|
|
16110
|
+
};
|
|
16111
|
+
}
|
|
16112
|
+
function generateTransitionFramesLinear(from, to2, durationMs, fps = FLAME_FRAME_RATE) {
|
|
16113
|
+
const steps = Math.max(1, Math.floor(durationMs / 1e3 * fps));
|
|
16114
|
+
const frames = Array.from({ length: steps });
|
|
16115
|
+
if (steps === 1) {
|
|
16116
|
+
frames[0] = to2;
|
|
16117
|
+
return frames;
|
|
16118
|
+
}
|
|
16119
|
+
for (let i2 = 0; i2 < steps; i2++) {
|
|
16120
|
+
const progress = i2 / (steps - 1);
|
|
16121
|
+
frames[i2] = linearLerp(from, to2, progress);
|
|
16122
|
+
}
|
|
16123
|
+
frames[0] = from;
|
|
16124
|
+
frames[frames.length - 1] = to2;
|
|
16125
|
+
return frames;
|
|
16126
|
+
}
|
|
16127
|
+
function createBezierEasing(x1, y1, x2, y2) {
|
|
16128
|
+
const cx = 3 * x1;
|
|
16129
|
+
const bx = 3 * (x2 - x1) - cx;
|
|
16130
|
+
const ax = 1 - cx - bx;
|
|
16131
|
+
const cy = 3 * y1;
|
|
16132
|
+
const by = 3 * (y2 - y1) - cy;
|
|
16133
|
+
const ay = 1 - cy - by;
|
|
16134
|
+
const sampleCurveX = (t2) => ((ax * t2 + bx) * t2 + cx) * t2;
|
|
16135
|
+
const sampleCurveY = (t2) => ((ay * t2 + by) * t2 + cy) * t2;
|
|
16136
|
+
const sampleCurveDerivativeX = (t2) => (3 * ax * t2 + 2 * bx) * t2 + cx;
|
|
16137
|
+
const solveCurveX = (x3) => {
|
|
16138
|
+
let t2 = x3;
|
|
16139
|
+
for (let i2 = 0; i2 < 8; i2++) {
|
|
16140
|
+
const error = sampleCurveX(t2) - x3;
|
|
16141
|
+
if (Math.abs(error) < 1e-6) break;
|
|
16142
|
+
const d2 = sampleCurveDerivativeX(t2);
|
|
16143
|
+
if (Math.abs(d2) < 1e-6) break;
|
|
16144
|
+
t2 -= error / d2;
|
|
16145
|
+
}
|
|
16146
|
+
return t2;
|
|
16147
|
+
};
|
|
16148
|
+
return (x3) => {
|
|
16149
|
+
if (x3 <= 0) return 0;
|
|
16150
|
+
if (x3 >= 1) return 1;
|
|
16151
|
+
return sampleCurveY(solveCurveX(x3));
|
|
16152
|
+
};
|
|
16153
|
+
}
|
|
16154
|
+
const BEZIER_CURVES = {
|
|
16155
|
+
jaw: createBezierEasing(...BEZIER_CURVES$1.jaw),
|
|
16156
|
+
expression: createBezierEasing(...BEZIER_CURVES$1.expression),
|
|
16157
|
+
eye: createBezierEasing(...BEZIER_CURVES$1.eye),
|
|
16158
|
+
neck: createBezierEasing(...BEZIER_CURVES$1.neck),
|
|
16159
|
+
global: createBezierEasing(...BEZIER_CURVES$1.global)
|
|
16160
|
+
};
|
|
16161
|
+
function bezierLerp(from, to2, progress) {
|
|
16162
|
+
const getT = (key) => {
|
|
16163
|
+
const scaledProgress = clamp01(progress * TIME_SCALE[key]);
|
|
16164
|
+
return BEZIER_CURVES[key](scaledProgress);
|
|
16165
|
+
};
|
|
16166
|
+
return {
|
|
16167
|
+
translation: lerpArrays(from.translation || [0, 0, 0], to2.translation || [0, 0, 0], getT("global")),
|
|
16168
|
+
rotation: lerpArrays(from.rotation || [0, 0, 0], to2.rotation || [0, 0, 0], getT("global")),
|
|
16169
|
+
neckPose: lerpArrays(from.neckPose || [0, 0, 0], to2.neckPose || [0, 0, 0], getT("neck")),
|
|
16170
|
+
jawPose: lerpArrays(from.jawPose || [0, 0, 0], to2.jawPose || [0, 0, 0], getT("jaw")),
|
|
16171
|
+
eyePose: lerpArrays(from.eyePose || [0, 0, 0, 0, 0, 0], to2.eyePose || [0, 0, 0, 0, 0, 0], getT("eye")),
|
|
16172
|
+
eyeLid: (() => {
|
|
16173
|
+
const fromEyelid = from.eyeLid;
|
|
16174
|
+
const toEyelid = to2.eyeLid;
|
|
16175
|
+
if ((fromEyelid == null ? void 0 : fromEyelid.length) && (toEyelid == null ? void 0 : toEyelid.length))
|
|
16176
|
+
return lerpArrays(fromEyelid, toEyelid, getT("eye"));
|
|
16177
|
+
return fromEyelid || toEyelid || [];
|
|
16178
|
+
})(),
|
|
16179
|
+
expression: lerpArrays(from.expression || [], to2.expression || [], getT("expression"))
|
|
16180
|
+
};
|
|
16181
|
+
}
|
|
16182
|
+
function generateTransitionFrames(from, to2, durationMs, fps = FLAME_FRAME_RATE) {
|
|
16183
|
+
const steps = Math.max(1, Math.floor(durationMs / 1e3 * fps));
|
|
16184
|
+
const frames = Array.from({ length: steps });
|
|
16185
|
+
if (steps === 1) {
|
|
16186
|
+
frames[0] = to2;
|
|
16187
|
+
return frames;
|
|
16188
|
+
}
|
|
16189
|
+
for (let i2 = 0; i2 < steps; i2++) {
|
|
16190
|
+
const progress = i2 / (steps - 1);
|
|
16191
|
+
frames[i2] = bezierLerp(from, to2, progress);
|
|
16192
|
+
}
|
|
16193
|
+
frames[0] = from;
|
|
16194
|
+
frames[frames.length - 1] = to2;
|
|
16195
|
+
return frames;
|
|
16196
|
+
}
|
|
16251
16197
|
class AvatarView {
|
|
16252
16198
|
/**
|
|
16253
16199
|
* Constructor
|
|
@@ -16271,16 +16217,20 @@ class AvatarView {
|
|
|
16271
16217
|
__publicField(this, "currentKeyframes", []);
|
|
16272
16218
|
__publicField(this, "lastRenderedFrameIndex", -1);
|
|
16273
16219
|
__publicField(this, "lastRealtimeProtoFrame", null);
|
|
16274
|
-
// Animation loop
|
|
16275
|
-
__publicField(this, "
|
|
16276
|
-
__publicField(this, "
|
|
16277
|
-
__publicField(this, "isConversationActive", false);
|
|
16220
|
+
// Animation loop types
|
|
16221
|
+
__publicField(this, "idleAnimationLoopId", null);
|
|
16222
|
+
__publicField(this, "realtimeAnimationLoopId", null);
|
|
16278
16223
|
__publicField(this, "resizeObserver", null);
|
|
16279
16224
|
__publicField(this, "onWindowResize", () => this.handleResize());
|
|
16280
16225
|
// FPS 计算
|
|
16281
16226
|
__publicField(this, "frameCount", 0);
|
|
16282
16227
|
__publicField(this, "lastFpsUpdate", 0);
|
|
16283
16228
|
__publicField(this, "currentFPS", 0);
|
|
16229
|
+
// Transition animation data
|
|
16230
|
+
__publicField(this, "transitionKeyframes", []);
|
|
16231
|
+
__publicField(this, "transitionStartTime", 0);
|
|
16232
|
+
__publicField(this, "startTransitionDurationMs", START_TRANSITION_DURATION_MS);
|
|
16233
|
+
// Idle -> Speaking 过渡时长
|
|
16284
16234
|
__publicField(this, "endTransitionDurationMs", END_TRANSITION_DURATION_MS);
|
|
16285
16235
|
// Speaking -> Idle 过渡时长
|
|
16286
16236
|
__publicField(this, "cachedIdleFirstFrame", null);
|
|
@@ -16380,6 +16330,27 @@ class AvatarView {
|
|
|
16380
16330
|
keyframes[keyframes.length - 1] = aligned.to;
|
|
16381
16331
|
return keyframes;
|
|
16382
16332
|
}
|
|
16333
|
+
/**
|
|
16334
|
+
* Idle loop increments index after rendering, so the currently displayed idle frame is (idleCurrentFrameIndex - 1).
|
|
16335
|
+
* @internal
|
|
16336
|
+
*/
|
|
16337
|
+
getDisplayedIdleFrameIndex() {
|
|
16338
|
+
return Math.max(0, this.idleCurrentFrameIndex - 1);
|
|
16339
|
+
}
|
|
16340
|
+
/**
|
|
16341
|
+
* Clamp realtime frame index to current keyframes range to avoid out-of-bounds access.
|
|
16342
|
+
* @internal
|
|
16343
|
+
*/
|
|
16344
|
+
getClampedRealtimeFrameIndex(frameIndex) {
|
|
16345
|
+
const lastIndex = this.currentKeyframes.length - 1;
|
|
16346
|
+
if (lastIndex < 0) {
|
|
16347
|
+
return -1;
|
|
16348
|
+
}
|
|
16349
|
+
if (frameIndex < 0) {
|
|
16350
|
+
return 0;
|
|
16351
|
+
}
|
|
16352
|
+
return Math.min(frameIndex, lastIndex);
|
|
16353
|
+
}
|
|
16383
16354
|
/**
|
|
16384
16355
|
* Get cached Idle first frame, fetch and cache if not cached
|
|
16385
16356
|
* @internal
|
|
@@ -16398,28 +16369,6 @@ class AvatarView {
|
|
|
16398
16369
|
}
|
|
16399
16370
|
return this.cachedIdleFirstFrame;
|
|
16400
16371
|
}
|
|
16401
|
-
/**
|
|
16402
|
-
* Get the currently displayed idle frame.
|
|
16403
|
-
* Used as the start frame for idle -> speaking transition to avoid startup jump.
|
|
16404
|
-
* @internal
|
|
16405
|
-
*/
|
|
16406
|
-
async getCurrentDisplayedIdleFrame() {
|
|
16407
|
-
if (this.renderingState !== "idle") {
|
|
16408
|
-
return null;
|
|
16409
|
-
}
|
|
16410
|
-
const avatarCore = AvatarSDK.getAvatarCore();
|
|
16411
|
-
if (!avatarCore) {
|
|
16412
|
-
return null;
|
|
16413
|
-
}
|
|
16414
|
-
try {
|
|
16415
|
-
const displayedIndex = Math.max(0, this.idleCurrentFrameIndex - 1);
|
|
16416
|
-
const idleParams = await avatarCore.getCurrentFrameParams(displayedIndex, this.characterId);
|
|
16417
|
-
return convertWasmParamsToProtoFlame(idleParams);
|
|
16418
|
-
} catch (e2) {
|
|
16419
|
-
logger.warn("[AvatarView] Failed to get current idle frame:", e2 instanceof Error ? e2.message : String(e2));
|
|
16420
|
-
return null;
|
|
16421
|
-
}
|
|
16422
|
-
}
|
|
16423
16372
|
/**
|
|
16424
16373
|
* Get controller (public interface)
|
|
16425
16374
|
*/
|
|
@@ -16535,7 +16484,7 @@ class AvatarView {
|
|
|
16535
16484
|
if (APP_CONFIG.debug)
|
|
16536
16485
|
logger.log("[AvatarView] Starting rendering...");
|
|
16537
16486
|
await this.renderFirstFrame();
|
|
16538
|
-
this.
|
|
16487
|
+
this.startIdleAnimationLoop();
|
|
16539
16488
|
this.isInitialized = true;
|
|
16540
16489
|
if (APP_CONFIG.debug)
|
|
16541
16490
|
logger.log("[AvatarView] Avatar view initialized successfully");
|
|
@@ -16708,12 +16657,16 @@ class AvatarView {
|
|
|
16708
16657
|
this.lastFpsUpdate = performance.now();
|
|
16709
16658
|
}
|
|
16710
16659
|
/**
|
|
16711
|
-
* Start
|
|
16712
|
-
* Priority: endTransitionFrames > conversation (Controller playbackLoop) > idle
|
|
16660
|
+
* Start idle animation loop
|
|
16713
16661
|
* @internal
|
|
16714
16662
|
*/
|
|
16715
|
-
|
|
16716
|
-
if (this.
|
|
16663
|
+
startIdleAnimationLoop() {
|
|
16664
|
+
if (this.idleAnimationLoopId) {
|
|
16665
|
+
this.stopIdleAnimationLoop();
|
|
16666
|
+
}
|
|
16667
|
+
if (this.renderingState !== "idle") {
|
|
16668
|
+
if (APP_CONFIG.debug)
|
|
16669
|
+
logger.log("[AvatarView] Skip starting idle loop because not in idle state");
|
|
16717
16670
|
return;
|
|
16718
16671
|
}
|
|
16719
16672
|
this.idleCurrentFrameIndex = 0;
|
|
@@ -16727,72 +16680,185 @@ class AvatarView {
|
|
|
16727
16680
|
return;
|
|
16728
16681
|
}
|
|
16729
16682
|
this.updateFPS();
|
|
16683
|
+
if (this.renderingState !== "idle") {
|
|
16684
|
+
this.idleAnimationLoopId = requestAnimationFrame(renderFrame);
|
|
16685
|
+
return;
|
|
16686
|
+
}
|
|
16730
16687
|
const elapsed = currentTime - lastTime;
|
|
16731
16688
|
if (elapsed < frameInterval) {
|
|
16732
|
-
this.
|
|
16689
|
+
this.idleAnimationLoopId = requestAnimationFrame(renderFrame);
|
|
16733
16690
|
return;
|
|
16734
16691
|
}
|
|
16735
16692
|
lastTime = currentTime - elapsed % frameInterval;
|
|
16736
|
-
if (this.isPureRenderingMode
|
|
16737
|
-
this.
|
|
16693
|
+
if (this.isPureRenderingMode) {
|
|
16694
|
+
this.idleAnimationLoopId = requestAnimationFrame(renderFrame);
|
|
16695
|
+
return;
|
|
16696
|
+
}
|
|
16697
|
+
if (!this._renderingEnabled) {
|
|
16698
|
+
this.idleAnimationLoopId = requestAnimationFrame(renderFrame);
|
|
16699
|
+
return;
|
|
16700
|
+
}
|
|
16701
|
+
const avatarCore = AvatarSDK.getAvatarCore();
|
|
16702
|
+
if (!avatarCore) {
|
|
16703
|
+
return;
|
|
16704
|
+
}
|
|
16705
|
+
const splatData = await avatarCore.computeCompleteFrameFlat({ frameIndex: this.idleCurrentFrameIndex }, this.characterHandle ?? void 0);
|
|
16706
|
+
this.idleCurrentFrameIndex++;
|
|
16707
|
+
if (splatData) {
|
|
16708
|
+
if (this.renderingState !== "idle") {
|
|
16709
|
+
return;
|
|
16710
|
+
}
|
|
16711
|
+
if (this.isPureRenderingMode) {
|
|
16712
|
+
return;
|
|
16713
|
+
}
|
|
16714
|
+
this.doRender(splatData);
|
|
16715
|
+
}
|
|
16716
|
+
this.idleAnimationLoopId = requestAnimationFrame(renderFrame);
|
|
16717
|
+
} catch (error) {
|
|
16718
|
+
logger.error("[AvatarView] Idle animation loop error:", error instanceof Error ? error.message : String(error));
|
|
16719
|
+
this.stopIdleAnimationLoop();
|
|
16720
|
+
}
|
|
16721
|
+
};
|
|
16722
|
+
this.idleAnimationLoopId = requestAnimationFrame(renderFrame);
|
|
16723
|
+
if (APP_CONFIG.debug)
|
|
16724
|
+
logger.log("[AvatarView] Idle animation loop started");
|
|
16725
|
+
}
|
|
16726
|
+
/**
|
|
16727
|
+
* Start realtime conversation animation loop
|
|
16728
|
+
* @internal
|
|
16729
|
+
*/
|
|
16730
|
+
startRealtimeAnimationLoop() {
|
|
16731
|
+
if (this.realtimeAnimationLoopId) {
|
|
16732
|
+
this.stopRealtimeAnimationLoop();
|
|
16733
|
+
}
|
|
16734
|
+
let lastTime = 0;
|
|
16735
|
+
const targetFPS = FLAME_FRAME_RATE;
|
|
16736
|
+
const frameInterval = 1e3 / targetFPS;
|
|
16737
|
+
this.initFPS();
|
|
16738
|
+
const renderFrame = async (currentTime) => {
|
|
16739
|
+
try {
|
|
16740
|
+
const state = this.renderingState;
|
|
16741
|
+
this.updateFPS();
|
|
16742
|
+
if (!this.renderSystem || state === "idle") {
|
|
16743
|
+
this.realtimeAnimationLoopId = null;
|
|
16744
|
+
return;
|
|
16745
|
+
}
|
|
16746
|
+
const elapsed = currentTime - lastTime;
|
|
16747
|
+
if (elapsed < frameInterval) {
|
|
16748
|
+
this.realtimeAnimationLoopId = requestAnimationFrame(renderFrame);
|
|
16738
16749
|
return;
|
|
16739
16750
|
}
|
|
16740
|
-
|
|
16741
|
-
|
|
16742
|
-
this.
|
|
16743
|
-
|
|
16744
|
-
|
|
16745
|
-
|
|
16746
|
-
|
|
16751
|
+
lastTime = currentTime - elapsed % frameInterval;
|
|
16752
|
+
if (state === "transitioningToSpeaking" || state === "transitioningToIdle") {
|
|
16753
|
+
if (this.transitionKeyframes.length === 0) {
|
|
16754
|
+
if (state === "transitioningToSpeaking") {
|
|
16755
|
+
this.setState(
|
|
16756
|
+
"speaking"
|
|
16757
|
+
/* Speaking */
|
|
16758
|
+
);
|
|
16759
|
+
this.avatarController.onTransitionComplete();
|
|
16760
|
+
} else if (state === "transitioningToIdle") {
|
|
16761
|
+
this.setState(
|
|
16762
|
+
"idle"
|
|
16763
|
+
/* Idle */
|
|
16764
|
+
);
|
|
16765
|
+
this.stopRealtimeAnimationLoop();
|
|
16766
|
+
this.startIdleAnimationLoop();
|
|
16767
|
+
return;
|
|
16768
|
+
}
|
|
16769
|
+
this.realtimeAnimationLoopId = requestAnimationFrame(renderFrame);
|
|
16770
|
+
return;
|
|
16771
|
+
}
|
|
16772
|
+
const elapsed2 = performance.now() - this.transitionStartTime;
|
|
16773
|
+
const currentTransitionDurationMs = state === "transitioningToSpeaking" ? this.startTransitionDurationMs : this.endTransitionDurationMs;
|
|
16774
|
+
const progress = Math.min(1, Math.max(0, elapsed2 / currentTransitionDurationMs));
|
|
16775
|
+
const steps = this.transitionKeyframes.length;
|
|
16776
|
+
const idx = Math.min(steps - 1, Math.floor(progress * (steps - 1)));
|
|
16777
|
+
const currentFrame = this.transitionKeyframes[idx];
|
|
16778
|
+
this.currentPlayingFrame = currentFrame;
|
|
16779
|
+
const wasmParams = convertProtoFlameToWasmParams(currentFrame);
|
|
16780
|
+
const avatarCore = AvatarSDK.getAvatarCore();
|
|
16781
|
+
if (avatarCore) {
|
|
16782
|
+
const sd = await avatarCore.computeFrameFlatFromParams(wasmParams, this.characterHandle ?? void 0);
|
|
16747
16783
|
if (sd) {
|
|
16748
16784
|
this.doRender(sd);
|
|
16749
16785
|
}
|
|
16750
16786
|
}
|
|
16751
|
-
if (
|
|
16787
|
+
if (progress >= 1) {
|
|
16788
|
+
if (state === "transitioningToSpeaking") {
|
|
16789
|
+
this.setState(
|
|
16790
|
+
"speaking"
|
|
16791
|
+
/* Speaking */
|
|
16792
|
+
);
|
|
16793
|
+
this.transitionKeyframes = [];
|
|
16794
|
+
this.avatarController.onTransitionComplete();
|
|
16795
|
+
} else if (state === "transitioningToIdle") {
|
|
16796
|
+
this.setState(
|
|
16797
|
+
"idle"
|
|
16798
|
+
/* Idle */
|
|
16799
|
+
);
|
|
16800
|
+
this.transitionKeyframes = [];
|
|
16801
|
+
this.stopRealtimeAnimationLoop();
|
|
16802
|
+
this.startIdleAnimationLoop();
|
|
16803
|
+
return;
|
|
16804
|
+
}
|
|
16805
|
+
}
|
|
16806
|
+
if (state === "transitioningToSpeaking" && this.transitionStartTime > 0 && this.transitionKeyframes.length > 0 && elapsed2 >= this.startTransitionDurationMs + 100) {
|
|
16752
16807
|
this.setState(
|
|
16753
|
-
"
|
|
16754
|
-
/*
|
|
16808
|
+
"speaking"
|
|
16809
|
+
/* Speaking */
|
|
16755
16810
|
);
|
|
16811
|
+
this.transitionKeyframes = [];
|
|
16812
|
+
this.avatarController.onTransitionComplete();
|
|
16756
16813
|
}
|
|
16757
|
-
this.
|
|
16814
|
+
this.realtimeAnimationLoopId = requestAnimationFrame(renderFrame);
|
|
16758
16815
|
return;
|
|
16759
16816
|
}
|
|
16760
|
-
if (
|
|
16761
|
-
this.
|
|
16817
|
+
if (state === "speaking") {
|
|
16818
|
+
this.realtimeAnimationLoopId = requestAnimationFrame(renderFrame);
|
|
16762
16819
|
return;
|
|
16763
16820
|
}
|
|
16764
|
-
const avatarCore = AvatarSDK.getAvatarCore();
|
|
16765
|
-
if (!avatarCore) {
|
|
16766
|
-
this.renderLoopId = requestAnimationFrame(renderFrame);
|
|
16767
|
-
return;
|
|
16768
|
-
}
|
|
16769
|
-
const splatData = await avatarCore.computeCompleteFrameFlat({ frameIndex: this.idleCurrentFrameIndex }, this.characterHandle ?? void 0);
|
|
16770
|
-
this.idleCurrentFrameIndex++;
|
|
16771
|
-
if (splatData && this.renderingState === "idle" && !this.isPureRenderingMode && !this.isConversationActive) {
|
|
16772
|
-
this.doRender(splatData);
|
|
16773
|
-
}
|
|
16774
|
-
this.renderLoopId = requestAnimationFrame(renderFrame);
|
|
16775
16821
|
} catch (error) {
|
|
16776
|
-
logger.error("[AvatarView]
|
|
16777
|
-
this.
|
|
16822
|
+
logger.error("[AvatarView] Realtime animation loop error:", error instanceof Error ? error.message : String(error));
|
|
16823
|
+
this.stopRealtimeAnimationLoop();
|
|
16778
16824
|
}
|
|
16779
16825
|
};
|
|
16780
|
-
this.
|
|
16826
|
+
this.realtimeAnimationLoopId = requestAnimationFrame(renderFrame);
|
|
16781
16827
|
if (APP_CONFIG.debug)
|
|
16782
|
-
logger.log("[AvatarView]
|
|
16828
|
+
logger.log("[AvatarView] Realtime animation loop started");
|
|
16783
16829
|
}
|
|
16784
16830
|
/**
|
|
16785
|
-
* Stop
|
|
16831
|
+
* Stop idle animation loop
|
|
16786
16832
|
* @internal
|
|
16787
16833
|
*/
|
|
16788
|
-
|
|
16789
|
-
if (this.
|
|
16790
|
-
cancelAnimationFrame(this.
|
|
16791
|
-
this.
|
|
16834
|
+
stopIdleAnimationLoop() {
|
|
16835
|
+
if (this.idleAnimationLoopId) {
|
|
16836
|
+
cancelAnimationFrame(this.idleAnimationLoopId);
|
|
16837
|
+
this.idleAnimationLoopId = null;
|
|
16792
16838
|
if (APP_CONFIG.debug)
|
|
16793
|
-
logger.log("[AvatarView]
|
|
16839
|
+
logger.log("[AvatarView] Idle animation loop stopped");
|
|
16794
16840
|
}
|
|
16795
16841
|
}
|
|
16842
|
+
/**
|
|
16843
|
+
* Stop realtime conversation animation loop
|
|
16844
|
+
* @internal
|
|
16845
|
+
*/
|
|
16846
|
+
stopRealtimeAnimationLoop() {
|
|
16847
|
+
if (this.realtimeAnimationLoopId) {
|
|
16848
|
+
cancelAnimationFrame(this.realtimeAnimationLoopId);
|
|
16849
|
+
this.realtimeAnimationLoopId = null;
|
|
16850
|
+
if (APP_CONFIG.debug)
|
|
16851
|
+
logger.log("[AvatarView] Realtime animation loop stopped");
|
|
16852
|
+
}
|
|
16853
|
+
}
|
|
16854
|
+
/**
|
|
16855
|
+
* Stop all animation loops
|
|
16856
|
+
* @internal
|
|
16857
|
+
*/
|
|
16858
|
+
stopAllAnimationLoops() {
|
|
16859
|
+
this.stopIdleAnimationLoop();
|
|
16860
|
+
this.stopRealtimeAnimationLoop();
|
|
16861
|
+
}
|
|
16796
16862
|
/**
|
|
16797
16863
|
* Unified render method - all rendering goes through here
|
|
16798
16864
|
* This is the single point of control for renderingEnabled flag
|
|
@@ -16814,42 +16880,80 @@ class AvatarView {
|
|
|
16814
16880
|
return;
|
|
16815
16881
|
}
|
|
16816
16882
|
this.lastRenderedFrameIndex = frameIndex;
|
|
16817
|
-
|
|
16818
|
-
|
|
16883
|
+
const clampedFrameIndex = this.getClampedRealtimeFrameIndex(frameIndex);
|
|
16884
|
+
if (clampedFrameIndex >= 0) {
|
|
16885
|
+
this.lastRealtimeProtoFrame = this.currentKeyframes[clampedFrameIndex];
|
|
16819
16886
|
this.currentPlayingFrame = this.lastRealtimeProtoFrame;
|
|
16820
16887
|
}
|
|
16821
16888
|
this.doRender(splatData);
|
|
16822
16889
|
}
|
|
16823
16890
|
/**
|
|
16824
16891
|
* State transition method
|
|
16892
|
+
* Unified state transition management to ensure state consistency
|
|
16825
16893
|
* @internal
|
|
16826
16894
|
*/
|
|
16827
16895
|
setState(newState) {
|
|
16896
|
+
const oldState = this.renderingState;
|
|
16828
16897
|
this.renderingState = newState;
|
|
16898
|
+
if (oldState === "transitioningToIdle" && newState !== "transitioningToIdle") {
|
|
16899
|
+
this.transitionKeyframes = [];
|
|
16900
|
+
}
|
|
16901
|
+
if (oldState === "transitioningToSpeaking" && newState !== "transitioningToSpeaking") {
|
|
16902
|
+
this.transitionKeyframes = [];
|
|
16903
|
+
}
|
|
16829
16904
|
if (newState === "idle") {
|
|
16830
16905
|
this.currentKeyframes = [];
|
|
16831
16906
|
this.lastRenderedFrameIndex = -1;
|
|
16832
16907
|
this.lastRealtimeProtoFrame = null;
|
|
16833
|
-
this.
|
|
16834
|
-
this.
|
|
16908
|
+
this.transitionKeyframes = [];
|
|
16909
|
+
this.transitionStartTime = 0;
|
|
16835
16910
|
this.currentPlayingFrame = null;
|
|
16836
|
-
this.idleCurrentFrameIndex = 0;
|
|
16837
16911
|
}
|
|
16838
16912
|
}
|
|
16839
16913
|
/**
|
|
16840
|
-
*
|
|
16914
|
+
* Check if in realtime playing state (Speaking or transitioning to Speaking)
|
|
16915
|
+
* @internal
|
|
16916
|
+
*/
|
|
16917
|
+
get isRealtimePlaying() {
|
|
16918
|
+
return this.renderingState === "speaking" || this.renderingState === "transitioningToSpeaking";
|
|
16919
|
+
}
|
|
16920
|
+
/**
|
|
16921
|
+
* Check if in transition state
|
|
16922
|
+
* @internal
|
|
16923
|
+
*/
|
|
16924
|
+
get isTransitioning() {
|
|
16925
|
+
return this.renderingState === "transitioningToSpeaking" || this.renderingState === "transitioningToIdle";
|
|
16926
|
+
}
|
|
16927
|
+
/**
|
|
16928
|
+
* Check if will return to Idle after transition ends
|
|
16929
|
+
* @internal
|
|
16930
|
+
*/
|
|
16931
|
+
get endToIdleAfterTransition() {
|
|
16932
|
+
return this.renderingState === "transitioningToIdle";
|
|
16933
|
+
}
|
|
16934
|
+
/**
|
|
16935
|
+
* Handle interrupt
|
|
16936
|
+
* When interrupted, should generate transition animation instead of directly jumping back to Idle
|
|
16841
16937
|
* @internal
|
|
16842
16938
|
*/
|
|
16843
16939
|
handleInterrupt() {
|
|
16844
|
-
|
|
16940
|
+
const state = this.renderingState;
|
|
16941
|
+
if (state === "idle") {
|
|
16845
16942
|
return;
|
|
16846
16943
|
}
|
|
16847
|
-
|
|
16848
|
-
|
|
16849
|
-
|
|
16850
|
-
|
|
16851
|
-
|
|
16852
|
-
|
|
16944
|
+
if (state === "transitioningToIdle") {
|
|
16945
|
+
return;
|
|
16946
|
+
}
|
|
16947
|
+
if (state === "speaking" || state === "transitioningToSpeaking") {
|
|
16948
|
+
this.stopRealtimeRendering();
|
|
16949
|
+
} else {
|
|
16950
|
+
this.setState(
|
|
16951
|
+
"idle"
|
|
16952
|
+
/* Idle */
|
|
16953
|
+
);
|
|
16954
|
+
this.stopRealtimeAnimationLoop();
|
|
16955
|
+
this.startIdleAnimationLoop();
|
|
16956
|
+
}
|
|
16853
16957
|
}
|
|
16854
16958
|
/**
|
|
16855
16959
|
* Setup AvatarController event listeners
|
|
@@ -16858,75 +16962,197 @@ class AvatarView {
|
|
|
16858
16962
|
setupControllerEventListeners() {
|
|
16859
16963
|
this.avatarController.setupInternalEventListeners({
|
|
16860
16964
|
onKeyframesUpdate: (keyframes) => {
|
|
16861
|
-
this.
|
|
16965
|
+
this.prepareRealtimeRendering(keyframes);
|
|
16862
16966
|
},
|
|
16863
16967
|
onStartRendering: () => {
|
|
16864
|
-
this.
|
|
16865
|
-
this.isConversationActive = true;
|
|
16866
|
-
this.setState(
|
|
16867
|
-
"speaking"
|
|
16868
|
-
/* Speaking */
|
|
16869
|
-
);
|
|
16870
|
-
logEvent("character_view", "info", {
|
|
16871
|
-
avatar_id: this.avatar.id,
|
|
16872
|
-
event: "rendering_started",
|
|
16873
|
-
keyframesCount: this.currentKeyframes.length
|
|
16874
|
-
});
|
|
16968
|
+
this.startRealtimeRendering();
|
|
16875
16969
|
},
|
|
16876
16970
|
onStopRendering: () => {
|
|
16877
|
-
this.
|
|
16878
|
-
this.generateEndTransition();
|
|
16971
|
+
this.stopRealtimeRendering();
|
|
16879
16972
|
},
|
|
16880
16973
|
onInterrupt: () => {
|
|
16881
16974
|
this.handleInterrupt();
|
|
16882
|
-
},
|
|
16883
|
-
getCurrentIdleFrame: async () => {
|
|
16884
|
-
return this.getCurrentDisplayedIdleFrame();
|
|
16885
16975
|
}
|
|
16886
16976
|
});
|
|
16887
16977
|
}
|
|
16888
16978
|
/**
|
|
16889
|
-
*
|
|
16979
|
+
* Prepare realtime rendering (generate transition to Speaking)
|
|
16980
|
+
* Unified logic: from current playing frame -> Speaking first frame
|
|
16890
16981
|
* @internal
|
|
16891
16982
|
*/
|
|
16892
|
-
async
|
|
16983
|
+
async prepareRealtimeRendering(keyframes) {
|
|
16984
|
+
const state = this.renderingState;
|
|
16985
|
+
if ((state === "speaking" || state === "transitioningToSpeaking") && this.currentKeyframes.length > 0) {
|
|
16986
|
+
this.currentKeyframes = keyframes;
|
|
16987
|
+
return;
|
|
16988
|
+
}
|
|
16989
|
+
this.stopIdleAnimationLoop();
|
|
16990
|
+
this.currentKeyframes = keyframes;
|
|
16893
16991
|
this.setState(
|
|
16894
|
-
"
|
|
16895
|
-
/*
|
|
16992
|
+
"transitioningToSpeaking"
|
|
16993
|
+
/* TransitioningToSpeaking */
|
|
16896
16994
|
);
|
|
16897
16995
|
try {
|
|
16898
|
-
|
|
16899
|
-
if (
|
|
16900
|
-
|
|
16901
|
-
}
|
|
16902
|
-
if (!fromFrame) {
|
|
16903
|
-
this.setState(
|
|
16904
|
-
"idle"
|
|
16905
|
-
/* Idle */
|
|
16906
|
-
);
|
|
16907
|
-
return;
|
|
16908
|
-
}
|
|
16909
|
-
const fromFrameWithPP = this.avatarController.applyPostProcessingToFlame(fromFrame);
|
|
16910
|
-
const idleFirstProto = await this.getCachedIdleFirstFrame();
|
|
16911
|
-
if (idleFirstProto) {
|
|
16912
|
-
const frames = this.generateAndAlignTransitionFrames(fromFrameWithPP, idleFirstProto, this.endTransitionDurationMs);
|
|
16913
|
-
if (frames.length > 0) {
|
|
16914
|
-
this.endTransitionFrames = frames;
|
|
16915
|
-
this.currentPlayingFrame = null;
|
|
16916
|
-
if (APP_CONFIG.debug)
|
|
16917
|
-
logger.log("[AvatarView] End transition started:", frames.length, "frames");
|
|
16996
|
+
const avatarCore = AvatarSDK.getAvatarCore();
|
|
16997
|
+
if (avatarCore && keyframes.length > 0) {
|
|
16998
|
+
if (this.renderingState !== "transitioningToSpeaking") {
|
|
16918
16999
|
return;
|
|
16919
17000
|
}
|
|
17001
|
+
let fromFrame = this.currentPlayingFrame;
|
|
17002
|
+
if (!fromFrame) {
|
|
17003
|
+
if (state === "idle") {
|
|
17004
|
+
const displayedIdleFrameIndex = this.getDisplayedIdleFrameIndex();
|
|
17005
|
+
const idleParams = await avatarCore.getCurrentFrameParams(displayedIdleFrameIndex, this.characterId);
|
|
17006
|
+
fromFrame = convertWasmParamsToProtoFlame(idleParams);
|
|
17007
|
+
} else if (state === "transitioningToIdle" || state === "transitioningToSpeaking") {
|
|
17008
|
+
if (this.transitionKeyframes.length > 0) {
|
|
17009
|
+
const elapsed = performance.now() - this.transitionStartTime;
|
|
17010
|
+
const currentTransitionDurationMs = state === "transitioningToSpeaking" ? this.startTransitionDurationMs : this.endTransitionDurationMs;
|
|
17011
|
+
const progress = Math.min(1, Math.max(0, elapsed / currentTransitionDurationMs));
|
|
17012
|
+
const steps = this.transitionKeyframes.length;
|
|
17013
|
+
const idx = Math.min(steps - 1, Math.floor(progress * (steps - 1)));
|
|
17014
|
+
fromFrame = this.transitionKeyframes[idx];
|
|
17015
|
+
}
|
|
17016
|
+
}
|
|
17017
|
+
}
|
|
17018
|
+
if (!fromFrame) {
|
|
17019
|
+
logger.warn("[AvatarView] Cannot get current playing frame, fallback to idle frame");
|
|
17020
|
+
const displayedIdleFrameIndex = this.getDisplayedIdleFrameIndex();
|
|
17021
|
+
const idleParams = await avatarCore.getCurrentFrameParams(displayedIdleFrameIndex, this.characterId);
|
|
17022
|
+
fromFrame = convertWasmParamsToProtoFlame(idleParams);
|
|
17023
|
+
}
|
|
17024
|
+
await this.getCachedIdleFirstFrame();
|
|
17025
|
+
const firstSpeaking = keyframes[0];
|
|
17026
|
+
const firstSpeakingWithPostProcessing = this.avatarController.applyPostProcessingToFlame(firstSpeaking);
|
|
17027
|
+
this.transitionKeyframes = this.generateAndAlignTransitionFrames(fromFrame, firstSpeakingWithPostProcessing, this.startTransitionDurationMs, true);
|
|
17028
|
+
this.transitionStartTime = performance.now();
|
|
17029
|
+
this.currentPlayingFrame = null;
|
|
17030
|
+
if (this.transitionKeyframes.length === 0) {
|
|
17031
|
+
this.setState(
|
|
17032
|
+
"speaking"
|
|
17033
|
+
/* Speaking */
|
|
17034
|
+
);
|
|
17035
|
+
this.avatarController.onTransitionComplete();
|
|
17036
|
+
} else {
|
|
17037
|
+
if (APP_CONFIG.debug)
|
|
17038
|
+
logger.log("[AvatarView] Transition started:", this.transitionKeyframes.length, "frames");
|
|
17039
|
+
}
|
|
16920
17040
|
}
|
|
16921
17041
|
} catch (e2) {
|
|
16922
|
-
logger.warn("[AvatarView]
|
|
17042
|
+
logger.warn("[AvatarView] Transition generation failed:", e2 instanceof Error ? e2.message : String(e2));
|
|
17043
|
+
if (this.renderingState === "transitioningToSpeaking") {
|
|
17044
|
+
this.setState(
|
|
17045
|
+
"speaking"
|
|
17046
|
+
/* Speaking */
|
|
17047
|
+
);
|
|
17048
|
+
this.avatarController.onTransitionComplete();
|
|
17049
|
+
}
|
|
17050
|
+
}
|
|
17051
|
+
this.startRealtimeAnimationLoop();
|
|
17052
|
+
}
|
|
17053
|
+
/**
|
|
17054
|
+
* Start realtime rendering loop
|
|
17055
|
+
* @internal
|
|
17056
|
+
*/
|
|
17057
|
+
startRealtimeRendering() {
|
|
17058
|
+
if (APP_CONFIG.debug)
|
|
17059
|
+
logger.log("[AvatarView] Starting realtime rendering with", this.currentKeyframes.length, "frames");
|
|
17060
|
+
logEvent("character_view", "info", {
|
|
17061
|
+
avatar_id: this.avatar.id,
|
|
17062
|
+
event: "rendering_started",
|
|
17063
|
+
keyframesCount: this.currentKeyframes.length
|
|
17064
|
+
});
|
|
17065
|
+
}
|
|
17066
|
+
/**
|
|
17067
|
+
* Stop realtime conversation rendering
|
|
17068
|
+
* @internal
|
|
17069
|
+
*/
|
|
17070
|
+
stopRealtimeRendering() {
|
|
17071
|
+
var _a, _b;
|
|
17072
|
+
const state = this.renderingState;
|
|
17073
|
+
if (state === "idle" || state === "transitioningToIdle") {
|
|
17074
|
+
return;
|
|
16923
17075
|
}
|
|
16924
|
-
if (
|
|
17076
|
+
if (state !== "speaking" && state !== "transitioningToSpeaking") {
|
|
17077
|
+
if (state === "transitioningToIdle") {
|
|
17078
|
+
return;
|
|
17079
|
+
}
|
|
16925
17080
|
this.setState(
|
|
16926
17081
|
"idle"
|
|
16927
17082
|
/* Idle */
|
|
16928
17083
|
);
|
|
17084
|
+
this.stopRealtimeAnimationLoop();
|
|
17085
|
+
this.startIdleAnimationLoop();
|
|
17086
|
+
return;
|
|
16929
17087
|
}
|
|
17088
|
+
this.setState(
|
|
17089
|
+
"transitioningToIdle"
|
|
17090
|
+
/* TransitioningToIdle */
|
|
17091
|
+
);
|
|
17092
|
+
(_b = (_a = this.avatarController).onConversationState) == null ? void 0 : _b.call(_a, ConversationState.idle);
|
|
17093
|
+
(async () => {
|
|
17094
|
+
try {
|
|
17095
|
+
if (this.renderingState !== "transitioningToIdle") {
|
|
17096
|
+
return;
|
|
17097
|
+
}
|
|
17098
|
+
const avatarCore = AvatarSDK.getAvatarCore();
|
|
17099
|
+
if (avatarCore) {
|
|
17100
|
+
let fromFrame = this.currentPlayingFrame;
|
|
17101
|
+
if (!fromFrame) {
|
|
17102
|
+
if (this.lastRealtimeProtoFrame) {
|
|
17103
|
+
fromFrame = this.lastRealtimeProtoFrame;
|
|
17104
|
+
} else if (this.currentKeyframes.length > 0) {
|
|
17105
|
+
const clampedFrameIndex = this.getClampedRealtimeFrameIndex(this.lastRenderedFrameIndex);
|
|
17106
|
+
if (clampedFrameIndex >= 0) {
|
|
17107
|
+
fromFrame = this.currentKeyframes[clampedFrameIndex];
|
|
17108
|
+
}
|
|
17109
|
+
}
|
|
17110
|
+
}
|
|
17111
|
+
if (!fromFrame && this.transitionKeyframes.length > 0) {
|
|
17112
|
+
const elapsed = performance.now() - this.transitionStartTime;
|
|
17113
|
+
const progress = Math.min(1, Math.max(0, elapsed / this.endTransitionDurationMs));
|
|
17114
|
+
const steps = this.transitionKeyframes.length;
|
|
17115
|
+
const idx = Math.min(steps - 1, Math.floor(progress * (steps - 1)));
|
|
17116
|
+
fromFrame = this.transitionKeyframes[idx];
|
|
17117
|
+
}
|
|
17118
|
+
if (!fromFrame) {
|
|
17119
|
+
logger.warn("[AvatarView] Cannot get current playing frame for transition to idle, fallback to idle frame");
|
|
17120
|
+
this.setState(
|
|
17121
|
+
"idle"
|
|
17122
|
+
/* Idle */
|
|
17123
|
+
);
|
|
17124
|
+
this.stopRealtimeAnimationLoop();
|
|
17125
|
+
this.startIdleAnimationLoop();
|
|
17126
|
+
return;
|
|
17127
|
+
}
|
|
17128
|
+
const fromFrameWithPostProcessing = this.avatarController.applyPostProcessingToFlame(fromFrame);
|
|
17129
|
+
const idleFirstProto = await this.getCachedIdleFirstFrame();
|
|
17130
|
+
if (idleFirstProto) {
|
|
17131
|
+
this.transitionKeyframes = this.generateAndAlignTransitionFrames(fromFrameWithPostProcessing, idleFirstProto, this.endTransitionDurationMs);
|
|
17132
|
+
this.transitionStartTime = performance.now();
|
|
17133
|
+
this.currentPlayingFrame = null;
|
|
17134
|
+
if (this.transitionKeyframes.length > 0 && this.renderingState === "transitioningToIdle") {
|
|
17135
|
+
if (APP_CONFIG.debug)
|
|
17136
|
+
logger.log("[AvatarView] Return transition started:", this.transitionKeyframes.length, "frames");
|
|
17137
|
+
if (!this.realtimeAnimationLoopId) {
|
|
17138
|
+
this.startRealtimeAnimationLoop();
|
|
17139
|
+
}
|
|
17140
|
+
return;
|
|
17141
|
+
}
|
|
17142
|
+
}
|
|
17143
|
+
}
|
|
17144
|
+
} catch (e2) {
|
|
17145
|
+
logger.warn("[AvatarView] Return transition generation failed:", e2 instanceof Error ? e2.message : String(e2));
|
|
17146
|
+
}
|
|
17147
|
+
if (this.renderingState === "transitioningToIdle") {
|
|
17148
|
+
this.setState(
|
|
17149
|
+
"idle"
|
|
17150
|
+
/* Idle */
|
|
17151
|
+
);
|
|
17152
|
+
this.stopRealtimeAnimationLoop();
|
|
17153
|
+
this.startIdleAnimationLoop();
|
|
17154
|
+
}
|
|
17155
|
+
})();
|
|
16930
17156
|
}
|
|
16931
17157
|
/**
|
|
16932
17158
|
* Cleanup view resources
|
|
@@ -16943,7 +17169,7 @@ class AvatarView {
|
|
|
16943
17169
|
this.avatarController.clear();
|
|
16944
17170
|
this.avatarController.dispose();
|
|
16945
17171
|
}
|
|
16946
|
-
this.
|
|
17172
|
+
this.stopAllAnimationLoops();
|
|
16947
17173
|
this.stopAvatarActiveHeartbeat();
|
|
16948
17174
|
this.setState(
|
|
16949
17175
|
"idle"
|
|
@@ -17100,14 +17326,14 @@ class AvatarView {
|
|
|
17100
17326
|
}
|
|
17101
17327
|
/**
|
|
17102
17328
|
* Pause rendering loop
|
|
17103
|
-
*
|
|
17329
|
+
*
|
|
17104
17330
|
* When called:
|
|
17105
17331
|
* - Rendering loop stops (no GPU/canvas updates)
|
|
17106
17332
|
* - Audio playback continues normally
|
|
17107
17333
|
* - Animation state machine continues running
|
|
17108
|
-
*
|
|
17334
|
+
*
|
|
17109
17335
|
* Use `resumeRendering()` to resume rendering.
|
|
17110
|
-
*
|
|
17336
|
+
*
|
|
17111
17337
|
* @example
|
|
17112
17338
|
* // Stop rendering to save GPU resources (audio continues)
|
|
17113
17339
|
* avatarView.pauseRendering()
|
|
@@ -17121,11 +17347,11 @@ class AvatarView {
|
|
|
17121
17347
|
}
|
|
17122
17348
|
/**
|
|
17123
17349
|
* Resume rendering loop
|
|
17124
|
-
*
|
|
17350
|
+
*
|
|
17125
17351
|
* When called:
|
|
17126
17352
|
* - Rendering loop resumes from current state
|
|
17127
17353
|
* - If in Idle state, immediately renders current frame to restore display
|
|
17128
|
-
*
|
|
17354
|
+
*
|
|
17129
17355
|
* @example
|
|
17130
17356
|
* // Resume rendering
|
|
17131
17357
|
* avatarView.resumeRendering()
|
|
@@ -17187,11 +17413,11 @@ class AvatarView {
|
|
|
17187
17413
|
}
|
|
17188
17414
|
/**
|
|
17189
17415
|
* Get or set avatar transform in canvas
|
|
17190
|
-
*
|
|
17416
|
+
*
|
|
17191
17417
|
* @example
|
|
17192
17418
|
* // Get current transform
|
|
17193
17419
|
* const current = avatarView.transform
|
|
17194
|
-
*
|
|
17420
|
+
*
|
|
17195
17421
|
* // Set transform
|
|
17196
17422
|
* avatarView.transform = { x: 0.5, y: 0, scale: 2.0 }
|
|
17197
17423
|
*/
|
package/dist/index.js
CHANGED
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.
|
|
4
|
+
"version": "1.0.0-beta.86",
|
|
5
5
|
"packageManager": "pnpm@10.18.2",
|
|
6
6
|
"description": "AvatarKit SDK - 3D Gaussian Splatting Avatar Rendering SDK",
|
|
7
7
|
"author": "AvatarKit Team",
|
|
@@ -62,12 +62,8 @@
|
|
|
62
62
|
"next": ">=13.0.0"
|
|
63
63
|
},
|
|
64
64
|
"peerDependenciesMeta": {
|
|
65
|
-
"vite": {
|
|
66
|
-
|
|
67
|
-
},
|
|
68
|
-
"next": {
|
|
69
|
-
"optional": true
|
|
70
|
-
}
|
|
65
|
+
"vite": { "optional": true },
|
|
66
|
+
"next": { "optional": true }
|
|
71
67
|
},
|
|
72
68
|
"dependencies": {
|
|
73
69
|
"@bufbuild/protobuf": "^2.10.0",
|