@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,254 +0,0 @@
|
|
|
1
|
-
export default class MediaSourceAudioPlayer {
|
|
2
|
-
private mediaSource: MediaSource | null = null;
|
|
3
|
-
private audioElement: HTMLAudioElement | null = null;
|
|
4
|
-
private sourceBuffer: SourceBuffer | null = null;
|
|
5
|
-
public isInitialized = false;
|
|
6
|
-
public isPlaying = false;
|
|
7
|
-
private volume = 1.0;
|
|
8
|
-
private readonly MIME_TYPE = 'audio/webm;codecs=opus';
|
|
9
|
-
private queue: Array<{ data: ArrayBuffer }> = [];
|
|
10
|
-
private isUpdating = false;
|
|
11
|
-
private currentObjectURL: string | null = null;
|
|
12
|
-
private isDestroyed = false;
|
|
13
|
-
|
|
14
|
-
// 速度同步相关(保留不动)
|
|
15
|
-
private playbackRate = 1.0;
|
|
16
|
-
public firstFrameIndex = -1;
|
|
17
|
-
|
|
18
|
-
// 单例
|
|
19
|
-
private static instance: MediaSourceAudioPlayer;
|
|
20
|
-
public static getInstance(sdk): MediaSourceAudioPlayer {
|
|
21
|
-
if (!MediaSourceAudioPlayer.instance) {
|
|
22
|
-
MediaSourceAudioPlayer.instance = new MediaSourceAudioPlayer(sdk);
|
|
23
|
-
}
|
|
24
|
-
return MediaSourceAudioPlayer.instance;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
private constructor(sdk) {
|
|
28
|
-
this.createAudioElement(sdk);
|
|
29
|
-
this.createNewMediaSource();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* 只复用 audio 标签!!!
|
|
34
|
-
*/
|
|
35
|
-
private createAudioElement(sdk) {
|
|
36
|
-
if (this.audioElement) return;
|
|
37
|
-
const el = document.getElementById("xmov-avatar-audio");
|
|
38
|
-
if (el) {
|
|
39
|
-
this.audioElement = el as HTMLAudioElement;
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
const audio = document.createElement("audio");
|
|
43
|
-
audio.id = "xmov-avatar-audio";
|
|
44
|
-
audio.preload = "auto";
|
|
45
|
-
audio.style.position = "absolute";
|
|
46
|
-
audio.style.width = "1px";
|
|
47
|
-
audio.style.height = "1px";
|
|
48
|
-
audio.style.opacity = "0";
|
|
49
|
-
audio.style.pointerEvents = "none";
|
|
50
|
-
audio.style.zIndex = "-9999";
|
|
51
|
-
sdk.el.appendChild(audio);
|
|
52
|
-
this.audioElement = audio;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* 每次都创建 NEW MediaSource
|
|
57
|
-
* 不复用!不复用!不复用!
|
|
58
|
-
*/
|
|
59
|
-
private createNewMediaSource(): Promise<void> {
|
|
60
|
-
return new Promise((resolve, reject) => {
|
|
61
|
-
try {
|
|
62
|
-
if(!this.audioElement) return;
|
|
63
|
-
this.mediaSource = new MediaSource();
|
|
64
|
-
this.currentObjectURL = URL.createObjectURL(this.mediaSource);
|
|
65
|
-
this.audioElement!.src = this.currentObjectURL;
|
|
66
|
-
|
|
67
|
-
this.mediaSource.addEventListener("sourceopen", () => {
|
|
68
|
-
try {
|
|
69
|
-
if (!this.mediaSource) return;
|
|
70
|
-
this.sourceBuffer = this.mediaSource.addSourceBuffer(this.MIME_TYPE);
|
|
71
|
-
this.sourceBuffer.mode = "sequence";
|
|
72
|
-
|
|
73
|
-
this.sourceBuffer.addEventListener("updateend", () => {
|
|
74
|
-
this.isUpdating = false;
|
|
75
|
-
this.processQueue();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
this.isInitialized = true;
|
|
79
|
-
resolve();
|
|
80
|
-
} catch (e) {
|
|
81
|
-
reject(e);
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
this.mediaSource.addEventListener("sourceclose", () => {
|
|
86
|
-
this.isInitialized = false;
|
|
87
|
-
});
|
|
88
|
-
} catch (e) {
|
|
89
|
-
reject(e);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* 销毁当前 MediaSource 所有资源
|
|
96
|
-
* stop 时调用
|
|
97
|
-
*/
|
|
98
|
-
private async destroyCurrentMediaSource(callback: () => void) {
|
|
99
|
-
try {
|
|
100
|
-
if (this.audioElement) {
|
|
101
|
-
this.audioElement.pause();
|
|
102
|
-
this.audioElement.removeAttribute("src");
|
|
103
|
-
this.audioElement.load();
|
|
104
|
-
}
|
|
105
|
-
} catch {}
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
if (this.currentObjectURL) {
|
|
109
|
-
URL.revokeObjectURL(this.currentObjectURL);
|
|
110
|
-
this.currentObjectURL = null;
|
|
111
|
-
}
|
|
112
|
-
} catch {}
|
|
113
|
-
|
|
114
|
-
this.mediaSource = null;
|
|
115
|
-
this.sourceBuffer = null;
|
|
116
|
-
this.isInitialized = false;
|
|
117
|
-
this.isPlaying = false;
|
|
118
|
-
this.queue = [];
|
|
119
|
-
this.isUpdating = false;
|
|
120
|
-
await this.createNewMediaSource();
|
|
121
|
-
callback();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ==========================
|
|
125
|
-
// 核心:stop 清空一切,只留 audio
|
|
126
|
-
// ==========================
|
|
127
|
-
public stop(callback: () => void) {
|
|
128
|
-
this.destroyCurrentMediaSource(callback);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ==========================
|
|
132
|
-
// 添加数据(自动初始化)
|
|
133
|
-
// ==========================
|
|
134
|
-
async addAudioSegment(audioData: ArrayBuffer): Promise<void> {
|
|
135
|
-
if (this.isDestroyed) return;
|
|
136
|
-
this.queue.push({ data: audioData });
|
|
137
|
-
this.processQueue();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
private processQueue() {
|
|
141
|
-
if (this.isDestroyed) return;
|
|
142
|
-
if (!this.sourceBuffer || !this.mediaSource) return;
|
|
143
|
-
if (this.isUpdating || this.queue.length === 0) return;
|
|
144
|
-
if (this.sourceBuffer.updating || this.mediaSource.readyState !== "open") return;
|
|
145
|
-
|
|
146
|
-
this.isUpdating = true;
|
|
147
|
-
const item = this.queue.shift()!;
|
|
148
|
-
try {
|
|
149
|
-
this.sourceBuffer.appendBuffer(item.data);
|
|
150
|
-
} catch (e) {
|
|
151
|
-
this.isUpdating = false;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// ==========================
|
|
156
|
-
// 播放
|
|
157
|
-
// ==========================
|
|
158
|
-
async start(): Promise<void> {
|
|
159
|
-
if (this.isDestroyed || !this.audioElement || this.isPlaying) return;
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
this.audioElement.currentTime = 0;
|
|
163
|
-
this.isPlaying = true;
|
|
164
|
-
await this.audioElement.play();
|
|
165
|
-
(window as any).avatarSDKLogger.log("mseAudioPlayer开始播放", this.getStats(), Date.now());
|
|
166
|
-
} catch {}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
getStats() {
|
|
170
|
-
return {
|
|
171
|
-
isPlaying: this.isPlaying,
|
|
172
|
-
isInitialized: this.isInitialized,
|
|
173
|
-
queueLength: this.queue.length,
|
|
174
|
-
bufferedRanges: this.getBufferedRanges(),
|
|
175
|
-
mediaSourceReadyState: this.mediaSource?.readyState || 'null',
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
setSpeed(speed: number) {
|
|
180
|
-
this.setPlaybackRate(speed);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* 获取缓冲范围
|
|
185
|
-
*/
|
|
186
|
-
private getBufferedRanges(): string {
|
|
187
|
-
try {
|
|
188
|
-
// stop/destroy 流程中浏览器会先移除 SourceBuffer 再置空引用,
|
|
189
|
-
// 此时访问 .buffered 会抛 InvalidStateError,需要 readyState 校验 + 兜底
|
|
190
|
-
if (
|
|
191
|
-
this.sourceBuffer &&
|
|
192
|
-
this.mediaSource?.readyState === "open" &&
|
|
193
|
-
this.sourceBuffer.buffered.length > 0
|
|
194
|
-
) {
|
|
195
|
-
const ranges: string[] = [];
|
|
196
|
-
for (let i = 0; i < this.sourceBuffer.buffered.length; i++) {
|
|
197
|
-
const start = this.sourceBuffer.buffered.start(i).toFixed(2);
|
|
198
|
-
const end = this.sourceBuffer.buffered.end(i).toFixed(2);
|
|
199
|
-
ranges.push(`[${start}-${end}]`);
|
|
200
|
-
}
|
|
201
|
-
return ranges.join(", ");
|
|
202
|
-
}
|
|
203
|
-
} catch (e) {
|
|
204
|
-
// SourceBuffer 已被移除(stop/destroy 竞态),视为无缓冲数据
|
|
205
|
-
return "-";
|
|
206
|
-
}
|
|
207
|
-
return "-";
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
setVolume(volume: number): void {
|
|
211
|
-
this.volume = Math.max(0, Math.min(1, volume));
|
|
212
|
-
if (this.audioElement) this.audioElement.volume = this.volume;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
setPlaybackRate(rate: number): void {
|
|
216
|
-
this.playbackRate = Math.max(0.5, Math.min(4.0, rate));
|
|
217
|
-
if (this.audioElement) this.audioElement.playbackRate = this.playbackRate;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
destroy() {
|
|
221
|
-
// 0. 标记已销毁,阻止异步操作继续执行
|
|
222
|
-
this.isDestroyed = true;
|
|
223
|
-
|
|
224
|
-
// 1. 停止播放并清除 audio 元素的 src(解除对 blob URL 的引用)
|
|
225
|
-
if (this.audioElement) {
|
|
226
|
-
this.audioElement.pause();
|
|
227
|
-
this.audioElement.removeAttribute("src");
|
|
228
|
-
this.audioElement.load();
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// 2. 释放 blob URL(必须在 audio 元素清除 src 之后)
|
|
232
|
-
if (this.currentObjectURL) {
|
|
233
|
-
try { URL.revokeObjectURL(this.currentObjectURL); } catch {}
|
|
234
|
-
this.currentObjectURL = null;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// 3. 移除 audio 元素
|
|
238
|
-
if (this.audioElement) {
|
|
239
|
-
this.audioElement.remove();
|
|
240
|
-
this.audioElement = null;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// 4. 重置所有状态
|
|
244
|
-
this.mediaSource = null;
|
|
245
|
-
this.sourceBuffer = null;
|
|
246
|
-
this.isInitialized = false;
|
|
247
|
-
this.isPlaying = false;
|
|
248
|
-
this.queue = [];
|
|
249
|
-
this.isUpdating = false;
|
|
250
|
-
|
|
251
|
-
// 5. 重置单例,下次 getInstance() 会重新创建
|
|
252
|
-
MediaSourceAudioPlayer.instance = null as any;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UI 控件渲染,所有轨道的元素
|
|
3
|
-
*/
|
|
4
|
-
import { DataCacheQueue } from "../control/DataCacheQueue";
|
|
5
|
-
import DefaultWidgetRenderer from "../modules/TrackRenderer/render-implements";
|
|
6
|
-
import TrackerRenderer from "../modules/TrackRenderer/index";
|
|
7
|
-
import ResourceManager from "../modules/ResourceManager";
|
|
8
|
-
import { performanceConstant } from "../utils/perfermance";
|
|
9
|
-
import XmovAvatar from "../index";
|
|
10
|
-
import { EErrorCode } from "../types/error";
|
|
11
|
-
|
|
12
|
-
type Option = {
|
|
13
|
-
sdk: XmovAvatar;
|
|
14
|
-
dataCacheQueue: DataCacheQueue;
|
|
15
|
-
resourceManager: ResourceManager;
|
|
16
|
-
enableClientInterrupt: boolean;
|
|
17
|
-
onVoiceEnd: (client_speak_id: number | string, speech_id: number) => void;
|
|
18
|
-
onVoiceStart: (duration: number, client_speak_id: number | string) => void;
|
|
19
|
-
onWalkStateChange: (state: string) => void;
|
|
20
|
-
sendSdkPoint: (type: string, data: any, extra?: any) => void;
|
|
21
|
-
onSpeakStateChange: (state: string, client_speak_id: number | string, speech_id: number) => void;
|
|
22
|
-
};
|
|
23
|
-
export default class UIRenderer {
|
|
24
|
-
private TAG = "[UIRenderer]";
|
|
25
|
-
options: Option;
|
|
26
|
-
trackerRenderer: TrackerRenderer[] = [];
|
|
27
|
-
root = document.createElement("div");
|
|
28
|
-
lastFrameIndex: number = -1;
|
|
29
|
-
onVoiceEnd: (client_speak_id: number | string, speech_id: number) => void;
|
|
30
|
-
onVoiceStart: (duration: number, client_speak_id: number | string) => void;
|
|
31
|
-
onWalkStateChange: (state: string) => void;
|
|
32
|
-
initEventsRendered: boolean = false;
|
|
33
|
-
lastSpeechId: number = -1;
|
|
34
|
-
interruptSpeechId: number = -1;
|
|
35
|
-
enableClientInterrupt: boolean = false;
|
|
36
|
-
onSpeakStateChange: (state: string, client_speak_id: number | string, speech_id: number) => void;
|
|
37
|
-
constructor(options: Option) {
|
|
38
|
-
this.options = options;
|
|
39
|
-
this.root.setAttribute("style", "position:absolute;");
|
|
40
|
-
this.onVoiceStart = options.onVoiceStart;
|
|
41
|
-
this.onVoiceEnd = options.onVoiceEnd;
|
|
42
|
-
this.initEventsRendered = false;
|
|
43
|
-
this.onWalkStateChange = options.onWalkStateChange;
|
|
44
|
-
this.enableClientInterrupt = options.enableClientInterrupt;
|
|
45
|
-
this.onSpeakStateChange = options.onSpeakStateChange;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
render(frame: number) {
|
|
49
|
-
// if(this.options.sdk.getStatus() === AvatarStatus.offline) return;
|
|
50
|
-
let initEvents: any[] = [];
|
|
51
|
-
if (this.lastFrameIndex === -1) {
|
|
52
|
-
const config = this.options.resourceManager.getConfig();
|
|
53
|
-
if(config.init_events && config.init_events.length > 0){
|
|
54
|
-
initEvents = config.init_events.map(item => ({...item, speech_id:0}));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
let event: any = null;
|
|
58
|
-
if (frame - this.lastFrameIndex > 1) {
|
|
59
|
-
event = this.options.dataCacheQueue._getEventInterval(
|
|
60
|
-
this.lastFrameIndex,
|
|
61
|
-
frame
|
|
62
|
-
);
|
|
63
|
-
} else {
|
|
64
|
-
event = this.options.dataCacheQueue._getEvent(frame);
|
|
65
|
-
}
|
|
66
|
-
this.lastFrameIndex = frame;
|
|
67
|
-
// 仅执行一次
|
|
68
|
-
if(!this.initEventsRendered){
|
|
69
|
-
this.initEventsRendered = true;
|
|
70
|
-
if(event){
|
|
71
|
-
event.e = event.e.concat(initEvents);
|
|
72
|
-
} else {
|
|
73
|
-
event = {
|
|
74
|
-
e: initEvents,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (!event?.e) {
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
(window as any).avatarSDKLogger.log("当前帧", frame, "获取到的事件", JSON.stringify(event));
|
|
82
|
-
// 过滤出不包含speechId的事件list
|
|
83
|
-
const noSpeechIdEvents = event.e?.filter(item => item?.speech_id === undefined || item?.speech_id === null);
|
|
84
|
-
if(noSpeechIdEvents?.length) {
|
|
85
|
-
const typesStr = noSpeechIdEvents.map(item => item.type).join(',');
|
|
86
|
-
this.options.sdk.onMessage({
|
|
87
|
-
code: EErrorCode.EVENT_WITHOUT_SPEECH_ID,
|
|
88
|
-
message: `Error: 事件中存在不包含speech_id的事件${typesStr}`,
|
|
89
|
-
e: noSpeechIdEvents
|
|
90
|
-
})
|
|
91
|
-
}
|
|
92
|
-
if(this.enableClientInterrupt) {
|
|
93
|
-
// 根据interruptSpeechId过滤旧的数据,加入前端interrupt事件后,可能在前端打断后,服务仍下发数据 如果type是以_end结尾的,不过滤
|
|
94
|
-
event.e = event.e?.filter(item => (item.speech_id > this.interruptSpeechId || item.speech_id === undefined || item.speech_id === null) || item.type.endsWith('_end') || item.type === 'subtitle_off');
|
|
95
|
-
}
|
|
96
|
-
// 过滤出当前语音的事件
|
|
97
|
-
if(this.lastSpeechId !== -1 && this.lastSpeechId) {
|
|
98
|
-
event.e = event.e?.filter(item => item?.speech_id >= this.lastSpeechId);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// 1. 处理 speak_start
|
|
102
|
-
if (event.e?.some(item => item.type === "speak_start")) {
|
|
103
|
-
const speakStart = event.e.find(item => item.type === "speak_start");
|
|
104
|
-
this.lastSpeechId = speakStart?.speech_id;
|
|
105
|
-
this.onSpeakStateChange("speak_start", speakStart.client_speak_id, speakStart?.speech_id || 0);
|
|
106
|
-
}
|
|
107
|
-
// 2. 处理 walk_start / voice_start / speak_walk_start
|
|
108
|
-
if (event.e?.some(item => item.type === "walk_start") || event.e?.some(item => item.type === "speak_walk_start")) {
|
|
109
|
-
this.onWalkStateChange("walk_start");
|
|
110
|
-
}
|
|
111
|
-
if(event.e?.some(item => item.type === "voice_start")) {
|
|
112
|
-
const voiceStart = event.e.find(item => item.type === "voice_start");
|
|
113
|
-
const duration = (window as any).performanceTracker.markEnd(performanceConstant.voice_response_play, 'speak');
|
|
114
|
-
this.options.sendSdkPoint('rendering_display', {multi_turn_conversation_id: voiceStart?.multi_turn_conversation_id});
|
|
115
|
-
this.onVoiceStart(duration, voiceStart?.client_speak_id);
|
|
116
|
-
}
|
|
117
|
-
// 3. 处理 walk_end / speak_walk_end / voice_end
|
|
118
|
-
if (event.e?.some(item => item.type === "walk_end") || event.e?.some(item => item.type === "speak_walk_end")) {
|
|
119
|
-
this.onWalkStateChange("walk_end");
|
|
120
|
-
}
|
|
121
|
-
if (event.e?.some(item => item.type === "voice_end")) {
|
|
122
|
-
const voiceEnd = event.e.find(item => item.type === "voice_end");
|
|
123
|
-
this.onVoiceEnd(voiceEnd?.client_speak_id || '', voiceEnd?.speech_id || 0);
|
|
124
|
-
}
|
|
125
|
-
// 4. 处理 speak_end / speak_error
|
|
126
|
-
const speakEndEventTypes = ["speak_end", "speak_error"];
|
|
127
|
-
speakEndEventTypes.forEach(eventType => {
|
|
128
|
-
const speakEvent = event.e.find(item => item.type === eventType);
|
|
129
|
-
if (speakEvent) {
|
|
130
|
-
this.onSpeakStateChange(eventType, speakEvent.client_speak_id, speakEvent?.speech_id || 0);
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const remainingWidgets = [...event.e];
|
|
136
|
-
|
|
137
|
-
// 循环遍历 event.e 数组中的所有元素
|
|
138
|
-
for (let i = 0; i < event.e.length; i++) {
|
|
139
|
-
const widgetData = event.e[i];
|
|
140
|
-
if (Object.prototype.hasOwnProperty.call(widgetData, "event_type")) {
|
|
141
|
-
// 先不处理 ka/ka_intent
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
// 如果有自定义的渲染器,则所有事件都交给自定义的渲染器处理
|
|
145
|
-
if (DefaultWidgetRenderer.CUSTOM_WIDGET) {
|
|
146
|
-
DefaultWidgetRenderer.CUSTOM_WIDGET(widgetData);
|
|
147
|
-
// 从剩余元素中删除已处理的元素
|
|
148
|
-
const index = remainingWidgets.indexOf(widgetData);
|
|
149
|
-
if (index > -1) {
|
|
150
|
-
remainingWidgets.splice(index, 1);
|
|
151
|
-
}
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
// 如果有代理的渲染器,则根据类型执行代理的渲染器
|
|
155
|
-
if (DefaultWidgetRenderer.PROXY_WIDGET && DefaultWidgetRenderer.PROXY_WIDGET[widgetData.type]) {
|
|
156
|
-
DefaultWidgetRenderer.PROXY_WIDGET[widgetData.type](widgetData);
|
|
157
|
-
|
|
158
|
-
// 从剩余元素中删除已处理的元素
|
|
159
|
-
const index = remainingWidgets.indexOf(widgetData);
|
|
160
|
-
if (index > -1) {
|
|
161
|
-
remainingWidgets.splice(index, 1);
|
|
162
|
-
}
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
// 对剩余未自定义的元素执行 TrackerRenderer
|
|
167
|
-
if (remainingWidgets.length > 0) {
|
|
168
|
-
// 遍历剩余的元素,每个单独执行 TrackerRenderer
|
|
169
|
-
// 传递 SDK 实例引用,用于区分不同数字人实例的 widget
|
|
170
|
-
for (const widgetData of remainingWidgets) {
|
|
171
|
-
const tracker = new TrackerRenderer(widgetData, this.options.sdk);
|
|
172
|
-
this.trackerRenderer.push(tracker);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
return this.trackerRenderer;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
clearTrackerRenderer() {
|
|
179
|
-
this.trackerRenderer = [];
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
setInterrupt(speech_id: number) {
|
|
183
|
-
this.interruptSpeechId = speech_id;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
destroy() {
|
|
187
|
-
this.lastSpeechId = -1;
|
|
188
|
-
this.interruptSpeechId = -1;
|
|
189
|
-
this.lastFrameIndex = -1;
|
|
190
|
-
this.initEventsRendered = false;
|
|
191
|
-
// 传递 SDK 实例引用,只清除当前实例的 widget
|
|
192
|
-
DefaultWidgetRenderer.destroy(this.options.sdk);
|
|
193
|
-
this.trackerRenderer.forEach((tracker) => tracker.destroy());
|
|
194
|
-
}
|
|
195
|
-
}
|