@spatius/avatarkit 1.3.7 → 1.3.9

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.
@@ -69,6 +69,11 @@ export declare class AvatarController {
69
69
  private receivedAudioBytes;
70
70
  private receivedAnimationFrames;
71
71
  private playbackStartedAt;
72
+ /**
73
+ * 新一轮起播时 SDK 自己打断上一轮走的也是 interrupt();那不是宿主的动作,不记
74
+ * lifecycle_api_called。内部调用经 interruptForNewRound() 置位后再进 interrupt()。
75
+ */
76
+ private suppressLifecycleEvent;
72
77
  private playbackEndedAt;
73
78
  /**
74
79
  * Whether this round's final animation batch (ServerResponseAnimation.end) has arrived.
@@ -82,10 +87,6 @@ export declare class AvatarController {
82
87
  * Orthogonal to user pause — user pause/resume does not change it; only frame arrival does.
83
88
  */
84
89
  private isAudioStalledForStarvation;
85
- private playbackStuckCheckState;
86
- private readonly MAX_AUDIO_TIME_ZERO_COUNT;
87
- private readonly MAX_AUDIO_TIME_STUCK_COUNT;
88
- private readonly AUDIO_TIME_STUCK_THRESHOLD;
89
90
  private latencyMarks;
90
91
  /**
91
92
  * W3C `traceparent` the driving service stamped on this round's animation
@@ -214,6 +215,14 @@ export declare class AvatarController {
214
215
  * Animation will continue from paused frame (because animation time base comes from audio, will auto-sync)
215
216
  */
216
217
  resume(): Promise<void>;
218
+ /**
219
+ * 宿主调了 close / interrupt(AvatarView.dispose 在 View 里记)。带调用栈:排查
220
+ * "数字人中途消失"时,这条日志直接指出是宿主哪段代码在断连后多久做了什么。
221
+ * 在播时记 warning,便于按级别筛出"播到一半被宿主打断"。
222
+ */
223
+ private logLifecycleCall;
224
+ /** SDK 内部起新一轮时打断上一轮:走 interrupt() 但不记宿主生命周期事件。 */
225
+ private interruptForNewRound;
217
226
  /**
218
227
  * Interrupt current playback
219
228
  */
@@ -48,6 +48,15 @@ export declare class AvatarSDK {
48
48
  /**
49
49
  * Cleanup resources
50
50
  */
51
+ private static onPageHide;
52
+ /**
53
+ * pagehide 时直发一条 `page_unload`:整页刷新 / 关闭是"数字人整个消失"的一种来源
54
+ * (宿主自己 reload 切 region 就是实例),服务端只能看到一个 1001,客户端此前什么都
55
+ * 不留。走 emitDirectLog(fetch keepalive)而不是 logEvent——后者先写 IndexedDB 再等
56
+ * 批处理,页面卸载时发不出去。
57
+ */
58
+ private static installPageHideProbe;
59
+ private static removePageHideProbe;
51
60
  static cleanup(): void;
52
61
  /**
53
62
  * Device performance score (CPU + GPU).
@@ -64,8 +73,9 @@ export declare class AvatarSDK {
64
73
  /**
65
74
  * Check if the current device can run the avatar.
66
75
  * Runs a ~2s benchmark, reports device info and scores to PostHog.
67
- * Currently always returns true thresholds will be calibrated from production data.
76
+ * Returns false when neither WebGL2 nor WebGPU can be created that is deterministic
77
+ * and the benchmark is skipped. Otherwise runs the benchmark and returns true; the
78
+ * score thresholds are not yet enforced and will be calibrated from production data.
68
79
  */
69
80
  static isDeviceSupported(): Promise<boolean>;
70
- private static _getGPURenderer;
71
81
  }
@@ -13,6 +13,10 @@ export declare class AvatarView {
13
13
  private resizeObserver;
14
14
  private onWindowResize;
15
15
  private onVisibilityChange;
16
+ private canvasSeenConnected;
17
+ private canvasDetachedReported;
18
+ private containerSeenSized;
19
+ private containerHiddenReported;
16
20
  private frameCount;
17
21
  private lastFpsUpdate;
18
22
  private currentFPS;
@@ -52,6 +56,44 @@ export declare class AvatarView {
52
56
  width: number;
53
57
  height: number;
54
58
  };
59
+ /**
60
+ * Set once the first-frame callback has fired; the render loop samples the
61
+ * canvas on the first frame after this deadline and then clears it.
62
+ *
63
+ * The check exists because "the render loop is running" does not mean anything
64
+ * reached the screen. Failures along the render path mostly log and return, so
65
+ * a user seeing nothing produces no signal at all. This asks the only question
66
+ * that matters at the end: did this layer draw anything?
67
+ *
68
+ * Sampling has to happen inside the loop, right after renderFrame() — the
69
+ * context runs with preserveDrawingBuffer:false, so the buffer is only valid
70
+ * there. Reading from a timer would find it already cleared and report every
71
+ * healthy session as blank.
72
+ */
73
+ private _blankCheckDueAt;
74
+ private _blankCheckDone;
75
+ /** How long after the first frame to sample. Long enough for the pipeline to settle. */
76
+ private static readonly BLANK_CHECK_DELAY_MS;
77
+ /**
78
+ * Sample size. The browser scales the full canvas into this, so it covers the
79
+ * whole frame rather than a crop — the avatar is found wherever it sits. Kept
80
+ * tiny because the question is only "any opaque pixel at all"; reading the
81
+ * full canvas would copy megabytes for a yes/no answer.
82
+ */
83
+ private static readonly BLANK_CHECK_WIDTH;
84
+ private static readonly BLANK_CHECK_HEIGHT;
85
+ private scheduleBlankCanvasCheck;
86
+ /**
87
+ * Called from the render loop immediately after renderFrame(), while the
88
+ * drawing buffer is still valid.
89
+ */
90
+ private runBlankCanvasCheckIfDue;
91
+ /**
92
+ * Count pixels that are not fully transparent. Null when the sample could not
93
+ * be taken at all, which is not the same as a blank frame and must not be
94
+ * reported as one.
95
+ */
96
+ private countOpaquePixels;
55
97
  private _exportBitmapResolve;
56
98
  /**
57
99
  * Exports the current rendering as a Blob (PNG).
@@ -62,8 +104,12 @@ export declare class AvatarView {
62
104
  * to work correctly with WebGL preserveDrawingBuffer:false.
63
105
  */
64
106
  exportBitmap(): Promise<Blob | null>;
65
- /** @deprecated Use startRenderLoop() */
66
- private startIdleAnimationLoop;
107
+ /**
108
+ * 当前初始化走到哪一步。供构造函数里的最终 catch 上报 `render_init_failed.stage`:
109
+ * 各步骤抛的错误类型五花八门(Error / AvatarError / WASM 抛的字符串),只靠
110
+ * message 没法聚合,stage 才是能按环节切分的低基数字段。
111
+ */
112
+ private _initStage;
67
113
  /**
68
114
  * Render a specific idle frame by index for benchmark capture.
69
115
  * Bypasses animation loop and _renderingEnabled check.
@@ -80,11 +126,17 @@ export declare class AvatarView {
80
126
  */
81
127
  dispose(): void;
82
128
  /**
83
- * 获取相机配置
129
+ * 获取相机配置。
130
+ *
131
+ * @deprecated 1.3.9 起弃用,下个版本移除。相机由角色自身的设置决定,渲染基线也按它校准;
132
+ * 宿主侧改 fov / 位置会让画面偏离基线。没有替代接口——请删除宿主侧的相机覆盖逻辑。
133
+ * SDK 自己的评测链路(web-iframe)仍可调用。
84
134
  */
85
135
  getCameraConfig(): CameraConfig | null;
86
136
  /**
87
- * 更新相机配置
137
+ * 更新相机配置。
138
+ *
139
+ * @deprecated 1.3.9 起弃用,下个版本移除,理由见 {@link getCameraConfig}。
88
140
  */
89
141
  updateCameraConfig(cameraConfig: CameraConfig): void;
90
142
  /**
@@ -96,30 +148,6 @@ export declare class AvatarView {
96
148
  * @param data - Raw protobuf bytes (a single Message containing ServerResponseAnimation)
97
149
  */
98
150
  renderFromProtobuf(data: ArrayBuffer | Uint8Array): Promise<void>;
99
- /**
100
- * Play a transition from idle to the target frame in the protobuf data,
101
- * then resolve when the transition is complete.
102
- *
103
- * The transition frames are generated and played internally at 25fps.
104
- * The caller should wait for the returned Promise before pushing streaming frames.
105
- *
106
- * @param data - Raw protobuf bytes containing the target frame
107
- * @param frameCount - Number of transition frames to generate
108
- * @returns Promise that resolves when the transition playback finishes
109
- */
110
- playTransitionFromProtobuf(data: ArrayBuffer | Uint8Array, frameCount: number): Promise<void>;
111
- /**
112
- * Play a transition from current animation back to idle,
113
- * then start the idle animation loop.
114
- *
115
- * Generates reverse transition frames from idle→lastFrame, reverses them,
116
- * plays at 25fps, then starts idle.
117
- *
118
- * @param data - Raw protobuf bytes containing the last animation frame
119
- * @param frameCount - Number of transition frames to generate
120
- * @returns Promise that resolves when idle animation starts
121
- */
122
- playTransitionToIdleFromProtobuf(data: ArrayBuffer | Uint8Array, frameCount: number): Promise<void>;
123
151
  /**
124
152
  * Start idle animation (stop pure rendering mode, resume idle loop).
125
153
  */
@@ -151,10 +179,19 @@ export declare class AvatarView {
151
179
  useLinear?: boolean;
152
180
  }): Promise<unknown[]>;
153
181
  /**
154
- * Cancel any in-progress frame sequence playback.
155
- * Called by renderFromProtobuf when streaming frames arrive during transition.
182
+ * canvas 是否还挂在 DOM 上。宿主卸载父组件而没调 dispose() 时,canvas 会跟着容器
183
+ * 一起被移除,SDK 却还在跑:没有任何现有埋点会响。渲染循环每 tick 查一次
184
+ * `isConnected`(不触发 layout),脱离时记一条 error,重新挂回记一条 info。
185
+ * 只在 canvas 曾经在 DOM 里之后才判,避免离屏构造的误报。
186
+ */
187
+ private probeCanvasAttachment;
188
+ /**
189
+ * 容器被缩成 0×0(display:none 或父级折叠)时 ResizeObserver 会回调一次 0 尺寸。
190
+ * 画面还在 DOM 里但用户看不见,与 detach 是不同的消失方式,分开记。
191
+ * 只在容器曾有过非零尺寸之后才判。
156
192
  */
157
- cancelFrameSequence(): void;
193
+ private probeContainerVisibility;
194
+ private syncCanvasSize;
158
195
  /** 计算 canvas backing-store 像素尺寸. 默认 css × dpr;
159
196
  * AvatarSDK.setRenderResolutionCap 启用且高度超过阈值时,
160
197
  * 按比例缩到阈值, css 尺寸不变 (浏览器自动拉伸). */
@@ -3,6 +3,8 @@ declare class AvatarViewRegistry {
3
3
  private entries;
4
4
  register(view: AvatarView): void;
5
5
  unregister(view: AvatarView): void;
6
+ /** 遍历仍存活的 view(顺手清掉已被 GC 的弱引用)。 */
7
+ forEachLive(fn: (view: AvatarView) => void): void;
6
8
  applyResolutionCapToAll(): void;
7
9
  }
8
10
  export declare const avatarViewRegistry: AvatarViewRegistry;