@xmov/avatar 2.0.0-alpha.38

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.
Files changed (160) hide show
  1. package/README.md +139 -0
  2. package/dist/agent/__tests__/agent.test.d.ts +1 -0
  3. package/dist/agent/audio-debug.d.ts +3 -0
  4. package/dist/agent/audio-uplink.d.ts +62 -0
  5. package/dist/agent/avatar.cjs +2 -0
  6. package/dist/agent/avatar.cjs.map +1 -0
  7. package/dist/agent/avatar.modern.js +2 -0
  8. package/dist/agent/avatar.modern.js.map +1 -0
  9. package/dist/agent/avatar.module.js +2 -0
  10. package/dist/agent/avatar.module.js.map +1 -0
  11. package/dist/agent/avatar.umd.js +2 -0
  12. package/dist/agent/avatar.umd.js.map +1 -0
  13. package/dist/agent/e2e-client.d.ts +32 -0
  14. package/dist/agent/fixed-audio-track.d.ts +48 -0
  15. package/dist/agent/index.cjs +5 -0
  16. package/dist/agent/index.d.ts +104 -0
  17. package/dist/agent/index.umd.js +10 -0
  18. package/dist/agent/microphone.d.ts +49 -0
  19. package/dist/agent/types.d.ts +153 -0
  20. package/dist/baseRender/AudioRenderer.d.ts +46 -0
  21. package/dist/baseRender/AudioWorklet.d.ts +78 -0
  22. package/dist/baseRender/AvatarRenderer.d.ts +226 -0
  23. package/dist/baseRender/MSEAudioPlayer.d.ts +51 -0
  24. package/dist/baseRender/UIRenderer.d.ts +39 -0
  25. package/dist/baseRender/pcm-audio-processor.d.ts +63 -0
  26. package/dist/control/APIForwarder.d.ts +23 -0
  27. package/dist/control/DataCacheQueue.d.ts +69 -0
  28. package/dist/control/EventDispatcher.d.ts +35 -0
  29. package/dist/control/RenderScheduler.d.ts +162 -0
  30. package/dist/control/SaveAndDownload.d.ts +14 -0
  31. package/dist/control/StreamingClient.d.ts +33 -0
  32. package/dist/control/VideoPlayer.d.ts +47 -0
  33. package/dist/control/ttsa.d.ts +111 -0
  34. package/dist/encoding/media-recorder-encoder.d.ts +33 -0
  35. package/dist/encoding/pcm-webm-encoder.d.ts +13 -0
  36. package/dist/encoding/webcodec-opus-encoder.d.ts +41 -0
  37. package/dist/encoding/webm-muxer.d.ts +90 -0
  38. package/dist/index.cjs +2 -0
  39. package/dist/index.cjs.map +1 -0
  40. package/dist/index.d.ts +231 -0
  41. package/dist/index.modern.js +2 -0
  42. package/dist/index.modern.js.map +1 -0
  43. package/dist/index.module.js +2 -0
  44. package/dist/index.module.js.map +1 -0
  45. package/dist/index.umd.js +2 -0
  46. package/dist/index.umd.js.map +1 -0
  47. package/dist/modules/Composition.d.ts +28 -0
  48. package/dist/modules/ResourceManager.d.ts +295 -0
  49. package/dist/modules/TrackRenderer/base-track.d.ts +4 -0
  50. package/dist/modules/TrackRenderer/index.d.ts +12 -0
  51. package/dist/modules/TrackRenderer/render-implements.d.ts +19 -0
  52. package/dist/modules/TrackRenderer/track-pic.d.ts +8 -0
  53. package/dist/modules/TrackRenderer/track-subtitle.d.ts +8 -0
  54. package/dist/modules/cache-manager.d.ts +5 -0
  55. package/dist/modules/decoder.d.ts +84 -0
  56. package/dist/modules/error-handle.d.ts +17 -0
  57. package/dist/modules/network.d.ts +21 -0
  58. package/dist/proto/face_data_pb.d.ts +0 -0
  59. package/dist/types/capability.d.ts +15 -0
  60. package/dist/types/error.d.ts +70 -0
  61. package/dist/types/event.d.ts +25 -0
  62. package/dist/types/frame-data.d.ts +107 -0
  63. package/dist/types/index.d.ts +187 -0
  64. package/dist/types/render.d.ts +18 -0
  65. package/dist/utils/DataInterface.d.ts +113 -0
  66. package/dist/utils/GLDevice.d.ts +46 -0
  67. package/dist/utils/GLPipeline.d.ts +84 -0
  68. package/dist/utils/GLPipelineDebugTools.d.ts +5 -0
  69. package/dist/utils/Math.d.ts +21 -0
  70. package/dist/utils/__tests__/capability-checker.test.d.ts +1 -0
  71. package/dist/utils/audio-session.d.ts +11 -0
  72. package/dist/utils/audio.d.ts +1 -0
  73. package/dist/utils/capability-checker.d.ts +23 -0
  74. package/dist/utils/encodeToken.d.ts +7 -0
  75. package/dist/utils/face.d.ts +1 -0
  76. package/dist/utils/float32-decoder.d.ts +26 -0
  77. package/dist/utils/fpsTracker.d.ts +27 -0
  78. package/dist/utils/index.d.ts +22 -0
  79. package/dist/utils/logger.d.ts +9 -0
  80. package/dist/utils/media-recorder-timestamp.d.ts +15 -0
  81. package/dist/utils/pcmAnalyzer.d.ts +40 -0
  82. package/dist/utils/perfermance.d.ts +15 -0
  83. package/dist/utils/request.d.ts +19 -0
  84. package/dist/utils/requestAnimateFrames.d.ts +31 -0
  85. package/dist/utils/time.d.ts +9 -0
  86. package/dist/view/DebugOverlay.d.ts +54 -0
  87. package/dist/worker/streaming-video.d.ts +1 -0
  88. package/package.json +82 -0
  89. package/src/agent/__tests__/agent.test.ts +3787 -0
  90. package/src/agent/audio-debug.ts +36 -0
  91. package/src/agent/audio-uplink.ts +392 -0
  92. package/src/agent/e2e-client.ts +215 -0
  93. package/src/agent/fixed-audio-track.ts +547 -0
  94. package/src/agent/index.ts +1393 -0
  95. package/src/agent/microphone.ts +534 -0
  96. package/src/agent/types.ts +197 -0
  97. package/src/baseRender/AudioRenderer.ts +317 -0
  98. package/src/baseRender/AudioWorklet.ts +776 -0
  99. package/src/baseRender/AvatarRenderer.ts +1559 -0
  100. package/src/baseRender/MSEAudioPlayer.ts +243 -0
  101. package/src/baseRender/UIRenderer.ts +195 -0
  102. package/src/baseRender/pcm-audio-processor.js +267 -0
  103. package/src/control/APIForwarder.ts +67 -0
  104. package/src/control/DataCacheQueue.ts +372 -0
  105. package/src/control/EventDispatcher.ts +106 -0
  106. package/src/control/RenderScheduler.ts +945 -0
  107. package/src/control/SaveAndDownload.js +77 -0
  108. package/src/control/StreamingClient.ts +138 -0
  109. package/src/control/VideoPlayer.ts +306 -0
  110. package/src/control/ttsa.ts +676 -0
  111. package/src/encoding/media-recorder-encoder.ts +175 -0
  112. package/src/encoding/pcm-webm-encoder.ts +72 -0
  113. package/src/encoding/webcodec-opus-encoder.ts +258 -0
  114. package/src/encoding/webm-muxer.ts +337 -0
  115. package/src/global.d.ts +105 -0
  116. package/src/index.ts +1920 -0
  117. package/src/modules/Composition.ts +79 -0
  118. package/src/modules/ResourceManager.ts +899 -0
  119. package/src/modules/TrackRenderer/base-track.ts +7 -0
  120. package/src/modules/TrackRenderer/index.ts +40 -0
  121. package/src/modules/TrackRenderer/render-implements.ts +269 -0
  122. package/src/modules/TrackRenderer/track-pic.ts +19 -0
  123. package/src/modules/TrackRenderer/track-subtitle.ts +16 -0
  124. package/src/modules/cache-manager.ts +10 -0
  125. package/src/modules/decoder.ts +515 -0
  126. package/src/modules/error-handle.ts +20 -0
  127. package/src/modules/network.ts +71 -0
  128. package/src/proto/face_data.proto +45 -0
  129. package/src/proto/face_data_pb.js +1297 -0
  130. package/src/proto/protobuf.min.js +8 -0
  131. package/src/types/capability.ts +23 -0
  132. package/src/types/error.ts +211 -0
  133. package/src/types/event.ts +38 -0
  134. package/src/types/frame-data.ts +121 -0
  135. package/src/types/index.ts +207 -0
  136. package/src/types/render.ts +18 -0
  137. package/src/utils/DataInterface.ts +746 -0
  138. package/src/utils/GLDevice.ts +286 -0
  139. package/src/utils/GLPipeline.ts +1293 -0
  140. package/src/utils/GLPipelineDebugTools.ts +174 -0
  141. package/src/utils/Math.ts +266 -0
  142. package/src/utils/__tests__/capability-checker.test.ts +159 -0
  143. package/src/utils/audio-session.ts +144 -0
  144. package/src/utils/audio.ts +44 -0
  145. package/src/utils/blueimp-md5.d.ts +3 -0
  146. package/src/utils/capability-checker.ts +379 -0
  147. package/src/utils/encodeToken.ts +100 -0
  148. package/src/utils/face.ts +29 -0
  149. package/src/utils/float32-decoder.js +130 -0
  150. package/src/utils/fpsTracker.ts +100 -0
  151. package/src/utils/index.ts +182 -0
  152. package/src/utils/logger.js +36 -0
  153. package/src/utils/media-recorder-timestamp.ts +79 -0
  154. package/src/utils/pcmAnalyzer.ts +154 -0
  155. package/src/utils/perfermance.ts +141 -0
  156. package/src/utils/request.ts +95 -0
  157. package/src/utils/requestAnimateFrames.ts +195 -0
  158. package/src/utils/time.ts +32 -0
  159. package/src/view/DebugOverlay.ts +404 -0
  160. package/src/worker/streaming-video.ts +4647 -0
@@ -0,0 +1,141 @@
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();
@@ -0,0 +1,95 @@
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
+ onProgress: (progress: number, e: ProgressEvent) => void
74
+ }): Promise<[boolean, ArrayBuffer | string]> {
75
+ return new Promise((resolve, reject) => {
76
+ const xhr = new XMLHttpRequest();
77
+ xhr.open('GET', options.url);
78
+ xhr.responseType = 'arraybuffer';
79
+ xhr.onload = function() {
80
+ if (xhr.readyState === 4 && xhr.status === 200) {
81
+ const arrayBuffer = xhr.response;
82
+ resolve([false, arrayBuffer]);
83
+ } else if (xhr.status !== 200) {
84
+ reject([true, xhr.statusText]);
85
+ }
86
+ };
87
+ xhr.onprogress = (e) => {
88
+ if (e.lengthComputable) {
89
+ const percent = Math.round((e.loaded / e.total) * 100);
90
+ options.onProgress(percent, e);
91
+ }
92
+ };
93
+ xhr.send();
94
+ });
95
+ }
@@ -0,0 +1,195 @@
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
+ }
@@ -0,0 +1,32 @@
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
+ }