@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,1000 +0,0 @@
|
|
|
1
|
-
import { DataCacheQueue } from "./DataCacheQueue";
|
|
2
|
-
import AvatarRenderer from "../baseRender/AvatarRenderer";
|
|
3
|
-
import UIRenderer from "../baseRender/UIRenderer";
|
|
4
|
-
import Composition from "../modules/Composition";
|
|
5
|
-
import ResourceManager from "../modules/ResourceManager";
|
|
6
|
-
import {
|
|
7
|
-
EFrameDataType,
|
|
8
|
-
IRawFrameData,
|
|
9
|
-
IRawBodyFrameData,
|
|
10
|
-
IRawEventFrameData,
|
|
11
|
-
IRawFaceFrameData,
|
|
12
|
-
IRawAudioFrameData,
|
|
13
|
-
ITtsFaceFrameData,
|
|
14
|
-
StateChangeInfo,
|
|
15
|
-
SYNC_FRAME_DATA,
|
|
16
|
-
IRawWidgetData,
|
|
17
|
-
FRAME_SOURCE_SOURCE,
|
|
18
|
-
} from "../types/frame-data";
|
|
19
|
-
import ParallelDecoder from "../modules/decoder";
|
|
20
|
-
import AnimationController from "../utils/requestAnimateFrames";
|
|
21
|
-
import { EErrorCode } from "../types/error";
|
|
22
|
-
import { formatMJT } from "../utils/DataInterface";
|
|
23
|
-
import AudioRenderer from "../baseRender/AudioRenderer";
|
|
24
|
-
import type XmovAvatar from "../index";
|
|
25
|
-
import SaveAndDownload from "./SaveAndDownload";
|
|
26
|
-
import { RenderState, WalkConfig, IFrameDataUpdate } from "../types/index";
|
|
27
|
-
import { Layout } from "../types";
|
|
28
|
-
import WidgetDefaultRender from "../modules/TrackRenderer/render-implements";
|
|
29
|
-
import { performanceConstant } from "../utils/perfermance";
|
|
30
|
-
import { FPSStats } from "../utils/fpsTracker";
|
|
31
|
-
|
|
32
|
-
interface CONFIG {
|
|
33
|
-
sdkInstance: XmovAvatar;
|
|
34
|
-
container: Element;
|
|
35
|
-
hardwareAcceleration: string;
|
|
36
|
-
resourceManager: ResourceManager;
|
|
37
|
-
enableDebugger?: boolean;
|
|
38
|
-
enableClientInterrupt?: boolean;
|
|
39
|
-
audioOnlyMode?: boolean;
|
|
40
|
-
onDownloadProgress: (progress: number) => void;
|
|
41
|
-
onStateChange: (state: string) => void;
|
|
42
|
-
onSpeakStateChange: (state: string, client_speak_id: number | string) => void;
|
|
43
|
-
onRenderChange: (state: RenderState, oldState?: RenderState) => void;
|
|
44
|
-
onVoiceStateChange: (state: string, duration?: number, client_speak_id?: string | number) => void;
|
|
45
|
-
onWalkStateChange: (state: string) => void;
|
|
46
|
-
sendVideoInfo: (info: { name: string, body_id: number, id: number }) => void;
|
|
47
|
-
sendMessage: (type: string, payload: any) => void;
|
|
48
|
-
setAudioInfo: (info: {
|
|
49
|
-
sf: number;
|
|
50
|
-
ef: number;
|
|
51
|
-
ad: Uint8Array;
|
|
52
|
-
}) => void;
|
|
53
|
-
setEventData: (info: {
|
|
54
|
-
sf: number;
|
|
55
|
-
ef: number;
|
|
56
|
-
event: Array<any>;
|
|
57
|
-
}) => void;
|
|
58
|
-
renderFrameCallback: (frame: number) => void;
|
|
59
|
-
reportMessage: (message: {
|
|
60
|
-
code: EErrorCode;
|
|
61
|
-
message: string;
|
|
62
|
-
e?: object;
|
|
63
|
-
}) => void;
|
|
64
|
-
sendSdkPoint: (type: string, data: any, extra?: any) => void;
|
|
65
|
-
onFPSUpdate: (stats: FPSStats) => void;
|
|
66
|
-
onFrameDataUpdate?: (data: IFrameDataUpdate) => void;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export default class RenderScheduler {
|
|
70
|
-
private TAG = "[RenderScheduler]";
|
|
71
|
-
private dataCacheQueue: DataCacheQueue;
|
|
72
|
-
private sdk: XmovAvatar;
|
|
73
|
-
private config: CONFIG;
|
|
74
|
-
|
|
75
|
-
private avatarRenderer: AvatarRenderer;
|
|
76
|
-
private audioRenderer: AudioRenderer;
|
|
77
|
-
private uiRenderer: UIRenderer;
|
|
78
|
-
private composition: Composition;
|
|
79
|
-
|
|
80
|
-
private currentSpeechId: number = -1;
|
|
81
|
-
|
|
82
|
-
private resourceManager: ResourceManager;
|
|
83
|
-
private frameAnimationController: AnimationController | undefined;
|
|
84
|
-
private isStartPlay = false;
|
|
85
|
-
private renderState: RenderState = RenderState.init;
|
|
86
|
-
|
|
87
|
-
private onDownloadProgress: (progress: number) => void;
|
|
88
|
-
private onRenderChange: (state: RenderState) => void;
|
|
89
|
-
private decoder: ParallelDecoder;
|
|
90
|
-
private saveAndDownload: SaveAndDownload;
|
|
91
|
-
public lastSpeechId = -1;
|
|
92
|
-
private enableClientInterrupt: boolean = false;
|
|
93
|
-
private audioOnlyMode: boolean = false;
|
|
94
|
-
private _speedResetTimer: any = null;
|
|
95
|
-
private offsetFrame:number = 0;
|
|
96
|
-
private hasSendTimeJson: boolean = false;
|
|
97
|
-
private onFPSUpdate: (stats: FPSStats) => void;
|
|
98
|
-
|
|
99
|
-
private setAudioInfo: (info: {
|
|
100
|
-
sf: number;
|
|
101
|
-
ef: number;
|
|
102
|
-
ad: Uint8Array;
|
|
103
|
-
}) => void;
|
|
104
|
-
private setEventData: (info: {
|
|
105
|
-
sf: number;
|
|
106
|
-
ef: number;
|
|
107
|
-
event: Array<any>;
|
|
108
|
-
}) => void;
|
|
109
|
-
private reportMessage: (message: {
|
|
110
|
-
code: EErrorCode;
|
|
111
|
-
message: string;
|
|
112
|
-
e?: object;
|
|
113
|
-
}) => void;
|
|
114
|
-
private sendSdkPoint: (type: string, data: any, extra?: any) => void;
|
|
115
|
-
private sendMessage: (type: string, payload: any) => void;
|
|
116
|
-
constructor(config: CONFIG) {
|
|
117
|
-
this.config = config;
|
|
118
|
-
this.setAudioInfo = config.setAudioInfo;
|
|
119
|
-
this.setEventData = config.setEventData;
|
|
120
|
-
this.reportMessage = config.reportMessage;
|
|
121
|
-
this.sendSdkPoint = config.sendSdkPoint;
|
|
122
|
-
this.onRenderChange = config.onRenderChange;
|
|
123
|
-
this.sendMessage = config.sendMessage;
|
|
124
|
-
this.enableClientInterrupt = config.enableClientInterrupt || false;
|
|
125
|
-
this.audioOnlyMode = config.audioOnlyMode || false;
|
|
126
|
-
this.saveAndDownload = new SaveAndDownload(
|
|
127
|
-
"avatarData.js",
|
|
128
|
-
config.enableDebugger
|
|
129
|
-
);
|
|
130
|
-
this.resourceManager = config.resourceManager;
|
|
131
|
-
this.sdk = config.sdkInstance;
|
|
132
|
-
this.onDownloadProgress = config.onDownloadProgress;
|
|
133
|
-
this.onFPSUpdate = config.onFPSUpdate;
|
|
134
|
-
this.dataCacheQueue = new DataCacheQueue();
|
|
135
|
-
this.decoder = new ParallelDecoder({
|
|
136
|
-
hardwareAcceleration: config.hardwareAcceleration,
|
|
137
|
-
resourceManager: this.resourceManager,
|
|
138
|
-
saveAndDownload: this.saveAndDownload,
|
|
139
|
-
dataCacheQueue: this.dataCacheQueue,
|
|
140
|
-
reportMessage: this.reportMessage,
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
this.avatarRenderer = new AvatarRenderer({
|
|
144
|
-
dataCacheQueue: this.dataCacheQueue,
|
|
145
|
-
resourceManager: this.resourceManager,
|
|
146
|
-
saveAndDownload: this.saveAndDownload,
|
|
147
|
-
forceSyncDecoder: this.forceSyncDecoder.bind(this),
|
|
148
|
-
reportMessage: this.reportMessage,
|
|
149
|
-
sendSdkPoint: this.sendSdkPoint,
|
|
150
|
-
onDownloadProgress: (progress: number) => {
|
|
151
|
-
this.onDownloadProgress(progress);
|
|
152
|
-
},
|
|
153
|
-
onStateChange: (state: string) => {
|
|
154
|
-
config.onStateChange(state);
|
|
155
|
-
},
|
|
156
|
-
onRenderChange: (state: RenderState) => {
|
|
157
|
-
config.onRenderChange(state, this.renderState);
|
|
158
|
-
this.renderState = state;
|
|
159
|
-
},
|
|
160
|
-
sendVideoInfo: config.sendVideoInfo,
|
|
161
|
-
});
|
|
162
|
-
this.uiRenderer = new UIRenderer({
|
|
163
|
-
sdk: this.sdk,
|
|
164
|
-
dataCacheQueue: this.dataCacheQueue,
|
|
165
|
-
resourceManager: this.resourceManager,
|
|
166
|
-
enableClientInterrupt: this.enableClientInterrupt,
|
|
167
|
-
onWalkStateChange: (state: string) => {
|
|
168
|
-
config.onWalkStateChange(state);
|
|
169
|
-
},
|
|
170
|
-
onVoiceStart: (duration: number, client_speak_id: string | number) => {
|
|
171
|
-
config.onVoiceStateChange("start", duration, client_speak_id);
|
|
172
|
-
},
|
|
173
|
-
onVoiceEnd: (client_speak_id: string | number, speech_id: number) => {
|
|
174
|
-
this.stopAudio(speech_id);
|
|
175
|
-
config.onVoiceStateChange("end", undefined, client_speak_id);
|
|
176
|
-
},
|
|
177
|
-
onSpeakStateChange: (state: string, client_speak_id: string | number, speech_id: number) => {
|
|
178
|
-
if(state === "speak_start") {
|
|
179
|
-
this.currentSpeechId = speech_id;
|
|
180
|
-
this.avatarRenderer.setInterrupt(false);
|
|
181
|
-
} else if(state === "speak_end") {
|
|
182
|
-
this.dataCacheQueue.clearSubtitleOn(speech_id);
|
|
183
|
-
// 仅当speak_end属于当前或更新的轮次时才设置interrupt,避免旧轮次覆盖新轮次的表情状态
|
|
184
|
-
if (speech_id >= this.currentSpeechId) {
|
|
185
|
-
this.avatarRenderer.setInterrupt(true);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
config.onSpeakStateChange(state, client_speak_id);
|
|
189
|
-
},
|
|
190
|
-
sendSdkPoint: (type: string, data: any, extra?: any) => {
|
|
191
|
-
config.sendSdkPoint(type, data, extra);
|
|
192
|
-
},
|
|
193
|
-
});
|
|
194
|
-
this.audioRenderer = new AudioRenderer({
|
|
195
|
-
sdk: this.sdk,
|
|
196
|
-
dataCacheQueue: this.dataCacheQueue,
|
|
197
|
-
resourceManager: this.resourceManager,
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
this.composition = new Composition({
|
|
201
|
-
container: config.container,
|
|
202
|
-
avatarRenderer: this.avatarRenderer,
|
|
203
|
-
restRenderers: [this.uiRenderer],
|
|
204
|
-
audioRenderer: this.audioRenderer,
|
|
205
|
-
audioOnlyMode: this.audioOnlyMode,
|
|
206
|
-
onFPSUpdate: (stats: FPSStats) => {
|
|
207
|
-
this.onFPSUpdate(stats);
|
|
208
|
-
},
|
|
209
|
-
});
|
|
210
|
-
this.frameAnimationController = new AnimationController({
|
|
211
|
-
defaultSpeed: 1,
|
|
212
|
-
frameRate: 24,
|
|
213
|
-
frameCallback: (frame, timeJson) => {
|
|
214
|
-
// (window as any).avatarSDKLogger.log("计时器回调帧号", frame)
|
|
215
|
-
if(!this.hasSendTimeJson && timeJson) {
|
|
216
|
-
(window as any).avatarSDKLogger.error("当前帧日期与当前帧不一致:", timeJson)
|
|
217
|
-
// console.warn("当前帧日期与当前帧不一致:", timeJson)
|
|
218
|
-
this.sendMessage("client_error", timeJson);
|
|
219
|
-
this.hasSendTimeJson = true;
|
|
220
|
-
}
|
|
221
|
-
frame += this.offsetFrame;
|
|
222
|
-
config.renderFrameCallback(frame);
|
|
223
|
-
if (this.isStartPlay) {
|
|
224
|
-
this.composition.compose(frame);
|
|
225
|
-
this.decoder._tryStartNext();
|
|
226
|
-
// 帧数据回调
|
|
227
|
-
if (config.onFrameDataUpdate) {
|
|
228
|
-
const frameData = this.avatarRenderer._getCurrentFrameData(frame);
|
|
229
|
-
config.onFrameDataUpdate({
|
|
230
|
-
frameIndex: frame,
|
|
231
|
-
body: frameData.body,
|
|
232
|
-
face: frameData.face,
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
},
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
init() {
|
|
241
|
-
if (this.audioOnlyMode) {
|
|
242
|
-
return; // Skip WebGL initialization in audioOnlyMode
|
|
243
|
-
}
|
|
244
|
-
const mouthShapeLib = this.resourceManager.getMouthShapeLib();
|
|
245
|
-
this.saveAndDownload.writeFields({
|
|
246
|
-
resource_pack: this.resourceManager.resource_pack,
|
|
247
|
-
});
|
|
248
|
-
this.avatarRenderer.init({
|
|
249
|
-
char: mouthShapeLib.char_info,
|
|
250
|
-
LUT: null,
|
|
251
|
-
transform: {
|
|
252
|
-
offsetX: 0.0,
|
|
253
|
-
offsetY: 0.0,
|
|
254
|
-
scaleX: 1.0,
|
|
255
|
-
scaleY: 1.0
|
|
256
|
-
},
|
|
257
|
-
multisample: 4
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* 处理数据:拆解并添加到缓存队列中
|
|
263
|
-
* @param data TTSA 下发的数据
|
|
264
|
-
*/
|
|
265
|
-
async handleData(type: EFrameDataType, data: IRawFrameData[] | SYNC_FRAME_DATA) {
|
|
266
|
-
if (this.audioOnlyMode) {
|
|
267
|
-
if (type === EFrameDataType.BODY ||
|
|
268
|
-
type === EFrameDataType.FACE ||
|
|
269
|
-
type === EFrameDataType.AA_FRAME) {
|
|
270
|
-
return; // Skip video data in audioOnlyMode
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
const currentFrameIndex = this.getCurrentFrameIndex();
|
|
274
|
-
switch (type) {
|
|
275
|
-
case EFrameDataType.BODY: {
|
|
276
|
-
this.decoder.clearOldQueue(currentFrameIndex);
|
|
277
|
-
// 根据currentFrame查找sf>currentFrame的body数据
|
|
278
|
-
const currentFrame = currentFrameIndex;
|
|
279
|
-
let mp4List = ([...data as IRawFrameData[]] as IRawBodyFrameData[]).filter((item) => item.ef > currentFrame);
|
|
280
|
-
(window as any).avatarSDKLogger.log(mp4List,"mp4List")
|
|
281
|
-
if (mp4List.length > 0) {
|
|
282
|
-
// 在暂停状态(隐身模式)下,不检查数据是否过期
|
|
283
|
-
// 因为此时帧动画暂停但数据仍在接收,可能导致帧索引与数据起始帧不同步
|
|
284
|
-
if (this.renderState !== RenderState.paused && currentFrameIndex && currentFrameIndex > data[0].sf && currentFrameIndex > 10) {
|
|
285
|
-
// 判断非初始场景下,body数据下发过期的情况
|
|
286
|
-
this.sdk.onMessage({
|
|
287
|
-
code: EErrorCode.BODY_DATA_EXPIRED,
|
|
288
|
-
message: `Error: 身体数据过期 cur:${currentFrameIndex}-sf:${data[0].sf}`,
|
|
289
|
-
e: data
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
// 已经插入队列的移除掉
|
|
293
|
-
this.decoder.decode(
|
|
294
|
-
mp4List,
|
|
295
|
-
(file: any, frame: any, index: any) => {
|
|
296
|
-
// createImageBitmap(frame).then((rgbBitmap) => {
|
|
297
|
-
if (file.start <= index && file.end >= index) {
|
|
298
|
-
this.dataCacheQueue._updateBodyImageBitmap({
|
|
299
|
-
frame,
|
|
300
|
-
frameIndex: file.startFrameIndex + index,
|
|
301
|
-
frameState: file.frameState,
|
|
302
|
-
id: file.id,
|
|
303
|
-
body_id: file.body_id,
|
|
304
|
-
name: file.name,
|
|
305
|
-
hfd: file.hfd,
|
|
306
|
-
sf: file.startFrameIndex,
|
|
307
|
-
offset: file?.x_offset[index] || 0,
|
|
308
|
-
});
|
|
309
|
-
// 视频获取完成,防止后端下发实际帧数大于需要抽的帧数,避免多余抽帧
|
|
310
|
-
if (file.endFrameIndex <= file.startFrameIndex + index) {
|
|
311
|
-
this.decoder.abortOne(
|
|
312
|
-
`${file.body_id ?? file.id ?? 0}_${file.name}`
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
},
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
break;
|
|
320
|
-
}
|
|
321
|
-
case EFrameDataType.FACE:
|
|
322
|
-
this.dataCacheQueue.clearOldFaceData(currentFrameIndex);
|
|
323
|
-
const faceData = this.handleFaceData(data as ITtsFaceFrameData[]);
|
|
324
|
-
if (faceData.length > 0) {
|
|
325
|
-
this.saveAndDownload.appendMultipleToArray("faceData", faceData);
|
|
326
|
-
}
|
|
327
|
-
this.dataCacheQueue.checkValidData(
|
|
328
|
-
faceData,
|
|
329
|
-
type
|
|
330
|
-
);
|
|
331
|
-
// https://rsjqcmnt5p.feishu.cn/wiki/UiTUwZRKRiRGitkqDc4c78eUnye
|
|
332
|
-
// 遍历处理完成之后的faceData,根据face_frame_type区分原始数据和实时数据,保存到不同的队列内
|
|
333
|
-
let realFaceData: Array<IRawFaceFrameData> = [], nowFaceData: Array<IRawFaceFrameData> = [];
|
|
334
|
-
for (let i = 0; i < faceData.length; i++) {
|
|
335
|
-
// face_frame_type 1 实时数据
|
|
336
|
-
if (!faceData[i]?.face_frame_type) {
|
|
337
|
-
realFaceData.push(faceData[i])
|
|
338
|
-
} else {
|
|
339
|
-
nowFaceData.push(faceData[i]);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
// 分类完成后一次性入队,避免循环内对累加数组重复 push 导致队列数据重复
|
|
343
|
-
nowFaceData.length && this.dataCacheQueue._updateFacial(nowFaceData);
|
|
344
|
-
realFaceData.length && this.dataCacheQueue._updateRealFacial(realFaceData);
|
|
345
|
-
break;
|
|
346
|
-
case EFrameDataType.AUDIO:
|
|
347
|
-
const audioData = data as IRawAudioFrameData[];
|
|
348
|
-
this.setAudioInfo({
|
|
349
|
-
sf: audioData[0].sf,
|
|
350
|
-
ef: audioData[0].ef,
|
|
351
|
-
ad: audioData[0].ad,
|
|
352
|
-
});
|
|
353
|
-
// 在暂停状态(隐身模式)下,不检查音频数据是否过期
|
|
354
|
-
// 因为此时帧动画暂停但数据仍在接收,可能导致帧索引与数据起始帧不同步
|
|
355
|
-
if (this.renderState !== RenderState.paused && currentFrameIndex > data[0].sf) {
|
|
356
|
-
const currentFrame = currentFrameIndex;
|
|
357
|
-
const delta = currentFrame ? currentFrame - data[0].sf : 0;
|
|
358
|
-
this.reportMessage({
|
|
359
|
-
code: EErrorCode.AUDIO_DATA_EXPIRED,
|
|
360
|
-
message: `Error: 音频数据过期 cur:${currentFrameIndex}-sf:${data[0].sf}`,
|
|
361
|
-
e: {
|
|
362
|
-
currentFrame: currentFrameIndex,
|
|
363
|
-
sf: data[0].sf,
|
|
364
|
-
ef: delta,
|
|
365
|
-
timestamp: Date.now(),
|
|
366
|
-
}
|
|
367
|
-
})
|
|
368
|
-
this.sendSdkPoint('audio_data_expired', {
|
|
369
|
-
currentFrame: currentFrameIndex,
|
|
370
|
-
sf: data[0].sf,
|
|
371
|
-
ef: delta,
|
|
372
|
-
timestamp: Date.now(),
|
|
373
|
-
});
|
|
374
|
-
// return;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// 常量定义:24帧每秒,每帧时长(ms)
|
|
378
|
-
const FRAME_RATE = 24;
|
|
379
|
-
const FRAME_DURATION_MS = 1000 / FRAME_RATE;
|
|
380
|
-
|
|
381
|
-
// 1. 解构获取数据,避免重复 data[0] 访问
|
|
382
|
-
const { sf: startFrame, ef: endFrame } = data[0];
|
|
383
|
-
const audioLenFrames = endFrame - startFrame;
|
|
384
|
-
|
|
385
|
-
// 2. 边界保护:音频长度无效直接返回
|
|
386
|
-
if (audioLenFrames <= 0) return;
|
|
387
|
-
|
|
388
|
-
// 3. 获取当前帧(仅调用一次)
|
|
389
|
-
const currentFrame = currentFrameIndex || 0;
|
|
390
|
-
|
|
391
|
-
// 4. 计算当前帧相对于音频起始帧的偏移
|
|
392
|
-
const frameOffset = currentFrame - startFrame;
|
|
393
|
-
|
|
394
|
-
// 5. 核心逻辑判断
|
|
395
|
-
if (frameOffset > audioLenFrames) {
|
|
396
|
-
// 超过时长,直接丢弃
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// 6. 计算播放速度 (仅在需要加速时计算)
|
|
401
|
-
let playbackSpeed = 1;
|
|
402
|
-
if (frameOffset > 0) {
|
|
403
|
-
// 防止除以零 (虽然前面已判断 audioLenFrames > 0,但防御性编程)
|
|
404
|
-
playbackSpeed = audioLenFrames / Math.max(audioLenFrames - frameOffset, 0.1);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// 7. 清理旧定时器,防止多个定时器冲突
|
|
408
|
-
if (this._speedResetTimer) {
|
|
409
|
-
clearTimeout(this._speedResetTimer);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// 播放加速大于4,直接丢弃
|
|
413
|
-
if (playbackSpeed > 4) {
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// 8. 应用速度
|
|
418
|
-
this.audioRenderer.setSpeed(playbackSpeed);
|
|
419
|
-
|
|
420
|
-
// 9. 计算正确的恢复时间 (原时长 / 速度),并设置新定时器
|
|
421
|
-
const originalDurationMs = audioLenFrames * FRAME_DURATION_MS;
|
|
422
|
-
const actualDurationMs = originalDurationMs / playbackSpeed;
|
|
423
|
-
|
|
424
|
-
this._speedResetTimer = setTimeout(() => {
|
|
425
|
-
this.audioRenderer.setSpeed(1);
|
|
426
|
-
this._speedResetTimer = null; // 清理引用
|
|
427
|
-
}, actualDurationMs);
|
|
428
|
-
|
|
429
|
-
this.dataCacheQueue.checkValidData(data as IRawAudioFrameData[], type);
|
|
430
|
-
|
|
431
|
-
const processedAudioData = (data as IRawAudioFrameData[]).map((item: IRawAudioFrameData) => {
|
|
432
|
-
// raw_audio = true 时,PCM 音频下发,不进行转换
|
|
433
|
-
if (this.resourceManager.config.raw_audio) {
|
|
434
|
-
return item;
|
|
435
|
-
}
|
|
436
|
-
// raw_audio = false 时,PCM 编码音频下发,进行转换 ArrayBuffer
|
|
437
|
-
return {
|
|
438
|
-
...item,
|
|
439
|
-
ad: item.ad.buffer.slice(item.ad.byteOffset, item.ad.byteOffset + item.ad.byteLength)
|
|
440
|
-
};
|
|
441
|
-
});
|
|
442
|
-
// 过期的音频数据,直接丢弃
|
|
443
|
-
if (processedAudioData[0].sid <= this.lastSpeechId) {
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
if (this.resourceManager.config.raw_audio) {
|
|
447
|
-
(window as any).avatarSDKLogger?.log(this.TAG, 'processedAudioData[0].sid', processedAudioData[0].sid, this.currentSpeechId, this.renderState);
|
|
448
|
-
|
|
449
|
-
this.audioRenderer.updateAudioData(processedAudioData);
|
|
450
|
-
} else {
|
|
451
|
-
// 临时修复: 先缓存一份数据,用于保留开始播放的时间
|
|
452
|
-
this.audioRenderer._updateAudio(processedAudioData);
|
|
453
|
-
}
|
|
454
|
-
break;
|
|
455
|
-
case EFrameDataType.EVENT:
|
|
456
|
-
this.dataCacheQueue.clearOldEvent(currentFrameIndex);
|
|
457
|
-
const d = data as IRawEventFrameData[];
|
|
458
|
-
this.setEventData({
|
|
459
|
-
sf: d[0].sf,
|
|
460
|
-
ef: d[0].ef,
|
|
461
|
-
event: d[0].e,
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
let hasAnySpeakStart = false;
|
|
465
|
-
let expiredSpeakStartEv :IRawWidgetData | null = null;
|
|
466
|
-
let expiredVoiceStartEv :IRawWidgetData | null = null;
|
|
467
|
-
|
|
468
|
-
// 单次遍历,同时收集标记与过期事件
|
|
469
|
-
for (const item of d) {
|
|
470
|
-
const events = item.e ?? [];
|
|
471
|
-
// 标记队列内存在speak_start事件
|
|
472
|
-
if (events.some(ev => ev.type === 'speak_start')) {
|
|
473
|
-
hasAnySpeakStart = true;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// 帧过期判断,无需额外数字校验
|
|
477
|
-
const isFrameExpired = item.sf < currentFrameIndex;
|
|
478
|
-
if (!isFrameExpired) continue;
|
|
479
|
-
// 最多只取一个speak_start、一个voice_start,找到就不再赋值
|
|
480
|
-
if (!expiredSpeakStartEv) {
|
|
481
|
-
expiredSpeakStartEv = events.find(ev => ev.type === 'speak_start') || null;
|
|
482
|
-
}
|
|
483
|
-
if (!expiredVoiceStartEv) {
|
|
484
|
-
expiredVoiceStartEv = events.find(ev => ev.type === 'voice_start') || null;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// 两个事件都找到,直接跳出循环
|
|
488
|
-
if (expiredSpeakStartEv && expiredVoiceStartEv) break;
|
|
489
|
-
}
|
|
490
|
-
// 1. 新speak_start到来且音频播放中,兜底打断上一轮
|
|
491
|
-
if (hasAnySpeakStart && this.audioRenderer.isPlaying) {
|
|
492
|
-
this.reportMessage({
|
|
493
|
-
code: EErrorCode.EVENT_TRIGGERED,
|
|
494
|
-
message: 'WARNING: 有新的speak_start事件,但是上一轮仍未结束,触发兜底结束',
|
|
495
|
-
});
|
|
496
|
-
const currentSpeakStart = d.flatMap(item => item.e ?? []).find(ev => ev.type === 'speak_start');
|
|
497
|
-
const newSpeakId = currentSpeakStart?.speech_id ?? 0;
|
|
498
|
-
const playingSpeechId = this.audioRenderer.speech_id;
|
|
499
|
-
// 仅当正在播放的确实是上一轮(sid 小于新一轮)时才打断旧轮次。
|
|
500
|
-
// 若事件晚于音频到达(playingSpeechId 已是新一轮 sid),此时播放的就是新一轮,
|
|
501
|
-
// 不能用 interrupt()(其目标取 audioRenderer.speech_id),否则会误清新一轮的音频队列、
|
|
502
|
-
// 字幕事件,并因 lastSpeechId/oldSpeechId 更新导致后续音视频帧全部被丢弃,
|
|
503
|
-
// 表现为"有口型、无字幕、无音频"。
|
|
504
|
-
if (playingSpeechId !== -1 && playingSpeechId < newSpeakId) {
|
|
505
|
-
this.interrupt('new_speak_start', playingSpeechId);
|
|
506
|
-
// stop() 内部有 oldSpeechId >= speech_id 的防重入守卫:当 speech_id 停留在
|
|
507
|
-
// 异常残留值(如初始值 0,长会话中 isPlaying 卡死为 true)时 stop 会被拦截,
|
|
508
|
-
// isPlaying/speech_id 无法复位,导致新一轮音频在 _updateAudio 中永远走缓存分支
|
|
509
|
-
// 不播放(日志表现:缓存数据长度持续增长且无"开始播放")。
|
|
510
|
-
// 这里检测到 stop 未生效(speech_id 未复位为 -1)时,用 pause() 强制复位,
|
|
511
|
-
// pause 会 flush 已缓存音频并把 speech_id/oldSpeechId 重置,新一轮可正常起播。
|
|
512
|
-
if (this.audioRenderer.speech_id !== -1) {
|
|
513
|
-
this.audioRenderer.pause();
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
// 新 speak_start 已到来,立即复位 interrupt 并更新轮次状态,
|
|
517
|
-
// 避免依赖 UIRenderer 在 sf 帧处理事件时(sf==当前帧且事件在 compose 后被处理)被漏处理,
|
|
518
|
-
// 导致新轮次实际说话期间仍被强制渲染为闭嘴表情
|
|
519
|
-
if (currentSpeakStart) {
|
|
520
|
-
this.currentSpeechId = currentSpeakStart.speech_id ?? 0;
|
|
521
|
-
this.avatarRenderer.setInterrupt(false);
|
|
522
|
-
this.uiRenderer.lastSpeechId = currentSpeakStart.speech_id ?? 0;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// 2. 处理过期speak_start
|
|
527
|
-
if (expiredSpeakStartEv) {
|
|
528
|
-
const { speech_id, client_speak_id } = expiredSpeakStartEv;
|
|
529
|
-
this.reportMessage({
|
|
530
|
-
code: EErrorCode.EVENT_SPEAK_START_EXPIRED,
|
|
531
|
-
message: 'WARNING: 收到speak_start过期,主动重置avatarRender,调用onSpeakStateChange',
|
|
532
|
-
});
|
|
533
|
-
this.currentSpeechId = speech_id ?? 0;
|
|
534
|
-
this.avatarRenderer.setInterrupt(false);
|
|
535
|
-
this.uiRenderer.lastSpeechId = speech_id ?? 0;
|
|
536
|
-
this.config.onSpeakStateChange('speak_start', client_speak_id ?? 0);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// 3. 处理过期voice_start
|
|
540
|
-
if (expiredVoiceStartEv) {
|
|
541
|
-
this.reportMessage({
|
|
542
|
-
code: EErrorCode.EVENT_VOICE_START_EXPIRED,
|
|
543
|
-
message: 'WARNING: 收到voice_start过期,主动调用onVoiceStateChange',
|
|
544
|
-
});
|
|
545
|
-
const duration = (window as any).performanceTracker.markEnd(performanceConstant.voice_response_play, 'speak');
|
|
546
|
-
this.config.onVoiceStateChange('start', duration, expiredVoiceStartEv?.client_speak_id ?? '');
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// d是一个数组,过滤出其中sf和ef为-1的事件
|
|
550
|
-
const specialEvents = d.filter(item => item.sf === -1 && item.ef === -1);
|
|
551
|
-
// 遍历这个事件,然后通过proxyEvent处理它
|
|
552
|
-
for (const item of specialEvents) {
|
|
553
|
-
for (let i = 0; i < item.e.length; i++) {
|
|
554
|
-
const widgetData = item.e[i];
|
|
555
|
-
if (WidgetDefaultRender.CUSTOM_WIDGET) {
|
|
556
|
-
WidgetDefaultRender.CUSTOM_WIDGET(widgetData);
|
|
557
|
-
} else if (WidgetDefaultRender.PROXY_WIDGET) {
|
|
558
|
-
WidgetDefaultRender.PROXY_WIDGET?.[widgetData.type]?.(widgetData);
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
const normalEvents = d.filter(item => item.sf !== -1);
|
|
563
|
-
this.dataCacheQueue._updateUiEvent(normalEvents);
|
|
564
|
-
break;
|
|
565
|
-
case EFrameDataType.SYNC_FRAME:
|
|
566
|
-
const syncData = data as SYNC_FRAME_DATA;
|
|
567
|
-
if(this.sdk.getOptions().config?.replay || syncData?.source !== FRAME_SOURCE_SOURCE) return;
|
|
568
|
-
const nowTime = Date.now() / 1000;
|
|
569
|
-
const serverTime = syncData.server_time ?? 0;
|
|
570
|
-
const clientTime = syncData.client_time ?? 0;
|
|
571
|
-
const serverClientFrame = syncData.server_client_frame ?? 0;
|
|
572
|
-
const rtt = nowTime - clientTime;
|
|
573
|
-
const correctedFrame = (serverClientFrame + (rtt / 2) * 24).toFixed(0);
|
|
574
|
-
this.offsetFrame += Number(correctedFrame) - Number(currentFrameIndex??0);
|
|
575
|
-
break;
|
|
576
|
-
default:
|
|
577
|
-
this.sdk.onMessage({
|
|
578
|
-
code: EErrorCode.INVALID_DATA_STRUCTURE,
|
|
579
|
-
message: `Error: 数据类型错误`,
|
|
580
|
-
e: { ...data, type }
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
setOffsetFrame(offsetFrame: number) {
|
|
586
|
-
this.offsetFrame += offsetFrame;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
setVolume(volume: number) {
|
|
590
|
-
this.audioRenderer.setVolume(volume);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
runStartFrameIndex() {
|
|
594
|
-
this.frameAnimationController?.play();
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
stateChangeHandle(e: any) {
|
|
598
|
-
this.dataCacheQueue.currentPlayState = e;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
stopAudio(speech_id: number) {
|
|
602
|
-
this.audioRenderer.stop(speech_id);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
render() {
|
|
606
|
-
if (this.audioOnlyMode) {
|
|
607
|
-
// audioOnlyMode 下跳过 WebGL 初始化,直接标记开始播放并触发 progress 100
|
|
608
|
-
this.isStartPlay = true;
|
|
609
|
-
this.renderState = RenderState.rendering;
|
|
610
|
-
this.onRenderChange(this.renderState);
|
|
611
|
-
this.onDownloadProgress(100);
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
// 如果 pipeline 还未创建,在这里初始化(普通模式初始化时会调用这个方法)
|
|
615
|
-
this.avatarRenderer.createPipeline();
|
|
616
|
-
this.isStartPlay = true;
|
|
617
|
-
this.renderState = RenderState.rendering;
|
|
618
|
-
this.onRenderChange(this.renderState);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
stop() {
|
|
622
|
-
this.isStartPlay = false;
|
|
623
|
-
this.renderState = RenderState.stopped;
|
|
624
|
-
this.onRenderChange(this.renderState);
|
|
625
|
-
this.composition.stop();
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
/**
|
|
629
|
-
* 暂停渲染(停止渲染循环和音频播放)
|
|
630
|
-
* 但继续接收和处理后端推送的数据并放入缓存队列,避免切换到在线时丢失数据导致丢帧
|
|
631
|
-
*/
|
|
632
|
-
pauseRender(): void {
|
|
633
|
-
if (this.renderState === RenderState.paused) {
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
// 1. 暂停动画帧循环(保留当前帧索引)
|
|
638
|
-
this.frameAnimationController?.pause();
|
|
639
|
-
|
|
640
|
-
// 2. 停止并清空音频播放(丢弃所有剩余的音频数据)
|
|
641
|
-
this.audioRenderer.stop(-1); // 传入 -1 清空所有音频数据
|
|
642
|
-
|
|
643
|
-
// 3. 清空所有表情数据,避免错误的旧脸部数据与当前身体数据一起渲染导致人脸分离
|
|
644
|
-
// 恢复渲染时会使用最新的脸部数据
|
|
645
|
-
this.dataCacheQueue.clearAllFaceData();
|
|
646
|
-
|
|
647
|
-
this.renderState = RenderState.paused;
|
|
648
|
-
this.onRenderChange(this.renderState);
|
|
649
|
-
|
|
650
|
-
(window as any).avatarSDKLogger?.log(this.TAG, `渲染已暂停(已清空表情数据,等待恢复): ${this.renderState}`);
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
/**
|
|
654
|
-
* 恢复渲染
|
|
655
|
-
*/
|
|
656
|
-
resumeRender(): void {
|
|
657
|
-
if (this.renderState !== RenderState.paused) {
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
660
|
-
// 在恢复渲染时初始化 pipeline(如果在隐身模式时还未创建)
|
|
661
|
-
// 这样可以避免在隐身模式时创建 pipeline 导致的 GPU 资源竞争
|
|
662
|
-
this.avatarRenderer.createPipeline();
|
|
663
|
-
|
|
664
|
-
// 重置表情相关状态,避免从隐身模式恢复时人脸和身体不匹配
|
|
665
|
-
this.avatarRenderer.resetFaceFrameState();
|
|
666
|
-
|
|
667
|
-
// 3. 恢复动画帧循环(从暂停时的帧索引继续)
|
|
668
|
-
// 注意:frameAnimationController 会保持暂停时的帧索引,恢复时继续
|
|
669
|
-
|
|
670
|
-
this.frameAnimationController?.play();
|
|
671
|
-
this.isStartPlay = true
|
|
672
|
-
this.forceSyncDecoder();
|
|
673
|
-
// 4. 恢复后立即进入渲染中状态
|
|
674
|
-
this.renderState = RenderState.resumed;
|
|
675
|
-
this.onRenderChange(this.renderState);
|
|
676
|
-
(window as any).avatarSDKLogger?.log(this.TAG, ' 渲染已恢复,状态: this.currentSpeechId===' + this.currentSpeechId);
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
/**
|
|
680
|
-
* 切换隐身模式(暂停/恢复渲染)
|
|
681
|
-
*/
|
|
682
|
-
switchInvisibleMode(): void {
|
|
683
|
-
if (this.renderState === RenderState.rendering || this.renderState === RenderState.resumed) {
|
|
684
|
-
(window as any).avatarSDKLogger?.log(this.TAG, 'switchInvisibleMode: rendering to paused');
|
|
685
|
-
this.pauseRender();
|
|
686
|
-
} else if (this.renderState === RenderState.paused) {
|
|
687
|
-
(window as any).avatarSDKLogger?.log(this.TAG, 'switchInvisibleMode: paused to rendering');
|
|
688
|
-
this.resumeRender();
|
|
689
|
-
} else {
|
|
690
|
-
// stopped 或 idle 状态不做任何操作
|
|
691
|
-
(window as any).avatarSDKLogger?.warn(this.TAG, `渲染状态为 ${this.renderState},无法切换隐身模式`);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
/**
|
|
696
|
-
* 获取渲染状态
|
|
697
|
-
*/
|
|
698
|
-
public getRenderState(): RenderState {
|
|
699
|
-
(window as any).avatarSDKLogger?.log(this.TAG, `getRenderState: ${this.renderState}`);
|
|
700
|
-
return this.renderState;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
destroy() {
|
|
704
|
-
this.isStartPlay = false;
|
|
705
|
-
this.hasSendTimeJson = false;
|
|
706
|
-
this.offsetFrame = 0;
|
|
707
|
-
this._speedResetTimer = null; // 清理引用
|
|
708
|
-
this.frameAnimationController?.destroy();
|
|
709
|
-
this.frameAnimationController = undefined;
|
|
710
|
-
this.dataCacheQueue.destroy();
|
|
711
|
-
this.composition.destroy();
|
|
712
|
-
this.decoder.destroy();
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
handleFaceData(data: ITtsFaceFrameData[]): IRawFaceFrameData[] {
|
|
716
|
-
try {
|
|
717
|
-
const resourcePack = this.resourceManager.resource_pack;
|
|
718
|
-
const framedata_proto_version = this.resourceManager.getConfig().framedata_proto_version;
|
|
719
|
-
const blendshape_map = [...resourcePack.blendshape_map ?? []];
|
|
720
|
-
const face_list: any[] = data.map((item) => {
|
|
721
|
-
let bsw_list: number[][] = [];
|
|
722
|
-
const { body_id, sf, ef, s: state, id, js, bsw, ms, face_frame_type, asf, aef, n, cs} = item;
|
|
723
|
-
let mesh: any[] = [];
|
|
724
|
-
if (blendshape_map?.length > 0) {
|
|
725
|
-
for (let i = 0; i < blendshape_map.length; i++) {
|
|
726
|
-
const blendshape_map_item = blendshape_map[i];
|
|
727
|
-
if (blendshape_map_item?.length > 0) {
|
|
728
|
-
for (let j = 0; j < blendshape_map_item.length; j++) {
|
|
729
|
-
if (!bsw_list[i]) {
|
|
730
|
-
bsw_list[i] = [];
|
|
731
|
-
}
|
|
732
|
-
bsw_list[i][j] = bsw[blendshape_map_item[j]];
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
if (!framedata_proto_version) {
|
|
737
|
-
mesh = Array.from({ length: ms.length }, (_, index) => ({
|
|
738
|
-
blendshapeWeights: bsw_list[index],
|
|
739
|
-
textureModelIndex: ms[index]?.[0] ?? 0,
|
|
740
|
-
texturePCAWeights: ms[index]?.[1] ?? [],
|
|
741
|
-
}));
|
|
742
|
-
} else {
|
|
743
|
-
mesh = Array.from({ length: ms.length }, (_, index) => ({
|
|
744
|
-
blendshapeWeights: bsw_list[index],
|
|
745
|
-
textureModelIndex: ms[index]?.index ?? 0,
|
|
746
|
-
texturePCAWeights: ms[index]?.weights ?? [],
|
|
747
|
-
}));
|
|
748
|
-
}
|
|
749
|
-
} else {
|
|
750
|
-
if (!framedata_proto_version) {
|
|
751
|
-
mesh = Array.from({ length: ms.length }, (_, index) => ({
|
|
752
|
-
// blendshape_map 内容如[[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49],[44,45,8,10,12],[44,45,9,11,13],[44,45]]
|
|
753
|
-
// 在遍历时,需要根据 blendshape_map 的索引,将 bsw 中的值替换为 blendshape_map 中索引的值
|
|
754
|
-
blendshapeWeights: index === 0 ? bsw : [],
|
|
755
|
-
textureModelIndex: ms[index]?.[0] ?? 0, // 增加可选链和默认值,防止越界
|
|
756
|
-
texturePCAWeights: ms[index]?.[1] ?? [],
|
|
757
|
-
}));
|
|
758
|
-
} else {
|
|
759
|
-
mesh = Array.from({ length: ms.length }, (_, index) => ({
|
|
760
|
-
// blendshape_map 内容如[[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49],[44,45,8,10,12],[44,45,9,11,13],[44,45]]
|
|
761
|
-
// 在遍历时,需要根据 blendshape_map 的索引,将 bsw 中的值替换为 blendshape_map 中索引的值
|
|
762
|
-
blendshapeWeights: index === 0 ? bsw : [],
|
|
763
|
-
textureModelIndex: ms[index]?.index ?? 0, // 增加可选链和默认值,防止越界
|
|
764
|
-
texturePCAWeights: ms[index]?.weights ?? [],
|
|
765
|
-
}));
|
|
766
|
-
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
let movableJointTransforms: any[] = []
|
|
770
|
-
if (!framedata_proto_version) {
|
|
771
|
-
movableJointTransforms = js.map((joint) =>
|
|
772
|
-
formatMJT(joint[0], joint[1])
|
|
773
|
-
);
|
|
774
|
-
} else {
|
|
775
|
-
movableJointTransforms = js.map((joint) =>
|
|
776
|
-
formatMJT(joint.translate, joint.rotate)
|
|
777
|
-
);
|
|
778
|
-
}
|
|
779
|
-
return {
|
|
780
|
-
body_id,
|
|
781
|
-
frameIndex: sf,
|
|
782
|
-
sf,
|
|
783
|
-
ef,
|
|
784
|
-
asf,
|
|
785
|
-
aef,
|
|
786
|
-
n,
|
|
787
|
-
cs,
|
|
788
|
-
state,
|
|
789
|
-
id,
|
|
790
|
-
face_frame_type,
|
|
791
|
-
FaceFrameData: {
|
|
792
|
-
mesh,
|
|
793
|
-
movableJointTransforms,
|
|
794
|
-
blendshapeWeights: bsw,
|
|
795
|
-
},
|
|
796
|
-
};
|
|
797
|
-
});
|
|
798
|
-
return face_list;
|
|
799
|
-
} catch (error) {
|
|
800
|
-
this.sdk.onMessage({
|
|
801
|
-
code: EErrorCode.FACE_PROCESSING_ERROR,
|
|
802
|
-
message: `Error: 表情数据处理失败`,
|
|
803
|
-
e: error,
|
|
804
|
-
});
|
|
805
|
-
return [];
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
forceSyncDecoder() {
|
|
809
|
-
const currentFrameIndex = this.getCurrentFrameIndex();
|
|
810
|
-
if (currentFrameIndex) {
|
|
811
|
-
this.decoder.syncDecode(currentFrameIndex);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
_reload() {
|
|
815
|
-
this.decoder._reload()
|
|
816
|
-
}
|
|
817
|
-
_getResumeInfo() {
|
|
818
|
-
const frame = this.getCurrentFrameIndex();
|
|
819
|
-
return this.avatarRenderer._getCurrentBodyFrameInfo(frame || 0)
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
sendVoiceEnd() {
|
|
823
|
-
const currentFrameIndex = (this.getCurrentFrameIndex() ?? 0) + 1;
|
|
824
|
-
this.dataCacheQueue._updateUiEvent([{
|
|
825
|
-
"id": 0,
|
|
826
|
-
"s": "",
|
|
827
|
-
"sf": currentFrameIndex,
|
|
828
|
-
"ef": currentFrameIndex + 1,
|
|
829
|
-
"e": [
|
|
830
|
-
{
|
|
831
|
-
"type": "speak_end",
|
|
832
|
-
"speech_id": this.uiRenderer.lastSpeechId ?? -1,
|
|
833
|
-
},
|
|
834
|
-
{
|
|
835
|
-
"type": "voice_end",
|
|
836
|
-
"speech_id": this.uiRenderer.lastSpeechId ?? -1,
|
|
837
|
-
},
|
|
838
|
-
]
|
|
839
|
-
}])
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
/**
|
|
843
|
-
* 重连成功恢复(含窗口内 socket.io 自动重连恢复、窗口外重开房间):
|
|
844
|
-
* 数据缓存在断网进入离线时已清空(_offlineMode/_offlineRun),此处仅恢复播放状态:
|
|
845
|
-
* - 标记帧号对齐:首个 BODY/AUDIO 数据到达时按 sf 重设帧时钟
|
|
846
|
-
* - 恢复音频播放
|
|
847
|
-
* - 解码器退出离线模式(重置 _offlineLastFrame、清空离线队列)
|
|
848
|
-
* 避免离线 idle 播放推进的帧号领先服务端续推数据导致人物卡死、音画不同步
|
|
849
|
-
*/
|
|
850
|
-
markReconnectRecover() {
|
|
851
|
-
// 恢复音频播放
|
|
852
|
-
this.audioRenderer.resume();
|
|
853
|
-
// 解码器退出离线模式(重置 _offlineLastFrame、清空离线队列)
|
|
854
|
-
this.decoder._reload();
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
_offlineMode() {
|
|
858
|
-
this.dataCacheQueue._clearEvent();
|
|
859
|
-
const currentFrame = this.getCurrentFrameIndex() ?? 0;
|
|
860
|
-
this.dataCacheQueue._updateUiEvent([{
|
|
861
|
-
"id": 0,
|
|
862
|
-
"s": "",
|
|
863
|
-
"sf": currentFrame + 1,
|
|
864
|
-
"ef": currentFrame + 2,
|
|
865
|
-
"e": [
|
|
866
|
-
{
|
|
867
|
-
"type": "subtitle_off",
|
|
868
|
-
"speech_id": this.uiRenderer.lastSpeechId ?? -1,
|
|
869
|
-
},
|
|
870
|
-
]
|
|
871
|
-
}])
|
|
872
|
-
this.audioRenderer.pause()
|
|
873
|
-
const frame = this.getCurrentFrameIndex();
|
|
874
|
-
if (frame) {
|
|
875
|
-
this.decoder._offLineMode(this.resourceManager._getOfflineIdle(), frame);
|
|
876
|
-
}
|
|
877
|
-
// 重置所有 speechId 相关状态,避免联网后新语音被过滤
|
|
878
|
-
// - lastSpeechId: 用于过滤旧 sid 的音频数据(L444)
|
|
879
|
-
// - currentSpeechId: 用于 speak_end 时判断是否 setInterrupt
|
|
880
|
-
// - uiRenderer.lastSpeechId: UIRenderer 会过滤 sid < lastSpeechId 的事件(含 speak_start)
|
|
881
|
-
// 若不重置,联网后新语音的 speak_start 会被过滤,导致字幕正常但无音频
|
|
882
|
-
this.lastSpeechId = -1;
|
|
883
|
-
this.currentSpeechId = -1;
|
|
884
|
-
this.uiRenderer.lastSpeechId = -1;
|
|
885
|
-
}
|
|
886
|
-
_offlineRun() {
|
|
887
|
-
this.decoder._offlineRun()
|
|
888
|
-
this.dataCacheQueue.clearAllFaceData();
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
ttsaStateChangeHandle(state: StateChangeInfo) {
|
|
892
|
-
this.dataCacheQueue.currentTtsaState = state;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
resume() {
|
|
896
|
-
this.audioRenderer.resume();
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
/**
|
|
900
|
-
* 设置数字人canvas的显隐状态
|
|
901
|
-
* @param visible 是否可见
|
|
902
|
-
*/
|
|
903
|
-
setAvatarCanvasVisible(visible: boolean) {
|
|
904
|
-
this.avatarRenderer.setCanvasVisibility(visible);
|
|
905
|
-
}
|
|
906
|
-
setCharacterCanvasLayout(layout?: Layout) {
|
|
907
|
-
this.avatarRenderer.setCharacterCanvasAnchor(layout);
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
setWalkConfig(walkConfig: WalkConfig) {
|
|
911
|
-
this.avatarRenderer.setWalkConfig(walkConfig);
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
// targetSpeechId: 显式指定要打断的轮次 sid。缺省时取 audioRenderer.speech_id(当前播放轮次)。
|
|
915
|
-
// 当事件晚于音频到达时,audioRenderer.speech_id 可能已经切换到新一轮,此时必须显式传旧 sid,
|
|
916
|
-
// 避免误清新一轮的音频与字幕。
|
|
917
|
-
interrupt(type?: string, targetSpeechId?: number): number{
|
|
918
|
-
const start = Date.now();
|
|
919
|
-
// 插入中断字幕和语音结束事件
|
|
920
|
-
const speech_id = targetSpeechId ?? this.audioRenderer.speech_id;
|
|
921
|
-
if(type === "in_offline_mode") {
|
|
922
|
-
this.lastSpeechId = -1;
|
|
923
|
-
} else {
|
|
924
|
-
this.lastSpeechId = speech_id;
|
|
925
|
-
}
|
|
926
|
-
if(type !== "in_offline_mode") {
|
|
927
|
-
this.avatarRenderer.setInterrupt(true);
|
|
928
|
-
}
|
|
929
|
-
this.dataCacheQueue._clearAudio(speech_id);
|
|
930
|
-
this.dataCacheQueue.clearSubtitleOn(speech_id);
|
|
931
|
-
const currentFrame = this.getCurrentFrameIndex() ?? 0;
|
|
932
|
-
this.dataCacheQueue._updateUiEvent([{
|
|
933
|
-
"id": 0,
|
|
934
|
-
"s": "",
|
|
935
|
-
"sf": currentFrame + 1,
|
|
936
|
-
"ef": currentFrame + 2,
|
|
937
|
-
"e": [
|
|
938
|
-
{
|
|
939
|
-
"type": "subtitle_off",
|
|
940
|
-
"speech_id": speech_id ?? -1,
|
|
941
|
-
},
|
|
942
|
-
]
|
|
943
|
-
}])
|
|
944
|
-
|
|
945
|
-
// 中断音频播放
|
|
946
|
-
if(type !== "in_offline_mode") {
|
|
947
|
-
this.audioRenderer.stop(speech_id);
|
|
948
|
-
}
|
|
949
|
-
this.uiRenderer.setInterrupt(speech_id)
|
|
950
|
-
return Date.now() - start;
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
seekToFrame(frame: number) {
|
|
954
|
-
this.frameAnimationController?.updateStartTime(frame);
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
getCurrentFrameIndex() {
|
|
958
|
-
return (this.frameAnimationController?.getCurrentFrame()??0) + this.offsetFrame;
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
getSpeechId() {
|
|
962
|
-
return this.audioRenderer.speech_id;
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
getAudioRenderer(): AudioRenderer {
|
|
966
|
-
return this.audioRenderer;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
// ── 测试用方法:模拟 WebGL 上下文丢失 ─────────────────────────────
|
|
970
|
-
|
|
971
|
-
/**
|
|
972
|
-
* [测试用] 模拟 WebGL 上下文丢失
|
|
973
|
-
*/
|
|
974
|
-
public simulateContextLoss(restoreAfterMs: number = 5000, forceFail: boolean = false): void {
|
|
975
|
-
this.avatarRenderer.simulateContextLoss(restoreAfterMs, forceFail);
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
/**
|
|
979
|
-
* [测试用] 获取当前恢复状态信息
|
|
980
|
-
*/
|
|
981
|
-
public getRecoveryStatus() {
|
|
982
|
-
return this.avatarRenderer.getRecoveryStatus();
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
/**
|
|
986
|
-
* [测试用] 批量测试分阶段恢复
|
|
987
|
-
*/
|
|
988
|
-
public async testStagedRecovery(): Promise<void> {
|
|
989
|
-
return this.avatarRenderer.testStagedRecovery();
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
/**
|
|
993
|
-
* 手动重启 WebGL:创建新 canvas + 新 GLDevice + 新 GLPipeline
|
|
994
|
-
* 用于分阶段恢复全部失败后的手动恢复
|
|
995
|
-
* @returns 是否成功
|
|
996
|
-
*/
|
|
997
|
-
public restartWebGL(): boolean {
|
|
998
|
-
return this.avatarRenderer.restartWebGL();
|
|
999
|
-
}
|
|
1000
|
-
}
|