@spatialwalk/avatarkit 1.0.0-beta.57 → 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 CHANGED
@@ -2,6 +2,14 @@
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
+
5
13
  ## [1.0.0-beta.57] - 2026-01-09
6
14
 
7
15
  ### 🐛 Bugfixes
@@ -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-cfYSlUmQ.js";
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);
@@ -1,4 +1,3 @@
1
- import { Flame } from '../generated/driveningress/v1/driveningress';
2
1
  import { Avatar } from './Avatar';
3
2
  import { AvatarController } from './AvatarController';
4
3
  export declare class AvatarView {
@@ -22,7 +21,8 @@ export declare class AvatarView {
22
21
  private currentFPS;
23
22
  private transitionKeyframes;
24
23
  private transitionStartTime;
25
- private readonly transitionDurationMs;
24
+ private startTransitionDurationMs;
25
+ private endTransitionDurationMs;
26
26
  private cachedIdleFirstFrame;
27
27
  private idleCurrentFrameIndex;
28
28
  private characterHandle;
@@ -57,7 +57,7 @@ export declare class AvatarView {
57
57
  private startRealtimeRendering;
58
58
  private stopRealtimeRendering;
59
59
  dispose(): void;
60
- renderFlame(flame: Flame): Promise<void>;
60
+ setTransitionDuration(isStart: boolean, durationMs: number): void;
61
61
  private rerenderCurrentFrameWithNewCamera;
62
62
  private handleResize;
63
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-Co3xec_k.js");
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.57");
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
- function linearLerp(from, to2, progress) {
13940
- const easedProgress = 0.5 - Math.cos(progress * Math.PI) * 0.5;
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], easedProgress),
13943
- rotation: lerpArrays(from.rotation || [0, 0, 0], to2.rotation || [0, 0, 0], easedProgress),
13944
- neckPose: lerpArrays(from.neckPose || [0, 0, 0], to2.neckPose || [0, 0, 0], easedProgress),
13945
- jawPose: lerpArrays(from.jawPose || [0, 0, 0], to2.jawPose || [0, 0, 0], easedProgress),
13946
- eyePose: lerpArrays(from.eyePose || [0, 0, 0, 0, 0, 0], to2.eyePose || [0, 0, 0, 0, 0, 0], easedProgress),
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 && fromEyelid.length > 0 && toEyelid && toEyelid.length > 0)
13951
- return lerpArrays(fromEyelid, toEyelid, easedProgress);
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 || [], easedProgress)
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] = linearLerp(from, to2, progress);
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, "transitionDurationMs", 400);
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
- this.transitionDurationMs,
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 progress = Math.min(1, Math.max(0, elapsed / this.transitionDurationMs));
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.transitionDurationMs + 100) {
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();
@@ -14550,7 +14603,7 @@ class AvatarView {
14550
14603
  await this.getCachedIdleFirstFrame();
14551
14604
  const firstSpeaking = keyframes[0];
14552
14605
  const firstSpeakingWithPostProcessing = this.avatarController.applyPostProcessingToFlame(firstSpeaking);
14553
- this.transitionKeyframes = this.generateAndAlignTransitionFrames(idleFrameProto, firstSpeakingWithPostProcessing);
14606
+ this.transitionKeyframes = this.generateAndAlignTransitionFrames(idleFrameProto, firstSpeakingWithPostProcessing, this.startTransitionDurationMs);
14554
14607
  this.transitionStartTime = performance.now();
14555
14608
  if (this.transitionKeyframes.length === 0) {
14556
14609
  this.setState("speaking");
@@ -14603,7 +14656,7 @@ class AvatarView {
14603
14656
  const lastSpeaking = this.avatarController.applyPostProcessingToFlame(lastSpeakingRaw);
14604
14657
  const idleFirstProto = await this.getCachedIdleFirstFrame();
14605
14658
  if (idleFirstProto) {
14606
- this.transitionKeyframes = this.generateAndAlignTransitionFrames(lastSpeaking, idleFirstProto);
14659
+ this.transitionKeyframes = this.generateAndAlignTransitionFrames(lastSpeaking, idleFirstProto, this.endTransitionDurationMs);
14607
14660
  this.transitionStartTime = performance.now();
14608
14661
  if (this.transitionKeyframes.length > 0 && this.renderingState === "transitioningToIdle") {
14609
14662
  if (APP_CONFIG.debug)
@@ -14667,6 +14720,22 @@ class AvatarView {
14667
14720
  if (APP_CONFIG.debug)
14668
14721
  logger.log("[AvatarView] Avatar view disposed successfully");
14669
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
+ }
14670
14739
  getCameraConfig() {
14671
14740
  return this.cameraConfig;
14672
14741
  }
@@ -14683,30 +14752,6 @@ class AvatarView {
14683
14752
  }
14684
14753
  }
14685
14754
  }
14686
- async renderFlame(flame) {
14687
- if (!this.isInitialized || !this.renderSystem) {
14688
- throw new Error("AvatarView not initialized");
14689
- }
14690
- try {
14691
- const processedFlame = this.avatarController.applyPostProcessingToFlame(flame);
14692
- const wasmParams = convertProtoFlameToWasmParams(processedFlame);
14693
- const avatarCore = AvatarSDK.getAvatarCore();
14694
- if (!avatarCore) {
14695
- throw new Error("AvatarCore not available");
14696
- }
14697
- const splatData = await avatarCore.computeFrameFlatFromParams(
14698
- wasmParams,
14699
- this.characterHandle ?? void 0
14700
- );
14701
- if (splatData) {
14702
- this.renderSystem.loadSplatsFromPackedData(splatData);
14703
- this.renderSystem.renderFrame();
14704
- }
14705
- } catch (error) {
14706
- logger.error("[AvatarView] Failed to render flame:", error instanceof Error ? error.message : String(error));
14707
- throw error;
14708
- }
14709
- }
14710
14755
  async rerenderCurrentFrameWithNewCamera() {
14711
14756
  if (this.avatarController.state !== AvatarState.paused || this.renderingState !== "speaking" || !this.renderSystem) {
14712
14757
  return;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { b, c, f, d, j, g, C, i, D, E, k, h, L, R, S, m } from "./index-cfYSlUmQ.js";
1
+ import { b, c, f, d, j, g, C, i, D, E, k, h, L, R, S, m } from "./index-wQJM-Vug.js";
2
2
  export {
3
3
  b as Avatar,
4
4
  c as AvatarController,
@@ -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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@spatialwalk/avatarkit",
3
3
  "type": "module",
4
- "version": "1.0.0-beta.57",
4
+ "version": "1.0.0-beta.58",
5
5
  "packageManager": "pnpm@10.18.2",
6
6
  "description": "SPAvatar SDK - 3D Gaussian Splatting Avatar Rendering SDK",
7
7
  "author": "SPAvatar Team",
@@ -1,28 +0,0 @@
1
- import { AvatarView, Flame } from '@spatialwalk/avatarkit';
2
- declare function initializeSDK(): Promise<void>;
3
- declare function createAvatarView(characterId: string, container: HTMLElement): Promise<AvatarView>;
4
- declare function fetchFlameDataFromServer(characterId: string, audioData: ArrayBuffer): Promise<Flame[]>;
5
- declare class FlameRenderer {
6
- private avatarView;
7
- private keyframes;
8
- private currentFrameIndex;
9
- private animationFrameId;
10
- private isPlaying;
11
- private fps;
12
- private frameInterval;
13
- private lastRenderTime;
14
- constructor(avatarView: AvatarView);
15
- setKeyframes(keyframes: Flame[]): void;
16
- start(): void;
17
- stop(): void;
18
- renderFrame(frameIndex: number): Promise<void>;
19
- private renderLoop;
20
- setFPS(fps: number): void;
21
- seek(frameIndex: number): void;
22
- getCurrentFrameIndex(): number;
23
- getTotalFrames(): number;
24
- }
25
- declare function main(): Promise<void>;
26
- declare function fetchAudioData(): Promise<ArrayBuffer>;
27
-
28
- export { initializeSDK, createAvatarView, fetchFlameDataFromServer, FlameRenderer, main, fetchAudioData };