@spatialwalk/avatarkit 1.0.0-beta.56 → 1.0.0-beta.58
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 +15 -0
- package/dist/{StreamingAudioPlayer-BL5cZ22i.js → StreamingAudioPlayer-Cka1VDtX.js} +1 -1
- package/dist/core/AvatarView.d.ts +3 -1
- package/dist/{index-C4RgEOS1.js → index-wQJM-Vug.js} +92 -20
- package/dist/index.js +1 -1
- package/dist/utils/animation-interpolation.d.ts +8 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.0-beta.58] - 2026-01-12
|
|
6
|
+
|
|
7
|
+
### ✨ New Features
|
|
8
|
+
- **Transition Duration Control API** - Added `setTransitionDuration(isStart: boolean, durationMs: number)` method to `AvatarView` to configure transition animation durations:
|
|
9
|
+
- `isStart: true` configures the start transition duration (Idle -> Speaking), default 400ms
|
|
10
|
+
- `isStart: false` configures the end transition duration (Speaking -> Idle), default 1600ms
|
|
11
|
+
- Allows external applications to customize transition animation timing
|
|
12
|
+
|
|
13
|
+
## [1.0.0-beta.57] - 2026-01-09
|
|
14
|
+
|
|
15
|
+
### 🐛 Bugfixes
|
|
16
|
+
- **Idle Animation Jiggle Fix** - Fixed issue where idle animation would "jiggle" before transitioning to speaking when external audio and animation data were sent. The `handleInterrupt()` method now correctly skips processing when already in `Idle` state, preventing unnecessary idle loop restarts.
|
|
17
|
+
|
|
18
|
+
## [1.0.0-beta.56] - 2026-01-09
|
|
19
|
+
|
|
5
20
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
21
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
22
|
|
|
@@ -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-wQJM-Vug.js";
|
|
5
5
|
class StreamingAudioPlayer {
|
|
6
6
|
constructor(options) {
|
|
7
7
|
__publicField(this, "audioContext", null);
|
|
@@ -21,7 +21,8 @@ export declare class AvatarView {
|
|
|
21
21
|
private currentFPS;
|
|
22
22
|
private transitionKeyframes;
|
|
23
23
|
private transitionStartTime;
|
|
24
|
-
private
|
|
24
|
+
private startTransitionDurationMs;
|
|
25
|
+
private endTransitionDurationMs;
|
|
25
26
|
private cachedIdleFirstFrame;
|
|
26
27
|
private idleCurrentFrameIndex;
|
|
27
28
|
private characterHandle;
|
|
@@ -56,6 +57,7 @@ export declare class AvatarView {
|
|
|
56
57
|
private startRealtimeRendering;
|
|
57
58
|
private stopRealtimeRendering;
|
|
58
59
|
dispose(): void;
|
|
60
|
+
setTransitionDuration(isStart: boolean, durationMs: number): void;
|
|
59
61
|
private rerenderCurrentFrameWithNewCamera;
|
|
60
62
|
private handleResize;
|
|
61
63
|
get transform(): {
|
|
@@ -7639,7 +7639,7 @@ const _AnimationPlayer = class _AnimationPlayer {
|
|
|
7639
7639
|
if (this.streamingPlayer) {
|
|
7640
7640
|
return;
|
|
7641
7641
|
}
|
|
7642
|
-
const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-
|
|
7642
|
+
const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-Cka1VDtX.js");
|
|
7643
7643
|
const { AvatarSDK: AvatarSDK2 } = await Promise.resolve().then(() => AvatarSDK$1);
|
|
7644
7644
|
const audioFormat = AvatarSDK2.getAudioFormat();
|
|
7645
7645
|
this.streamingPlayer = new StreamingAudioPlayer({
|
|
@@ -9062,7 +9062,7 @@ class AvatarSDK {
|
|
|
9062
9062
|
}
|
|
9063
9063
|
__publicField(AvatarSDK, "_isInitialized", false);
|
|
9064
9064
|
__publicField(AvatarSDK, "_configuration", null);
|
|
9065
|
-
__publicField(AvatarSDK, "_version", "1.0.0-beta.
|
|
9065
|
+
__publicField(AvatarSDK, "_version", "1.0.0-beta.58");
|
|
9066
9066
|
__publicField(AvatarSDK, "_avatarCore", null);
|
|
9067
9067
|
__publicField(AvatarSDK, "_dynamicSdkConfig", null);
|
|
9068
9068
|
const AvatarSDK$1 = Object.freeze(Object.defineProperty({
|
|
@@ -13936,31 +13936,82 @@ function lerpArrays(from, to2, progress) {
|
|
|
13936
13936
|
}
|
|
13937
13937
|
return result2;
|
|
13938
13938
|
}
|
|
13939
|
-
|
|
13940
|
-
|
|
13939
|
+
const clamp01 = (x2) => Math.max(0, Math.min(1, x2));
|
|
13940
|
+
function createBezierEasing(x1, y1, x2, y2) {
|
|
13941
|
+
const cx = 3 * x1;
|
|
13942
|
+
const bx = 3 * (x2 - x1) - cx;
|
|
13943
|
+
const ax = 1 - cx - bx;
|
|
13944
|
+
const cy = 3 * y1;
|
|
13945
|
+
const by = 3 * (y2 - y1) - cy;
|
|
13946
|
+
const ay = 1 - cy - by;
|
|
13947
|
+
const sampleCurveX = (t2) => ((ax * t2 + bx) * t2 + cx) * t2;
|
|
13948
|
+
const sampleCurveY = (t2) => ((ay * t2 + by) * t2 + cy) * t2;
|
|
13949
|
+
const sampleCurveDerivativeX = (t2) => (3 * ax * t2 + 2 * bx) * t2 + cx;
|
|
13950
|
+
const solveCurveX = (x3) => {
|
|
13951
|
+
let t2 = x3;
|
|
13952
|
+
for (let i2 = 0; i2 < 8; i2++) {
|
|
13953
|
+
const error = sampleCurveX(t2) - x3;
|
|
13954
|
+
if (Math.abs(error) < 1e-6) break;
|
|
13955
|
+
const d2 = sampleCurveDerivativeX(t2);
|
|
13956
|
+
if (Math.abs(d2) < 1e-6) break;
|
|
13957
|
+
t2 -= error / d2;
|
|
13958
|
+
}
|
|
13959
|
+
return t2;
|
|
13960
|
+
};
|
|
13961
|
+
return (x3) => {
|
|
13962
|
+
if (x3 <= 0) return 0;
|
|
13963
|
+
if (x3 >= 1) return 1;
|
|
13964
|
+
return sampleCurveY(solveCurveX(x3));
|
|
13965
|
+
};
|
|
13966
|
+
}
|
|
13967
|
+
const BEZIER_CURVES = {
|
|
13968
|
+
jaw: createBezierEasing(0.2, 0.8, 0.3, 1),
|
|
13969
|
+
expression: createBezierEasing(0.4, 0, 0.2, 1),
|
|
13970
|
+
eye: createBezierEasing(0.3, 0, 0.1, 1),
|
|
13971
|
+
neck: createBezierEasing(0.1, 0.2, 0.2, 1),
|
|
13972
|
+
global: createBezierEasing(0.42, 0, 0.58, 1)
|
|
13973
|
+
};
|
|
13974
|
+
const TIME_SCALE = {
|
|
13975
|
+
jaw: 2.5,
|
|
13976
|
+
expression: 1.6,
|
|
13977
|
+
eye: 1.3,
|
|
13978
|
+
neck: 1,
|
|
13979
|
+
global: 1
|
|
13980
|
+
};
|
|
13981
|
+
function bezierLerp(from, to2, progress) {
|
|
13982
|
+
const getT = (key) => {
|
|
13983
|
+
const scaledProgress = clamp01(progress * TIME_SCALE[key]);
|
|
13984
|
+
return BEZIER_CURVES[key](scaledProgress);
|
|
13985
|
+
};
|
|
13941
13986
|
return {
|
|
13942
|
-
translation: lerpArrays(from.translation || [0, 0, 0], to2.translation || [0, 0, 0],
|
|
13943
|
-
rotation: lerpArrays(from.rotation || [0, 0, 0], to2.rotation || [0, 0, 0],
|
|
13944
|
-
neckPose: lerpArrays(from.neckPose || [0, 0, 0], to2.neckPose || [0, 0, 0],
|
|
13945
|
-
jawPose: lerpArrays(from.jawPose || [0, 0, 0], to2.jawPose || [0, 0, 0],
|
|
13946
|
-
eyePose: lerpArrays(from.eyePose || [0, 0, 0, 0, 0, 0], to2.eyePose || [0, 0, 0, 0, 0, 0],
|
|
13987
|
+
translation: lerpArrays(from.translation || [0, 0, 0], to2.translation || [0, 0, 0], getT("global")),
|
|
13988
|
+
rotation: lerpArrays(from.rotation || [0, 0, 0], to2.rotation || [0, 0, 0], getT("global")),
|
|
13989
|
+
neckPose: lerpArrays(from.neckPose || [0, 0, 0], to2.neckPose || [0, 0, 0], getT("neck")),
|
|
13990
|
+
jawPose: lerpArrays(from.jawPose || [0, 0, 0], to2.jawPose || [0, 0, 0], getT("jaw")),
|
|
13991
|
+
eyePose: lerpArrays(from.eyePose || [0, 0, 0, 0, 0, 0], to2.eyePose || [0, 0, 0, 0, 0, 0], getT("eye")),
|
|
13947
13992
|
eyeLid: (() => {
|
|
13948
13993
|
const fromEyelid = from.eyeLid;
|
|
13949
13994
|
const toEyelid = to2.eyeLid;
|
|
13950
|
-
if (fromEyelid
|
|
13951
|
-
return lerpArrays(fromEyelid, toEyelid,
|
|
13995
|
+
if ((fromEyelid == null ? void 0 : fromEyelid.length) && (toEyelid == null ? void 0 : toEyelid.length))
|
|
13996
|
+
return lerpArrays(fromEyelid, toEyelid, getT("eye"));
|
|
13952
13997
|
return fromEyelid || toEyelid || [];
|
|
13953
13998
|
})(),
|
|
13954
|
-
expression: lerpArrays(from.expression || [], to2.expression || [],
|
|
13999
|
+
expression: lerpArrays(from.expression || [], to2.expression || [], getT("expression"))
|
|
13955
14000
|
};
|
|
13956
14001
|
}
|
|
13957
14002
|
function generateTransitionFrames(from, to2, durationMs, fps = 25) {
|
|
13958
14003
|
const steps = Math.max(1, Math.floor(durationMs / 1e3 * fps));
|
|
13959
14004
|
const frames = Array.from({ length: steps });
|
|
14005
|
+
if (steps === 1) {
|
|
14006
|
+
frames[0] = to2;
|
|
14007
|
+
return frames;
|
|
14008
|
+
}
|
|
13960
14009
|
for (let i2 = 0; i2 < steps; i2++) {
|
|
13961
14010
|
const progress = i2 / (steps - 1);
|
|
13962
|
-
frames[i2] =
|
|
14011
|
+
frames[i2] = bezierLerp(from, to2, progress);
|
|
13963
14012
|
}
|
|
14013
|
+
frames[0] = from;
|
|
14014
|
+
frames[frames.length - 1] = to2;
|
|
13964
14015
|
return frames;
|
|
13965
14016
|
}
|
|
13966
14017
|
class AvatarView {
|
|
@@ -13985,7 +14036,8 @@ class AvatarView {
|
|
|
13985
14036
|
__publicField(this, "currentFPS", 0);
|
|
13986
14037
|
__publicField(this, "transitionKeyframes", []);
|
|
13987
14038
|
__publicField(this, "transitionStartTime", 0);
|
|
13988
|
-
__publicField(this, "
|
|
14039
|
+
__publicField(this, "startTransitionDurationMs", 400);
|
|
14040
|
+
__publicField(this, "endTransitionDurationMs", 1600);
|
|
13989
14041
|
__publicField(this, "cachedIdleFirstFrame", null);
|
|
13990
14042
|
__publicField(this, "idleCurrentFrameIndex", 0);
|
|
13991
14043
|
__publicField(this, "characterHandle", null);
|
|
@@ -14039,12 +14091,12 @@ class AvatarView {
|
|
|
14039
14091
|
toFixed.expression = ensureLen(toFixed.expression, exprLen);
|
|
14040
14092
|
return { from: fromFixed, to: toFixed };
|
|
14041
14093
|
}
|
|
14042
|
-
generateAndAlignTransitionFrames(from, to2) {
|
|
14094
|
+
generateAndAlignTransitionFrames(from, to2, durationMs) {
|
|
14043
14095
|
const aligned = this.alignFlamePair(from, to2);
|
|
14044
14096
|
let keyframes = generateTransitionFrames(
|
|
14045
14097
|
aligned.from,
|
|
14046
14098
|
aligned.to,
|
|
14047
|
-
|
|
14099
|
+
durationMs,
|
|
14048
14100
|
APP_CONFIG.animation.fps
|
|
14049
14101
|
);
|
|
14050
14102
|
if (keyframes.length < 2) {
|
|
@@ -14394,7 +14446,8 @@ class AvatarView {
|
|
|
14394
14446
|
return;
|
|
14395
14447
|
}
|
|
14396
14448
|
const elapsed = performance.now() - this.transitionStartTime;
|
|
14397
|
-
const
|
|
14449
|
+
const currentTransitionDurationMs = state === "transitioningToSpeaking" ? this.startTransitionDurationMs : this.endTransitionDurationMs;
|
|
14450
|
+
const progress = Math.min(1, Math.max(0, elapsed / currentTransitionDurationMs));
|
|
14398
14451
|
const steps = this.transitionKeyframes.length;
|
|
14399
14452
|
const idx = Math.min(steps - 1, Math.floor(progress * (steps - 1)));
|
|
14400
14453
|
const currentFrame = this.transitionKeyframes[idx];
|
|
@@ -14420,7 +14473,7 @@ class AvatarView {
|
|
|
14420
14473
|
return;
|
|
14421
14474
|
}
|
|
14422
14475
|
}
|
|
14423
|
-
if (state === "transitioningToSpeaking" && this.transitionStartTime > 0 && this.transitionKeyframes.length > 0 && elapsed >= this.
|
|
14476
|
+
if (state === "transitioningToSpeaking" && this.transitionStartTime > 0 && this.transitionKeyframes.length > 0 && elapsed >= this.startTransitionDurationMs + 100) {
|
|
14424
14477
|
this.setState("speaking");
|
|
14425
14478
|
this.transitionKeyframes = [];
|
|
14426
14479
|
this.avatarController.onTransitionComplete();
|
|
@@ -14500,6 +14553,9 @@ class AvatarView {
|
|
|
14500
14553
|
}
|
|
14501
14554
|
handleInterrupt() {
|
|
14502
14555
|
const state = this.renderingState;
|
|
14556
|
+
if (state === "idle") {
|
|
14557
|
+
return;
|
|
14558
|
+
}
|
|
14503
14559
|
if (state === "transitioningToIdle") {
|
|
14504
14560
|
return;
|
|
14505
14561
|
}
|
|
@@ -14547,7 +14603,7 @@ class AvatarView {
|
|
|
14547
14603
|
await this.getCachedIdleFirstFrame();
|
|
14548
14604
|
const firstSpeaking = keyframes[0];
|
|
14549
14605
|
const firstSpeakingWithPostProcessing = this.avatarController.applyPostProcessingToFlame(firstSpeaking);
|
|
14550
|
-
this.transitionKeyframes = this.generateAndAlignTransitionFrames(idleFrameProto, firstSpeakingWithPostProcessing);
|
|
14606
|
+
this.transitionKeyframes = this.generateAndAlignTransitionFrames(idleFrameProto, firstSpeakingWithPostProcessing, this.startTransitionDurationMs);
|
|
14551
14607
|
this.transitionStartTime = performance.now();
|
|
14552
14608
|
if (this.transitionKeyframes.length === 0) {
|
|
14553
14609
|
this.setState("speaking");
|
|
@@ -14600,7 +14656,7 @@ class AvatarView {
|
|
|
14600
14656
|
const lastSpeaking = this.avatarController.applyPostProcessingToFlame(lastSpeakingRaw);
|
|
14601
14657
|
const idleFirstProto = await this.getCachedIdleFirstFrame();
|
|
14602
14658
|
if (idleFirstProto) {
|
|
14603
|
-
this.transitionKeyframes = this.generateAndAlignTransitionFrames(lastSpeaking, idleFirstProto);
|
|
14659
|
+
this.transitionKeyframes = this.generateAndAlignTransitionFrames(lastSpeaking, idleFirstProto, this.endTransitionDurationMs);
|
|
14604
14660
|
this.transitionStartTime = performance.now();
|
|
14605
14661
|
if (this.transitionKeyframes.length > 0 && this.renderingState === "transitioningToIdle") {
|
|
14606
14662
|
if (APP_CONFIG.debug)
|
|
@@ -14664,6 +14720,22 @@ class AvatarView {
|
|
|
14664
14720
|
if (APP_CONFIG.debug)
|
|
14665
14721
|
logger.log("[AvatarView] Avatar view disposed successfully");
|
|
14666
14722
|
}
|
|
14723
|
+
setTransitionDuration(isStart, durationMs) {
|
|
14724
|
+
if (durationMs <= 0) {
|
|
14725
|
+
throw new Error("Transition duration must be a positive number");
|
|
14726
|
+
}
|
|
14727
|
+
if (isStart) {
|
|
14728
|
+
this.startTransitionDurationMs = durationMs;
|
|
14729
|
+
if (APP_CONFIG.debug) {
|
|
14730
|
+
logger.log("[AvatarView] Start transition duration updated:", durationMs);
|
|
14731
|
+
}
|
|
14732
|
+
} else {
|
|
14733
|
+
this.endTransitionDurationMs = durationMs;
|
|
14734
|
+
if (APP_CONFIG.debug) {
|
|
14735
|
+
logger.log("[AvatarView] End transition duration updated:", durationMs);
|
|
14736
|
+
}
|
|
14737
|
+
}
|
|
14738
|
+
}
|
|
14667
14739
|
getCameraConfig() {
|
|
14668
14740
|
return this.cameraConfig;
|
|
14669
14741
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { Flame } from '../generated/driveningress/v1/driveningress';
|
|
2
|
+
export interface TransitionConfig {
|
|
3
|
+
duration: number;
|
|
4
|
+
delay: number;
|
|
5
|
+
easing: (t: number) => number;
|
|
6
|
+
}
|
|
7
|
+
export declare const TRANSITION_LAYERS: Record<string, TransitionConfig>;
|
|
8
|
+
|
|
9
|
+
export declare function bezierLerp(from: Flame, to: Flame, progress: number): Flame;
|
|
2
10
|
|
|
3
11
|
export declare function linearLerp(from: Flame, to: Flame, progress: number): Flame;
|
|
4
12
|
|
package/package.json
CHANGED