@spatialwalk/avatarkit 1.0.0-beta.67 → 1.0.0-beta.69

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +33 -11
  2. package/README.md +102 -18
  3. package/dist/{StreamingAudioPlayer-CD9jBs6B.js → StreamingAudioPlayer-DiIRp5nx.js} +109 -1
  4. package/dist/animation/AnimationWebSocketClient.d.ts +26 -0
  5. package/dist/animation/utils/eventEmitter.d.ts +3 -0
  6. package/dist/animation/utils/flameConverter.d.ts +10 -3
  7. package/dist/audio/AnimationPlayer.d.ts +46 -0
  8. package/dist/audio/StreamingAudioPlayer.d.ts +93 -0
  9. package/dist/config/app-config.d.ts +5 -1
  10. package/dist/config/constants.d.ts +7 -1
  11. package/dist/config/sdk-config-loader.d.ts +11 -3
  12. package/dist/core/Avatar.d.ts +10 -0
  13. package/dist/core/AvatarController.d.ts +164 -2
  14. package/dist/core/AvatarDownloader.d.ts +10 -0
  15. package/dist/core/AvatarManager.d.ts +27 -1
  16. package/dist/core/AvatarSDK.d.ts +27 -0
  17. package/dist/core/AvatarView.d.ts +148 -3
  18. package/dist/core/NetworkLayer.d.ts +6 -0
  19. package/dist/generated/common/v1/models.d.ts +8 -1
  20. package/dist/generated/driveningress/v1/driveningress.d.ts +11 -1
  21. package/dist/generated/driveningress/v2/driveningress.d.ts +5 -2
  22. package/dist/generated/google/protobuf/struct.d.ts +38 -5
  23. package/dist/generated/google/protobuf/timestamp.d.ts +102 -1
  24. package/dist/{index-GRm00rtd.js → index-BT9yxWW8.js} +1468 -30
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.js +1 -1
  27. package/dist/renderer/RenderSystem.d.ts +8 -0
  28. package/dist/renderer/covariance.d.ts +11 -0
  29. package/dist/renderer/sortSplats.d.ts +10 -0
  30. package/dist/renderer/webgl/reorderData.d.ts +12 -0
  31. package/dist/renderer/webgl/webglRenderer.d.ts +53 -0
  32. package/dist/renderer/webgpu/webgpuRenderer.d.ts +38 -0
  33. package/dist/types/character-settings.d.ts +4 -0
  34. package/dist/types/character.d.ts +9 -3
  35. package/dist/types/index.d.ts +56 -23
  36. package/dist/utils/animation-interpolation.d.ts +30 -5
  37. package/dist/utils/client-id.d.ts +5 -0
  38. package/dist/utils/conversationId.d.ts +18 -0
  39. package/dist/utils/error-utils.d.ts +24 -1
  40. package/dist/utils/id-manager.d.ts +26 -0
  41. package/dist/utils/logger.d.ts +4 -1
  42. package/dist/utils/posthog-tracker.d.ts +27 -5
  43. package/dist/utils/pwa-cache-manager.d.ts +36 -0
  44. package/dist/utils/usage-tracker.d.ts +17 -2
  45. package/dist/vite.d.ts +16 -1
  46. package/dist/wasm/avatarCoreAdapter.d.ts +145 -0
  47. package/dist/wasm/avatarCoreMemory.d.ts +52 -0
  48. package/package.json +3 -3
  49. package/vite.js +45 -29
@@ -2,6 +2,12 @@ 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
4
  class Avatar {
5
+ /**
6
+ * 构造函数(内部使用)
7
+ * @param id 数字人 ID
8
+ * @param characterMeta 角色元数据
9
+ * @param resources 资源数据
10
+ */
5
11
  constructor(id, characterMeta, resources) {
6
12
  __publicField(this, "id");
7
13
  __publicField(this, "characterMeta");
@@ -10,15 +16,27 @@ class Avatar {
10
16
  this.characterMeta = characterMeta;
11
17
  this.resources = resources;
12
18
  }
19
+ /**
20
+ * Get character metadata
21
+ * @returns Character metadata, including all configuration information (version, resource URLs, camera config, etc.)
22
+ */
13
23
  getCharacterMeta() {
14
24
  return this.characterMeta;
15
25
  }
26
+ /**
27
+ * 更新角色元数据(内部使用)
28
+ * @internal
29
+ */
16
30
  updateCharacterMeta(newMeta) {
17
31
  this.characterMeta = newMeta;
18
32
  if (this.resources.characterSettings !== newMeta.characterSettings) {
19
33
  this.resources.characterSettings = newMeta.characterSettings;
20
34
  }
21
35
  }
36
+ /**
37
+ * 获取资源数据(内部使用)
38
+ * @internal
39
+ */
22
40
  getResources() {
23
41
  return this.resources;
24
42
  }
@@ -346,6 +364,9 @@ class BinaryWriter {
346
364
  this.chunks = [];
347
365
  this.buf = [];
348
366
  }
367
+ /**
368
+ * Return all bytes written and reset this writer.
369
+ */
349
370
  finish() {
350
371
  if (this.buf.length) {
351
372
  this.chunks.push(new Uint8Array(this.buf));
@@ -363,12 +384,22 @@ class BinaryWriter {
363
384
  this.chunks = [];
364
385
  return bytes;
365
386
  }
387
+ /**
388
+ * Start a new fork for length-delimited data like a message
389
+ * or a packed repeated field.
390
+ *
391
+ * Must be joined later with `join()`.
392
+ */
366
393
  fork() {
367
394
  this.stack.push({ chunks: this.chunks, buf: this.buf });
368
395
  this.chunks = [];
369
396
  this.buf = [];
370
397
  return this;
371
398
  }
399
+ /**
400
+ * Join the last fork. Write its length and bytes, then
401
+ * return to the previous state.
402
+ */
372
403
  join() {
373
404
  let chunk = this.finish();
374
405
  let prev = this.stack.pop();
@@ -379,9 +410,19 @@ class BinaryWriter {
379
410
  this.uint32(chunk.byteLength);
380
411
  return this.raw(chunk);
381
412
  }
413
+ /**
414
+ * Writes a tag (field number and wire type).
415
+ *
416
+ * Equivalent to `uint32( (fieldNo << 3 | type) >>> 0 )`.
417
+ *
418
+ * Generated code should compute the tag ahead of time and call `uint32()`.
419
+ */
382
420
  tag(fieldNo, type) {
383
421
  return this.uint32((fieldNo << 3 | type) >>> 0);
384
422
  }
423
+ /**
424
+ * Write a chunk of raw bytes.
425
+ */
385
426
  raw(chunk) {
386
427
  if (this.buf.length) {
387
428
  this.chunks.push(new Uint8Array(this.buf));
@@ -390,6 +431,9 @@ class BinaryWriter {
390
431
  this.chunks.push(chunk);
391
432
  return this;
392
433
  }
434
+ /**
435
+ * Write a `uint32` value, an unsigned 32 bit varint.
436
+ */
393
437
  uint32(value) {
394
438
  assertUInt32(value);
395
439
  while (value > 127) {
@@ -399,75 +443,117 @@ class BinaryWriter {
399
443
  this.buf.push(value);
400
444
  return this;
401
445
  }
446
+ /**
447
+ * Write a `int32` value, a signed 32 bit varint.
448
+ */
402
449
  int32(value) {
403
450
  assertInt32(value);
404
451
  varint32write(value, this.buf);
405
452
  return this;
406
453
  }
454
+ /**
455
+ * Write a `bool` value, a variant.
456
+ */
407
457
  bool(value) {
408
458
  this.buf.push(value ? 1 : 0);
409
459
  return this;
410
460
  }
461
+ /**
462
+ * Write a `bytes` value, length-delimited arbitrary data.
463
+ */
411
464
  bytes(value) {
412
465
  this.uint32(value.byteLength);
413
466
  return this.raw(value);
414
467
  }
468
+ /**
469
+ * Write a `string` value, length-delimited data converted to UTF-8 text.
470
+ */
415
471
  string(value) {
416
472
  let chunk = this.encodeUtf8(value);
417
473
  this.uint32(chunk.byteLength);
418
474
  return this.raw(chunk);
419
475
  }
476
+ /**
477
+ * Write a `float` value, 32-bit floating point number.
478
+ */
420
479
  float(value) {
421
480
  assertFloat32(value);
422
481
  let chunk = new Uint8Array(4);
423
482
  new DataView(chunk.buffer).setFloat32(0, value, true);
424
483
  return this.raw(chunk);
425
484
  }
485
+ /**
486
+ * Write a `double` value, a 64-bit floating point number.
487
+ */
426
488
  double(value) {
427
489
  let chunk = new Uint8Array(8);
428
490
  new DataView(chunk.buffer).setFloat64(0, value, true);
429
491
  return this.raw(chunk);
430
492
  }
493
+ /**
494
+ * Write a `fixed32` value, an unsigned, fixed-length 32-bit integer.
495
+ */
431
496
  fixed32(value) {
432
497
  assertUInt32(value);
433
498
  let chunk = new Uint8Array(4);
434
499
  new DataView(chunk.buffer).setUint32(0, value, true);
435
500
  return this.raw(chunk);
436
501
  }
502
+ /**
503
+ * Write a `sfixed32` value, a signed, fixed-length 32-bit integer.
504
+ */
437
505
  sfixed32(value) {
438
506
  assertInt32(value);
439
507
  let chunk = new Uint8Array(4);
440
508
  new DataView(chunk.buffer).setInt32(0, value, true);
441
509
  return this.raw(chunk);
442
510
  }
511
+ /**
512
+ * Write a `sint32` value, a signed, zigzag-encoded 32-bit varint.
513
+ */
443
514
  sint32(value) {
444
515
  assertInt32(value);
445
516
  value = (value << 1 ^ value >> 31) >>> 0;
446
517
  varint32write(value, this.buf);
447
518
  return this;
448
519
  }
520
+ /**
521
+ * Write a `fixed64` value, a signed, fixed-length 64-bit integer.
522
+ */
449
523
  sfixed64(value) {
450
524
  let chunk = new Uint8Array(8), view = new DataView(chunk.buffer), tc = protoInt64.enc(value);
451
525
  view.setInt32(0, tc.lo, true);
452
526
  view.setInt32(4, tc.hi, true);
453
527
  return this.raw(chunk);
454
528
  }
529
+ /**
530
+ * Write a `fixed64` value, an unsigned, fixed-length 64 bit integer.
531
+ */
455
532
  fixed64(value) {
456
533
  let chunk = new Uint8Array(8), view = new DataView(chunk.buffer), tc = protoInt64.uEnc(value);
457
534
  view.setInt32(0, tc.lo, true);
458
535
  view.setInt32(4, tc.hi, true);
459
536
  return this.raw(chunk);
460
537
  }
538
+ /**
539
+ * Write a `int64` value, a signed 64-bit varint.
540
+ */
461
541
  int64(value) {
462
542
  let tc = protoInt64.enc(value);
463
543
  varint64write(tc.lo, tc.hi, this.buf);
464
544
  return this;
465
545
  }
546
+ /**
547
+ * Write a `sint64` value, a signed, zig-zag-encoded 64-bit varint.
548
+ */
466
549
  sint64(value) {
467
550
  const tc = protoInt64.enc(value), sign = tc.hi >> 31, lo2 = tc.lo << 1 ^ sign, hi2 = (tc.hi << 1 | tc.lo >>> 31) ^ sign;
468
551
  varint64write(lo2, hi2, this.buf);
469
552
  return this;
470
553
  }
554
+ /**
555
+ * Write a `uint64` value, an unsigned 64-bit varint.
556
+ */
471
557
  uint64(value) {
472
558
  const tc = protoInt64.uEnc(value);
473
559
  varint64write(tc.lo, tc.hi, this.buf);
@@ -484,12 +570,21 @@ class BinaryReader {
484
570
  this.pos = 0;
485
571
  this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
486
572
  }
573
+ /**
574
+ * Reads a tag - field number and wire type.
575
+ */
487
576
  tag() {
488
577
  let tag = this.uint32(), fieldNo = tag >>> 3, wireType = tag & 7;
489
578
  if (fieldNo <= 0 || wireType < 0 || wireType > 5)
490
579
  throw new Error("illegal tag: field no " + fieldNo + " wire type " + wireType);
491
580
  return [fieldNo, wireType];
492
581
  }
582
+ /**
583
+ * Skip one element and return the skipped data.
584
+ *
585
+ * When skipping StartGroup, provide the tags field number to check for
586
+ * matching field number in the EndGroup tag.
587
+ */
493
588
  skip(wireType, fieldNo) {
494
589
  let start = this.pos;
495
590
  switch (wireType) {
@@ -524,23 +619,41 @@ class BinaryReader {
524
619
  this.assertBounds();
525
620
  return this.buf.subarray(start, this.pos);
526
621
  }
622
+ /**
623
+ * Throws error if position in byte array is out of range.
624
+ */
527
625
  assertBounds() {
528
626
  if (this.pos > this.len)
529
627
  throw new RangeError("premature EOF");
530
628
  }
629
+ /**
630
+ * Read a `int32` field, a signed 32 bit varint.
631
+ */
531
632
  int32() {
532
633
  return this.uint32() | 0;
533
634
  }
635
+ /**
636
+ * Read a `sint32` field, a signed, zigzag-encoded 32-bit varint.
637
+ */
534
638
  sint32() {
535
639
  let zze = this.uint32();
536
640
  return zze >>> 1 ^ -(zze & 1);
537
641
  }
642
+ /**
643
+ * Read a `int64` field, a signed 64-bit varint.
644
+ */
538
645
  int64() {
539
646
  return protoInt64.dec(...this.varint64());
540
647
  }
648
+ /**
649
+ * Read a `uint64` field, an unsigned 64-bit varint.
650
+ */
541
651
  uint64() {
542
652
  return protoInt64.uDec(...this.varint64());
543
653
  }
654
+ /**
655
+ * Read a `sint64` field, a signed, zig-zag-encoded 64-bit varint.
656
+ */
544
657
  sint64() {
545
658
  let [lo2, hi2] = this.varint64();
546
659
  let s2 = -(lo2 & 1);
@@ -548,34 +661,61 @@ class BinaryReader {
548
661
  hi2 = hi2 >>> 1 ^ s2;
549
662
  return protoInt64.dec(lo2, hi2);
550
663
  }
664
+ /**
665
+ * Read a `bool` field, a variant.
666
+ */
551
667
  bool() {
552
668
  let [lo2, hi2] = this.varint64();
553
669
  return lo2 !== 0 || hi2 !== 0;
554
670
  }
671
+ /**
672
+ * Read a `fixed32` field, an unsigned, fixed-length 32-bit integer.
673
+ */
555
674
  fixed32() {
556
675
  return this.view.getUint32((this.pos += 4) - 4, true);
557
676
  }
677
+ /**
678
+ * Read a `sfixed32` field, a signed, fixed-length 32-bit integer.
679
+ */
558
680
  sfixed32() {
559
681
  return this.view.getInt32((this.pos += 4) - 4, true);
560
682
  }
683
+ /**
684
+ * Read a `fixed64` field, an unsigned, fixed-length 64 bit integer.
685
+ */
561
686
  fixed64() {
562
687
  return protoInt64.uDec(this.sfixed32(), this.sfixed32());
563
688
  }
689
+ /**
690
+ * Read a `fixed64` field, a signed, fixed-length 64-bit integer.
691
+ */
564
692
  sfixed64() {
565
693
  return protoInt64.dec(this.sfixed32(), this.sfixed32());
566
694
  }
695
+ /**
696
+ * Read a `float` field, 32-bit floating point number.
697
+ */
567
698
  float() {
568
699
  return this.view.getFloat32((this.pos += 4) - 4, true);
569
700
  }
701
+ /**
702
+ * Read a `double` field, a 64-bit floating point number.
703
+ */
570
704
  double() {
571
705
  return this.view.getFloat64((this.pos += 8) - 8, true);
572
706
  }
707
+ /**
708
+ * Read a `bytes` field, length-delimited arbitrary data.
709
+ */
573
710
  bytes() {
574
711
  let len = this.uint32(), start = this.pos;
575
712
  this.pos += len;
576
713
  this.assertBounds();
577
714
  return this.buf.subarray(start, start + len);
578
715
  }
716
+ /**
717
+ * Read a `string` field, length-delimited data converted to UTF-8 text.
718
+ */
579
719
  string() {
580
720
  return this.decodeUtf8(this.bytes());
581
721
  }
@@ -1266,6 +1406,7 @@ function getPostHogConfig(_environment) {
1266
1406
  host: POSTHOG_HOST_INTL,
1267
1407
  apiKey: POSTHOG_API_KEY_INTL,
1268
1408
  disableCompression: false
1409
+ // 国外官方 PostHog 支持压缩,保持启用
1269
1410
  };
1270
1411
  }
1271
1412
  function hasDebugParam() {
@@ -1279,6 +1420,7 @@ function isDebugMode() {
1279
1420
  return hasDebugParam();
1280
1421
  }
1281
1422
  const APP_CONFIG = {
1423
+ // Dynamic debug mode check (includes URL parameter)
1282
1424
  get debug() {
1283
1425
  return isDebugMode();
1284
1426
  },
@@ -1291,10 +1433,14 @@ const APP_CONFIG = {
1291
1433
  },
1292
1434
  animation: {
1293
1435
  fps: 25
1436
+ // 动画帧率(从 CharacterMeta 加载动画资源)
1294
1437
  },
1295
1438
  audio: {
1296
1439
  sampleRate: 16e3
1440
+ // 音频采样率(后端要求 16kHz)
1297
1441
  },
1442
+ // FLAME 全局模板资源 CDN
1443
+ // 这些资源是所有角色共享的基础模板
1298
1444
  flame: {
1299
1445
  cdnBaseUrl: "https://cdn.spatialwalk.top/public",
1300
1446
  resources: {
@@ -1316,6 +1462,7 @@ function convertProtoFlameToWasmParams(protoFlame) {
1316
1462
  eyelid: protoFlame.eyeLid || [0, 0],
1317
1463
  expr_params: protoFlame.expression || [],
1318
1464
  shape_params: [],
1465
+ // Realtime doesn't provide shape params, use default
1319
1466
  has_eyelid: (((_a = protoFlame.eyeLid) == null ? void 0 : _a.length) || 0) > 0
1320
1467
  };
1321
1468
  }
@@ -5965,6 +6112,12 @@ function requireStackframe() {
5965
6112
  var CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m;
5966
6113
  var SAFARI_NATIVE_CODE_REGEXP = /^(eval@)?(\[native code])?$/;
5967
6114
  return {
6115
+ /**
6116
+ * Given an Error object, extract the most information from it.
6117
+ *
6118
+ * @param {Error} error object
6119
+ * @return {Array} of StackFrames
6120
+ */
5968
6121
  parse: function ErrorStackParser$$parse(error) {
5969
6122
  if (typeof error.stacktrace !== "undefined" || typeof error["opera#sourceloc"] !== "undefined") {
5970
6123
  return this.parseOpera(error);
@@ -5976,6 +6129,7 @@ function requireStackframe() {
5976
6129
  throw new Error("Cannot parse given Error object");
5977
6130
  }
5978
6131
  },
6132
+ // Separate line and column numbers from a string of the form: (URI:Line:Column)
5979
6133
  extractLocation: function ErrorStackParser$$extractLocation(urlLike) {
5980
6134
  if (urlLike.indexOf(":") === -1) {
5981
6135
  return [urlLike];
@@ -6078,6 +6232,7 @@ function requireStackframe() {
6078
6232
  }
6079
6233
  return result2;
6080
6234
  },
6235
+ // Opera 10.65+ Error.stack very similar to FF/Safari
6081
6236
  parseOpera11: function ErrorStackParser$$parseOpera11(error) {
6082
6237
  var filtered = error.stack.split("\n").filter(function(line) {
6083
6238
  return !!line.match(FIREFOX_SAFARI_STACK_REGEXP) && !line.match(/^Error created at/);
@@ -6964,6 +7119,7 @@ function createLogg(context) {
6964
7119
  column: stacks[0].column
6965
7120
  });
6966
7121
  },
7122
+ // Placeholder implementations, will be overridden below
6967
7123
  debug: () => {
6968
7124
  },
6969
7125
  verbose: () => {
@@ -6976,6 +7132,7 @@ function createLogg(context) {
6976
7132
  },
6977
7133
  warn: () => {
6978
7134
  },
7135
+ // TODO: remove in next major release
6979
7136
  withTimeFormat: (_2) => {
6980
7137
  const logger2 = logObj.child();
6981
7138
  return logger2;
@@ -7115,10 +7272,22 @@ var ResourceType = /* @__PURE__ */ ((ResourceType2) => {
7115
7272
  function extractResourceUrls(meta) {
7116
7273
  var _a, _b, _c, _d, _e2, _f, _g, _h, _i2, _j, _k;
7117
7274
  return {
7118
- ["camera"]: ((_b = (_a = meta.camera) == null ? void 0 : _a.resource) == null ? void 0 : _b.remote) || null,
7119
- ["frameIdle"]: ((_e2 = (_d = (_c = meta.animations) == null ? void 0 : _c.frameIdle) == null ? void 0 : _d.resource) == null ? void 0 : _e2.remote) || null,
7120
- ["shape"]: ((_h = (_g = (_f = meta.models) == null ? void 0 : _f.shape) == null ? void 0 : _g.resource) == null ? void 0 : _h.remote) || null,
7121
- ["gsStandard"]: ((_k = (_j = (_i2 = meta.models) == null ? void 0 : _i2.gsStandard) == null ? void 0 : _j.resource) == null ? void 0 : _k.remote) || null
7275
+ [
7276
+ "camera"
7277
+ /* CAMERA */
7278
+ ]: ((_b = (_a = meta.camera) == null ? void 0 : _a.resource) == null ? void 0 : _b.remote) || null,
7279
+ [
7280
+ "frameIdle"
7281
+ /* ANIMATION_IDLE */
7282
+ ]: ((_e2 = (_d = (_c = meta.animations) == null ? void 0 : _c.frameIdle) == null ? void 0 : _d.resource) == null ? void 0 : _e2.remote) || null,
7283
+ [
7284
+ "shape"
7285
+ /* MODEL_SHAPE */
7286
+ ]: ((_h = (_g = (_f = meta.models) == null ? void 0 : _f.shape) == null ? void 0 : _g.resource) == null ? void 0 : _h.remote) || null,
7287
+ [
7288
+ "gsStandard"
7289
+ /* MODEL_GS */
7290
+ ]: ((_k = (_j = (_i2 = meta.models) == null ? void 0 : _i2.gsStandard) == null ? void 0 : _j.resource) == null ? void 0 : _k.remote) || null
7122
7291
  };
7123
7292
  }
7124
7293
  var Environment = /* @__PURE__ */ ((Environment2) => {
@@ -7313,6 +7482,7 @@ class IdManager {
7313
7482
  });
7314
7483
  this.ids.clientId = getOrCreateClientId();
7315
7484
  }
7485
+ // ========== 全局 ID ==========
7316
7486
  getClientId() {
7317
7487
  return this.ids.clientId;
7318
7488
  }
@@ -7334,10 +7504,17 @@ class IdManager {
7334
7504
  getSessionToken() {
7335
7505
  return this.ids.sessionToken;
7336
7506
  }
7507
+ // ========== 连接级别 ID ==========
7508
+ /**
7509
+ * 生成新的 connectionId(用于 WebSocket 连接)
7510
+ */
7337
7511
  generateConnectionId() {
7338
7512
  this.ids.connectionId = generateConversationId();
7339
7513
  return this.ids.connectionId;
7340
7514
  }
7515
+ /**
7516
+ * 设置 connectionId(用于从服务器接收到的 connectionId)
7517
+ */
7341
7518
  setConnectionId(connectionId) {
7342
7519
  this.ids.connectionId = connectionId;
7343
7520
  }
@@ -7347,6 +7524,10 @@ class IdManager {
7347
7524
  clearConnectionId() {
7348
7525
  this.ids.connectionId = null;
7349
7526
  }
7527
+ // ========== 会话级别 ID ==========
7528
+ /**
7529
+ * 生成新的 conversationId(用于每次对话)
7530
+ */
7350
7531
  generateNewConversationId() {
7351
7532
  this.ids.conversationId = generateConversationId();
7352
7533
  return this.ids.conversationId;
@@ -7360,9 +7541,16 @@ class IdManager {
7360
7541
  clearConversationId() {
7361
7542
  this.ids.conversationId = null;
7362
7543
  }
7544
+ // ========== 批量获取(用于 PostHog 日志)==========
7545
+ /**
7546
+ * 获取所有 ID(用于日志上报)
7547
+ */
7363
7548
  getAllIds() {
7364
7549
  return { ...this.ids };
7365
7550
  }
7551
+ /**
7552
+ * 获取公共日志参数(包含所有必要的 ID)
7553
+ */
7366
7554
  getLogContext() {
7367
7555
  return {
7368
7556
  client_id: this.ids.clientId,
@@ -7372,6 +7560,9 @@ class IdManager {
7372
7560
  ...this.ids.conversationId && { conversation_id: this.ids.conversationId }
7373
7561
  };
7374
7562
  }
7563
+ /**
7564
+ * 清理所有 ID(用于测试或重置)
7565
+ */
7375
7566
  clear() {
7376
7567
  this.ids.userId = null;
7377
7568
  this.ids.appId = null;
@@ -7413,10 +7604,15 @@ function initializePostHog(environment, version) {
7413
7604
  api_host: host,
7414
7605
  person_profiles: "identified_only",
7415
7606
  capture_pageview: false,
7607
+ // SDK 不需要自动捕获页面浏览
7416
7608
  capture_pageleave: false,
7609
+ // SDK 不需要自动捕获页面离开
7417
7610
  disable_compression: disableCompression,
7611
+ // 根据环境配置压缩:国内禁用,国外启用
7418
7612
  disable_session_recording: true,
7613
+ // 禁用 Session Recording,避免额外的请求和错误
7419
7614
  autocapture: false,
7615
+ // 禁用自动捕获,只上报 SDK 自定义事件
7420
7616
  loaded: (posthogInstance) => {
7421
7617
  logger.log(`[PostHog] Initialized successfully - environment: ${environment}, host: ${host}, instance: ${SDK_POSTHOG_INSTANCE_NAME}`);
7422
7618
  isInitialized = true;
@@ -7502,15 +7698,19 @@ function trackEvent(event, level = "info", contents = {}) {
7502
7698
  try {
7503
7699
  const logContext = idManager.getLogContext();
7504
7700
  const properties = {
7701
+ // 基础属性
7505
7702
  service_module: "sdk",
7506
7703
  platform: "Web",
7507
7704
  sdk_version: sdkVersion,
7508
7705
  level,
7509
7706
  environment: currentEnvironment || "unknown",
7707
+ // ID 信息
7510
7708
  app_id: logContext.app_id,
7511
7709
  user_id: logContext.user_id,
7512
7710
  client_id: logContext.client_id,
7711
+ // 时间戳
7513
7712
  timestamp: Date.now(),
7713
+ // 自定义内容
7514
7714
  ...contents
7515
7715
  };
7516
7716
  if (logContext.connection_id) {
@@ -7572,6 +7772,10 @@ const _AnimationPlayer = class _AnimationPlayer {
7572
7772
  __publicField(this, "onEndedCallback");
7573
7773
  __publicField(this, "useStreaming", false);
7574
7774
  }
7775
+ /**
7776
+ * 解锁音频上下文(Safari 自动播放策略)
7777
+ * 必须在用户交互事件(如 click)中调用
7778
+ */
7575
7779
  static async unlockAudioContext() {
7576
7780
  if (_AnimationPlayer.audioUnlocked) {
7577
7781
  return;
@@ -7589,6 +7793,9 @@ const _AnimationPlayer = class _AnimationPlayer {
7589
7793
  logger.warn("⚠️ Failed to unlock audio context:", err);
7590
7794
  }
7591
7795
  }
7796
+ /**
7797
+ * Initialize with HTMLAudioElement (traditional way)
7798
+ */
7592
7799
  async initialize(audioUrl, onEnded) {
7593
7800
  this.onEndedCallback = onEnded;
7594
7801
  this.useStreaming = false;
@@ -7604,6 +7811,10 @@ const _AnimationPlayer = class _AnimationPlayer {
7604
7811
  this.audio.load();
7605
7812
  });
7606
7813
  }
7814
+ /**
7815
+ * Initialize with StreamingAudioPlayer (streaming way)
7816
+ * @deprecated 使用 prepareStreamingPlayer() 代替
7817
+ */
7607
7818
  async initializeStreaming(streamingPlayer, onEnded) {
7608
7819
  this.streamingPlayer = streamingPlayer;
7609
7820
  this.onEndedCallback = onEnded;
@@ -7614,17 +7825,27 @@ const _AnimationPlayer = class _AnimationPlayer {
7614
7825
  (_a = this.onEndedCallback) == null ? void 0 : _a.call(this);
7615
7826
  });
7616
7827
  }
7828
+ /**
7829
+ * 检查流式播放器是否已准备好
7830
+ */
7617
7831
  isStreamingReady() {
7618
7832
  return this.useStreaming && this.streamingPlayer !== null;
7619
7833
  }
7834
+ /**
7835
+ * 获取流式播放器实例
7836
+ */
7620
7837
  getStreamingPlayer() {
7621
7838
  return this.streamingPlayer;
7622
7839
  }
7840
+ /**
7841
+ * 创建并初始化流式播放器
7842
+ * 在服务连接建立时调用
7843
+ */
7623
7844
  async createAndInitializeStreamingPlayer() {
7624
7845
  if (this.streamingPlayer) {
7625
7846
  return;
7626
7847
  }
7627
- const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-CD9jBs6B.js");
7848
+ const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-DiIRp5nx.js");
7628
7849
  const { AvatarSDK: AvatarSDK2 } = await Promise.resolve().then(() => AvatarSDK$1);
7629
7850
  const audioFormat = AvatarSDK2.getAudioFormat();
7630
7851
  this.streamingPlayer = new StreamingAudioPlayer({
@@ -7645,6 +7866,10 @@ const _AnimationPlayer = class _AnimationPlayer {
7645
7866
  }
7646
7867
  this.useStreaming = true;
7647
7868
  }
7869
+ /**
7870
+ * 准备流式播放器(如果未创建则创建并初始化)
7871
+ * 停止之前的播放并更新结束回调
7872
+ */
7648
7873
  async prepareStreamingPlayer(onEnded) {
7649
7874
  if (!this.streamingPlayer) {
7650
7875
  await this.createAndInitializeStreamingPlayer();
@@ -7705,6 +7930,9 @@ const _AnimationPlayer = class _AnimationPlayer {
7705
7930
  const currentTime = this.getCurrentTime();
7706
7931
  return Math.floor(currentTime * this.fps);
7707
7932
  }
7933
+ /**
7934
+ * Get current playback time
7935
+ */
7708
7936
  getCurrentTime() {
7709
7937
  var _a, _b;
7710
7938
  if (this.useStreaming) {
@@ -7712,6 +7940,9 @@ const _AnimationPlayer = class _AnimationPlayer {
7712
7940
  }
7713
7941
  return ((_b = this.audio) == null ? void 0 : _b.currentTime) ?? 0;
7714
7942
  }
7943
+ /**
7944
+ * 添加音频块(仅用于流式播放)
7945
+ */
7715
7946
  addAudioChunk(audio, isLast = false) {
7716
7947
  if (this.useStreaming && this.streamingPlayer) {
7717
7948
  this.streamingPlayer.addChunk(audio, isLast);
@@ -7719,6 +7950,9 @@ const _AnimationPlayer = class _AnimationPlayer {
7719
7950
  logger.warn("[AnimationPlayer] Cannot add audio chunk - streaming player not ready");
7720
7951
  }
7721
7952
  }
7953
+ /**
7954
+ * 暂停播放
7955
+ */
7722
7956
  pause() {
7723
7957
  var _a;
7724
7958
  if (this.useStreaming) {
@@ -7729,6 +7963,9 @@ const _AnimationPlayer = class _AnimationPlayer {
7729
7963
  }
7730
7964
  }
7731
7965
  }
7966
+ /**
7967
+ * 继续播放
7968
+ */
7732
7969
  async resume() {
7733
7970
  var _a;
7734
7971
  if (this.useStreaming) {
@@ -7739,6 +7976,11 @@ const _AnimationPlayer = class _AnimationPlayer {
7739
7976
  }
7740
7977
  }
7741
7978
  }
7979
+ /**
7980
+ * 设置音量 (0.0 - 1.0)
7981
+ * 注意:这仅控制数字人音频播放器的音量,不影响系统音量
7982
+ * @param volume 音量值,范围 0.0 到 1.0(0.0 为静音,1.0 为最大音量)
7983
+ */
7742
7984
  setVolume(volume) {
7743
7985
  if (this.useStreaming && this.streamingPlayer) {
7744
7986
  this.streamingPlayer.setVolume(volume);
@@ -7748,6 +7990,10 @@ const _AnimationPlayer = class _AnimationPlayer {
7748
7990
  }
7749
7991
  }
7750
7992
  }
7993
+ /**
7994
+ * 获取当前音量
7995
+ * @returns 当前音量值 (0.0 - 1.0)
7996
+ */
7751
7997
  getVolume() {
7752
7998
  var _a;
7753
7999
  if (this.useStreaming && this.streamingPlayer) {
@@ -7793,6 +8039,7 @@ async function fetchSdkConfig(version) {
7793
8039
  logger.log(`[SdkConfigLoader] Fetching SDK config from: ${configUrl}`);
7794
8040
  const response = await fetch(configUrl, {
7795
8041
  method: "GET"
8042
+ // GET 请求不需要 Content-Type header,避免触发 CORS 预检
7796
8043
  });
7797
8044
  if (!response.ok) {
7798
8045
  throw new Error(`HTTP ${response.status} ${response.statusText}`);
@@ -7852,6 +8099,9 @@ class AvatarCoreMemoryManager {
7852
8099
  this.allocatedPointers = /* @__PURE__ */ new Set();
7853
8100
  this.structPointers = /* @__PURE__ */ new Map();
7854
8101
  }
8102
+ /**
8103
+ * 分配 WASM 内存并复制数据 - 纯 API 方式
8104
+ */
7855
8105
  allocateAndCopy(arrayBuffer) {
7856
8106
  const size = arrayBuffer.byteLength;
7857
8107
  const ptr = this.module._malloc(size);
@@ -7863,6 +8113,9 @@ class AvatarCoreMemoryManager {
7863
8113
  this.allocatedPointers.add(ptr);
7864
8114
  return { ptr, size };
7865
8115
  }
8116
+ /**
8117
+ * 创建 AvatarTemplateConfig 结构体
8118
+ */
7866
8119
  createTemplateConfig(resources) {
7867
8120
  const structSize = 10 * 4;
7868
8121
  const configPtr = this.module._malloc(structSize);
@@ -7909,6 +8162,9 @@ class AvatarCoreMemoryManager {
7909
8162
  this.structPointers.set("template_config", configPtr);
7910
8163
  return configPtr;
7911
8164
  }
8165
+ /**
8166
+ * 创建 AvatarCharacterData 结构体 - 新版 Emscripten 方式
8167
+ */
7912
8168
  createCharacterData(shapeBuffer, plyBuffer, characterId) {
7913
8169
  const structSize = 4 * 4;
7914
8170
  const dataPtr = this.module._malloc(structSize);
@@ -7930,6 +8186,10 @@ class AvatarCoreMemoryManager {
7930
8186
  this.structPointers.set(key, dataPtr);
7931
8187
  return { dataPtr, shapePtr: shape.ptr, plyPtr: ply.ptr };
7932
8188
  }
8189
+ /**
8190
+ * 读取 AvatarFlameParams 结构体数据
8191
+ * Used to extract current animation frame parameters from WASM memory
8192
+ */
7933
8193
  readFlameParams(paramsPtr) {
7934
8194
  let byteOffset = 0;
7935
8195
  const shape_params = [];
@@ -7985,6 +8245,9 @@ class AvatarCoreMemoryManager {
7985
8245
  has_eyelid
7986
8246
  };
7987
8247
  }
8248
+ /**
8249
+ * 创建 AvatarFlameParams 结构体 - 新版 Emscripten 方式
8250
+ */
7988
8251
  createFlameParams(params) {
7989
8252
  var _a, _b, _c, _d, _e2, _f, _g, _h;
7990
8253
  const structSize = (300 + 100 + 3 + 3 + 3 + 3 + 6 + 2 + 1) * 4;
@@ -8037,6 +8300,21 @@ class AvatarCoreMemoryManager {
8037
8300
  byteOffset += 4;
8038
8301
  return paramsPtr;
8039
8302
  }
8303
+ /**
8304
+ * 读取 AvatarSplatPointFlatArray 结构体数据(预计算协方差)
8305
+ *
8306
+ * 结构体布局:
8307
+ * - AvatarSplatPointFlat* points (0-3, 32位指针)
8308
+ * - uint32_t point_count (4-7)
8309
+ * - float compute_time_ms (8-11)
8310
+ *
8311
+ * 每个点布局 (52 bytes):
8312
+ * - position[3] (12 bytes)
8313
+ * - color[4] (16 bytes, RGBA)
8314
+ * - covariance[6] (24 bytes, 预计算好的协方差矩阵上三角)
8315
+ *
8316
+ * ⚠️ 使用 getValue 逐个读取,避免动态内存的 HEAPF32 detachment 问题
8317
+ */
8040
8318
  readSplatPointFlatArray(arrayPtr) {
8041
8319
  if (!arrayPtr) {
8042
8320
  throw new Error("Invalid array pointer");
@@ -8053,6 +8331,9 @@ class AvatarCoreMemoryManager {
8053
8331
  flatData.set(this.module.HEAPF32.subarray(floatOffset, floatOffset + totalFloats));
8054
8332
  return flatData;
8055
8333
  }
8334
+ /**
8335
+ * 读取AvatarMeshData结构体数据
8336
+ */
8056
8337
  readMeshData(outputPtr) {
8057
8338
  const verticesPtr = this.module.getValue(outputPtr + 0, "*");
8058
8339
  const vertexCount = this.module.getValue(outputPtr + 4, "i32");
@@ -8090,12 +8371,18 @@ class AvatarCoreMemoryManager {
8090
8371
  computeTime
8091
8372
  };
8092
8373
  }
8374
+ /**
8375
+ * 释放指定指针的内存
8376
+ */
8093
8377
  free(ptr) {
8094
8378
  if (ptr && this.allocatedPointers.has(ptr)) {
8095
8379
  this.module._free(ptr);
8096
8380
  this.allocatedPointers.delete(ptr);
8097
8381
  }
8098
8382
  }
8383
+ /**
8384
+ * 释放结构体内存
8385
+ */
8099
8386
  freeStruct(name) {
8100
8387
  const ptr = this.structPointers.get(name);
8101
8388
  if (ptr) {
@@ -8103,6 +8390,9 @@ class AvatarCoreMemoryManager {
8103
8390
  this.structPointers.delete(name);
8104
8391
  }
8105
8392
  }
8393
+ /**
8394
+ * 清理所有分配的内存
8395
+ */
8106
8396
  cleanup() {
8107
8397
  for (const ptr of this.allocatedPointers) {
8108
8398
  this.module._free(ptr);
@@ -8113,6 +8403,9 @@ class AvatarCoreMemoryManager {
8113
8403
  }
8114
8404
  this.structPointers.clear();
8115
8405
  }
8406
+ /**
8407
+ * 获取内存使用统计
8408
+ */
8116
8409
  getMemoryStats() {
8117
8410
  return {
8118
8411
  allocatedPointers: this.allocatedPointers.size,
@@ -8123,22 +8416,36 @@ class AvatarCoreMemoryManager {
8123
8416
  }
8124
8417
  class AvatarCoreAdapter {
8125
8418
  constructor(options = {}) {
8419
+ // 配置
8126
8420
  __publicField(this, "options");
8127
8421
  __publicField(this, "wasmConfig");
8422
+ // 核心组件
8423
+ // eslint-disable-next-line ts/no-explicit-any
8128
8424
  __publicField(this, "wasmModule");
8129
8425
  __publicField(this, "memoryManager");
8426
+ // Avatar Core handles
8130
8427
  __publicField(this, "coreHandle");
8428
+ // Support multiple character handles (key: characterId, value: handle)
8131
8429
  __publicField(this, "characterHandles", /* @__PURE__ */ new Map());
8430
+ // Animation handles per character (key: characterId, value: { handle, totalFrames })
8132
8431
  __publicField(this, "animationHandles", /* @__PURE__ */ new Map());
8432
+ // Legacy single character support (deprecated, kept for backward compatibility)
8133
8433
  __publicField(this, "characterHandle");
8134
8434
  __publicField(this, "animationHandle");
8135
8435
  __publicField(this, "totalFrames");
8436
+ // 状态管理
8136
8437
  __publicField(this, "isInitialized");
8137
8438
  __publicField(this, "isCharacterLoaded");
8439
+ // C API 函数包装
8440
+ // eslint-disable-next-line ts/no-explicit-any
8138
8441
  __publicField(this, "api");
8442
+ // 性能监控
8139
8443
  __publicField(this, "performanceMetrics");
8140
8444
  __publicField(this, "wasmTime", 0);
8445
+ // 最近一次 WASM 计算耗时
8446
+ // 错误码映射
8141
8447
  __publicField(this, "errorCodes");
8448
+ // 模型信息
8142
8449
  __publicField(this, "flameInfo");
8143
8450
  __publicField(this, "characterInfo");
8144
8451
  this.options = {
@@ -8178,6 +8485,19 @@ class AvatarCoreAdapter {
8178
8485
  6: "AVATAR_ERROR_COMPUTATION_FAILED"
8179
8486
  };
8180
8487
  }
8488
+ /**
8489
+ * 加载 WASM 模块并设置 API
8490
+ * 每次都创建全新的 WASM 实例,确保 C++ 内存是干净的
8491
+ *
8492
+ * 注意:这里使用动态 import() 导入 WASM 模块。
8493
+ * Vite 在构建时会自动为 WASM 文件和 JS glue 代码添加 hash(如 avatar_core_wasm-CxWuw7eS.wasm),
8494
+ * 确保浏览器缓存与版本一致,不会有缓存问题。
8495
+ *
8496
+ * Hash 的作用机制:
8497
+ * - WASM 文件内容变化 → hash 自动变化 → URL 变化 → 浏览器拉取新版本
8498
+ * - WASM 文件内容不变 → hash 保持不变 → URL 不变 → 浏览器使用缓存
8499
+ * - Emscripten 生成的 JS 内部会使用 hard-coded 的 hash 路径加载 .wasm 文件
8500
+ */
8181
8501
  async loadWASMModule() {
8182
8502
  try {
8183
8503
  const { default: AvatarCoreModule } = await import("./avatar_core_wasm-Dv943JJl.js");
@@ -8195,6 +8515,9 @@ class AvatarCoreAdapter {
8195
8515
  throw new Error(`Failed to load WASM module: ${errorMessage}`);
8196
8516
  }
8197
8517
  }
8518
+ /**
8519
+ * 验证 WASM 模块功能
8520
+ */
8198
8521
  validateWASMModule() {
8199
8522
  const requiredFunctions = ["cwrap", "_malloc", "_free", "setValue", "getValue", "writeArrayToMemory"];
8200
8523
  for (const funcName of requiredFunctions) {
@@ -8204,6 +8527,9 @@ class AvatarCoreAdapter {
8204
8527
  }
8205
8528
  this.initializeMemoryViews();
8206
8529
  }
8530
+ /**
8531
+ * 初始化内存视图
8532
+ */
8207
8533
  initializeMemoryViews() {
8208
8534
  try {
8209
8535
  const testPtr = this.wasmModule._malloc(4);
@@ -8222,32 +8548,57 @@ class AvatarCoreAdapter {
8222
8548
  throw new Error(`Memory system initialization failed: ${errorMessage}`);
8223
8549
  }
8224
8550
  }
8551
+ /**
8552
+ * 设置 C API 函数包装
8553
+ */
8225
8554
  setupCAPIFunctions() {
8226
8555
  this.api = {
8556
+ // 核心系统管理
8227
8557
  initialize: this.wasmModule.cwrap("avatar_core_initialize", "number", ["number"]),
8558
+ // 返回 handle
8228
8559
  release: this.wasmModule.cwrap("avatar_core_release", null, ["number"]),
8229
8560
  getVersion: this.wasmModule.cwrap("avatar_core_get_version", "string", []),
8230
8561
  getErrorString: this.wasmModule.cwrap("avatar_core_get_error_string", "string", ["number"]),
8231
8562
  resetProcessorForReuse: this.wasmModule.cwrap("avatar_core_reset_processor_for_reuse", null, ["number"]),
8563
+ // 角色管理
8232
8564
  loadCharacter: this.wasmModule.cwrap("avatar_core_load_character", "number", ["number", "number"]),
8565
+ // 返回 handle
8233
8566
  removeCharacter: this.wasmModule.cwrap("avatar_core_remove_character", null, ["number", "number"]),
8234
8567
  getCharacterInfo: this.wasmModule.cwrap("avatar_core_get_character_info", "number", ["number", "number", "number"]),
8235
8568
  getCharacterShapeParams: this.wasmModule.cwrap("avatar_core_get_character_shape_params", "number", ["number", "number"]),
8569
+ // characterHandle, outputPtr
8570
+ // 动画管理
8236
8571
  loadAnimation: this.wasmModule.cwrap("avatar_core_load_animation", "number", ["number", "number"]),
8237
8572
  parseAnimationFramesFromFile: this.wasmModule.cwrap("avatar_core_parse_animation_frames_from_file", "number", ["number", "number", "number"]),
8238
8573
  getAnimationFrameCount: this.wasmModule.cwrap("avatar_core_get_animation_frame_count", "number", ["number", "number"]),
8239
8574
  getFrameFromAnimation: this.wasmModule.cwrap("avatar_core_get_frame_from_animation", "number", ["number", "number", "number"]),
8240
8575
  removeAnimation: this.wasmModule.cwrap("avatar_core_remove_animation", null, ["number", "number"]),
8576
+ // 帧计算 (Flat 格式 - GPU 优化版本,协方差预计算)
8241
8577
  computeFrameFlat: this.wasmModule.cwrap("avatar_core_compute_frame_as_splat_points_flat", "number", ["number", "number", "number", "number"]),
8578
+ // 返回错误码
8242
8579
  freeSplatPointsFlat: this.wasmModule.cwrap("avatar_core_free_splat_points_flat", null, ["number"]),
8580
+ // Mesh 计算(用于传统 mesh 渲染)
8243
8581
  computeFrameAsMesh: this.wasmModule.cwrap("avatar_core_compute_frame_as_mesh", "number", ["number", "number", "number", "number"]),
8582
+ // 返回错误码
8244
8583
  freeMeshData: this.wasmModule.cwrap("avatar_core_free_mesh_data", null, ["number"]),
8584
+ // 眼部追踪(高级功能)
8245
8585
  setEyeTrackingConfig: this.wasmModule.cwrap("avatar_core_set_eye_tracking_config", "number", ["number", "number", "number"]),
8586
+ // core, character, config
8246
8587
  setGazeTarget: this.wasmModule.cwrap("avatar_core_set_gaze_target", "number", ["number", "number", "number", "number", "number"]),
8588
+ // core, character, x, y, z
8247
8589
  resetEyeTracking: this.wasmModule.cwrap("avatar_core_reset_eye_tracking", "number", ["number"]),
8590
+ // FLAME 信息查询
8248
8591
  getFlameInfo: this.wasmModule.cwrap("avatar_core_get_flame_info", "number", ["number", "number", "number", "number"])
8249
8592
  };
8250
8593
  }
8594
+ /**
8595
+ * 读取当前动画帧的 FlameParams(用于过渡或调试)
8596
+ */
8597
+ /**
8598
+ * Get current frame parameters
8599
+ * @param frameIndex Frame index
8600
+ * @param characterId Optional character ID for multi-character support
8601
+ */
8251
8602
  async getCurrentFrameParams(frameIndex = 0, characterId) {
8252
8603
  const paramsPtr = await this.getAnimationFrameParams(frameIndex, characterId);
8253
8604
  try {
@@ -8257,6 +8608,9 @@ class AvatarCoreAdapter {
8257
8608
  this.wasmModule._free(paramsPtr);
8258
8609
  }
8259
8610
  }
8611
+ /**
8612
+ * 初始化 Avatar Core 核心
8613
+ */
8260
8614
  async initializeAvatarCore(templateResources) {
8261
8615
  try {
8262
8616
  const configPtr = this.memoryManager.createTemplateConfig(templateResources);
@@ -8271,6 +8625,9 @@ class AvatarCoreAdapter {
8271
8625
  throw new Error(`Failed to initialize Avatar Core: ${errorMessage}`);
8272
8626
  }
8273
8627
  }
8628
+ /**
8629
+ * 加载模板资源(从预加载的 ArrayBuffers)
8630
+ */
8274
8631
  async loadTemplateResourcesFromBuffers(templateResources) {
8275
8632
  const formattedResources = {
8276
8633
  flameModel: templateResources.flameModel || templateResources["model.pb"] || new ArrayBuffer(0),
@@ -8294,6 +8651,16 @@ class AvatarCoreAdapter {
8294
8651
  throw new Error(`Template resources loading failed: ${errorMessage}`);
8295
8652
  }
8296
8653
  }
8654
+ /**
8655
+ * 加载角色数据(从预加载的 ArrayBuffers)
8656
+ */
8657
+ /**
8658
+ * Load character from buffers and return handle
8659
+ * @param shapeBuffer Shape data buffer
8660
+ * @param pointCloudBuffer Point cloud data buffer
8661
+ * @param characterId Optional character ID for multi-character support
8662
+ * @returns Character handle
8663
+ */
8297
8664
  async loadCharacterFromBuffers(shapeBuffer, pointCloudBuffer, characterId) {
8298
8665
  if (!this.isInitialized) {
8299
8666
  throw new Error("Avatar Core not initialized");
@@ -8347,6 +8714,12 @@ class AvatarCoreAdapter {
8347
8714
  }
8348
8715
  return handle;
8349
8716
  }
8717
+ /**
8718
+ * Load animation from ArrayBuffer (for CDN-based dynamic loading)
8719
+ * @param animData Animation data buffer
8720
+ * @param characterId Optional character ID for multi-character support
8721
+ * @returns Animation handle
8722
+ */
8350
8723
  async loadAnimationFromBuffer(animData, characterId) {
8351
8724
  if (!this.isInitialized) {
8352
8725
  throw new Error("Avatar Core not initialized");
@@ -8388,6 +8761,10 @@ class AvatarCoreAdapter {
8388
8761
  throw new Error(`Failed to switch animation: ${errorMessage}`);
8389
8762
  }
8390
8763
  }
8764
+ /**
8765
+ * 获取动画总帧数
8766
+ * @param animationHandle Optional animation handle (for multi-character support)
8767
+ */
8391
8768
  async getAnimationTotalFrames(animationHandle) {
8392
8769
  const handle = animationHandle || this.animationHandle;
8393
8770
  if (!handle) {
@@ -8413,6 +8790,11 @@ class AvatarCoreAdapter {
8413
8790
  throw new Error(`Failed to get animation frame count: ${errorMessage}`);
8414
8791
  }
8415
8792
  }
8793
+ /**
8794
+ * 从动画中获取指定帧的参数
8795
+ * @param frameIndex Frame index
8796
+ * @param characterId Optional character ID for multi-character support
8797
+ */
8416
8798
  async getAnimationFrameParams(frameIndex = 0, characterId) {
8417
8799
  const id = characterId || "default";
8418
8800
  const animInfo = this.animationHandles.get(id);
@@ -8449,6 +8831,11 @@ class AvatarCoreAdapter {
8449
8831
  throw new Error(`Failed to get animation frame ${frameIndex}: ${errorMessage}`);
8450
8832
  }
8451
8833
  }
8834
+ /**
8835
+ * 设置眼部追踪配置(对齐 app 实现)
8836
+ * @param config Eye tracking configuration
8837
+ * @param characterId Optional character ID for multi-character support
8838
+ */
8452
8839
  async setEyeTrackingConfig(config, characterId) {
8453
8840
  if (!this.isCharacterLoaded) {
8454
8841
  throw new Error("Character not loaded");
@@ -8473,6 +8860,13 @@ class AvatarCoreAdapter {
8473
8860
  throw new Error(`Failed to set eye tracking config: ${errorMessage}`);
8474
8861
  }
8475
8862
  }
8863
+ /**
8864
+ * 设置眼部追踪目标(高级功能)
8865
+ * @param x Target X coordinate
8866
+ * @param y Target Y coordinate
8867
+ * @param z Target Z coordinate
8868
+ * @param characterId Optional character ID for multi-character support
8869
+ */
8476
8870
  async setGazeTarget(x2, y2, z2, characterId) {
8477
8871
  if (!this.isCharacterLoaded) {
8478
8872
  throw new Error("Character not loaded");
@@ -8492,6 +8886,9 @@ class AvatarCoreAdapter {
8492
8886
  throw new Error(`Failed to set gaze target: ${errorMessage}`);
8493
8887
  }
8494
8888
  }
8889
+ /**
8890
+ * 重置眼部追踪
8891
+ */
8495
8892
  async resetEyeTracking() {
8496
8893
  if (!this.isCharacterLoaded) {
8497
8894
  throw new Error("Character not loaded");
@@ -8506,6 +8903,9 @@ class AvatarCoreAdapter {
8506
8903
  throw new Error(`Failed to reset eye tracking: ${errorMessage}`);
8507
8904
  }
8508
8905
  }
8906
+ /**
8907
+ * 查询 FLAME 模型信息
8908
+ */
8509
8909
  async queryFlameInfo() {
8510
8910
  try {
8511
8911
  const vertexCountPtr = this.wasmModule._malloc(4);
@@ -8530,6 +8930,9 @@ class AvatarCoreAdapter {
8530
8930
  logger.errorWithError("Failed to query model info:", errorMessage);
8531
8931
  }
8532
8932
  }
8933
+ /**
8934
+ * 查询角色信息
8935
+ */
8533
8936
  async queryCharacterInfo() {
8534
8937
  try {
8535
8938
  const pointCountPtr = this.wasmModule._malloc(4);
@@ -8550,6 +8953,9 @@ class AvatarCoreAdapter {
8550
8953
  logger.errorWithError("❌ Failed to query character info:", errorMessage);
8551
8954
  }
8552
8955
  }
8956
+ /**
8957
+ * 检查错误码并抛出异常
8958
+ */
8553
8959
  checkError(errorCode, functionName) {
8554
8960
  if (errorCode !== 0) {
8555
8961
  const errorName = this.errorCodes[errorCode] || `UNKNOWN_ERROR_${errorCode}`;
@@ -8558,11 +8964,17 @@ class AvatarCoreAdapter {
8558
8964
  throw new Error(`${functionName} failed: ${errorName} - ${errorMessage}`);
8559
8965
  }
8560
8966
  }
8967
+ /**
8968
+ * 更新进度回调
8969
+ */
8561
8970
  async updateProgress(callback, message, progress) {
8562
8971
  if (callback && typeof callback === "function") {
8563
8972
  callback({ message, progress });
8564
8973
  }
8565
8974
  }
8975
+ /**
8976
+ * 获取性能指标
8977
+ */
8566
8978
  getPerformanceMetrics() {
8567
8979
  var _a, _b;
8568
8980
  return {
@@ -8572,6 +8984,11 @@ class AvatarCoreAdapter {
8572
8984
  version: ((_b = (_a = this.api).getVersion) == null ? void 0 : _b.call(_a)) || "unknown"
8573
8985
  };
8574
8986
  }
8987
+ /**
8988
+ * 获取角色的点云数量
8989
+ * @param characterHandle 角色 handle(可选,默认使用当前角色)
8990
+ * @returns 点云数量,如果角色未加载则返回 null
8991
+ */
8575
8992
  getPointCount(characterHandle) {
8576
8993
  if (!this.isCharacterLoaded) {
8577
8994
  return null;
@@ -8598,6 +9015,14 @@ class AvatarCoreAdapter {
8598
9015
  return null;
8599
9016
  }
8600
9017
  }
9018
+ /**
9019
+ * 释放当前角色和动画资源(但保留 core)
9020
+ */
9021
+ /**
9022
+ * Remove a specific character by handle
9023
+ * @param characterHandle Character handle to remove
9024
+ * @param characterId Optional character ID for cleanup
9025
+ */
8601
9026
  removeCharacter(characterHandle, characterId) {
8602
9027
  try {
8603
9028
  if (!this.coreHandle) {
@@ -8621,11 +9046,18 @@ class AvatarCoreAdapter {
8621
9046
  logger.errorWithError("❌ Failed to remove character:", error);
8622
9047
  }
8623
9048
  }
9049
+ /**
9050
+ * Release current character (legacy method, kept for backward compatibility)
9051
+ * @deprecated Use removeCharacter() instead for multi-character support
9052
+ */
8624
9053
  releaseCurrentCharacter() {
8625
9054
  if (this.characterHandle) {
8626
9055
  this.removeCharacter(this.characterHandle);
8627
9056
  }
8628
9057
  }
9058
+ /**
9059
+ * 释放所有资源(包括 core)
9060
+ */
8629
9061
  release() {
8630
9062
  try {
8631
9063
  this.releaseCurrentCharacter();
@@ -8648,12 +9080,31 @@ class AvatarCoreAdapter {
8648
9080
  logger.errorWithError("❌ Failed to release resources:", error);
8649
9081
  }
8650
9082
  }
9083
+ /**
9084
+ * 兼容性接口:提供与 FlameComplete3DGSManager 相同的接口
9085
+ */
9086
+ // 兼容 loadFlameModel
9087
+ // eslint-disable-next-line ts/no-explicit-any
8651
9088
  async loadFlameModel(_modelData) {
8652
9089
  return true;
8653
9090
  }
9091
+ // 兼容 load3DGSData
9092
+ // eslint-disable-next-line ts/no-explicit-any
8654
9093
  async load3DGSData(_original3DGSPoints, _binding, _flameFaces) {
8655
9094
  return true;
8656
9095
  }
9096
+ /**
9097
+ * 计算帧并返回 GPU 友好的 flat 格式(零拷贝,协方差预计算)
9098
+ * 🚀 性能优化版本:
9099
+ * - C++ 预计算协方差矩阵
9100
+ * - 零拷贝直接访问 WASM 内存http://localhost:3000/us/
9101
+ * - 直接输出 GPU 格式 [pos3, color4, cov6]
9102
+ */
9103
+ /**
9104
+ * Compute complete frame from animation
9105
+ * @param params Frame parameters
9106
+ * @param characterHandle Optional character handle (for multi-character support)
9107
+ */
8657
9108
  async computeCompleteFrameFlat(params, characterHandle) {
8658
9109
  if (!this.isCharacterLoaded) {
8659
9110
  throw new Error("Character not loaded");
@@ -8709,6 +9160,15 @@ class AvatarCoreAdapter {
8709
9160
  }
8710
9161
  }
8711
9162
  }
9163
+ /**
9164
+ * 计算帧并返回 GPU 友好的 flat 格式(从 FLAME 参数)
9165
+ * 🔑 用于 Realtime: 接受自定义 FLAME 参数并计算 Splat
9166
+ */
9167
+ /**
9168
+ * Compute frame from Flame parameters
9169
+ * @param flameParams Flame parameters
9170
+ * @param characterHandle Optional character handle (for multi-character support)
9171
+ */
8712
9172
  async computeFrameFlatFromParams(flameParams, characterHandle) {
8713
9173
  if (!this.isCharacterLoaded) {
8714
9174
  throw new Error("Character not loaded");
@@ -8751,6 +9211,10 @@ class AvatarCoreAdapter {
8751
9211
  }
8752
9212
  }
8753
9213
  }
9214
+ /**
9215
+ * 获取 WASM 内存使用情况(MB)
9216
+ * @returns WASM 内存使用量(MB),如果模块未加载则返回 null
9217
+ */
8754
9218
  getWASMMemoryMB() {
8755
9219
  if (!this.wasmModule) {
8756
9220
  return null;
@@ -8769,6 +9233,11 @@ class AvatarCoreAdapter {
8769
9233
  }
8770
9234
  }
8771
9235
  class AvatarSDK {
9236
+ /**
9237
+ * Initialize SDK
9238
+ * @param appId Application ID to be included in both HTTP Headers and WebSocket Headers
9239
+ * @param configuration Configuration parameters
9240
+ */
8772
9241
  static async initialize(appId, configuration) {
8773
9242
  var _a;
8774
9243
  try {
@@ -8815,6 +9284,9 @@ class AvatarSDK {
8815
9284
  throw error;
8816
9285
  }
8817
9286
  }
9287
+ /**
9288
+ * 初始化WASM模块(跟随整个SDK生命周期)
9289
+ */
8818
9290
  static async initializeWASMModule() {
8819
9291
  try {
8820
9292
  logger.log("[AvatarSDK] Initializing WASM module...");
@@ -8825,12 +9297,15 @@ class AvatarSDK {
8825
9297
  baseAssetsPath: "",
8826
9298
  wasmPath: "./avatar_core_wasm.js",
8827
9299
  wasmConfig: {
9300
+ // Print function for debugging WASM output
8828
9301
  print: (text) => {
8829
9302
  logger.log(`[WASM] ${text}`);
8830
9303
  },
8831
9304
  printErr: (text) => {
8832
9305
  logger.error(`[WASM ERROR] ${text}`);
8833
9306
  },
9307
+ // 配置 locateFile 以确保能找到 WASM 文件
9308
+ // WASM 文件与 Emscripten JS 文件在同一目录(dist/ 根目录)
8834
9309
  locateFile: (path, prefix) => {
8835
9310
  if (path.startsWith("data:") || path.startsWith("http://") || path.startsWith("https://")) {
8836
9311
  return path;
@@ -8846,6 +9321,10 @@ class AvatarSDK {
8846
9321
  throw new Error(`WASM module initialization failed: ${error instanceof Error ? error.message : String(error)}`);
8847
9322
  }
8848
9323
  }
9324
+ /**
9325
+ * 初始化模板资源(在SDK初始化时加载)
9326
+ * 从全局 CDN 加载模板资源,失败时直接抛出错误,阻止SDK初始化
9327
+ */
8849
9328
  static async initializeTemplateResources() {
8850
9329
  if (!this._avatarCore) {
8851
9330
  throw new Error("AvatarCore not available");
@@ -8871,12 +9350,22 @@ class AvatarSDK {
8871
9350
  throw new Error(`SDK initialization failed: Template resources loading failed - ${errorMessage}`);
8872
9351
  }
8873
9352
  }
9353
+ /**
9354
+ * Set sessionToken
9355
+ * Developer Client -> Developer Server -> AvatarKit Server -> return sessionToken (max 1 hour validity)
9356
+ * Include in WebSocket Headers for avatar WebSocket service authentication
9357
+ */
8874
9358
  static setSessionToken(token) {
8875
9359
  idManager.setSessionToken(token);
8876
9360
  }
9361
+ /**
9362
+ * Set userId
9363
+ * Optional interface for developers, SDK includes this in telemetry logs
9364
+ */
8877
9365
  static setUserId(userId) {
8878
9366
  idManager.setUserId(userId);
8879
9367
  }
9368
+ // 只读属性
8880
9369
  static get isInitialized() {
8881
9370
  return this._isInitialized;
8882
9371
  }
@@ -8895,17 +9384,32 @@ class AvatarSDK {
8895
9384
  static get version() {
8896
9385
  return this._version;
8897
9386
  }
9387
+ /**
9388
+ * 获取播放模式(根据 drivingServiceMode)
9389
+ * @internal
9390
+ */
8898
9391
  static getPlaybackMode() {
8899
9392
  var _a;
8900
9393
  return ((_a = this._configuration) == null ? void 0 : _a.drivingServiceMode) ?? DrivingServiceMode.sdk;
8901
9394
  }
9395
+ /**
9396
+ * 获取音频格式配置(带默认值)
9397
+ * @internal
9398
+ */
8902
9399
  static getAudioFormat() {
8903
9400
  var _a;
8904
9401
  return ((_a = this._configuration) == null ? void 0 : _a.audioFormat) ?? { channelCount: 1, sampleRate: 16e3 };
8905
9402
  }
9403
+ /**
9404
+ * 获取AvatarCore实例(仅供 SDK 内部使用)
9405
+ * @internal
9406
+ */
8906
9407
  static getAvatarCore() {
8907
9408
  return this._avatarCore;
8908
9409
  }
9410
+ /**
9411
+ * Cleanup resources
9412
+ */
8909
9413
  static cleanup() {
8910
9414
  if (!this._isInitialized) {
8911
9415
  return;
@@ -8926,6 +9430,9 @@ class AvatarSDK {
8926
9430
  logger.error("Failed to cleanup AvatarSDK:", error instanceof Error ? error.message : String(error));
8927
9431
  }
8928
9432
  }
9433
+ /**
9434
+ * 从远程配置接口获取SDK配置(已简化,逻辑移到 config/sdk-config-loader.ts)
9435
+ */
8929
9436
  static async _fetchSdkConfig() {
8930
9437
  try {
8931
9438
  this._dynamicSdkConfig = await fetchSdkConfig(this._version);
@@ -8937,6 +9444,10 @@ class AvatarSDK {
8937
9444
  });
8938
9445
  }
8939
9446
  }
9447
+ /**
9448
+ * 获取环境配置(对齐 web 应用的 region-config)
9449
+ * @internal
9450
+ */
8940
9451
  static getEnvironmentConfig() {
8941
9452
  if (!this._configuration) {
8942
9453
  throw new Error("AvatarSDK not initialized");
@@ -8961,10 +9472,10 @@ class AvatarSDK {
8961
9472
  }
8962
9473
  __publicField(AvatarSDK, "_isInitialized", false);
8963
9474
  __publicField(AvatarSDK, "_configuration", null);
8964
- __publicField(AvatarSDK, "_version", "1.0.0-beta.67");
9475
+ __publicField(AvatarSDK, "_version", "1.0.0-beta.69");
8965
9476
  __publicField(AvatarSDK, "_avatarCore", null);
8966
9477
  __publicField(AvatarSDK, "_dynamicSdkConfig", null);
8967
- const AvatarSDK$1 = Object.freeze(Object.defineProperty({
9478
+ const AvatarSDK$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
8968
9479
  __proto__: null,
8969
9480
  AvatarSDK
8970
9481
  }, Symbol.toStringTag, { value: "Module" }));
@@ -9936,6 +10447,7 @@ class EventEmitter {
9936
10447
  }
9937
10448
  }
9938
10449
  }
10450
+ // eslint-disable-next-line ts/no-explicit-any
9939
10451
  emit(event, ...args) {
9940
10452
  const handlers = this.events.get(event);
9941
10453
  if (handlers) {
@@ -9951,6 +10463,7 @@ class EventEmitter {
9951
10463
  }
9952
10464
  }
9953
10465
  class AnimationWebSocketClient extends EventEmitter {
10466
+ // v2 协议:标记会话是否已配置
9954
10467
  constructor(options) {
9955
10468
  super();
9956
10469
  __publicField(this, "wsUrl");
@@ -9971,6 +10484,9 @@ class AnimationWebSocketClient extends EventEmitter {
9971
10484
  this.appId = options.appId;
9972
10485
  this.clientId = options.clientId;
9973
10486
  }
10487
+ /**
10488
+ * 连接WebSocket
10489
+ */
9974
10490
  async connect(characterId) {
9975
10491
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
9976
10492
  logger.log("[AnimationWebSocketClient] Already connected");
@@ -9996,6 +10512,9 @@ class AnimationWebSocketClient extends EventEmitter {
9996
10512
  throw error;
9997
10513
  }
9998
10514
  }
10515
+ /**
10516
+ * 断开连接
10517
+ */
9999
10518
  disconnect() {
10000
10519
  if (this.ws) {
10001
10520
  this.ws.close(1e3, "Normal closure");
@@ -10013,6 +10532,10 @@ class AnimationWebSocketClient extends EventEmitter {
10013
10532
  }
10014
10533
  logger.log("[AnimationWebSocketClient] Disconnected");
10015
10534
  }
10535
+ /**
10536
+ * 发送音频数据
10537
+ * @param conversationId - 会话ID(在 protobuf 协议中映射为 reqId 字段)
10538
+ */
10016
10539
  sendAudioData(conversationId, audioData, end) {
10017
10540
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
10018
10541
  logger.error("[AnimationWebSocketClient] WebSocket not connected");
@@ -10048,15 +10571,26 @@ class AnimationWebSocketClient extends EventEmitter {
10048
10571
  return false;
10049
10572
  }
10050
10573
  }
10574
+ /**
10575
+ * 生成会话ID
10576
+ * 使用统一的会话ID生成规则:YYYYMMDDHHmmss_nanoid
10577
+ */
10051
10578
  generateConversationId() {
10052
10579
  return idManager.generateNewConversationId();
10053
10580
  }
10581
+ /**
10582
+ * 获取连接状态
10583
+ */
10054
10584
  isConnected() {
10055
10585
  return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
10056
10586
  }
10587
+ /**
10588
+ * 获取当前角色ID
10589
+ */
10057
10590
  getCurrentCharacterId() {
10058
10591
  return this.currentCharacterId;
10059
10592
  }
10593
+ // ========== 私有方法 ==========
10060
10594
  buildWebSocketUrl(characterId) {
10061
10595
  const url = new URL(this.wsUrl);
10062
10596
  url.searchParams.set("id", characterId);
@@ -10183,6 +10717,9 @@ class AnimationWebSocketClient extends EventEmitter {
10183
10717
  }
10184
10718
  });
10185
10719
  }
10720
+ /**
10721
+ * 清理 URL 用于日志记录(隐藏敏感信息)
10722
+ */
10186
10723
  sanitizeUrlForLog(url) {
10187
10724
  try {
10188
10725
  const urlObj = new URL(url);
@@ -10199,6 +10736,9 @@ class AnimationWebSocketClient extends EventEmitter {
10199
10736
  return url.length > 100 ? `${url.substring(0, 100)}...` : url;
10200
10737
  }
10201
10738
  }
10739
+ /**
10740
+ * v2 协议:配置会话(发送采样率等参数)
10741
+ */
10202
10742
  configureSession() {
10203
10743
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
10204
10744
  logger.error("[AnimationWebSocketClient] Cannot configure session: WebSocket not open");
@@ -10211,6 +10751,7 @@ class AnimationWebSocketClient extends EventEmitter {
10211
10751
  clientConfigureSession: {
10212
10752
  sampleRate: audioFormatConfig.sampleRate,
10213
10753
  bitrate: 0,
10754
+ // 根据实际需求设置
10214
10755
  audioFormat: AudioFormat.AUDIO_FORMAT_PCM_S16LE,
10215
10756
  transportCompression: TransportCompression.TRANSPORT_COMPRESSION_NONE
10216
10757
  }
@@ -10285,13 +10826,18 @@ class NetworkLayer {
10285
10826
  constructor(dataController) {
10286
10827
  __publicField(this, "wsClient");
10287
10828
  __publicField(this, "dataController");
10829
+ // 组合播放层
10288
10830
  __publicField(this, "currentConversationId", null);
10289
10831
  __publicField(this, "audioMetrics", this.createAudioMetrics());
10290
10832
  __publicField(this, "isFallbackMode", false);
10833
+ // 连接超时降级模式标记
10291
10834
  __publicField(this, "isConnecting", false);
10835
+ // 避免并发连接
10836
+ // 驱动服务心跳检测相关
10292
10837
  __publicField(this, "heartbeatTimer", null);
10293
10838
  __publicField(this, "visibilityChangeHandler", null);
10294
10839
  __publicField(this, "heartbeatFailureCount", 0);
10840
+ // 心跳失败计数
10295
10841
  __publicField(this, "HEARTBEAT_INTERVAL", 12e4);
10296
10842
  this.dataController = dataController;
10297
10843
  const config = AvatarSDK.getEnvironmentConfig();
@@ -10305,10 +10851,17 @@ class NetworkLayer {
10305
10851
  this.setupWebSocketListeners();
10306
10852
  this.startHeartbeatCheck();
10307
10853
  }
10854
+ // 每2分钟检查一次
10855
+ /**
10856
+ * 获取音频字节每秒(根据配置的采样率动态计算)
10857
+ */
10308
10858
  getAudioBytesPerSecond() {
10309
10859
  const audioFormat = AvatarSDK.getAudioFormat();
10310
10860
  return audioFormat.sampleRate * 2;
10311
10861
  }
10862
+ /**
10863
+ * 连接服务
10864
+ */
10312
10865
  async connect(characterId) {
10313
10866
  var _a, _b, _c, _d;
10314
10867
  if (this.isConnecting) {
@@ -10352,9 +10905,17 @@ class NetworkLayer {
10352
10905
  this.isConnecting = false;
10353
10906
  }
10354
10907
  }
10908
+ /**
10909
+ * 检查是否可以发送音频数据(包括降级模式)
10910
+ * @internal
10911
+ */
10355
10912
  canSend() {
10356
10913
  return this.isFallbackMode || this.dataController.connected;
10357
10914
  }
10915
+ /**
10916
+ * 发送音频数据到服务器
10917
+ * 注意:打断逻辑由 AvatarController.send() 统一处理,这里只负责网络通信
10918
+ */
10358
10919
  sendAudioData(audioData, isLast) {
10359
10920
  var _a, _b;
10360
10921
  if (!this.currentConversationId) {
@@ -10400,6 +10961,9 @@ class NetworkLayer {
10400
10961
  return;
10401
10962
  }
10402
10963
  }
10964
+ /**
10965
+ * 断开连接
10966
+ */
10403
10967
  disconnect() {
10404
10968
  this.isFallbackMode = false;
10405
10969
  this.isConnecting = false;
@@ -10409,13 +10973,23 @@ class NetworkLayer {
10409
10973
  idManager.clearConnectionId();
10410
10974
  this.currentConversationId = null;
10411
10975
  }
10976
+ /**
10977
+ * 获取当前会话ID
10978
+ */
10412
10979
  getCurrentConversationId() {
10413
10980
  return this.currentConversationId;
10414
10981
  }
10982
+ /**
10983
+ * 重置会话ID(用于打断后清理)
10984
+ */
10415
10985
  resetConversationId() {
10416
10986
  this.currentConversationId = null;
10417
10987
  this.resetAudioMetrics();
10418
10988
  }
10989
+ /**
10990
+ * 设置 WebSocket 事件监听器
10991
+ * Safety: Remove existing listeners before adding new ones to prevent duplicates
10992
+ */
10419
10993
  setupWebSocketListeners() {
10420
10994
  this.wsClient.removeAllListeners();
10421
10995
  this.wsClient.on("sessionConfirmed", (connectionId) => {
@@ -10450,6 +11024,9 @@ class NetworkLayer {
10450
11024
  this.handleMessage(message);
10451
11025
  });
10452
11026
  }
11027
+ /**
11028
+ * 处理接收到的消息
11029
+ */
10453
11030
  handleMessage(message) {
10454
11031
  try {
10455
11032
  switch (message.type) {
@@ -10465,6 +11042,9 @@ class NetworkLayer {
10465
11042
  logger.error("[NetworkLayer] Failed to handle message:", message2);
10466
11043
  }
10467
11044
  }
11045
+ /**
11046
+ * 处理动画消息
11047
+ */
10468
11048
  handleAnimationMessage(message) {
10469
11049
  if (!message.serverResponseAnimation) {
10470
11050
  logger.error("[NetworkLayer] Invalid animation message");
@@ -10514,6 +11094,9 @@ class NetworkLayer {
10514
11094
  });
10515
11095
  }
10516
11096
  }
11097
+ /**
11098
+ * 处理错误消息
11099
+ */
10517
11100
  handleErrorMessage(message) {
10518
11101
  var _a, _b;
10519
11102
  if (!message.serverError) {
@@ -10558,6 +11141,9 @@ class NetworkLayer {
10558
11141
  this.dataController.yieldKeyframes([], conversationId);
10559
11142
  }
10560
11143
  }
11144
+ /**
11145
+ * 创建音频指标
11146
+ */
10561
11147
  createAudioMetrics() {
10562
11148
  return {
10563
11149
  accumulatedBytes: 0,
@@ -10569,9 +11155,16 @@ class NetworkLayer {
10569
11155
  didReportLatency: false
10570
11156
  };
10571
11157
  }
11158
+ /**
11159
+ * 重置音频指标
11160
+ */
10572
11161
  resetAudioMetrics() {
10573
11162
  this.audioMetrics = this.createAudioMetrics();
10574
11163
  }
11164
+ /**
11165
+ * 上报 driving_service_latency 事件
11166
+ * 每轮会话只上报一次(在收到首帧时)
11167
+ */
10575
11168
  reportDrivingServiceLatency(conversationId) {
10576
11169
  if (!conversationId) {
10577
11170
  return;
@@ -10580,12 +11173,21 @@ class NetworkLayer {
10580
11173
  logEvent("driving_service_latency", "info", {
10581
11174
  req_id: conversationId,
10582
11175
  dsm: "sdk",
11176
+ // 写死 sdk
10583
11177
  tap_0: metrics.startTimestamp || 0,
11178
+ // 必须有非零值
10584
11179
  tap_1: metrics.tap1Timestamp || 0,
11180
+ // 缺省值为 0
10585
11181
  tap_2: metrics.tap2Timestamp || 0,
11182
+ // 缺省值为 0
10586
11183
  tap_f: metrics.recvFirstFlameTimestamp || 0
11184
+ // 必须有非零值
10587
11185
  });
10588
11186
  }
11187
+ /**
11188
+ * 启动驱动服务心跳检测(每2分钟检查一次连接状态)
11189
+ * @private
11190
+ */
10589
11191
  startHeartbeatCheck() {
10590
11192
  this.stopHeartbeatCheck();
10591
11193
  this.heartbeatTimer = window.setInterval(() => {
@@ -10595,6 +11197,10 @@ class NetworkLayer {
10595
11197
  }, this.HEARTBEAT_INTERVAL);
10596
11198
  this.setupVisibilityListener();
10597
11199
  }
11200
+ /**
11201
+ * 停止驱动服务心跳检测
11202
+ * @private
11203
+ */
10598
11204
  stopHeartbeatCheck() {
10599
11205
  if (this.heartbeatTimer !== null) {
10600
11206
  clearInterval(this.heartbeatTimer);
@@ -10603,6 +11209,11 @@ class NetworkLayer {
10603
11209
  this.removeVisibilityListener();
10604
11210
  this.heartbeatFailureCount = 0;
10605
11211
  }
11212
+ /**
11213
+ * 执行心跳检查:检测 WebSocket 连接状态
11214
+ * 如果连接失败 3 次,上报 heartbeat_failed 事件
11215
+ * @private
11216
+ */
10606
11217
  performHeartbeatCheck() {
10607
11218
  try {
10608
11219
  const isConnected = this.wsClient.isConnected();
@@ -10631,6 +11242,10 @@ class NetworkLayer {
10631
11242
  }
10632
11243
  }
10633
11244
  }
11245
+ /**
11246
+ * 设置页面可见性监听器(用于页面重新可见时立即执行心跳检查)
11247
+ * @private
11248
+ */
10634
11249
  setupVisibilityListener() {
10635
11250
  if (typeof window === "undefined" || typeof document === "undefined") {
10636
11251
  return;
@@ -10643,6 +11258,10 @@ class NetworkLayer {
10643
11258
  };
10644
11259
  document.addEventListener("visibilitychange", this.visibilityChangeHandler);
10645
11260
  }
11261
+ /**
11262
+ * 移除页面可见性监听器
11263
+ * @private
11264
+ */
10646
11265
  removeVisibilityListener() {
10647
11266
  if (typeof window === "undefined" || typeof document === "undefined" || !this.visibilityChangeHandler) {
10648
11267
  return;
@@ -10652,35 +11271,56 @@ class NetworkLayer {
10652
11271
  }
10653
11272
  }
10654
11273
  class AvatarController {
11274
+ // 16kHz * 2 bytes per sample
10655
11275
  constructor(avatar, options) {
11276
+ // ========== Configuration and Composition ==========
10656
11277
  __publicField(this, "networkLayer");
10657
11278
  __publicField(this, "playbackMode");
10658
11279
  __publicField(this, "avatar");
11280
+ // ========== Player Management ==========
10659
11281
  __publicField(this, "animationPlayer", null);
10660
11282
  __publicField(this, "currentKeyframes", []);
10661
11283
  __publicField(this, "pendingAudioChunks", []);
10662
11284
  __publicField(this, "isPlaying", false);
10663
11285
  __publicField(this, "isStartingPlayback", false);
11286
+ // 防止重复调用 startStreamingPlayback
11287
+ // ========== State Management ==========
10664
11288
  __publicField(this, "isConnected", false);
10665
11289
  __publicField(this, "currentState", AvatarState.idle);
11290
+ // ========== Conversation ID Management (for host mode) ==========
10666
11291
  __publicField(this, "currentConversationId", null);
10667
11292
  __publicField(this, "reqEnd", false);
11293
+ // ========== Event System ==========
10668
11294
  __publicField(this, "onConnectionState", null);
10669
11295
  __publicField(this, "onConversationState", null);
10670
11296
  __publicField(this, "onError", null);
10671
11297
  __publicField(this, "eventListeners", /* @__PURE__ */ new Map());
11298
+ // ========== Callbacks ==========
10672
11299
  __publicField(this, "renderCallback");
10673
11300
  __publicField(this, "characterHandle", null);
11301
+ // Character handle for multi-character support
10674
11302
  __publicField(this, "characterId", null);
11303
+ // Character ID for multi-character support (used for eye tracking)
11304
+ // ========== Post-processing Configuration ==========
10675
11305
  __publicField(this, "postProcessingConfig", null);
11306
+ // ========== Playback Loop ==========
10676
11307
  __publicField(this, "playbackLoopId", null);
10677
11308
  __publicField(this, "lastRenderedFrameIndex", -1);
10678
11309
  __publicField(this, "keyframesOffset", 0);
11310
+ // Offset to track how many frames were removed from the beginning
10679
11311
  __publicField(this, "MAX_KEYFRAMES", 5e3);
11312
+ // Maximum keyframes to keep in memory during playback
10680
11313
  __publicField(this, "KEYFRAMES_CLEANUP_THRESHOLD", 3e3);
11314
+ // Cleanup threshold (keep some buffer)
11315
+ // 日志控制:避免刷屏
10681
11316
  __publicField(this, "lastSyncLogTime", 0);
11317
+ // 上次同步状态日志时间
10682
11318
  __publicField(this, "lastOutOfBoundsState", false);
11319
+ // 上次是否超出范围的状态
11320
+ // ========== Audio Only Mode ==========
10683
11321
  __publicField(this, "isAudioOnlyMode", false);
11322
+ // 音频独立播放模式标志
11323
+ // ========== Playback Stuck Detection ==========
10684
11324
  __publicField(this, "playbackStuckCheckState", {
10685
11325
  audioTimeZeroCount: 0,
10686
11326
  lastAudioTime: 0,
@@ -10688,8 +11328,12 @@ class AvatarController {
10688
11328
  reported: false
10689
11329
  });
10690
11330
  __publicField(this, "MAX_AUDIO_TIME_ZERO_COUNT", 60);
11331
+ // 约 1 秒(60fps)
10691
11332
  __publicField(this, "MAX_AUDIO_TIME_STUCK_COUNT", 60);
11333
+ // 约 1 秒(60fps)
10692
11334
  __publicField(this, "AUDIO_TIME_STUCK_THRESHOLD", 1e-3);
11335
+ // 1ms,小于此值视为卡住
11336
+ // ========== Host Mode Latency Metrics ==========
10693
11337
  __publicField(this, "hostModeMetrics", {
10694
11338
  accumulatedBytes: 0,
10695
11339
  startTimestamp: 0,
@@ -10706,21 +11350,46 @@ class AvatarController {
10706
11350
  this.networkLayer = new NetworkLayer(this);
10707
11351
  }
10708
11352
  }
11353
+ // ========== Internal Accessors (for NetworkLayer and AvatarView) ==========
11354
+ /**
11355
+ * Get Avatar ID (for NetworkLayer use)
11356
+ * @internal
11357
+ */
10709
11358
  getAvatarId() {
10710
11359
  return this.avatar.id;
10711
11360
  }
11361
+ /**
11362
+ * Get playback state (for NetworkLayer use)
11363
+ * @internal
11364
+ */
10712
11365
  getIsPlaying() {
10713
11366
  return this.isPlaying;
10714
11367
  }
11368
+ /**
11369
+ * Set connection state (for NetworkLayer use)
11370
+ * @internal
11371
+ */
10715
11372
  setConnected(connected) {
10716
11373
  this.isConnected = connected;
10717
11374
  }
11375
+ /**
11376
+ * Get connection state
11377
+ * @internal
11378
+ */
10718
11379
  get connected() {
10719
11380
  return this.isConnected;
10720
11381
  }
11382
+ /**
11383
+ * Get current state
11384
+ * @internal
11385
+ */
10721
11386
  get state() {
10722
11387
  return this.currentState;
10723
11388
  }
11389
+ /**
11390
+ * 将内部 AvatarState 映射到外部 ConversationState
11391
+ * @internal
11392
+ */
10724
11393
  mapToConversationState(avatarState) {
10725
11394
  switch (avatarState) {
10726
11395
  case AvatarState.idle:
@@ -10735,12 +11404,35 @@ class AvatarController {
10735
11404
  return ConversationState.idle;
10736
11405
  }
10737
11406
  }
11407
+ /**
11408
+ * Get animation player instance
11409
+ * @internal
11410
+ */
10738
11411
  getAnimationPlayer() {
10739
11412
  return this.animationPlayer;
10740
11413
  }
11414
+ /**
11415
+ * Get current conversation ID
11416
+ * Returns the current conversation ID for the active audio session
11417
+ * @returns Current conversation ID, or null if no active session
11418
+ */
10741
11419
  getCurrentConversationId() {
10742
11420
  return this.getEffectiveConversationId();
10743
11421
  }
11422
+ // ========== Audio Context Initialization ==========
11423
+ /**
11424
+ * Initialize audio context (must be called in user gesture context)
11425
+ *
11426
+ * This method must be called before any audio operations (send, yieldAudioData, etc.)
11427
+ * to ensure AudioContext is created and initialized in a user gesture context.
11428
+ *
11429
+ * @example
11430
+ * // In user click handler
11431
+ * button.addEventListener('click', async () => {
11432
+ * await avatarView.controller.initializeAudioContext()
11433
+ * // Now you can safely use send() or yieldAudioData()
11434
+ * })
11435
+ */
10744
11436
  async initializeAudioContext() {
10745
11437
  var _a;
10746
11438
  if ((_a = this.animationPlayer) == null ? void 0 : _a.isStreamingReady()) {
@@ -10773,6 +11465,10 @@ class AvatarController {
10773
11465
  }
10774
11466
  }
10775
11467
  }
11468
+ /**
11469
+ * Check if audio context is initialized
11470
+ * @throws AvatarError if audio context is not initialized
11471
+ */
10776
11472
  checkAudioContextInitialized() {
10777
11473
  var _a;
10778
11474
  if (!((_a = this.animationPlayer) == null ? void 0 : _a.isStreamingReady())) {
@@ -10782,6 +11478,10 @@ class AvatarController {
10782
11478
  );
10783
11479
  }
10784
11480
  }
11481
+ // ========== SDK Mode Interface ==========
11482
+ /**
11483
+ * Start service (SDK mode only)
11484
+ */
10785
11485
  async start() {
10786
11486
  if (!this.networkLayer) {
10787
11487
  throw new AvatarError(
@@ -10792,6 +11492,11 @@ class AvatarController {
10792
11492
  this.checkAudioContextInitialized();
10793
11493
  await this.networkLayer.connect(this.avatar.id);
10794
11494
  }
11495
+ /**
11496
+ * Send audio to server (SDK mode only)
11497
+ * Also cache to data layer for playback
11498
+ * @returns conversationId - Conversation ID for this audio session
11499
+ */
10795
11500
  send(audioData, end = false) {
10796
11501
  var _a, _b, _c, _d;
10797
11502
  try {
@@ -10827,6 +11532,9 @@ class AvatarController {
10827
11532
  }
10828
11533
  return this.networkLayer.getCurrentConversationId();
10829
11534
  }
11535
+ /**
11536
+ * Close service (SDK mode only)
11537
+ */
10830
11538
  close() {
10831
11539
  var _a;
10832
11540
  if (this.isPlaying || this.currentState === AvatarState.paused) {
@@ -10842,6 +11550,15 @@ class AvatarController {
10842
11550
  this.isConnected = false;
10843
11551
  (_a = this.onConnectionState) == null ? void 0 : _a.call(this, ConnectionState.disconnected);
10844
11552
  }
11553
+ // ========== Host Mode Interface ==========
11554
+ /**
11555
+ * Playback existing audio and animation data (host mode)
11556
+ * Starts a new conversation by generating a new conversation ID and interrupting any existing conversation
11557
+ * @param initialAudioChunks - Existing audio chunks to playback
11558
+ * @param initialKeyframes - Existing animation keyframes to playback
11559
+ * @returns conversationId - New conversation ID for this conversation session
11560
+ * @internal
11561
+ */
10845
11562
  async playback(initialAudioChunks, initialKeyframes) {
10846
11563
  this.checkAudioContextInitialized();
10847
11564
  if (this.isPlaying || this.currentConversationId) {
@@ -10875,6 +11592,11 @@ class AvatarController {
10875
11592
  }
10876
11593
  return this.currentConversationId;
10877
11594
  }
11595
+ /**
11596
+ * Send audio data (host mode)
11597
+ * Stream additional audio data after playback()
11598
+ * @returns conversationId - Conversation ID for this audio session
11599
+ */
10878
11600
  yieldAudioData(data, isLast = false) {
10879
11601
  var _a, _b, _c;
10880
11602
  try {
@@ -10923,6 +11645,15 @@ class AvatarController {
10923
11645
  }
10924
11646
  return this.currentConversationId;
10925
11647
  }
11648
+ /**
11649
+ * Send animation keyframes (host mode or SDK mode)
11650
+ * Stream additional animation data after playback()
11651
+ *
11652
+ * Public API: accepts binary data array (protobuf encoded Message array)
11653
+ * @param keyframesDataArray - Animation keyframes binary data array (each element is a protobuf encoded Message) or empty array to trigger audio-only mode
11654
+ * @param conversationId - Conversation ID (required). If conversationId doesn't match current conversationId, keyframes will be discarded.
11655
+ * Use getCurrentConversationId() to get the current conversationId.
11656
+ */
10926
11657
  yieldFramesData(keyframesDataArray, conversationId) {
10927
11658
  var _a, _b;
10928
11659
  if (!keyframesDataArray || keyframesDataArray.length === 0) {
@@ -10963,6 +11694,18 @@ class AvatarController {
10963
11694
  }
10964
11695
  this.yieldKeyframes(allKeyframes, conversationId);
10965
11696
  }
11697
+ /**
11698
+ * Send animation keyframes (host mode)
11699
+ * Stream animation keyframes data after yieldAudioData()
11700
+ *
11701
+ * ⚠️ **Not recommended**: Prefer using `yieldFramesData()` method, which accepts binary data (protobuf encoded Message) with better performance.
11702
+ * This method is only for scenarios where you already have decoded KeyframeData arrays.
11703
+ *
11704
+ * Public API: accepts decoded keyframes
11705
+ * @param keyframes - Animation keyframes array (KeyframeData[]) or empty array to trigger audio-only mode
11706
+ * @param conversationId - Conversation ID (required). If conversationId doesn't match current conversationId, keyframes will be discarded.
11707
+ * Use getCurrentConversationId() to get the current conversationId.
11708
+ */
10966
11709
  yieldKeyframes(keyframes, conversationId) {
10967
11710
  if (!conversationId || typeof conversationId !== "string") {
10968
11711
  logger.error(`[AvatarController] yieldKeyframes requires a valid conversationId. Use getCurrentConversationId() to get the current conversationId.`);
@@ -11003,8 +11746,9 @@ class AvatarController {
11003
11746
  this.enableAudioOnlyMode();
11004
11747
  return;
11005
11748
  }
11749
+ const flameKeyframes = keyframes;
11006
11750
  if (this.currentKeyframes.length === 0) {
11007
- this.currentKeyframes = keyframes;
11751
+ this.currentKeyframes = flameKeyframes;
11008
11752
  if (this.playbackMode === DrivingServiceMode.host && !this.hostModeMetrics.didRecvFirstFlame) {
11009
11753
  this.hostModeMetrics.didRecvFirstFlame = true;
11010
11754
  this.hostModeMetrics.recvFirstFlameTimestamp = Date.now();
@@ -11014,7 +11758,7 @@ class AvatarController {
11014
11758
  }
11015
11759
  }
11016
11760
  } else {
11017
- this.currentKeyframes.push(...keyframes);
11761
+ this.currentKeyframes.push(...flameKeyframes);
11018
11762
  }
11019
11763
  this.emit("keyframesUpdate", this.currentKeyframes);
11020
11764
  if (!this.isPlaying && !this.isStartingPlayback && this.pendingAudioChunks.length > 0 && this.currentKeyframes.length > 0) {
@@ -11026,6 +11770,11 @@ class AvatarController {
11026
11770
  });
11027
11771
  }
11028
11772
  }
11773
+ // ========== Common Interface ==========
11774
+ /**
11775
+ * Pause playback (can be resumed later)
11776
+ * Pause audio playback and stop render loop, but preserve all state (keyframes, audio buffers, etc.)
11777
+ */
11029
11778
  pause() {
11030
11779
  var _a, _b;
11031
11780
  if (!this.isPlaying || this.currentState === AvatarState.paused) {
@@ -11038,6 +11787,11 @@ class AvatarController {
11038
11787
  (_b = this.onConversationState) == null ? void 0 : _b.call(this, this.mapToConversationState(AvatarState.paused));
11039
11788
  logger.log("[AvatarController] Playback paused");
11040
11789
  }
11790
+ /**
11791
+ * Resume playback (from paused state)
11792
+ * Resume audio playback and restart render loop
11793
+ * Animation will continue from paused frame (because animation time base comes from audio, will auto-sync)
11794
+ */
11041
11795
  async resume() {
11042
11796
  var _a, _b;
11043
11797
  if (!this.isPlaying || this.currentState !== AvatarState.paused) {
@@ -11050,6 +11804,9 @@ class AvatarController {
11050
11804
  (_b = this.onConversationState) == null ? void 0 : _b.call(this, this.mapToConversationState(AvatarState.playing));
11051
11805
  logger.log("[AvatarController] Playback resumed");
11052
11806
  }
11807
+ /**
11808
+ * Interrupt current playback
11809
+ */
11053
11810
  interrupt() {
11054
11811
  var _a;
11055
11812
  if (this.currentState === AvatarState.paused) {
@@ -11062,6 +11819,9 @@ class AvatarController {
11062
11819
  this.resetConversationIdState();
11063
11820
  this.isAudioOnlyMode = false;
11064
11821
  }
11822
+ /**
11823
+ * Clear all data and resources
11824
+ */
11065
11825
  clear() {
11066
11826
  var _a, _b;
11067
11827
  if (this.isPlaying) {
@@ -11079,6 +11839,11 @@ class AvatarController {
11079
11839
  }
11080
11840
  this.reqEnd = false;
11081
11841
  }
11842
+ /**
11843
+ * Dispose controller, clean up all callbacks to avoid memory leaks
11844
+ * Should be called when AvatarView.dispose()
11845
+ * @internal
11846
+ */
11082
11847
  dispose() {
11083
11848
  this.onConnectionState = null;
11084
11849
  this.onConversationState = null;
@@ -11086,6 +11851,11 @@ class AvatarController {
11086
11851
  this.renderCallback = void 0;
11087
11852
  this.eventListeners.clear();
11088
11853
  }
11854
+ // ========== Internal Helper Methods ==========
11855
+ /**
11856
+ * Generate new conversation ID and log conversation started event
11857
+ * @private
11858
+ */
11089
11859
  generateAndLogNewConversationId() {
11090
11860
  const conversationId = idManager.generateNewConversationId();
11091
11861
  logEvent("character_manager", "info", {
@@ -11095,6 +11865,10 @@ class AvatarController {
11095
11865
  });
11096
11866
  return conversationId;
11097
11867
  }
11868
+ /**
11869
+ * Clear playback data (keyframes, audio chunks, and playback state)
11870
+ * @private
11871
+ */
11098
11872
  clearPlaybackData() {
11099
11873
  this.currentKeyframes = [];
11100
11874
  this.pendingAudioChunks = [];
@@ -11115,6 +11889,10 @@ class AvatarController {
11115
11889
  };
11116
11890
  }
11117
11891
  }
11892
+ /**
11893
+ * Reset conversation ID state (for both network and external modes)
11894
+ * @private
11895
+ */
11118
11896
  resetConversationIdState() {
11119
11897
  if (this.networkLayer) {
11120
11898
  this.networkLayer.resetConversationId();
@@ -11135,6 +11913,10 @@ class AvatarController {
11135
11913
  };
11136
11914
  }
11137
11915
  }
11916
+ /**
11917
+ * Get effective conversation ID (handles both SDK and host modes)
11918
+ * @private
11919
+ */
11138
11920
  getEffectiveConversationId() {
11139
11921
  var _a;
11140
11922
  if (this.playbackMode === DrivingServiceMode.sdk) {
@@ -11143,18 +11925,35 @@ class AvatarController {
11143
11925
  return this.currentConversationId;
11144
11926
  }
11145
11927
  }
11928
+ // ========== Internal Methods (for NetworkLayer and AvatarView use) ==========
11929
+ /**
11930
+ * Start streaming playback (internal method, called by NetworkLayer or playback())
11931
+ * @internal
11932
+ */
11146
11933
  startStreamingPlayback() {
11147
11934
  return this.startStreamingPlaybackInternal();
11148
11935
  }
11936
+ /**
11937
+ * Set render callback (called by AvatarView)
11938
+ * @internal
11939
+ */
11149
11940
  setRenderCallback(callback, characterHandle) {
11150
11941
  this.renderCallback = callback;
11151
11942
  if (characterHandle !== void 0) {
11152
11943
  this.characterHandle = characterHandle;
11153
11944
  }
11154
11945
  }
11946
+ /**
11947
+ * Set character ID (for multi-character support, used for eye tracking)
11948
+ * @internal
11949
+ */
11155
11950
  setCharacterId(characterId) {
11156
11951
  this.characterId = characterId;
11157
11952
  }
11953
+ /**
11954
+ * Get point cloud count of the current avatar
11955
+ * @returns Point cloud count, or null if avatar is not loaded
11956
+ */
11158
11957
  getPointCount() {
11159
11958
  const avatarCore = AvatarSDK.getAvatarCore();
11160
11959
  if (!avatarCore || !this.characterHandle) {
@@ -11162,6 +11961,11 @@ class AvatarController {
11162
11961
  }
11163
11962
  return avatarCore.getPointCount(this.characterHandle);
11164
11963
  }
11964
+ /**
11965
+ * Set post-processing configuration
11966
+ * These parameters will be applied in real-time to animation parameters returned by the server
11967
+ * @param config Post-processing configuration, or null to clear
11968
+ */
11165
11969
  setPostProcessingConfig(config) {
11166
11970
  this.postProcessingConfig = config;
11167
11971
  if (config == null ? void 0 : config.eyefocus) {
@@ -11173,6 +11977,10 @@ class AvatarController {
11173
11977
  this.rerenderCurrentFrame();
11174
11978
  }
11175
11979
  }
11980
+ /**
11981
+ * 设置完整的 eye tracking 配置到 C++
11982
+ * @private
11983
+ */
11176
11984
  async setEyeTrackingConfig(eyefocus) {
11177
11985
  const avatarCore = AvatarSDK.getAvatarCore();
11178
11986
  if (!avatarCore || !this.characterId) {
@@ -11196,6 +12004,10 @@ class AvatarController {
11196
12004
  logger.warn("[AvatarController] Failed to set eye tracking config:", error instanceof Error ? error.message : String(error));
11197
12005
  }
11198
12006
  }
12007
+ /**
12008
+ * 重新渲染当前帧(用于暂停状态下更新后处理参数或相机配置)
12009
+ * @internal
12010
+ */
11199
12011
  async rerenderCurrentFrameIfPaused() {
11200
12012
  if (this.currentState !== AvatarState.paused || !this.renderCallback) {
11201
12013
  return;
@@ -11228,9 +12040,17 @@ class AvatarController {
11228
12040
  logger.error("[AvatarController] Failed to rerender current frame:", error instanceof Error ? error.message : String(error));
11229
12041
  }
11230
12042
  }
12043
+ /**
12044
+ * 重新渲染当前帧(用于暂停状态下更新后处理参数)
12045
+ * @private
12046
+ */
11231
12047
  async rerenderCurrentFrame() {
11232
12048
  await this.rerenderCurrentFrameIfPaused();
11233
12049
  }
12050
+ /**
12051
+ * Transition complete notification (called by AvatarView)
12052
+ * @internal
12053
+ */
11234
12054
  onTransitionComplete() {
11235
12055
  var _a;
11236
12056
  const streamingPlayer = (_a = this.animationPlayer) == null ? void 0 : _a.getStreamingPlayer();
@@ -11238,6 +12058,11 @@ class AvatarController {
11238
12058
  streamingPlayer.play();
11239
12059
  }
11240
12060
  }
12061
+ /**
12062
+ * Set audio playback volume
12063
+ * Note: This only controls the avatar audio player volume, not the system volume
12064
+ * @param volume Volume value, range from 0.0 to 1.0 (0.0 = mute, 1.0 = max volume)
12065
+ */
11241
12066
  setVolume(volume) {
11242
12067
  var _a;
11243
12068
  if (volume < 0 || volume > 1) {
@@ -11251,10 +12076,18 @@ class AvatarController {
11251
12076
  volume
11252
12077
  });
11253
12078
  }
12079
+ /**
12080
+ * Get current audio playback volume
12081
+ * @returns Current volume value (0.0 - 1.0)
12082
+ */
11254
12083
  getVolume() {
11255
12084
  var _a;
11256
12085
  return ((_a = this.animationPlayer) == null ? void 0 : _a.getVolume()) ?? 1;
11257
12086
  }
12087
+ /**
12088
+ * Provide interface for AvatarView to register internal events
12089
+ * @internal
12090
+ */
11258
12091
  setupInternalEventListeners(callbacks) {
11259
12092
  if (callbacks.onKeyframesUpdate) {
11260
12093
  this.registerEventListener("keyframesUpdate", callbacks.onKeyframesUpdate);
@@ -11269,6 +12102,10 @@ class AvatarController {
11269
12102
  this.registerEventListener("interrupt", callbacks.onInterrupt);
11270
12103
  }
11271
12104
  }
12105
+ // ========== Private Methods ==========
12106
+ /**
12107
+ * Start streaming playback (internal implementation)
12108
+ */
11272
12109
  async startStreamingPlaybackInternal() {
11273
12110
  var _a, _b, _c;
11274
12111
  this.checkAudioContextInitialized();
@@ -11336,6 +12173,18 @@ class AvatarController {
11336
12173
  this.isStartingPlayback = false;
11337
12174
  }
11338
12175
  }
12176
+ /**
12177
+ * Playback loop: Calculate animation frame based on audio time, notify render layer to render
12178
+ */
12179
+ /**
12180
+ * 检测播放是否卡住(在过渡动画完成后)
12181
+ * 注意:AudioContext suspended 的情况现在由 StreamingAudioPlayer 自动处理,不再作为触发条件
12182
+ * 此函数主要用于检测其他原因导致的播放卡住(如网络问题、音频数据问题等)
12183
+ *
12184
+ * @param audioTime 当前音频时间
12185
+ * @returns true 如果检测到卡住并已上报,false 否则
12186
+ * @internal
12187
+ */
11339
12188
  checkPlaybackStuck(audioTime) {
11340
12189
  var _a, _b, _c, _d, _e2, _f;
11341
12190
  const streamingPlayer = (_a = this.animationPlayer) == null ? void 0 : _a.getStreamingPlayer();
@@ -11378,6 +12227,7 @@ class AvatarController {
11378
12227
  event: "playback_stuck_after_transition",
11379
12228
  avatar_id: this.avatar.id,
11380
12229
  conversationId: ((_e2 = this.networkLayer) == null ? void 0 : _e2.getCurrentConversationId()) || void 0,
12230
+ // 诊断信息(包含 audioContextState 用于诊断,但不作为触发条件)
11381
12231
  audioContextState,
11382
12232
  audioTime,
11383
12233
  audioTimeZero: audioTime === 0,
@@ -11488,12 +12338,22 @@ class AvatarController {
11488
12338
  };
11489
12339
  this.playbackLoopId = requestAnimationFrame(playLoop);
11490
12340
  }
12341
+ /**
12342
+ * Stop playback loop
12343
+ */
11491
12344
  stopPlaybackLoop() {
11492
12345
  if (this.playbackLoopId) {
11493
12346
  cancelAnimationFrame(this.playbackLoopId);
11494
12347
  this.playbackLoopId = null;
11495
12348
  }
11496
12349
  }
12350
+ // ========== Audio Only Mode ==========
12351
+ /**
12352
+ * 启用音频独立播放模式(当服务器错误或超时时调用)
12353
+ * 此模式下,音频会独立播放,不依赖动画数据
12354
+ * 一旦启用,本次会话后续的动画数据将被忽略
12355
+ * @private
12356
+ */
11497
12357
  enableAudioOnlyMode() {
11498
12358
  if (this.isAudioOnlyMode) {
11499
12359
  return;
@@ -11507,6 +12367,10 @@ class AvatarController {
11507
12367
  });
11508
12368
  }
11509
12369
  }
12370
+ /**
12371
+ * 音频独立播放(完全独立的逻辑,不影响正常播放流程)
12372
+ * @private
12373
+ */
11510
12374
  async startAudioOnlyPlayback() {
11511
12375
  var _a, _b;
11512
12376
  if (!this.animationPlayer) {
@@ -11553,6 +12417,11 @@ class AvatarController {
11553
12417
  throw error;
11554
12418
  }
11555
12419
  }
12420
+ /**
12421
+ * 音频监控循环(仅用于音频独立模式)
12422
+ * 只检测音频是否结束,不进行动画渲染
12423
+ * @private
12424
+ */
11556
12425
  startAudioMonitoringLoop() {
11557
12426
  if (this.playbackLoopId) {
11558
12427
  return;
@@ -11571,6 +12440,9 @@ class AvatarController {
11571
12440
  };
11572
12441
  this.playbackLoopId = requestAnimationFrame(monitorLoop);
11573
12442
  }
12443
+ /**
12444
+ * Stop playback
12445
+ */
11574
12446
  stopPlayback() {
11575
12447
  var _a;
11576
12448
  this.stopPlaybackLoop();
@@ -11585,12 +12457,19 @@ class AvatarController {
11585
12457
  this.currentState = AvatarState.idle;
11586
12458
  (_a = this.onConversationState) == null ? void 0 : _a.call(this, this.mapToConversationState(AvatarState.idle));
11587
12459
  }
12460
+ /**
12461
+ * Clean up players
12462
+ */
11588
12463
  cleanupPlayers() {
11589
12464
  if (this.animationPlayer) {
11590
12465
  this.animationPlayer.dispose();
11591
12466
  this.animationPlayer = null;
11592
12467
  }
11593
12468
  }
12469
+ /**
12470
+ * Add audio chunk to buffer
12471
+ * Note: animationPlayer should already be initialized before calling this method
12472
+ */
11594
12473
  addAudioChunkToBuffer(data, isLast) {
11595
12474
  if (!this.animationPlayer) {
11596
12475
  logger.warn("[AvatarController] animationPlayer is null in addAudioChunkToBuffer, this should not happen");
@@ -11603,18 +12482,29 @@ class AvatarController {
11603
12482
  this.pendingAudioChunks.push({ data, isLast });
11604
12483
  }
11605
12484
  }
12485
+ /**
12486
+ * Event system
12487
+ */
11606
12488
  registerEventListener(event, callback) {
11607
12489
  if (!this.eventListeners.has(event)) {
11608
12490
  this.eventListeners.set(event, /* @__PURE__ */ new Set());
11609
12491
  }
11610
12492
  this.eventListeners.get(event).add(callback);
11611
12493
  }
12494
+ /**
12495
+ * Emit event
12496
+ */
11612
12497
  emit(event, data) {
11613
12498
  const listeners = this.eventListeners.get(event);
11614
12499
  if (listeners) {
11615
12500
  listeners.forEach((callback) => callback(data));
11616
12501
  }
11617
12502
  }
12503
+ /**
12504
+ * Apply post-processing parameters to a Flame (proto format)
12505
+ * Used for transition animations
12506
+ * @internal
12507
+ */
11618
12508
  applyPostProcessingToFlame(flame) {
11619
12509
  if (!this.postProcessingConfig) {
11620
12510
  return flame;
@@ -11623,6 +12513,10 @@ class AvatarController {
11623
12513
  wasmParams = this.applyPostProcessingToParams(wasmParams);
11624
12514
  return convertWasmParamsToProtoFlame(wasmParams);
11625
12515
  }
12516
+ /**
12517
+ * Apply post-processing parameters to animation parameters
12518
+ * @private
12519
+ */
11626
12520
  applyPostProcessingToParams(baseParams) {
11627
12521
  if (!this.postProcessingConfig) {
11628
12522
  return baseParams;
@@ -11696,6 +12590,11 @@ class AvatarController {
11696
12590
  }
11697
12591
  return result2;
11698
12592
  }
12593
+ /**
12594
+ * 上报 driving_service_latency 事件(Host 模式)
12595
+ * 每轮会话只上报一次(在收到首帧时)
12596
+ * @private
12597
+ */
11699
12598
  reportDrivingServiceLatency(conversationId) {
11700
12599
  if (!conversationId || this.playbackMode !== DrivingServiceMode.host) {
11701
12600
  return;
@@ -11704,10 +12603,15 @@ class AvatarController {
11704
12603
  logEvent("driving_service_latency", "info", {
11705
12604
  req_id: conversationId,
11706
12605
  dsm: "host",
12606
+ // Host 模式
11707
12607
  tap_0: metrics.startTimestamp || 0,
12608
+ // 必须有非零值
11708
12609
  tap_1: metrics.tap1Timestamp || 0,
12610
+ // 缺省值为 0
11709
12611
  tap_2: metrics.tap2Timestamp || 0,
12612
+ // 缺省值为 0
11710
12613
  tap_f: metrics.recvFirstFlameTimestamp || 0
12614
+ // 必须有非零值
11711
12615
  });
11712
12616
  }
11713
12617
  }
@@ -11731,12 +12635,21 @@ function errorToMessage(err) {
11731
12635
  return String(err);
11732
12636
  }
11733
12637
  const _PwaCacheManager = class _PwaCacheManager {
12638
+ /**
12639
+ * 检查是否支持 Cache API
12640
+ */
11734
12641
  static isSupported() {
11735
12642
  return typeof caches !== "undefined";
11736
12643
  }
12644
+ /**
12645
+ * 获取角色缓存名称
12646
+ */
11737
12647
  static getCharacterCacheName(characterId) {
11738
12648
  return `${_PwaCacheManager.CHARACTER_CACHE_PREFIX}${characterId}${_PwaCacheManager.CHARACTER_CACHE_SUFFIX}`;
11739
12649
  }
12650
+ /**
12651
+ * 从角色缓存获取资源
12652
+ */
11740
12653
  static async getCharacterResource(characterId, url) {
11741
12654
  if (!_PwaCacheManager.isSupported()) {
11742
12655
  return null;
@@ -11756,6 +12669,9 @@ const _PwaCacheManager = class _PwaCacheManager {
11756
12669
  return null;
11757
12670
  }
11758
12671
  }
12672
+ /**
12673
+ * 将角色资源写入缓存
12674
+ */
11759
12675
  static async putCharacterResource(characterId, url, data) {
11760
12676
  if (!_PwaCacheManager.isSupported()) {
11761
12677
  return;
@@ -11775,6 +12691,9 @@ const _PwaCacheManager = class _PwaCacheManager {
11775
12691
  logger.warn(`[PwaCacheManager] Failed to put character resource to cache:`, error);
11776
12692
  }
11777
12693
  }
12694
+ /**
12695
+ * 从模板缓存获取资源
12696
+ */
11778
12697
  static async getTemplateResource(url) {
11779
12698
  if (!_PwaCacheManager.isSupported()) {
11780
12699
  return null;
@@ -11793,6 +12712,10 @@ const _PwaCacheManager = class _PwaCacheManager {
11793
12712
  return null;
11794
12713
  }
11795
12714
  }
12715
+ /**
12716
+ * 将模板资源写入缓存
12717
+ * 模板资源不设置数量限制,永久保留直到版本更新
12718
+ */
11796
12719
  static async putTemplateResource(url, data) {
11797
12720
  if (!_PwaCacheManager.isSupported()) {
11798
12721
  return;
@@ -11805,6 +12728,9 @@ const _PwaCacheManager = class _PwaCacheManager {
11805
12728
  logger.warn(`[PwaCacheManager] Failed to put template resource to cache:`, error);
11806
12729
  }
11807
12730
  }
12731
+ /**
12732
+ * 清理角色缓存
12733
+ */
11808
12734
  static async clearCharacterCache(characterId) {
11809
12735
  if (!_PwaCacheManager.isSupported()) {
11810
12736
  return;
@@ -11817,6 +12743,11 @@ const _PwaCacheManager = class _PwaCacheManager {
11817
12743
  logger.warn(`[PwaCacheManager] Failed to clear character cache:`, error);
11818
12744
  }
11819
12745
  }
12746
+ /**
12747
+ * 检查模板缓存版本,如果版本变化则清理
12748
+ * 使用独立的模板资源版本号(不依赖 SDK 版本),这样不同 SDK 版本可以共享相同模板资源的缓存
12749
+ * @returns true 如果版本变化并清理了缓存,false 否则
12750
+ */
11820
12751
  static async checkTemplateCacheVersion() {
11821
12752
  if (!_PwaCacheManager.isSupported()) {
11822
12753
  return false;
@@ -11841,14 +12772,17 @@ const _PwaCacheManager = class _PwaCacheManager {
11841
12772
  }
11842
12773
  }
11843
12774
  };
12775
+ // 模板缓存版本(独立于 SDK 版本,当模板资源更新时需要手动更新此版本号)
11844
12776
  __publicField(_PwaCacheManager, "TEMPLATE_RESOURCE_VERSION", "1.0.0");
11845
12777
  __publicField(_PwaCacheManager, "TEMPLATE_CACHE_NAME", `spatialwalk-sdk-template-cache-${_PwaCacheManager.TEMPLATE_RESOURCE_VERSION}`);
11846
12778
  __publicField(_PwaCacheManager, "TEMPLATE_VERSION_STORAGE_KEY", "spatialwalk-sdk-template-cache-version");
12779
+ // 角色缓存前缀和后缀
11847
12780
  __publicField(_PwaCacheManager, "CHARACTER_CACHE_PREFIX", "spatialwalk-sdk-character-");
11848
12781
  __publicField(_PwaCacheManager, "CHARACTER_CACHE_SUFFIX", "-cache");
12782
+ // 角色缓存 LRU 限制:最多 1000 个资源条目(支持约 250 个角色,每个角色 4 个资源)
11849
12783
  __publicField(_PwaCacheManager, "MAX_CHARACTER_CACHE_ENTRIES", 1e3);
11850
12784
  let PwaCacheManager = _PwaCacheManager;
11851
- const pwaCacheManager = Object.freeze(Object.defineProperty({
12785
+ const pwaCacheManager = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
11852
12786
  __proto__: null,
11853
12787
  PwaCacheManager
11854
12788
  }, Symbol.toStringTag, { value: "Module" }));
@@ -11943,6 +12877,11 @@ class AvatarDownloader {
11943
12877
  __publicField(this, "baseAssetsPath");
11944
12878
  this.baseAssetsPath = baseAssetsPath;
11945
12879
  }
12880
+ /**
12881
+ * Load template resources from CharacterMeta flame CDN URLs
12882
+ * Falls back to global CDN config if not provided by API
12883
+ * @internal
12884
+ */
11946
12885
  async loadTemplateResources(flameResources, progressCallback = null) {
11947
12886
  var _a, _b, _c, _d;
11948
12887
  await PwaCacheManager.checkTemplateCacheVersion();
@@ -12000,6 +12939,11 @@ class AvatarDownloader {
12000
12939
  await Promise.all(promises);
12001
12940
  return templateResources;
12002
12941
  }
12942
+ /**
12943
+ * Load global FLAME template resources from CDN
12944
+ * Uses centralized FLAME CDN config (shared across all characters)
12945
+ * @internal 供内部使用(测试、调试等场景),SDK 初始化默认使用本地打包的资源
12946
+ */
12003
12947
  async loadGlobalFlameResources(progressCallback = null) {
12004
12948
  var _a;
12005
12949
  await PwaCacheManager.checkTemplateCacheVersion();
@@ -12065,6 +13009,10 @@ class AvatarDownloader {
12065
13009
  throw error;
12066
13010
  }
12067
13011
  }
13012
+ /**
13013
+ * Load camera settings from CharacterMeta (optional)
13014
+ * @internal
13015
+ */
12068
13016
  async loadCameraSettings(characterMeta) {
12069
13017
  var _a, _b;
12070
13018
  const cameraUrl = (_b = (_a = characterMeta.camera) == null ? void 0 : _a.resource) == null ? void 0 : _b.remote;
@@ -12087,6 +13035,10 @@ class AvatarDownloader {
12087
13035
  return void 0;
12088
13036
  }
12089
13037
  }
13038
+ /**
13039
+ * Load character data from CharacterMeta (iOS compatible)
13040
+ * @internal
13041
+ */
12090
13042
  async loadCharacterData(characterMeta, options) {
12091
13043
  var _a, _b, _c, _d, _e2, _f, _g, _h, _i2, _j;
12092
13044
  const { progressCallback = null } = options || {};
@@ -12189,6 +13141,10 @@ class AvatarDownloader {
12189
13141
  });
12190
13142
  return characterData;
12191
13143
  }
13144
+ /**
13145
+ * Preload all resources (template + character data + camera info + settings)
13146
+ * @internal
13147
+ */
12192
13148
  async preloadResources(characterMeta, options) {
12193
13149
  const { progressCallback = null } = options || {};
12194
13150
  const [characterData, preloadCameraSettings] = await Promise.all([
@@ -12210,6 +13166,13 @@ class AvatarDownloader {
12210
13166
  characterSettings: characterMeta.characterSettings
12211
13167
  };
12212
13168
  }
13169
+ // ============ API Client Methods ============
13170
+ /**
13171
+ * Get AvatarKit SDK API Client (api.open.spatialwalk.top for cn, api.intl.spatialwalk.cloud for intl)
13172
+ * Used for: character details and resource URLs (public endpoints, no auth required)
13173
+ * Note: This endpoint does not require authentication, so we don't add X-App-Id or Authorization headers
13174
+ * to avoid CORS preflight requests for simple GET requests
13175
+ */
12213
13176
  getSdkApiClient() {
12214
13177
  return {
12215
13178
  async request(url, options = {}) {
@@ -12255,6 +13218,13 @@ class AvatarDownloader {
12255
13218
  }
12256
13219
  };
12257
13220
  }
13221
+ /**
13222
+ * Get single character by ID from AvatarKit SDK API (v2, iOS compatible)
13223
+ * Domain: api.open.spatialwalk.top (cn) / api.intl.spatialwalk.cloud (intl)
13224
+ * Auth: Public endpoint, no authentication required
13225
+ * Returns CharacterMeta with nested resource structure
13226
+ * @internal
13227
+ */
12258
13228
  async getCharacterById(characterId) {
12259
13229
  var _a;
12260
13230
  const startTime = Date.now();
@@ -12288,7 +13258,7 @@ class AvatarDownloader {
12288
13258
  }
12289
13259
  }
12290
13260
  }
12291
- const AvatarDownloader$1 = Object.freeze(Object.defineProperty({
13261
+ const AvatarDownloader$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
12292
13262
  __proto__: null,
12293
13263
  AvatarDownloader
12294
13264
  }, Symbol.toStringTag, { value: "Module" }));
@@ -12297,15 +13267,25 @@ const _AvatarManager = class _AvatarManager {
12297
13267
  __publicField(this, "avatarDownloader", null);
12298
13268
  __publicField(this, "avatarCache", /* @__PURE__ */ new Map());
12299
13269
  __publicField(this, "loadingPromises", /* @__PURE__ */ new Map());
13270
+ // 下载队列:确保资源下载串行执行
12300
13271
  __publicField(this, "downloadQueue", []);
12301
13272
  __publicField(this, "isDownloading", false);
12302
13273
  }
13274
+ /**
13275
+ * Access via global singleton
13276
+ */
12303
13277
  static get shared() {
12304
13278
  if (!this._instance) {
12305
13279
  this._instance = new _AvatarManager();
12306
13280
  }
12307
13281
  return this._instance;
12308
13282
  }
13283
+ /**
13284
+ * Load avatar
13285
+ * @param id Avatar ID
13286
+ * @param onProgress Progress callback
13287
+ * @returns Promise<Avatar>
13288
+ */
12309
13289
  async load(id, onProgress) {
12310
13290
  const loadingPromise = this.loadingPromises.get(id);
12311
13291
  if (loadingPromise) {
@@ -12354,6 +13334,9 @@ const _AvatarManager = class _AvatarManager {
12354
13334
  this.processDownloadQueue();
12355
13335
  return loadPromise;
12356
13336
  }
13337
+ /**
13338
+ * 处理下载队列(确保串行执行)
13339
+ */
12357
13340
  async processDownloadQueue() {
12358
13341
  if (this.isDownloading || this.downloadQueue.length === 0) {
12359
13342
  return;
@@ -12371,6 +13354,9 @@ const _AvatarManager = class _AvatarManager {
12371
13354
  this.processDownloadQueue();
12372
13355
  }
12373
13356
  }
13357
+ /**
13358
+ * 执行实际的加载逻辑(私有方法)
13359
+ */
12374
13360
  async doLoad(id, characterMeta, onProgress) {
12375
13361
  try {
12376
13362
  logger.log("[AvatarManager] Step 1: Downloading resources...");
@@ -12401,22 +13387,31 @@ const _AvatarManager = class _AvatarManager {
12401
13387
  throw error;
12402
13388
  }
12403
13389
  }
13390
+ /**
13391
+ * Get cached avatar
13392
+ * @param id Avatar ID
13393
+ * @returns Avatar instance, or undefined if not in cache
13394
+ */
12404
13395
  retrieve(id) {
12405
13396
  return this.avatarCache.get(id);
12406
13397
  }
13398
+ /**
13399
+ * Clear cached avatar for specified ID
13400
+ * @param id Avatar ID
13401
+ */
12407
13402
  clear(id) {
12408
13403
  const removed = this.avatarCache.delete(id);
12409
13404
  if (removed) {
12410
13405
  logger.log(`[AvatarManager] Cleared avatar cache for id: ${id}`);
12411
13406
  }
12412
13407
  }
13408
+ /**
13409
+ * Clear all avatar cache and resource loader cache
13410
+ */
12413
13411
  clearAll() {
12414
13412
  this.avatarCache.clear();
12415
13413
  logger.log("[AvatarManager] Cleared all avatar cache");
12416
13414
  }
12417
- clearCache() {
12418
- this.clearAll();
12419
- }
12420
13415
  };
12421
13416
  __publicField(_AvatarManager, "_instance", null);
12422
13417
  let AvatarManager = _AvatarManager;
@@ -12523,11 +13518,14 @@ class WebGLRenderer {
12523
13518
  __publicField(this, "splatCount");
12524
13519
  __publicField(this, "isInitialized");
12525
13520
  __publicField(this, "splatBufferSize");
13521
+ // 跟踪当前 buffer 大小
13522
+ // Render texture framebuffer
12526
13523
  __publicField(this, "framebuffer", null);
12527
13524
  __publicField(this, "renderTexture", null);
12528
13525
  __publicField(this, "depthBuffer", null);
12529
13526
  __publicField(this, "framebufferWidth", 0);
12530
13527
  __publicField(this, "framebufferHeight", 0);
13528
+ // Blit shader for drawing render texture to screen
12531
13529
  __publicField(this, "blitShaderProgram", null);
12532
13530
  __publicField(this, "blitUniformLocations", { offset: null, scale: null, texture: null });
12533
13531
  __publicField(this, "blitAttributeLocations", { position: 0, texCoord: 0 });
@@ -12548,11 +13546,15 @@ class WebGLRenderer {
12548
13546
  this.splatBufferSize = 0;
12549
13547
  this.alpha = alpha;
12550
13548
  }
13549
+ /**
13550
+ * 初始化 WebGL 渲染器
13551
+ */
12551
13552
  async initialize() {
12552
13553
  try {
12553
13554
  this.gl = this.canvas.getContext("webgl2", {
12554
13555
  antialias: false,
12555
13556
  alpha: this.alpha,
13557
+ // 根据 isOpaque 设置透明度
12556
13558
  premultipliedAlpha: true,
12557
13559
  powerPreference: "high-performance",
12558
13560
  preserveDrawingBuffer: false
@@ -12572,6 +13574,9 @@ class WebGLRenderer {
12572
13574
  throw error;
12573
13575
  }
12574
13576
  }
13577
+ /**
13578
+ * 设置着色器位置
13579
+ */
12575
13580
  setupShaderLocations() {
12576
13581
  const gl = this.gl;
12577
13582
  if (!gl)
@@ -12587,12 +13592,20 @@ class WebGLRenderer {
12587
13592
  };
12588
13593
  this.attributeLocations = {
12589
13594
  quadVertex: 0,
13595
+ // a_quadVertex (共享四边形顶点)
12590
13596
  position: 1,
13597
+ // a_position (实例化)
12591
13598
  color: 2,
13599
+ // a_color (实例化)
12592
13600
  covA: 3,
13601
+ // a_covA (实例化)
12593
13602
  covB: 4
13603
+ // a_covB (实例化)
12594
13604
  };
12595
13605
  }
13606
+ /**
13607
+ * 设置 WebGL 渲染状态
13608
+ */
12596
13609
  setupWebGLState() {
12597
13610
  const gl = this.gl;
12598
13611
  if (!gl)
@@ -12608,6 +13621,9 @@ class WebGLRenderer {
12608
13621
  this.backgroundColor[3]
12609
13622
  );
12610
13623
  }
13624
+ /**
13625
+ * 创建渲染缓冲区
13626
+ */
12611
13627
  createBuffers() {
12612
13628
  const gl = this.gl;
12613
13629
  if (!gl)
@@ -12616,6 +13632,9 @@ class WebGLRenderer {
12616
13632
  this.splatBuffer = gl.createBuffer();
12617
13633
  this.createQuadVertexBuffer();
12618
13634
  }
13635
+ /**
13636
+ * 创建四边形顶点缓冲区(实例化渲染用)
13637
+ */
12619
13638
  createQuadVertexBuffer() {
12620
13639
  const gl = this.gl;
12621
13640
  if (!gl)
@@ -12623,17 +13642,29 @@ class WebGLRenderer {
12623
13642
  const quadVertices = new Float32Array([
12624
13643
  -1,
12625
13644
  -1,
13645
+ // 左下
12626
13646
  -1,
12627
13647
  1,
13648
+ // 左上
12628
13649
  1,
12629
13650
  -1,
13651
+ // 右下
12630
13652
  1,
12631
13653
  1
13654
+ // 右上
12632
13655
  ]);
12633
13656
  this.quadVertexBuffer = gl.createBuffer();
12634
13657
  gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
12635
13658
  gl.bufferData(gl.ARRAY_BUFFER, quadVertices, gl.STATIC_DRAW);
12636
13659
  }
13660
+ /**
13661
+ * 从已打包数据加载(零拷贝,GPU 优化路径)
13662
+ * 🚀 性能优化版本:直接使用 WASM 输出的 packed 数据
13663
+ * 🚀 Buffer 复用:避免每帧重新分配,使用 bufferSubData 更新
13664
+ * @param packedData Float32Array [pos3, color4, cov6] x N 个点
13665
+ * @param pointCount 点数
13666
+ * @param _sortOrder WebGL 忽略此参数(已在 RenderSystem 中重排序)
13667
+ */
12637
13668
  loadSplatsFromPackedData(packedData, pointCount, _sortOrder) {
12638
13669
  if (!this.isInitialized) {
12639
13670
  throw new Error("Renderer not initialized");
@@ -12641,6 +13672,9 @@ class WebGLRenderer {
12641
13672
  this.splatCount = pointCount;
12642
13673
  this.uploadToGPU(packedData);
12643
13674
  }
13675
+ /**
13676
+ * 上传数据到 GPU
13677
+ */
12644
13678
  uploadToGPU(packedData) {
12645
13679
  const gl = this.gl;
12646
13680
  if (!gl)
@@ -12653,6 +13687,9 @@ class WebGLRenderer {
12653
13687
  gl.bufferSubData(gl.ARRAY_BUFFER, 0, packedData);
12654
13688
  }
12655
13689
  }
13690
+ /**
13691
+ * 设置实例化渲染顶点属性
13692
+ */
12656
13693
  setupVertexAttributes() {
12657
13694
  const gl = this.gl;
12658
13695
  if (!gl)
@@ -12713,6 +13750,9 @@ class WebGLRenderer {
12713
13750
  );
12714
13751
  gl.vertexAttribDivisor(this.attributeLocations.covB, 1);
12715
13752
  }
13753
+ /**
13754
+ * 渲染一帧
13755
+ */
12716
13756
  render(viewMatrix, projectionMatrix, screenSize, transform) {
12717
13757
  if (!this.isInitialized || this.splatCount === 0) {
12718
13758
  return;
@@ -12736,6 +13776,9 @@ class WebGLRenderer {
12736
13776
  this.render3DGS(gl, viewMatrix, projectionMatrix, screenSize, width, height);
12737
13777
  }
12738
13778
  }
13779
+ /**
13780
+ * 创建着色器程序
13781
+ */
12739
13782
  createShaderProgram(gl) {
12740
13783
  const vertexShader = gl.createShader(gl.VERTEX_SHADER);
12741
13784
  if (!vertexShader)
@@ -12780,6 +13823,9 @@ class WebGLRenderer {
12780
13823
  gl.deleteShader(fragmentShader);
12781
13824
  return program;
12782
13825
  }
13826
+ /**
13827
+ * 更新背景颜色
13828
+ */
12783
13829
  updateBackgroundColor(backgroundColor) {
12784
13830
  this.backgroundColor = backgroundColor;
12785
13831
  if (this.gl) {
@@ -12791,6 +13837,9 @@ class WebGLRenderer {
12791
13837
  );
12792
13838
  }
12793
13839
  }
13840
+ /**
13841
+ * 渲染 3DGS 场景(公共方法,用于直接渲染和渲染到 framebuffer)
13842
+ */
12794
13843
  render3DGS(gl, viewMatrix, projectionMatrix, screenSize, width, height) {
12795
13844
  gl.viewport(0, 0, width, height);
12796
13845
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
@@ -12806,6 +13855,9 @@ class WebGLRenderer {
12806
13855
  this.setupVertexAttributes();
12807
13856
  gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, this.splatCount);
12808
13857
  }
13858
+ /**
13859
+ * 创建 framebuffer 和 render texture
13860
+ */
12809
13861
  createFramebuffer(width, height) {
12810
13862
  const gl = this.gl;
12811
13863
  if (!gl)
@@ -12848,6 +13900,9 @@ class WebGLRenderer {
12848
13900
  this.framebufferWidth = width;
12849
13901
  this.framebufferHeight = height;
12850
13902
  }
13903
+ /**
13904
+ * 创建 blit shader(用于绘制 render texture 到屏幕)
13905
+ */
12851
13906
  createBlitShader(gl) {
12852
13907
  const vertexShader = gl.createShader(gl.VERTEX_SHADER);
12853
13908
  if (!vertexShader)
@@ -12905,22 +13960,27 @@ class WebGLRenderer {
12905
13960
  this.blitVAO = gl.createVertexArray();
12906
13961
  gl.bindVertexArray(this.blitVAO);
12907
13962
  const quadData = new Float32Array([
13963
+ // position (x, y), texCoord (u, v)
12908
13964
  -1,
12909
13965
  -1,
12910
13966
  0,
12911
13967
  0,
13968
+ // 左下
12912
13969
  -1,
12913
13970
  1,
12914
13971
  0,
12915
13972
  1,
13973
+ // 左上
12916
13974
  1,
12917
13975
  -1,
12918
13976
  1,
12919
13977
  0,
13978
+ // 右下
12920
13979
  1,
12921
13980
  1,
12922
13981
  1,
12923
13982
  1
13983
+ // 右上
12924
13984
  ]);
12925
13985
  this.blitQuadBuffer = gl.createBuffer();
12926
13986
  gl.bindBuffer(gl.ARRAY_BUFFER, this.blitQuadBuffer);
@@ -12931,6 +13991,9 @@ class WebGLRenderer {
12931
13991
  gl.vertexAttribPointer(this.blitAttributeLocations.texCoord, 2, gl.FLOAT, false, 16, 8);
12932
13992
  gl.bindVertexArray(null);
12933
13993
  }
13994
+ /**
13995
+ * 将 render texture 绘制到屏幕(应用 transform)
13996
+ */
12934
13997
  blitToScreen(transform) {
12935
13998
  const gl = this.gl;
12936
13999
  if (!gl || !this.blitShaderProgram || !this.renderTexture || !this.blitVAO) {
@@ -12959,6 +14022,9 @@ class WebGLRenderer {
12959
14022
  gl.enable(gl.BLEND);
12960
14023
  }
12961
14024
  }
14025
+ /**
14026
+ * 清理资源
14027
+ */
12962
14028
  dispose() {
12963
14029
  if (!this.gl)
12964
14030
  return;
@@ -13006,21 +14072,26 @@ class WebGPURenderer {
13006
14072
  __publicField(this, "context", null);
13007
14073
  __publicField(this, "renderPipeline", null);
13008
14074
  __publicField(this, "renderTexturePipeline", null);
14075
+ // 用于渲染到 render texture
13009
14076
  __publicField(this, "quadVertexBuffer", null);
13010
14077
  __publicField(this, "uniformBuffer", null);
13011
14078
  __publicField(this, "uniformBindGroup", null);
14079
+ // 🚀 间接索引渲染 buffers
13012
14080
  __publicField(this, "sortIndexBuffer", null);
13013
14081
  __publicField(this, "splatDataBuffer", null);
13014
14082
  __publicField(this, "storageBindGroup", null);
13015
14083
  __publicField(this, "bindGroupNeedsUpdate", false);
14084
+ // 标记 bind group 是否需要更新
13016
14085
  __publicField(this, "splatCount", 0);
13017
14086
  __publicField(this, "presentationFormat", "bgra8unorm");
13018
14087
  __publicField(this, "alpha");
14088
+ // Render texture framebuffer
13019
14089
  __publicField(this, "renderTexture", null);
13020
14090
  __publicField(this, "renderTextureView", null);
13021
14091
  __publicField(this, "depthTexture", null);
13022
14092
  __publicField(this, "framebufferWidth", 0);
13023
14093
  __publicField(this, "framebufferHeight", 0);
14094
+ // Blit pipeline for drawing render texture to screen
13024
14095
  __publicField(this, "blitPipeline", null);
13025
14096
  __publicField(this, "blitUniformBuffer", null);
13026
14097
  __publicField(this, "blitQuadBuffer", null);
@@ -13029,6 +14100,9 @@ class WebGPURenderer {
13029
14100
  this.backgroundColor = backgroundColor || [0, 0, 0, 0];
13030
14101
  this.alpha = alpha;
13031
14102
  }
14103
+ /**
14104
+ * 初始化 WebGPU 渲染器
14105
+ */
13032
14106
  async initialize() {
13033
14107
  const adapter = await navigator.gpu.requestAdapter({
13034
14108
  powerPreference: "high-performance"
@@ -13052,6 +14126,9 @@ class WebGPURenderer {
13052
14126
  await this.createRenderPipeline();
13053
14127
  await this.createBlitPipeline();
13054
14128
  }
14129
+ /**
14130
+ * 创建 Uniform Buffer
14131
+ */
13055
14132
  createUniformBuffer() {
13056
14133
  if (!this.device)
13057
14134
  return;
@@ -13062,18 +14139,25 @@ class WebGPURenderer {
13062
14139
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
13063
14140
  });
13064
14141
  }
14142
+ /**
14143
+ * 创建四边形顶点缓冲区(实例化渲染用)
14144
+ */
13065
14145
  createQuadVertexBuffer() {
13066
14146
  if (!this.device)
13067
14147
  return;
13068
14148
  const quadVertices = new Float32Array([
13069
14149
  -1,
13070
14150
  -1,
14151
+ // 左下
13071
14152
  -1,
13072
14153
  1,
14154
+ // 左上
13073
14155
  1,
13074
14156
  -1,
14157
+ // 右下
13075
14158
  1,
13076
14159
  1
14160
+ // 右上
13077
14161
  ]);
13078
14162
  this.quadVertexBuffer = this.device.createBuffer({
13079
14163
  label: "Quad Vertex Buffer",
@@ -13084,6 +14168,9 @@ class WebGPURenderer {
13084
14168
  new Float32Array(this.quadVertexBuffer.getMappedRange()).set(quadVertices);
13085
14169
  this.quadVertexBuffer.unmap();
13086
14170
  }
14171
+ /**
14172
+ * 创建 Render Pipeline
14173
+ */
13087
14174
  async createRenderPipeline() {
13088
14175
  if (!this.device)
13089
14176
  return;
@@ -13106,11 +14193,13 @@ class WebGPURenderer {
13106
14193
  entries: [
13107
14194
  {
13108
14195
  binding: 0,
14196
+ // sortIndices
13109
14197
  visibility: GPUShaderStage.VERTEX,
13110
14198
  buffer: { type: "read-only-storage" }
13111
14199
  },
13112
14200
  {
13113
14201
  binding: 1,
14202
+ // splatData
13114
14203
  visibility: GPUShaderStage.VERTEX,
13115
14204
  buffer: { type: "read-only-storage" }
13116
14205
  }
@@ -13121,12 +14210,15 @@ class WebGPURenderer {
13121
14210
  bindGroupLayouts: [uniformBindGroupLayout, storageBindGroupLayout]
13122
14211
  });
13123
14212
  const vertexBufferLayouts = [
14213
+ // Buffer 0: Quad vertices (per-vertex)
13124
14214
  {
13125
14215
  arrayStride: 8,
14216
+ // 2 floats
13126
14217
  stepMode: "vertex",
13127
14218
  attributes: [
13128
14219
  {
13129
14220
  shaderLocation: 0,
14221
+ // quadVertex
13130
14222
  offset: 0,
13131
14223
  format: "float32x2"
13132
14224
  }
@@ -13148,6 +14240,7 @@ class WebGPURenderer {
13148
14240
  {
13149
14241
  format: this.presentationFormat,
13150
14242
  blend: {
14243
+ // 预乘 alpha 混合(匹配 alphaMode: 'premultiplied')
13151
14244
  color: {
13152
14245
  srcFactor: "one",
13153
14246
  dstFactor: "one-minus-src-alpha",
@@ -13167,6 +14260,7 @@ class WebGPURenderer {
13167
14260
  stripIndexFormat: void 0
13168
14261
  },
13169
14262
  depthStencil: void 0
14263
+ // 3DGS 不使用深度测试
13170
14264
  });
13171
14265
  this.uniformBindGroup = this.device.createBindGroup({
13172
14266
  label: "Uniform Bind Group",
@@ -13192,6 +14286,7 @@ class WebGPURenderer {
13192
14286
  targets: [
13193
14287
  {
13194
14288
  format: "rgba16float",
14289
+ // 使用 16-bit float 格式
13195
14290
  blend: {
13196
14291
  color: {
13197
14292
  srcFactor: "one",
@@ -13214,10 +14309,15 @@ class WebGPURenderer {
13214
14309
  depthStencil: {
13215
14310
  format: "depth24plus",
13216
14311
  depthWriteEnabled: false,
14312
+ // 不写入深度
13217
14313
  depthCompare: "always"
14314
+ // 总是通过深度测试(实际上不使用)
13218
14315
  }
13219
14316
  });
13220
14317
  }
14318
+ /**
14319
+ * 创建 Blit Pipeline(用于绘制 render texture 到屏幕)
14320
+ */
13221
14321
  async createBlitPipeline() {
13222
14322
  if (!this.device)
13223
14323
  return;
@@ -13235,6 +14335,7 @@ class WebGPURenderer {
13235
14335
  entries: [
13236
14336
  {
13237
14337
  binding: 0,
14338
+ // Uniforms
13238
14339
  visibility: GPUShaderStage.VERTEX,
13239
14340
  buffer: { type: "uniform" }
13240
14341
  }
@@ -13245,11 +14346,13 @@ class WebGPURenderer {
13245
14346
  entries: [
13246
14347
  {
13247
14348
  binding: 0,
14349
+ // Texture
13248
14350
  visibility: GPUShaderStage.FRAGMENT,
13249
14351
  texture: {}
13250
14352
  },
13251
14353
  {
13252
14354
  binding: 1,
14355
+ // Sampler
13253
14356
  visibility: GPUShaderStage.FRAGMENT,
13254
14357
  sampler: {}
13255
14358
  }
@@ -13265,22 +14368,27 @@ class WebGPURenderer {
13265
14368
  minFilter: "linear"
13266
14369
  });
13267
14370
  const quadData = new Float32Array([
14371
+ // position (x, y), texCoord (u, v)
13268
14372
  -1,
13269
14373
  -1,
13270
14374
  0,
13271
14375
  0,
14376
+ // 左下
13272
14377
  -1,
13273
14378
  1,
13274
14379
  0,
13275
14380
  1,
14381
+ // 左上
13276
14382
  1,
13277
14383
  -1,
13278
14384
  1,
13279
14385
  0,
14386
+ // 右下
13280
14387
  1,
13281
14388
  1,
13282
14389
  1,
13283
14390
  1
14391
+ // 右上
13284
14392
  ]);
13285
14393
  this.blitQuadBuffer = this.device.createBuffer({
13286
14394
  label: "Blit Quad Buffer",
@@ -13299,6 +14407,7 @@ class WebGPURenderer {
13299
14407
  buffers: [
13300
14408
  {
13301
14409
  arrayStride: 16,
14410
+ // 4 floats (2 position + 2 texCoord)
13302
14411
  stepMode: "vertex",
13303
14412
  attributes: [
13304
14413
  {
@@ -13321,6 +14430,7 @@ class WebGPURenderer {
13321
14430
  targets: [
13322
14431
  {
13323
14432
  format: this.presentationFormat,
14433
+ // render texture 已经是最终渲染结果,直接覆盖,不需要混合
13324
14434
  blend: void 0
13325
14435
  }
13326
14436
  ]
@@ -13330,6 +14440,9 @@ class WebGPURenderer {
13330
14440
  }
13331
14441
  });
13332
14442
  }
14443
+ /**
14444
+ * 创建 render texture 和 depth texture
14445
+ */
13333
14446
  createRenderTexture(width, height) {
13334
14447
  if (!this.device)
13335
14448
  return;
@@ -13343,6 +14456,7 @@ class WebGPURenderer {
13343
14456
  label: "Render Texture",
13344
14457
  size: [width, height],
13345
14458
  format: "rgba16float",
14459
+ // 使用 16-bit float 以保持精度
13346
14460
  usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
13347
14461
  });
13348
14462
  this.renderTextureView = this.renderTexture.createView();
@@ -13355,6 +14469,11 @@ class WebGPURenderer {
13355
14469
  this.framebufferWidth = width;
13356
14470
  this.framebufferHeight = height;
13357
14471
  }
14472
+ /**
14473
+ * 从已打包数据加载
14474
+ * 🚀 间接索引渲染:packedData 是原始数据,sortOrder 是排序索引
14475
+ * 🚀 完全消除 CPU 重排序开销
14476
+ */
13358
14477
  loadSplatsFromPackedData(packedData, pointCount, sortOrder) {
13359
14478
  if (!this.device)
13360
14479
  throw new Error("Device not initialized");
@@ -13417,6 +14536,9 @@ class WebGPURenderer {
13417
14536
  }
13418
14537
  }
13419
14538
  }
14539
+ /**
14540
+ * 渲染一帧
14541
+ */
13420
14542
  render(viewMatrix, projectionMatrix, screenSize, transform) {
13421
14543
  if (!this.device || !this.context || !this.renderPipeline)
13422
14544
  return;
@@ -13488,6 +14610,9 @@ class WebGPURenderer {
13488
14610
  }
13489
14611
  this.device.queue.submit([commandEncoder.finish()]);
13490
14612
  }
14613
+ /**
14614
+ * 将 render texture 绘制到屏幕(应用 transform)
14615
+ */
13491
14616
  blitToScreen(commandEncoder, transform) {
13492
14617
  if (!this.device || !this.blitPipeline || !this.renderTextureView || !this.blitQuadBuffer || !this.blitUniformBuffer || !this.blitSampler) {
13493
14618
  logger.error(`[WebGPURenderer] Blit failed: device=${!!this.device}, pipeline=${!!this.blitPipeline}, texture=${!!this.renderTextureView}, buffer=${!!this.blitQuadBuffer}, uniform=${!!this.blitUniformBuffer}, sampler=${!!this.blitSampler}`);
@@ -13551,6 +14676,9 @@ class WebGPURenderer {
13551
14676
  blitPass.draw(4);
13552
14677
  blitPass.end();
13553
14678
  }
14679
+ /**
14680
+ * 更新 Uniform Buffer
14681
+ */
13554
14682
  updateUniforms(viewMatrix, projectionMatrix, screenSize) {
13555
14683
  if (!this.device || !this.uniformBuffer)
13556
14684
  return;
@@ -13564,9 +14692,15 @@ class WebGPURenderer {
13564
14692
  uint32View[36] = 1;
13565
14693
  this.device.queue.writeBuffer(this.uniformBuffer, 0, uniformData);
13566
14694
  }
14695
+ /**
14696
+ * 更新背景颜色
14697
+ */
13567
14698
  updateBackgroundColor(backgroundColor) {
13568
14699
  this.backgroundColor = backgroundColor;
13569
14700
  }
14701
+ /**
14702
+ * 清理资源
14703
+ */
13570
14704
  dispose() {
13571
14705
  var _a, _b, _c, _d, _e2, _f, _g, _h, _i2;
13572
14706
  (_a = this.sortIndexBuffer) == null ? void 0 : _a.destroy();
@@ -13604,19 +14738,29 @@ class RenderSystem {
13604
14738
  __publicField(this, "canvas");
13605
14739
  __publicField(this, "options");
13606
14740
  __publicField(this, "backgroundColor");
14741
+ // Camera configuration
13607
14742
  __publicField(this, "camera");
14743
+ // Matrix cache (reuse buffers to avoid per-frame allocation)
13608
14744
  __publicField(this, "viewMatrix", new Float32Array(16));
13609
14745
  __publicField(this, "projectionMatrix", new Float32Array(16));
14746
+ // Camera forward vector cache (reuse to avoid per-frame allocation)
13610
14747
  __publicField(this, "cachedForward", [0, 0, 1]);
13611
14748
  __publicField(this, "forwardCacheValid", false);
14749
+ // Temporary vectors for view matrix calculation (reuse buffers)
13612
14750
  __publicField(this, "tempForward", [0, 0, 0]);
13613
14751
  __publicField(this, "tempRight", [0, 0, 0]);
13614
14752
  __publicField(this, "tempUp", [0, 0, 0]);
14753
+ // Camera parameter cache for matrix update optimization
13615
14754
  __publicField(this, "cachedCameraParams", null);
13616
14755
  __publicField(this, "matricesCacheValid", false);
14756
+ // 当前数据(GPU 格式:[pos3, color4, cov6] x N)
13617
14757
  __publicField(this, "originalPackedData", null);
14758
+ // 性能统计
13618
14759
  __publicField(this, "renderTime", 0);
14760
+ // 总渲染耗时
13619
14761
  __publicField(this, "sortTime", 0);
14762
+ // 排序耗时
14763
+ // Transform for render texture blit
13620
14764
  __publicField(this, "offsetX", 0);
13621
14765
  __publicField(this, "offsetY", 0);
13622
14766
  __publicField(this, "scale", 1);
@@ -13636,6 +14780,9 @@ class RenderSystem {
13636
14780
  aspect: 1
13637
14781
  };
13638
14782
  }
14783
+ /**
14784
+ * Initialize render system
14785
+ */
13639
14786
  async initialize() {
13640
14787
  const { preferBackend, alpha = true } = this.options;
13641
14788
  const backgroundColor = this.backgroundColor;
@@ -13660,11 +14807,20 @@ class RenderSystem {
13660
14807
  logger.log("✅ Using WebGL renderer");
13661
14808
  this.updateCameraAspect();
13662
14809
  }
14810
+ /**
14811
+ * Load packed Splat data (zero-copy, GPU format)
14812
+ * Directly receives WASM packed data
14813
+ *
14814
+ * @param packedData Float32Array [pos3, color4, cov6] x N points
14815
+ */
13663
14816
  loadSplatsFromPackedData(packedData) {
13664
14817
  if (!this.renderer)
13665
14818
  throw new Error("Renderer not initialized");
13666
14819
  this.originalPackedData = packedData;
13667
14820
  }
14821
+ /**
14822
+ * 渲染一帧
14823
+ */
13668
14824
  renderFrame() {
13669
14825
  if (!this.renderer || !this.originalPackedData)
13670
14826
  return;
@@ -13689,43 +14845,72 @@ class RenderSystem {
13689
14845
  this.viewMatrix,
13690
14846
  this.projectionMatrix,
13691
14847
  [this.canvas.width, this.canvas.height],
14848
+ // 传递 transform(如果不需要 transform,则不传递,保持向后兼容)
13692
14849
  this.offsetX !== 0 || this.offsetY !== 0 || this.scale !== 1 ? { x: this.offsetX, y: this.offsetY, scale: this.scale } : void 0
13693
14850
  );
13694
14851
  const renderTime = performance.now() - startRender;
13695
14852
  this.renderTime = renderTime;
13696
14853
  }
14854
+ /**
14855
+ * Set transform for render texture blit
14856
+ * @param x - Horizontal offset in normalized coordinates (-1 to 1, where -1 = left edge, 0 = center, 1 = right edge)
14857
+ * @param y - Vertical offset in normalized coordinates (-1 to 1, where -1 = bottom edge, 0 = center, 1 = top edge)
14858
+ * @param scale - Scale factor (1.0 = original size, 2.0 = double size, 0.5 = half size)
14859
+ */
13697
14860
  setTransform(x2, y2, scale = 1) {
13698
14861
  logger.log(`[RenderSystem] Setting transform: x=${x2}, y=${y2}, scale=${scale}`);
13699
14862
  this.offsetX = x2;
13700
14863
  this.offsetY = y2;
13701
14864
  this.scale = scale;
13702
14865
  }
14866
+ /**
14867
+ * Get current transform
14868
+ */
13703
14869
  getTransform() {
13704
14870
  return { x: this.offsetX, y: this.offsetY, scale: this.scale };
13705
14871
  }
14872
+ /**
14873
+ * Update camera parameters
14874
+ */
13706
14875
  updateCamera(params) {
13707
14876
  Object.assign(this.camera, params);
13708
14877
  this.updateCameraAspect();
13709
14878
  }
14879
+ /**
14880
+ * Handle window resize
14881
+ */
13710
14882
  handleResize() {
13711
14883
  this.updateCameraAspect();
13712
14884
  this.updateCameraMatrices();
13713
14885
  }
14886
+ /**
14887
+ * 获取当前使用的后端
14888
+ */
13714
14889
  getBackend() {
13715
14890
  return this.backend;
13716
14891
  }
14892
+ /**
14893
+ * 更新背景颜色
14894
+ */
13717
14895
  updateBackgroundColor(backgroundColor) {
13718
14896
  this.backgroundColor = backgroundColor;
13719
14897
  if (this.renderer && typeof this.renderer.updateBackgroundColor === "function") {
13720
14898
  this.renderer.updateBackgroundColor(backgroundColor);
13721
14899
  }
13722
14900
  }
14901
+ /**
14902
+ * Dispose resources
14903
+ */
13723
14904
  dispose() {
13724
14905
  var _a;
13725
14906
  (_a = this.renderer) == null ? void 0 : _a.dispose();
13726
14907
  this.renderer = null;
13727
14908
  this.originalPackedData = null;
13728
14909
  }
14910
+ // ========== Private Methods ==========
14911
+ /**
14912
+ * Check WebGPU support
14913
+ */
13729
14914
  async checkWebGPUSupport() {
13730
14915
  if (!navigator.gpu)
13731
14916
  return false;
@@ -13736,10 +14921,16 @@ class RenderSystem {
13736
14921
  return false;
13737
14922
  }
13738
14923
  }
14924
+ /**
14925
+ * Update camera aspect ratio
14926
+ */
13739
14927
  updateCameraAspect() {
13740
14928
  this.camera.aspect = this.canvas.width / this.canvas.height;
13741
14929
  this.matricesCacheValid = false;
13742
14930
  }
14931
+ /**
14932
+ * Update camera matrices (with caching to avoid unnecessary updates)
14933
+ */
13743
14934
  updateCameraMatrices() {
13744
14935
  const { position, target, up, fov, aspect, near, far } = this.camera;
13745
14936
  const paramsChanged = !this.cachedCameraParams || this.cachedCameraParams.position[0] !== position[0] || this.cachedCameraParams.position[1] !== position[1] || this.cachedCameraParams.position[2] !== position[2] || this.cachedCameraParams.target[0] !== target[0] || this.cachedCameraParams.target[1] !== target[1] || this.cachedCameraParams.target[2] !== target[2] || this.cachedCameraParams.up[0] !== up[0] || this.cachedCameraParams.up[1] !== up[1] || this.cachedCameraParams.up[2] !== up[2] || this.cachedCameraParams.fov !== fov || this.cachedCameraParams.aspect !== aspect || this.cachedCameraParams.near !== near || this.cachedCameraParams.far !== far;
@@ -13776,6 +14967,9 @@ class RenderSystem {
13776
14967
  this.updateViewMatrix(position, target, up);
13777
14968
  this.matricesCacheValid = true;
13778
14969
  }
14970
+ /**
14971
+ * Get camera forward vector (cached version)
14972
+ */
13779
14973
  getCameraForward() {
13780
14974
  if (this.forwardCacheValid) {
13781
14975
  return this.cachedForward;
@@ -13794,6 +14988,9 @@ class RenderSystem {
13794
14988
  this.forwardCacheValid = true;
13795
14989
  return this.cachedForward;
13796
14990
  }
14991
+ /**
14992
+ * Update perspective projection matrix (reuse buffer)
14993
+ */
13797
14994
  updatePerspectiveMatrix(fov, aspect, near, far) {
13798
14995
  const fovYRadians = fov * Math.PI / 180;
13799
14996
  const f2 = 1 / Math.tan(fovYRadians / 2);
@@ -13816,6 +15013,12 @@ class RenderSystem {
13816
15013
  m2[14] = zs2 * near;
13817
15014
  m2[15] = 0;
13818
15015
  }
15016
+ /**
15017
+ * Update view matrix (directly update pre-allocated buffer to avoid per-frame allocation)
15018
+ * Equivalent to: inverse(translation) * inverse(rotation) = T^(-1) * R^(-1)
15019
+ * Where T is translation matrix, R is rotation matrix
15020
+ * Uses reusable temporary vector buffers to avoid allocations
15021
+ */
13819
15022
  updateViewMatrix(position, target, up) {
13820
15023
  this.tempForward[0] = target[0] - position[0];
13821
15024
  this.tempForward[1] = target[1] - position[1];
@@ -13925,17 +15128,26 @@ function createBezierEasing(x1, y1, x2, y2) {
13925
15128
  };
13926
15129
  }
13927
15130
  const BEZIER_CURVES = {
15131
+ // jaw: 快速启动,平稳停止
13928
15132
  jaw: createBezierEasing(0.2, 0.8, 0.3, 1),
15133
+ // expression: 平滑 S 曲线
13929
15134
  expression: createBezierEasing(0.4, 0, 0.2, 1),
15135
+ // eye: 更柔和的 S 曲线
13930
15136
  eye: createBezierEasing(0.3, 0, 0.1, 1),
15137
+ // neck: 慢启动,惯性停止
13931
15138
  neck: createBezierEasing(0.1, 0.2, 0.2, 1),
15139
+ // global: 标准 ease-in-out
13932
15140
  global: createBezierEasing(0.42, 0, 0.58, 1)
13933
15141
  };
13934
15142
  const TIME_SCALE = {
13935
15143
  jaw: 2.5,
15144
+ // 40% 时间完成
13936
15145
  expression: 1.6,
15146
+ // 62.5% 时间完成
13937
15147
  eye: 1.3,
15148
+ // 77% 时间完成
13938
15149
  neck: 1,
15150
+ // 100% 时间完成
13939
15151
  global: 1
13940
15152
  };
13941
15153
  function bezierLerp(from, to2, progress) {
@@ -13975,35 +15187,55 @@ function generateTransitionFrames(from, to2, durationMs, fps = 25) {
13975
15187
  return frames;
13976
15188
  }
13977
15189
  class AvatarView {
15190
+ /**
15191
+ * Constructor
15192
+ * Creates a unified AvatarController, internally composes network layer based on configuration
15193
+ * @param avatar - Avatar instance
15194
+ * @param container - Canvas container element (required)
15195
+ */
13978
15196
  constructor(avatar, container) {
13979
15197
  __publicField(this, "avatarController");
13980
15198
  __publicField(this, "avatar");
15199
+ // 首帧渲染回调
13981
15200
  __publicField(this, "onFirstRendering");
15201
+ // Canvas and rendering
13982
15202
  __publicField(this, "canvas");
13983
15203
  __publicField(this, "renderSystem", null);
13984
15204
  __publicField(this, "isInitialized", false);
13985
15205
  __publicField(this, "cameraConfig", null);
15206
+ // Rendering state machine
13986
15207
  __publicField(this, "renderingState", "idle");
15208
+ // Realtime animation data
13987
15209
  __publicField(this, "currentKeyframes", []);
13988
15210
  __publicField(this, "lastRenderedFrameIndex", -1);
13989
15211
  __publicField(this, "lastRealtimeProtoFrame", null);
15212
+ // Animation loop types
13990
15213
  __publicField(this, "idleAnimationLoopId", null);
13991
15214
  __publicField(this, "realtimeAnimationLoopId", null);
13992
15215
  __publicField(this, "resizeObserver", null);
13993
15216
  __publicField(this, "onWindowResize", () => this.handleResize());
15217
+ // FPS 计算
13994
15218
  __publicField(this, "frameCount", 0);
13995
15219
  __publicField(this, "lastFpsUpdate", 0);
13996
15220
  __publicField(this, "currentFPS", 0);
15221
+ // Transition animation data
13997
15222
  __publicField(this, "transitionKeyframes", []);
13998
15223
  __publicField(this, "transitionStartTime", 0);
13999
15224
  __publicField(this, "startTransitionDurationMs", 200);
15225
+ // Idle -> Speaking 过渡时长
14000
15226
  __publicField(this, "endTransitionDurationMs", 1600);
15227
+ // Speaking -> Idle 过渡时长
14001
15228
  __publicField(this, "cachedIdleFirstFrame", null);
14002
15229
  __publicField(this, "idleCurrentFrameIndex", 0);
15230
+ // 当前正在播放的帧(统一用于所有过渡,无论处于什么状态)
14003
15231
  __publicField(this, "currentPlayingFrame", null);
15232
+ // Character handle for multi-character support
14004
15233
  __publicField(this, "characterHandle", null);
14005
15234
  __publicField(this, "characterId");
15235
+ // Unique ID for this character instance
15236
+ // 纯渲染模式标志(阻止 idle 循环渲染)
14006
15237
  __publicField(this, "isPureRenderingMode", false);
15238
+ // avatar_active 埋点相关
14007
15239
  __publicField(this, "avatarActiveTimer", null);
14008
15240
  __publicField(this, "AVATAR_ACTIVE_INTERVAL", 6e5);
14009
15241
  this.avatar = avatar;
@@ -14024,6 +15256,10 @@ class AvatarView {
14024
15256
  });
14025
15257
  this.setupControllerEventListeners();
14026
15258
  }
15259
+ // 10分钟 = 600000ms
15260
+ /**
15261
+ * 对齐两端 Flame 维度:标量统一长度,expression 取最大长度并零填充
15262
+ */
14027
15263
  alignFlamePair(from, to2) {
14028
15264
  const ensureLen = (arr, len) => {
14029
15265
  const a2 = Array.isArray(arr) ? arr.slice(0, len) : [];
@@ -14055,6 +15291,13 @@ class AvatarView {
14055
15291
  toFixed.expression = ensureLen(toFixed.expression, exprLen);
14056
15292
  return { from: fromFixed, to: toFixed };
14057
15293
  }
15294
+ /**
15295
+ * 生成并对齐过渡帧,确保首尾帧与起止帧完全一致
15296
+ * @param from 起始帧
15297
+ * @param to 目标帧
15298
+ * @param durationMs 过渡时长(开头或结尾)
15299
+ * @param useLinearInterpolation 是否使用线性插值(旧实现),true 用于 idle->speaking,false 用于 speaking->idle(使用 Bezier 曲线)
15300
+ */
14058
15301
  generateAndAlignTransitionFrames(from, to2, durationMs, useLinearInterpolation = false) {
14059
15302
  const aligned = this.alignFlamePair(from, to2);
14060
15303
  let keyframes = useLinearInterpolation ? generateTransitionFramesLinear(
@@ -14075,6 +15318,9 @@ class AvatarView {
14075
15318
  keyframes[keyframes.length - 1] = aligned.to;
14076
15319
  return keyframes;
14077
15320
  }
15321
+ /**
15322
+ * 获取缓存的 Idle 首帧,如果未缓存则获取并缓存
15323
+ */
14078
15324
  async getCachedIdleFirstFrame() {
14079
15325
  if (!this.cachedIdleFirstFrame) {
14080
15326
  const avatarCore = AvatarSDK.getAvatarCore();
@@ -14089,9 +15335,15 @@ class AvatarView {
14089
15335
  }
14090
15336
  return this.cachedIdleFirstFrame;
14091
15337
  }
15338
+ /**
15339
+ * Get controller (public interface)
15340
+ */
14092
15341
  get controller() {
14093
15342
  return this.avatarController;
14094
15343
  }
15344
+ /**
15345
+ * 创建canvas元素
15346
+ */
14095
15347
  createCanvas(container) {
14096
15348
  const canvas = document.createElement("canvas");
14097
15349
  const containerWidth = container.offsetWidth || 800;
@@ -14128,9 +15380,16 @@ class AvatarView {
14128
15380
  window.addEventListener("resize", this.onWindowResize);
14129
15381
  return canvas;
14130
15382
  }
15383
+ /**
15384
+ * 获取canvas元素(供外部访问)
15385
+ * @internal
15386
+ */
14131
15387
  getCanvas() {
14132
15388
  return this.canvas;
14133
15389
  }
15390
+ /**
15391
+ * 初始化视图系统
15392
+ */
14134
15393
  async initializeView(avatar) {
14135
15394
  var _a;
14136
15395
  try {
@@ -14198,6 +15457,9 @@ class AvatarView {
14198
15457
  throw error;
14199
15458
  }
14200
15459
  }
15460
+ /**
15461
+ * 初始化渲染系统
15462
+ */
14201
15463
  async initializeRenderSystem(cameraInfo) {
14202
15464
  this.cameraConfig = cameraInfo || this.getDefaultCameraConfig();
14203
15465
  if (cameraInfo) {
@@ -14209,15 +15471,23 @@ class AvatarView {
14209
15471
  canvas: this.canvas,
14210
15472
  camera: this.cameraConfig,
14211
15473
  backgroundColor: [0, 0, 0, 0],
15474
+ // 透明背景
14212
15475
  alpha: true
15476
+ // 启用 alpha 通道
14213
15477
  });
14214
15478
  await this.renderSystem.initialize();
14215
15479
  if (APP_CONFIG.debug)
14216
15480
  logger.log("[AvatarView] Render system initialized successfully");
14217
15481
  }
15482
+ /**
15483
+ * 获取默认相机配置
15484
+ */
14218
15485
  getDefaultCameraConfig() {
14219
15486
  return { ...APP_CONFIG.camera };
14220
15487
  }
15488
+ /**
15489
+ * 根据资源解析最终的相机配置,优先使用角色设置,其次 camera.json
15490
+ */
14221
15491
  resolveCameraConfig(resources) {
14222
15492
  var _a, _b;
14223
15493
  const defaultCamera = this.getDefaultCameraConfig();
@@ -14231,6 +15501,9 @@ class AvatarView {
14231
15501
  const source = characterCameraSettings ? "characterSettings" : "camera.json";
14232
15502
  return this.deriveCameraConfigFromSettings(candidateSettings, defaultCamera, source);
14233
15503
  }
15504
+ /**
15505
+ * 从角色设置中推导相机配置
15506
+ */
14234
15507
  deriveCameraConfigFromSettings(cameraSettings, fallback, source) {
14235
15508
  const safeValue = (value, fallbackValue) => Number.isFinite(value) ? value : fallbackValue;
14236
15509
  const translationX = safeValue(cameraSettings.translationX, fallback.position[0]);
@@ -14268,10 +15541,12 @@ class AvatarView {
14268
15541
  const fov = hasCustomFov ? fovRadians * 180 / Math.PI : fallback.fov;
14269
15542
  const derivedCamera = {
14270
15543
  ...fallback,
15544
+ // 自动继承 near/far 等配置(来自 APP_CONFIG.camera)
14271
15545
  position,
14272
15546
  target,
14273
15547
  up,
14274
15548
  fov
15549
+ // near/far 从 fallback 继承,无需硬编码
14275
15550
  };
14276
15551
  logger.log("[AvatarView] Applied camera settings from resources", {
14277
15552
  source,
@@ -14285,6 +15560,9 @@ class AvatarView {
14285
15560
  });
14286
15561
  return derivedCamera;
14287
15562
  }
15563
+ /**
15564
+ * 渲染第一帧
15565
+ */
14288
15566
  async renderFirstFrame() {
14289
15567
  var _a;
14290
15568
  if (!this.renderSystem) {
@@ -14316,6 +15594,9 @@ class AvatarView {
14316
15594
  throw new Error("Failed to compute first frame splat data");
14317
15595
  }
14318
15596
  }
15597
+ /**
15598
+ * 更新 FPS 统计(在 requestAnimationFrame 回调中调用)
15599
+ */
14319
15600
  updateFPS() {
14320
15601
  this.frameCount++;
14321
15602
  const now = performance.now();
@@ -14325,10 +15606,16 @@ class AvatarView {
14325
15606
  this.lastFpsUpdate = now;
14326
15607
  }
14327
15608
  }
15609
+ /**
15610
+ * 初始化 FPS 计算
15611
+ */
14328
15612
  initFPS() {
14329
15613
  this.frameCount = 0;
14330
15614
  this.lastFpsUpdate = performance.now();
14331
15615
  }
15616
+ /**
15617
+ * 开始idle动画循环
15618
+ */
14332
15619
  startIdleAnimationLoop() {
14333
15620
  if (this.idleAnimationLoopId) {
14334
15621
  this.stopIdleAnimationLoop();
@@ -14388,6 +15675,9 @@ class AvatarView {
14388
15675
  if (APP_CONFIG.debug)
14389
15676
  logger.log("[AvatarView] Idle animation loop started");
14390
15677
  }
15678
+ /**
15679
+ * 开始实时对话动画循环
15680
+ */
14391
15681
  startRealtimeAnimationLoop() {
14392
15682
  if (this.realtimeAnimationLoopId) {
14393
15683
  this.stopRealtimeAnimationLoop();
@@ -14412,10 +15702,16 @@ class AvatarView {
14412
15702
  if (state === "transitioningToSpeaking" || state === "transitioningToIdle") {
14413
15703
  if (this.transitionKeyframes.length === 0) {
14414
15704
  if (state === "transitioningToSpeaking") {
14415
- this.setState("speaking");
15705
+ this.setState(
15706
+ "speaking"
15707
+ /* Speaking */
15708
+ );
14416
15709
  this.avatarController.onTransitionComplete();
14417
15710
  } else if (state === "transitioningToIdle") {
14418
- this.setState("idle");
15711
+ this.setState(
15712
+ "idle"
15713
+ /* Idle */
15714
+ );
14419
15715
  this.stopRealtimeAnimationLoop();
14420
15716
  this.startIdleAnimationLoop();
14421
15717
  return;
@@ -14441,11 +15737,17 @@ class AvatarView {
14441
15737
  }
14442
15738
  if (progress >= 1) {
14443
15739
  if (state === "transitioningToSpeaking") {
14444
- this.setState("speaking");
15740
+ this.setState(
15741
+ "speaking"
15742
+ /* Speaking */
15743
+ );
14445
15744
  this.transitionKeyframes = [];
14446
15745
  this.avatarController.onTransitionComplete();
14447
15746
  } else if (state === "transitioningToIdle") {
14448
- this.setState("idle");
15747
+ this.setState(
15748
+ "idle"
15749
+ /* Idle */
15750
+ );
14449
15751
  this.transitionKeyframes = [];
14450
15752
  this.stopRealtimeAnimationLoop();
14451
15753
  this.startIdleAnimationLoop();
@@ -14453,7 +15755,10 @@ class AvatarView {
14453
15755
  }
14454
15756
  }
14455
15757
  if (state === "transitioningToSpeaking" && this.transitionStartTime > 0 && this.transitionKeyframes.length > 0 && elapsed >= this.startTransitionDurationMs + 100) {
14456
- this.setState("speaking");
15758
+ this.setState(
15759
+ "speaking"
15760
+ /* Speaking */
15761
+ );
14457
15762
  this.transitionKeyframes = [];
14458
15763
  this.avatarController.onTransitionComplete();
14459
15764
  }
@@ -14473,6 +15778,9 @@ class AvatarView {
14473
15778
  if (APP_CONFIG.debug)
14474
15779
  logger.log("[AvatarView] Realtime animation loop started");
14475
15780
  }
15781
+ /**
15782
+ * 停止idle动画循环
15783
+ */
14476
15784
  stopIdleAnimationLoop() {
14477
15785
  if (this.idleAnimationLoopId) {
14478
15786
  cancelAnimationFrame(this.idleAnimationLoopId);
@@ -14481,6 +15789,9 @@ class AvatarView {
14481
15789
  logger.log("[AvatarView] Idle animation loop stopped");
14482
15790
  }
14483
15791
  }
15792
+ /**
15793
+ * 停止实时对话动画循环
15794
+ */
14484
15795
  stopRealtimeAnimationLoop() {
14485
15796
  if (this.realtimeAnimationLoopId) {
14486
15797
  cancelAnimationFrame(this.realtimeAnimationLoopId);
@@ -14489,10 +15800,16 @@ class AvatarView {
14489
15800
  logger.log("[AvatarView] Realtime animation loop stopped");
14490
15801
  }
14491
15802
  }
15803
+ /**
15804
+ * 停止所有动画循环
15805
+ */
14492
15806
  stopAllAnimationLoops() {
14493
15807
  this.stopIdleAnimationLoop();
14494
15808
  this.stopRealtimeAnimationLoop();
14495
15809
  }
15810
+ /**
15811
+ * 渲染实时帧(由播放层回调调用)
15812
+ */
14496
15813
  renderRealtimeFrame(splatData, frameIndex) {
14497
15814
  if (!this.renderSystem || this.renderingState !== "speaking") {
14498
15815
  return;
@@ -14505,6 +15822,10 @@ class AvatarView {
14505
15822
  this.currentPlayingFrame = this.lastRealtimeProtoFrame;
14506
15823
  }
14507
15824
  }
15825
+ /**
15826
+ * 状态转换方法
15827
+ * 统一管理状态转换,确保状态一致性
15828
+ */
14508
15829
  setState(newState) {
14509
15830
  const oldState = this.renderingState;
14510
15831
  this.renderingState = newState;
@@ -14523,15 +15844,28 @@ class AvatarView {
14523
15844
  this.currentPlayingFrame = null;
14524
15845
  }
14525
15846
  }
15847
+ /**
15848
+ * 检查是否在实时播放状态(Speaking 或过渡到 Speaking)
15849
+ */
14526
15850
  get isRealtimePlaying() {
14527
15851
  return this.renderingState === "speaking" || this.renderingState === "transitioningToSpeaking";
14528
15852
  }
15853
+ /**
15854
+ * 检查是否在过渡中
15855
+ */
14529
15856
  get isTransitioning() {
14530
15857
  return this.renderingState === "transitioningToSpeaking" || this.renderingState === "transitioningToIdle";
14531
15858
  }
15859
+ /**
15860
+ * 检查过渡结束后是否回到 Idle
15861
+ */
14532
15862
  get endToIdleAfterTransition() {
14533
15863
  return this.renderingState === "transitioningToIdle";
14534
15864
  }
15865
+ /**
15866
+ * 处理打断
15867
+ * 打断时应该生成过渡动画,而不是直接跳回 Idle
15868
+ */
14535
15869
  handleInterrupt() {
14536
15870
  const state = this.renderingState;
14537
15871
  if (state === "idle") {
@@ -14543,11 +15877,17 @@ class AvatarView {
14543
15877
  if (state === "speaking" || state === "transitioningToSpeaking") {
14544
15878
  this.stopRealtimeRendering();
14545
15879
  } else {
14546
- this.setState("idle");
15880
+ this.setState(
15881
+ "idle"
15882
+ /* Idle */
15883
+ );
14547
15884
  this.stopRealtimeAnimationLoop();
14548
15885
  this.startIdleAnimationLoop();
14549
15886
  }
14550
15887
  }
15888
+ /**
15889
+ * 设置 AvatarController 事件监听器
15890
+ */
14551
15891
  setupControllerEventListeners() {
14552
15892
  this.avatarController.setupInternalEventListeners({
14553
15893
  onKeyframesUpdate: (keyframes) => {
@@ -14564,6 +15904,10 @@ class AvatarView {
14564
15904
  }
14565
15905
  });
14566
15906
  }
15907
+ /**
15908
+ * 准备实时渲染(生成过渡到 Speaking)
15909
+ * 统一逻辑:从当前正在播放的帧 -> Speaking 第一帧
15910
+ */
14567
15911
  async prepareRealtimeRendering(keyframes) {
14568
15912
  const state = this.renderingState;
14569
15913
  if ((state === "speaking" || state === "transitioningToSpeaking") && this.currentKeyframes.length > 0) {
@@ -14572,7 +15916,10 @@ class AvatarView {
14572
15916
  }
14573
15917
  this.stopIdleAnimationLoop();
14574
15918
  this.currentKeyframes = keyframes;
14575
- this.setState("transitioningToSpeaking");
15919
+ this.setState(
15920
+ "transitioningToSpeaking"
15921
+ /* TransitioningToSpeaking */
15922
+ );
14576
15923
  try {
14577
15924
  const avatarCore = AvatarSDK.getAvatarCore();
14578
15925
  if (avatarCore && keyframes.length > 0) {
@@ -14607,7 +15954,10 @@ class AvatarView {
14607
15954
  this.transitionStartTime = performance.now();
14608
15955
  this.currentPlayingFrame = null;
14609
15956
  if (this.transitionKeyframes.length === 0) {
14610
- this.setState("speaking");
15957
+ this.setState(
15958
+ "speaking"
15959
+ /* Speaking */
15960
+ );
14611
15961
  this.avatarController.onTransitionComplete();
14612
15962
  } else {
14613
15963
  if (APP_CONFIG.debug)
@@ -14617,12 +15967,18 @@ class AvatarView {
14617
15967
  } catch (e2) {
14618
15968
  logger.warn("[AvatarView] Transition generation failed:", e2 instanceof Error ? e2.message : String(e2));
14619
15969
  if (this.renderingState === "transitioningToSpeaking") {
14620
- this.setState("speaking");
15970
+ this.setState(
15971
+ "speaking"
15972
+ /* Speaking */
15973
+ );
14621
15974
  this.avatarController.onTransitionComplete();
14622
15975
  }
14623
15976
  }
14624
15977
  this.startRealtimeAnimationLoop();
14625
15978
  }
15979
+ /**
15980
+ * 开始实时渲染循环
15981
+ */
14626
15982
  startRealtimeRendering() {
14627
15983
  if (APP_CONFIG.debug)
14628
15984
  logger.log("[AvatarView] Starting realtime rendering with", this.currentKeyframes.length, "frames");
@@ -14632,6 +15988,9 @@ class AvatarView {
14632
15988
  keyframesCount: this.currentKeyframes.length
14633
15989
  });
14634
15990
  }
15991
+ /**
15992
+ * 停止实时对话渲染
15993
+ */
14635
15994
  stopRealtimeRendering() {
14636
15995
  var _a, _b;
14637
15996
  const state = this.renderingState;
@@ -14642,12 +16001,18 @@ class AvatarView {
14642
16001
  if (state === "transitioningToIdle") {
14643
16002
  return;
14644
16003
  }
14645
- this.setState("idle");
16004
+ this.setState(
16005
+ "idle"
16006
+ /* Idle */
16007
+ );
14646
16008
  this.stopRealtimeAnimationLoop();
14647
16009
  this.startIdleAnimationLoop();
14648
16010
  return;
14649
16011
  }
14650
- this.setState("transitioningToIdle");
16012
+ this.setState(
16013
+ "transitioningToIdle"
16014
+ /* TransitioningToIdle */
16015
+ );
14651
16016
  (_b = (_a = this.avatarController).onConversationState) == null ? void 0 : _b.call(_a, ConversationState.idle);
14652
16017
  (async () => {
14653
16018
  try {
@@ -14673,7 +16038,10 @@ class AvatarView {
14673
16038
  }
14674
16039
  if (!fromFrame) {
14675
16040
  logger.warn("[AvatarView] Cannot get current playing frame for transition to idle, fallback to idle frame");
14676
- this.setState("idle");
16041
+ this.setState(
16042
+ "idle"
16043
+ /* Idle */
16044
+ );
14677
16045
  this.stopRealtimeAnimationLoop();
14678
16046
  this.startIdleAnimationLoop();
14679
16047
  return;
@@ -14698,12 +16066,19 @@ class AvatarView {
14698
16066
  logger.warn("[AvatarView] Return transition generation failed:", e2 instanceof Error ? e2.message : String(e2));
14699
16067
  }
14700
16068
  if (this.renderingState === "transitioningToIdle") {
14701
- this.setState("idle");
16069
+ this.setState(
16070
+ "idle"
16071
+ /* Idle */
16072
+ );
14702
16073
  this.stopRealtimeAnimationLoop();
14703
16074
  this.startIdleAnimationLoop();
14704
16075
  }
14705
16076
  })();
14706
16077
  }
16078
+ /**
16079
+ * Cleanup view resources
16080
+ * Closes avatarController and cleans up all related resources
16081
+ */
14707
16082
  dispose() {
14708
16083
  if (APP_CONFIG.debug)
14709
16084
  logger.log("[AvatarView] Disposing avatar view...");
@@ -14717,7 +16092,10 @@ class AvatarView {
14717
16092
  }
14718
16093
  this.stopAllAnimationLoops();
14719
16094
  this.stopAvatarActiveHeartbeat();
14720
- this.setState("idle");
16095
+ this.setState(
16096
+ "idle"
16097
+ /* Idle */
16098
+ );
14721
16099
  this.cachedIdleFirstFrame = null;
14722
16100
  this.idleCurrentFrameIndex = 0;
14723
16101
  this.currentPlayingFrame = null;
@@ -14748,9 +16126,17 @@ class AvatarView {
14748
16126
  if (APP_CONFIG.debug)
14749
16127
  logger.log("[AvatarView] Avatar view disposed successfully");
14750
16128
  }
16129
+ /**
16130
+ * 获取相机配置
16131
+ * @internal
16132
+ */
14751
16133
  getCameraConfig() {
14752
16134
  return this.cameraConfig;
14753
16135
  }
16136
+ /**
16137
+ * 更新相机配置
16138
+ * @internal
16139
+ */
14754
16140
  updateCameraConfig(cameraConfig) {
14755
16141
  this.cameraConfig = cameraConfig;
14756
16142
  if (APP_CONFIG.debug)
@@ -14764,7 +16150,13 @@ class AvatarView {
14764
16150
  }
14765
16151
  }
14766
16152
  }
14767
- async renderFlame(flame, enableIdleRendering) {
16153
+ /**
16154
+ * Render specified keyframe (pure rendering mode, no audio-animation synchronization)
16155
+ * Suitable for scenarios where external application controls audio playback and animation playback
16156
+ * @param keyframeData - Keyframe data
16157
+ * @param enableIdleRendering - Whether to enable idle loop rendering. true: Enable idle rendering and return immediately (skip this keyframe); false: Disable idle rendering and process keyframe (default)
16158
+ */
16159
+ async renderFlame(keyframeData, enableIdleRendering) {
14768
16160
  if (!this.isInitialized || !this.renderSystem) {
14769
16161
  throw new Error("AvatarView not initialized");
14770
16162
  }
@@ -14774,6 +16166,7 @@ class AvatarView {
14774
16166
  }
14775
16167
  this.isPureRenderingMode = true;
14776
16168
  try {
16169
+ const flame = keyframeData;
14777
16170
  const processedFlame = this.avatarController.applyPostProcessingToFlame(flame);
14778
16171
  const wasmParams = convertProtoFlameToWasmParams(processedFlame);
14779
16172
  const avatarCore = AvatarSDK.getAvatarCore();
@@ -14793,7 +16186,14 @@ class AvatarView {
14793
16186
  throw error;
14794
16187
  }
14795
16188
  }
14796
- async generateTransitionFromIdle(toFlame, frameCount, transitionType = "start") {
16189
+ /**
16190
+ * Generate transition frame array from current idle frame to target keyframe (pure rendering mode)
16191
+ * @param toKeyframeData - Target keyframe data
16192
+ * @param frameCount - Number of transition frames
16193
+ * @param transitionType - Transition type: 'start' means Idle -> toKeyframeData (start transition), 'end' means toKeyframeData -> Idle (end transition)
16194
+ * @returns Transition frame array with length of frameCount
16195
+ */
16196
+ async generateTransitionFromIdle(toKeyframeData, frameCount, transitionType = "start") {
14797
16197
  if (!this.isInitialized) {
14798
16198
  throw new Error("AvatarView not initialized");
14799
16199
  }
@@ -14807,6 +16207,7 @@ class AvatarView {
14807
16207
  try {
14808
16208
  const idleParams = await avatarCore.getCurrentFrameParams(this.idleCurrentFrameIndex, this.characterId);
14809
16209
  const idleFrameProto = convertWasmParamsToProtoFlame(idleParams);
16210
+ const toFlame = toKeyframeData;
14810
16211
  const toFlameWithPostProcessing = this.avatarController.applyPostProcessingToFlame(toFlame);
14811
16212
  const aligned = this.alignFlamePair(idleFrameProto, toFlameWithPostProcessing);
14812
16213
  const from = transitionType === "start" ? aligned.from : aligned.to;
@@ -14833,17 +16234,31 @@ class AvatarView {
14833
16234
  throw error;
14834
16235
  }
14835
16236
  }
16237
+ /**
16238
+ * 使用新的相机配置重新渲染当前帧(用于暂停状态下更新相机)
16239
+ * 复用 AvatarController 的重新渲染逻辑,因为 renderCallback 会调用 renderRealtimeFrame,
16240
+ * 而 renderRealtimeFrame 会使用已更新的相机配置(通过 renderSystem.updateCamera)
16241
+ * @private
16242
+ */
14836
16243
  async rerenderCurrentFrameWithNewCamera() {
14837
16244
  if (this.avatarController.state !== AvatarState.paused || this.renderingState !== "speaking" || !this.renderSystem) {
14838
16245
  return;
14839
16246
  }
14840
16247
  await this.avatarController.rerenderCurrentFrameIfPaused();
14841
16248
  }
16249
+ /**
16250
+ * 处理尺寸变化:通知渲染系统更新视口与投影
16251
+ */
14842
16252
  handleResize() {
14843
16253
  if (this.renderSystem) {
14844
16254
  this.renderSystem.handleResize();
14845
16255
  }
14846
16256
  }
16257
+ /**
16258
+ * 获取渲染性能统计
16259
+ * @returns 渲染性能统计数据,如果渲染系统未初始化则返回 null
16260
+ * @internal Not part of public API yet
16261
+ */
14847
16262
  getPerformanceStats() {
14848
16263
  if (!this.renderSystem || !this.isInitialized) {
14849
16264
  return null;
@@ -14853,8 +16268,19 @@ class AvatarView {
14853
16268
  sortTime: this.renderSystem.sortTime,
14854
16269
  backend: this.renderSystem.getBackend(),
14855
16270
  fps: this.currentFPS
16271
+ // pointCount 可后续通过 AvatarCoreAdapter 添加公开方法获取
14856
16272
  };
14857
16273
  }
16274
+ /**
16275
+ * Get or set avatar transform in canvas
16276
+ *
16277
+ * @example
16278
+ * // Get current transform
16279
+ * const current = avatarView.transform
16280
+ *
16281
+ * // Set transform
16282
+ * avatarView.transform = { x: 0.5, y: 0, scale: 2.0 }
16283
+ */
14858
16284
  get transform() {
14859
16285
  if (!this.renderSystem) {
14860
16286
  throw new Error("Render system not initialized");
@@ -14872,6 +16298,10 @@ class AvatarView {
14872
16298
  this.renderSystem.renderFrame();
14873
16299
  }
14874
16300
  }
16301
+ /**
16302
+ * 上报 avatar_active 埋点
16303
+ * @private
16304
+ */
14875
16305
  reportAvatarActive() {
14876
16306
  var _a, _b;
14877
16307
  logEvent("avatar_active", "info", {
@@ -14880,6 +16310,10 @@ class AvatarView {
14880
16310
  dsm: ((_b = AvatarSDK.configuration) == null ? void 0 : _b.drivingServiceMode) || DrivingServiceMode.sdk
14881
16311
  });
14882
16312
  }
16313
+ /**
16314
+ * 启动 avatar_active 心跳埋点(每10分钟一次)
16315
+ * @private
16316
+ */
14883
16317
  startAvatarActiveHeartbeat() {
14884
16318
  this.stopAvatarActiveHeartbeat();
14885
16319
  this.avatarActiveTimer = window.setInterval(() => {
@@ -14888,6 +16322,10 @@ class AvatarView {
14888
16322
  }
14889
16323
  }, this.AVATAR_ACTIVE_INTERVAL);
14890
16324
  }
16325
+ /**
16326
+ * 停止 avatar_active 心跳埋点
16327
+ * @private
16328
+ */
14891
16329
  stopAvatarActiveHeartbeat() {
14892
16330
  if (this.avatarActiveTimer !== null) {
14893
16331
  clearInterval(this.avatarActiveTimer);