@spatius/avatarkit 1.1.0-beta.1 → 1.2.0-beta.2
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 +17 -0
- package/dist/{StreamingAudioPlayer-DrHXn-YA.js → StreamingAudioPlayer-CqzPGdy5.js} +1 -1
- package/dist/core/AvatarController.d.ts +28 -8
- package/dist/{index-CVQn5uEB.js → index-BnubRaUR.js} +399 -141
- package/dist/index.js +2 -1
- package/dist/types/index.d.ts +13 -47
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ 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.2.0-beta.2] - 2026-06-23
|
|
9
|
+
|
|
10
|
+
### Removed
|
|
11
|
+
- Post-processing support has been removed from the SDK. The `PostProcessingConfig` type and `AvatarController.setPostProcessingConfig()` are gone; the SDK now renders frames exactly as provided. No effect on standard playback or RTC.
|
|
12
|
+
|
|
13
|
+
## [1.2.0-beta.1] - 2026-06-19
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- Configurable frame-starvation handling. New public API:
|
|
17
|
+
- `FrameStarvationMode` enum with `audioIndependent` (default) and `strictSync`.
|
|
18
|
+
- `AvatarController.frameStarvationMode: FrameStarvationMode` — choose how playback behaves when animation frames can't keep up with audio. `audioIndependent` keeps audio playing while animation catches up (previous behavior); `strictSync` pauses audio until frames arrive, keeping audio and animation strictly in sync.
|
|
19
|
+
- `AvatarController.onPlaybackStall: ((stalled: boolean) => void) | null` — fires when audio is paused/resumed due to frame starvation (only in `strictSync`).
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Avatars now load via the latest avatar asset format.
|
|
23
|
+
- CDN selection now adapts to the configured region.
|
|
24
|
+
|
|
8
25
|
## [1.1.0-beta.1] - 2026-06-08
|
|
9
26
|
|
|
10
27
|
First 1.1 pre-release. Includes breaking changes — see below.
|
|
@@ -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-BnubRaUR.js";
|
|
5
5
|
class StreamingAudioPlayer {
|
|
6
6
|
// Mark if AudioContext is being resumed, avoid concurrent resume requests
|
|
7
7
|
constructor(options) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Avatar } from './Avatar';
|
|
2
|
-
import { ConnectionState, AvatarError, DrivingServiceMode, ConversationState, AnimationType
|
|
2
|
+
import { ConnectionState, AvatarError, DrivingServiceMode, FrameStarvationMode, ConversationState, AnimationType } from '../types';
|
|
3
3
|
import { FrameRateInfo } from '../performance/FrameRateMonitor';
|
|
4
4
|
export declare class AvatarController {
|
|
5
5
|
private networkLayer?;
|
|
@@ -12,6 +12,21 @@ export declare class AvatarController {
|
|
|
12
12
|
onError: ((error: AvatarError) => void) | null;
|
|
13
13
|
/** Callback for animation type changes (e.g., idle → mono in fallback mode). Aligned with iOS/Android AvatarController.onAnimationState. */
|
|
14
14
|
onAnimationState: ((type: AnimationType) => void) | null;
|
|
15
|
+
/**
|
|
16
|
+
* Strategy for handling animation-frame starvation. Default is
|
|
17
|
+
* {@link FrameStarvationMode.audioIndependent} (audio keeps playing, starvation only
|
|
18
|
+
* reported as telemetry — historical behavior). Set to {@link FrameStarvationMode.strictSync}
|
|
19
|
+
* to pause audio when frames run out and resume on new frames, notified via {@link onPlaybackStall}.
|
|
20
|
+
* Aligned with iOS/Android AvatarController.frameStarvationMode.
|
|
21
|
+
*/
|
|
22
|
+
frameStarvationMode: FrameStarvationMode;
|
|
23
|
+
/**
|
|
24
|
+
* Fires when audio is paused/resumed due to frame starvation. Only invoked in
|
|
25
|
+
* {@link FrameStarvationMode.strictSync}. `stalled=true` — frames ran out, audio paused;
|
|
26
|
+
* `stalled=false` — new frames arrived, audio resumed (or conversation ended / fell back).
|
|
27
|
+
* Aligned with iOS/Android AvatarController.onPlaybackStall.
|
|
28
|
+
*/
|
|
29
|
+
onPlaybackStall: ((stalled: boolean) => void) | null;
|
|
15
30
|
private eventListeners;
|
|
16
31
|
private readonly frameRateMonitor;
|
|
17
32
|
/** Frame rate monitoring callback. Fires with aggregated metrics from a 2-second sliding window. */
|
|
@@ -22,7 +37,6 @@ export declare class AvatarController {
|
|
|
22
37
|
set frameRateMonitorEnabled(value: boolean);
|
|
23
38
|
private renderCallback?;
|
|
24
39
|
private characterHandle;
|
|
25
|
-
private postProcessingConfig;
|
|
26
40
|
private lastRenderedFrameIndex;
|
|
27
41
|
private keyframesOffset;
|
|
28
42
|
private readonly MAX_KEYFRAMES;
|
|
@@ -32,6 +46,18 @@ export declare class AvatarController {
|
|
|
32
46
|
private isFallbackMode;
|
|
33
47
|
private frameStarvationEvents;
|
|
34
48
|
private isFrameStarved;
|
|
49
|
+
/**
|
|
50
|
+
* Whether this round's final animation batch (ServerResponseAnimation.end) has arrived.
|
|
51
|
+
* Frame starvation is only possible BEFORE this — once all frames are in, any remaining
|
|
52
|
+
* audio tail is normal end-of-round, not starvation, so audio must keep playing to idle
|
|
53
|
+
* and must never be paused (matters most in strictSync). Reset per conversation.
|
|
54
|
+
*/
|
|
55
|
+
private animationEnded;
|
|
56
|
+
/**
|
|
57
|
+
* Whether audio is currently paused because of frame starvation (strictSync only).
|
|
58
|
+
* Orthogonal to user pause — user pause/resume does not change it; only frame arrival does.
|
|
59
|
+
*/
|
|
60
|
+
private isAudioStalledForStarvation;
|
|
35
61
|
private playbackStuckCheckState;
|
|
36
62
|
private readonly MAX_AUDIO_TIME_ZERO_COUNT;
|
|
37
63
|
private readonly MAX_AUDIO_TIME_STUCK_COUNT;
|
|
@@ -113,12 +139,6 @@ export declare class AvatarController {
|
|
|
113
139
|
* The point count of current avatar, or null if avatar is not loaded.
|
|
114
140
|
*/
|
|
115
141
|
get pointCount(): number | null;
|
|
116
|
-
/**
|
|
117
|
-
* Set post-processing configuration
|
|
118
|
-
* These parameters will be applied in real-time to animation parameters returned by the server
|
|
119
|
-
* @param config Post-processing configuration, or null to clear
|
|
120
|
-
*/
|
|
121
|
-
setPostProcessingConfig(config: PostProcessingConfig | null): void;
|
|
122
142
|
/**
|
|
123
143
|
* Set audio playback volume
|
|
124
144
|
* Note: This only controls the avatar audio player volume, not the system volume
|
|
@@ -1257,6 +1257,7 @@ function messageTypeToJSON(object) {
|
|
|
1257
1257
|
}
|
|
1258
1258
|
var AudioFormat = /* @__PURE__ */ ((AudioFormat2) => {
|
|
1259
1259
|
AudioFormat2[AudioFormat2["AUDIO_FORMAT_PCM_S16LE"] = 0] = "AUDIO_FORMAT_PCM_S16LE";
|
|
1260
|
+
AudioFormat2[AudioFormat2["AUDIO_FORMAT_OGG_OPUS"] = 1] = "AUDIO_FORMAT_OGG_OPUS";
|
|
1260
1261
|
AudioFormat2[AudioFormat2["UNRECOGNIZED"] = -1] = "UNRECOGNIZED";
|
|
1261
1262
|
return AudioFormat2;
|
|
1262
1263
|
})(AudioFormat || {});
|
|
@@ -1265,6 +1266,9 @@ function audioFormatFromJSON(object) {
|
|
|
1265
1266
|
case 0:
|
|
1266
1267
|
case "AUDIO_FORMAT_PCM_S16LE":
|
|
1267
1268
|
return 0;
|
|
1269
|
+
case 1:
|
|
1270
|
+
case "AUDIO_FORMAT_OGG_OPUS":
|
|
1271
|
+
return 1;
|
|
1268
1272
|
case -1:
|
|
1269
1273
|
case "UNRECOGNIZED":
|
|
1270
1274
|
default:
|
|
@@ -1275,6 +1279,8 @@ function audioFormatToJSON(object) {
|
|
|
1275
1279
|
switch (object) {
|
|
1276
1280
|
case 0:
|
|
1277
1281
|
return "AUDIO_FORMAT_PCM_S16LE";
|
|
1282
|
+
case 1:
|
|
1283
|
+
return "AUDIO_FORMAT_OGG_OPUS";
|
|
1278
1284
|
case -1:
|
|
1279
1285
|
default:
|
|
1280
1286
|
return "UNRECOGNIZED";
|
|
@@ -1343,7 +1349,16 @@ function egressTypeToJSON(object) {
|
|
|
1343
1349
|
}
|
|
1344
1350
|
}
|
|
1345
1351
|
function createBaseLiveKitEgressConfig() {
|
|
1346
|
-
return {
|
|
1352
|
+
return {
|
|
1353
|
+
url: "",
|
|
1354
|
+
apiKey: "",
|
|
1355
|
+
apiSecret: "",
|
|
1356
|
+
roomName: "",
|
|
1357
|
+
publisherId: "",
|
|
1358
|
+
extraAttributes: {},
|
|
1359
|
+
idleTimeout: 0,
|
|
1360
|
+
apiToken: ""
|
|
1361
|
+
};
|
|
1347
1362
|
}
|
|
1348
1363
|
const LiveKitEgressConfig = {
|
|
1349
1364
|
encode(message, writer = new BinaryWriter()) {
|
|
@@ -1362,6 +1377,15 @@ const LiveKitEgressConfig = {
|
|
|
1362
1377
|
if (message.publisherId !== "") {
|
|
1363
1378
|
writer.uint32(42).string(message.publisherId);
|
|
1364
1379
|
}
|
|
1380
|
+
globalThis.Object.entries(message.extraAttributes).forEach(([key, value]) => {
|
|
1381
|
+
LiveKitEgressConfig_ExtraAttributesEntry.encode({ key, value }, writer.uint32(50).fork()).join();
|
|
1382
|
+
});
|
|
1383
|
+
if (message.idleTimeout !== 0) {
|
|
1384
|
+
writer.uint32(56).int32(message.idleTimeout);
|
|
1385
|
+
}
|
|
1386
|
+
if (message.apiToken !== "") {
|
|
1387
|
+
writer.uint32(66).string(message.apiToken);
|
|
1388
|
+
}
|
|
1365
1389
|
return writer;
|
|
1366
1390
|
},
|
|
1367
1391
|
decode(input, length) {
|
|
@@ -1406,6 +1430,30 @@ const LiveKitEgressConfig = {
|
|
|
1406
1430
|
message.publisherId = reader.string();
|
|
1407
1431
|
continue;
|
|
1408
1432
|
}
|
|
1433
|
+
case 6: {
|
|
1434
|
+
if (tag !== 50) {
|
|
1435
|
+
break;
|
|
1436
|
+
}
|
|
1437
|
+
const entry6 = LiveKitEgressConfig_ExtraAttributesEntry.decode(reader, reader.uint32());
|
|
1438
|
+
if (entry6.value !== void 0) {
|
|
1439
|
+
message.extraAttributes[entry6.key] = entry6.value;
|
|
1440
|
+
}
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
case 7: {
|
|
1444
|
+
if (tag !== 56) {
|
|
1445
|
+
break;
|
|
1446
|
+
}
|
|
1447
|
+
message.idleTimeout = reader.int32();
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
case 8: {
|
|
1451
|
+
if (tag !== 66) {
|
|
1452
|
+
break;
|
|
1453
|
+
}
|
|
1454
|
+
message.apiToken = reader.string();
|
|
1455
|
+
continue;
|
|
1456
|
+
}
|
|
1409
1457
|
}
|
|
1410
1458
|
if ((tag & 7) === 4 || tag === 0) {
|
|
1411
1459
|
break;
|
|
@@ -1420,7 +1468,22 @@ const LiveKitEgressConfig = {
|
|
|
1420
1468
|
apiKey: isSet(object.apiKey) ? globalThis.String(object.apiKey) : isSet(object.api_key) ? globalThis.String(object.api_key) : "",
|
|
1421
1469
|
apiSecret: isSet(object.apiSecret) ? globalThis.String(object.apiSecret) : isSet(object.api_secret) ? globalThis.String(object.api_secret) : "",
|
|
1422
1470
|
roomName: isSet(object.roomName) ? globalThis.String(object.roomName) : isSet(object.room_name) ? globalThis.String(object.room_name) : "",
|
|
1423
|
-
publisherId: isSet(object.publisherId) ? globalThis.String(object.publisherId) : isSet(object.publisher_id) ? globalThis.String(object.publisher_id) : ""
|
|
1471
|
+
publisherId: isSet(object.publisherId) ? globalThis.String(object.publisherId) : isSet(object.publisher_id) ? globalThis.String(object.publisher_id) : "",
|
|
1472
|
+
extraAttributes: isObject(object.extraAttributes) ? globalThis.Object.entries(object.extraAttributes).reduce(
|
|
1473
|
+
(acc, [key, value]) => {
|
|
1474
|
+
acc[key] = globalThis.String(value);
|
|
1475
|
+
return acc;
|
|
1476
|
+
},
|
|
1477
|
+
{}
|
|
1478
|
+
) : isObject(object.extra_attributes) ? globalThis.Object.entries(object.extra_attributes).reduce(
|
|
1479
|
+
(acc, [key, value]) => {
|
|
1480
|
+
acc[key] = globalThis.String(value);
|
|
1481
|
+
return acc;
|
|
1482
|
+
},
|
|
1483
|
+
{}
|
|
1484
|
+
) : {},
|
|
1485
|
+
idleTimeout: isSet(object.idleTimeout) ? globalThis.Number(object.idleTimeout) : isSet(object.idle_timeout) ? globalThis.Number(object.idle_timeout) : 0,
|
|
1486
|
+
apiToken: isSet(object.apiToken) ? globalThis.String(object.apiToken) : isSet(object.api_token) ? globalThis.String(object.api_token) : ""
|
|
1424
1487
|
};
|
|
1425
1488
|
},
|
|
1426
1489
|
toJSON(message) {
|
|
@@ -1440,6 +1503,21 @@ const LiveKitEgressConfig = {
|
|
|
1440
1503
|
if (message.publisherId !== "") {
|
|
1441
1504
|
obj.publisherId = message.publisherId;
|
|
1442
1505
|
}
|
|
1506
|
+
if (message.extraAttributes) {
|
|
1507
|
+
const entries = globalThis.Object.entries(message.extraAttributes);
|
|
1508
|
+
if (entries.length > 0) {
|
|
1509
|
+
obj.extraAttributes = {};
|
|
1510
|
+
entries.forEach(([k2, v2]) => {
|
|
1511
|
+
obj.extraAttributes[k2] = v2;
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
if (message.idleTimeout !== 0) {
|
|
1516
|
+
obj.idleTimeout = Math.round(message.idleTimeout);
|
|
1517
|
+
}
|
|
1518
|
+
if (message.apiToken !== "") {
|
|
1519
|
+
obj.apiToken = message.apiToken;
|
|
1520
|
+
}
|
|
1443
1521
|
return obj;
|
|
1444
1522
|
},
|
|
1445
1523
|
create(base) {
|
|
@@ -1452,6 +1530,85 @@ const LiveKitEgressConfig = {
|
|
|
1452
1530
|
message.apiSecret = object.apiSecret ?? "";
|
|
1453
1531
|
message.roomName = object.roomName ?? "";
|
|
1454
1532
|
message.publisherId = object.publisherId ?? "";
|
|
1533
|
+
message.extraAttributes = globalThis.Object.entries(object.extraAttributes ?? {}).reduce(
|
|
1534
|
+
(acc, [key, value]) => {
|
|
1535
|
+
if (value !== void 0) {
|
|
1536
|
+
acc[key] = globalThis.String(value);
|
|
1537
|
+
}
|
|
1538
|
+
return acc;
|
|
1539
|
+
},
|
|
1540
|
+
{}
|
|
1541
|
+
);
|
|
1542
|
+
message.idleTimeout = object.idleTimeout ?? 0;
|
|
1543
|
+
message.apiToken = object.apiToken ?? "";
|
|
1544
|
+
return message;
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
function createBaseLiveKitEgressConfig_ExtraAttributesEntry() {
|
|
1548
|
+
return { key: "", value: "" };
|
|
1549
|
+
}
|
|
1550
|
+
const LiveKitEgressConfig_ExtraAttributesEntry = {
|
|
1551
|
+
encode(message, writer = new BinaryWriter()) {
|
|
1552
|
+
if (message.key !== "") {
|
|
1553
|
+
writer.uint32(10).string(message.key);
|
|
1554
|
+
}
|
|
1555
|
+
if (message.value !== "") {
|
|
1556
|
+
writer.uint32(18).string(message.value);
|
|
1557
|
+
}
|
|
1558
|
+
return writer;
|
|
1559
|
+
},
|
|
1560
|
+
decode(input, length) {
|
|
1561
|
+
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
|
1562
|
+
const end = length === void 0 ? reader.len : reader.pos + length;
|
|
1563
|
+
const message = createBaseLiveKitEgressConfig_ExtraAttributesEntry();
|
|
1564
|
+
while (reader.pos < end) {
|
|
1565
|
+
const tag = reader.uint32();
|
|
1566
|
+
switch (tag >>> 3) {
|
|
1567
|
+
case 1: {
|
|
1568
|
+
if (tag !== 10) {
|
|
1569
|
+
break;
|
|
1570
|
+
}
|
|
1571
|
+
message.key = reader.string();
|
|
1572
|
+
continue;
|
|
1573
|
+
}
|
|
1574
|
+
case 2: {
|
|
1575
|
+
if (tag !== 18) {
|
|
1576
|
+
break;
|
|
1577
|
+
}
|
|
1578
|
+
message.value = reader.string();
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
if ((tag & 7) === 4 || tag === 0) {
|
|
1583
|
+
break;
|
|
1584
|
+
}
|
|
1585
|
+
reader.skip(tag & 7);
|
|
1586
|
+
}
|
|
1587
|
+
return message;
|
|
1588
|
+
},
|
|
1589
|
+
fromJSON(object) {
|
|
1590
|
+
return {
|
|
1591
|
+
key: isSet(object.key) ? globalThis.String(object.key) : "",
|
|
1592
|
+
value: isSet(object.value) ? globalThis.String(object.value) : ""
|
|
1593
|
+
};
|
|
1594
|
+
},
|
|
1595
|
+
toJSON(message) {
|
|
1596
|
+
const obj = {};
|
|
1597
|
+
if (message.key !== "") {
|
|
1598
|
+
obj.key = message.key;
|
|
1599
|
+
}
|
|
1600
|
+
if (message.value !== "") {
|
|
1601
|
+
obj.value = message.value;
|
|
1602
|
+
}
|
|
1603
|
+
return obj;
|
|
1604
|
+
},
|
|
1605
|
+
create(base) {
|
|
1606
|
+
return LiveKitEgressConfig_ExtraAttributesEntry.fromPartial(base ?? {});
|
|
1607
|
+
},
|
|
1608
|
+
fromPartial(object) {
|
|
1609
|
+
const message = createBaseLiveKitEgressConfig_ExtraAttributesEntry();
|
|
1610
|
+
message.key = object.key ?? "";
|
|
1611
|
+
message.value = object.value ?? "";
|
|
1455
1612
|
return message;
|
|
1456
1613
|
}
|
|
1457
1614
|
};
|
|
@@ -3999,6 +4156,11 @@ var DrivingServiceMode = /* @__PURE__ */ ((DrivingServiceMode2) => {
|
|
|
3999
4156
|
DrivingServiceMode2["backend"] = "backend";
|
|
4000
4157
|
return DrivingServiceMode2;
|
|
4001
4158
|
})(DrivingServiceMode || {});
|
|
4159
|
+
var FrameStarvationMode = /* @__PURE__ */ ((FrameStarvationMode2) => {
|
|
4160
|
+
FrameStarvationMode2["audioIndependent"] = "audioIndependent";
|
|
4161
|
+
FrameStarvationMode2["strictSync"] = "strictSync";
|
|
4162
|
+
return FrameStarvationMode2;
|
|
4163
|
+
})(FrameStarvationMode || {});
|
|
4002
4164
|
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
4003
4165
|
LogLevel2["off"] = "off";
|
|
4004
4166
|
LogLevel2["error"] = "error";
|
|
@@ -12349,6 +12511,11 @@ function convertWasmParamsToProtoFlame(wasmParams) {
|
|
|
12349
12511
|
expression: wasmParams.expr_params || []
|
|
12350
12512
|
};
|
|
12351
12513
|
}
|
|
12514
|
+
const GLOBAL_FLAME_CDN_BASE = "https://cdn.spatialwalk.cloud/public";
|
|
12515
|
+
const CN_FLAME_CDN_BASE = "https://cdn.spatialwalk.top/public";
|
|
12516
|
+
function getFlameCdnBase(region) {
|
|
12517
|
+
return region.startsWith("cn-") ? CN_FLAME_CDN_BASE : GLOBAL_FLAME_CDN_BASE;
|
|
12518
|
+
}
|
|
12352
12519
|
const APP_CONFIG = {
|
|
12353
12520
|
// Dynamic debug mode check (includes URL parameter)
|
|
12354
12521
|
get debug() {
|
|
@@ -12371,7 +12538,6 @@ const APP_CONFIG = {
|
|
|
12371
12538
|
},
|
|
12372
12539
|
// Unified template model CDN (single compressed model shared by all characters)
|
|
12373
12540
|
flame: {
|
|
12374
|
-
cdnBase: "https://cdn.spatialwalk.cloud/public",
|
|
12375
12541
|
unifiedModelPath: "base_model.pb.gz"
|
|
12376
12542
|
}
|
|
12377
12543
|
};
|
|
@@ -12465,7 +12631,7 @@ const _AnimationPlayer = class _AnimationPlayer {
|
|
|
12465
12631
|
if (this.streamingPlayer) {
|
|
12466
12632
|
return;
|
|
12467
12633
|
}
|
|
12468
|
-
const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-
|
|
12634
|
+
const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-CqzPGdy5.js");
|
|
12469
12635
|
const { AvatarSDK: AvatarSDK2 } = await Promise.resolve().then(() => AvatarSDK$1);
|
|
12470
12636
|
const audioFormat = AvatarSDK2.getAudioFormat();
|
|
12471
12637
|
this.streamingPlayer = new StreamingAudioPlayer({
|
|
@@ -14525,7 +14691,7 @@ class AvatarSDK {
|
|
|
14525
14691
|
__publicField(AvatarSDK, "_initializationState", "uninitialized");
|
|
14526
14692
|
__publicField(AvatarSDK, "_initializingPromise", null);
|
|
14527
14693
|
__publicField(AvatarSDK, "_configuration", null);
|
|
14528
|
-
__publicField(AvatarSDK, "_version", "1.
|
|
14694
|
+
__publicField(AvatarSDK, "_version", "1.2.0-beta.2");
|
|
14529
14695
|
__publicField(AvatarSDK, "_avatarCore", null);
|
|
14530
14696
|
__publicField(AvatarSDK, "_cachedDeviceScore", null);
|
|
14531
14697
|
__publicField(AvatarSDK, "_rendererBackend", null);
|
|
@@ -14576,6 +14742,40 @@ class EventEmitter {
|
|
|
14576
14742
|
}
|
|
14577
14743
|
}
|
|
14578
14744
|
}
|
|
14745
|
+
class PendingAudioBuffer {
|
|
14746
|
+
constructor() {
|
|
14747
|
+
__publicField(this, "items", []);
|
|
14748
|
+
}
|
|
14749
|
+
get isEmpty() {
|
|
14750
|
+
return this.items.length === 0;
|
|
14751
|
+
}
|
|
14752
|
+
get count() {
|
|
14753
|
+
return this.items.length;
|
|
14754
|
+
}
|
|
14755
|
+
/** Buffer a chunk sent while not yet able to reach the server. */
|
|
14756
|
+
enqueue(item) {
|
|
14757
|
+
this.items.push(item);
|
|
14758
|
+
}
|
|
14759
|
+
/**
|
|
14760
|
+
* Drain the buffer, returning only the latest conversation's chunks (in
|
|
14761
|
+
* arrival order) for replay and dropping anything from earlier conversations.
|
|
14762
|
+
* The buffer is empty afterwards.
|
|
14763
|
+
*/
|
|
14764
|
+
flush() {
|
|
14765
|
+
const buffered = this.items;
|
|
14766
|
+
this.items = [];
|
|
14767
|
+
if (buffered.length === 0) {
|
|
14768
|
+
return { toReplay: [], dropped: 0 };
|
|
14769
|
+
}
|
|
14770
|
+
const currentId = buffered[buffered.length - 1].conversationId;
|
|
14771
|
+
const toReplay = buffered.filter((it2) => it2.conversationId === currentId);
|
|
14772
|
+
return { toReplay, dropped: buffered.length - toReplay.length };
|
|
14773
|
+
}
|
|
14774
|
+
/** Drop everything (conversation ended / explicit disconnect). */
|
|
14775
|
+
clear() {
|
|
14776
|
+
this.items = [];
|
|
14777
|
+
}
|
|
14778
|
+
}
|
|
14579
14779
|
class AnimationWebSocketClient extends EventEmitter {
|
|
14580
14780
|
constructor(options) {
|
|
14581
14781
|
super();
|
|
@@ -14593,6 +14793,13 @@ class AnimationWebSocketClient extends EventEmitter {
|
|
|
14593
14793
|
__publicField(this, "sessionConfigured", false);
|
|
14594
14794
|
// v2 protocol: mark if session is configured
|
|
14595
14795
|
__publicField(this, "connectionStartTime", 0);
|
|
14796
|
+
/**
|
|
14797
|
+
* Direct mode: audio sent before the session is confirmed is buffered here and
|
|
14798
|
+
* replayed (in order) the moment the session is confirmed, so the server's
|
|
14799
|
+
* audio stream stays identical to what played locally. See PendingAudioBuffer
|
|
14800
|
+
* for the rationale and conversation-boundary handling.
|
|
14801
|
+
*/
|
|
14802
|
+
__publicField(this, "pendingAudioBuffer", new PendingAudioBuffer());
|
|
14596
14803
|
this.wsUrl = options.wsUrl;
|
|
14597
14804
|
this.reconnectAttempts = options.reconnectAttempts ?? 5;
|
|
14598
14805
|
this.jwtToken = options.jwtToken;
|
|
@@ -14637,6 +14844,7 @@ class AnimationWebSocketClient extends EventEmitter {
|
|
|
14637
14844
|
this.ws.close(1e3, "Normal closure");
|
|
14638
14845
|
this.ws = null;
|
|
14639
14846
|
}
|
|
14847
|
+
this.pendingAudioBuffer.clear();
|
|
14640
14848
|
idManager.clearConnectionId();
|
|
14641
14849
|
this.removeAllListeners();
|
|
14642
14850
|
this.currentRetryCount = 0;
|
|
@@ -14655,14 +14863,18 @@ class AnimationWebSocketClient extends EventEmitter {
|
|
|
14655
14863
|
* @internal
|
|
14656
14864
|
*/
|
|
14657
14865
|
sendAudioData(conversationId, audioData, end) {
|
|
14658
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
14659
|
-
logger.warn("[AnimationWebSocketClient]
|
|
14660
|
-
|
|
14661
|
-
}
|
|
14662
|
-
if (!this.sessionConfigured) {
|
|
14663
|
-
logger.warn("[AnimationWebSocketClient] Session not configured yet, skipping audio send");
|
|
14866
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.sessionConfigured) {
|
|
14867
|
+
logger.warn("[AnimationWebSocketClient] Not ready (socket/session), buffering audio for replay on connect");
|
|
14868
|
+
this.pendingAudioBuffer.enqueue({ conversationId, audioData, end });
|
|
14664
14869
|
return false;
|
|
14665
14870
|
}
|
|
14871
|
+
return this.encodeAndSendAudio(conversationId, audioData, end);
|
|
14872
|
+
}
|
|
14873
|
+
/**
|
|
14874
|
+
* Encode and write one audio chunk to the open socket. Caller must ensure the
|
|
14875
|
+
* socket is open and the session is configured.
|
|
14876
|
+
*/
|
|
14877
|
+
encodeAndSendAudio(conversationId, audioData, end) {
|
|
14666
14878
|
try {
|
|
14667
14879
|
const message = {
|
|
14668
14880
|
type: MessageType.MESSAGE_CLIENT_AUDIO_INPUT,
|
|
@@ -14689,6 +14901,23 @@ class AnimationWebSocketClient extends EventEmitter {
|
|
|
14689
14901
|
return false;
|
|
14690
14902
|
}
|
|
14691
14903
|
}
|
|
14904
|
+
/**
|
|
14905
|
+
* Replay audio buffered before the session was confirmed, in arrival order,
|
|
14906
|
+
* now that it's confirmed. Only the latest conversation's audio is replayed so
|
|
14907
|
+
* the server's audio stream matches what played locally; earlier conversations
|
|
14908
|
+
* are dropped. Aligned with iOS / Android flushPendingAudioMessages.
|
|
14909
|
+
*/
|
|
14910
|
+
flushPendingAudio() {
|
|
14911
|
+
if (this.pendingAudioBuffer.isEmpty) return;
|
|
14912
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.sessionConfigured) return;
|
|
14913
|
+
const { toReplay, dropped } = this.pendingAudioBuffer.flush();
|
|
14914
|
+
logger.log(
|
|
14915
|
+
`[AnimationWebSocketClient] Flushing ${toReplay.length} buffered audio chunk(s) to driving service.` + (dropped > 0 ? ` Dropped ${dropped} from earlier conversation(s).` : "")
|
|
14916
|
+
);
|
|
14917
|
+
for (const item of toReplay) {
|
|
14918
|
+
this.encodeAndSendAudio(item.conversationId, item.audioData, item.end);
|
|
14919
|
+
}
|
|
14920
|
+
}
|
|
14692
14921
|
/**
|
|
14693
14922
|
* Generate conversation ID
|
|
14694
14923
|
* Uses unified conversation ID generation rule: YYYYMMDDHHmmss_nanoid
|
|
@@ -14937,6 +15166,7 @@ class AnimationWebSocketClient extends EventEmitter {
|
|
|
14937
15166
|
} else {
|
|
14938
15167
|
logger.log("[AnimationWebSocketClient] Session confirmed by server");
|
|
14939
15168
|
}
|
|
15169
|
+
this.flushPendingAudio();
|
|
14940
15170
|
this.emit("sessionConfirmed", connectionId);
|
|
14941
15171
|
return;
|
|
14942
15172
|
}
|
|
@@ -15212,7 +15442,7 @@ class NetworkLayer {
|
|
|
15212
15442
|
logger.error("[NetworkLayer] Invalid animation message");
|
|
15213
15443
|
return;
|
|
15214
15444
|
}
|
|
15215
|
-
const { reqId, animation, avatarId } = message.serverResponseAnimation;
|
|
15445
|
+
const { reqId, animation, avatarId, end } = message.serverResponseAnimation;
|
|
15216
15446
|
const conversationId = reqId;
|
|
15217
15447
|
if (avatarId && avatarId !== this.dataController.getAvatarId()) {
|
|
15218
15448
|
logger.error(`[NetworkLayer] Ignoring animation data for mismatched avatar - expected: ${this.dataController.getAvatarId()}, received: ${avatarId}`);
|
|
@@ -15239,7 +15469,7 @@ class NetworkLayer {
|
|
|
15239
15469
|
if ((animation == null ? void 0 : animation.keyframes) && animation.keyframes.length > 0) {
|
|
15240
15470
|
const keyframes = animation.keyframes;
|
|
15241
15471
|
try {
|
|
15242
|
-
this.dataController.yieldKeyframes(keyframes, conversationId);
|
|
15472
|
+
this.dataController.yieldKeyframes(keyframes, conversationId, end);
|
|
15243
15473
|
} catch (error) {
|
|
15244
15474
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
15245
15475
|
logger.error(`[NetworkLayer] Failed to yield animation data: ${errorMessage}`);
|
|
@@ -15606,6 +15836,21 @@ class AvatarController {
|
|
|
15606
15836
|
__publicField(this, "onError", null);
|
|
15607
15837
|
/** Callback for animation type changes (e.g., idle → mono in fallback mode). Aligned with iOS/Android AvatarController.onAnimationState. */
|
|
15608
15838
|
__publicField(this, "onAnimationState", null);
|
|
15839
|
+
/**
|
|
15840
|
+
* Strategy for handling animation-frame starvation. Default is
|
|
15841
|
+
* {@link FrameStarvationMode.audioIndependent} (audio keeps playing, starvation only
|
|
15842
|
+
* reported as telemetry — historical behavior). Set to {@link FrameStarvationMode.strictSync}
|
|
15843
|
+
* to pause audio when frames run out and resume on new frames, notified via {@link onPlaybackStall}.
|
|
15844
|
+
* Aligned with iOS/Android AvatarController.frameStarvationMode.
|
|
15845
|
+
*/
|
|
15846
|
+
__publicField(this, "frameStarvationMode", FrameStarvationMode.audioIndependent);
|
|
15847
|
+
/**
|
|
15848
|
+
* Fires when audio is paused/resumed due to frame starvation. Only invoked in
|
|
15849
|
+
* {@link FrameStarvationMode.strictSync}. `stalled=true` — frames ran out, audio paused;
|
|
15850
|
+
* `stalled=false` — new frames arrived, audio resumed (or conversation ended / fell back).
|
|
15851
|
+
* Aligned with iOS/Android AvatarController.onPlaybackStall.
|
|
15852
|
+
*/
|
|
15853
|
+
__publicField(this, "onPlaybackStall", null);
|
|
15609
15854
|
__publicField(this, "eventListeners", /* @__PURE__ */ new Map());
|
|
15610
15855
|
// ========== Performance Monitoring ==========
|
|
15611
15856
|
__publicField(this, "frameRateMonitor", new FrameRateMonitor());
|
|
@@ -15614,7 +15859,6 @@ class AvatarController {
|
|
|
15614
15859
|
__publicField(this, "characterHandle", null);
|
|
15615
15860
|
// Character handle for multi-character support
|
|
15616
15861
|
// ========== Post-processing Configuration ==========
|
|
15617
|
-
__publicField(this, "postProcessingConfig", null);
|
|
15618
15862
|
// ========== Playback Loop ==========
|
|
15619
15863
|
__publicField(this, "lastRenderedFrameIndex", -1);
|
|
15620
15864
|
__publicField(this, "keyframesOffset", 0);
|
|
@@ -15634,6 +15878,18 @@ class AvatarController {
|
|
|
15634
15878
|
// ========== Frame Starvation Tracking ==========
|
|
15635
15879
|
__publicField(this, "frameStarvationEvents", []);
|
|
15636
15880
|
__publicField(this, "isFrameStarved", false);
|
|
15881
|
+
/**
|
|
15882
|
+
* Whether this round's final animation batch (ServerResponseAnimation.end) has arrived.
|
|
15883
|
+
* Frame starvation is only possible BEFORE this — once all frames are in, any remaining
|
|
15884
|
+
* audio tail is normal end-of-round, not starvation, so audio must keep playing to idle
|
|
15885
|
+
* and must never be paused (matters most in strictSync). Reset per conversation.
|
|
15886
|
+
*/
|
|
15887
|
+
__publicField(this, "animationEnded", false);
|
|
15888
|
+
/**
|
|
15889
|
+
* Whether audio is currently paused because of frame starvation (strictSync only).
|
|
15890
|
+
* Orthogonal to user pause — user pause/resume does not change it; only frame arrival does.
|
|
15891
|
+
*/
|
|
15892
|
+
__publicField(this, "isAudioStalledForStarvation", false);
|
|
15637
15893
|
// ========== Playback Stuck Detection ==========
|
|
15638
15894
|
__publicField(this, "playbackStuckCheckState", {
|
|
15639
15895
|
audioTimeZeroCount: 0,
|
|
@@ -16151,7 +16407,7 @@ class AvatarController {
|
|
|
16151
16407
|
if (allKeyframes.length === 0) {
|
|
16152
16408
|
logger.warn(`[AvatarController] No keyframes decoded from ${keyframesDataArray.length} message chunks`);
|
|
16153
16409
|
}
|
|
16154
|
-
this.yieldKeyframes(allKeyframes, conversationId);
|
|
16410
|
+
this.yieldKeyframes(allKeyframes, conversationId, isEnd);
|
|
16155
16411
|
return isEnd;
|
|
16156
16412
|
}
|
|
16157
16413
|
/**
|
|
@@ -16159,12 +16415,15 @@ class AvatarController {
|
|
|
16159
16415
|
* External consumers should use `yieldFramesData()` instead.
|
|
16160
16416
|
* @internal
|
|
16161
16417
|
*/
|
|
16162
|
-
yieldKeyframes(keyframes, conversationId) {
|
|
16163
|
-
var _a;
|
|
16418
|
+
yieldKeyframes(keyframes, conversationId, isEnd = false) {
|
|
16419
|
+
var _a, _b;
|
|
16164
16420
|
if (!conversationId || typeof conversationId !== "string") {
|
|
16165
16421
|
logger.error(`[AvatarController] yieldKeyframes requires a valid conversationId. The conversationId is returned by yieldAudioData().`);
|
|
16166
16422
|
return;
|
|
16167
16423
|
}
|
|
16424
|
+
if (isEnd) {
|
|
16425
|
+
this.animationEnded = true;
|
|
16426
|
+
}
|
|
16168
16427
|
const expectedConversationId = this.getEffectiveConversationId();
|
|
16169
16428
|
if (!expectedConversationId || conversationId !== expectedConversationId) {
|
|
16170
16429
|
logger.warn(`[AvatarController] Ignoring mismatched animation data - expected conversationId: ${expectedConversationId}, received conversationId: ${conversationId}`);
|
|
@@ -16194,6 +16453,14 @@ class AvatarController {
|
|
|
16194
16453
|
this.currentKeyframes.push(...flameKeyframes);
|
|
16195
16454
|
}
|
|
16196
16455
|
this.emit("keyframesUpdate", this.currentKeyframes);
|
|
16456
|
+
if (this.isAudioStalledForStarvation) {
|
|
16457
|
+
const audioTime = ((_b = this.animationPlayer) == null ? void 0 : _b.getCurrentTime()) ?? 0;
|
|
16458
|
+
const frameIndex = Math.round(audioTime * FLAME_FRAME_RATE);
|
|
16459
|
+
const arrayIndex = frameIndex - this.keyframesOffset;
|
|
16460
|
+
if (arrayIndex >= 0 && arrayIndex < this.currentKeyframes.length) {
|
|
16461
|
+
this.resumeAudioFromStarvation();
|
|
16462
|
+
}
|
|
16463
|
+
}
|
|
16197
16464
|
if (!this.isPlaying && !this.isStartingPlayback && this.pendingAudioChunks.length > 0 && this.currentKeyframes.length > 0) {
|
|
16198
16465
|
this.startStreamingPlayback().catch((error) => {
|
|
16199
16466
|
var _a2;
|
|
@@ -16230,7 +16497,9 @@ class AvatarController {
|
|
|
16230
16497
|
return;
|
|
16231
16498
|
}
|
|
16232
16499
|
logger.log("[AvatarController] Resuming playback");
|
|
16233
|
-
|
|
16500
|
+
if (!this.isAudioStalledForStarvation) {
|
|
16501
|
+
await ((_a = this.animationPlayer) == null ? void 0 : _a.resume());
|
|
16502
|
+
}
|
|
16234
16503
|
this.currentState = AvatarState.playing;
|
|
16235
16504
|
this.notifyConversationState(AvatarState.playing);
|
|
16236
16505
|
logger.log("[AvatarController] Playback resumed");
|
|
@@ -16305,6 +16574,7 @@ class AvatarController {
|
|
|
16305
16574
|
* @internal
|
|
16306
16575
|
*/
|
|
16307
16576
|
clearPlaybackData() {
|
|
16577
|
+
var _a;
|
|
16308
16578
|
this.currentKeyframes = [];
|
|
16309
16579
|
this.pendingAudioChunks = [];
|
|
16310
16580
|
this.lastRenderedFrameIndex = -1;
|
|
@@ -16312,6 +16582,11 @@ class AvatarController {
|
|
|
16312
16582
|
this.isFallbackMode = false;
|
|
16313
16583
|
this.lastSyncLogTime = 0;
|
|
16314
16584
|
this.lastOutOfBoundsState = false;
|
|
16585
|
+
this.animationEnded = false;
|
|
16586
|
+
if (this.isAudioStalledForStarvation) {
|
|
16587
|
+
this.isAudioStalledForStarvation = false;
|
|
16588
|
+
(_a = this.onPlaybackStall) == null ? void 0 : _a.call(this, false);
|
|
16589
|
+
}
|
|
16315
16590
|
if (this.playbackMode === DrivingServiceMode.backend) {
|
|
16316
16591
|
this.hostModeMetrics = {
|
|
16317
16592
|
accumulatedBytes: 0,
|
|
@@ -16387,18 +16662,7 @@ class AvatarController {
|
|
|
16387
16662
|
return avatarCore.getPointCount(this.characterHandle);
|
|
16388
16663
|
}
|
|
16389
16664
|
/**
|
|
16390
|
-
*
|
|
16391
|
-
* These parameters will be applied in real-time to animation parameters returned by the server
|
|
16392
|
-
* @param config Post-processing configuration, or null to clear
|
|
16393
|
-
*/
|
|
16394
|
-
setPostProcessingConfig(config) {
|
|
16395
|
-
this.postProcessingConfig = config;
|
|
16396
|
-
if (this.currentState === AvatarState.paused && this.isPlaying) {
|
|
16397
|
-
this.rerenderCurrentFrame();
|
|
16398
|
-
}
|
|
16399
|
-
}
|
|
16400
|
-
/**
|
|
16401
|
-
* 重新渲染当前帧(用于暂停状态下更新后处理参数或相机配置)
|
|
16665
|
+
* 重新渲染当前帧(用于暂停状态下更新相机配置)
|
|
16402
16666
|
* @internal
|
|
16403
16667
|
*/
|
|
16404
16668
|
async rerenderCurrentFrameIfPaused() {
|
|
@@ -16418,10 +16682,7 @@ class AvatarController {
|
|
|
16418
16682
|
}
|
|
16419
16683
|
try {
|
|
16420
16684
|
const currentFrame = this.currentKeyframes[arrayIndex];
|
|
16421
|
-
|
|
16422
|
-
if (this.postProcessingConfig) {
|
|
16423
|
-
wasmParams = this.applyPostProcessingToParams(wasmParams);
|
|
16424
|
-
}
|
|
16685
|
+
const wasmParams = convertProtoFlameToWasmParams(currentFrame);
|
|
16425
16686
|
const avatarCore = AvatarSDK.getAvatarCore();
|
|
16426
16687
|
if (avatarCore) {
|
|
16427
16688
|
const splatData = await avatarCore.computeFrameFlatFromParams(wasmParams, this.characterHandle ?? void 0);
|
|
@@ -16574,6 +16835,13 @@ class AvatarController {
|
|
|
16574
16835
|
const hasAnimationData = this.currentKeyframes.length > 0;
|
|
16575
16836
|
const hasAudioData = ((_b = streamingPlayer.audioChunks) == null ? void 0 : _b.length) > 0;
|
|
16576
16837
|
const isNotPaused = this.currentState !== AvatarState.paused;
|
|
16838
|
+
if (this.isAudioStalledForStarvation) {
|
|
16839
|
+
state.audioTimeZeroCount = 0;
|
|
16840
|
+
state.audioTimeStuckCount = 0;
|
|
16841
|
+
state.lastAudioTime = 0;
|
|
16842
|
+
state.reported = false;
|
|
16843
|
+
return false;
|
|
16844
|
+
}
|
|
16577
16845
|
if (!hasAnimationData || !hasAudioData || this.currentState === AvatarState.paused) {
|
|
16578
16846
|
state.audioTimeZeroCount = 0;
|
|
16579
16847
|
state.audioTimeStuckCount = 0;
|
|
@@ -16677,13 +16945,49 @@ class AvatarController {
|
|
|
16677
16945
|
this.frameStarvationEvents.push({ audioTime, reqEnd: this.reqEnd });
|
|
16678
16946
|
}
|
|
16679
16947
|
}
|
|
16948
|
+
if (!this.animationEnded) {
|
|
16949
|
+
this.pauseAudioForStarvation();
|
|
16950
|
+
}
|
|
16680
16951
|
} else {
|
|
16681
16952
|
this.isFrameStarved = false;
|
|
16682
16953
|
if (isOutOfBounds !== this.lastOutOfBoundsState) {
|
|
16683
16954
|
this.lastOutOfBoundsState = isOutOfBounds;
|
|
16684
16955
|
}
|
|
16956
|
+
this.resumeAudioFromStarvation();
|
|
16685
16957
|
}
|
|
16686
16958
|
}
|
|
16959
|
+
/**
|
|
16960
|
+
* strictSync: pause audio on frame starvation, waiting for new frames.
|
|
16961
|
+
*
|
|
16962
|
+
* The default (audioIndependent) keeps audio running and lets animation catch up;
|
|
16963
|
+
* strictSync instead pauses audio so audio/video stay strictly in sync. Idempotent;
|
|
16964
|
+
* only fires onPlaybackStall once. No-op unless mode is strictSync.
|
|
16965
|
+
* @internal
|
|
16966
|
+
*/
|
|
16967
|
+
pauseAudioForStarvation() {
|
|
16968
|
+
var _a, _b;
|
|
16969
|
+
if (this.frameStarvationMode !== FrameStarvationMode.strictSync) return;
|
|
16970
|
+
if (this.isAudioStalledForStarvation) return;
|
|
16971
|
+
if (!this.isPlaying) return;
|
|
16972
|
+
(_a = this.animationPlayer) == null ? void 0 : _a.pause();
|
|
16973
|
+
this.isAudioStalledForStarvation = true;
|
|
16974
|
+
logger.log("[AvatarController] Frame starvation: paused audio, waiting for frames.");
|
|
16975
|
+
(_b = this.onPlaybackStall) == null ? void 0 : _b.call(this, true);
|
|
16976
|
+
}
|
|
16977
|
+
/**
|
|
16978
|
+
* strictSync: frame starvation resolved (new frames arrived). Resumes audio and fires
|
|
16979
|
+
* onPlaybackStall(false). Idempotent — returns immediately if not stalled (so it is a
|
|
16980
|
+
* no-op in audioIndependent, where isAudioStalledForStarvation stays false).
|
|
16981
|
+
* @internal
|
|
16982
|
+
*/
|
|
16983
|
+
resumeAudioFromStarvation() {
|
|
16984
|
+
var _a, _b;
|
|
16985
|
+
if (!this.isAudioStalledForStarvation) return;
|
|
16986
|
+
this.isAudioStalledForStarvation = false;
|
|
16987
|
+
void ((_a = this.animationPlayer) == null ? void 0 : _a.resume());
|
|
16988
|
+
logger.log("[AvatarController] Frame starvation recovered: resumed audio.");
|
|
16989
|
+
(_b = this.onPlaybackStall) == null ? void 0 : _b.call(this, false);
|
|
16990
|
+
}
|
|
16687
16991
|
/**
|
|
16688
16992
|
* Start playback loop
|
|
16689
16993
|
* @internal
|
|
@@ -16729,6 +17033,7 @@ class AvatarController {
|
|
|
16729
17033
|
}
|
|
16730
17034
|
logger.warn("[AvatarController] Enabling fallback mode");
|
|
16731
17035
|
this.isFallbackMode = true;
|
|
17036
|
+
this.resumeAudioFromStarvation();
|
|
16732
17037
|
logEvent("fallback_mode_entered", "warning", {
|
|
16733
17038
|
avatar_id: this.avatar.id,
|
|
16734
17039
|
reason,
|
|
@@ -16852,97 +17157,6 @@ class AvatarController {
|
|
|
16852
17157
|
listeners.forEach((callback) => callback(data));
|
|
16853
17158
|
}
|
|
16854
17159
|
}
|
|
16855
|
-
/**
|
|
16856
|
-
* Apply post-processing parameters to a Flame (proto format)
|
|
16857
|
-
* Used for transition animations
|
|
16858
|
-
* @internal
|
|
16859
|
-
*/
|
|
16860
|
-
applyPostProcessingToFlame(flame) {
|
|
16861
|
-
if (!this.postProcessingConfig) {
|
|
16862
|
-
return flame;
|
|
16863
|
-
}
|
|
16864
|
-
let wasmParams = convertProtoFlameToWasmParams(flame);
|
|
16865
|
-
wasmParams = this.applyPostProcessingToParams(wasmParams);
|
|
16866
|
-
return convertWasmParamsToProtoFlame(wasmParams);
|
|
16867
|
-
}
|
|
16868
|
-
/**
|
|
16869
|
-
* Apply post-processing parameters to animation parameters
|
|
16870
|
-
* @internal
|
|
16871
|
-
*/
|
|
16872
|
-
/** @internal */
|
|
16873
|
-
applyPostProcessingToParams(baseParams) {
|
|
16874
|
-
if (!this.postProcessingConfig) {
|
|
16875
|
-
return baseParams;
|
|
16876
|
-
}
|
|
16877
|
-
const result2 = {
|
|
16878
|
-
shape_params: baseParams.shape_params || Array.from({ length: 300 }, () => 0),
|
|
16879
|
-
expr_params: baseParams.expr_params ? [...baseParams.expr_params] : Array.from({ length: 100 }, () => 0),
|
|
16880
|
-
rotation: baseParams.rotation ? [...baseParams.rotation] : [0, 0, 0],
|
|
16881
|
-
translation: baseParams.translation ? [...baseParams.translation] : [0, 0, 0],
|
|
16882
|
-
neck_pose: baseParams.neck_pose ? [...baseParams.neck_pose] : [0, 0, 0],
|
|
16883
|
-
jaw_pose: baseParams.jaw_pose ? [...baseParams.jaw_pose] : [0, 0, 0],
|
|
16884
|
-
eyes_pose: baseParams.eyes_pose ? [...baseParams.eyes_pose] : [0, 0, 0, 0, 0, 0],
|
|
16885
|
-
eyelid: baseParams.eyelid ? [...baseParams.eyelid] : [0, 0],
|
|
16886
|
-
has_eyelid: baseParams.has_eyelid || false
|
|
16887
|
-
};
|
|
16888
|
-
if (this.postProcessingConfig.rotation && result2.rotation) {
|
|
16889
|
-
result2.rotation[0] += this.postProcessingConfig.rotation.x ?? 0;
|
|
16890
|
-
result2.rotation[1] += this.postProcessingConfig.rotation.y ?? 0;
|
|
16891
|
-
result2.rotation[2] += this.postProcessingConfig.rotation.z ?? 0;
|
|
16892
|
-
}
|
|
16893
|
-
if (this.postProcessingConfig.translation && result2.translation) {
|
|
16894
|
-
result2.translation[0] += this.postProcessingConfig.translation.x ?? 0;
|
|
16895
|
-
result2.translation[1] += this.postProcessingConfig.translation.y ?? 0;
|
|
16896
|
-
result2.translation[2] += this.postProcessingConfig.translation.z ?? 0;
|
|
16897
|
-
}
|
|
16898
|
-
if (this.postProcessingConfig.neckPose && result2.neck_pose) {
|
|
16899
|
-
const biasX = this.postProcessingConfig.neckPose.x ?? 0;
|
|
16900
|
-
const biasY = this.postProcessingConfig.neckPose.y ?? 0;
|
|
16901
|
-
const biasZ = this.postProcessingConfig.neckPose.z ?? 0;
|
|
16902
|
-
const weightX = this.postProcessingConfig.neckPose.weightX ?? 1;
|
|
16903
|
-
const weightY = this.postProcessingConfig.neckPose.weightY ?? 1;
|
|
16904
|
-
const weightZ = this.postProcessingConfig.neckPose.weightZ ?? 1;
|
|
16905
|
-
result2.neck_pose[0] = result2.neck_pose[0] * weightX + biasX;
|
|
16906
|
-
result2.neck_pose[1] = result2.neck_pose[1] * weightY + biasY;
|
|
16907
|
-
result2.neck_pose[2] = result2.neck_pose[2] * weightZ + biasZ;
|
|
16908
|
-
}
|
|
16909
|
-
if (this.postProcessingConfig.jawPose && result2.jaw_pose) {
|
|
16910
|
-
const biasX = this.postProcessingConfig.jawPose.x ?? 0;
|
|
16911
|
-
const biasY = this.postProcessingConfig.jawPose.y ?? 0;
|
|
16912
|
-
const biasZ = this.postProcessingConfig.jawPose.z ?? 0;
|
|
16913
|
-
const weight = this.postProcessingConfig.jawPose.weight ?? 1;
|
|
16914
|
-
result2.jaw_pose[0] = result2.jaw_pose[0] * weight + biasX;
|
|
16915
|
-
result2.jaw_pose[1] = result2.jaw_pose[1] * weight + biasY;
|
|
16916
|
-
result2.jaw_pose[2] = result2.jaw_pose[2] * weight + biasZ;
|
|
16917
|
-
}
|
|
16918
|
-
if (this.postProcessingConfig.eyePose && result2.eyes_pose) {
|
|
16919
|
-
const biasX = this.postProcessingConfig.eyePose.x ?? 0;
|
|
16920
|
-
const biasY = this.postProcessingConfig.eyePose.y ?? 0;
|
|
16921
|
-
const biasZ = this.postProcessingConfig.eyePose.z ?? 0;
|
|
16922
|
-
const weightX = this.postProcessingConfig.eyePose.weightX ?? 1;
|
|
16923
|
-
const weightY = this.postProcessingConfig.eyePose.weightY ?? 1;
|
|
16924
|
-
const weightZ = this.postProcessingConfig.eyePose.weightZ ?? 1;
|
|
16925
|
-
result2.eyes_pose[0] = result2.eyes_pose[0] * weightX + biasX;
|
|
16926
|
-
result2.eyes_pose[1] = result2.eyes_pose[1] * weightY + biasY;
|
|
16927
|
-
result2.eyes_pose[2] = result2.eyes_pose[2] * weightZ + biasZ;
|
|
16928
|
-
result2.eyes_pose[3] = result2.eyes_pose[3] * weightX + biasX;
|
|
16929
|
-
result2.eyes_pose[4] = result2.eyes_pose[4] * weightY + biasY;
|
|
16930
|
-
result2.eyes_pose[5] = result2.eyes_pose[5] * weightZ + biasZ;
|
|
16931
|
-
}
|
|
16932
|
-
if (this.postProcessingConfig.eyeBlink !== void 0 && result2.eyelid) {
|
|
16933
|
-
const blinkValue = this.postProcessingConfig.eyeBlink;
|
|
16934
|
-
result2.eyelid[0] = blinkValue;
|
|
16935
|
-
result2.eyelid[1] = blinkValue;
|
|
16936
|
-
result2.has_eyelid = true;
|
|
16937
|
-
}
|
|
16938
|
-
if (this.postProcessingConfig.expressionWeight !== void 0 && result2.expr_params) {
|
|
16939
|
-
const weight = this.postProcessingConfig.expressionWeight;
|
|
16940
|
-
for (let i2 = 0; i2 < result2.expr_params.length; i2++) {
|
|
16941
|
-
result2.expr_params[i2] *= weight;
|
|
16942
|
-
}
|
|
16943
|
-
}
|
|
16944
|
-
return result2;
|
|
16945
|
-
}
|
|
16946
17160
|
}
|
|
16947
17161
|
function errorToMessage(err) {
|
|
16948
17162
|
if (err instanceof Error) {
|
|
@@ -17253,9 +17467,12 @@ class AvatarDownloader {
|
|
|
17253
17467
|
* @internal
|
|
17254
17468
|
*/
|
|
17255
17469
|
async loadUnifiedTemplate() {
|
|
17470
|
+
var _a;
|
|
17256
17471
|
await PwaCacheManager.checkTemplateCacheVersion();
|
|
17257
17472
|
const startTime = Date.now();
|
|
17258
|
-
const
|
|
17473
|
+
const region = ((_a = AvatarSDK.configuration) == null ? void 0 : _a.region) || DEFAULT_REGION;
|
|
17474
|
+
const cdnBase = getFlameCdnBase(region);
|
|
17475
|
+
const { unifiedModelPath } = APP_CONFIG.flame;
|
|
17259
17476
|
const url = `${cdnBase}/${unifiedModelPath}`;
|
|
17260
17477
|
logger.log(`📥 Loading unified template from: ${url}`);
|
|
17261
17478
|
const cached = await PwaCacheManager.getTemplateResource(url);
|
|
@@ -17526,7 +17743,7 @@ class AvatarDownloader {
|
|
|
17526
17743
|
}
|
|
17527
17744
|
let error;
|
|
17528
17745
|
if (response.status === 404) {
|
|
17529
|
-
const urlMatch = url.match(/\/v2\/character\/([^/?]+)/);
|
|
17746
|
+
const urlMatch = url.match(/\/v2\/(?:character|avatar)\/([^/?]+)/);
|
|
17530
17747
|
const extractedCharacterId = urlMatch ? urlMatch[1] : "unknown";
|
|
17531
17748
|
const callerHeaders = options.headers || {};
|
|
17532
17749
|
const callerTraceId = callerHeaders["x-sp-trace-id"];
|
|
@@ -17562,10 +17779,50 @@ class AvatarDownloader {
|
|
|
17562
17779
|
};
|
|
17563
17780
|
}
|
|
17564
17781
|
/**
|
|
17565
|
-
*
|
|
17782
|
+
* Map the new `/v2/avatar/{id}` `AvatarAsset` payload onto the internal
|
|
17783
|
+
* `CharacterMeta` shape used by the existing download / render pipeline.
|
|
17784
|
+
*
|
|
17785
|
+
* The backend (grpc-gateway) serialises proto fields as camelCase JSON, so the
|
|
17786
|
+
* runtime object is loosely shaped like the generated `AvatarAsset`. We:
|
|
17787
|
+
* - lift `models.gs` into `models.gsStandard` (downloader/asset-count read gsStandard)
|
|
17788
|
+
* - rename `animations.frameFallback` → `animations.frameMono`
|
|
17789
|
+
* - fold the inline `camera` / `transform` into `characterSettings` so the
|
|
17790
|
+
* renderer's `resolveCameraConfig` reads structured values and no camera
|
|
17791
|
+
* resource is downloaded (top-level `camera` is intentionally left unset)
|
|
17792
|
+
* @internal
|
|
17793
|
+
*/
|
|
17794
|
+
mapAvatarAssetToCharacterMeta(asset, avatarId) {
|
|
17795
|
+
var _a, _b, _c, _d, _e2;
|
|
17796
|
+
const characterSettings = {
|
|
17797
|
+
...asset.camera ? { camera: { ...asset.camera } } : {},
|
|
17798
|
+
...asset.transform ? { transform: { ...asset.transform } } : {}
|
|
17799
|
+
};
|
|
17800
|
+
return {
|
|
17801
|
+
characterId: avatarId,
|
|
17802
|
+
version: asset.version ?? "",
|
|
17803
|
+
updatedAt: asset.updatedAt,
|
|
17804
|
+
models: {
|
|
17805
|
+
shape: (_a = asset.models) == null ? void 0 : _a.shape,
|
|
17806
|
+
gsStandard: (_b = asset.models) == null ? void 0 : _b.gs
|
|
17807
|
+
},
|
|
17808
|
+
animations: {
|
|
17809
|
+
frameIdle: (_c = asset.animations) == null ? void 0 : _c.frameIdle,
|
|
17810
|
+
frameMono: (_d = asset.animations) == null ? void 0 : _d.frameFallback
|
|
17811
|
+
},
|
|
17812
|
+
customAnimations: ((_e2 = asset.animations) == null ? void 0 : _e2.customAnimations) ?? [],
|
|
17813
|
+
characterSettings
|
|
17814
|
+
};
|
|
17815
|
+
}
|
|
17816
|
+
/**
|
|
17817
|
+
* Get single avatar by ID from AvatarKit SDK API (v2 driven-ingress avatar API).
|
|
17566
17818
|
* Domain: composed from region as api.${region}.spatius.ai
|
|
17567
17819
|
* Auth: Public endpoint, no authentication required
|
|
17568
|
-
*
|
|
17820
|
+
* Fetches the new `AvatarAsset` payload from `/v2/avatar/{id}` and maps it onto
|
|
17821
|
+
* the internal `CharacterMeta` shape consumed by the download / render pipeline:
|
|
17822
|
+
* - `models.gs` → `models.gsStandard`
|
|
17823
|
+
* - `animations.frameFallback` → `animations.frameMono`
|
|
17824
|
+
* - inline `camera` / `transform` → `characterSettings.{camera,transform}`
|
|
17825
|
+
* (so the renderer reads structured values directly and no camera resource is downloaded)
|
|
17569
17826
|
* @internal
|
|
17570
17827
|
*/
|
|
17571
17828
|
async getCharacterById(characterId, options) {
|
|
@@ -17578,7 +17835,7 @@ class AvatarDownloader {
|
|
|
17578
17835
|
throw new Error("Request cancelled");
|
|
17579
17836
|
}
|
|
17580
17837
|
const client = this.getSdkApiClient();
|
|
17581
|
-
const response = await client.request(`/v2/
|
|
17838
|
+
const response = await client.request(`/v2/avatar/${characterId}`, {
|
|
17582
17839
|
method: "GET",
|
|
17583
17840
|
headers: { "x-sp-trace-id": traceId },
|
|
17584
17841
|
signal
|
|
@@ -17597,7 +17854,7 @@ class AvatarDownloader {
|
|
|
17597
17854
|
duration,
|
|
17598
17855
|
trace_id: traceId
|
|
17599
17856
|
});
|
|
17600
|
-
return response;
|
|
17857
|
+
return this.mapAvatarAssetToCharacterMeta(response, characterId);
|
|
17601
17858
|
} catch (error) {
|
|
17602
17859
|
if (error instanceof Error && (error.name === "AbortError" || error.message === "Request cancelled")) {
|
|
17603
17860
|
logEvent("fetch_avatar_metadata_cancelled", "info", {
|
|
@@ -20519,8 +20776,7 @@ class AvatarView {
|
|
|
20519
20776
|
if (arrayIndex >= 0 && arrayIndex < keyframes.length) {
|
|
20520
20777
|
const currentFrame = keyframes[arrayIndex];
|
|
20521
20778
|
this.currentFrame = currentFrame;
|
|
20522
|
-
|
|
20523
|
-
wasmParams = this.avatarController.applyPostProcessingToParams(wasmParams);
|
|
20779
|
+
const wasmParams = convertProtoFlameToWasmParams(currentFrame);
|
|
20524
20780
|
computeInFlight = true;
|
|
20525
20781
|
avatarCore.computeFrameFlatFromParams(wasmParams, this.characterHandle ?? void 0).then((splatData) => {
|
|
20526
20782
|
if (splatData && !this.isPureRenderingMode) {
|
|
@@ -20547,7 +20803,7 @@ class AvatarView {
|
|
|
20547
20803
|
const offset = this.avatarController.getKeyframesOffset();
|
|
20548
20804
|
const targetArrayIdx = targetIdx - offset;
|
|
20549
20805
|
if (targetArrayIdx >= 0 && targetArrayIdx < keyframes.length) {
|
|
20550
|
-
const targetFrame =
|
|
20806
|
+
const targetFrame = keyframes[targetArrayIdx];
|
|
20551
20807
|
this.transitionFrames = this.generateAndAlignTransitionFrames(
|
|
20552
20808
|
this.currentFrame,
|
|
20553
20809
|
targetFrame,
|
|
@@ -20793,6 +21049,10 @@ class AvatarView {
|
|
|
20793
21049
|
},
|
|
20794
21050
|
onPlayFallback: () => {
|
|
20795
21051
|
this.playAnimation(AnimationType.idle, true);
|
|
21052
|
+
if (!this.cachedIdleFirstFrame) {
|
|
21053
|
+
this.getCachedIdleFirstFrame().catch(() => {
|
|
21054
|
+
});
|
|
21055
|
+
}
|
|
20796
21056
|
this.isConversationActive = true;
|
|
20797
21057
|
this.lastRenderedFrameIndex = -1;
|
|
20798
21058
|
},
|
|
@@ -20819,10 +21079,9 @@ class AvatarView {
|
|
|
20819
21079
|
const fromFrame = this.currentFrame;
|
|
20820
21080
|
if (fromFrame) {
|
|
20821
21081
|
const firstSpeaking = keyframes[0];
|
|
20822
|
-
const firstSpeakingWithPP = this.avatarController.applyPostProcessingToFlame(firstSpeaking);
|
|
20823
21082
|
this.transitionFrames = this.generateAndAlignTransitionFrames(
|
|
20824
21083
|
fromFrame,
|
|
20825
|
-
|
|
21084
|
+
firstSpeaking,
|
|
20826
21085
|
START_TRANSITION_DURATION_MS,
|
|
20827
21086
|
true
|
|
20828
21087
|
);
|
|
@@ -20854,9 +21113,8 @@ class AvatarView {
|
|
|
20854
21113
|
const fromFrame = this.currentFrame;
|
|
20855
21114
|
if (fromFrame && this.cachedIdleFirstFrame) {
|
|
20856
21115
|
try {
|
|
20857
|
-
const fromFrameWithPP = this.avatarController.applyPostProcessingToFlame(fromFrame);
|
|
20858
21116
|
this.transitionFrames = this.generateAndAlignTransitionFrames(
|
|
20859
|
-
|
|
21117
|
+
fromFrame,
|
|
20860
21118
|
this.cachedIdleFirstFrame,
|
|
20861
21119
|
END_TRANSITION_DURATION_MS,
|
|
20862
21120
|
false
|
|
@@ -20974,8 +21232,7 @@ class AvatarView {
|
|
|
20974
21232
|
try {
|
|
20975
21233
|
const flame = keyframeData;
|
|
20976
21234
|
this.currentFrame = flame;
|
|
20977
|
-
const
|
|
20978
|
-
const wasmParams = convertProtoFlameToWasmParams(processedFlame);
|
|
21235
|
+
const wasmParams = convertProtoFlameToWasmParams(flame);
|
|
20979
21236
|
const avatarCore = AvatarSDK.getAvatarCore();
|
|
20980
21237
|
if (!avatarCore) {
|
|
20981
21238
|
throw new Error("AvatarCore not available");
|
|
@@ -21017,7 +21274,7 @@ class AvatarView {
|
|
|
21017
21274
|
}
|
|
21018
21275
|
let toFrame;
|
|
21019
21276
|
if (to2) {
|
|
21020
|
-
toFrame =
|
|
21277
|
+
toFrame = to2;
|
|
21021
21278
|
} else {
|
|
21022
21279
|
const idleParams = await avatarCore.getCurrentFrameParams(this.activeAnimationState.frameIndex, this.characterId);
|
|
21023
21280
|
toFrame = convertWasmParamsToProtoFlame(idleParams);
|
|
@@ -21465,6 +21722,7 @@ export {
|
|
|
21465
21722
|
ConnectionState as C,
|
|
21466
21723
|
DrivingServiceMode as D,
|
|
21467
21724
|
ErrorCode as E,
|
|
21725
|
+
FrameStarvationMode as F,
|
|
21468
21726
|
LogLevel as L,
|
|
21469
21727
|
RenderQuality as R,
|
|
21470
21728
|
TransitionType as T,
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { k, b, c, o, f, d, n, g, C, m, i, D, E, j, L, h, R, p, T, q } from "./index-
|
|
1
|
+
import { k, b, c, o, f, d, n, g, C, m, i, D, E, F, j, L, h, R, p, T, q } from "./index-BnubRaUR.js";
|
|
2
2
|
export {
|
|
3
3
|
k as AnimationType,
|
|
4
4
|
b as Avatar,
|
|
@@ -13,6 +13,7 @@ export {
|
|
|
13
13
|
i as DEFAULT_REGION,
|
|
14
14
|
D as DrivingServiceMode,
|
|
15
15
|
E as ErrorCode,
|
|
16
|
+
F as FrameStarvationMode,
|
|
16
17
|
j as LoadProgress,
|
|
17
18
|
L as LogLevel,
|
|
18
19
|
h as RENDER_QUALITY_PARAMS,
|
package/dist/types/index.d.ts
CHANGED
|
@@ -7,6 +7,19 @@ export declare enum DrivingServiceMode {
|
|
|
7
7
|
/** Driven by host application */
|
|
8
8
|
backend = "backend"
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Strategy for handling animation-frame starvation (animation frames can't keep up
|
|
12
|
+
* with the audio clock).
|
|
13
|
+
*
|
|
14
|
+
* - `audioIndependent` (default): audio keeps playing, animation catches up; starvation
|
|
15
|
+
* is only reported as telemetry. This is the historical default behavior.
|
|
16
|
+
* - `strictSync`: pause audio and wait when frames run out, resume once new frames
|
|
17
|
+
* arrive, notifying via `AvatarController.onPlaybackStall`.
|
|
18
|
+
*/
|
|
19
|
+
export declare enum FrameStarvationMode {
|
|
20
|
+
audioIndependent = "audioIndependent",
|
|
21
|
+
strictSync = "strictSync"
|
|
22
|
+
}
|
|
10
23
|
export declare enum LogLevel {
|
|
11
24
|
/** Disable all logs */
|
|
12
25
|
off = "off",
|
|
@@ -134,53 +147,6 @@ export interface CameraConfig {
|
|
|
134
147
|
up?: [number, number, number];
|
|
135
148
|
aspect?: number;
|
|
136
149
|
}
|
|
137
|
-
/**
|
|
138
|
-
* Post-processing parameter configuration
|
|
139
|
-
* Used to overlay in real-time on animation parameters returned by the server
|
|
140
|
-
*/
|
|
141
|
-
export interface PostProcessingConfig {
|
|
142
|
-
/** Rotation (Euler angles, in radians) */
|
|
143
|
-
rotation?: {
|
|
144
|
-
x?: number;
|
|
145
|
-
y?: number;
|
|
146
|
-
z?: number;
|
|
147
|
-
};
|
|
148
|
-
/** Translation (position offset) */
|
|
149
|
-
translation?: {
|
|
150
|
-
x?: number;
|
|
151
|
-
y?: number;
|
|
152
|
-
z?: number;
|
|
153
|
-
};
|
|
154
|
-
/** Neck pose offset (in radians) */
|
|
155
|
-
neckPose?: {
|
|
156
|
-
x?: number;
|
|
157
|
-
y?: number;
|
|
158
|
-
z?: number;
|
|
159
|
-
weightX?: number;
|
|
160
|
-
weightY?: number;
|
|
161
|
-
weightZ?: number;
|
|
162
|
-
};
|
|
163
|
-
/** Jaw pose offset (in radians) */
|
|
164
|
-
jawPose?: {
|
|
165
|
-
x?: number;
|
|
166
|
-
y?: number;
|
|
167
|
-
z?: number;
|
|
168
|
-
weight?: number;
|
|
169
|
-
};
|
|
170
|
-
/** Eye pose offset (in radians) */
|
|
171
|
-
eyePose?: {
|
|
172
|
-
x?: number;
|
|
173
|
-
y?: number;
|
|
174
|
-
z?: number;
|
|
175
|
-
weightX?: number;
|
|
176
|
-
weightY?: number;
|
|
177
|
-
weightZ?: number;
|
|
178
|
-
};
|
|
179
|
-
/** Eye blink value (-1.0 to 1.0, larger value means more closed) */
|
|
180
|
-
eyeBlink?: number;
|
|
181
|
-
/** Expression weight (0.0-2.0, 0=no expression, 1=normal, >1=enhanced) */
|
|
182
|
-
expressionWeight?: number;
|
|
183
|
-
}
|
|
184
150
|
export interface CharacterInfo {
|
|
185
151
|
pointCount: number;
|
|
186
152
|
hasAnimation: boolean;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spatius/avatarkit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.0-beta.2",
|
|
5
5
|
"packageManager": "pnpm@10.18.2",
|
|
6
6
|
"description": "AvatarKit SDK - 3D Gaussian Splatting Avatar Rendering SDK",
|
|
7
7
|
"author": "AvatarKit Team",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"build": "SDK_BUILD=true vite build --mode library && npm run build:vite-plugin && npm run build:next-plugin",
|
|
49
49
|
"build:vite-plugin": "tsc vite.ts --outDir . --module esnext --target es2020 --moduleResolution bundler --esModuleInterop --skipLibCheck --declaration --declarationMap",
|
|
50
50
|
"build:next-plugin": "tsc next.ts --outDir . --module esnext --target es2020 --moduleResolution bundler --esModuleInterop --skipLibCheck --declaration --declarationMap",
|
|
51
|
+
"prepare": "npm run build",
|
|
51
52
|
"dev": "vite build --mode library --watch",
|
|
52
53
|
"demo": "vite --config demo/vite.config.mjs",
|
|
53
54
|
"demo:benchmark": "vite --config benchmark-demo/vite.config.mjs",
|