@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,67 @@
1
+ /**
2
+ * APIForwarder - 薄客户端模式下的 API 代理转发器
3
+ * 将客户端的 speak/idle/listen 等 API 调用封装为 api_call 消息,
4
+ * 通过 StreamingClient 发送给 Server 端 SDK 执行。
5
+ */
6
+
7
+ import { StreamingClient } from './StreamingClient';
8
+
9
+ export class APIForwarder {
10
+ private client: StreamingClient;
11
+
12
+ constructor(client: StreamingClient) {
13
+ this.client = client;
14
+ }
15
+
16
+ speak(ssml: string, is_start: boolean = true, is_end: boolean = true, extra: any = {}) {
17
+ this.client.sendApiCall('speak', [ssml, is_start, is_end, extra]);
18
+ }
19
+
20
+ idle() {
21
+ this.client.sendApiCall('idle', []);
22
+ }
23
+
24
+ listen() {
25
+ this.client.sendApiCall('listen', []);
26
+ }
27
+
28
+ think() {
29
+ this.client.sendApiCall('think', []);
30
+ }
31
+
32
+ interactiveidle() {
33
+ this.client.sendApiCall('interactiveidle', []);
34
+ }
35
+
36
+ interrupt(type: string) {
37
+ this.client.sendApiCall('interrupt', [type]);
38
+ }
39
+
40
+ changeLayout(layout: any) {
41
+ this.client.sendApiCall('changeLayout', [layout]);
42
+ }
43
+
44
+ changeWalkConfig(walkConfig: any) {
45
+ this.client.sendApiCall('changeWalkConfig', [walkConfig]);
46
+ }
47
+
48
+ setVolume(volume: number) {
49
+ this.client.sendApiCall('setVolume', [volume]);
50
+ }
51
+
52
+ changeAvatarVisible(visible: boolean) {
53
+ this.client.sendApiCall('changeAvatarVisible', [visible]);
54
+ }
55
+
56
+ switchInvisibleMode() {
57
+ this.client.sendApiCall('switchInvisibleMode', []);
58
+ }
59
+
60
+ destroy(reason: string = 'user') {
61
+ this.client.sendApiCall('destroy', [reason]);
62
+ }
63
+
64
+ init(config?: any) {
65
+ this.client.send({ type: 'init', config });
66
+ }
67
+ }
@@ -0,0 +1,372 @@
1
+ import {
2
+ EFrameDataType,
3
+ IRawAudioFrameData,
4
+ IRawFaceFrameData,
5
+ IRawEventFrameData,
6
+ IRawFrameData,
7
+ StateChangeInfo,
8
+ } from "../types/frame-data";
9
+
10
+ export interface IBodyFrame {
11
+ frame: any;
12
+ frameIndex: number;
13
+ frameState: string;
14
+ id: number;
15
+ name: string;
16
+ body_id: number;
17
+ hfd: boolean;
18
+ sf: number;
19
+ offset: number;
20
+ }
21
+
22
+ export class DataCacheQueue {
23
+ private TAG = "[DataCacheQueue]";
24
+ // body视频抽帧副本,使用 Map 优化查找和删除性能
25
+ private _bodyQueue: Map<number, IBodyFrame> = new Map();
26
+ // 表情match信息列表
27
+ private _facialQueue: Array<IRawFaceFrameData> = [];
28
+ // 原始表情数据
29
+ private _realFacialQueue: Array<IRawFaceFrameData> = [];
30
+ // 音频队列
31
+ private audioQueue: Array<IRawAudioFrameData> = [];
32
+ // UI事件队列
33
+ private eventQueue: Array<IRawEventFrameData> = [];
34
+ // 当前处理好的视频id组
35
+ private videoIdList: string[] = [];
36
+ // 当前状态
37
+ private _currentPlayState: string = "idle";
38
+ // 当前ttsa状态
39
+ private _currentTtsaState: StateChangeInfo | null = null;
40
+ constructor() {
41
+ // @ts-ignore
42
+ window["__dev_event_queue__"] = this.eventQueue;
43
+ }
44
+
45
+ set currentPlayState(state: string) {
46
+ this._currentPlayState = state;
47
+ }
48
+
49
+ get currentPlayState() {
50
+ return this._currentPlayState;
51
+ }
52
+
53
+ set currentTtsaState(state: StateChangeInfo | null) {
54
+ this._currentTtsaState = state;
55
+ }
56
+ get currentTtsaState() {
57
+ return this._currentTtsaState;
58
+ }
59
+
60
+ get bodyQueue() {
61
+ return Array.from(this._bodyQueue.values());
62
+ }
63
+
64
+ // 更新body视频抽帧副本
65
+ _updateBodyImageBitmap(data: IBodyFrame) {
66
+ const old = this._bodyQueue.get(data.frameIndex);
67
+ if (old && old.frame && typeof old.frame.close === "function") {
68
+ old.frame.close();
69
+ }
70
+ // 清理过期帧,根据data.body_id,删除所有小于的帧
71
+ for (const [curIndex, curFrame] of this._bodyQueue.entries()) {
72
+ if (curFrame.body_id < data.body_id && curFrame.frameIndex >= data.frameIndex) {
73
+ curFrame.frame.close();
74
+ this._bodyQueue.delete(curIndex);
75
+ }
76
+ }
77
+ this._bodyQueue.set(data.frameIndex, data);
78
+ }
79
+
80
+ clearOldFrames(sf: number) {
81
+ for (const [curIndex, curFrame] of this._bodyQueue.entries()) {
82
+ if (curIndex >= sf) {
83
+ curFrame.frame.close();
84
+ this._bodyQueue.delete(curIndex);
85
+ }
86
+ }
87
+ }
88
+
89
+ clearOldFaceData(frameNumber: number) {
90
+ this._facialQueue = this._facialQueue.filter(
91
+ (item) => item.frameIndex >= frameNumber
92
+ );
93
+ this._realFacialQueue = this._realFacialQueue.filter(
94
+ (item) => item.frameIndex >= frameNumber
95
+ );
96
+ }
97
+
98
+ setVideoIdList(videoId: string) {
99
+ if (!this.videoIdList.includes(videoId)) {
100
+ this.videoIdList.push(videoId);
101
+ }
102
+ }
103
+
104
+ getVideoIdList() {
105
+ return this.videoIdList;
106
+ }
107
+
108
+ /**
109
+ * 获取指定帧并从Map中删除
110
+ * @param frameIndex 要获取的帧索引
111
+ * @returns 找到的帧数据(未找到则返回undefined)
112
+ */
113
+ _getBodyImageBitmap(frameIndex: number) {
114
+ const frame = this._bodyQueue.get(frameIndex);
115
+
116
+ // 清理过期帧
117
+ for (const [curIndex, curFrame] of this._bodyQueue.entries()) {
118
+ if (curIndex < frameIndex) {
119
+ curFrame.frame.close();
120
+ this._bodyQueue.delete(curIndex);
121
+ }
122
+ }
123
+
124
+ if (frame) {
125
+ return frame;
126
+ }
127
+ return undefined;
128
+ }
129
+
130
+ // 获取body内的视频名称list
131
+ getBodyVideoNameListLength() {
132
+ const list: string[] = [];
133
+ this.bodyQueue.forEach((item) => {
134
+ if (!list.includes(item.name)) {
135
+ list.push(item.name);
136
+ }
137
+ });
138
+ return list.length;
139
+ }
140
+
141
+ _getFaceImageBitmap(frameIndex: number, body_id: number) {
142
+ // 找出face中frameIndex和body_id相同的帧
143
+ let targetItem: IRawFaceFrameData | null = null;
144
+ for (let i = this._facialQueue.length - 1; i >= 0; i--) {
145
+ const item = this._facialQueue[i];
146
+ if (item.frameIndex === frameIndex && item.body_id === body_id) {
147
+ targetItem = item;
148
+ break; // 找到后立即退出循环
149
+ }
150
+ }
151
+ // 删除所有小于frameIndex的帧
152
+ this._facialQueue = this._facialQueue.filter(
153
+ (item) => item.frameIndex >= frameIndex
154
+ );
155
+ return targetItem;
156
+ }
157
+ // 更新表情队列
158
+ _updateFacial(data: Array<IRawFaceFrameData>) {
159
+ this._facialQueue.push(...data);
160
+ }
161
+ get facialQueue() {
162
+ return this._facialQueue;
163
+ }
164
+ _getRealFaceImageBitmap(frameIndex: number, body_id: number) {
165
+ // 找出face中frameIndex和body_id相同的帧
166
+ let targetItem: IRawFaceFrameData | null = null;
167
+ for (let i = this._realFacialQueue.length - 1; i >= 0; i--) {
168
+ const item = this._realFacialQueue[i];
169
+ if (item?.frameIndex === frameIndex && item?.body_id === body_id) {
170
+ targetItem = item;
171
+ break; // 找到后立即退出循环
172
+ }
173
+ }
174
+ // 删除所有小于frameIndex的帧
175
+ this._realFacialQueue = this._realFacialQueue.filter(
176
+ (item) => item?.frameIndex >= frameIndex
177
+ );
178
+ return targetItem;
179
+ }
180
+ _updateRealFacial(data: Array<IRawFaceFrameData>) {
181
+ this._realFacialQueue.push(...data);
182
+ }
183
+ get realFacialQueue() {
184
+ return this._realFacialQueue;
185
+ }
186
+
187
+ /**
188
+ * 清空所有表情数据(用于切换隐身模式时)
189
+ */
190
+ clearAllFaceData() {
191
+ this._facialQueue = [];
192
+ this._realFacialQueue = [];
193
+ (window as any).avatarSDKLogger?.log(
194
+ this.TAG,
195
+ "已清空所有表情数据"
196
+ );
197
+ }
198
+ _updateAudio(data: Array<IRawAudioFrameData>) {
199
+ this.audioQueue.push(...data);
200
+ }
201
+
202
+ _clearAudio(speech_id: number) {
203
+ if (speech_id !== -1) {
204
+ this.audioQueue = this.audioQueue.filter(item => item.sid !== speech_id);
205
+ } else {
206
+ this.audioQueue = [];
207
+ }
208
+ }
209
+
210
+ _getAudio(frameIndex: number) {
211
+ const targetIndex = this.audioQueue.findIndex(
212
+ (item) => item.sf === frameIndex
213
+ );
214
+ if (targetIndex === -1) {
215
+ return;
216
+ }
217
+ const [targetItem] = this.audioQueue.splice(targetIndex, 1);
218
+ return targetItem;
219
+ }
220
+
221
+ _getAudioInterval(startFrame: number, endFrame: number) {
222
+ const audioList = this.audioQueue.filter(
223
+ (item) => item.sf >= startFrame && item.sf <= endFrame
224
+ );
225
+ if (audioList.length === 0) {
226
+ return;
227
+ }
228
+ const audio = audioList[audioList.length - 1];
229
+ const index = this.audioQueue.findIndex((item) => item.id === audio.id);
230
+ this.audioQueue.splice(index, 1);
231
+ return audio;
232
+ }
233
+
234
+ _updateUiEvent(data: Array<IRawEventFrameData>) {
235
+ this.eventQueue.push(...data);
236
+ }
237
+
238
+ clearOldEvent(frame: number) {
239
+ this.eventQueue = this.eventQueue.filter(item => item.sf >= frame);
240
+ }
241
+
242
+ clearSubtitleOn(speech_id: number) {
243
+ this.eventQueue = this.eventQueue.map(item => ({
244
+ ...item,
245
+ e: item.e?.filter((subItem: any) => subItem.speech_id > speech_id || subItem.type !== "subtitle_on")
246
+ }));
247
+ }
248
+
249
+ _clearEvent() {
250
+ this.eventQueue = [];
251
+ }
252
+
253
+ // 获取指定帧的事件,并合并相同 sf 的 e 数组
254
+ _getEvent(frame: number) {
255
+ // 1. 找出所有匹配 sf 的事件
256
+ const matchedItems = this.eventQueue.filter(item => item.sf === frame);
257
+ if (matchedItems.length === 0) return;
258
+
259
+ // 2. 从原队列删除这些事件
260
+ this.eventQueue = this.eventQueue.filter(item => item.sf !== frame);
261
+
262
+ // 3. 合并所有 e 数组(核心逻辑)
263
+ return this._mergeEventsBySf(matchedItems);
264
+ }
265
+
266
+ // 获取区间内事件,并合并相同 sf 的 e 数组
267
+ _getEventInterval(startFrame: number, endFrame: number) {
268
+ // 1. 找出所有区间内的事件
269
+ const matchedItems = this.eventQueue.filter(
270
+ item => item.sf >= startFrame && item.sf <= endFrame
271
+ );
272
+ if (matchedItems.length === 0) return;
273
+
274
+ // 2. 从原队列删除这些事件
275
+ this.eventQueue = this.eventQueue.filter(
276
+ item => !(item.sf >= startFrame && item.sf <= endFrame)
277
+ );
278
+
279
+ // 3. 合并所有 e 数组(核心逻辑)
280
+ return this._mergeEventsBySf(matchedItems);
281
+ }
282
+
283
+ // 【工具方法】合并相同 sf 的事件,把 e 数组拼接在一起
284
+ _mergeEventsBySf(events: any[]) {
285
+ // 以 sf 为 key 分组合并
286
+ const grouped = {};
287
+ for (const event of events) {
288
+ const key = event.sf;
289
+ if (!grouped[key]) {
290
+ // 第一次遇到该 sf,创建基础结构
291
+ grouped[key] = { ...event, e: [] };
292
+ }
293
+ // 把当前事件的 e 数组 push 进去(合并)
294
+ grouped[key].e.push(...event.e);
295
+ }
296
+
297
+ // 取出合并后的结果(你这里 sf 都相同,只会有一个)
298
+ const mergedList:any = Object.values(grouped);
299
+ if(mergedList.length === 0) return null
300
+ if (mergedList.length > 1) {
301
+ // 合并所有帧的e到一个数组
302
+ const allE: any[] = [];
303
+ mergedList.forEach((item:any) => allE.push(...item.e));
304
+ // 构造单个event对象,和你原来代码格式完全一致
305
+ return {
306
+ ...mergedList[0],
307
+ mergeFrameFlag: 1,
308
+ e: allE
309
+ };
310
+ } else{
311
+ return mergedList[0];
312
+ }
313
+ }
314
+
315
+ /**
316
+ * 检查数据是否因seek等原因失效,并清理缓存。
317
+ * @param data 新的数据帧数组
318
+ * @param type 数据类型
319
+ */
320
+ checkValidData(data: Array<IRawFrameData>, type: string) {
321
+ switch (type) {
322
+ case EFrameDataType.BODY:
323
+ break;
324
+ case EFrameDataType.FACE: {
325
+ // 根据bodyId获取face,暂时去掉过滤
326
+ // if (this._facialQueue.length === 0 || data.length === 0) {
327
+ // return;
328
+ // }
329
+ // const isDiscontinuous =
330
+ // this._facialQueue[this._facialQueue.length - 1]?.ef >= data[0].sf ||
331
+ // data[0].sf <= this._facialQueue[0]?.sf;
332
+ // if (isDiscontinuous) {
333
+ // const facialIndex = this._facialQueue.findIndex(
334
+ // (item) => item.sf >= data[0].sf
335
+ // );
336
+ // if (facialIndex !== -1) {
337
+ // this._facialQueue.length = facialIndex;
338
+ // }
339
+ // }
340
+ break;
341
+ }
342
+ case EFrameDataType.AUDIO: {
343
+ if (this.audioQueue.length === 0 || data.length === 0) {
344
+ return;
345
+ }
346
+ const isDiscontinuous =
347
+ this.audioQueue[this.audioQueue.length - 1]?.ef < data[0].sf ||
348
+ data[0].sf < this.audioQueue[0]?.sf;
349
+ if (isDiscontinuous) {
350
+ const audioIndex = this.audioQueue.findIndex(
351
+ (item) => item.sf >= data[0].sf
352
+ );
353
+ if (audioIndex !== -1) {
354
+ this.audioQueue.length = audioIndex;
355
+ }
356
+ }
357
+ break;
358
+ }
359
+ }
360
+ }
361
+ destroy() {
362
+ this._bodyQueue.forEach((item) => {
363
+ if (item.frame && typeof item.frame.close === "function") {
364
+ item.frame.close();
365
+ }
366
+ });
367
+ this._bodyQueue.clear();
368
+ this._facialQueue = [];
369
+ this.audioQueue = [];
370
+ this.eventQueue = [];
371
+ }
372
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * EventDispatcher - 薄客户端模式下的事件分发器
3
+ * 接收来自 Streaming Server 转发的 event_proxy 消息,
4
+ * 分发到用户注册的回调函数。
5
+ */
6
+
7
+ import { AvatarStatus, RenderState, IFrameDataUpdate } from '../types/index';
8
+ import type { IRawWidgetData } from '../types/frame-data';
9
+ import { FPSStats } from '../utils/fpsTracker';
10
+
11
+ export interface EventDispatcherCallbacks {
12
+ onStateChange?: (state: string) => void;
13
+ onSpeakStateChange?: (state: string, client_speak_id: string | number) => void;
14
+ onVoiceStateChange?: (state: string, duration?: number, client_speak_id?: string | number) => void;
15
+ onStatusChange?: (status: AvatarStatus) => void;
16
+ onRenderChange?: (state: RenderState) => void;
17
+ onWalkStateChange?: (state: string) => void;
18
+ onWidgetEvent?: (data: IRawWidgetData) => void;
19
+ onMessage?: (error: any) => void;
20
+ onNetworkInfo?: (info: any) => void;
21
+ onFPSUpdate?: (stats: FPSStats) => void;
22
+ onDownloadProgress?: (progress: number) => void;
23
+ onSessionId?: (sessionId: string) => void;
24
+ onFrameDataUpdate?: (data: IFrameDataUpdate) => void;
25
+ }
26
+
27
+ export class EventDispatcher {
28
+ private callbacks: EventDispatcherCallbacks;
29
+ private subtitleText = '';
30
+ private subtitleVisible = false;
31
+
32
+ constructor(callbacks: EventDispatcherCallbacks) {
33
+ this.callbacks = callbacks;
34
+ }
35
+
36
+ /**
37
+ * 处理来自 Streaming Server 的 event_proxy 消息
38
+ */
39
+ dispatch(eventType: string, data: any, _serverFrame?: number) {
40
+ switch (eventType) {
41
+ case 'state_change':
42
+ this.callbacks.onStateChange?.(data.state || data);
43
+ break;
44
+
45
+ case 'speak_state_change':
46
+ this.callbacks.onSpeakStateChange?.(data.state, data.client_speak_id);
47
+ break;
48
+
49
+ case 'voice_state_change':
50
+ this.callbacks.onVoiceStateChange?.(data.state, data.duration, data.client_speak_id || '');
51
+ break;
52
+
53
+ case 'status_change':
54
+ this.callbacks.onStatusChange?.(data.status);
55
+ break;
56
+
57
+ case 'render_change':
58
+ this.callbacks.onRenderChange?.(data.state);
59
+ break;
60
+
61
+ case 'walk_state_change':
62
+ this.callbacks.onWalkStateChange?.(data.state || data);
63
+ break;
64
+
65
+ case 'widget_event':
66
+ this.callbacks.onWidgetEvent?.(data);
67
+ break;
68
+
69
+ case 'message':
70
+ case 'error':
71
+ this.callbacks.onMessage?.(data);
72
+ break;
73
+
74
+ case 'network_info':
75
+ this.callbacks.onNetworkInfo?.(data);
76
+ break;
77
+
78
+ case 'fps_update':
79
+ this.callbacks.onFPSUpdate?.(data);
80
+ break;
81
+
82
+ case 'download_progress':
83
+ this.callbacks.onDownloadProgress?.(data.progress);
84
+ break;
85
+
86
+ case 'session_id':
87
+ this.callbacks.onSessionId?.(data.session_id);
88
+ break;
89
+
90
+ case 'frame_data_update':
91
+ this.callbacks.onFrameDataUpdate?.(data);
92
+ break;
93
+
94
+ default:
95
+ console.log('[EventDispatcher] 未处理的事件类型:', eventType, data);
96
+ }
97
+ }
98
+
99
+ getSubtitleText(): string {
100
+ return this.subtitleText;
101
+ }
102
+
103
+ isSubtitleVisible(): boolean {
104
+ return this.subtitleVisible;
105
+ }
106
+ }