@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
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
// pcm-audio-processor.js - 运行在音频线程的Worklet处理器
|
|
2
|
-
class PCMAudioProcessor extends AudioWorkletProcessor {
|
|
3
|
-
// 音量配置(与主线程保持一致)
|
|
4
|
-
static get parameterDescriptors() {
|
|
5
|
-
return [
|
|
6
|
-
{
|
|
7
|
-
name: 'volume',
|
|
8
|
-
defaultValue: 1.0,
|
|
9
|
-
minValue: 0.0,
|
|
10
|
-
maxValue: 1.0,
|
|
11
|
-
automationRate: 'a-rate' // 支持音频速率自动化
|
|
12
|
-
}
|
|
13
|
-
];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
constructor() {
|
|
17
|
-
super();
|
|
18
|
-
this.config = {
|
|
19
|
-
sampleRate: 24000, // 固定24kHz采样率
|
|
20
|
-
bytesPerSample: 2, // S16LE=2字节/采样点
|
|
21
|
-
};
|
|
22
|
-
this.pcmCache = new Uint8Array(0); // Worklet内的PCM缓存
|
|
23
|
-
this.isActive = false; // 播放音频状态(控制是否生成声音)
|
|
24
|
-
this.isDestroyed = false;
|
|
25
|
-
this.fadeState = {
|
|
26
|
-
// 淡入淡出状态(避免片段切换爆音)
|
|
27
|
-
phase: 'idle', // idle/fadeIn/fadeOut/steady
|
|
28
|
-
currentGain: 0,
|
|
29
|
-
fadeTime: 0.01, // 淡入淡出时间(秒,建议最小0.01s避免爆音)
|
|
30
|
-
fadeSamples: Math.floor(0.01 * 24000), // 淡入淡出样本数(24000Hz下为240样本)
|
|
31
|
-
};
|
|
32
|
-
this.currentSegment = null; // 当前处理的音频段(float32数组)
|
|
33
|
-
this.segmentOffset = 0; // 当前音频段的播放偏移量
|
|
34
|
-
|
|
35
|
-
// 监听主线程消息(接收PCM数据、状态控制等)
|
|
36
|
-
this.port.onmessage = (e) => this.handleMainThreadMessage(e.data);
|
|
37
|
-
|
|
38
|
-
// 初始化淡入淡出样本数
|
|
39
|
-
this.fadeState.fadeSamples = Math.floor(this.fadeState.fadeTime * this.config.sampleRate);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* 处理主线程发来的消息(PCM数据/状态控制等)
|
|
44
|
-
* @param {Object} data - 消息数据
|
|
45
|
-
* @param {string} data.type - 消息类型:'pcm'/'start'/'pause'/'stop'/'config'/'destroy'/'volume'
|
|
46
|
-
* @param {Uint8Array} [data.pcmData] - PCM数据(仅type='pcm'时存在)
|
|
47
|
-
* @param {Object} [data.config] - 配置更新(仅type='config'时存在)
|
|
48
|
-
* @param {number} [data.volume] - 音量值(仅type='volume'时存在)
|
|
49
|
-
*/
|
|
50
|
-
handleMainThreadMessage(data) {
|
|
51
|
-
switch (data.type) {
|
|
52
|
-
// 接收主线程下发的PCM流数据
|
|
53
|
-
case 'pcm':
|
|
54
|
-
if (data.pcmData instanceof Uint8Array && data.pcmData.length > 0) {
|
|
55
|
-
// 确保PCM数据字节数为2的整数倍(S16LE要求)
|
|
56
|
-
const validLength = data.pcmData.length - (data.pcmData.length % this.config.bytesPerSample);
|
|
57
|
-
if (validLength <= 0) break;
|
|
58
|
-
|
|
59
|
-
// 合并到Worklet缓存
|
|
60
|
-
const newCache = new Uint8Array(this.pcmCache.length + validLength);
|
|
61
|
-
newCache.set(this.pcmCache, 0);
|
|
62
|
-
newCache.set(data.pcmData.subarray(0, validLength), this.pcmCache.length);
|
|
63
|
-
this.pcmCache = newCache;
|
|
64
|
-
|
|
65
|
-
// 向主线程发送缓存状态
|
|
66
|
-
this.port.postMessage({
|
|
67
|
-
type: 'cacheStatus',
|
|
68
|
-
size: this.pcmCache.length,
|
|
69
|
-
sampleCount: this.pcmCache.length / this.config.bytesPerSample
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
break;
|
|
73
|
-
|
|
74
|
-
// 启动播放(继续音频生成)
|
|
75
|
-
case 'start':
|
|
76
|
-
this.isActive = true;
|
|
77
|
-
break;
|
|
78
|
-
|
|
79
|
-
// 暂停播放(停止音频生成,保留缓存)
|
|
80
|
-
case 'pause':
|
|
81
|
-
this.isActive = false;
|
|
82
|
-
this.resetSegmentState();
|
|
83
|
-
break;
|
|
84
|
-
|
|
85
|
-
// 停止播放(清空缓存+重置状态)
|
|
86
|
-
case 'stop':
|
|
87
|
-
this.isActive = false;
|
|
88
|
-
this.pcmCache = new Uint8Array(0);
|
|
89
|
-
this.resetSegmentState();
|
|
90
|
-
break;
|
|
91
|
-
|
|
92
|
-
// 更新配置(如采样率/淡入淡出时间)
|
|
93
|
-
case 'config':
|
|
94
|
-
if (data.config.sampleRate) {
|
|
95
|
-
this.config.sampleRate = data.config.sampleRate;
|
|
96
|
-
// 重新计算淡入淡出样本数
|
|
97
|
-
this.fadeState.fadeSamples = Math.floor(this.fadeState.fadeTime * this.config.sampleRate);
|
|
98
|
-
}
|
|
99
|
-
if (data.config.fadeTime) {
|
|
100
|
-
this.fadeState.fadeTime = data.config.fadeTime;
|
|
101
|
-
this.fadeState.fadeSamples = Math.floor(this.fadeState.fadeTime * this.config.sampleRate);
|
|
102
|
-
}
|
|
103
|
-
break;
|
|
104
|
-
|
|
105
|
-
// 单独更新音量(可选,也可通过参数自动化控制)
|
|
106
|
-
case 'volume':
|
|
107
|
-
this.volume = Number(data.volume);
|
|
108
|
-
// 通知主线程同步AudioWorkletNode的parameters
|
|
109
|
-
this.port.postMessage({
|
|
110
|
-
type: 'syncVolume',
|
|
111
|
-
volume: this.volume
|
|
112
|
-
});
|
|
113
|
-
break;
|
|
114
|
-
|
|
115
|
-
// 销毁处理器
|
|
116
|
-
case 'destroy':
|
|
117
|
-
this.isDestroyed = true;
|
|
118
|
-
this.isActive = false;
|
|
119
|
-
this.pcmCache = new Uint8Array(0);
|
|
120
|
-
this.resetSegmentState();
|
|
121
|
-
this.port.postMessage({ type: 'destroyConfirmed' });
|
|
122
|
-
break;
|
|
123
|
-
|
|
124
|
-
default:
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* 重置当前音频段状态(暂停/停止时调用)
|
|
130
|
-
*/
|
|
131
|
-
resetSegmentState() {
|
|
132
|
-
this.currentSegment = null;
|
|
133
|
-
this.segmentOffset = 0;
|
|
134
|
-
this.fadeState.phase = 'idle';
|
|
135
|
-
this.fadeState.currentGain = 0;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* S16LE Uint8Array 转 Float32Array(-1~1范围)
|
|
140
|
-
* @param {Uint8Array} pcmData - PCM数据
|
|
141
|
-
* @returns {Float32Array} 转换后的音频样本
|
|
142
|
-
*/
|
|
143
|
-
pcmToFloat32(pcmData) {
|
|
144
|
-
const sampleCount = pcmData.length / this.config.bytesPerSample;
|
|
145
|
-
const floatData = new Float32Array(sampleCount);
|
|
146
|
-
const dataView = new DataView(pcmData.buffer);
|
|
147
|
-
|
|
148
|
-
for (let i = 0; i < sampleCount; i++) {
|
|
149
|
-
// S16LE格式:小端序解析,范围-32768~32767 归一化到-1~1
|
|
150
|
-
floatData[i] = dataView.getInt16(i * this.config.bytesPerSample, true) / 32768;
|
|
151
|
-
}
|
|
152
|
-
return floatData;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* 计算当前样本的增益(淡入淡出)
|
|
157
|
-
* @param {number} segmentLength - 当前音频段的总样本数
|
|
158
|
-
* @returns {number} 增益值(0~1)
|
|
159
|
-
*/
|
|
160
|
-
calculateGain(segmentLength) {
|
|
161
|
-
const { phase, fadeSamples } = this.fadeState;
|
|
162
|
-
const { segmentOffset } = this;
|
|
163
|
-
|
|
164
|
-
// 边界保护:防止无效值导致的NaN
|
|
165
|
-
if (typeof segmentOffset !== 'number' || typeof segmentLength !== 'number' || segmentLength <= 0) {
|
|
166
|
-
return 0;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
switch (phase) {
|
|
170
|
-
// 淡入阶段:从0线性增加到1
|
|
171
|
-
case 'fadeIn':
|
|
172
|
-
if (segmentOffset >= fadeSamples) {
|
|
173
|
-
this.fadeState.phase = 'steady';
|
|
174
|
-
return 1;
|
|
175
|
-
}
|
|
176
|
-
return segmentOffset / fadeSamples;
|
|
177
|
-
|
|
178
|
-
// 稳定阶段:增益保持1
|
|
179
|
-
case 'steady':
|
|
180
|
-
// 检测是否进入淡出阶段(接近段尾)
|
|
181
|
-
if (segmentOffset >= segmentLength - fadeSamples) {
|
|
182
|
-
this.fadeState.phase = 'fadeOut';
|
|
183
|
-
}
|
|
184
|
-
return 1;
|
|
185
|
-
|
|
186
|
-
// 淡出阶段:从1线性降低到0.0001(避免分音)
|
|
187
|
-
case 'fadeOut':
|
|
188
|
-
const fadeStart = Math.max(segmentLength - fadeSamples, 0);
|
|
189
|
-
const fadeProgress = (segmentOffset - fadeStart) / fadeSamples;
|
|
190
|
-
return Math.max(1 - fadeProgress, 0.0001);
|
|
191
|
-
|
|
192
|
-
// 空闲阶段:增益0
|
|
193
|
-
case 'idle':
|
|
194
|
-
default:
|
|
195
|
-
return 0;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* AudioWorklet核心方法:实时生成音频样本(音频线程调用)
|
|
201
|
-
* @param {Float32Array[][]} inputs - 输入样本
|
|
202
|
-
* @param {Float32Array[][]} outputs - 输出样本(单声道)
|
|
203
|
-
* @param {Object} parameters - 音频参数
|
|
204
|
-
* @returns {boolean} 是否继续运行
|
|
205
|
-
*/
|
|
206
|
-
process(inputs, outputs, parameters) {
|
|
207
|
-
// 销毁状态:终止处理
|
|
208
|
-
if (this.isDestroyed) {
|
|
209
|
-
if (outputs.length > 0 && outputs[0].length > 0) {
|
|
210
|
-
outputs[0][0].fill(0);
|
|
211
|
-
}
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// 未激活或无输出:返回静音
|
|
216
|
-
if (!this.isActive || outputs.length === 0 || outputs[0].length === 0) {
|
|
217
|
-
outputs[0][0].fill(0);
|
|
218
|
-
return true;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const outputBuffer = outputs[0][0];
|
|
222
|
-
const bufferLength = outputBuffer.length;
|
|
223
|
-
// 获取音量参数(支持自动化)
|
|
224
|
-
const volume = parameters.volume.length > 1
|
|
225
|
-
? parameters.volume // 自动化模式:每个样本点可能有不同值
|
|
226
|
-
: parameters.volume[0]; // 控制模式:整个缓冲区使用相同值
|
|
227
|
-
|
|
228
|
-
// 生成输出样本
|
|
229
|
-
for (let i = 0; i < bufferLength; i++) {
|
|
230
|
-
// 如当前无音频段且缓存有数据,创建新段
|
|
231
|
-
if (!this.currentSegment && this.pcmCache.length > 0) {
|
|
232
|
-
// 使用缓存中的所有数据创建新段
|
|
233
|
-
this.currentSegment = this.pcmToFloat32(this.pcmCache);
|
|
234
|
-
// 清空缓存(已处理)
|
|
235
|
-
this.pcmCache = new Uint8Array(0);
|
|
236
|
-
this.segmentOffset = 0;
|
|
237
|
-
// 根据前一段状态决定是否淡入
|
|
238
|
-
this.fadeState.phase = this.fadeState.phase === 'fadeOut' ? 'steady' : 'fadeIn';
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// 生成当前样本
|
|
242
|
-
let sample = 0;
|
|
243
|
-
if (this.currentSegment) {
|
|
244
|
-
const segmentLength = this.currentSegment.length;
|
|
245
|
-
// 确保偏移量有效
|
|
246
|
-
if (this.segmentOffset < segmentLength) {
|
|
247
|
-
const gain = this.calculateGain(segmentLength);
|
|
248
|
-
// 应用淡入淡出增益和音量控制
|
|
249
|
-
sample = this.currentSegment[this.segmentOffset] * gain * volume;
|
|
250
|
-
this.segmentOffset++;
|
|
251
|
-
} else {
|
|
252
|
-
// 当前段处理完毕,重置
|
|
253
|
-
this.currentSegment = null;
|
|
254
|
-
this.segmentOffset = 0;
|
|
255
|
-
this.fadeState.phase = 'idle';
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
outputBuffer[i] = sample;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return true;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// 注册Worklet处理器
|
|
267
|
-
registerProcessor('pcm-audio-processor', PCMAudioProcessor);
|
|
@@ -1,67 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,372 +0,0 @@
|
|
|
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
|
-
}
|