@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/encodeToken.ts
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import md5 from 'blueimp-md5';
|
|
2
|
-
import { generateFingerprint } from './index';
|
|
3
|
-
|
|
4
|
-
function encodeWithMd5(s: string): string {
|
|
5
|
-
return md5(s);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 生成 request_id:用于 startSession / stopSession 的幂等与兜底销毁。
|
|
10
|
-
* 组成:md5(机器指纹 + 毫秒时间戳 + appId)
|
|
11
|
-
* 同一次 init → destroy 生命周期内始终不变,即使 session_id 尚未返回
|
|
12
|
-
* 也能用 request_id 让服务端销毁已占用的房间资源。
|
|
13
|
-
*/
|
|
14
|
-
export function generateRequestId(appId: string): string {
|
|
15
|
-
const fingerprint = (() => {
|
|
16
|
-
try { return generateFingerprint(); } catch { return 'unknown'; }
|
|
17
|
-
})();
|
|
18
|
-
const payload = `${fingerprint}|${Date.now()}|${appId}`;
|
|
19
|
-
return md5(payload);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function escapeNonAsciiForPythonJson(value: string): string {
|
|
23
|
-
return value.replace(/[^\u0000-\u007E]/g, character => {
|
|
24
|
-
return `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`;
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function compareUnicodeCodePoints(left: string, right: string): number {
|
|
29
|
-
const leftPoints = Array.from(left, character => character.codePointAt(0)!);
|
|
30
|
-
const rightPoints = Array.from(right, character => character.codePointAt(0)!);
|
|
31
|
-
const length = Math.min(leftPoints.length, rightPoints.length);
|
|
32
|
-
|
|
33
|
-
for (let index = 0; index < length; index += 1) {
|
|
34
|
-
if (leftPoints[index] !== rightPoints[index]) {
|
|
35
|
-
return leftPoints[index] - rightPoints[index];
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return leftPoints.length - rightPoints.length;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface Headers {
|
|
43
|
-
[key: string]: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function parseUrlWithoutBase(url: string): { isAbsolute: boolean; pathWithQuery: string } {
|
|
47
|
-
try {
|
|
48
|
-
// 尝试直接解析为绝对 URL(不含基准)
|
|
49
|
-
const urlObj = new URL(url);
|
|
50
|
-
// 若是绝对 URL,返回标记+完整路径+查询
|
|
51
|
-
return {
|
|
52
|
-
isAbsolute: true,
|
|
53
|
-
pathWithQuery: urlObj.pathname + urlObj.search
|
|
54
|
-
};
|
|
55
|
-
} catch {
|
|
56
|
-
// 解析失败,视为相对路径(path+query)
|
|
57
|
-
// 处理开头可能的斜杠,确保统一格式(如 "path" → "/path")
|
|
58
|
-
let path = url.trim();
|
|
59
|
-
if (!path.startsWith('/')) {
|
|
60
|
-
path = '/' + path;
|
|
61
|
-
}
|
|
62
|
-
return {
|
|
63
|
-
isAbsolute: false,
|
|
64
|
-
pathWithQuery: path
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function headersNeedSign(
|
|
70
|
-
ak: string,
|
|
71
|
-
sk: string,
|
|
72
|
-
method: string,
|
|
73
|
-
url: string,
|
|
74
|
-
data: Record<string, any>
|
|
75
|
-
): { headers: Headers; data: Record<string, any> } {
|
|
76
|
-
const headers: Headers = {};
|
|
77
|
-
const t = Math.floor(Date.now() / 1000);
|
|
78
|
-
const urlPathQuery = parseUrlWithoutBase(url).pathWithQuery;
|
|
79
|
-
|
|
80
|
-
// 递归排序对象的所有 key(包括嵌套对象)
|
|
81
|
-
function sortObjectKeys(obj: any): any {
|
|
82
|
-
if (obj === null || obj === undefined) {
|
|
83
|
-
return obj;
|
|
84
|
-
}
|
|
85
|
-
if (Array.isArray(obj)) {
|
|
86
|
-
return obj.map(item => sortObjectKeys(item));
|
|
87
|
-
}
|
|
88
|
-
if (typeof obj !== 'object') {
|
|
89
|
-
return obj;
|
|
90
|
-
}
|
|
91
|
-
const sortedKeys = Object.keys(obj).sort(compareUnicodeCodePoints);
|
|
92
|
-
const sortedObj: Record<string, any> = {};
|
|
93
|
-
for (const key of sortedKeys) {
|
|
94
|
-
sortedObj[key] = sortObjectKeys(obj[key]);
|
|
95
|
-
}
|
|
96
|
-
return sortedObj;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// 对象按 key 排序并去除空格
|
|
100
|
-
const sortedData = sortObjectKeys(data);
|
|
101
|
-
|
|
102
|
-
// 对齐 Python json.dumps(..., sort_keys=True) 的默认 ensure_ascii=True 行为。
|
|
103
|
-
const dataStr = escapeNonAsciiForPythonJson(JSON.stringify(sortedData)).replace(/ /g, '');
|
|
104
|
-
|
|
105
|
-
const oriSign = `${urlPathQuery.toLowerCase()}${method.toLowerCase()}${dataStr}${sk}${t}`;
|
|
106
|
-
const sign = encodeWithMd5(oriSign);
|
|
107
|
-
|
|
108
|
-
headers['X-APP-ID'] = ak;
|
|
109
|
-
headers['X-TOKEN'] = sign;
|
|
110
|
-
headers['X-TIMESTAMP'] = t.toString();
|
|
111
|
-
return {
|
|
112
|
-
headers,
|
|
113
|
-
data: sortedData,
|
|
114
|
-
};
|
|
115
|
-
}
|
package/src/utils/face.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// translate numpy.float16 tobytes() to Float32Array
|
|
2
|
-
const halfToFloat = (h: number) => {
|
|
3
|
-
const s = (h & 0x8000) >> 15;
|
|
4
|
-
let e = (h & 0x7C00) >> 10;
|
|
5
|
-
let f = h & 0x03FF;
|
|
6
|
-
|
|
7
|
-
if (e === 0) {
|
|
8
|
-
// subnormal
|
|
9
|
-
return ((-1) ** s) * (f / 0x400) * 2 ** (-14);
|
|
10
|
-
} else if (e === 0x1F) {
|
|
11
|
-
// ±Inf or NaN
|
|
12
|
-
return f ? NaN : ((-1) ** s) * Infinity;
|
|
13
|
-
} else {
|
|
14
|
-
// normal
|
|
15
|
-
return ((-1) ** s) * (1 + f / 0x400) * 2 ** (e - 15);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const translateBufferToFloatArray = (buf: any) => {
|
|
20
|
-
const count = buf.byteLength / 2;
|
|
21
|
-
const floats = new Float32Array(count);
|
|
22
|
-
const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
23
|
-
|
|
24
|
-
for (let i = 0; i < count; i++) {
|
|
25
|
-
const h = dv.getUint16(i * 2, true); // true = little‑endian
|
|
26
|
-
floats[i] = halfToFloat(h);
|
|
27
|
-
}
|
|
28
|
-
return Array.from(floats)
|
|
29
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base64 字符串 → Uint8Array 字节流(工具函数,处理 2 字节对齐)
|
|
3
|
-
* @param base64Str 服务端返回的 Base64 编码字符串(int16 字节流 Base64 后)
|
|
4
|
-
* @returns Uint8Array 字节流(2 字节/元素,对应 int16)
|
|
5
|
-
*/
|
|
6
|
-
function base64ToUint8Array(base64Str) {
|
|
7
|
-
if (!base64Str || base64Str.trim() === '') {
|
|
8
|
-
return new Uint8Array(0);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const binaryStr = atob(base64Str);
|
|
12
|
-
const uint8Array = new Uint8Array(binaryStr.length);
|
|
13
|
-
for (let i = 0; i < binaryStr.length; i++) {
|
|
14
|
-
uint8Array[i] = binaryStr.charCodeAt(i);
|
|
15
|
-
}
|
|
16
|
-
return uint8Array;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* 2 字节小端序 → int16 数值(兼容服务端 np.clip 范围:-32767~32767)
|
|
21
|
-
* @param byte0 小端序低位字节
|
|
22
|
-
* @param byte1 小端序高位字节
|
|
23
|
-
* @returns 有符号 int16 数值(严格限制在 -32767~32767,与服务端一致)
|
|
24
|
-
*/
|
|
25
|
-
function twoByteToInt16(byte0, byte1) {
|
|
26
|
-
// 小端序合并为无符号 16 位整数
|
|
27
|
-
const uint16 = (byte0 << 0) | (byte1 << 8);
|
|
28
|
-
// 无符号 → 有符号 int16(自动处理负数)
|
|
29
|
-
let int16Value = uint16 > 32767 ? uint16 - 65536 : uint16;
|
|
30
|
-
// 兼容服务端 np.clip:强制限制范围(防止异常数据超出)
|
|
31
|
-
return Math.max(-32767, Math.min(32767, int16Value));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* 服务端 floats_to_scaled_int16_bytes 反向函数:Base64 → float32 数组
|
|
36
|
-
* 核心逻辑:Base64→int16字节流→int16数值(兼容round+clip)→÷0x7FFF→float32
|
|
37
|
-
* @param base64Str 服务端返回的 Base64 编码字符串
|
|
38
|
-
* @returns float32 精度的浮点数数组(与服务端原始 floats 一致)
|
|
39
|
-
*/
|
|
40
|
-
export function scaledInt16BytesToFloat32(base64Str) {
|
|
41
|
-
// 步骤1:Base64 → Uint8Array 字节流
|
|
42
|
-
const uint8Array = base64ToUint8Array(base64Str);
|
|
43
|
-
|
|
44
|
-
// 校验:int16 占 2 字节,必须 2 字节对齐
|
|
45
|
-
if (uint8Array.length === 0 || uint8Array.length % 2 !== 0) {
|
|
46
|
-
return [];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const SCALE_FACTOR = 0x7FFF; // 与服务端一致:32767
|
|
50
|
-
const length = uint8Array.length / 2; // 计算 int16 元素个数
|
|
51
|
-
|
|
52
|
-
// 批量处理:用 Float32Array 确保 float32 精度(与服务端输入对齐)
|
|
53
|
-
const buffer = new ArrayBuffer(4 * length);
|
|
54
|
-
const float32View = new Float32Array(buffer);
|
|
55
|
-
|
|
56
|
-
// 步骤2:遍历解析+反量化
|
|
57
|
-
for (let i = 0; i < length; i++) {
|
|
58
|
-
const byteIndex = i * 2;
|
|
59
|
-
const byte0 = uint8Array[byteIndex]; // 小端序低位字节
|
|
60
|
-
const byte1 = uint8Array[byteIndex + 1]; // 小端序高位字节
|
|
61
|
-
|
|
62
|
-
// 步骤2.1:解析 int16(兼容服务端 round+clip)
|
|
63
|
-
const int16Value = twoByteToInt16(byte0, byte1);
|
|
64
|
-
|
|
65
|
-
// 步骤2.2:反量化(除以缩放因子,还原原始浮点数)
|
|
66
|
-
const floatValue = int16Value / SCALE_FACTOR;
|
|
67
|
-
|
|
68
|
-
// 步骤2.3:强制 float32 精度(消除 JS float64 多余精度)
|
|
69
|
-
float32View[i] = floatValue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 转为普通数组返回(方便业务使用)
|
|
73
|
-
return Array.from(float32View);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* 可选:带平滑滤波的版本(解决“脸色闪”问题,优化视觉效果)
|
|
78
|
-
* @param base64Str 服务端 Base64 字符串
|
|
79
|
-
* @param lastData 上一帧数据(用于平滑)
|
|
80
|
-
* @param alpha 平滑系数(0~1,默认0.2,越大响应越快,越小越平滑)
|
|
81
|
-
* @returns 平滑后的 float32 数组
|
|
82
|
-
*/
|
|
83
|
-
export function scaledInt16BytesToSmoothFloat32(base64Str, lastData = [], alpha = 0.2) {
|
|
84
|
-
const rawData = scaledInt16BytesToFloat32(base64Str);
|
|
85
|
-
// 首次无历史数据,直接返回原始数据
|
|
86
|
-
if (lastData.length !== rawData.length) return rawData;
|
|
87
|
-
|
|
88
|
-
// 一阶低通滤波:current = alpha*raw + (1-alpha)*last
|
|
89
|
-
return rawData.map((val, idx) => {
|
|
90
|
-
return alpha * val + (1 - alpha) * lastData[idx];
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* 解析后端传输的 float32 字节流为普通数组
|
|
97
|
-
* @param {string|ArrayBuffer|Uint8Array} bytes - 后端传来的二进制数据(注意格式兼容)
|
|
98
|
-
* @returns {number[]} 解析后的浮点数组(空数据返回空数组)
|
|
99
|
-
*/
|
|
100
|
-
/**
|
|
101
|
-
* 从 Uint8Array 解析出 float32 数组(核心修复版)
|
|
102
|
-
* @param {Uint8Array} uint8Arr - 后端传来的 Uint8Array 原始字节
|
|
103
|
-
* @returns {number[]} 解析后的浮点数组
|
|
104
|
-
*/
|
|
105
|
-
export function parseUint8ToFloat32(uint8Arr) {
|
|
106
|
-
// 空值处理
|
|
107
|
-
if (!uint8Arr || uint8Arr.length === 0) {
|
|
108
|
-
return [];
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// 步骤1:校验总字节数是否为 4 的倍数(float32 每个元素占 4 字节)
|
|
113
|
-
if (uint8Arr.length % 4 !== 0) {
|
|
114
|
-
window.avatarSDKLogger.error('Uint8Array长度不是4的倍数,无法解析为float32数组');
|
|
115
|
-
return [];
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// 步骤2:创建新的 ArrayBuffer 并拷贝字节(强制偏移量为 0)
|
|
119
|
-
// 核心:不管原偏移量是多少,拷贝后新数组偏移量=0,必然满足 4 的倍数
|
|
120
|
-
const alignedBuffer = new ArrayBuffer(uint8Arr.length);
|
|
121
|
-
const alignedUint8 = new Uint8Array(alignedBuffer);
|
|
122
|
-
alignedUint8.set(uint8Arr); // 把原始字节拷贝到新数组
|
|
123
|
-
|
|
124
|
-
// 步骤3:基于对齐的 ArrayBuffer 创建 Float32Array
|
|
125
|
-
const float32Arr = new Float32Array(alignedBuffer);
|
|
126
|
-
|
|
127
|
-
// 步骤4:转普通数组返回
|
|
128
|
-
const result = Array.from(float32Arr);
|
|
129
|
-
return result;
|
|
130
|
-
}
|
package/src/utils/fpsTracker.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
export interface FPSStats {
|
|
2
|
-
current: number;
|
|
3
|
-
average: number;
|
|
4
|
-
min: number;
|
|
5
|
-
max: number;
|
|
6
|
-
droppedFrames: number;
|
|
7
|
-
totalFrames: number;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export class FPSTracker {
|
|
11
|
-
private frameTimestamps: number[] = [];
|
|
12
|
-
private maxSamples: number;
|
|
13
|
-
private lastFrameTime: number = 0;
|
|
14
|
-
private frameCount: number = 0;
|
|
15
|
-
private lastUpdateTime: number = 0;
|
|
16
|
-
private currentFPS: number = 0;
|
|
17
|
-
private totalFrames: number = 0;
|
|
18
|
-
private totalTime: number = 0;
|
|
19
|
-
private droppedFrames: number = 0;
|
|
20
|
-
private totalRenderedFrames: number = 0;
|
|
21
|
-
|
|
22
|
-
constructor(maxSamples: number = 60) {
|
|
23
|
-
this.maxSamples = maxSamples;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
tick(rendered: boolean = true): void {
|
|
27
|
-
const now = performance.now();
|
|
28
|
-
|
|
29
|
-
if (this.lastFrameTime > 0) {
|
|
30
|
-
const delta = now - this.lastFrameTime;
|
|
31
|
-
this.frameTimestamps.push(delta);
|
|
32
|
-
|
|
33
|
-
if (this.frameTimestamps.length > this.maxSamples) {
|
|
34
|
-
this.frameTimestamps.shift();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
this.totalFrames++;
|
|
38
|
-
this.totalTime += delta;
|
|
39
|
-
|
|
40
|
-
if (rendered) {
|
|
41
|
-
this.frameCount++;
|
|
42
|
-
this.totalRenderedFrames++;
|
|
43
|
-
} else {
|
|
44
|
-
this.droppedFrames++;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (now - this.lastUpdateTime >= 1000) {
|
|
48
|
-
this.currentFPS = Math.round(this.frameCount * 1000 / (now - this.lastUpdateTime));
|
|
49
|
-
this.frameCount = 0;
|
|
50
|
-
this.lastUpdateTime = now;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
this.lastFrameTime = now;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
getStats(): FPSStats {
|
|
58
|
-
if (this.frameTimestamps.length === 0) {
|
|
59
|
-
return { current: 0, average: 0, min: 0, max: 0, droppedFrames: 0, totalFrames: 0 };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const avgDelta = this.frameTimestamps.reduce((a, b) => a + b, 0) / this.frameTimestamps.length;
|
|
63
|
-
const minDelta = Math.min(...this.frameTimestamps);
|
|
64
|
-
const maxDelta = Math.max(...this.frameTimestamps);
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
current: this.currentFPS,
|
|
68
|
-
average: this.frameTimestamps.length > 0 ? Math.round(1000 / avgDelta) : 0,
|
|
69
|
-
min: minDelta > 0 ? Math.round(1000 / maxDelta) : 0,
|
|
70
|
-
max: maxDelta > 0 ? Math.round(1000 / minDelta) : 0,
|
|
71
|
-
droppedFrames: this.droppedFrames,
|
|
72
|
-
totalFrames: this.totalFrames,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
getCurrentFPS(): number {
|
|
77
|
-
return this.currentFPS;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
getAverageFPS(): number {
|
|
81
|
-
if (this.totalTime === 0) return 0;
|
|
82
|
-
return Math.round(this.totalFrames * 1000 / this.totalTime);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
reset(): void {
|
|
86
|
-
this.frameTimestamps = [];
|
|
87
|
-
this.lastFrameTime = 0;
|
|
88
|
-
this.frameCount = 0;
|
|
89
|
-
this.lastUpdateTime = 0;
|
|
90
|
-
this.currentFPS = 0;
|
|
91
|
-
this.totalFrames = 0;
|
|
92
|
-
this.totalTime = 0;
|
|
93
|
-
this.droppedFrames = 0;
|
|
94
|
-
this.totalRenderedFrames = 0;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
getDroppedFrames(): number {
|
|
98
|
-
return this.droppedFrames;
|
|
99
|
-
}
|
|
100
|
-
}
|
package/src/utils/index.ts
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
// @ts-ignore
|
|
2
|
-
const DefineENV = ENV
|
|
3
|
-
export function isDEV() {
|
|
4
|
-
return DefineENV === "development";
|
|
5
|
-
}
|
|
6
|
-
export function isSupportMES() {
|
|
7
|
-
const ua = navigator.userAgent;
|
|
8
|
-
const vendor = navigator.vendor;
|
|
9
|
-
|
|
10
|
-
// 检查 userAgent 中是否包含 Chrome
|
|
11
|
-
const hasChrome = /Chrome/.test(ua);
|
|
12
|
-
|
|
13
|
-
// 检查 vendor 是否为 Google Inc.
|
|
14
|
-
const isGoogleVendor = /Google Inc/.test(vendor);
|
|
15
|
-
|
|
16
|
-
// Edge(基于 Chromium 的新版 Edge 也包含 Chrome 标识)
|
|
17
|
-
const isEdge = /Edg/.test(ua);
|
|
18
|
-
|
|
19
|
-
// MediaSource 支持性检查(可选)
|
|
20
|
-
const hasMediaSource = typeof (window as any).MediaSource !== "undefined";
|
|
21
|
-
|
|
22
|
-
return hasChrome && (isGoogleVendor || isEdge) && hasMediaSource;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function getStyleStr(styles: Record<string, any>) {
|
|
26
|
-
return Object.entries(styles).map(([key, value]) => {
|
|
27
|
-
// 处理zIndex等数字属性转字符串
|
|
28
|
-
const val = typeof value === 'number' ? value.toString() : value;
|
|
29
|
-
// 驼峰转短横线(如zIndex → z-index)
|
|
30
|
-
const cssKey = key.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
31
|
-
return `${cssKey}:${val};`;
|
|
32
|
-
}).join('');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function updateCanvasXOffset(canvas, offset_PX) {
|
|
36
|
-
// 1. 获取当前 transform 样式
|
|
37
|
-
const currentTransform = canvas.style.transform || '';
|
|
38
|
-
|
|
39
|
-
// 2. 正则匹配现有 translateX 的值(提取数字)
|
|
40
|
-
const translateXRegex = /translateX\((-?\d+(\.\d+)?)px\)/;
|
|
41
|
-
|
|
42
|
-
// 3. 计算新的偏移量(初始为0,匹配到则取原有值)
|
|
43
|
-
|
|
44
|
-
// 4. 移除原有 translateX,拼接新值(保留其他 transform 效果)
|
|
45
|
-
const newTransform = currentTransform
|
|
46
|
-
.replace(translateXRegex, '') // 移除旧的 translateX
|
|
47
|
-
.trim() + ` translateX(${offset_PX}px)`; // 添加新的 translateX
|
|
48
|
-
|
|
49
|
-
// 5. 应用新样式(去除多余空格)
|
|
50
|
-
canvas.style.transform = newTransform.replace(/\s+/g, ' ').trim();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// 生成设备指纹
|
|
54
|
-
export function generateFingerprint() {
|
|
55
|
-
const screenSize = `${screen.width}x${screen.height}`;
|
|
56
|
-
const userAgent = navigator.userAgent;
|
|
57
|
-
const language = navigator.language;
|
|
58
|
-
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
59
|
-
const fingerprint = encodeURIComponent(`${screenSize}|${userAgent}|${language}|${timezone}`);
|
|
60
|
-
return btoa(fingerprint);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// 获取设备品牌及型号及版本号
|
|
64
|
-
export function getDeviceInfo() {
|
|
65
|
-
const ua = navigator.userAgent;
|
|
66
|
-
let deviceBrand = 'Unknown Brand';
|
|
67
|
-
let deviceModel = 'Unknown Model';
|
|
68
|
-
let deviceVersion = 'Unknown Version';
|
|
69
|
-
let browserType = 'Unknown Browser';
|
|
70
|
-
let browserVersion = 'Unknown Version';
|
|
71
|
-
|
|
72
|
-
// 获取操作系统版本
|
|
73
|
-
const windowsMatch = ua.match(/Windows NT (\d+\.\d+)/);
|
|
74
|
-
const androidMatch = ua.match(/Android (\d+\.\d+(\.\d+)?)/);
|
|
75
|
-
const iosMatch = ua.match(/OS (\d+_\d+(\._\d+)?) like Mac OS X/);
|
|
76
|
-
|
|
77
|
-
if (windowsMatch) {
|
|
78
|
-
deviceVersion = `Windows NT ${windowsMatch[1]}`;
|
|
79
|
-
} else if (androidMatch) {
|
|
80
|
-
deviceVersion = `Android ${androidMatch[1]}`;
|
|
81
|
-
} else if (iosMatch) {
|
|
82
|
-
deviceVersion = `iOS ${iosMatch[1].replace(/_/g, '.')}`;
|
|
83
|
-
} else if (ua.includes('Macintosh')) {
|
|
84
|
-
deviceVersion = 'Macintosh';
|
|
85
|
-
} else if (ua.includes('Linux')) {
|
|
86
|
-
deviceVersion = 'Linux';
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// 匹配品牌 - 更准确的匹配逻辑
|
|
90
|
-
// 首先检查苹果设备
|
|
91
|
-
if (ua.includes('iPhone') || ua.includes('iPad') || ua.includes('iPod')) {
|
|
92
|
-
deviceBrand = 'Apple';
|
|
93
|
-
if (ua.includes('iPhone')) deviceModel = 'iPhone';
|
|
94
|
-
else if (ua.includes('iPad')) deviceModel = 'iPad';
|
|
95
|
-
else if (ua.includes('iPod')) deviceModel = 'iPod';
|
|
96
|
-
}
|
|
97
|
-
// 检查 Android 设备
|
|
98
|
-
else if (ua.includes('Android')) {
|
|
99
|
-
deviceBrand = 'Android';
|
|
100
|
-
deviceModel = 'Android';
|
|
101
|
-
}
|
|
102
|
-
// 检查 Windows 设备
|
|
103
|
-
else if (ua.includes('Windows')) {
|
|
104
|
-
deviceBrand = 'Windows';
|
|
105
|
-
if (ua.includes('Windows Phone')) deviceModel = 'Windows Phone';
|
|
106
|
-
else deviceModel = 'Windows Desktop';
|
|
107
|
-
}
|
|
108
|
-
// 检查 Linux 设备
|
|
109
|
-
else if (ua.includes('Linux')) {
|
|
110
|
-
deviceBrand = 'Linux';
|
|
111
|
-
deviceModel = 'Linux';
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// 匹配浏览器类型和版本
|
|
115
|
-
const chromeMatch = ua.match(/Chrome\/(\d+\.\d+\.\d+\.\d+)/);
|
|
116
|
-
const safariMatch = ua.match(/Version\/(\d+\.\d+(\.\d+)?) Safari/);
|
|
117
|
-
const firefoxMatch = ua.match(/Firefox\/(\d+\.\d+(\.\d+)?)\b/);
|
|
118
|
-
const edgeMatch = ua.match(/Edg\/(\d+\.\d+\.\d+\.\d+)/);
|
|
119
|
-
const operaMatch = ua.match(/Opera\/(\d+\.\d+)/);
|
|
120
|
-
const ieMatch = ua.match(/MSIE (\d+\.\d+)/);
|
|
121
|
-
|
|
122
|
-
if (chromeMatch) {
|
|
123
|
-
browserType = 'Chrome';
|
|
124
|
-
browserVersion = chromeMatch[1];
|
|
125
|
-
} else if (safariMatch) {
|
|
126
|
-
browserType = 'Safari';
|
|
127
|
-
browserVersion = safariMatch[1];
|
|
128
|
-
} else if (firefoxMatch) {
|
|
129
|
-
browserType = 'Firefox';
|
|
130
|
-
browserVersion = firefoxMatch[1];
|
|
131
|
-
} else if (edgeMatch) {
|
|
132
|
-
browserType = 'Edge';
|
|
133
|
-
browserVersion = edgeMatch[1];
|
|
134
|
-
} else if (operaMatch) {
|
|
135
|
-
browserType = 'Opera';
|
|
136
|
-
browserVersion = operaMatch[1];
|
|
137
|
-
} else if (ieMatch) {
|
|
138
|
-
browserType = 'IE';
|
|
139
|
-
browserVersion = ieMatch[1];
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
device_id: generateFingerprint(),
|
|
144
|
-
device_brand: deviceBrand,
|
|
145
|
-
device_model: deviceModel,
|
|
146
|
-
os_version: deviceVersion,
|
|
147
|
-
browser: browserType,
|
|
148
|
-
browser_version: browserVersion,
|
|
149
|
-
browser_ua: ua,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ---------------------- 扩展:支持小数的版本(可选) ----------------------
|
|
154
|
-
/**
|
|
155
|
-
* 校验scale格式(支持小数)并返回合法值
|
|
156
|
-
* @param {unknown} scale - 待校验的scale值
|
|
157
|
-
* @param {string} tag - 日志标签
|
|
158
|
-
* @returns {number | string} 合法值,非法返回1
|
|
159
|
-
*/
|
|
160
|
-
export function validateScaleWithDecimal(scale: unknown, tag: string): number | string {
|
|
161
|
-
const validVhRegex = /^\d+(\.\d+)?vh$/; // 支持 "1.5vh"、"10vh"
|
|
162
|
-
const validNumberStrRegex = /^\d+(\.\d+)?$/; // 支持 "2.8"、"5"
|
|
163
|
-
|
|
164
|
-
if (typeof scale === 'number') {
|
|
165
|
-
return scale;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (typeof scale === 'string' && (validVhRegex.test(scale) || validNumberStrRegex.test(scale))) {
|
|
169
|
-
return scale;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
(window as any).avatarSDKLogger?.warn(
|
|
173
|
-
tag,
|
|
174
|
-
'scale 必须是数字、带小数的数字字符串或者"xxxvh"格式(xxx为数字/小数)'
|
|
175
|
-
);
|
|
176
|
-
return 1;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export function isharmony() {
|
|
180
|
-
const userAgent = (navigator.userAgent || "").toLowerCase();
|
|
181
|
-
return /openharmony|arkweb/.test(userAgent);
|
|
182
|
-
}
|
package/src/utils/logger.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// logger.js - 改进的单例日志系统
|
|
2
|
-
let isLoggingEnabled = false;
|
|
3
|
-
|
|
4
|
-
function executeLog(consoleMethod, ...args) {
|
|
5
|
-
// 边界防护:1. 开关关闭 2. 控制台方法不存在(兼容异常环境)
|
|
6
|
-
if (!isLoggingEnabled || typeof consoleMethod !== 'function') {
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
// 统一拼接前缀,执行原生控制台方法
|
|
10
|
-
consoleMethod( ...args);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const sdkLog = {
|
|
14
|
-
log: (...args) => executeLog(console.log, ...args),
|
|
15
|
-
info: (...args) => executeLog(console.info, ...args),
|
|
16
|
-
warn: (...args) => executeLog(console.warn, ...args),
|
|
17
|
-
error: (...args) => executeLog(console.error, ...args)
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// 对外暴露的配置方法
|
|
22
|
-
function setLoggingEnabled(enabled) {
|
|
23
|
-
isLoggingEnabled = enabled;
|
|
24
|
-
// 可选:添加日志说明当前日志状态变化
|
|
25
|
-
if (!enabled) {
|
|
26
|
-
console.log('[AVATAR SDK] version:', VERSION, '日志已禁用');
|
|
27
|
-
} else {
|
|
28
|
-
console.log('[AVATAR SDK] version:', VERSION, '日志已启用');
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// 定义完整的日志对象
|
|
33
|
-
window.avatarSDKLogger = {
|
|
34
|
-
...sdkLog,
|
|
35
|
-
setEnabled: setLoggingEnabled
|
|
36
|
-
};
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
type EpochClock = () => number;
|
|
2
|
-
|
|
3
|
-
function monotonicEpochNow() {
|
|
4
|
-
const clock = globalThis.performance;
|
|
5
|
-
if (
|
|
6
|
-
clock
|
|
7
|
-
&& Number.isFinite(clock.timeOrigin)
|
|
8
|
-
&& typeof clock.now === "function"
|
|
9
|
-
) {
|
|
10
|
-
return clock.timeOrigin + clock.now();
|
|
11
|
-
}
|
|
12
|
-
return Date.now();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Normalizes browser-specific BlobEvent.timecode units to epoch milliseconds. */
|
|
16
|
-
export class MediaRecorderTimestamp {
|
|
17
|
-
private readonly startedAt: number;
|
|
18
|
-
private readonly now: EpochClock;
|
|
19
|
-
private timecodeScale: 1 | 1000 | null = null;
|
|
20
|
-
private firstTimecode: number | null = null;
|
|
21
|
-
private lastTimecode: number | null = null;
|
|
22
|
-
private lastObservedAt: number | null = null;
|
|
23
|
-
private lastTimestamp: number | null = null;
|
|
24
|
-
|
|
25
|
-
constructor(
|
|
26
|
-
private readonly chunkMs: number,
|
|
27
|
-
now: EpochClock = monotonicEpochNow,
|
|
28
|
-
) {
|
|
29
|
-
this.now = now;
|
|
30
|
-
this.startedAt = now();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
resolve(timecode: number): number {
|
|
34
|
-
const observedAt = this.now();
|
|
35
|
-
const validTimecode = Number.isFinite(timecode) && timecode >= 0;
|
|
36
|
-
|
|
37
|
-
if (validTimecode && this.firstTimecode === null) {
|
|
38
|
-
this.firstTimecode = timecode;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
validTimecode
|
|
43
|
-
&& this.timecodeScale === null
|
|
44
|
-
&& this.lastTimecode !== null
|
|
45
|
-
&& timecode > this.lastTimecode
|
|
46
|
-
) {
|
|
47
|
-
const timecodeDelta = timecode - this.lastTimecode;
|
|
48
|
-
const observedDelta = this.lastObservedAt === null ? 0 : observedAt - this.lastObservedAt;
|
|
49
|
-
const expectedDelta = observedDelta >= this.chunkMs / 4 ? observedDelta : this.chunkMs;
|
|
50
|
-
const millisecondError = Math.abs(timecodeDelta - expectedDelta);
|
|
51
|
-
const secondError = Math.abs(timecodeDelta * 1000 - expectedDelta);
|
|
52
|
-
this.timecodeScale = secondError < millisecondError ? 1000 : 1;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let timestamp = observedAt - this.chunkMs;
|
|
56
|
-
const relativeTimecode = validTimecode && this.firstTimecode !== null
|
|
57
|
-
? timecode - this.firstTimecode
|
|
58
|
-
: null;
|
|
59
|
-
if (
|
|
60
|
-
relativeTimecode !== null
|
|
61
|
-
&& relativeTimecode >= 0
|
|
62
|
-
&& (this.timecodeScale !== null || relativeTimecode === 0)
|
|
63
|
-
) {
|
|
64
|
-
timestamp = this.startedAt + relativeTimecode * (this.timecodeScale ?? 1);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (this.lastTimestamp !== null) {
|
|
68
|
-
// A non-empty MediaRecorder Blob must not collapse into an already-used
|
|
69
|
-
// AU bucket when a browser repeats or resets its timecode.
|
|
70
|
-
timestamp = Math.max(timestamp, this.lastTimestamp + this.chunkMs);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const resolved = Math.max(Math.round(this.startedAt), Math.round(timestamp));
|
|
74
|
-
this.lastTimecode = validTimecode ? timecode : this.lastTimecode;
|
|
75
|
-
this.lastObservedAt = observedAt;
|
|
76
|
-
this.lastTimestamp = resolved;
|
|
77
|
-
return resolved;
|
|
78
|
-
}
|
|
79
|
-
}
|