@xmov/avatar 2.4.0 → 2.5.0-alpha.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/README.md +34 -6
- package/package-dist/agent/avatar.cjs +1 -0
- package/package-dist/agent/avatar.modern.js +1 -0
- package/package-dist/agent/avatar.module.js +1 -0
- package/package-dist/agent/avatar.umd.js +1 -0
- package/package-dist/agent/index.d.ts +42 -0
- package/{dist/agent/types.d.ts → package-dist/agent/public-types.d.ts} +115 -116
- package/package-dist/index.cjs +3 -0
- package/package-dist/index.d.ts +34 -0
- package/package-dist/index.modern.js +1 -0
- package/package-dist/index.module.js +1 -0
- package/package-dist/index.umd.js +14 -0
- package/package-dist/public-types.d.ts +205 -0
- package/package.json +41 -33
- package/dist/agent/avatar.cjs +0 -1
- package/dist/agent/avatar.modern.js +0 -1
- package/dist/agent/avatar.module.js +0 -1
- package/dist/agent/avatar.umd.js +0 -1
- package/dist/agent/index.d.ts +0 -137
- package/dist/baseRender/AudioRenderer.d.ts +0 -46
- package/dist/baseRender/AudioWorklet.d.ts +0 -78
- package/dist/baseRender/AvatarRenderer.d.ts +0 -225
- package/dist/baseRender/MSEAudioPlayer.d.ts +0 -51
- package/dist/baseRender/UIRenderer.d.ts +0 -39
- package/dist/baseRender/pcm-audio-processor.d.ts +0 -63
- package/dist/control/APIForwarder.d.ts +0 -23
- package/dist/control/DataCacheQueue.d.ts +0 -69
- package/dist/control/EventDispatcher.d.ts +0 -35
- package/dist/control/RenderScheduler.d.ts +0 -171
- package/dist/control/SaveAndDownload.d.ts +0 -14
- package/dist/control/StreamingClient.d.ts +0 -33
- package/dist/control/VideoPlayer.d.ts +0 -47
- package/dist/control/ttsa.d.ts +0 -125
- package/dist/encoding/media-recorder-encoder.d.ts +0 -33
- package/dist/encoding/pcm-webm-encoder.d.ts +0 -13
- package/dist/encoding/webcodec-opus-encoder.d.ts +0 -41
- package/dist/encoding/webm-muxer.d.ts +0 -90
- package/dist/index.cjs +0 -2
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.ts +0 -251
- package/dist/index.modern.js +0 -2
- package/dist/index.modern.js.map +0 -1
- package/dist/index.module.js +0 -2
- package/dist/index.module.js.map +0 -1
- package/dist/index.umd.js +0 -2
- package/dist/index.umd.js.map +0 -1
- package/dist/modules/Composition.d.ts +0 -28
- package/dist/modules/ResourceManager.d.ts +0 -299
- package/dist/modules/TrackRenderer/base-track.d.ts +0 -4
- package/dist/modules/TrackRenderer/index.d.ts +0 -12
- package/dist/modules/TrackRenderer/render-implements.d.ts +0 -19
- package/dist/modules/TrackRenderer/track-pic.d.ts +0 -8
- package/dist/modules/TrackRenderer/track-subtitle.d.ts +0 -8
- package/dist/modules/cache-manager.d.ts +0 -5
- package/dist/modules/decoder.d.ts +0 -84
- package/dist/modules/error-handle.d.ts +0 -17
- package/dist/modules/network.d.ts +0 -21
- package/dist/modules/sentry-reporter.d.ts +0 -58
- package/dist/proto/face_data_pb.d.ts +0 -0
- package/dist/types/capability.d.ts +0 -15
- package/dist/types/error.d.ts +0 -70
- package/dist/types/event.d.ts +0 -25
- package/dist/types/frame-data.d.ts +0 -107
- package/dist/types/index.d.ts +0 -196
- package/dist/types/render.d.ts +0 -18
- package/dist/utils/DataInterface.d.ts +0 -113
- package/dist/utils/GLDevice.d.ts +0 -46
- package/dist/utils/GLPipeline.d.ts +0 -84
- package/dist/utils/GLPipelineDebugTools.d.ts +0 -5
- package/dist/utils/Math.d.ts +0 -21
- package/dist/utils/__tests__/capability-checker.test.d.ts +0 -1
- package/dist/utils/audio-session.d.ts +0 -11
- package/dist/utils/audio.d.ts +0 -1
- package/dist/utils/capability-checker.d.ts +0 -28
- package/dist/utils/encodeToken.d.ts +0 -14
- package/dist/utils/face.d.ts +0 -1
- package/dist/utils/float32-decoder.d.ts +0 -26
- package/dist/utils/fpsTracker.d.ts +0 -27
- package/dist/utils/index.d.ts +0 -22
- package/dist/utils/logger.d.ts +0 -9
- package/dist/utils/media-recorder-timestamp.d.ts +0 -15
- package/dist/utils/pcmAnalyzer.d.ts +0 -40
- package/dist/utils/perfermance.d.ts +0 -15
- package/dist/utils/request.d.ts +0 -20
- package/dist/utils/requestAnimateFrames.d.ts +0 -31
- package/dist/utils/time.d.ts +0 -9
- package/dist/utils/video-probe-asset.d.ts +0 -28
- package/dist/view/DebugOverlay.d.ts +0 -54
- package/dist/worker/streaming-video.d.ts +0 -1
- package/src/baseRender/AudioRenderer.ts +0 -328
- package/src/baseRender/AudioWorklet.ts +0 -776
- package/src/baseRender/AvatarRenderer.ts +0 -1550
- package/src/baseRender/MSEAudioPlayer.ts +0 -254
- package/src/baseRender/UIRenderer.ts +0 -195
- package/src/baseRender/pcm-audio-processor.js +0 -267
- package/src/control/APIForwarder.ts +0 -67
- package/src/control/DataCacheQueue.ts +0 -372
- package/src/control/EventDispatcher.ts +0 -106
- package/src/control/RenderScheduler.ts +0 -1000
- package/src/control/SaveAndDownload.js +0 -77
- package/src/control/StreamingClient.ts +0 -138
- package/src/control/VideoPlayer.ts +0 -306
- package/src/control/ttsa.ts +0 -754
- package/src/encoding/media-recorder-encoder.ts +0 -175
- package/src/encoding/pcm-webm-encoder.ts +0 -72
- package/src/encoding/webcodec-opus-encoder.ts +0 -258
- package/src/encoding/webm-muxer.ts +0 -337
- package/src/global.d.ts +0 -112
- package/src/index.ts +0 -2053
- package/src/modules/Composition.ts +0 -79
- package/src/modules/ResourceManager.ts +0 -926
- package/src/modules/TrackRenderer/base-track.ts +0 -7
- package/src/modules/TrackRenderer/index.ts +0 -40
- package/src/modules/TrackRenderer/render-implements.ts +0 -269
- package/src/modules/TrackRenderer/track-pic.ts +0 -19
- package/src/modules/TrackRenderer/track-subtitle.ts +0 -16
- package/src/modules/cache-manager.ts +0 -10
- package/src/modules/decoder.ts +0 -548
- package/src/modules/error-handle.ts +0 -20
- package/src/modules/network.ts +0 -71
- package/src/modules/sentry-reporter.ts +0 -171
- package/src/proto/face_data.proto +0 -45
- package/src/proto/face_data_pb.js +0 -1297
- package/src/proto/protobuf.min.js +0 -8
- package/src/types/capability.ts +0 -24
- package/src/types/error.ts +0 -211
- package/src/types/event.ts +0 -38
- package/src/types/frame-data.ts +0 -121
- package/src/types/index.ts +0 -220
- package/src/types/render.ts +0 -18
- package/src/utils/DataInterface.ts +0 -746
- package/src/utils/GLDevice.ts +0 -286
- package/src/utils/GLPipeline.ts +0 -1293
- package/src/utils/GLPipelineDebugTools.ts +0 -174
- package/src/utils/Math.ts +0 -266
- package/src/utils/__tests__/capability-checker.test.ts +0 -159
- package/src/utils/audio-session.ts +0 -144
- package/src/utils/audio.ts +0 -44
- package/src/utils/blueimp-md5.d.ts +0 -3
- package/src/utils/capability-checker.ts +0 -762
- package/src/utils/encodeToken.ts +0 -115
- package/src/utils/face.ts +0 -29
- package/src/utils/float32-decoder.js +0 -130
- package/src/utils/fpsTracker.ts +0 -100
- package/src/utils/index.ts +0 -182
- package/src/utils/logger.js +0 -36
- package/src/utils/media-recorder-timestamp.ts +0 -79
- package/src/utils/pcmAnalyzer.ts +0 -154
- package/src/utils/perfermance.ts +0 -141
- package/src/utils/request.ts +0 -107
- package/src/utils/requestAnimateFrames.ts +0 -195
- package/src/utils/time.ts +0 -32
- package/src/utils/video-probe-asset.ts +0 -99
- package/src/view/DebugOverlay.ts +0 -409
- package/src/worker/streaming-video.ts +0 -4647
- /package/{dist → package-dist}/agent/index.cjs +0 -0
- /package/{dist → package-dist}/agent/index.umd.js +0 -0
package/src/utils/pcmAnalyzer.ts
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PCM 音频参数分析工具
|
|
3
|
-
* 用于检测和分析 PCM 音频数据的基本参数
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface PCMAudioParams {
|
|
7
|
-
sampleRate: number; // 采样率 (Hz)
|
|
8
|
-
channels: number; // 声道数 (1=单声道, 2=双声道)
|
|
9
|
-
bitDepth: number; // 位深度 (8, 16, 24, 32)
|
|
10
|
-
format: string; // 格式 (S16LE, S24LE, F32LE 等)
|
|
11
|
-
encoding: string; // 编码方式 (PCM)
|
|
12
|
-
bytesPerSample: number; // 每个采样点的字节数
|
|
13
|
-
sampleCount?: number; // 样本数量(如果提供了数据)
|
|
14
|
-
duration?: number; // 时长(秒)(如果提供了数据)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* 分析 PCM 音频数据的基本参数
|
|
19
|
-
* @param pcmData PCM 音频数据(Uint8Array)
|
|
20
|
-
* @param knownSampleRate 已知的采样率(可选,如果不提供则使用默认值 24000)
|
|
21
|
-
* @returns PCM 音频参数
|
|
22
|
-
*/
|
|
23
|
-
export function analyzePCMParams(
|
|
24
|
-
pcmData?: Uint8Array,
|
|
25
|
-
knownSampleRate?: number
|
|
26
|
-
): PCMAudioParams {
|
|
27
|
-
// 根据代码中的配置,PCM 音频使用固定参数
|
|
28
|
-
const sampleRate = knownSampleRate || 24000;
|
|
29
|
-
const channels = 1; // 单声道
|
|
30
|
-
const bitDepth = 16; // 16位
|
|
31
|
-
const format = 'S16LE'; // Signed 16-bit Little Endian
|
|
32
|
-
const encoding = 'PCM'; // 未压缩的 PCM 编码
|
|
33
|
-
const bytesPerSample = 2; // 16位 = 2字节/采样点
|
|
34
|
-
|
|
35
|
-
let sampleCount: number | undefined;
|
|
36
|
-
let duration: number | undefined;
|
|
37
|
-
|
|
38
|
-
if (pcmData) {
|
|
39
|
-
// 计算样本数量
|
|
40
|
-
sampleCount = Math.floor(pcmData.length / bytesPerSample);
|
|
41
|
-
// 计算时长(秒)
|
|
42
|
-
duration = sampleCount / sampleRate;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
sampleRate,
|
|
47
|
-
channels,
|
|
48
|
-
bitDepth,
|
|
49
|
-
format,
|
|
50
|
-
encoding,
|
|
51
|
-
bytesPerSample,
|
|
52
|
-
sampleCount,
|
|
53
|
-
duration,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* 检测 PCM 数据的可能参数(通过分析数据特征)
|
|
59
|
-
* 注意:这种方法只能提供推测,最准确的参数应该从服务器配置中获取
|
|
60
|
-
* @param pcmData PCM 音频数据
|
|
61
|
-
* @returns 检测到的参数(可能不准确)
|
|
62
|
-
*/
|
|
63
|
-
export function detectPCMParams(pcmData: Uint8Array): Partial<PCMAudioParams> {
|
|
64
|
-
const dataLength = pcmData.length;
|
|
65
|
-
|
|
66
|
-
// 检测位深度和字节序
|
|
67
|
-
// 由于数据是 16 位,每个样本占 2 字节
|
|
68
|
-
const bytesPerSample = 2; // 根据代码,固定为 2 字节(16位)
|
|
69
|
-
const bitDepth = 16;
|
|
70
|
-
|
|
71
|
-
// 检测声道数(通过数据长度和样本对齐)
|
|
72
|
-
// 如果数据长度是 2 的倍数,可能是单声道
|
|
73
|
-
// 如果数据长度是 4 的倍数,可能是双声道
|
|
74
|
-
let channels = 1;
|
|
75
|
-
if (dataLength % 4 === 0) {
|
|
76
|
-
// 可能是双声道(2 通道 × 2 字节 = 4 字节/样本)
|
|
77
|
-
// 但根据代码,这里固定为单声道
|
|
78
|
-
channels = 1;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// 尝试检测采样率(这个很难准确检测,只能推测)
|
|
82
|
-
// 如果知道数据时长和样本数,可以反推采样率
|
|
83
|
-
const sampleCount = Math.floor(dataLength / bytesPerSample);
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
channels,
|
|
87
|
-
bitDepth,
|
|
88
|
-
format: 'S16LE', // 根据代码,固定为 S16LE
|
|
89
|
-
encoding: 'PCM',
|
|
90
|
-
bytesPerSample,
|
|
91
|
-
sampleCount,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* 格式化显示 PCM 参数
|
|
97
|
-
* @param params PCM 音频参数
|
|
98
|
-
* @returns 格式化的字符串
|
|
99
|
-
*/
|
|
100
|
-
export function formatPCMParams(params: PCMAudioParams): string {
|
|
101
|
-
const parts = [
|
|
102
|
-
`采样率: ${params.sampleRate} Hz`,
|
|
103
|
-
`声道: ${params.channels === 1 ? '单声道 (Mono)' : `双声道 (Stereo, ${params.channels} channels)`}`,
|
|
104
|
-
`位深度: ${params.bitDepth} bit`,
|
|
105
|
-
`格式: ${params.format}`,
|
|
106
|
-
`编码: ${params.encoding}`,
|
|
107
|
-
`每样本字节数: ${params.bytesPerSample} bytes`,
|
|
108
|
-
];
|
|
109
|
-
|
|
110
|
-
if (params.sampleCount !== undefined) {
|
|
111
|
-
parts.push(`样本数: ${params.sampleCount.toLocaleString()}`);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (params.duration !== undefined) {
|
|
115
|
-
parts.push(`时长: ${params.duration.toFixed(3)} 秒 (${(params.duration * 1000).toFixed(0)} ms)`);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return parts.join('\n');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* 从音频渲染器获取当前 PCM 配置
|
|
123
|
-
* @param audioRenderer 音频渲染器实例
|
|
124
|
-
* @returns PCM 音频参数
|
|
125
|
-
*/
|
|
126
|
-
export function getPCMParamsFromRenderer(audioRenderer: any): PCMAudioParams {
|
|
127
|
-
// 尝试从 AudioWorklet 或 Audio 播放器获取配置
|
|
128
|
-
let sampleRate = 24000; // 默认值
|
|
129
|
-
let channels = 1;
|
|
130
|
-
let bitDepth = 16;
|
|
131
|
-
let format = 'S16LE';
|
|
132
|
-
let bytesPerSample = 2;
|
|
133
|
-
|
|
134
|
-
// 检查 AudioWorklet
|
|
135
|
-
if (audioRenderer?.audio?.audioCtx) {
|
|
136
|
-
sampleRate = audioRenderer.audio.audioCtx.sampleRate || 24000;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// 检查旧的 Audio 播放器
|
|
140
|
-
if (audioRenderer?.audio?.SAMPLE_RATE) {
|
|
141
|
-
sampleRate = audioRenderer.audio.SAMPLE_RATE;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// 根据代码,PCM 音频固定使用这些参数
|
|
145
|
-
return {
|
|
146
|
-
sampleRate,
|
|
147
|
-
channels,
|
|
148
|
-
bitDepth,
|
|
149
|
-
format,
|
|
150
|
-
encoding: 'PCM',
|
|
151
|
-
bytesPerSample,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
package/src/utils/perfermance.ts
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import Ttsa from "../control/ttsa";
|
|
2
|
-
|
|
3
|
-
export const performanceConstant = {
|
|
4
|
-
load_resource: 'load_resource',
|
|
5
|
-
ttsa_connect: 'ttsa_connect',
|
|
6
|
-
ttsa_ready: 'ttsa_ready',
|
|
7
|
-
first_avatar_render: 'first_avatar_render',
|
|
8
|
-
first_webgl_render: 'first_webgl_render',
|
|
9
|
-
|
|
10
|
-
ttsa_body_res: 'ttsa_body_res',
|
|
11
|
-
|
|
12
|
-
start_action_res: 'start_action_res',
|
|
13
|
-
start_action_render: 'start_action_render',
|
|
14
|
-
load_video: 'load_video',
|
|
15
|
-
decode_video: 'decode_video',
|
|
16
|
-
voice_response_play: 'voice_response_play',
|
|
17
|
-
first_decode_video: 'first_decode_video',
|
|
18
|
-
first_render: 'first_render',
|
|
19
|
-
};
|
|
20
|
-
const performanceText={
|
|
21
|
-
[performanceConstant.load_resource]: '加载资源',
|
|
22
|
-
[performanceConstant.ttsa_connect]: 'ttsa连接',
|
|
23
|
-
[performanceConstant.ttsa_ready]: 'ttsa准备返回first_start_timestamp',
|
|
24
|
-
[performanceConstant.first_avatar_render]: '前端处理好第一帧渲染数据',
|
|
25
|
-
[performanceConstant.first_webgl_render]: '前端处理好第一帧webgl渲染',
|
|
26
|
-
[performanceConstant.ttsa_body_res]: 'ttsa返回body',
|
|
27
|
-
[performanceConstant.start_action_res]: '触发行为返回',
|
|
28
|
-
[performanceConstant.start_action_render]: '触发行为并渲染耗时',
|
|
29
|
-
[performanceConstant.load_video]: '视频加载',
|
|
30
|
-
[performanceConstant.decode_video]: '视频解码',
|
|
31
|
-
[performanceConstant.voice_response_play]: 'speak音频响应',
|
|
32
|
-
[performanceConstant.first_decode_video]: '第一帧视频解码',
|
|
33
|
-
[performanceConstant.first_render]: '第一帧webgl渲染',
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface PerformanceMark {
|
|
37
|
-
state?: string;
|
|
38
|
-
time: number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
class PerformanceTracker {
|
|
42
|
-
private marks: Map<keyof typeof performanceText, PerformanceMark> = new Map();
|
|
43
|
-
private measures: Map<keyof typeof performanceText, PerformanceMark> = new Map();
|
|
44
|
-
private reportFunc: Ttsa | null = null;
|
|
45
|
-
private onStateRenderChange: (state: string, duration: number) => void = () => {};
|
|
46
|
-
public firstDecodeTime = 0;
|
|
47
|
-
public firstRenderTime = 0;
|
|
48
|
-
constructor() {
|
|
49
|
-
this.marks = new Map();
|
|
50
|
-
this.measures = new Map();
|
|
51
|
-
}
|
|
52
|
-
getFirstInfo() {
|
|
53
|
-
return {
|
|
54
|
-
decode_speed: this.firstDecodeTime,
|
|
55
|
-
render_speed: this.firstRenderTime,
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
// 记录开始时间点
|
|
59
|
-
markStart(key: string, state?: string) {
|
|
60
|
-
this.marks.set(key as keyof typeof performanceText, {
|
|
61
|
-
state,
|
|
62
|
-
time: performance.now(),
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 记录结束时间点并计算耗时
|
|
67
|
-
markEnd(key: string, state?: string | any) {
|
|
68
|
-
const mark = this.marks.get(key as keyof typeof performanceText);
|
|
69
|
-
if (!mark) return;
|
|
70
|
-
if(state && typeof state === 'string' && state!== mark?.state) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
const endTime = performance.now();
|
|
74
|
-
const duration = endTime - mark.time;
|
|
75
|
-
this.measures.set(key as keyof typeof performanceText, {
|
|
76
|
-
state: mark?.state,
|
|
77
|
-
time: duration,
|
|
78
|
-
});
|
|
79
|
-
// 提供给业务方状态改变到渲染耗时
|
|
80
|
-
if(mark.state && key === performanceConstant.start_action_render) {
|
|
81
|
-
this.onStateRenderChange(mark.state, duration);
|
|
82
|
-
}
|
|
83
|
-
this.reportMetric();
|
|
84
|
-
|
|
85
|
-
if(key !== performanceConstant.load_video && key !== performanceConstant.decode_video) {
|
|
86
|
-
this.marks.delete(key as keyof typeof performanceText);
|
|
87
|
-
}
|
|
88
|
-
// 记录第一帧解码时间
|
|
89
|
-
if(key === performanceConstant.first_decode_video) {
|
|
90
|
-
this.firstDecodeTime = duration;
|
|
91
|
-
}
|
|
92
|
-
// 记录第一帧渲染时间
|
|
93
|
-
if(key === performanceConstant.first_render) {
|
|
94
|
-
this.firstRenderTime = duration;
|
|
95
|
-
}
|
|
96
|
-
return duration;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// 获取所有性能数据
|
|
100
|
-
getAllMetrics() {
|
|
101
|
-
return Object.fromEntries(this.measures.entries());
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
getVideoMetrics() {
|
|
105
|
-
const data = this.getAllMetrics();
|
|
106
|
-
const filterData = Object.fromEntries(
|
|
107
|
-
Object.entries(data).filter(([key]) => [performanceConstant.decode_video, performanceConstant.load_video].includes(key))
|
|
108
|
-
);
|
|
109
|
-
return filterData;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
setReportFunc(ttsa: Ttsa) {
|
|
113
|
-
this.reportFunc = ttsa;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// 上报数据到服务器
|
|
117
|
-
reportMetric() {
|
|
118
|
-
const data = this.getAllMetrics();
|
|
119
|
-
if(Object.keys(data).length > 0 && this.reportFunc?.getStatus()) {
|
|
120
|
-
// 过滤load_video和decode_video
|
|
121
|
-
const filterData = Object.fromEntries(
|
|
122
|
-
Object.entries(data).filter(([key]) => ![performanceConstant.load_video, performanceConstant.decode_video].includes(key))
|
|
123
|
-
);
|
|
124
|
-
if(Object.keys(filterData).length > 0) {
|
|
125
|
-
this.reportFunc?.sendPerfLog(filterData);
|
|
126
|
-
}
|
|
127
|
-
// 遍历data
|
|
128
|
-
for(const key in filterData) {
|
|
129
|
-
if(key !== performanceConstant.decode_video && key !== performanceConstant.load_video) {
|
|
130
|
-
this.measures.delete(key);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
setOnStateRenderChange(onStateRenderChange: (state: string, duration: number) => void) {
|
|
136
|
-
this.onStateRenderChange = onStateRenderChange;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// 创建全局单例
|
|
141
|
-
(window as any).performanceTracker = new PerformanceTracker();
|
package/src/utils/request.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD'
|
|
2
|
-
|
|
3
|
-
interface RequestOptions {
|
|
4
|
-
method?: HttpMethod
|
|
5
|
-
data?: Record<string, any>
|
|
6
|
-
headers?: Record<string, string>
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface ErrorResponse {
|
|
10
|
-
status: number
|
|
11
|
-
statusText: string
|
|
12
|
-
message: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const TAG = '[REQUEST]'
|
|
16
|
-
export default async function request(
|
|
17
|
-
url: string,
|
|
18
|
-
options: RequestOptions = {}
|
|
19
|
-
) {
|
|
20
|
-
const { method = 'GET', data, headers = {} } = options
|
|
21
|
-
|
|
22
|
-
const config: RequestInit = {
|
|
23
|
-
method,
|
|
24
|
-
// headers,
|
|
25
|
-
headers: {
|
|
26
|
-
'Content-Type': 'application/json',
|
|
27
|
-
...headers,
|
|
28
|
-
},
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (method === 'GET' && data) {
|
|
32
|
-
const params = new URLSearchParams(data).toString()
|
|
33
|
-
url += `${url.includes('?') ? '&' : '?'}${params}`
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (method !== 'GET' && data) {
|
|
37
|
-
config.body = JSON.stringify(data)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
const response = await fetch(url, config)
|
|
42
|
-
|
|
43
|
-
if (!response.ok) {
|
|
44
|
-
const error: ErrorResponse = {
|
|
45
|
-
status: response.status,
|
|
46
|
-
statusText: response.statusText,
|
|
47
|
-
message: `HTTP error! status: ${response.status}`,
|
|
48
|
-
}
|
|
49
|
-
throw error
|
|
50
|
-
}
|
|
51
|
-
return response
|
|
52
|
-
} catch (error) {
|
|
53
|
-
if (error instanceof Error) {
|
|
54
|
-
throw {
|
|
55
|
-
message: error.message,
|
|
56
|
-
status: 500,
|
|
57
|
-
statusText: 'Client Error',
|
|
58
|
-
} as ErrorResponse
|
|
59
|
-
}
|
|
60
|
-
throw error
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* 资源下载请求
|
|
66
|
-
* @returns {Promise<[isError: boolean, data: ArrayBuffer | string]>}
|
|
67
|
-
* */
|
|
68
|
-
export function XMLRequest(options: {
|
|
69
|
-
url: string
|
|
70
|
-
cache?: {
|
|
71
|
-
enable: boolean
|
|
72
|
-
}
|
|
73
|
-
timeout?: number
|
|
74
|
-
onProgress: (progress: number, e: ProgressEvent) => void
|
|
75
|
-
}): Promise<[boolean, ArrayBuffer | string]> {
|
|
76
|
-
return new Promise((resolve, reject) => {
|
|
77
|
-
const xhr = new XMLHttpRequest();
|
|
78
|
-
xhr.open('GET', options.url);
|
|
79
|
-
xhr.responseType = 'arraybuffer';
|
|
80
|
-
// 超时兜底:断网/请求挂起时不再永久等待(默认 60s,资源下载可能超时失败)
|
|
81
|
-
xhr.timeout = options.timeout ?? 60000;
|
|
82
|
-
xhr.onload = function() {
|
|
83
|
-
if (xhr.readyState === 4 && xhr.status === 200) {
|
|
84
|
-
const arrayBuffer = xhr.response;
|
|
85
|
-
resolve([false, arrayBuffer]);
|
|
86
|
-
} else {
|
|
87
|
-
reject([true, `HTTP ${xhr.status} ${xhr.statusText}`]);
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
xhr.onerror = function() {
|
|
91
|
-
reject([true, 'network error']);
|
|
92
|
-
};
|
|
93
|
-
xhr.ontimeout = function() {
|
|
94
|
-
reject([true, `timeout after ${xhr.timeout}ms`]);
|
|
95
|
-
};
|
|
96
|
-
xhr.onabort = function() {
|
|
97
|
-
reject([true, 'aborted']);
|
|
98
|
-
};
|
|
99
|
-
xhr.onprogress = (e) => {
|
|
100
|
-
if (e.lengthComputable) {
|
|
101
|
-
const percent = Math.round((e.loaded / e.total) * 100);
|
|
102
|
-
options.onProgress(percent, e);
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
xhr.send();
|
|
106
|
-
});
|
|
107
|
-
}
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
interface FrameAnimationControllerOptions {
|
|
2
|
-
defaultSpeed?: number;
|
|
3
|
-
frameRate?: number;
|
|
4
|
-
frameCallback?: (frame: number, timeJson: any) => void;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export default class FrameAnimationController {
|
|
8
|
-
private startTime: number | null; // 新增:开始时间
|
|
9
|
-
private currentFrame: number;
|
|
10
|
-
private speed: number;
|
|
11
|
-
private frameCallback: ((frame: number, timeJson: any) => void) | null;
|
|
12
|
-
private isPlaying: boolean;
|
|
13
|
-
private animationId: number | null;
|
|
14
|
-
private frameRate: number; // 新增:帧率
|
|
15
|
-
private startTimeFromDate: number | null; // 新增:开始时间日期
|
|
16
|
-
private currentFrameFromDate: number | null; // 新增:当前帧日期
|
|
17
|
-
|
|
18
|
-
constructor({
|
|
19
|
-
defaultSpeed = 1,
|
|
20
|
-
frameRate = 24,
|
|
21
|
-
frameCallback,
|
|
22
|
-
}: FrameAnimationControllerOptions = {}) {
|
|
23
|
-
this.startTime = null; // 初始化开始时间为null
|
|
24
|
-
this.currentFrame = 1;
|
|
25
|
-
this.speed = defaultSpeed;
|
|
26
|
-
this.frameCallback = frameCallback || null;
|
|
27
|
-
this.frameRate = frameRate; // 保存帧率
|
|
28
|
-
|
|
29
|
-
this.isPlaying = false;
|
|
30
|
-
this.animationId = null;
|
|
31
|
-
this.startTimeFromDate = null; // 初始化开始时间日期为null
|
|
32
|
-
this.currentFrameFromDate = null; // 初始化开始帧日期为null
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
get playing() {
|
|
36
|
-
return this.isPlaying;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// 启动动画
|
|
40
|
-
play(): void {
|
|
41
|
-
if (this.isPlaying) return;
|
|
42
|
-
|
|
43
|
-
this.isPlaying = true;
|
|
44
|
-
(window as any).avatarSDKLogger.log("开始时间", this.startTime)
|
|
45
|
-
// 记录开始时间(如果还没有开始时间)
|
|
46
|
-
if (this.startTime === null) {
|
|
47
|
-
(window as any).avatarSDKLogger.log("当前时间", performance.now())
|
|
48
|
-
this.startTime = performance.now();
|
|
49
|
-
// startTimeFromDate 必须与 startTime 同步设置,
|
|
50
|
-
// 否则 pause→play 后两个时间源基准不一致,导致 frame_number_error 误报
|
|
51
|
-
this.startTimeFromDate = Date.now();
|
|
52
|
-
}
|
|
53
|
-
this.animationId = requestAnimationFrame(this._animate.bind(this));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// 暂停动画
|
|
57
|
-
pause(): void {
|
|
58
|
-
this.isPlaying = false;
|
|
59
|
-
if (this.animationId) {
|
|
60
|
-
cancelAnimationFrame(this.animationId);
|
|
61
|
-
this.animationId = null;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// 重置动画
|
|
66
|
-
reset(): void {
|
|
67
|
-
this.pause();
|
|
68
|
-
this.startTime = null; // 重置开始时间
|
|
69
|
-
this.currentFrame = 1;
|
|
70
|
-
this._updateFrame();
|
|
71
|
-
this.startTimeFromDate = null; // 重置开始时间日期
|
|
72
|
-
this.currentFrameFromDate = null; // 重置当前帧日期
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 设置速度
|
|
76
|
-
setSpeed(speed: number): void {
|
|
77
|
-
this.speed = speed;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 跳转到指定帧
|
|
81
|
-
gotoFrame(frameNumber: number): void {
|
|
82
|
-
if (frameNumber < 1) frameNumber = 1;
|
|
83
|
-
|
|
84
|
-
this.currentFrame = frameNumber;
|
|
85
|
-
this._updateFrame();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// 下一帧
|
|
89
|
-
nextFrame(): void {
|
|
90
|
-
this.currentFrame++;
|
|
91
|
-
this._updateFrame();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// 上一帧
|
|
95
|
-
prevFrame(): void {
|
|
96
|
-
this.gotoFrame(this.currentFrame - 1);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// 清理资源,在组件销毁时调用
|
|
100
|
-
destroy(): void {
|
|
101
|
-
this.pause();
|
|
102
|
-
this.frameCallback = null;
|
|
103
|
-
this.startTime = null; // 清理开始时间
|
|
104
|
-
this.startTimeFromDate = null; // 清理开始时间日期
|
|
105
|
-
this.currentFrame = 1; // 清理当前帧
|
|
106
|
-
this.currentFrameFromDate = null; // 清理当前帧日期
|
|
107
|
-
this.speed = 1;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// 动画循环
|
|
111
|
-
private _animate(timestamp: number): void {
|
|
112
|
-
let nowTime = performance.now();
|
|
113
|
-
const deltaTime = timestamp - nowTime;
|
|
114
|
-
// 如果差异在100ms以内,使用timestamp作为时间戳
|
|
115
|
-
// 如果差异在100ms以上,则使用performance.now()作为时间戳
|
|
116
|
-
if (Math.abs(deltaTime) < 100) {
|
|
117
|
-
nowTime = timestamp;
|
|
118
|
-
}
|
|
119
|
-
if (this.currentFrame < 50) {
|
|
120
|
-
(window as any).avatarSDKLogger.log("animate时间", timestamp, "开始时间", this.startTime, "performance", performance.now())
|
|
121
|
-
}
|
|
122
|
-
if (!this.isPlaying) return;
|
|
123
|
-
|
|
124
|
-
// 基于开始时间计算当前应该显示的帧数
|
|
125
|
-
if (this.startTime !== null) {
|
|
126
|
-
const elapsedTime = nowTime - this.startTime;
|
|
127
|
-
// 使用1/48秒的计算速率来提高准确度,然后除以2来保持1/24秒的显示速率
|
|
128
|
-
const calculatedFrame =
|
|
129
|
-
Math.floor(
|
|
130
|
-
Math.floor(elapsedTime / (1000 / (this.frameRate * 2))) / 2
|
|
131
|
-
) + 1;
|
|
132
|
-
|
|
133
|
-
// (window as any).avatarSDKLogger.log("计算帧号", calculatedFrame)
|
|
134
|
-
// 如果计算出的帧号与当前帧号不同,更新帧号
|
|
135
|
-
if (calculatedFrame !== this.currentFrame) {
|
|
136
|
-
this.currentFrame = calculatedFrame;
|
|
137
|
-
this._updateFrame();
|
|
138
|
-
}
|
|
139
|
-
// 基于开始时间日期计算当前应该显示的帧数
|
|
140
|
-
const elapsedTimeFromDate = Date.now() - this.startTimeFromDate!;
|
|
141
|
-
const calculatedFrameFromDate = Math.floor(
|
|
142
|
-
Math.floor(elapsedTimeFromDate / (1000 / (this.frameRate * 2))) / 2
|
|
143
|
-
) + 1;
|
|
144
|
-
// (window as any).avatarSDKLogger.log("计算日期帧号", calculatedFrameFromDate)
|
|
145
|
-
this.currentFrameFromDate = calculatedFrameFromDate;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (this.isPlaying) {
|
|
149
|
-
this.animationId = requestAnimationFrame(this._animate.bind(this));
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// 更新帧 - 基于时间的帧计算
|
|
154
|
-
private _updateFrame(): void {
|
|
155
|
-
let timeJson:any = null
|
|
156
|
-
if (this.frameCallback) {
|
|
157
|
-
// 执行帧更新回调
|
|
158
|
-
if (this.currentFrameFromDate !== this.currentFrame && this.currentFrameFromDate !== null) {
|
|
159
|
-
timeJson = {
|
|
160
|
-
"key": "frame_number_error",
|
|
161
|
-
"Date帧号": this.currentFrameFromDate,
|
|
162
|
-
"performance帧号": this.currentFrame,
|
|
163
|
-
"开始时间": this.startTime,
|
|
164
|
-
"开始时间日期": this.startTimeFromDate,
|
|
165
|
-
"当前时间": Date.now(),
|
|
166
|
-
"performance.now()": performance.now(),
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
this.frameCallback(this.currentFrame, timeJson);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// 获取当前帧
|
|
174
|
-
getCurrentFrame(): number {
|
|
175
|
-
// 这里不能直接返回this.currentFrame,因为存在用户将游览器置到后台,
|
|
176
|
-
// 游览器会暂停requestAnimationFrame,导致this.currentFrame不更新
|
|
177
|
-
// 这里需要重新计算
|
|
178
|
-
if (this.startTime !== null) {
|
|
179
|
-
const elapsedTime = performance.now() - this.startTime;
|
|
180
|
-
const calculatedFrame =
|
|
181
|
-
Math.floor(
|
|
182
|
-
Math.floor(elapsedTime / (1000 / (this.frameRate * 2))) / 2
|
|
183
|
-
) + 1;
|
|
184
|
-
return calculatedFrame;
|
|
185
|
-
}
|
|
186
|
-
return this.currentFrame;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// 更新开始时间
|
|
190
|
-
updateStartTime(frame: number): void {
|
|
191
|
-
this.startTime = performance.now() - (frame - 1) * (1000 / this.frameRate);
|
|
192
|
-
// 同步 Date 时间基准,避免 performance 和 Date 双时间源脱节导致 frame_number_error 误报
|
|
193
|
-
this.startTimeFromDate = Date.now() - (frame - 1) * (1000 / this.frameRate);
|
|
194
|
-
}
|
|
195
|
-
}
|
package/src/utils/time.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file 时间处理工具函数
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 格式化时间戳字符串为 'YYYY-MM-DD HH:mm:ss' 格式
|
|
7
|
-
* @param timestamp - ISO 格式的时间字符串, e.g., "2025-07-02T15:02:36.391615"
|
|
8
|
-
* @returns 格式化后的时间字符串,如果输入无效则返回 'N/A'
|
|
9
|
-
*/
|
|
10
|
-
export function formatTimestamp(timestamp: string): string {
|
|
11
|
-
if (!timestamp || typeof timestamp !== 'string') {
|
|
12
|
-
return 'N/A';
|
|
13
|
-
}
|
|
14
|
-
try {
|
|
15
|
-
const date = new Date(timestamp);
|
|
16
|
-
if (isNaN(date.getTime())) {
|
|
17
|
-
return 'Invalid Date';
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const year = date.getFullYear();
|
|
21
|
-
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
22
|
-
const day = date.getDate().toString().padStart(2, '0');
|
|
23
|
-
const hours = date.getHours().toString().padStart(2, '0');
|
|
24
|
-
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
25
|
-
const seconds = date.getSeconds().toString().padStart(2, '0');
|
|
26
|
-
|
|
27
|
-
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
28
|
-
} catch (error) {
|
|
29
|
-
(window as any).avatarSDKLogger.error('Error formatting timestamp:', error);
|
|
30
|
-
return timestamp; // 如果格式化失败,返回原始字符串
|
|
31
|
-
}
|
|
32
|
-
}
|