@spatialwalk/avatarkit 1.0.0-beta.60 → 1.0.0-beta.62
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 +26 -4
- package/README.md +3 -8
- package/dist/{StreamingAudioPlayer-CH89JZHk.js → StreamingAudioPlayer-BWsAt_s7.js} +1 -1
- package/dist/core/AvatarController.d.ts +1 -1
- package/dist/core/AvatarView.d.ts +8 -2
- package/dist/{index-D9nprBw2.js → index-Bhjn1nq3.js} +230 -203
- package/dist/index.js +1 -1
- package/dist/utils/animation-interpolation.d.ts +1 -1
- package/package.json +1 -1
- package/dist/utils/heartbeat-manager.d.ts +0 -18
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.0-beta.62] - 2026-01-14
|
|
6
|
+
|
|
7
|
+
### ✨ New Features
|
|
8
|
+
- **Bezier Curve Transition Animation** - Implemented Bezier curve easing functions for smoother transitions
|
|
9
|
+
- Added Bezier curve interpolation with different curves for different facial components (jaw, expression, eye, neck, global)
|
|
10
|
+
- Replaced linear interpolation with Bezier curve interpolation for more natural animation
|
|
11
|
+
- Split transition duration into start (200ms) and end (1600ms) for different transition types
|
|
12
|
+
|
|
13
|
+
### 🔧 Improvements
|
|
14
|
+
- **Transition API Enhancement** - Updated `generateTransitionFromIdle()` to support both start and end transitions
|
|
15
|
+
- Added `transitionType` parameter: `'start'` for Idle -> Flame, `'end'` for Flame -> Idle
|
|
16
|
+
- Removed deprecated linear interpolation code and unused easing functions
|
|
17
|
+
|
|
18
|
+
## [1.0.0-beta.61] - 2026-01-14
|
|
19
|
+
|
|
20
|
+
### 🔧 Improvements
|
|
21
|
+
- **Telemetry Events Update** - Updated telemetry events with new parameters and structure
|
|
22
|
+
- `sdk_initialized`: Added `env` and `dsm` parameters
|
|
23
|
+
- `driving_service_latency`: Replaced `driving_latency`, now tracks `tap_0`, `tap_1`, `tap_2`, `tap_f` timestamps (Number type, milliseconds)
|
|
24
|
+
- `avatar_active`: New event for avatar view activity tracking with `env` and `dsm` parameters, reported on first render and every 10 minutes
|
|
25
|
+
- **Heartbeat Refactoring** - Removed `HeartbeatManager`, integrated heartbeat functionality into `NetworkLayer` (driving service heartbeat) and `AvatarView` (avatar active heartbeat)
|
|
26
|
+
|
|
5
27
|
## [1.0.0-beta.60] - 2026-01-14
|
|
6
28
|
|
|
7
29
|
### 🔧 Configuration Changes
|
|
@@ -240,11 +262,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
240
262
|
- `LogLevel.warning` - Warning and error logs
|
|
241
263
|
- `LogLevel.all` - All logs (default)
|
|
242
264
|
- **Standardized CLS Logging Events** - All CLS logging events now use standardized field names for better querying
|
|
243
|
-
- Standard events: `sdk_initialized`, `send_audio_failed`, `message_error`, `heartbeat_failed`, `service_restarted`, `session_token_invalid`, `session_token_expired`, `yield_animation_failed`, `fetch_avatar_metadata_failed`, `download_avatar_assets_failed`
|
|
265
|
+
- Standard events: `sdk_initialized`, `send_audio_failed`, `message_error`, `heartbeat_failed`, `service_restarted`, `session_token_invalid`, `session_token_expired`, `yield_animation_failed`, `fetch_avatar_metadata_failed`, `download_avatar_assets_failed`, `avatar_active`
|
|
244
266
|
- Standard fields: `req_id`, `con_id`, `avatar_id`, `description`
|
|
245
|
-
- **End-to-End Latency Tracking** - Added `
|
|
246
|
-
- Tracks: `
|
|
247
|
-
- Automatically reported
|
|
267
|
+
- **End-to-End Latency Tracking** - Added `driving_service_latency` event to track latency metrics for both SDK and Host modes
|
|
268
|
+
- Tracks: `tap_0`, `tap_1`, `tap_2`, `tap_f` timestamps (Number type, milliseconds)
|
|
269
|
+
- Automatically reported once per conversation when first frame is received
|
|
248
270
|
|
|
249
271
|
### 🔧 Improvements
|
|
250
272
|
- **CLS Configuration Alignment** - Updated CLS endpoints and Topic IDs to match iOS SDK configuration
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AvatarKit SDK
|
|
2
2
|
|
|
3
3
|
Real-time virtual avatar rendering SDK based on 3D Gaussian Splatting, supporting audio-driven animation rendering and high-quality 3D rendering.
|
|
4
4
|
|
|
@@ -97,11 +97,7 @@ avatarView.avatarController.yieldFramesData(animationDataArray, conversationId)
|
|
|
97
97
|
|
|
98
98
|
### Complete Examples
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
**Example Project:** [AvatarSDK-Web-Demo](https://github.com/spatialwalk/AvatarSDK-Web-Demo)
|
|
103
|
-
|
|
104
|
-
This repository contains complete examples for Vanilla JS, Vue 3, and React, demonstrating:
|
|
100
|
+
This SDK supports two usage modes:
|
|
105
101
|
- SDK mode: Real-time audio input with automatic animation data reception
|
|
106
102
|
- Host mode: Custom data sources with manual audio/animation data management
|
|
107
103
|
|
|
@@ -652,6 +648,5 @@ Issues and Pull Requests are welcome!
|
|
|
652
648
|
## 📞 Support
|
|
653
649
|
|
|
654
650
|
For questions, please contact:
|
|
655
|
-
- Email:
|
|
651
|
+
- Email: code@spatialwalk.net
|
|
656
652
|
- Documentation: https://docs.spatialreal.ai
|
|
657
|
-
- GitHub: https://github.com/spavatar/sdk
|
|
@@ -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-Bhjn1nq3.js";
|
|
5
5
|
class StreamingAudioPlayer {
|
|
6
6
|
constructor(options) {
|
|
7
7
|
__publicField(this, "audioContext", null);
|
|
@@ -22,12 +22,15 @@ export declare class AvatarView {
|
|
|
22
22
|
private currentFPS;
|
|
23
23
|
private transitionKeyframes;
|
|
24
24
|
private transitionStartTime;
|
|
25
|
-
private readonly
|
|
25
|
+
private readonly startTransitionDurationMs;
|
|
26
|
+
private readonly endTransitionDurationMs;
|
|
26
27
|
private cachedIdleFirstFrame;
|
|
27
28
|
private idleCurrentFrameIndex;
|
|
28
29
|
private characterHandle;
|
|
29
30
|
private characterId;
|
|
30
31
|
private isPureRenderingMode;
|
|
32
|
+
private avatarActiveTimer;
|
|
33
|
+
private readonly AVATAR_ACTIVE_INTERVAL;
|
|
31
34
|
private alignFlamePair;
|
|
32
35
|
private generateAndAlignTransitionFrames;
|
|
33
36
|
private getCachedIdleFirstFrame;
|
|
@@ -59,7 +62,7 @@ export declare class AvatarView {
|
|
|
59
62
|
private stopRealtimeRendering;
|
|
60
63
|
dispose(): void;
|
|
61
64
|
renderFlame(flame: Flame, enableIdleRendering?: boolean): Promise<void>;
|
|
62
|
-
generateTransitionFromIdle(toFlame: Flame, frameCount: number): Promise<Flame[]>;
|
|
65
|
+
generateTransitionFromIdle(toFlame: Flame, frameCount: number, transitionType?: 'start' | 'end'): Promise<Flame[]>;
|
|
63
66
|
private rerenderCurrentFrameWithNewCamera;
|
|
64
67
|
private handleResize;
|
|
65
68
|
get transform(): {
|
|
@@ -72,4 +75,7 @@ export declare class AvatarView {
|
|
|
72
75
|
y: number;
|
|
73
76
|
scale: number;
|
|
74
77
|
});
|
|
78
|
+
private reportAvatarActive;
|
|
79
|
+
private startAvatarActiveHeartbeat;
|
|
80
|
+
private stopAvatarActiveHeartbeat;
|
|
75
81
|
}
|
|
@@ -7387,7 +7387,19 @@ let isInitialized = false;
|
|
|
7387
7387
|
const SDK_POSTHOG_INSTANCE_NAME = "spatialwalk-posthog";
|
|
7388
7388
|
let sdkPosthogInstance = null;
|
|
7389
7389
|
const eventQueue = [];
|
|
7390
|
+
const FILTERED_HOSTNAMES = ["localhost", "127.0.0.1", "0.0.0.0"];
|
|
7391
|
+
function shouldFilterHostname() {
|
|
7392
|
+
if (typeof window === "undefined") {
|
|
7393
|
+
return false;
|
|
7394
|
+
}
|
|
7395
|
+
const hostname = window.location.hostname;
|
|
7396
|
+
return FILTERED_HOSTNAMES.includes(hostname);
|
|
7397
|
+
}
|
|
7390
7398
|
function initializePostHog(environment, version) {
|
|
7399
|
+
if (shouldFilterHostname()) {
|
|
7400
|
+
logger.log(`[PostHog] Tracking disabled due to filtered hostname: ${window.location.hostname}`);
|
|
7401
|
+
return;
|
|
7402
|
+
}
|
|
7391
7403
|
const { host, apiKey, disableCompression } = getPostHogConfig();
|
|
7392
7404
|
if (isInitialized) {
|
|
7393
7405
|
logger.log("[PostHog] Already initialized, skipping");
|
|
@@ -7478,6 +7490,9 @@ function flushEventQueue() {
|
|
|
7478
7490
|
}, 0);
|
|
7479
7491
|
}
|
|
7480
7492
|
function trackEvent(event, level = "info", contents = {}) {
|
|
7493
|
+
if (shouldFilterHostname()) {
|
|
7494
|
+
return;
|
|
7495
|
+
}
|
|
7481
7496
|
const instance = getSdkPosthogInstance();
|
|
7482
7497
|
if (!instance) {
|
|
7483
7498
|
eventQueue.push({ event, level, contents });
|
|
@@ -7609,7 +7624,7 @@ const _AnimationPlayer = class _AnimationPlayer {
|
|
|
7609
7624
|
if (this.streamingPlayer) {
|
|
7610
7625
|
return;
|
|
7611
7626
|
}
|
|
7612
|
-
const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-
|
|
7627
|
+
const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-BWsAt_s7.js");
|
|
7613
7628
|
const { AvatarSDK: AvatarSDK2 } = await Promise.resolve().then(() => AvatarSDK$1);
|
|
7614
7629
|
const audioFormat = AvatarSDK2.getAudioFormat();
|
|
7615
7630
|
this.streamingPlayer = new StreamingAudioPlayer({
|
|
@@ -7828,95 +7843,6 @@ function isFirstUse() {
|
|
|
7828
7843
|
return false;
|
|
7829
7844
|
}
|
|
7830
7845
|
}
|
|
7831
|
-
class HeartbeatManager {
|
|
7832
|
-
constructor() {
|
|
7833
|
-
__publicField(this, "heartbeatTimer", null);
|
|
7834
|
-
__publicField(this, "visibilityChangeHandler", null);
|
|
7835
|
-
__publicField(this, "isInitialized", false);
|
|
7836
|
-
__publicField(this, "currentEnvironment", null);
|
|
7837
|
-
__publicField(this, "currentAppId", null);
|
|
7838
|
-
__publicField(this, "failureCount", 0);
|
|
7839
|
-
}
|
|
7840
|
-
start(environment) {
|
|
7841
|
-
if (this.isInitialized) {
|
|
7842
|
-
return;
|
|
7843
|
-
}
|
|
7844
|
-
this.isInitialized = true;
|
|
7845
|
-
this.currentEnvironment = environment;
|
|
7846
|
-
this.currentAppId = idManager.getAppId();
|
|
7847
|
-
this.startHeartbeatTimer();
|
|
7848
|
-
this.setupVisibilityListener();
|
|
7849
|
-
logger.log("[HeartbeatManager] Started");
|
|
7850
|
-
}
|
|
7851
|
-
stop() {
|
|
7852
|
-
if (!this.isInitialized) {
|
|
7853
|
-
return;
|
|
7854
|
-
}
|
|
7855
|
-
this.stopHeartbeatTimer();
|
|
7856
|
-
this.removeVisibilityListener();
|
|
7857
|
-
this.isInitialized = false;
|
|
7858
|
-
this.currentEnvironment = null;
|
|
7859
|
-
this.currentAppId = null;
|
|
7860
|
-
logger.log("[HeartbeatManager] Stopped");
|
|
7861
|
-
}
|
|
7862
|
-
startHeartbeatTimer() {
|
|
7863
|
-
if (this.heartbeatTimer !== null) {
|
|
7864
|
-
return;
|
|
7865
|
-
}
|
|
7866
|
-
const HEARTBEAT_INTERVAL = 12e4;
|
|
7867
|
-
this.heartbeatTimer = window.setInterval(() => {
|
|
7868
|
-
if (this.isInitialized && typeof document !== "undefined" && document.visibilityState === "visible") {
|
|
7869
|
-
this.reportHeartbeat();
|
|
7870
|
-
}
|
|
7871
|
-
}, HEARTBEAT_INTERVAL);
|
|
7872
|
-
}
|
|
7873
|
-
stopHeartbeatTimer() {
|
|
7874
|
-
if (this.heartbeatTimer !== null) {
|
|
7875
|
-
clearInterval(this.heartbeatTimer);
|
|
7876
|
-
this.heartbeatTimer = null;
|
|
7877
|
-
}
|
|
7878
|
-
}
|
|
7879
|
-
reportHeartbeat(additionalData) {
|
|
7880
|
-
try {
|
|
7881
|
-
trackEvent("sdk_heartbeat", "info", {
|
|
7882
|
-
appId: this.currentAppId || idManager.getAppId(),
|
|
7883
|
-
environment: this.currentEnvironment,
|
|
7884
|
-
...additionalData
|
|
7885
|
-
});
|
|
7886
|
-
this.failureCount = 0;
|
|
7887
|
-
} catch (error) {
|
|
7888
|
-
this.failureCount++;
|
|
7889
|
-
logger.warn(`[HeartbeatManager] Heartbeat failed (count: ${this.failureCount})`);
|
|
7890
|
-
if (this.failureCount >= 3) {
|
|
7891
|
-
trackEvent("heartbeat_failed", "warning", {
|
|
7892
|
-
con_id: idManager.getConnectionId() || "",
|
|
7893
|
-
description: `Heartbeat failed ${this.failureCount} times`
|
|
7894
|
-
});
|
|
7895
|
-
this.failureCount = 0;
|
|
7896
|
-
}
|
|
7897
|
-
}
|
|
7898
|
-
}
|
|
7899
|
-
setupVisibilityListener() {
|
|
7900
|
-
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
7901
|
-
return;
|
|
7902
|
-
}
|
|
7903
|
-
this.removeVisibilityListener();
|
|
7904
|
-
this.visibilityChangeHandler = () => {
|
|
7905
|
-
if (document.visibilityState === "visible" && this.isInitialized) {
|
|
7906
|
-
this.reportHeartbeat({ resumed: true });
|
|
7907
|
-
}
|
|
7908
|
-
};
|
|
7909
|
-
document.addEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
7910
|
-
}
|
|
7911
|
-
removeVisibilityListener() {
|
|
7912
|
-
if (typeof window === "undefined" || typeof document === "undefined" || !this.visibilityChangeHandler) {
|
|
7913
|
-
return;
|
|
7914
|
-
}
|
|
7915
|
-
document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
7916
|
-
this.visibilityChangeHandler = null;
|
|
7917
|
-
}
|
|
7918
|
-
}
|
|
7919
|
-
const heartbeatManager = new HeartbeatManager();
|
|
7920
7846
|
class AvatarCoreMemoryManager {
|
|
7921
7847
|
constructor(wasmModule) {
|
|
7922
7848
|
__publicField(this, "module");
|
|
@@ -8869,14 +8795,16 @@ class AvatarSDK {
|
|
|
8869
8795
|
await this.initializeWASMModule();
|
|
8870
8796
|
await this.initializeTemplateResources();
|
|
8871
8797
|
this._isInitialized = true;
|
|
8872
|
-
logEvent("sdk_initialized", "info", {
|
|
8798
|
+
logEvent("sdk_initialized", "info", {
|
|
8799
|
+
env: configuration.environment,
|
|
8800
|
+
dsm: configuration.drivingServiceMode || DrivingServiceMode.sdk
|
|
8801
|
+
});
|
|
8873
8802
|
if (isFirstUse()) {
|
|
8874
8803
|
logEvent("sdk_first_use", "info", {
|
|
8875
8804
|
appId: idManager.getAppId(),
|
|
8876
8805
|
environment: (_a = this._configuration) == null ? void 0 : _a.environment
|
|
8877
8806
|
});
|
|
8878
8807
|
}
|
|
8879
|
-
heartbeatManager.start(this._configuration.environment);
|
|
8880
8808
|
logger.log(`[AvatarSDK] Successfully initialized`);
|
|
8881
8809
|
} catch (error) {
|
|
8882
8810
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -8983,7 +8911,6 @@ class AvatarSDK {
|
|
|
8983
8911
|
return;
|
|
8984
8912
|
}
|
|
8985
8913
|
try {
|
|
8986
|
-
heartbeatManager.stop();
|
|
8987
8914
|
if (this._avatarCore) {
|
|
8988
8915
|
this._avatarCore.release();
|
|
8989
8916
|
this._avatarCore = null;
|
|
@@ -9034,7 +8961,7 @@ class AvatarSDK {
|
|
|
9034
8961
|
}
|
|
9035
8962
|
__publicField(AvatarSDK, "_isInitialized", false);
|
|
9036
8963
|
__publicField(AvatarSDK, "_configuration", null);
|
|
9037
|
-
__publicField(AvatarSDK, "_version", "1.0.0-beta.
|
|
8964
|
+
__publicField(AvatarSDK, "_version", "1.0.0-beta.62");
|
|
9038
8965
|
__publicField(AvatarSDK, "_avatarCore", null);
|
|
9039
8966
|
__publicField(AvatarSDK, "_dynamicSdkConfig", null);
|
|
9040
8967
|
const AvatarSDK$1 = Object.freeze(Object.defineProperty({
|
|
@@ -10362,6 +10289,10 @@ class NetworkLayer {
|
|
|
10362
10289
|
__publicField(this, "audioMetrics", this.createAudioMetrics());
|
|
10363
10290
|
__publicField(this, "isFallbackMode", false);
|
|
10364
10291
|
__publicField(this, "isConnecting", false);
|
|
10292
|
+
__publicField(this, "heartbeatTimer", null);
|
|
10293
|
+
__publicField(this, "visibilityChangeHandler", null);
|
|
10294
|
+
__publicField(this, "heartbeatFailureCount", 0);
|
|
10295
|
+
__publicField(this, "HEARTBEAT_INTERVAL", 12e4);
|
|
10365
10296
|
this.dataController = dataController;
|
|
10366
10297
|
const config = AvatarSDK.getEnvironmentConfig();
|
|
10367
10298
|
this.wsClient = new AnimationWebSocketClient({
|
|
@@ -10372,6 +10303,7 @@ class NetworkLayer {
|
|
|
10372
10303
|
clientId: idManager.getClientId()
|
|
10373
10304
|
});
|
|
10374
10305
|
this.setupWebSocketListeners();
|
|
10306
|
+
this.startHeartbeatCheck();
|
|
10375
10307
|
}
|
|
10376
10308
|
getAudioBytesPerSecond() {
|
|
10377
10309
|
const audioFormat = AvatarSDK.getAudioFormat();
|
|
@@ -10435,28 +10367,16 @@ class NetworkLayer {
|
|
|
10435
10367
|
});
|
|
10436
10368
|
}
|
|
10437
10369
|
const metrics = this.audioMetrics;
|
|
10438
|
-
let shouldReportMetrics = false;
|
|
10439
10370
|
if (metrics.startTimestamp === 0) {
|
|
10440
10371
|
metrics.startTimestamp = Date.now();
|
|
10441
|
-
metrics.cachedStartTimestamp = String(metrics.startTimestamp);
|
|
10442
|
-
shouldReportMetrics = true;
|
|
10443
10372
|
}
|
|
10444
10373
|
metrics.accumulatedBytes += audioData.byteLength;
|
|
10445
10374
|
const currentDuration = metrics.accumulatedBytes / this.getAudioBytesPerSecond();
|
|
10375
|
+
if (currentDuration >= 1 && metrics.tap1Timestamp === 0) {
|
|
10376
|
+
metrics.tap1Timestamp = Date.now();
|
|
10377
|
+
}
|
|
10446
10378
|
if (currentDuration >= 2 && metrics.tap2Timestamp === 0) {
|
|
10447
10379
|
metrics.tap2Timestamp = Date.now();
|
|
10448
|
-
metrics.cachedTap2Timestamp = String(metrics.tap2Timestamp);
|
|
10449
|
-
shouldReportMetrics = true;
|
|
10450
|
-
}
|
|
10451
|
-
if (currentDuration >= 4 && metrics.tap4Timestamp === 0) {
|
|
10452
|
-
metrics.tap4Timestamp = Date.now();
|
|
10453
|
-
metrics.cachedTap4Timestamp = String(metrics.tap4Timestamp);
|
|
10454
|
-
shouldReportMetrics = true;
|
|
10455
|
-
}
|
|
10456
|
-
if (isLast && metrics.endTimestamp === 0) {
|
|
10457
|
-
metrics.endTimestamp = Date.now();
|
|
10458
|
-
metrics.cachedEndTimestamp = String(metrics.endTimestamp);
|
|
10459
|
-
shouldReportMetrics = true;
|
|
10460
10380
|
}
|
|
10461
10381
|
if (audioData.byteLength === 0 && !isLast) {
|
|
10462
10382
|
logger.warn("[NetworkLayer] Warning: sending empty audio data (size=0, end=false)");
|
|
@@ -10479,15 +10399,13 @@ class NetworkLayer {
|
|
|
10479
10399
|
});
|
|
10480
10400
|
return;
|
|
10481
10401
|
}
|
|
10482
|
-
if (shouldReportMetrics && this.currentConversationId) {
|
|
10483
|
-
this.reportDrivingLatency(this.currentConversationId);
|
|
10484
|
-
}
|
|
10485
10402
|
}
|
|
10486
10403
|
disconnect() {
|
|
10487
10404
|
this.isFallbackMode = false;
|
|
10488
10405
|
this.isConnecting = false;
|
|
10489
10406
|
this.wsClient.removeAllListeners();
|
|
10490
10407
|
this.wsClient.disconnect();
|
|
10408
|
+
this.stopHeartbeatCheck();
|
|
10491
10409
|
idManager.clearConnectionId();
|
|
10492
10410
|
this.currentConversationId = null;
|
|
10493
10411
|
}
|
|
@@ -10582,9 +10500,9 @@ class NetworkLayer {
|
|
|
10582
10500
|
if (!this.audioMetrics.didRecvFirstFlame) {
|
|
10583
10501
|
this.audioMetrics.didRecvFirstFlame = true;
|
|
10584
10502
|
this.audioMetrics.recvFirstFlameTimestamp = Date.now();
|
|
10585
|
-
this.
|
|
10586
|
-
|
|
10587
|
-
this.
|
|
10503
|
+
if (this.currentConversationId && !this.audioMetrics.didReportLatency) {
|
|
10504
|
+
this.reportDrivingServiceLatency(this.currentConversationId);
|
|
10505
|
+
this.audioMetrics.didReportLatency = true;
|
|
10588
10506
|
}
|
|
10589
10507
|
}
|
|
10590
10508
|
} else {
|
|
@@ -10644,36 +10562,94 @@ class NetworkLayer {
|
|
|
10644
10562
|
return {
|
|
10645
10563
|
accumulatedBytes: 0,
|
|
10646
10564
|
startTimestamp: 0,
|
|
10565
|
+
tap1Timestamp: 0,
|
|
10647
10566
|
tap2Timestamp: 0,
|
|
10648
|
-
tap4Timestamp: 0,
|
|
10649
|
-
endTimestamp: 0,
|
|
10650
10567
|
recvFirstFlameTimestamp: 0,
|
|
10651
10568
|
didRecvFirstFlame: false,
|
|
10652
|
-
|
|
10653
|
-
cachedTap2Timestamp: "",
|
|
10654
|
-
cachedTap4Timestamp: "",
|
|
10655
|
-
cachedEndTimestamp: "",
|
|
10656
|
-
cachedFirstFrameTimestamp: ""
|
|
10569
|
+
didReportLatency: false
|
|
10657
10570
|
};
|
|
10658
10571
|
}
|
|
10659
10572
|
resetAudioMetrics() {
|
|
10660
10573
|
this.audioMetrics = this.createAudioMetrics();
|
|
10661
10574
|
}
|
|
10662
|
-
|
|
10575
|
+
reportDrivingServiceLatency(conversationId) {
|
|
10663
10576
|
if (!conversationId) {
|
|
10664
10577
|
return;
|
|
10665
10578
|
}
|
|
10666
10579
|
const metrics = this.audioMetrics;
|
|
10667
|
-
logEvent("
|
|
10668
|
-
driving_service_mode: "sdk",
|
|
10580
|
+
logEvent("driving_service_latency", "info", {
|
|
10669
10581
|
req_id: conversationId,
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10582
|
+
dsm: "sdk",
|
|
10583
|
+
tap_0: metrics.startTimestamp || 0,
|
|
10584
|
+
tap_1: metrics.tap1Timestamp || 0,
|
|
10585
|
+
tap_2: metrics.tap2Timestamp || 0,
|
|
10586
|
+
tap_f: metrics.recvFirstFlameTimestamp || 0
|
|
10675
10587
|
});
|
|
10676
10588
|
}
|
|
10589
|
+
startHeartbeatCheck() {
|
|
10590
|
+
this.stopHeartbeatCheck();
|
|
10591
|
+
this.heartbeatTimer = window.setInterval(() => {
|
|
10592
|
+
if (typeof document !== "undefined" && document.visibilityState === "visible") {
|
|
10593
|
+
this.performHeartbeatCheck();
|
|
10594
|
+
}
|
|
10595
|
+
}, this.HEARTBEAT_INTERVAL);
|
|
10596
|
+
this.setupVisibilityListener();
|
|
10597
|
+
}
|
|
10598
|
+
stopHeartbeatCheck() {
|
|
10599
|
+
if (this.heartbeatTimer !== null) {
|
|
10600
|
+
clearInterval(this.heartbeatTimer);
|
|
10601
|
+
this.heartbeatTimer = null;
|
|
10602
|
+
}
|
|
10603
|
+
this.removeVisibilityListener();
|
|
10604
|
+
this.heartbeatFailureCount = 0;
|
|
10605
|
+
}
|
|
10606
|
+
performHeartbeatCheck() {
|
|
10607
|
+
try {
|
|
10608
|
+
const isConnected = this.wsClient.isConnected();
|
|
10609
|
+
if (isConnected) {
|
|
10610
|
+
this.heartbeatFailureCount = 0;
|
|
10611
|
+
} else {
|
|
10612
|
+
this.heartbeatFailureCount++;
|
|
10613
|
+
logger.warn(`[NetworkLayer] Driving service heartbeat failed (count: ${this.heartbeatFailureCount})`);
|
|
10614
|
+
if (this.heartbeatFailureCount >= 3) {
|
|
10615
|
+
logEvent("heartbeat_failed", "warning", {
|
|
10616
|
+
con_id: idManager.getConnectionId() || "",
|
|
10617
|
+
description: `Driving service heartbeat failed ${this.heartbeatFailureCount} times`
|
|
10618
|
+
});
|
|
10619
|
+
this.heartbeatFailureCount = 0;
|
|
10620
|
+
}
|
|
10621
|
+
}
|
|
10622
|
+
} catch (error) {
|
|
10623
|
+
this.heartbeatFailureCount++;
|
|
10624
|
+
logger.warn(`[NetworkLayer] Heartbeat check error (count: ${this.heartbeatFailureCount}):`, error instanceof Error ? error.message : String(error));
|
|
10625
|
+
if (this.heartbeatFailureCount >= 3) {
|
|
10626
|
+
logEvent("heartbeat_failed", "warning", {
|
|
10627
|
+
con_id: idManager.getConnectionId() || "",
|
|
10628
|
+
description: `Heartbeat check error: ${error instanceof Error ? error.message : String(error)}`
|
|
10629
|
+
});
|
|
10630
|
+
this.heartbeatFailureCount = 0;
|
|
10631
|
+
}
|
|
10632
|
+
}
|
|
10633
|
+
}
|
|
10634
|
+
setupVisibilityListener() {
|
|
10635
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
10636
|
+
return;
|
|
10637
|
+
}
|
|
10638
|
+
this.removeVisibilityListener();
|
|
10639
|
+
this.visibilityChangeHandler = () => {
|
|
10640
|
+
if (document.visibilityState === "visible") {
|
|
10641
|
+
this.performHeartbeatCheck();
|
|
10642
|
+
}
|
|
10643
|
+
};
|
|
10644
|
+
document.addEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
10645
|
+
}
|
|
10646
|
+
removeVisibilityListener() {
|
|
10647
|
+
if (typeof window === "undefined" || typeof document === "undefined" || !this.visibilityChangeHandler) {
|
|
10648
|
+
return;
|
|
10649
|
+
}
|
|
10650
|
+
document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
10651
|
+
this.visibilityChangeHandler = null;
|
|
10652
|
+
}
|
|
10677
10653
|
}
|
|
10678
10654
|
class AvatarController {
|
|
10679
10655
|
constructor(avatar, options) {
|
|
@@ -10717,16 +10693,11 @@ class AvatarController {
|
|
|
10717
10693
|
__publicField(this, "hostModeMetrics", {
|
|
10718
10694
|
accumulatedBytes: 0,
|
|
10719
10695
|
startTimestamp: 0,
|
|
10696
|
+
tap1Timestamp: 0,
|
|
10720
10697
|
tap2Timestamp: 0,
|
|
10721
|
-
tap4Timestamp: 0,
|
|
10722
|
-
endTimestamp: 0,
|
|
10723
10698
|
recvFirstFlameTimestamp: 0,
|
|
10724
10699
|
didRecvFirstFlame: false,
|
|
10725
|
-
|
|
10726
|
-
cachedTap2Timestamp: "",
|
|
10727
|
-
cachedTap4Timestamp: "",
|
|
10728
|
-
cachedEndTimestamp: "",
|
|
10729
|
-
cachedFirstFrameTimestamp: ""
|
|
10700
|
+
didReportLatency: false
|
|
10730
10701
|
});
|
|
10731
10702
|
__publicField(this, "audioBytesPerSecond", 16e3 * 2);
|
|
10732
10703
|
this.avatar = avatar;
|
|
@@ -10896,31 +10867,16 @@ class AvatarController {
|
|
|
10896
10867
|
}
|
|
10897
10868
|
if (this.playbackMode === DrivingServiceMode.host) {
|
|
10898
10869
|
const metrics = this.hostModeMetrics;
|
|
10899
|
-
let shouldReportMetrics = false;
|
|
10900
10870
|
if (metrics.startTimestamp === 0) {
|
|
10901
10871
|
metrics.startTimestamp = Date.now();
|
|
10902
|
-
metrics.cachedStartTimestamp = String(metrics.startTimestamp);
|
|
10903
|
-
shouldReportMetrics = true;
|
|
10904
10872
|
}
|
|
10905
10873
|
metrics.accumulatedBytes += data.length;
|
|
10906
10874
|
const currentDuration = metrics.accumulatedBytes / this.audioBytesPerSecond;
|
|
10875
|
+
if (currentDuration >= 1 && metrics.tap1Timestamp === 0) {
|
|
10876
|
+
metrics.tap1Timestamp = Date.now();
|
|
10877
|
+
}
|
|
10907
10878
|
if (currentDuration >= 2 && metrics.tap2Timestamp === 0) {
|
|
10908
10879
|
metrics.tap2Timestamp = Date.now();
|
|
10909
|
-
metrics.cachedTap2Timestamp = String(metrics.tap2Timestamp);
|
|
10910
|
-
shouldReportMetrics = true;
|
|
10911
|
-
}
|
|
10912
|
-
if (currentDuration >= 4 && metrics.tap4Timestamp === 0) {
|
|
10913
|
-
metrics.tap4Timestamp = Date.now();
|
|
10914
|
-
metrics.cachedTap4Timestamp = String(metrics.tap4Timestamp);
|
|
10915
|
-
shouldReportMetrics = true;
|
|
10916
|
-
}
|
|
10917
|
-
if (isLast && metrics.endTimestamp === 0) {
|
|
10918
|
-
metrics.endTimestamp = Date.now();
|
|
10919
|
-
metrics.cachedEndTimestamp = String(metrics.endTimestamp);
|
|
10920
|
-
shouldReportMetrics = true;
|
|
10921
|
-
}
|
|
10922
|
-
if (shouldReportMetrics && this.currentConversationId) {
|
|
10923
|
-
this.reportDrivingLatency(this.currentConversationId);
|
|
10924
10880
|
}
|
|
10925
10881
|
}
|
|
10926
10882
|
if (this.isPlaying && ((_a = this.animationPlayer) == null ? void 0 : _a.isStreamingReady())) {
|
|
@@ -11018,9 +10974,9 @@ class AvatarController {
|
|
|
11018
10974
|
if (this.playbackMode === DrivingServiceMode.host && !this.hostModeMetrics.didRecvFirstFlame) {
|
|
11019
10975
|
this.hostModeMetrics.didRecvFirstFlame = true;
|
|
11020
10976
|
this.hostModeMetrics.recvFirstFlameTimestamp = Date.now();
|
|
11021
|
-
|
|
11022
|
-
|
|
11023
|
-
this.
|
|
10977
|
+
if (conversationId && !this.hostModeMetrics.didReportLatency) {
|
|
10978
|
+
this.reportDrivingServiceLatency(conversationId);
|
|
10979
|
+
this.hostModeMetrics.didReportLatency = true;
|
|
11024
10980
|
}
|
|
11025
10981
|
}
|
|
11026
10982
|
} else {
|
|
@@ -11117,16 +11073,11 @@ class AvatarController {
|
|
|
11117
11073
|
this.hostModeMetrics = {
|
|
11118
11074
|
accumulatedBytes: 0,
|
|
11119
11075
|
startTimestamp: 0,
|
|
11076
|
+
tap1Timestamp: 0,
|
|
11120
11077
|
tap2Timestamp: 0,
|
|
11121
|
-
tap4Timestamp: 0,
|
|
11122
|
-
endTimestamp: 0,
|
|
11123
11078
|
recvFirstFlameTimestamp: 0,
|
|
11124
11079
|
didRecvFirstFlame: false,
|
|
11125
|
-
|
|
11126
|
-
cachedTap2Timestamp: "",
|
|
11127
|
-
cachedTap4Timestamp: "",
|
|
11128
|
-
cachedEndTimestamp: "",
|
|
11129
|
-
cachedFirstFrameTimestamp: ""
|
|
11080
|
+
didReportLatency: false
|
|
11130
11081
|
};
|
|
11131
11082
|
}
|
|
11132
11083
|
}
|
|
@@ -11142,16 +11093,11 @@ class AvatarController {
|
|
|
11142
11093
|
this.hostModeMetrics = {
|
|
11143
11094
|
accumulatedBytes: 0,
|
|
11144
11095
|
startTimestamp: 0,
|
|
11096
|
+
tap1Timestamp: 0,
|
|
11145
11097
|
tap2Timestamp: 0,
|
|
11146
|
-
tap4Timestamp: 0,
|
|
11147
|
-
endTimestamp: 0,
|
|
11148
11098
|
recvFirstFlameTimestamp: 0,
|
|
11149
11099
|
didRecvFirstFlame: false,
|
|
11150
|
-
|
|
11151
|
-
cachedTap2Timestamp: "",
|
|
11152
|
-
cachedTap4Timestamp: "",
|
|
11153
|
-
cachedEndTimestamp: "",
|
|
11154
|
-
cachedFirstFrameTimestamp: ""
|
|
11100
|
+
didReportLatency: false
|
|
11155
11101
|
};
|
|
11156
11102
|
}
|
|
11157
11103
|
}
|
|
@@ -11742,19 +11688,18 @@ class AvatarController {
|
|
|
11742
11688
|
}
|
|
11743
11689
|
return result2;
|
|
11744
11690
|
}
|
|
11745
|
-
|
|
11691
|
+
reportDrivingServiceLatency(conversationId) {
|
|
11746
11692
|
if (!conversationId || this.playbackMode !== DrivingServiceMode.host) {
|
|
11747
11693
|
return;
|
|
11748
11694
|
}
|
|
11749
11695
|
const metrics = this.hostModeMetrics;
|
|
11750
|
-
logEvent("
|
|
11751
|
-
driving_service_mode: "host",
|
|
11696
|
+
logEvent("driving_service_latency", "info", {
|
|
11752
11697
|
req_id: conversationId,
|
|
11753
|
-
|
|
11754
|
-
|
|
11755
|
-
|
|
11756
|
-
|
|
11757
|
-
|
|
11698
|
+
dsm: "host",
|
|
11699
|
+
tap_0: metrics.startTimestamp || 0,
|
|
11700
|
+
tap_1: metrics.tap1Timestamp || 0,
|
|
11701
|
+
tap_2: metrics.tap2Timestamp || 0,
|
|
11702
|
+
tap_f: metrics.recvFirstFlameTimestamp || 0
|
|
11758
11703
|
});
|
|
11759
11704
|
}
|
|
11760
11705
|
}
|
|
@@ -13906,31 +13851,82 @@ function lerpArrays(from, to2, progress) {
|
|
|
13906
13851
|
}
|
|
13907
13852
|
return result2;
|
|
13908
13853
|
}
|
|
13909
|
-
|
|
13910
|
-
|
|
13854
|
+
const clamp01 = (x2) => Math.max(0, Math.min(1, x2));
|
|
13855
|
+
function createBezierEasing(x1, y1, x2, y2) {
|
|
13856
|
+
const cx = 3 * x1;
|
|
13857
|
+
const bx = 3 * (x2 - x1) - cx;
|
|
13858
|
+
const ax = 1 - cx - bx;
|
|
13859
|
+
const cy = 3 * y1;
|
|
13860
|
+
const by = 3 * (y2 - y1) - cy;
|
|
13861
|
+
const ay = 1 - cy - by;
|
|
13862
|
+
const sampleCurveX = (t2) => ((ax * t2 + bx) * t2 + cx) * t2;
|
|
13863
|
+
const sampleCurveY = (t2) => ((ay * t2 + by) * t2 + cy) * t2;
|
|
13864
|
+
const sampleCurveDerivativeX = (t2) => (3 * ax * t2 + 2 * bx) * t2 + cx;
|
|
13865
|
+
const solveCurveX = (x3) => {
|
|
13866
|
+
let t2 = x3;
|
|
13867
|
+
for (let i2 = 0; i2 < 8; i2++) {
|
|
13868
|
+
const error = sampleCurveX(t2) - x3;
|
|
13869
|
+
if (Math.abs(error) < 1e-6) break;
|
|
13870
|
+
const d2 = sampleCurveDerivativeX(t2);
|
|
13871
|
+
if (Math.abs(d2) < 1e-6) break;
|
|
13872
|
+
t2 -= error / d2;
|
|
13873
|
+
}
|
|
13874
|
+
return t2;
|
|
13875
|
+
};
|
|
13876
|
+
return (x3) => {
|
|
13877
|
+
if (x3 <= 0) return 0;
|
|
13878
|
+
if (x3 >= 1) return 1;
|
|
13879
|
+
return sampleCurveY(solveCurveX(x3));
|
|
13880
|
+
};
|
|
13881
|
+
}
|
|
13882
|
+
const BEZIER_CURVES = {
|
|
13883
|
+
jaw: createBezierEasing(0.2, 0.8, 0.3, 1),
|
|
13884
|
+
expression: createBezierEasing(0.4, 0, 0.2, 1),
|
|
13885
|
+
eye: createBezierEasing(0.3, 0, 0.1, 1),
|
|
13886
|
+
neck: createBezierEasing(0.1, 0.2, 0.2, 1),
|
|
13887
|
+
global: createBezierEasing(0.42, 0, 0.58, 1)
|
|
13888
|
+
};
|
|
13889
|
+
const TIME_SCALE = {
|
|
13890
|
+
jaw: 2.5,
|
|
13891
|
+
expression: 1.6,
|
|
13892
|
+
eye: 1.3,
|
|
13893
|
+
neck: 1,
|
|
13894
|
+
global: 1
|
|
13895
|
+
};
|
|
13896
|
+
function bezierLerp(from, to2, progress) {
|
|
13897
|
+
const getT = (key) => {
|
|
13898
|
+
const scaledProgress = clamp01(progress * TIME_SCALE[key]);
|
|
13899
|
+
return BEZIER_CURVES[key](scaledProgress);
|
|
13900
|
+
};
|
|
13911
13901
|
return {
|
|
13912
|
-
translation: lerpArrays(from.translation || [0, 0, 0], to2.translation || [0, 0, 0],
|
|
13913
|
-
rotation: lerpArrays(from.rotation || [0, 0, 0], to2.rotation || [0, 0, 0],
|
|
13914
|
-
neckPose: lerpArrays(from.neckPose || [0, 0, 0], to2.neckPose || [0, 0, 0],
|
|
13915
|
-
jawPose: lerpArrays(from.jawPose || [0, 0, 0], to2.jawPose || [0, 0, 0],
|
|
13916
|
-
eyePose: lerpArrays(from.eyePose || [0, 0, 0, 0, 0, 0], to2.eyePose || [0, 0, 0, 0, 0, 0],
|
|
13902
|
+
translation: lerpArrays(from.translation || [0, 0, 0], to2.translation || [0, 0, 0], getT("global")),
|
|
13903
|
+
rotation: lerpArrays(from.rotation || [0, 0, 0], to2.rotation || [0, 0, 0], getT("global")),
|
|
13904
|
+
neckPose: lerpArrays(from.neckPose || [0, 0, 0], to2.neckPose || [0, 0, 0], getT("neck")),
|
|
13905
|
+
jawPose: lerpArrays(from.jawPose || [0, 0, 0], to2.jawPose || [0, 0, 0], getT("jaw")),
|
|
13906
|
+
eyePose: lerpArrays(from.eyePose || [0, 0, 0, 0, 0, 0], to2.eyePose || [0, 0, 0, 0, 0, 0], getT("eye")),
|
|
13917
13907
|
eyeLid: (() => {
|
|
13918
13908
|
const fromEyelid = from.eyeLid;
|
|
13919
13909
|
const toEyelid = to2.eyeLid;
|
|
13920
|
-
if (fromEyelid
|
|
13921
|
-
return lerpArrays(fromEyelid, toEyelid,
|
|
13910
|
+
if ((fromEyelid == null ? void 0 : fromEyelid.length) && (toEyelid == null ? void 0 : toEyelid.length))
|
|
13911
|
+
return lerpArrays(fromEyelid, toEyelid, getT("eye"));
|
|
13922
13912
|
return fromEyelid || toEyelid || [];
|
|
13923
13913
|
})(),
|
|
13924
|
-
expression: lerpArrays(from.expression || [], to2.expression || [],
|
|
13914
|
+
expression: lerpArrays(from.expression || [], to2.expression || [], getT("expression"))
|
|
13925
13915
|
};
|
|
13926
13916
|
}
|
|
13927
13917
|
function generateTransitionFrames(from, to2, durationMs, fps = 25) {
|
|
13928
13918
|
const steps = Math.max(1, Math.floor(durationMs / 1e3 * fps));
|
|
13929
13919
|
const frames = Array.from({ length: steps });
|
|
13920
|
+
if (steps === 1) {
|
|
13921
|
+
frames[0] = to2;
|
|
13922
|
+
return frames;
|
|
13923
|
+
}
|
|
13930
13924
|
for (let i2 = 0; i2 < steps; i2++) {
|
|
13931
13925
|
const progress = i2 / (steps - 1);
|
|
13932
|
-
frames[i2] =
|
|
13926
|
+
frames[i2] = bezierLerp(from, to2, progress);
|
|
13933
13927
|
}
|
|
13928
|
+
frames[0] = from;
|
|
13929
|
+
frames[frames.length - 1] = to2;
|
|
13934
13930
|
return frames;
|
|
13935
13931
|
}
|
|
13936
13932
|
class AvatarView {
|
|
@@ -13955,12 +13951,15 @@ class AvatarView {
|
|
|
13955
13951
|
__publicField(this, "currentFPS", 0);
|
|
13956
13952
|
__publicField(this, "transitionKeyframes", []);
|
|
13957
13953
|
__publicField(this, "transitionStartTime", 0);
|
|
13958
|
-
__publicField(this, "
|
|
13954
|
+
__publicField(this, "startTransitionDurationMs", 200);
|
|
13955
|
+
__publicField(this, "endTransitionDurationMs", 1600);
|
|
13959
13956
|
__publicField(this, "cachedIdleFirstFrame", null);
|
|
13960
13957
|
__publicField(this, "idleCurrentFrameIndex", 0);
|
|
13961
13958
|
__publicField(this, "characterHandle", null);
|
|
13962
13959
|
__publicField(this, "characterId");
|
|
13963
13960
|
__publicField(this, "isPureRenderingMode", false);
|
|
13961
|
+
__publicField(this, "avatarActiveTimer", null);
|
|
13962
|
+
__publicField(this, "AVATAR_ACTIVE_INTERVAL", 6e5);
|
|
13964
13963
|
this.avatar = avatar;
|
|
13965
13964
|
this.characterId = `${avatar.id}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
13966
13965
|
if (!AvatarSDK.configuration) {
|
|
@@ -14010,12 +14009,12 @@ class AvatarView {
|
|
|
14010
14009
|
toFixed.expression = ensureLen(toFixed.expression, exprLen);
|
|
14011
14010
|
return { from: fromFixed, to: toFixed };
|
|
14012
14011
|
}
|
|
14013
|
-
generateAndAlignTransitionFrames(from, to2) {
|
|
14012
|
+
generateAndAlignTransitionFrames(from, to2, durationMs) {
|
|
14014
14013
|
const aligned = this.alignFlamePair(from, to2);
|
|
14015
14014
|
let keyframes = generateTransitionFrames(
|
|
14016
14015
|
aligned.from,
|
|
14017
14016
|
aligned.to,
|
|
14018
|
-
|
|
14017
|
+
durationMs,
|
|
14019
14018
|
APP_CONFIG.animation.fps
|
|
14020
14019
|
);
|
|
14021
14020
|
if (keyframes.length < 2) {
|
|
@@ -14260,6 +14259,8 @@ class AvatarView {
|
|
|
14260
14259
|
if (APP_CONFIG.debug)
|
|
14261
14260
|
logger.log("[AvatarView] First frame rendered successfully");
|
|
14262
14261
|
(_a = this.onFirstRendering) == null ? void 0 : _a.call(this);
|
|
14262
|
+
this.reportAvatarActive();
|
|
14263
|
+
this.startAvatarActiveHeartbeat();
|
|
14263
14264
|
} else {
|
|
14264
14265
|
throw new Error("Failed to compute first frame splat data");
|
|
14265
14266
|
}
|
|
@@ -14372,7 +14373,8 @@ class AvatarView {
|
|
|
14372
14373
|
return;
|
|
14373
14374
|
}
|
|
14374
14375
|
const elapsed = performance.now() - this.transitionStartTime;
|
|
14375
|
-
const
|
|
14376
|
+
const currentTransitionDurationMs = state === "transitioningToSpeaking" ? this.startTransitionDurationMs : this.endTransitionDurationMs;
|
|
14377
|
+
const progress = Math.min(1, Math.max(0, elapsed / currentTransitionDurationMs));
|
|
14376
14378
|
const steps = this.transitionKeyframes.length;
|
|
14377
14379
|
const idx = Math.min(steps - 1, Math.floor(progress * (steps - 1)));
|
|
14378
14380
|
const currentFrame = this.transitionKeyframes[idx];
|
|
@@ -14398,7 +14400,7 @@ class AvatarView {
|
|
|
14398
14400
|
return;
|
|
14399
14401
|
}
|
|
14400
14402
|
}
|
|
14401
|
-
if (state === "transitioningToSpeaking" && this.transitionStartTime > 0 && this.transitionKeyframes.length > 0 && elapsed >= this.
|
|
14403
|
+
if (state === "transitioningToSpeaking" && this.transitionStartTime > 0 && this.transitionKeyframes.length > 0 && elapsed >= this.startTransitionDurationMs + 100) {
|
|
14402
14404
|
this.setState("speaking");
|
|
14403
14405
|
this.transitionKeyframes = [];
|
|
14404
14406
|
this.avatarController.onTransitionComplete();
|
|
@@ -14528,7 +14530,7 @@ class AvatarView {
|
|
|
14528
14530
|
await this.getCachedIdleFirstFrame();
|
|
14529
14531
|
const firstSpeaking = keyframes[0];
|
|
14530
14532
|
const firstSpeakingWithPostProcessing = this.avatarController.applyPostProcessingToFlame(firstSpeaking);
|
|
14531
|
-
this.transitionKeyframes = this.generateAndAlignTransitionFrames(idleFrameProto, firstSpeakingWithPostProcessing);
|
|
14533
|
+
this.transitionKeyframes = this.generateAndAlignTransitionFrames(idleFrameProto, firstSpeakingWithPostProcessing, this.startTransitionDurationMs);
|
|
14532
14534
|
this.transitionStartTime = performance.now();
|
|
14533
14535
|
if (this.transitionKeyframes.length === 0) {
|
|
14534
14536
|
this.setState("speaking");
|
|
@@ -14581,7 +14583,7 @@ class AvatarView {
|
|
|
14581
14583
|
const lastSpeaking = this.avatarController.applyPostProcessingToFlame(lastSpeakingRaw);
|
|
14582
14584
|
const idleFirstProto = await this.getCachedIdleFirstFrame();
|
|
14583
14585
|
if (idleFirstProto) {
|
|
14584
|
-
this.transitionKeyframes = this.generateAndAlignTransitionFrames(lastSpeaking, idleFirstProto);
|
|
14586
|
+
this.transitionKeyframes = this.generateAndAlignTransitionFrames(lastSpeaking, idleFirstProto, this.endTransitionDurationMs);
|
|
14585
14587
|
this.transitionStartTime = performance.now();
|
|
14586
14588
|
if (this.transitionKeyframes.length > 0 && this.renderingState === "transitioningToIdle") {
|
|
14587
14589
|
if (APP_CONFIG.debug)
|
|
@@ -14615,6 +14617,7 @@ class AvatarView {
|
|
|
14615
14617
|
this.avatarController.dispose();
|
|
14616
14618
|
}
|
|
14617
14619
|
this.stopAllAnimationLoops();
|
|
14620
|
+
this.stopAvatarActiveHeartbeat();
|
|
14618
14621
|
this.setState("idle");
|
|
14619
14622
|
this.cachedIdleFirstFrame = null;
|
|
14620
14623
|
this.idleCurrentFrameIndex = 0;
|
|
@@ -14690,7 +14693,7 @@ class AvatarView {
|
|
|
14690
14693
|
throw error;
|
|
14691
14694
|
}
|
|
14692
14695
|
}
|
|
14693
|
-
async generateTransitionFromIdle(toFlame, frameCount) {
|
|
14696
|
+
async generateTransitionFromIdle(toFlame, frameCount, transitionType = "start") {
|
|
14694
14697
|
if (!this.isInitialized) {
|
|
14695
14698
|
throw new Error("AvatarView not initialized");
|
|
14696
14699
|
}
|
|
@@ -14706,16 +14709,18 @@ class AvatarView {
|
|
|
14706
14709
|
const idleFrameProto = convertWasmParamsToProtoFlame(idleParams);
|
|
14707
14710
|
const toFlameWithPostProcessing = this.avatarController.applyPostProcessingToFlame(toFlame);
|
|
14708
14711
|
const aligned = this.alignFlamePair(idleFrameProto, toFlameWithPostProcessing);
|
|
14712
|
+
const from = transitionType === "start" ? aligned.from : aligned.to;
|
|
14713
|
+
const to2 = transitionType === "start" ? aligned.to : aligned.from;
|
|
14709
14714
|
const fps = APP_CONFIG.animation.fps;
|
|
14710
14715
|
const durationMs = frameCount / fps * 1e3;
|
|
14711
14716
|
const transitionFrames = generateTransitionFrames(
|
|
14712
|
-
|
|
14713
|
-
|
|
14717
|
+
from,
|
|
14718
|
+
to2,
|
|
14714
14719
|
durationMs,
|
|
14715
14720
|
fps
|
|
14716
14721
|
);
|
|
14717
|
-
transitionFrames[0] =
|
|
14718
|
-
transitionFrames[transitionFrames.length - 1] =
|
|
14722
|
+
transitionFrames[0] = from;
|
|
14723
|
+
transitionFrames[transitionFrames.length - 1] = to2;
|
|
14719
14724
|
return transitionFrames;
|
|
14720
14725
|
} catch (error) {
|
|
14721
14726
|
logger.error("[AvatarView] Failed to generate transition from idle:", error instanceof Error ? error.message : String(error));
|
|
@@ -14761,6 +14766,28 @@ class AvatarView {
|
|
|
14761
14766
|
this.renderSystem.renderFrame();
|
|
14762
14767
|
}
|
|
14763
14768
|
}
|
|
14769
|
+
reportAvatarActive() {
|
|
14770
|
+
var _a, _b;
|
|
14771
|
+
logEvent("avatar_active", "info", {
|
|
14772
|
+
avatar_id: this.avatar.id,
|
|
14773
|
+
env: (_a = AvatarSDK.configuration) == null ? void 0 : _a.environment,
|
|
14774
|
+
dsm: ((_b = AvatarSDK.configuration) == null ? void 0 : _b.drivingServiceMode) || DrivingServiceMode.sdk
|
|
14775
|
+
});
|
|
14776
|
+
}
|
|
14777
|
+
startAvatarActiveHeartbeat() {
|
|
14778
|
+
this.stopAvatarActiveHeartbeat();
|
|
14779
|
+
this.avatarActiveTimer = window.setInterval(() => {
|
|
14780
|
+
if (this.isInitialized && typeof document !== "undefined" && document.visibilityState === "visible") {
|
|
14781
|
+
this.reportAvatarActive();
|
|
14782
|
+
}
|
|
14783
|
+
}, this.AVATAR_ACTIVE_INTERVAL);
|
|
14784
|
+
}
|
|
14785
|
+
stopAvatarActiveHeartbeat() {
|
|
14786
|
+
if (this.avatarActiveTimer !== null) {
|
|
14787
|
+
clearInterval(this.avatarActiveTimer);
|
|
14788
|
+
this.avatarActiveTimer = null;
|
|
14789
|
+
}
|
|
14790
|
+
}
|
|
14764
14791
|
}
|
|
14765
14792
|
export {
|
|
14766
14793
|
APP_CONFIG as A,
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Flame } from '../generated/driveningress/v1/driveningress';
|
|
2
2
|
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function bezierLerp(from: Flame, to: Flame, progress: number): Flame;
|
|
4
4
|
|
|
5
5
|
export declare function generateTransitionFrames(from: Flame, to: Flame, durationMs: number, fps?: number): Flame[];
|
|
6
6
|
|
package/package.json
CHANGED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Environment } from '../types';
|
|
2
|
-
declare class HeartbeatManager {
|
|
3
|
-
private heartbeatTimer;
|
|
4
|
-
private visibilityChangeHandler;
|
|
5
|
-
private isInitialized;
|
|
6
|
-
private currentEnvironment;
|
|
7
|
-
private currentAppId;
|
|
8
|
-
private failureCount;
|
|
9
|
-
start(environment: Environment): void;
|
|
10
|
-
stop(): void;
|
|
11
|
-
private startHeartbeatTimer;
|
|
12
|
-
private stopHeartbeatTimer;
|
|
13
|
-
private reportHeartbeat;
|
|
14
|
-
private setupVisibilityListener;
|
|
15
|
-
private removeVisibilityListener;
|
|
16
|
-
}
|
|
17
|
-
export declare const heartbeatManager: HeartbeatManager;
|
|
18
|
-
export {};
|