@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.
- package/README.md +139 -0
- package/dist/agent/__tests__/agent.test.d.ts +1 -0
- package/dist/agent/audio-debug.d.ts +3 -0
- package/dist/agent/audio-uplink.d.ts +62 -0
- package/dist/agent/avatar.cjs +2 -0
- package/dist/agent/avatar.cjs.map +1 -0
- package/dist/agent/avatar.modern.js +2 -0
- package/dist/agent/avatar.modern.js.map +1 -0
- package/dist/agent/avatar.module.js +2 -0
- package/dist/agent/avatar.module.js.map +1 -0
- package/dist/agent/avatar.umd.js +2 -0
- package/dist/agent/avatar.umd.js.map +1 -0
- package/dist/agent/e2e-client.d.ts +32 -0
- package/dist/agent/fixed-audio-track.d.ts +48 -0
- package/dist/agent/index.cjs +5 -0
- package/dist/agent/index.d.ts +104 -0
- package/dist/agent/index.umd.js +10 -0
- package/dist/agent/microphone.d.ts +49 -0
- package/dist/agent/types.d.ts +153 -0
- package/dist/baseRender/AudioRenderer.d.ts +46 -0
- package/dist/baseRender/AudioWorklet.d.ts +78 -0
- package/dist/baseRender/AvatarRenderer.d.ts +226 -0
- package/dist/baseRender/MSEAudioPlayer.d.ts +51 -0
- package/dist/baseRender/UIRenderer.d.ts +39 -0
- package/dist/baseRender/pcm-audio-processor.d.ts +63 -0
- package/dist/control/APIForwarder.d.ts +23 -0
- package/dist/control/DataCacheQueue.d.ts +69 -0
- package/dist/control/EventDispatcher.d.ts +35 -0
- package/dist/control/RenderScheduler.d.ts +162 -0
- package/dist/control/SaveAndDownload.d.ts +14 -0
- package/dist/control/StreamingClient.d.ts +33 -0
- package/dist/control/VideoPlayer.d.ts +47 -0
- package/dist/control/ttsa.d.ts +111 -0
- package/dist/encoding/media-recorder-encoder.d.ts +33 -0
- package/dist/encoding/pcm-webm-encoder.d.ts +13 -0
- package/dist/encoding/webcodec-opus-encoder.d.ts +41 -0
- package/dist/encoding/webm-muxer.d.ts +90 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +231 -0
- package/dist/index.modern.js +2 -0
- package/dist/index.modern.js.map +1 -0
- package/dist/index.module.js +2 -0
- package/dist/index.module.js.map +1 -0
- package/dist/index.umd.js +2 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/modules/Composition.d.ts +28 -0
- package/dist/modules/ResourceManager.d.ts +295 -0
- package/dist/modules/TrackRenderer/base-track.d.ts +4 -0
- package/dist/modules/TrackRenderer/index.d.ts +12 -0
- package/dist/modules/TrackRenderer/render-implements.d.ts +19 -0
- package/dist/modules/TrackRenderer/track-pic.d.ts +8 -0
- package/dist/modules/TrackRenderer/track-subtitle.d.ts +8 -0
- package/dist/modules/cache-manager.d.ts +5 -0
- package/dist/modules/decoder.d.ts +84 -0
- package/dist/modules/error-handle.d.ts +17 -0
- package/dist/modules/network.d.ts +21 -0
- package/dist/proto/face_data_pb.d.ts +0 -0
- package/dist/types/capability.d.ts +15 -0
- package/dist/types/error.d.ts +70 -0
- package/dist/types/event.d.ts +25 -0
- package/dist/types/frame-data.d.ts +107 -0
- package/dist/types/index.d.ts +187 -0
- package/dist/types/render.d.ts +18 -0
- package/dist/utils/DataInterface.d.ts +113 -0
- package/dist/utils/GLDevice.d.ts +46 -0
- package/dist/utils/GLPipeline.d.ts +84 -0
- package/dist/utils/GLPipelineDebugTools.d.ts +5 -0
- package/dist/utils/Math.d.ts +21 -0
- package/dist/utils/__tests__/capability-checker.test.d.ts +1 -0
- package/dist/utils/audio-session.d.ts +11 -0
- package/dist/utils/audio.d.ts +1 -0
- package/dist/utils/capability-checker.d.ts +23 -0
- package/dist/utils/encodeToken.d.ts +7 -0
- package/dist/utils/face.d.ts +1 -0
- package/dist/utils/float32-decoder.d.ts +26 -0
- package/dist/utils/fpsTracker.d.ts +27 -0
- package/dist/utils/index.d.ts +22 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/media-recorder-timestamp.d.ts +15 -0
- package/dist/utils/pcmAnalyzer.d.ts +40 -0
- package/dist/utils/perfermance.d.ts +15 -0
- package/dist/utils/request.d.ts +19 -0
- package/dist/utils/requestAnimateFrames.d.ts +31 -0
- package/dist/utils/time.d.ts +9 -0
- package/dist/view/DebugOverlay.d.ts +54 -0
- package/dist/worker/streaming-video.d.ts +1 -0
- package/package.json +82 -0
- package/src/agent/__tests__/agent.test.ts +3787 -0
- package/src/agent/audio-debug.ts +36 -0
- package/src/agent/audio-uplink.ts +392 -0
- package/src/agent/e2e-client.ts +215 -0
- package/src/agent/fixed-audio-track.ts +547 -0
- package/src/agent/index.ts +1393 -0
- package/src/agent/microphone.ts +534 -0
- package/src/agent/types.ts +197 -0
- package/src/baseRender/AudioRenderer.ts +317 -0
- package/src/baseRender/AudioWorklet.ts +776 -0
- package/src/baseRender/AvatarRenderer.ts +1559 -0
- package/src/baseRender/MSEAudioPlayer.ts +243 -0
- package/src/baseRender/UIRenderer.ts +195 -0
- package/src/baseRender/pcm-audio-processor.js +267 -0
- package/src/control/APIForwarder.ts +67 -0
- package/src/control/DataCacheQueue.ts +372 -0
- package/src/control/EventDispatcher.ts +106 -0
- package/src/control/RenderScheduler.ts +945 -0
- package/src/control/SaveAndDownload.js +77 -0
- package/src/control/StreamingClient.ts +138 -0
- package/src/control/VideoPlayer.ts +306 -0
- package/src/control/ttsa.ts +676 -0
- package/src/encoding/media-recorder-encoder.ts +175 -0
- package/src/encoding/pcm-webm-encoder.ts +72 -0
- package/src/encoding/webcodec-opus-encoder.ts +258 -0
- package/src/encoding/webm-muxer.ts +337 -0
- package/src/global.d.ts +105 -0
- package/src/index.ts +1920 -0
- package/src/modules/Composition.ts +79 -0
- package/src/modules/ResourceManager.ts +899 -0
- package/src/modules/TrackRenderer/base-track.ts +7 -0
- package/src/modules/TrackRenderer/index.ts +40 -0
- package/src/modules/TrackRenderer/render-implements.ts +269 -0
- package/src/modules/TrackRenderer/track-pic.ts +19 -0
- package/src/modules/TrackRenderer/track-subtitle.ts +16 -0
- package/src/modules/cache-manager.ts +10 -0
- package/src/modules/decoder.ts +515 -0
- package/src/modules/error-handle.ts +20 -0
- package/src/modules/network.ts +71 -0
- package/src/proto/face_data.proto +45 -0
- package/src/proto/face_data_pb.js +1297 -0
- package/src/proto/protobuf.min.js +8 -0
- package/src/types/capability.ts +23 -0
- package/src/types/error.ts +211 -0
- package/src/types/event.ts +38 -0
- package/src/types/frame-data.ts +121 -0
- package/src/types/index.ts +207 -0
- package/src/types/render.ts +18 -0
- package/src/utils/DataInterface.ts +746 -0
- package/src/utils/GLDevice.ts +286 -0
- package/src/utils/GLPipeline.ts +1293 -0
- package/src/utils/GLPipelineDebugTools.ts +174 -0
- package/src/utils/Math.ts +266 -0
- package/src/utils/__tests__/capability-checker.test.ts +159 -0
- package/src/utils/audio-session.ts +144 -0
- package/src/utils/audio.ts +44 -0
- package/src/utils/blueimp-md5.d.ts +3 -0
- package/src/utils/capability-checker.ts +379 -0
- package/src/utils/encodeToken.ts +100 -0
- package/src/utils/face.ts +29 -0
- package/src/utils/float32-decoder.js +130 -0
- package/src/utils/fpsTracker.ts +100 -0
- package/src/utils/index.ts +182 -0
- package/src/utils/logger.js +36 -0
- package/src/utils/media-recorder-timestamp.ts +79 -0
- package/src/utils/pcmAnalyzer.ts +154 -0
- package/src/utils/perfermance.ts +141 -0
- package/src/utils/request.ts +95 -0
- package/src/utils/requestAnimateFrames.ts +195 -0
- package/src/utils/time.ts +32 -0
- package/src/view/DebugOverlay.ts +404 -0
- package/src/worker/streaming-video.ts +4647 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import { bodyFile, IRawBodyFrameData } from "../types/frame-data";
|
|
2
|
+
import { workerURL } from "../worker/streaming-video";
|
|
3
|
+
import ResourceManager, { IOfflineIdle } from "./ResourceManager";
|
|
4
|
+
import { performanceConstant } from "../utils/perfermance";
|
|
5
|
+
import SaveAndDownload from "../control/SaveAndDownload";
|
|
6
|
+
import { DataCacheQueue } from "../control/DataCacheQueue";
|
|
7
|
+
import { EErrorCode } from "../types/error";
|
|
8
|
+
import { parseUint8ToFloat32 } from "../utils/float32-decoder";
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export default class ParallelDecoder {
|
|
12
|
+
private tasks: Map<
|
|
13
|
+
string,
|
|
14
|
+
{
|
|
15
|
+
worker: Worker;
|
|
16
|
+
status: string;
|
|
17
|
+
onMessage: (e: MessageEvent) => void;
|
|
18
|
+
ef?: number;
|
|
19
|
+
}
|
|
20
|
+
>;
|
|
21
|
+
private queue: Array<IRawBodyFrameData>;
|
|
22
|
+
private maxParallel: number;
|
|
23
|
+
private currentParallel: number;
|
|
24
|
+
private _locked: boolean;
|
|
25
|
+
private currentTaskId: string | null;
|
|
26
|
+
private resourceManager: ResourceManager;
|
|
27
|
+
private onFrame: (file: bodyFile, frame: any, index: number) => void;
|
|
28
|
+
private onDone: (file: bodyFile, id: string) => void;
|
|
29
|
+
private isFirstDecode: boolean;
|
|
30
|
+
private pendingAbort: boolean = false;
|
|
31
|
+
private abortAfterEf: number | null = null;
|
|
32
|
+
private pendingNewQueue: Array<IRawBodyFrameData> | null = null;
|
|
33
|
+
private currentDecodedFrameIndex: number | null = null;
|
|
34
|
+
private abortAfterFrame: number | null = null;
|
|
35
|
+
private cacheVideoCount: number = 5; // 缓存视频数量
|
|
36
|
+
private saveAndDownload: SaveAndDownload;
|
|
37
|
+
private dataCacheQueue: DataCacheQueue;
|
|
38
|
+
private bodyFrameCountMap: Map<string, number>;
|
|
39
|
+
private maxVideoCount: number = 2;
|
|
40
|
+
private hardwareAcceleration: string = "default";
|
|
41
|
+
private MAX_LOAD_VIDEO_TIMEOUT_MS = 2000; // 最大加载视频超时时间(毫秒)
|
|
42
|
+
|
|
43
|
+
_offlineLastFrame = 0;
|
|
44
|
+
_offlineIdle: IOfflineIdle[] = [];
|
|
45
|
+
_offlineIdleIndex = 0;
|
|
46
|
+
firstDecodeFrame: boolean = true;
|
|
47
|
+
reportMessage: (message: {
|
|
48
|
+
code: EErrorCode;
|
|
49
|
+
message: string;
|
|
50
|
+
e?: object;
|
|
51
|
+
}) => void;
|
|
52
|
+
|
|
53
|
+
constructor(options: {
|
|
54
|
+
hardwareAcceleration: string;
|
|
55
|
+
resourceManager: ResourceManager;
|
|
56
|
+
saveAndDownload: SaveAndDownload;
|
|
57
|
+
dataCacheQueue: DataCacheQueue;
|
|
58
|
+
reportMessage: (message: {
|
|
59
|
+
code: EErrorCode;
|
|
60
|
+
message: string;
|
|
61
|
+
e?: object;
|
|
62
|
+
}) => void;
|
|
63
|
+
}) {
|
|
64
|
+
this.tasks = new Map();
|
|
65
|
+
this.queue = [];
|
|
66
|
+
this.maxParallel = 1;
|
|
67
|
+
this.currentParallel = 0;
|
|
68
|
+
this._locked = false; // 并发保护锁
|
|
69
|
+
this.currentTaskId = null;
|
|
70
|
+
this.resourceManager = options.resourceManager;
|
|
71
|
+
this.onFrame = () => {};
|
|
72
|
+
this.onDone = () => {};
|
|
73
|
+
this.isFirstDecode = true;
|
|
74
|
+
this.saveAndDownload = options.saveAndDownload;
|
|
75
|
+
this.dataCacheQueue = options.dataCacheQueue;
|
|
76
|
+
this.bodyFrameCountMap = new Map();
|
|
77
|
+
this.hardwareAcceleration = options.hardwareAcceleration;
|
|
78
|
+
this.reportMessage = options.reportMessage;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getRandomTaskId() {
|
|
82
|
+
return Date.now() + "_" + Math.random().toString(36).slice(2, 10);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
decode(
|
|
86
|
+
files: Array<IRawBodyFrameData>,
|
|
87
|
+
onFrame: (file: bodyFile, frame: any, index: number) => void,
|
|
88
|
+
) {
|
|
89
|
+
if (this.isFirstDecode) {
|
|
90
|
+
if (this._locked) return; // 并发保护
|
|
91
|
+
this.abort(); // 清理所有旧任务
|
|
92
|
+
this._locked = true;
|
|
93
|
+
this.queue = files; // 拷贝一份队列
|
|
94
|
+
this.onFrame = (file, frame, index) => {
|
|
95
|
+
this.currentDecodedFrameIndex = file.startFrameIndex + index;
|
|
96
|
+
this._handlePendingAbort(file, index);
|
|
97
|
+
onFrame(file, frame, index);
|
|
98
|
+
};
|
|
99
|
+
this.currentParallel = 0;
|
|
100
|
+
this.currentTaskId = this.getRandomTaskId(); // 唯一任务ID
|
|
101
|
+
this.isFirstDecode = false;
|
|
102
|
+
} else {
|
|
103
|
+
this.updateQueue(files);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
clearOldQueue(frame: number) {
|
|
108
|
+
this.queue = this.queue.filter((item) => item.sf >= frame);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
updateQueue(files: Array<IRawBodyFrameData>) {
|
|
112
|
+
this.abortAfterFrame = null;
|
|
113
|
+
if (this.queue.length === 0) {
|
|
114
|
+
this.queue.push(...files);
|
|
115
|
+
// (window as any).avatarSDKLogger.log("队列为空,直接追加", JSON.stringify(this.getQueue()));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const lastQueueEf = this.queue[this.queue.length - 1].ef;
|
|
120
|
+
const firstQueueSf = this.queue[0].sf;
|
|
121
|
+
const newSf = files[0].sf;
|
|
122
|
+
// (window as any).avatarSDKLogger.log("开始校验视频队列,目前队列", JSON.stringify(this.getQueue()));
|
|
123
|
+
|
|
124
|
+
if (newSf >= lastQueueEf) {
|
|
125
|
+
// 顺序追加
|
|
126
|
+
this.queue.push(...files);
|
|
127
|
+
// (window as any).avatarSDKLogger.log("顺序追加结果", JSON.stringify(this.getQueue()));
|
|
128
|
+
} else if (newSf < firstQueueSf) {
|
|
129
|
+
// 回退/seek,判断是否需要等待
|
|
130
|
+
if (
|
|
131
|
+
this.currentDecodedFrameIndex !== null &&
|
|
132
|
+
newSf > this.currentDecodedFrameIndex + 1
|
|
133
|
+
) {
|
|
134
|
+
// 有间隔,需要等待
|
|
135
|
+
this.abortAfterFrame = newSf - 1;
|
|
136
|
+
// 找到数据中ef小于abortAfterFrame的帧
|
|
137
|
+
const oldQueue = this.queue.filter((item) => item.ef < newSf);
|
|
138
|
+
this.queue = oldQueue.concat(files);
|
|
139
|
+
// (window as any).avatarSDKLogger.log("等待处理到abortAfterFrame后再abort", this.abortAfterFrame, this.pendingNewQueue);
|
|
140
|
+
return;
|
|
141
|
+
} else {
|
|
142
|
+
// 没有间隔,直接abort
|
|
143
|
+
this.abort();
|
|
144
|
+
this.queue = files.slice();
|
|
145
|
+
this._locked = true;
|
|
146
|
+
this.currentTaskId = this.getRandomTaskId();
|
|
147
|
+
// (window as any).avatarSDKLogger.log("回退/seek,重置队列", JSON.stringify(this.getQueue()));
|
|
148
|
+
// 清除队列中sf小于newsf的帧
|
|
149
|
+
const newQueueSf = this.queue[0].sf;
|
|
150
|
+
this.dataCacheQueue.clearOldFrames(newQueueSf);
|
|
151
|
+
}
|
|
152
|
+
} else if (newSf === firstQueueSf) {
|
|
153
|
+
this.queue = files.slice();
|
|
154
|
+
const newQueueSf = this.queue[0].sf;
|
|
155
|
+
this.dataCacheQueue.clearOldFrames(newQueueSf);
|
|
156
|
+
// (window as any).avatarSDKLogger.log("重置队列", JSON.stringify(this.getQueue()));
|
|
157
|
+
} else {
|
|
158
|
+
// 在队列中间,找到重叠点,截断再追加
|
|
159
|
+
const mp4Index = this.queue.findIndex((item) => item.sf >= newSf);
|
|
160
|
+
if (mp4Index !== -1) {
|
|
161
|
+
this.queue.length = mp4Index;
|
|
162
|
+
}
|
|
163
|
+
this.abortAfterFrame = newSf-1;
|
|
164
|
+
this.queue.push(...files);
|
|
165
|
+
// (window as any).avatarSDKLogger.log("中间追加,截断后追加", JSON.stringify(this.getQueue()));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
_isIOS() {
|
|
169
|
+
return /iPad|iPhone|iPod|iOS/.test(navigator.userAgent);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
getQueue() {
|
|
173
|
+
return this.queue.map(item => ({
|
|
174
|
+
...item,
|
|
175
|
+
x_offset: []
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 定义超时函数:超过指定时间后 reject
|
|
180
|
+
timeoutPromise(ms: number, reason = "请求超时") {
|
|
181
|
+
return new Promise((_, reject) => {
|
|
182
|
+
setTimeout(() => {
|
|
183
|
+
// (window as any).avatarSDKLogger.error(reason);
|
|
184
|
+
reject();
|
|
185
|
+
}, ms);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 加载视频并添加超时控制(800ms)
|
|
190
|
+
async loadVideoWithTimeout(videoKey: string): Promise<ArrayBuffer | null> {
|
|
191
|
+
try {
|
|
192
|
+
// Promise.race:谁先完成就取谁的结果(加载成功 或 超时)
|
|
193
|
+
const arrayBuffer = await Promise.race([
|
|
194
|
+
this.resourceManager.loadVideo(videoKey), // 原加载逻辑
|
|
195
|
+
this.timeoutPromise(this.MAX_LOAD_VIDEO_TIMEOUT_MS, `视频 ${videoKey} 加载超时(${this.MAX_LOAD_VIDEO_TIMEOUT_MS}ms)`) // 800ms 超时
|
|
196
|
+
]);
|
|
197
|
+
return arrayBuffer as ArrayBuffer; // 加载成功,返回 ArrayBuffer
|
|
198
|
+
} catch (error) {
|
|
199
|
+
return null; // 超时或失败,返回 null 标识
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async _tryStartNext() {
|
|
204
|
+
// 新增:判断当前队列是否包含回退视频(sf < 当前已解码帧索引)
|
|
205
|
+
const hasBackwardVideo = this.queue?.some(item =>
|
|
206
|
+
this.currentDecodedFrameIndex !== null && item.sf < this.currentDecodedFrameIndex
|
|
207
|
+
);
|
|
208
|
+
// 缓存数量判断:回退场景下放宽限制(允许多1个)
|
|
209
|
+
let maxAllowed: number;
|
|
210
|
+
|
|
211
|
+
if (hasBackwardVideo) {
|
|
212
|
+
// 回退时:iOS 保持原限制,其他平台临时+1
|
|
213
|
+
maxAllowed = this._isIOS() ? this.maxVideoCount : this.maxVideoCount + 1;
|
|
214
|
+
} else {
|
|
215
|
+
// 非回退时:iOS 限制为1,其他平台用默认最大数量
|
|
216
|
+
maxAllowed = this._isIOS() ? 1 : this.maxVideoCount;
|
|
217
|
+
}
|
|
218
|
+
if (this.dataCacheQueue.getBodyVideoNameListLength() >= maxAllowed) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (this.currentParallel < this.maxParallel && this.queue.length > 0) {
|
|
222
|
+
const bodyInfo = this.queue.shift() as IRawBodyFrameData;
|
|
223
|
+
this.saveAndDownload.appendMultipleToArray("videoData", [{
|
|
224
|
+
...bodyInfo,
|
|
225
|
+
x_offset: parseUint8ToFloat32(bodyInfo.x_offset) || []
|
|
226
|
+
}]);
|
|
227
|
+
this._cleanupOldWorkers(bodyInfo.body_id ?? bodyInfo.id ?? 0);
|
|
228
|
+
this.currentParallel++;
|
|
229
|
+
(window as any).performanceTracker.markStart(
|
|
230
|
+
performanceConstant.load_video,
|
|
231
|
+
bodyInfo
|
|
232
|
+
);
|
|
233
|
+
if(!bodyInfo.n) {
|
|
234
|
+
this.currentParallel--;
|
|
235
|
+
this._tryStartNext();
|
|
236
|
+
this.reportMessage({
|
|
237
|
+
code: EErrorCode.INVALID_BODY_NAME,
|
|
238
|
+
message: `body数据无Name: ${JSON.stringify(bodyInfo)}`,
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// 加载视频并添加超时控制
|
|
243
|
+
const arrayBuffer = (await this.loadVideoWithTimeout(
|
|
244
|
+
bodyInfo.n
|
|
245
|
+
)) as ArrayBuffer;
|
|
246
|
+
if(!arrayBuffer) {
|
|
247
|
+
this.currentParallel--;
|
|
248
|
+
this._tryStartNext();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
(window as any).performanceTracker.markEnd(
|
|
252
|
+
performanceConstant.load_video,
|
|
253
|
+
bodyInfo
|
|
254
|
+
);
|
|
255
|
+
(window as any).performanceTracker.markStart(
|
|
256
|
+
performanceConstant.decode_video,
|
|
257
|
+
bodyInfo
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
(window as any).performanceTracker.markStart(
|
|
261
|
+
performanceConstant.first_decode_video,
|
|
262
|
+
bodyInfo
|
|
263
|
+
);
|
|
264
|
+
this._startWorker({
|
|
265
|
+
name: bodyInfo.n,
|
|
266
|
+
id: bodyInfo.id,
|
|
267
|
+
frameState: bodyInfo.s,
|
|
268
|
+
start: bodyInfo.asf,
|
|
269
|
+
end: bodyInfo.aef,
|
|
270
|
+
hfd: bodyInfo.hfd,
|
|
271
|
+
data: arrayBuffer,
|
|
272
|
+
startFrameIndex: bodyInfo.sf,
|
|
273
|
+
endFrameIndex: bodyInfo.ef,
|
|
274
|
+
body_id: bodyInfo.body_id,
|
|
275
|
+
x_offset: parseUint8ToFloat32(bodyInfo.x_offset) || []
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
_startWorker(file: bodyFile) {
|
|
281
|
+
const taskId = this.currentTaskId;
|
|
282
|
+
const worker = new Worker(workerURL);
|
|
283
|
+
const id = `${file.body_id ?? file.id ?? 0}_${file.name}`;
|
|
284
|
+
const data = file.data;
|
|
285
|
+
const dataCopy = new ArrayBuffer(data.byteLength);
|
|
286
|
+
new Uint8Array(dataCopy).set(new Uint8Array(data));
|
|
287
|
+
|
|
288
|
+
const onMessage = (e: MessageEvent) => {
|
|
289
|
+
if (taskId !== this.currentTaskId) return; // 只处理当前任务
|
|
290
|
+
if (e.data.type === "frame") {
|
|
291
|
+
if(this.firstDecodeFrame) {
|
|
292
|
+
this.firstDecodeFrame = false;
|
|
293
|
+
(window as any).performanceTracker.markEnd(
|
|
294
|
+
performanceConstant.first_decode_video,
|
|
295
|
+
file
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
if(e.data.index < file.start || e.data.index > file.end) {
|
|
299
|
+
e.data.frame.close();
|
|
300
|
+
} else {
|
|
301
|
+
this.cacheVideo();
|
|
302
|
+
this.onFrame(file, e.data.frame, e.data.index);
|
|
303
|
+
const bodyFrameCount = this.bodyFrameCountMap.get(file.name) ?? 0
|
|
304
|
+
this.bodyFrameCountMap.set(file.name, bodyFrameCount + 1)
|
|
305
|
+
if (this.abortAfterFrame && e.data.index + file.startFrameIndex >= this.abortAfterFrame) {
|
|
306
|
+
(window as any).avatarSDKLogger.log("abortAfterFrame", this.abortAfterFrame, file)
|
|
307
|
+
this.abortOne(id);
|
|
308
|
+
this.abortAfterFrame = null;
|
|
309
|
+
this._tryStartNext();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
} else if(e.data.type === "configSupport") {
|
|
313
|
+
(window as any).avatarSDKLogger.log("configSupport", e.data);
|
|
314
|
+
}else if (e.data.type === "done") {
|
|
315
|
+
(window as any).performanceTracker.markEnd(
|
|
316
|
+
performanceConstant.decode_video,
|
|
317
|
+
file
|
|
318
|
+
);
|
|
319
|
+
const bodyFrameCount = this.bodyFrameCountMap.get(file.name) ?? 0
|
|
320
|
+
if(bodyFrameCount < file.end- file.start - 1) {
|
|
321
|
+
this.bodyFrameCountMap.delete(file.name)
|
|
322
|
+
}
|
|
323
|
+
const task = this.tasks.get(id);
|
|
324
|
+
if (task) {
|
|
325
|
+
task.status = "done";
|
|
326
|
+
task.worker.postMessage({ type: "abort" });
|
|
327
|
+
}
|
|
328
|
+
this.currentParallel--;
|
|
329
|
+
this.onDone && this.onDone(file, id);
|
|
330
|
+
worker.removeEventListener("message", onMessage);
|
|
331
|
+
worker.terminate();
|
|
332
|
+
this.tasks.delete(id);
|
|
333
|
+
if (this.currentParallel === 0 && this.queue.length === 0) {
|
|
334
|
+
this._locked = false; // 解锁
|
|
335
|
+
}
|
|
336
|
+
this._tryStartNext();
|
|
337
|
+
} else if (e.data.type === "error") {
|
|
338
|
+
const task = this.tasks.get(id);
|
|
339
|
+
if (task) {
|
|
340
|
+
task.status = "error";
|
|
341
|
+
task.worker.postMessage({ type: "abort" });
|
|
342
|
+
}
|
|
343
|
+
this.currentParallel--;
|
|
344
|
+
this.onDone && this.onDone(file, id);
|
|
345
|
+
worker.removeEventListener("message", onMessage);
|
|
346
|
+
worker.terminate();
|
|
347
|
+
this.tasks.delete(id);
|
|
348
|
+
if (this.currentParallel === 0 && this.queue.length === 0) {
|
|
349
|
+
this._locked = false; // 解锁
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
worker.addEventListener("message", onMessage);
|
|
354
|
+
this.tasks.set(id, {
|
|
355
|
+
worker,
|
|
356
|
+
status: "decoding",
|
|
357
|
+
onMessage,
|
|
358
|
+
ef: file.endFrameIndex,
|
|
359
|
+
});
|
|
360
|
+
(window as any).avatarSDKLogger.log("start decode", file.name, file.startFrameIndex, file.endFrameIndex)
|
|
361
|
+
worker.postMessage(
|
|
362
|
+
{
|
|
363
|
+
type: "decode",
|
|
364
|
+
file: { data: dataCopy },
|
|
365
|
+
start: file.start,
|
|
366
|
+
end: file.end,
|
|
367
|
+
hardwareAcceleration: this.hardwareAcceleration,
|
|
368
|
+
taskId,
|
|
369
|
+
},
|
|
370
|
+
[dataCopy]
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// 在当前视频解出第一帧后,缓存队列中的前cacheVideoCount个视频(包括当前视频)
|
|
375
|
+
// 首次调用会缓存当前视频及后续cacheVideoCount-1个视频
|
|
376
|
+
// 后续每次调用会重新请求前cacheVideoCount个视频(已缓存的视频会被loadVideo内部过滤)
|
|
377
|
+
cacheVideo() {
|
|
378
|
+
const cacheQueue = this.queue.slice(0, this.cacheVideoCount);
|
|
379
|
+
for (const item of cacheQueue) {
|
|
380
|
+
this.resourceManager.preloadVideo(item.n);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
abort() {
|
|
385
|
+
this.tasks.forEach(({ worker, status, onMessage }) => {
|
|
386
|
+
if (status === "decoding") {
|
|
387
|
+
worker.postMessage({ type: "abort" });
|
|
388
|
+
if (onMessage) worker.removeEventListener("message", onMessage);
|
|
389
|
+
worker.terminate();
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
this.tasks.clear();
|
|
393
|
+
this.queue = [];
|
|
394
|
+
this.currentParallel = 0;
|
|
395
|
+
this._locked = false; // 解锁
|
|
396
|
+
this.currentTaskId = null;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
abortOne(id: string) {
|
|
400
|
+
const task = this.tasks.get(id);
|
|
401
|
+
if (task && task.status === "decoding") {
|
|
402
|
+
task?.worker.postMessage({ type: "abort" });
|
|
403
|
+
if (task?.onMessage) {
|
|
404
|
+
task.worker.removeEventListener("message", task.onMessage);
|
|
405
|
+
}
|
|
406
|
+
task?.worker.terminate();
|
|
407
|
+
this.tasks.delete(id);
|
|
408
|
+
this.currentParallel--;
|
|
409
|
+
if (this.currentParallel === 0 && this.queue.length === 0) {
|
|
410
|
+
this._locked = false; // 解锁
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
destroy() {
|
|
416
|
+
this.abort();
|
|
417
|
+
this.queue = [];
|
|
418
|
+
this.currentParallel = 0;
|
|
419
|
+
this._locked = false;
|
|
420
|
+
this.currentTaskId = null;
|
|
421
|
+
this.pendingAbort = false;
|
|
422
|
+
this.abortAfterEf = null;
|
|
423
|
+
this.pendingNewQueue = null;
|
|
424
|
+
this.abortAfterFrame = null;
|
|
425
|
+
this.currentDecodedFrameIndex = null;
|
|
426
|
+
this.firstDecodeFrame = true;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
_handlePendingAbort(file: bodyFile, index: number) {
|
|
430
|
+
if (this.pendingAbort && this.abortAfterEf !== null) {
|
|
431
|
+
const currentFrameIndex = file.startFrameIndex + index;
|
|
432
|
+
if (currentFrameIndex >= this.abortAfterEf) {
|
|
433
|
+
this.abort();
|
|
434
|
+
this.queue = this.pendingNewQueue!;
|
|
435
|
+
this._locked = true;
|
|
436
|
+
this.currentTaskId = this.getRandomTaskId();
|
|
437
|
+
// 重置等待标志
|
|
438
|
+
this.pendingAbort = false;
|
|
439
|
+
this.abortAfterEf = null;
|
|
440
|
+
this.pendingNewQueue = null;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private _cleanupOldWorkers(newBodyId: number) {
|
|
446
|
+
for (const [taskId, task] of this.tasks) {
|
|
447
|
+
// taskId 格式如 '143_stand_by_unface03_cut_011'
|
|
448
|
+
const [bodyIdStr] = taskId.split("_");
|
|
449
|
+
const bodyId = parseInt(bodyIdStr, 10);
|
|
450
|
+
if (!isNaN(bodyId) && bodyId < newBodyId) {
|
|
451
|
+
task.worker.postMessage({ type: "abort" });
|
|
452
|
+
if (task.onMessage)
|
|
453
|
+
task.worker.removeEventListener("message", task.onMessage);
|
|
454
|
+
task.worker.terminate();
|
|
455
|
+
this.tasks.delete(taskId);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
syncDecode(currentFrameIndex: number) {
|
|
460
|
+
// 直接打断当前解帧的视频,理论上1s,很小概率用户会切换回来。
|
|
461
|
+
const mp4List = this.queue.filter((item) => item.ef > currentFrameIndex);
|
|
462
|
+
if (mp4List.length === 0) {
|
|
463
|
+
return
|
|
464
|
+
}
|
|
465
|
+
this.abort();
|
|
466
|
+
// 如果上述条件不满足,则打断当前处理中的任务,根据currentFrameIndex获取queue中ef>currentFrameIndex的queue,重新装填到解帧队列,开始解帧
|
|
467
|
+
this.queue = mp4List;
|
|
468
|
+
this._locked = true;
|
|
469
|
+
this.currentTaskId = this.getRandomTaskId();
|
|
470
|
+
this._tryStartNext();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/** 进入正常模式 */
|
|
474
|
+
_reload() {
|
|
475
|
+
this._offlineLastFrame = 0
|
|
476
|
+
this.queue = []
|
|
477
|
+
}
|
|
478
|
+
/** 进入离线,清空剩余身体数据 */
|
|
479
|
+
_offLineMode(offlineIdle: IOfflineIdle[], currentFrame: number) {
|
|
480
|
+
this.abort()
|
|
481
|
+
this._offlineLastFrame = currentFrame
|
|
482
|
+
this._offlineIdle = offlineIdle
|
|
483
|
+
this.currentParallel -= 1;
|
|
484
|
+
this.queue = []
|
|
485
|
+
this._offlineRun()
|
|
486
|
+
}
|
|
487
|
+
_offlineRun() {
|
|
488
|
+
const idle = this._offlineIdle[this._offlineIdleIndex]
|
|
489
|
+
if (idle) {
|
|
490
|
+
this._offlineIdleIndex += 1
|
|
491
|
+
this._putOfflineBodyData(idle)
|
|
492
|
+
} else {
|
|
493
|
+
this._offlineIdleIndex = 0
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/** 设置 idle 视频 */
|
|
497
|
+
_putOfflineBodyData(idle: IOfflineIdle) {
|
|
498
|
+
const delta = idle.aef - idle.asf
|
|
499
|
+
|
|
500
|
+
const bodyInfo = {
|
|
501
|
+
id: -1,
|
|
502
|
+
n: idle.n,
|
|
503
|
+
s: 'idle',
|
|
504
|
+
hfd: false,
|
|
505
|
+
body_id: -1,
|
|
506
|
+
asf: idle.asf,
|
|
507
|
+
aef: idle.aef,
|
|
508
|
+
sf: this._offlineLastFrame,
|
|
509
|
+
ef: this._offlineLastFrame + delta,
|
|
510
|
+
x_offset: new Uint8Array(),
|
|
511
|
+
}
|
|
512
|
+
this.queue.push(bodyInfo)
|
|
513
|
+
this._offlineLastFrame = bodyInfo.ef
|
|
514
|
+
}
|
|
515
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { EErrorCode, ERROR_CODE_MAP, ERROR_NAME_MAP } from "../types/error";
|
|
2
|
+
|
|
3
|
+
export default function Errors(params: {
|
|
4
|
+
code: EErrorCode
|
|
5
|
+
message: string
|
|
6
|
+
e?: any
|
|
7
|
+
session_id?: string
|
|
8
|
+
client_speak_id?: string
|
|
9
|
+
}) {
|
|
10
|
+
return {
|
|
11
|
+
code: params.code,
|
|
12
|
+
error_code: ERROR_CODE_MAP[params.code] ?? params.code,
|
|
13
|
+
error_name: ERROR_NAME_MAP[params.code] ?? 'UNKNOWN_ERROR',
|
|
14
|
+
message: params.message,
|
|
15
|
+
session_id: params.session_id || '',
|
|
16
|
+
client_speak_id: params.client_speak_id,
|
|
17
|
+
timestamp: Date.now(),
|
|
18
|
+
originalError: params.e ? JSON.stringify(params.e) : null,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
interface NetworkOptions {
|
|
2
|
+
offlineCallback(message?: string): void
|
|
3
|
+
onlineCallback(message?: string): void
|
|
4
|
+
// retryCallback(count: number): Promise<void>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export default class NetworkMonitor {
|
|
8
|
+
private TAG = "[NetworkMonitor]";
|
|
9
|
+
static ONLINE = true;
|
|
10
|
+
private readonly options: NetworkOptions;
|
|
11
|
+
private retryCount = 1;
|
|
12
|
+
private retryTimer = -1;
|
|
13
|
+
private bindHandleOnline: () => void;
|
|
14
|
+
private bindHandleOffline: () => void;
|
|
15
|
+
public constructor(options: NetworkOptions) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.bindHandleOnline = this._handleOnline.bind(this);
|
|
18
|
+
this.bindHandleOffline = this._handleOffline.bind(this);
|
|
19
|
+
this._initEventListeners();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private _initEventListeners(): void {
|
|
23
|
+
window.addEventListener('online', this.bindHandleOnline);
|
|
24
|
+
window.addEventListener('offline', this.bindHandleOffline);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private _handleOnline(): void {
|
|
28
|
+
window.clearTimeout(this.retryTimer)
|
|
29
|
+
NetworkMonitor.ONLINE = true
|
|
30
|
+
this.options.onlineCallback('[NetworkMonitor] 网络已恢复');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private _handleOffline(): void {
|
|
34
|
+
NetworkMonitor.ONLINE = false
|
|
35
|
+
this.options.offlineCallback('[NetworkMonitor] 网络已断开');
|
|
36
|
+
this._attempt();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private _attempt(): void {
|
|
40
|
+
let delay = 1000
|
|
41
|
+
if (this.retryCount > 1) {
|
|
42
|
+
delay = Math.pow(2, this.retryCount) * 1000;
|
|
43
|
+
}
|
|
44
|
+
this.retryTimer = window.setTimeout(() => {
|
|
45
|
+
// this.options.retryCallback(this.retryCount)
|
|
46
|
+
// .then((result) => {
|
|
47
|
+
// if (result === null) {
|
|
48
|
+
// this.retryCount += 1;
|
|
49
|
+
// this._attempt();
|
|
50
|
+
// } else {
|
|
51
|
+
// this._handleOnline()
|
|
52
|
+
// }
|
|
53
|
+
// })
|
|
54
|
+
// .catch(() => {
|
|
55
|
+
// this.retryCount += 1;
|
|
56
|
+
// this._attempt();
|
|
57
|
+
// });
|
|
58
|
+
}, delay);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
setState(isOnline: boolean) {
|
|
62
|
+
NetworkMonitor.ONLINE = isOnline;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
destroy() {
|
|
66
|
+
window.removeEventListener('online', this.bindHandleOnline);
|
|
67
|
+
window.removeEventListener('offline', this.bindHandleOffline);
|
|
68
|
+
this.retryTimer = -1;
|
|
69
|
+
this.retryCount = 1;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
message MeshData {
|
|
4
|
+
int32 index = 1;
|
|
5
|
+
repeated float weights = 2 [deprecated = true];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
message JointData {
|
|
9
|
+
repeated float translate = 1;
|
|
10
|
+
bytes rotate = 2 [deprecated = true];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
message FaceFrameData {
|
|
14
|
+
int32 id = 1;
|
|
15
|
+
string s = 2;
|
|
16
|
+
int32 sf = 3;
|
|
17
|
+
int32 ef = 4;
|
|
18
|
+
|
|
19
|
+
bytes bsw = 5;
|
|
20
|
+
|
|
21
|
+
// v1 废弃字段
|
|
22
|
+
// repeated repeated float movable_joint_transforms = 6 [deprecated = true];
|
|
23
|
+
// int32 head_texture_model_index = 7 [deprecated = true];
|
|
24
|
+
// repeated float head_texture_pca_weights = 8 [deprecated = true];
|
|
25
|
+
// int32 teeth_texture_model_index = 9 [deprecated = true];
|
|
26
|
+
// repeated float teeth_texture_pca_weights = 10 [deprecated = true];
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
// v2
|
|
30
|
+
bytes cs = 11;
|
|
31
|
+
repeated JointData js = 12;
|
|
32
|
+
repeated MeshData ms = 13;
|
|
33
|
+
|
|
34
|
+
int32 body_id = 14;
|
|
35
|
+
int32 face_frame_type = 15;
|
|
36
|
+
|
|
37
|
+
// 机器人表情块字段
|
|
38
|
+
int32 asf = 16;
|
|
39
|
+
int32 aef = 17;
|
|
40
|
+
string n = 18;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
message FaceFrameDataList {
|
|
44
|
+
repeated FaceFrameData data = 1;
|
|
45
|
+
}
|