@spatius/avatarkit 1.3.0 → 1.3.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.3.1-beta.1] - 2026-07-16
9
+
10
+ ### Changed
11
+ - The `region` configuration now defaults to automatic selection: when left unset, the SDK picks the closest serving region at initialization. Passing an explicit `region` continues to force that region, unchanged. If automatic selection can't be reached, the SDK falls back to a default region and continues initializing.
12
+
8
13
  ## [1.3.0] - 2026-07-04
9
14
 
10
15
  ### Added
@@ -1,7 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { A as APP_CONFIG, l as logger, e as errorToMessage, a as logEvent } from "./index-BDFs1WRg.js";
4
+ import { A as APP_CONFIG, l as logger, e as errorToMessage, a as logEvent } from "./index-C54v6mHz.js";
5
5
  class StreamingAudioPlayer {
6
6
  // Mark if AudioContext is being resumed, avoid concurrent resume requests
7
7
  constructor(options) {
@@ -4189,6 +4189,7 @@ const RENDER_QUALITY_PARAMS = {
4189
4189
  ]: { renderScale: 1, splatRadius: 3 }
4190
4190
  };
4191
4191
  const DEFAULT_REGION = "us-west";
4192
+ const DEFAULT_REGION_REQUEST = "auto";
4192
4193
  var LoadProgress = /* @__PURE__ */ ((LoadProgress2) => {
4193
4194
  LoadProgress2["downloading"] = "downloading";
4194
4195
  LoadProgress2["completed"] = "completed";
@@ -11586,11 +11587,259 @@ class EventStore {
11586
11587
  }
11587
11588
  }
11588
11589
  const eventStore = new EventStore();
11590
+ const BOOTSTRAP_URL = "https://global.spatialwalk.top/bootstrap";
11591
+ async function fetchBootstrap(opts, signal, onResponse) {
11592
+ const res = await fetch(BOOTSTRAP_URL, {
11593
+ method: "POST",
11594
+ body: JSON.stringify({
11595
+ app_id: opts.appId,
11596
+ sdk_version: opts.sdkVersion,
11597
+ region: opts.region ?? "auto",
11598
+ platform: opts.platform ?? "web"
11599
+ }),
11600
+ cache: "no-store",
11601
+ signal
11602
+ });
11603
+ onResponse == null ? void 0 : onResponse();
11604
+ if (!res.ok) {
11605
+ throw new Error(`bootstrap HTTP ${res.status}`);
11606
+ }
11607
+ return await res.json();
11608
+ }
11609
+ const PROBE_TIMEOUT_MS = 5e3;
11610
+ const SAMPLE_COUNT = 3;
11611
+ const RECALIBRATE_INTERVAL_MS = 20 * 60 * 1e3;
11612
+ class ClockSync {
11613
+ constructor() {
11614
+ __publicField(this, "localBase", Date.now());
11615
+ __publicField(this, "serverBase", null);
11616
+ __publicField(this, "monoAtCalibrate", performance.now());
11617
+ /** 首次校准是否已落定(落定前埋点应挂队列) */
11618
+ __publicField(this, "calibrated", false);
11619
+ __publicField(this, "appId", "");
11620
+ __publicField(this, "sdkVersion", "");
11621
+ __publicField(this, "platform", "web");
11622
+ __publicField(this, "timer", null);
11623
+ __publicField(this, "visibilityHandler", null);
11624
+ __publicField(this, "syncing", false);
11625
+ /** 首次校准落定后触发的回调(供上报层 flush 挂起队列) */
11626
+ __publicField(this, "readyCallbacks", []);
11627
+ }
11628
+ /** 首次校准是否已完成(供上报闸门判断) */
11629
+ isReady() {
11630
+ return this.calibrated;
11631
+ }
11632
+ /**
11633
+ * 注册"首次校准落定"回调(成功或失败都算落定)。
11634
+ * 若已落定则立即同步触发一次。
11635
+ */
11636
+ onReady(cb) {
11637
+ if (this.calibrated) {
11638
+ cb();
11639
+ return;
11640
+ }
11641
+ this.readyCallbacks.push(cb);
11642
+ }
11643
+ fireReady() {
11644
+ const cbs = this.readyCallbacks;
11645
+ this.readyCallbacks = [];
11646
+ for (const cb of cbs) {
11647
+ try {
11648
+ cb();
11649
+ } catch {
11650
+ }
11651
+ }
11652
+ }
11653
+ /** 本地时间戳:本地基准 + 单调流逝。一定有值。 */
11654
+ localNow() {
11655
+ return this.localAt(performance.now());
11656
+ }
11657
+ /** 后台时间戳:后台基准 + 单调流逝。未校准到后台时返回 null(上报留空)。 */
11658
+ serverNow() {
11659
+ return this.serverAt(performance.now());
11660
+ }
11661
+ /**
11662
+ * 仅供单测:直接设定基准,绕过真实网络校准。
11663
+ * @internal
11664
+ */
11665
+ __setBaselineForTest(opts) {
11666
+ this.localBase = opts.localBase;
11667
+ this.serverBase = opts.serverBase;
11668
+ this.monoAtCalibrate = opts.monoAtCalibrate;
11669
+ this.calibrated = opts.calibrated ?? true;
11670
+ }
11671
+ /** 用"指定单调读数"算本地时间戳(供入队事件按入队时刻还原)。取整到毫秒。 */
11672
+ localAt(mono) {
11673
+ return Math.round(this.localBase + (mono - this.monoAtCalibrate));
11674
+ }
11675
+ /** 用"指定单调读数"算后台时间戳;未校准到后台时返回 null。取整到毫秒。 */
11676
+ serverAt(mono) {
11677
+ if (this.serverBase === null) return null;
11678
+ return Math.round(this.serverBase + (mono - this.monoAtCalibrate));
11679
+ }
11680
+ /**
11681
+ * 用时间戳做逻辑(端到端延迟 tap_N/anim_N 等)时用:优先后台时间戳,
11682
+ * 拿不到后台才退本地,保证与后台时刻同一时间线对齐。
11683
+ */
11684
+ timelineNow() {
11685
+ return this.serverNow() ?? this.localNow();
11686
+ }
11687
+ /**
11688
+ * 把"采集时刻的单调读数"换算成时间线时刻(优先后台、退本地)。
11689
+ * 供标记解析用——采集时若校准未完成,先记单调读数,上报时再用最新基准换算。
11690
+ * @param mono 采集那一刻的 performance.now()
11691
+ */
11692
+ resolveMono(mono) {
11693
+ const drift = mono - this.monoAtCalibrate;
11694
+ const base = this.serverBase ?? this.localBase;
11695
+ return Math.round(base + drift);
11696
+ }
11697
+ /**
11698
+ * 启动:立即校准一次,并注册定时 + 页面可见性重校准。
11699
+ * 幂等;fire-and-forget,不阻塞初始化。
11700
+ * @internal
11701
+ */
11702
+ start(opts) {
11703
+ this.appId = opts.appId;
11704
+ this.sdkVersion = opts.sdkVersion;
11705
+ void this.calibrate();
11706
+ if (this.timer === null) {
11707
+ this.timer = setInterval(() => void this.calibrate(), RECALIBRATE_INTERVAL_MS);
11708
+ }
11709
+ if (this.visibilityHandler === null && typeof document !== "undefined") {
11710
+ this.visibilityHandler = () => {
11711
+ if (document.visibilityState === "visible") {
11712
+ void this.calibrate();
11713
+ }
11714
+ };
11715
+ document.addEventListener("visibilitychange", this.visibilityHandler);
11716
+ }
11717
+ }
11718
+ /**
11719
+ * 校准一次:多采样取最小 RTT 的样本,同时刷新本地/后台基准与单调零点。
11720
+ * - 拿到后台:serverBase 更新为估算值。
11721
+ * - 拿不到后台:serverBase 置空(后台时间戳留空),localBase 照常更新为当前墙钟。
11722
+ * 已校准过后若本次全失败:保留旧基准、跳过(不把好基准换成更差的)。
11723
+ * @internal
11724
+ */
11725
+ async calibrate() {
11726
+ if (this.syncing) return;
11727
+ this.syncing = true;
11728
+ try {
11729
+ const samples = [];
11730
+ for (let i2 = 0; i2 < SAMPLE_COUNT; i2++) {
11731
+ const s2 = await this.probe();
11732
+ if (s2) samples.push(s2);
11733
+ }
11734
+ if (samples.length > 0) {
11735
+ const best = samples.reduce((a2, b2) => b2.rttNet < a2.rttNet ? b2 : a2);
11736
+ const firstCalibration = !this.calibrated;
11737
+ this.serverBase = best.serverBase;
11738
+ this.localBase = best.localBase;
11739
+ this.monoAtCalibrate = best.monoAtCalibrate;
11740
+ this.calibrated = true;
11741
+ if (firstCalibration) this.fireReady();
11742
+ logEvent("time_calibrated", "info", {
11743
+ has_server: true,
11744
+ rtt_ms: Math.round(best.rttNet)
11745
+ });
11746
+ return;
11747
+ }
11748
+ if (this.calibrated) {
11749
+ logger.log("[ClockSync] recalibrate got no server time, keeping previous baseline");
11750
+ return;
11751
+ }
11752
+ this.localBase = Date.now();
11753
+ this.monoAtCalibrate = performance.now();
11754
+ this.serverBase = null;
11755
+ this.calibrated = true;
11756
+ this.fireReady();
11757
+ logEvent("time_calibrated", "info", { has_server: false });
11758
+ } finally {
11759
+ this.syncing = false;
11760
+ }
11761
+ }
11762
+ /** 单次采样:请求 bootstrap,返回一组 (rttNet, serverBase, monoAtCalibrate, localBase);失败返回 null。 */
11763
+ async probe() {
11764
+ const controller = new AbortController();
11765
+ const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
11766
+ try {
11767
+ let t1 = 0;
11768
+ let localAtT1 = 0;
11769
+ const t0 = performance.now();
11770
+ const body = await fetchBootstrap(
11771
+ {
11772
+ appId: this.appId,
11773
+ sdkVersion: this.sdkVersion,
11774
+ region: "auto",
11775
+ platform: this.platform
11776
+ },
11777
+ controller.signal,
11778
+ () => {
11779
+ t1 = performance.now();
11780
+ localAtT1 = Date.now();
11781
+ }
11782
+ );
11783
+ const ts2 = body == null ? void 0 : body.time_sync;
11784
+ if (!ts2 || typeof ts2.server_receive_ms !== "number" || typeof ts2.server_send_ms !== "number") {
11785
+ return null;
11786
+ }
11787
+ const rttNet = t1 - t0 - (ts2.server_send_ms - ts2.server_receive_ms);
11788
+ const serverBase = ts2.server_send_ms + rttNet / 2;
11789
+ return {
11790
+ rttNet: Math.max(0, rttNet),
11791
+ serverBase,
11792
+ monoAtCalibrate: t1,
11793
+ localBase: localAtT1
11794
+ };
11795
+ } catch {
11796
+ return null;
11797
+ } finally {
11798
+ clearTimeout(timer);
11799
+ }
11800
+ }
11801
+ /** 清理定时器与监听器。 */
11802
+ cleanup() {
11803
+ if (this.timer !== null) {
11804
+ clearInterval(this.timer);
11805
+ this.timer = null;
11806
+ }
11807
+ if (this.visibilityHandler !== null && typeof document !== "undefined") {
11808
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
11809
+ this.visibilityHandler = null;
11810
+ }
11811
+ }
11812
+ }
11813
+ const clockSync = new ClockSync();
11814
+ const MARK_KEY = "__mark";
11815
+ function monoTimestamp() {
11816
+ return { [MARK_KEY]: "mono", value: performance.now() };
11817
+ }
11818
+ function isMark(v2) {
11819
+ return typeof v2 === "object" && v2 !== null && MARK_KEY in v2;
11820
+ }
11821
+ function resolveMark(mark) {
11822
+ switch (mark[MARK_KEY]) {
11823
+ case "mono":
11824
+ return clockSync.resolveMono(mark.value);
11825
+ }
11826
+ }
11827
+ function resolveMarks(contents) {
11828
+ let out = null;
11829
+ for (const [k2, v2] of Object.entries(contents)) {
11830
+ if (isMark(v2)) {
11831
+ if (!out) out = { ...contents };
11832
+ out[k2] = resolveMark(v2);
11833
+ }
11834
+ }
11835
+ return out ?? contents;
11836
+ }
11589
11837
  const OTEL_LOGGER_NAME = "spatius-avatarkit";
11590
11838
  let sdkVersion$1 = "1.0.0";
11591
11839
  let isInitialized$1 = false;
11592
11840
  let loggerProvider = null;
11593
11841
  const eventQueue$1 = [];
11842
+ const preCalibrationQueue = [];
11594
11843
  function buildBasicAuthHeader() {
11595
11844
  const credentials = `${OTEL_USERNAME}:${OTEL_PASSWORD}`;
11596
11845
  return `Basic ${btoa(credentials)}`;
@@ -11644,6 +11893,7 @@ function initializeOtel(version, resourceAttrs) {
11644
11893
  isInitialized$1 = true;
11645
11894
  logger.log(`[OTel] Initialized successfully - endpoint: ${OTEL_LOGS_ENDPOINT}, stream: ${OTEL_STREAM_NAME}`);
11646
11895
  flushEventQueue$1();
11896
+ clockSync.onReady(() => flushPreCalibrationQueue());
11647
11897
  replayPersistedEvents().catch(() => {
11648
11898
  });
11649
11899
  } catch (error) {
@@ -11704,7 +11954,26 @@ function flushEventQueue$1() {
11704
11954
  eventQueue$1.length = 0;
11705
11955
  }
11706
11956
  function trackEventOtel(event, level = "info", contents = {}) {
11707
- const timestamp = Date.now();
11957
+ if (!clockSync.isReady()) {
11958
+ preCalibrationQueue.push({ event, level, contents, enqueueMono: performance.now() });
11959
+ return;
11960
+ }
11961
+ emitOtelEvent(event, level, contents, performance.now());
11962
+ }
11963
+ function flushPreCalibrationQueue() {
11964
+ if (preCalibrationQueue.length === 0) return;
11965
+ const pending = preCalibrationQueue.splice(0, preCalibrationQueue.length);
11966
+ for (const e2 of pending) {
11967
+ emitOtelEvent(e2.event, e2.level, e2.contents, e2.enqueueMono);
11968
+ }
11969
+ }
11970
+ function emitOtelEvent(event, level, contents, atMono) {
11971
+ contents = resolveMarks(contents);
11972
+ const timestamp = clockSync.localAt(atMono);
11973
+ const serverTs = clockSync.serverAt(atMono);
11974
+ if (serverTs !== null) {
11975
+ contents = { ...contents, server_timestamp: serverTs };
11976
+ }
11708
11977
  eventStore.add({ event, level, contents, timestamp }).then((id) => {
11709
11978
  const contentsWithIndex = { ...contents, _index: id };
11710
11979
  if (!loggerProvider) return;
@@ -11807,6 +12076,15 @@ function cleanupOtel() {
11807
12076
  loggerProvider = null;
11808
12077
  }
11809
12078
  }
12079
+ function timestampFields(enqueueMono) {
12080
+ const local = enqueueMono === void 0 ? clockSync.localNow() : clockSync.localAt(enqueueMono);
12081
+ const server = enqueueMono === void 0 ? clockSync.serverNow() : clockSync.serverAt(enqueueMono);
12082
+ const fields = { timestamp: local };
12083
+ if (server !== null) {
12084
+ fields.server_timestamp = server;
12085
+ }
12086
+ return fields;
12087
+ }
11810
12088
  let sdkVersion = "1.0.0";
11811
12089
  let isInitialized = false;
11812
12090
  let commonRegion = "";
@@ -11885,6 +12163,7 @@ function initializePostHog(version, commonFields) {
11885
12163
  }
11886
12164
  posthogInstance.setPersonPropertiesForFlags(userProperties);
11887
12165
  flushEventQueue();
12166
+ clockSync.onReady(() => flushEventQueue());
11888
12167
  }
11889
12168
  }, SDK_POSTHOG_INSTANCE_NAME);
11890
12169
  } catch (error) {
@@ -11948,20 +12227,20 @@ function updatePostHogPersonPropertiesForFlags() {
11948
12227
  instance.setPersonPropertiesForFlags(commonFields);
11949
12228
  }
11950
12229
  function flushEventQueue() {
11951
- if (!sdkPosthogInstance || eventQueue.length === 0) {
12230
+ if (!sdkPosthogInstance || !clockSync.isReady() || eventQueue.length === 0) {
11952
12231
  return;
11953
12232
  }
11954
12233
  logger.log(`[PostHog] Flushing ${eventQueue.length} queued events`);
11955
12234
  setTimeout(() => {
11956
- for (const { event, level, contents } of eventQueue) {
12235
+ for (const { event, level, contents, enqueueMono } of eventQueue) {
11957
12236
  try {
11958
12237
  const commonFields = getCommonFields();
11959
12238
  const properties = {
11960
12239
  ...commonFields,
11961
12240
  level,
11962
12241
  service_module: "sdk",
11963
- timestamp: Date.now(),
11964
- ...contents
12242
+ ...timestampFields(enqueueMono),
12243
+ ...resolveMarks(contents)
11965
12244
  };
11966
12245
  sdkPosthogInstance.capture(event, properties);
11967
12246
  } catch (error) {
@@ -11977,8 +12256,8 @@ function trackEvent(event, level = "info", contents = {}) {
11977
12256
  return;
11978
12257
  }
11979
12258
  const instance = getSdkPosthogInstance();
11980
- if (!instance) {
11981
- eventQueue.push({ event, level, contents });
12259
+ if (!instance || !clockSync.isReady()) {
12260
+ eventQueue.push({ event, level, contents, enqueueMono: performance.now() });
11982
12261
  return;
11983
12262
  }
11984
12263
  sdkPosthogInstance = instance;
@@ -11988,8 +12267,8 @@ function trackEvent(event, level = "info", contents = {}) {
11988
12267
  ...commonFields,
11989
12268
  level,
11990
12269
  service_module: "sdk",
11991
- timestamp: Date.now(),
11992
- ...contents
12270
+ ...timestampFields(),
12271
+ ...resolveMarks(contents)
11993
12272
  };
11994
12273
  sdkPosthogInstance.capture(event, properties);
11995
12274
  } catch (error) {
@@ -12627,7 +12906,7 @@ const _AnimationPlayer = class _AnimationPlayer {
12627
12906
  if (this.streamingPlayer) {
12628
12907
  return;
12629
12908
  }
12630
- const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-DmOtqI_a.js");
12909
+ const { StreamingAudioPlayer } = await import("./StreamingAudioPlayer-BQawYU25.js");
12631
12910
  const { AvatarSDK: AvatarSDK2 } = await Promise.resolve().then(() => AvatarSDK$1);
12632
12911
  const audioFormat = AvatarSDK2.getAudioFormat();
12633
12912
  this.streamingPlayer = new StreamingAudioPlayer({
@@ -12885,6 +13164,38 @@ class AvatarViewRegistry {
12885
13164
  }
12886
13165
  }
12887
13166
  const avatarViewRegistry = new AvatarViewRegistry();
13167
+ const RESOLVE_TIMEOUT_MS = 5e3;
13168
+ async function resolveRegion(opts) {
13169
+ var _a;
13170
+ const { appId, sdkVersion: sdkVersion2, requestedRegion } = opts;
13171
+ if (requestedRegion !== DEFAULT_REGION_REQUEST) {
13172
+ return requestedRegion;
13173
+ }
13174
+ const controller = new AbortController();
13175
+ const timer = setTimeout(() => controller.abort(), RESOLVE_TIMEOUT_MS);
13176
+ try {
13177
+ const res = await fetchBootstrap(
13178
+ { appId, sdkVersion: sdkVersion2, region: "auto" },
13179
+ controller.signal
13180
+ );
13181
+ const current = (_a = res.region) == null ? void 0 : _a.current;
13182
+ if (typeof current === "string" && current.length > 0) {
13183
+ logger.log(`[RegionResolver] auto → ${current}`);
13184
+ return current;
13185
+ }
13186
+ throw new Error("bootstrap response missing region.current");
13187
+ } catch (error) {
13188
+ const message = error instanceof Error ? error.message : String(error);
13189
+ logger.warn(`[RegionResolver] auto resolve failed, falling back to ${DEFAULT_REGION}: ${message}`);
13190
+ logEvent("region_resolve_failed", "error", {
13191
+ fallback_region: DEFAULT_REGION,
13192
+ reason: message
13193
+ });
13194
+ return DEFAULT_REGION;
13195
+ } finally {
13196
+ clearTimeout(timer);
13197
+ }
13198
+ }
12888
13199
  class AvatarCoreMemoryManager {
12889
13200
  constructor(wasmModule) {
12890
13201
  __publicField(this, "module");
@@ -14322,17 +14633,23 @@ class AvatarSDK {
14322
14633
  }
14323
14634
  static async _initializeInternal(appId, configuration) {
14324
14635
  try {
14325
- logger.log(`[AvatarSDK] Initializing with appId: ${appId}, region: ${configuration.region || DEFAULT_REGION}`);
14636
+ const requestedRegion = configuration.region || DEFAULT_REGION_REQUEST;
14637
+ logger.log(`[AvatarSDK] Initializing with appId: ${appId}, region: ${requestedRegion}`);
14326
14638
  resetDeprecationWarnings();
14327
- this._configuration = configuration;
14328
14639
  setLogLevel(configuration.logLevel ?? LogLevel.off);
14329
14640
  idManager.setAppId(appId);
14330
14641
  logger.log(`[AvatarSDK] Client ID: ${idManager.getClientId()}`);
14331
- const region = configuration.region || DEFAULT_REGION;
14332
14642
  const dsm = configuration.drivingServiceMode || DrivingServiceMode.direct;
14333
14643
  const appIdValue = idManager.getAppId() || "";
14644
+ const region = await resolveRegion({
14645
+ appId: appIdValue,
14646
+ sdkVersion: this._version,
14647
+ requestedRegion
14648
+ });
14649
+ this._configuration = { ...configuration, region };
14334
14650
  initializePostHog(this._version, { region, dsm });
14335
14651
  initializeOtel(this._version, { region, dsm, appId: appIdValue });
14652
+ clockSync.start({ appId: appIdValue, sdkVersion: this._version });
14336
14653
  await this.initializeWASMModule();
14337
14654
  await this.initializeTemplateResources();
14338
14655
  this._initializationState = "initialized";
@@ -14594,6 +14911,7 @@ class AvatarSDK {
14594
14911
  idManager.clear();
14595
14912
  cleanupPostHog();
14596
14913
  cleanupOtel();
14914
+ clockSync.cleanup();
14597
14915
  logger.log("[AvatarSDK] Cleanup completed");
14598
14916
  } catch (error) {
14599
14917
  logger.error("Failed to cleanup AvatarSDK:", error instanceof Error ? error.message : String(error));
@@ -14712,7 +15030,7 @@ class AvatarSDK {
14712
15030
  __publicField(AvatarSDK, "_initializationState", "uninitialized");
14713
15031
  __publicField(AvatarSDK, "_initializingPromise", null);
14714
15032
  __publicField(AvatarSDK, "_configuration", null);
14715
- __publicField(AvatarSDK, "_version", "1.3.0");
15033
+ __publicField(AvatarSDK, "_version", "1.3.1-beta.1");
14716
15034
  __publicField(AvatarSDK, "_avatarCore", null);
14717
15035
  __publicField(AvatarSDK, "_cachedDeviceScore", null);
14718
15036
  __publicField(AvatarSDK, "_rendererBackend", null);
@@ -15338,14 +15656,14 @@ class NetworkLayer {
15338
15656
  }
15339
15657
  const metrics = this.audioMetrics;
15340
15658
  if (!metrics.taps.has(0)) {
15341
- metrics.taps.set(0, Date.now());
15659
+ metrics.taps.set(0, monoTimestamp());
15342
15660
  }
15343
15661
  metrics.accumulatedBytes += audioData.byteLength;
15344
15662
  const currentDuration = metrics.accumulatedBytes / this.getAudioBytesPerSecond();
15345
15663
  const maxTapKey = metrics.taps.size > 0 ? Math.max(...metrics.taps.keys()) : 0;
15346
15664
  let s2 = maxTapKey + 1;
15347
15665
  while (currentDuration >= s2) {
15348
- metrics.taps.set(s2, Date.now());
15666
+ metrics.taps.set(s2, monoTimestamp());
15349
15667
  s2++;
15350
15668
  }
15351
15669
  if (audioData.byteLength === 0 && !isLast) {
@@ -15502,7 +15820,7 @@ class NetworkLayer {
15502
15820
  if (!this.dataController.getIsPlaying()) {
15503
15821
  this.dataController.startStreamingPlayback();
15504
15822
  }
15505
- this.audioMetrics.anims.set(this.audioMetrics.animGroupCount, Date.now());
15823
+ this.audioMetrics.anims.set(this.audioMetrics.animGroupCount, monoTimestamp());
15506
15824
  this.audioMetrics.animGroupCount++;
15507
15825
  } else {
15508
15826
  logger.warn(`[NetworkLayer] Animation message has no animation data - conversationId: ${conversationId}`);
@@ -16336,15 +16654,15 @@ class AvatarController {
16336
16654
  if (this.playbackMode === DrivingServiceMode.backend) {
16337
16655
  const metrics = this.hostModeMetrics;
16338
16656
  if (metrics.startTimestamp === 0) {
16339
- metrics.startTimestamp = Date.now();
16657
+ metrics.startTimestamp = clockSync.timelineNow();
16340
16658
  }
16341
16659
  metrics.accumulatedBytes += data.length;
16342
16660
  const currentDuration = metrics.accumulatedBytes / this.audioBytesPerSecond;
16343
16661
  if (currentDuration >= 1 && metrics.tap1Timestamp === 0) {
16344
- metrics.tap1Timestamp = Date.now();
16662
+ metrics.tap1Timestamp = clockSync.timelineNow();
16345
16663
  }
16346
16664
  if (currentDuration >= 2 && metrics.tap2Timestamp === 0) {
16347
- metrics.tap2Timestamp = Date.now();
16665
+ metrics.tap2Timestamp = clockSync.timelineNow();
16348
16666
  }
16349
16667
  }
16350
16668
  if (this.isPlaying && ((_b = this.animationPlayer) == null ? void 0 : _b.isStreamingReady())) {
@@ -16465,7 +16783,7 @@ class AvatarController {
16465
16783
  this.currentKeyframes = flameKeyframes;
16466
16784
  if (this.playbackMode === DrivingServiceMode.backend && !this.hostModeMetrics.didRecvFirstFlame) {
16467
16785
  this.hostModeMetrics.didRecvFirstFlame = true;
16468
- this.hostModeMetrics.recvFirstFlameTimestamp = Date.now();
16786
+ this.hostModeMetrics.recvFirstFlameTimestamp = clockSync.timelineNow();
16469
16787
  }
16470
16788
  } else {
16471
16789
  this.currentKeyframes.push(...flameKeyframes);
@@ -20400,6 +20718,11 @@ class AvatarView {
20400
20718
  * @internal
20401
20719
  */
20402
20720
  createCanvas(container) {
20721
+ if (container.querySelector("canvas")) {
20722
+ logger.warn(
20723
+ "[AvatarView] The container already contains a <canvas>. This usually means another AvatarView was created in the same container without disposing the previous one, which will render two avatars side by side. Call dispose() on the previous AvatarView before creating a new one, or use a separate container."
20724
+ );
20725
+ }
20403
20726
  const canvas = document.createElement("canvas");
20404
20727
  const containerWidth = container.offsetWidth || 800;
20405
20728
  const containerHeight = container.offsetHeight || 600;
@@ -21746,12 +22069,13 @@ export {
21746
22069
  AvatarView as g,
21747
22070
  RENDER_QUALITY_PARAMS as h,
21748
22071
  DEFAULT_REGION as i,
21749
- LoadProgress as j,
21750
- AnimationType as k,
22072
+ DEFAULT_REGION_REQUEST as j,
22073
+ LoadProgress as k,
21751
22074
  logger as l,
21752
- ConversationState as m,
21753
- AvatarState as n,
21754
- AvatarError as o,
21755
- ResourceType as p,
21756
- extractResourceUrls as q
22075
+ AnimationType as m,
22076
+ ConversationState as n,
22077
+ AvatarState as o,
22078
+ AvatarError as p,
22079
+ ResourceType as q,
22080
+ extractResourceUrls as r
21757
22081
  };
package/dist/index.js CHANGED
@@ -1,24 +1,25 @@
1
- import { k, b, c, o, f, d, n, g, C, m, i, D, E, F, j, L, h, R, p, T, q } from "./index-BDFs1WRg.js";
1
+ import { m, b, c, p, f, d, o, g, C, n, i, j, D, E, F, k, L, h, R, q, T, r } from "./index-C54v6mHz.js";
2
2
  export {
3
- k as AnimationType,
3
+ m as AnimationType,
4
4
  b as Avatar,
5
5
  c as AvatarController,
6
- o as AvatarError,
6
+ p as AvatarError,
7
7
  f as AvatarManager,
8
8
  d as AvatarSDK,
9
- n as AvatarState,
9
+ o as AvatarState,
10
10
  g as AvatarView,
11
11
  C as ConnectionState,
12
- m as ConversationState,
12
+ n as ConversationState,
13
13
  i as DEFAULT_REGION,
14
+ j as DEFAULT_REGION_REQUEST,
14
15
  D as DrivingServiceMode,
15
16
  E as ErrorCode,
16
17
  F as FrameStarvationMode,
17
- j as LoadProgress,
18
+ k as LoadProgress,
18
19
  L as LogLevel,
19
20
  h as RENDER_QUALITY_PARAMS,
20
21
  R as RenderQuality,
21
- p as ResourceType,
22
+ q as ResourceType,
22
23
  T as TransitionType,
23
- q as extractResourceUrls
24
+ r as extractResourceUrls
24
25
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@spatius/avatarkit",
3
3
  "type": "module",
4
- "version": "1.3.0",
4
+ "version": "1.3.1-beta.1",
5
5
  "packageManager": "pnpm@10.18.2",
6
6
  "description": "AvatarKit SDK - Real-time Avatar Rendering SDK for Web",
7
7
  "author": "AvatarKit Team",
@@ -87,6 +87,7 @@
87
87
  "devDependencies": {
88
88
  "@types/node": "^20.11.30",
89
89
  "@webgpu/types": "^0.1.65",
90
+ "fflate": "0.8.3",
90
91
  "tsx": "^4.20.6",
91
92
  "typescript": "^5.0.0",
92
93
  "vite": "^5.0.0",