@spatius/avatarkit 1.3.6 → 1.3.8
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 +24 -0
- package/dist/assets/{OpusDecoderWorker.worker-DbdfH4qX.js → OpusDecoderWorker.worker-DVOREuTG.js} +8551 -4624
- package/dist/assets/{OpusEncoderWorker.worker-DVnsr0L5.js → OpusEncoderWorker.worker-fkLsXEK1.js} +8550 -4623
- package/dist/core/AvatarController.d.ts +0 -4
- package/dist/core/AvatarView.d.ts +38 -0
- package/dist/index.js +114 -116
- package/dist/internal-telemetry.d.ts +35 -0
- package/dist/internal-telemetry.js +41 -2
- package/dist/{otel-trace-B4E2lODB.js → otel-trace-CZJAGjGg.js} +8510 -4598
- package/package.json +2 -2
|
@@ -82,10 +82,6 @@ export declare class AvatarController {
|
|
|
82
82
|
* Orthogonal to user pause — user pause/resume does not change it; only frame arrival does.
|
|
83
83
|
*/
|
|
84
84
|
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
85
|
private latencyMarks;
|
|
90
86
|
/**
|
|
91
87
|
* W3C `traceparent` the driving service stamped on this round's animation
|
|
@@ -52,6 +52,44 @@ export declare class AvatarView {
|
|
|
52
52
|
width: number;
|
|
53
53
|
height: number;
|
|
54
54
|
};
|
|
55
|
+
/**
|
|
56
|
+
* Set once the first-frame callback has fired; the render loop samples the
|
|
57
|
+
* canvas on the first frame after this deadline and then clears it.
|
|
58
|
+
*
|
|
59
|
+
* The check exists because "the render loop is running" does not mean anything
|
|
60
|
+
* reached the screen. Failures along the render path mostly log and return, so
|
|
61
|
+
* a user seeing nothing produces no signal at all. This asks the only question
|
|
62
|
+
* that matters at the end: did this layer draw anything?
|
|
63
|
+
*
|
|
64
|
+
* Sampling has to happen inside the loop, right after renderFrame() — the
|
|
65
|
+
* context runs with preserveDrawingBuffer:false, so the buffer is only valid
|
|
66
|
+
* there. Reading from a timer would find it already cleared and report every
|
|
67
|
+
* healthy session as blank.
|
|
68
|
+
*/
|
|
69
|
+
private _blankCheckDueAt;
|
|
70
|
+
private _blankCheckDone;
|
|
71
|
+
/** How long after the first frame to sample. Long enough for the pipeline to settle. */
|
|
72
|
+
private static readonly BLANK_CHECK_DELAY_MS;
|
|
73
|
+
/**
|
|
74
|
+
* Sample size. The browser scales the full canvas into this, so it covers the
|
|
75
|
+
* whole frame rather than a crop — the avatar is found wherever it sits. Kept
|
|
76
|
+
* tiny because the question is only "any opaque pixel at all"; reading the
|
|
77
|
+
* full canvas would copy megabytes for a yes/no answer.
|
|
78
|
+
*/
|
|
79
|
+
private static readonly BLANK_CHECK_WIDTH;
|
|
80
|
+
private static readonly BLANK_CHECK_HEIGHT;
|
|
81
|
+
private scheduleBlankCanvasCheck;
|
|
82
|
+
/**
|
|
83
|
+
* Called from the render loop immediately after renderFrame(), while the
|
|
84
|
+
* drawing buffer is still valid.
|
|
85
|
+
*/
|
|
86
|
+
private runBlankCanvasCheckIfDue;
|
|
87
|
+
/**
|
|
88
|
+
* Count pixels that are not fully transparent. Null when the sample could not
|
|
89
|
+
* be taken at all, which is not the same as a blank frame and must not be
|
|
90
|
+
* reported as one.
|
|
91
|
+
*/
|
|
92
|
+
private countOpaquePixels;
|
|
55
93
|
private _exportBitmapResolve;
|
|
56
94
|
/**
|
|
57
95
|
* Exports the current rendering as a Blob (PNG).
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as __toESM, t as __commonJSMin } from "./rolldown-runtime-FDOR9p9I.js";
|
|
2
|
-
import { $ as DEFAULT_REGION, A as
|
|
2
|
+
import { $ as DEFAULT_REGION, A as monoTimestamp, B as resolveSdkVersion, C as logMetric, D as initializeOtel, E as cleanupOtel, F as cleanupOtelMetrics, G as idManager, H as OTEL_LOGS_ENDPOINT, I as initializeOtelMetrics, J as AvatarError, K as generateTraceId, L as recordHttpClientDuration, M as clockSync, N as fetchBootstrap, P as hostOf, Q as DEFAULT_OPUS_BITRATE, R as recordMetric, S as logEvent, T as updatePostHogPersonPropertiesForFlags, U as OTEL_STREAM_NAME, W as isDebugMode, X as ConnectionState, Y as AvatarState, Z as ConversationState, _ as logSink, a as startLoadTrace, at as LogLevel, b as getLogsFeatureFlag, c as AudioFormat, ct as TransitionType, d as MessageType, et as DEFAULT_REGION_REQUEST, f as TransportCompression, g as setLogLevel, h as logger, i as startInitTrace, it as LoadProgress, j as resolveMarks, k as monoMarkFrom, l as EgressType, lt as requireSDK, m as BinaryWriter, n as initializeOtelTrace, nt as ErrorCode, o as startPlaybackTrace, ot as RENDER_QUALITY_PARAMS, p as BinaryReader, q as AnimationType, r as startConnectTrace, rt as FrameStarvationMode, s as buildTraceparent, st as RenderQuality, t as cleanupOtelTrace, tt as DrivingServiceMode, u as Message, ut as setSDKRef, v as cleanupPostHog, w as trackEvent, x as initializePostHog, y as clientContextFields, z as getSdkPackage } from "./otel-trace-CZJAGjGg.js";
|
|
3
3
|
//#region core/Avatar.ts
|
|
4
4
|
var Avatar = class {
|
|
5
5
|
id;
|
|
@@ -41912,7 +41912,7 @@ var OpusDecoderProxy = class OpusDecoderProxy {
|
|
|
41912
41912
|
try {
|
|
41913
41913
|
worker = new Worker(new URL(
|
|
41914
41914
|
/* @vite-ignore */
|
|
41915
|
-
"" + new URL("assets/OpusDecoderWorker.worker-
|
|
41915
|
+
"" + new URL("assets/OpusDecoderWorker.worker-DVOREuTG.js", import.meta.url).href,
|
|
41916
41916
|
"" + import.meta.url
|
|
41917
41917
|
), { type: "module" });
|
|
41918
41918
|
} catch (err) {
|
|
@@ -44372,29 +44372,26 @@ function emitInitErrorLog(params) {
|
|
|
44372
44372
|
];
|
|
44373
44373
|
const client = clientContextFields();
|
|
44374
44374
|
for (const [k, v] of Object.entries(client)) logAttrs.push(kv(k, v));
|
|
44375
|
-
const payload = { resourceLogs: [{
|
|
44376
|
-
resource: { attributes: resourceAttrs },
|
|
44377
|
-
scopeLogs: [{
|
|
44378
|
-
scope: { name: OTEL_LOGGER_NAME },
|
|
44379
|
-
logRecords: [{
|
|
44380
|
-
timeUnixNano: nowNanos,
|
|
44381
|
-
severityNumber: SEVERITY_ERROR,
|
|
44382
|
-
severityText: "ERROR",
|
|
44383
|
-
body: { stringValue: event },
|
|
44384
|
-
attributes: logAttrs
|
|
44385
|
-
}]
|
|
44386
|
-
}]
|
|
44387
|
-
}] };
|
|
44388
|
-
const authToken = btoa(`${OTEL_USERNAME}:${OTEL_PASSWORD}`);
|
|
44389
44375
|
fetch(OTEL_LOGS_ENDPOINT, {
|
|
44390
44376
|
method: "POST",
|
|
44391
44377
|
keepalive: true,
|
|
44392
44378
|
headers: {
|
|
44393
44379
|
"Content-Type": "application/json",
|
|
44394
|
-
Authorization: `Basic ${authToken}`,
|
|
44395
44380
|
"stream-name": OTEL_STREAM_NAME
|
|
44396
44381
|
},
|
|
44397
|
-
body: JSON.stringify(
|
|
44382
|
+
body: JSON.stringify({ resourceLogs: [{
|
|
44383
|
+
resource: { attributes: resourceAttrs },
|
|
44384
|
+
scopeLogs: [{
|
|
44385
|
+
scope: { name: OTEL_LOGGER_NAME },
|
|
44386
|
+
logRecords: [{
|
|
44387
|
+
timeUnixNano: nowNanos,
|
|
44388
|
+
severityNumber: SEVERITY_ERROR,
|
|
44389
|
+
severityText: "ERROR",
|
|
44390
|
+
body: { stringValue: event },
|
|
44391
|
+
attributes: logAttrs
|
|
44392
|
+
}]
|
|
44393
|
+
}]
|
|
44394
|
+
}] })
|
|
44398
44395
|
}).catch(() => {});
|
|
44399
44396
|
} catch {}
|
|
44400
44397
|
}
|
|
@@ -45730,7 +45727,7 @@ var AvatarSDK = class {
|
|
|
45730
45727
|
static _initializationState = "uninitialized";
|
|
45731
45728
|
static _initializingPromise = null;
|
|
45732
45729
|
static _configuration = null;
|
|
45733
|
-
static _version = "1.3.
|
|
45730
|
+
static _version = "1.3.8";
|
|
45734
45731
|
static _avatarCore = null;
|
|
45735
45732
|
static _cachedDeviceScore = null;
|
|
45736
45733
|
static _rendererBackend = null;
|
|
@@ -46295,7 +46292,7 @@ var OpusEncoderProxy = class OpusEncoderProxy {
|
|
|
46295
46292
|
try {
|
|
46296
46293
|
worker = new Worker(new URL(
|
|
46297
46294
|
/* @vite-ignore */
|
|
46298
|
-
"" + new URL("assets/OpusEncoderWorker.worker-
|
|
46295
|
+
"" + new URL("assets/OpusEncoderWorker.worker-fkLsXEK1.js", import.meta.url).href,
|
|
46299
46296
|
"" + import.meta.url
|
|
46300
46297
|
), { type: "module" });
|
|
46301
46298
|
} catch (err) {
|
|
@@ -47830,15 +47827,6 @@ var AvatarController = class {
|
|
|
47830
47827
|
* Orthogonal to user pause — user pause/resume does not change it; only frame arrival does.
|
|
47831
47828
|
*/
|
|
47832
47829
|
isAudioStalledForStarvation = false;
|
|
47833
|
-
playbackStuckCheckState = {
|
|
47834
|
-
audioTimeZeroCount: 0,
|
|
47835
|
-
lastAudioTime: 0,
|
|
47836
|
-
audioTimeStuckCount: 0,
|
|
47837
|
-
reported: false
|
|
47838
|
-
};
|
|
47839
|
-
MAX_AUDIO_TIME_ZERO_COUNT = 25;
|
|
47840
|
-
MAX_AUDIO_TIME_STUCK_COUNT = 25;
|
|
47841
|
-
AUDIO_TIME_STUCK_THRESHOLD = .001;
|
|
47842
47830
|
latencyMarks = {
|
|
47843
47831
|
accumulatedBytes: 0,
|
|
47844
47832
|
taps: /* @__PURE__ */ new Map(),
|
|
@@ -49141,79 +49129,6 @@ var AvatarController = class {
|
|
|
49141
49129
|
* Playback loop: Calculate animation frame based on audio time, notify render layer to render
|
|
49142
49130
|
*/
|
|
49143
49131
|
/**
|
|
49144
|
-
* 检测播放是否卡住(在过渡动画完成后)
|
|
49145
|
-
* 注意:AudioContext suspended 的情况现在由 StreamingAudioPlayer 自动处理,不再作为触发条件
|
|
49146
|
-
* 此函数主要用于检测其他原因导致的播放卡住(如网络问题、音频数据问题等)
|
|
49147
|
-
*
|
|
49148
|
-
* @param audioTime 当前音频时间
|
|
49149
|
-
* @returns true 如果检测到卡住并已上报,false 否则
|
|
49150
|
-
* @internal
|
|
49151
|
-
*/
|
|
49152
|
-
checkPlaybackStuck(audioTime) {
|
|
49153
|
-
const streamingPlayer = this.animationPlayer?.getStreamingPlayer();
|
|
49154
|
-
if (!streamingPlayer) return false;
|
|
49155
|
-
const state = this.playbackStuckCheckState;
|
|
49156
|
-
const hasAnimationData = this.currentKeyframes.length > 0;
|
|
49157
|
-
const hasAudioData = streamingPlayer.audioChunks?.length > 0;
|
|
49158
|
-
const isNotPaused = this.currentState !== AvatarState.paused;
|
|
49159
|
-
if (this.isAudioStalledForStarvation) {
|
|
49160
|
-
state.audioTimeZeroCount = 0;
|
|
49161
|
-
state.audioTimeStuckCount = 0;
|
|
49162
|
-
state.lastAudioTime = 0;
|
|
49163
|
-
state.reported = false;
|
|
49164
|
-
return false;
|
|
49165
|
-
}
|
|
49166
|
-
if (!hasAnimationData || !hasAudioData || this.currentState === AvatarState.paused) {
|
|
49167
|
-
state.audioTimeZeroCount = 0;
|
|
49168
|
-
state.audioTimeStuckCount = 0;
|
|
49169
|
-
state.lastAudioTime = 0;
|
|
49170
|
-
state.reported = false;
|
|
49171
|
-
return false;
|
|
49172
|
-
}
|
|
49173
|
-
if (state.reported) return true;
|
|
49174
|
-
const audioContextState = streamingPlayer.audioContext?.state || "unknown";
|
|
49175
|
-
const activeSourcesCount = streamingPlayer.activeSources?.size || 0;
|
|
49176
|
-
const scheduledChunks = streamingPlayer.scheduledChunks || 0;
|
|
49177
|
-
const hasScheduledButNoActive = scheduledChunks > 0 && activeSourcesCount === 0;
|
|
49178
|
-
if (audioTime === 0) state.audioTimeZeroCount++;
|
|
49179
|
-
else state.audioTimeZeroCount = 0;
|
|
49180
|
-
if (Math.abs(audioTime - state.lastAudioTime) < this.AUDIO_TIME_STUCK_THRESHOLD) state.audioTimeStuckCount++;
|
|
49181
|
-
else state.audioTimeStuckCount = 0;
|
|
49182
|
-
state.lastAudioTime = audioTime;
|
|
49183
|
-
if ((state.audioTimeZeroCount > this.MAX_AUDIO_TIME_ZERO_COUNT || state.audioTimeStuckCount > this.MAX_AUDIO_TIME_STUCK_COUNT || hasScheduledButNoActive && this.isPlaying) && isNotPaused) {
|
|
49184
|
-
state.reported = true;
|
|
49185
|
-
logEvent("playback_stuck_after_transition", "error", {
|
|
49186
|
-
avatar_id: this.avatar.id,
|
|
49187
|
-
conversation_id: this.currentConversationId || void 0,
|
|
49188
|
-
audio_context_state: audioContextState,
|
|
49189
|
-
audio_time: audioTime,
|
|
49190
|
-
audio_time_zero: audioTime === 0,
|
|
49191
|
-
audio_time_zero_count: state.audioTimeZeroCount,
|
|
49192
|
-
audio_time_stuck: state.audioTimeStuckCount > this.MAX_AUDIO_TIME_STUCK_COUNT,
|
|
49193
|
-
audio_time_stuck_count: state.audioTimeStuckCount,
|
|
49194
|
-
has_scheduled_but_no_active: hasScheduledButNoActive,
|
|
49195
|
-
is_playing: this.isPlaying,
|
|
49196
|
-
current_state: this.currentState,
|
|
49197
|
-
animation_frames_count: this.currentKeyframes.length,
|
|
49198
|
-
audio_chunks_count: streamingPlayer.audioChunks?.length || 0,
|
|
49199
|
-
scheduled_chunks: scheduledChunks,
|
|
49200
|
-
active_sources_count: activeSourcesCount
|
|
49201
|
-
});
|
|
49202
|
-
return true;
|
|
49203
|
-
}
|
|
49204
|
-
return false;
|
|
49205
|
-
}
|
|
49206
|
-
/**
|
|
49207
|
-
* Reset playback stuck detection counters
|
|
49208
|
-
* @internal
|
|
49209
|
-
*/
|
|
49210
|
-
resetPlaybackStuckCheckState() {
|
|
49211
|
-
this.playbackStuckCheckState.audioTimeZeroCount = 0;
|
|
49212
|
-
this.playbackStuckCheckState.audioTimeStuckCount = 0;
|
|
49213
|
-
this.playbackStuckCheckState.lastAudioTime = 0;
|
|
49214
|
-
this.playbackStuckCheckState.reported = false;
|
|
49215
|
-
}
|
|
49216
|
-
/**
|
|
49217
49132
|
* Memory optimization: clean old keyframes if array is too long.
|
|
49218
49133
|
* Extracted from the former computeAndRenderFrame so that the monitoring-only
|
|
49219
49134
|
* playback loop can still manage memory without doing WASM computation.
|
|
@@ -49342,11 +49257,6 @@ var AvatarController = class {
|
|
|
49342
49257
|
monitorPlaybackTick(audioTime, frameIndex) {
|
|
49343
49258
|
if (!this.isPlaying || this.currentState === AvatarState.paused) return;
|
|
49344
49259
|
try {
|
|
49345
|
-
this.checkPlaybackStuck(audioTime);
|
|
49346
|
-
if (audioTime > 0 && Math.abs(audioTime - this.playbackStuckCheckState.lastAudioTime) >= this.AUDIO_TIME_STUCK_THRESHOLD) {
|
|
49347
|
-
this.playbackStuckCheckState.audioTimeZeroCount = 0;
|
|
49348
|
-
this.playbackStuckCheckState.audioTimeStuckCount = 0;
|
|
49349
|
-
}
|
|
49350
49260
|
if (audioTime > 0) {
|
|
49351
49261
|
if (frameIndex > this.lastRenderedFrameIndex) this.lastRenderedFrameIndex = frameIndex;
|
|
49352
49262
|
this.cleanupKeyframesIfNeeded(frameIndex);
|
|
@@ -49416,7 +49326,6 @@ var AvatarController = class {
|
|
|
49416
49326
|
this.resetConversationIdState();
|
|
49417
49327
|
}
|
|
49418
49328
|
stopPlayback() {
|
|
49419
|
-
this.resetPlaybackStuckCheckState();
|
|
49420
49329
|
this.isStartingPlayback = false;
|
|
49421
49330
|
if (this.animationPlayer) {
|
|
49422
49331
|
this.animationPlayer.stop();
|
|
@@ -51794,7 +51703,7 @@ function generateTransitionFrames(from, to, durationMs, fps = 25) {
|
|
|
51794
51703
|
}
|
|
51795
51704
|
//#endregion
|
|
51796
51705
|
//#region core/AvatarView.ts
|
|
51797
|
-
var AvatarView = class {
|
|
51706
|
+
var AvatarView = class AvatarView {
|
|
51798
51707
|
avatarController;
|
|
51799
51708
|
avatar;
|
|
51800
51709
|
onFirstRendering;
|
|
@@ -51982,6 +51891,83 @@ var AvatarView = class {
|
|
|
51982
51891
|
height: this.canvas.height
|
|
51983
51892
|
};
|
|
51984
51893
|
}
|
|
51894
|
+
/**
|
|
51895
|
+
* Set once the first-frame callback has fired; the render loop samples the
|
|
51896
|
+
* canvas on the first frame after this deadline and then clears it.
|
|
51897
|
+
*
|
|
51898
|
+
* The check exists because "the render loop is running" does not mean anything
|
|
51899
|
+
* reached the screen. Failures along the render path mostly log and return, so
|
|
51900
|
+
* a user seeing nothing produces no signal at all. This asks the only question
|
|
51901
|
+
* that matters at the end: did this layer draw anything?
|
|
51902
|
+
*
|
|
51903
|
+
* Sampling has to happen inside the loop, right after renderFrame() — the
|
|
51904
|
+
* context runs with preserveDrawingBuffer:false, so the buffer is only valid
|
|
51905
|
+
* there. Reading from a timer would find it already cleared and report every
|
|
51906
|
+
* healthy session as blank.
|
|
51907
|
+
*/
|
|
51908
|
+
_blankCheckDueAt = null;
|
|
51909
|
+
_blankCheckDone = false;
|
|
51910
|
+
/** How long after the first frame to sample. Long enough for the pipeline to settle. */
|
|
51911
|
+
static BLANK_CHECK_DELAY_MS = 1e3;
|
|
51912
|
+
/**
|
|
51913
|
+
* Sample size. The browser scales the full canvas into this, so it covers the
|
|
51914
|
+
* whole frame rather than a crop — the avatar is found wherever it sits. Kept
|
|
51915
|
+
* tiny because the question is only "any opaque pixel at all"; reading the
|
|
51916
|
+
* full canvas would copy megabytes for a yes/no answer.
|
|
51917
|
+
*/
|
|
51918
|
+
static BLANK_CHECK_WIDTH = 64;
|
|
51919
|
+
static BLANK_CHECK_HEIGHT = 128;
|
|
51920
|
+
scheduleBlankCanvasCheck() {
|
|
51921
|
+
if (this._blankCheckDone || this._blankCheckDueAt !== null) return;
|
|
51922
|
+
this._blankCheckDueAt = performance.now() + AvatarView.BLANK_CHECK_DELAY_MS;
|
|
51923
|
+
}
|
|
51924
|
+
/**
|
|
51925
|
+
* Called from the render loop immediately after renderFrame(), while the
|
|
51926
|
+
* drawing buffer is still valid.
|
|
51927
|
+
*/
|
|
51928
|
+
runBlankCanvasCheckIfDue() {
|
|
51929
|
+
const dueAt = this._blankCheckDueAt;
|
|
51930
|
+
if (dueAt === null || performance.now() < dueAt) return;
|
|
51931
|
+
this._blankCheckDueAt = null;
|
|
51932
|
+
this._blankCheckDone = true;
|
|
51933
|
+
const sampleStart = performance.now();
|
|
51934
|
+
const opaquePixels = this.countOpaquePixels();
|
|
51935
|
+
const sampleMs = performance.now() - sampleStart;
|
|
51936
|
+
if (opaquePixels === null || opaquePixels > 0) return;
|
|
51937
|
+
logger.error(`[AvatarView] Blank canvas: nothing was drawn ${AvatarView.BLANK_CHECK_DELAY_MS}ms after the first frame`);
|
|
51938
|
+
trackEvent("render_blank_canvas", "error", {
|
|
51939
|
+
canvas_width: this.canvas?.width ?? 0,
|
|
51940
|
+
canvas_height: this.canvas?.height ?? 0,
|
|
51941
|
+
sampled_pixels: AvatarView.BLANK_CHECK_WIDTH * AvatarView.BLANK_CHECK_HEIGHT,
|
|
51942
|
+
delay_ms: AvatarView.BLANK_CHECK_DELAY_MS,
|
|
51943
|
+
sample_ms: Number(sampleMs.toFixed(1))
|
|
51944
|
+
});
|
|
51945
|
+
}
|
|
51946
|
+
/**
|
|
51947
|
+
* Count pixels that are not fully transparent. Null when the sample could not
|
|
51948
|
+
* be taken at all, which is not the same as a blank frame and must not be
|
|
51949
|
+
* reported as one.
|
|
51950
|
+
*/
|
|
51951
|
+
countOpaquePixels() {
|
|
51952
|
+
const canvas = this.canvas;
|
|
51953
|
+
if (!canvas || canvas.width === 0 || canvas.height === 0) return null;
|
|
51954
|
+
const w = AvatarView.BLANK_CHECK_WIDTH;
|
|
51955
|
+
const h = AvatarView.BLANK_CHECK_HEIGHT;
|
|
51956
|
+
try {
|
|
51957
|
+
const scratch = document.createElement("canvas");
|
|
51958
|
+
scratch.width = w;
|
|
51959
|
+
scratch.height = h;
|
|
51960
|
+
const ctx = scratch.getContext("2d", { willReadFrequently: true });
|
|
51961
|
+
if (!ctx) return null;
|
|
51962
|
+
ctx.drawImage(canvas, 0, 0, w, h);
|
|
51963
|
+
const data = ctx.getImageData(0, 0, w, h).data;
|
|
51964
|
+
let count = 0;
|
|
51965
|
+
for (let i = 3; i < data.length; i += 4) if (data[i] !== 0) count++;
|
|
51966
|
+
return count;
|
|
51967
|
+
} catch {
|
|
51968
|
+
return null;
|
|
51969
|
+
}
|
|
51970
|
+
}
|
|
51985
51971
|
_exportBitmapResolve = null;
|
|
51986
51972
|
/**
|
|
51987
51973
|
* Exports the current rendering as a Blob (PNG).
|
|
@@ -52210,6 +52196,7 @@ var AvatarView = class {
|
|
|
52210
52196
|
this.renderSystem.renderFrame();
|
|
52211
52197
|
if (APP_CONFIG.debug) logger.log("[AvatarView] First frame rendered successfully");
|
|
52212
52198
|
this.onFirstRendering?.();
|
|
52199
|
+
this.scheduleBlankCanvasCheck();
|
|
52213
52200
|
} else throw new Error("Failed to compute first frame splat data");
|
|
52214
52201
|
}
|
|
52215
52202
|
/**
|
|
@@ -52289,8 +52276,10 @@ var AvatarView = class {
|
|
|
52289
52276
|
this.currentFrame = frame;
|
|
52290
52277
|
const wasmParams = convertProtoFlameToWasmParams(frame);
|
|
52291
52278
|
computeInFlight = true;
|
|
52279
|
+
const computeStart = performance.now();
|
|
52292
52280
|
avatarCore.computeFrameFlatFromParams(wasmParams, this.characterHandle ?? void 0).then((splatData) => {
|
|
52293
|
-
|
|
52281
|
+
const computeMs = performance.now() - computeStart;
|
|
52282
|
+
if (splatData && !this.isPureRenderingMode) this.doRender(splatData, computeMs);
|
|
52294
52283
|
}).catch((e) => logger.warn("[AvatarView] Transition frame compute failed:", e instanceof Error ? e.message : String(e))).finally(() => {
|
|
52295
52284
|
computeInFlight = false;
|
|
52296
52285
|
});
|
|
@@ -52312,8 +52301,10 @@ var AvatarView = class {
|
|
|
52312
52301
|
this.currentFrame = currentFrame;
|
|
52313
52302
|
const wasmParams = convertProtoFlameToWasmParams(currentFrame);
|
|
52314
52303
|
computeInFlight = true;
|
|
52304
|
+
const computeStart = performance.now();
|
|
52315
52305
|
avatarCore.computeFrameFlatFromParams(wasmParams, this.characterHandle ?? void 0).then((splatData) => {
|
|
52316
|
-
|
|
52306
|
+
const computeMs = performance.now() - computeStart;
|
|
52307
|
+
if (splatData && !this.isPureRenderingMode) this.doRender(splatData, computeMs);
|
|
52317
52308
|
}).catch((e) => logger.warn("[AvatarView] Conversation frame compute failed:", e instanceof Error ? e.message : String(e))).finally(() => {
|
|
52318
52309
|
computeInFlight = false;
|
|
52319
52310
|
});
|
|
@@ -52345,13 +52336,15 @@ var AvatarView = class {
|
|
|
52345
52336
|
computeInFlight = true;
|
|
52346
52337
|
(async () => {
|
|
52347
52338
|
try {
|
|
52339
|
+
const computeStart = performance.now();
|
|
52348
52340
|
const splatData = await avatarCore.computeCompleteFrameFlat({
|
|
52349
52341
|
frameIndex: fi,
|
|
52350
52342
|
animationInfo: animInfo
|
|
52351
52343
|
}, this.characterHandle ?? void 0);
|
|
52344
|
+
const computeMs = performance.now() - computeStart;
|
|
52352
52345
|
const currentParams = await avatarCore.getCurrentFrameParams(fi, this.characterId, animInfo);
|
|
52353
52346
|
this.currentFrame = convertWasmParamsToProtoFlame(currentParams);
|
|
52354
|
-
if (splatData && !this.isPureRenderingMode) this.doRender(splatData);
|
|
52347
|
+
if (splatData && !this.isPureRenderingMode) this.doRender(splatData, computeMs);
|
|
52355
52348
|
} catch (e) {
|
|
52356
52349
|
logger.warn("[AvatarView] Idle frame compute failed:", e instanceof Error ? e.message : String(e));
|
|
52357
52350
|
} finally {
|
|
@@ -52481,12 +52474,13 @@ var AvatarView = class {
|
|
|
52481
52474
|
* This is the single point of control for renderingEnabled flag
|
|
52482
52475
|
* @internal
|
|
52483
52476
|
*/
|
|
52484
|
-
doRender(splatData) {
|
|
52477
|
+
doRender(splatData, computeMs = 0) {
|
|
52485
52478
|
if (!this.renderSystem || !this._renderingEnabled) return;
|
|
52486
|
-
const startTime = performance.now();
|
|
52479
|
+
const startTime = performance.now() - computeMs;
|
|
52487
52480
|
this.updateFPS();
|
|
52488
52481
|
this.renderSystem.loadSplatsFromPackedData(splatData);
|
|
52489
52482
|
this.renderSystem.renderFrame();
|
|
52483
|
+
this.runBlankCanvasCheckIfDue();
|
|
52490
52484
|
if (this._exportBitmapResolve) {
|
|
52491
52485
|
const resolve = this._exportBitmapResolve;
|
|
52492
52486
|
this._exportBitmapResolve = null;
|
|
@@ -52676,8 +52670,10 @@ var AvatarView = class {
|
|
|
52676
52670
|
const wasmParams = convertProtoFlameToWasmParams(flame);
|
|
52677
52671
|
const avatarCore = AvatarSDK.getAvatarCore();
|
|
52678
52672
|
if (!avatarCore) throw new Error("AvatarCore not available");
|
|
52673
|
+
const computeStart = performance.now();
|
|
52679
52674
|
const splatData = await avatarCore.computeFrameFlatFromParams(wasmParams, this.characterHandle ?? void 0);
|
|
52680
|
-
|
|
52675
|
+
const computeMs = performance.now() - computeStart;
|
|
52676
|
+
if (splatData) this.doRender(splatData, computeMs);
|
|
52681
52677
|
} catch (error) {
|
|
52682
52678
|
logger.error("[AvatarView] Failed to render flame:", error instanceof Error ? error.message : String(error));
|
|
52683
52679
|
throw error;
|
|
@@ -52983,8 +52979,10 @@ var AvatarView = class {
|
|
|
52983
52979
|
const avatarCore = AvatarSDK.getAvatarCore();
|
|
52984
52980
|
if (!avatarCore || !this.renderSystem) return;
|
|
52985
52981
|
try {
|
|
52982
|
+
const computeStart = performance.now();
|
|
52986
52983
|
const splatData = await avatarCore.computeCompleteFrameFlat({ frameIndex: this.activeAnimationState.frameIndex }, this.characterHandle ?? void 0);
|
|
52987
|
-
|
|
52984
|
+
const computeMs = performance.now() - computeStart;
|
|
52985
|
+
if (splatData) this.doRender(splatData, computeMs);
|
|
52988
52986
|
} catch (error) {
|
|
52989
52987
|
logger.warn("[AvatarView] Failed to render current idle frame:", error);
|
|
52990
52988
|
}
|
|
@@ -79,6 +79,41 @@ export declare function peekTraceparent(rawMessage: Uint8Array): string | undefi
|
|
|
79
79
|
* dropped before it is sent.
|
|
80
80
|
*/
|
|
81
81
|
export declare function recordMetric(name: string, value: number, attributes?: Attributes): void;
|
|
82
|
+
/**
|
|
83
|
+
* Emit one event onto avatarkit's OTel log channel.
|
|
84
|
+
*
|
|
85
|
+
* Must go through here rather than the RTC SDK calling `logs.getLogger().emit()`
|
|
86
|
+
* itself. Sharing the global LoggerProvider only carries Resource-level
|
|
87
|
+
* attributes (app_id / region / sdk.version); the record-level identity fields
|
|
88
|
+
* — `user_id`, `client_id` — are stamped inside this SDK's emit path, so a
|
|
89
|
+
* record emitted directly through the provider carries none of them. Routing
|
|
90
|
+
* through here is what the Android / iOS RTC SDKs already do via their
|
|
91
|
+
* `RTCTelemetry.track` entry, and it additionally picks up crash-replay
|
|
92
|
+
* persistence and the pre-calibration queue.
|
|
93
|
+
*
|
|
94
|
+
* The timestamp is taken here rather than accepted from the caller: this path
|
|
95
|
+
* resolves it against the calibrated clock and stamps `server_timestamp`
|
|
96
|
+
* alongside, which a caller-supplied `Date.now()` cannot do.
|
|
97
|
+
*/
|
|
98
|
+
export declare function trackEvent(event: string, level?: 'debug' | 'info' | 'warning' | 'error', contents?: Record<string, unknown>): void;
|
|
99
|
+
/**
|
|
100
|
+
* The client environment fields this SDK stamps on every OTel log record —
|
|
101
|
+
* `host`, `domain`, `url`, `pathname`, `referrer`, `user_agent`, `locale`,
|
|
102
|
+
* screen/viewport size and `timezone`.
|
|
103
|
+
*
|
|
104
|
+
* Shared with the RTC SDK rather than re-derived there. These are record-level
|
|
105
|
+
* fields, not Resource attributes: they are read from `window.location` at emit
|
|
106
|
+
* time because `url`/`pathname` change as a single-page app routes. Sharing the
|
|
107
|
+
* OTel providers therefore does not carry them across — the RTC SDK emits
|
|
108
|
+
* through its own logger and would report no `host` at all, which is what
|
|
109
|
+
* prompted this export. Re-implementing the collection on that side would let
|
|
110
|
+
* the two drift apart in field names and in how each value is read, and the
|
|
111
|
+
* backend aggregates on exactly these names.
|
|
112
|
+
*
|
|
113
|
+
* Call at emit time, once per record; spread the result before the caller's own
|
|
114
|
+
* properties so an explicit value at the call site still wins.
|
|
115
|
+
*/
|
|
116
|
+
export declare function clientContextFields(): Record<string, string | number>;
|
|
82
117
|
/**
|
|
83
118
|
* Declare which SDK these records come from, as the `sdk.package` and
|
|
84
119
|
* `sdk.version` resource attributes.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { M as clockSync, O as trackEventOtel, R as recordMetric$1, V as setSdkIdentity$1, o as startPlaybackTrace$1, u as Message, y as clientContextFields$1 } from "./otel-trace-CZJAGjGg.js";
|
|
2
2
|
//#region internal-telemetry.ts
|
|
3
3
|
/**
|
|
4
4
|
* Internal telemetry surface for the companion RTC SDK.
|
|
@@ -91,6 +91,45 @@ function recordMetric(name, value, attributes = {}) {
|
|
|
91
91
|
recordMetric$1(name, value, attributes);
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
|
+
* Emit one event onto avatarkit's OTel log channel.
|
|
95
|
+
*
|
|
96
|
+
* Must go through here rather than the RTC SDK calling `logs.getLogger().emit()`
|
|
97
|
+
* itself. Sharing the global LoggerProvider only carries Resource-level
|
|
98
|
+
* attributes (app_id / region / sdk.version); the record-level identity fields
|
|
99
|
+
* — `user_id`, `client_id` — are stamped inside this SDK's emit path, so a
|
|
100
|
+
* record emitted directly through the provider carries none of them. Routing
|
|
101
|
+
* through here is what the Android / iOS RTC SDKs already do via their
|
|
102
|
+
* `RTCTelemetry.track` entry, and it additionally picks up crash-replay
|
|
103
|
+
* persistence and the pre-calibration queue.
|
|
104
|
+
*
|
|
105
|
+
* The timestamp is taken here rather than accepted from the caller: this path
|
|
106
|
+
* resolves it against the calibrated clock and stamps `server_timestamp`
|
|
107
|
+
* alongside, which a caller-supplied `Date.now()` cannot do.
|
|
108
|
+
*/
|
|
109
|
+
function trackEvent(event, level = "info", contents = {}) {
|
|
110
|
+
trackEventOtel(event, level, contents);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The client environment fields this SDK stamps on every OTel log record —
|
|
114
|
+
* `host`, `domain`, `url`, `pathname`, `referrer`, `user_agent`, `locale`,
|
|
115
|
+
* screen/viewport size and `timezone`.
|
|
116
|
+
*
|
|
117
|
+
* Shared with the RTC SDK rather than re-derived there. These are record-level
|
|
118
|
+
* fields, not Resource attributes: they are read from `window.location` at emit
|
|
119
|
+
* time because `url`/`pathname` change as a single-page app routes. Sharing the
|
|
120
|
+
* OTel providers therefore does not carry them across — the RTC SDK emits
|
|
121
|
+
* through its own logger and would report no `host` at all, which is what
|
|
122
|
+
* prompted this export. Re-implementing the collection on that side would let
|
|
123
|
+
* the two drift apart in field names and in how each value is read, and the
|
|
124
|
+
* backend aggregates on exactly these names.
|
|
125
|
+
*
|
|
126
|
+
* Call at emit time, once per record; spread the result before the caller's own
|
|
127
|
+
* properties so an explicit value at the call site still wins.
|
|
128
|
+
*/
|
|
129
|
+
function clientContextFields() {
|
|
130
|
+
return clientContextFields$1();
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
94
133
|
* Declare which SDK these records come from, as the `sdk.package` and
|
|
95
134
|
* `sdk.version` resource attributes.
|
|
96
135
|
*
|
|
@@ -117,4 +156,4 @@ function setSdkIdentity(identity) {
|
|
|
117
156
|
setSdkIdentity$1(identity);
|
|
118
157
|
}
|
|
119
158
|
//#endregion
|
|
120
|
-
export { isClockCalibrated, peekTraceparent, recordMetric, resolveMonoTimestamp, setSdkIdentity, startPlaybackTrace };
|
|
159
|
+
export { clientContextFields, isClockCalibrated, peekTraceparent, recordMetric, resolveMonoTimestamp, setSdkIdentity, startPlaybackTrace, trackEvent };
|