@xmov/avatar 2.0.0-alpha.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +139 -0
  2. package/dist/agent/__tests__/agent.test.d.ts +1 -0
  3. package/dist/agent/audio-debug.d.ts +3 -0
  4. package/dist/agent/audio-uplink.d.ts +62 -0
  5. package/dist/agent/avatar.cjs +2 -0
  6. package/dist/agent/avatar.cjs.map +1 -0
  7. package/dist/agent/avatar.modern.js +2 -0
  8. package/dist/agent/avatar.modern.js.map +1 -0
  9. package/dist/agent/avatar.module.js +2 -0
  10. package/dist/agent/avatar.module.js.map +1 -0
  11. package/dist/agent/avatar.umd.js +2 -0
  12. package/dist/agent/avatar.umd.js.map +1 -0
  13. package/dist/agent/e2e-client.d.ts +32 -0
  14. package/dist/agent/fixed-audio-track.d.ts +48 -0
  15. package/dist/agent/index.cjs +5 -0
  16. package/dist/agent/index.d.ts +104 -0
  17. package/dist/agent/index.umd.js +10 -0
  18. package/dist/agent/microphone.d.ts +49 -0
  19. package/dist/agent/types.d.ts +153 -0
  20. package/dist/baseRender/AudioRenderer.d.ts +46 -0
  21. package/dist/baseRender/AudioWorklet.d.ts +78 -0
  22. package/dist/baseRender/AvatarRenderer.d.ts +226 -0
  23. package/dist/baseRender/MSEAudioPlayer.d.ts +51 -0
  24. package/dist/baseRender/UIRenderer.d.ts +39 -0
  25. package/dist/baseRender/pcm-audio-processor.d.ts +63 -0
  26. package/dist/control/APIForwarder.d.ts +23 -0
  27. package/dist/control/DataCacheQueue.d.ts +69 -0
  28. package/dist/control/EventDispatcher.d.ts +35 -0
  29. package/dist/control/RenderScheduler.d.ts +162 -0
  30. package/dist/control/SaveAndDownload.d.ts +14 -0
  31. package/dist/control/StreamingClient.d.ts +33 -0
  32. package/dist/control/VideoPlayer.d.ts +47 -0
  33. package/dist/control/ttsa.d.ts +111 -0
  34. package/dist/encoding/media-recorder-encoder.d.ts +33 -0
  35. package/dist/encoding/pcm-webm-encoder.d.ts +13 -0
  36. package/dist/encoding/webcodec-opus-encoder.d.ts +41 -0
  37. package/dist/encoding/webm-muxer.d.ts +90 -0
  38. package/dist/index.cjs +2 -0
  39. package/dist/index.cjs.map +1 -0
  40. package/dist/index.d.ts +231 -0
  41. package/dist/index.modern.js +2 -0
  42. package/dist/index.modern.js.map +1 -0
  43. package/dist/index.module.js +2 -0
  44. package/dist/index.module.js.map +1 -0
  45. package/dist/index.umd.js +2 -0
  46. package/dist/index.umd.js.map +1 -0
  47. package/dist/modules/Composition.d.ts +28 -0
  48. package/dist/modules/ResourceManager.d.ts +295 -0
  49. package/dist/modules/TrackRenderer/base-track.d.ts +4 -0
  50. package/dist/modules/TrackRenderer/index.d.ts +12 -0
  51. package/dist/modules/TrackRenderer/render-implements.d.ts +19 -0
  52. package/dist/modules/TrackRenderer/track-pic.d.ts +8 -0
  53. package/dist/modules/TrackRenderer/track-subtitle.d.ts +8 -0
  54. package/dist/modules/cache-manager.d.ts +5 -0
  55. package/dist/modules/decoder.d.ts +84 -0
  56. package/dist/modules/error-handle.d.ts +17 -0
  57. package/dist/modules/network.d.ts +21 -0
  58. package/dist/proto/face_data_pb.d.ts +0 -0
  59. package/dist/types/capability.d.ts +15 -0
  60. package/dist/types/error.d.ts +70 -0
  61. package/dist/types/event.d.ts +25 -0
  62. package/dist/types/frame-data.d.ts +107 -0
  63. package/dist/types/index.d.ts +187 -0
  64. package/dist/types/render.d.ts +18 -0
  65. package/dist/utils/DataInterface.d.ts +113 -0
  66. package/dist/utils/GLDevice.d.ts +46 -0
  67. package/dist/utils/GLPipeline.d.ts +84 -0
  68. package/dist/utils/GLPipelineDebugTools.d.ts +5 -0
  69. package/dist/utils/Math.d.ts +21 -0
  70. package/dist/utils/__tests__/capability-checker.test.d.ts +1 -0
  71. package/dist/utils/audio-session.d.ts +11 -0
  72. package/dist/utils/audio.d.ts +1 -0
  73. package/dist/utils/capability-checker.d.ts +23 -0
  74. package/dist/utils/encodeToken.d.ts +7 -0
  75. package/dist/utils/face.d.ts +1 -0
  76. package/dist/utils/float32-decoder.d.ts +26 -0
  77. package/dist/utils/fpsTracker.d.ts +27 -0
  78. package/dist/utils/index.d.ts +22 -0
  79. package/dist/utils/logger.d.ts +9 -0
  80. package/dist/utils/media-recorder-timestamp.d.ts +15 -0
  81. package/dist/utils/pcmAnalyzer.d.ts +40 -0
  82. package/dist/utils/perfermance.d.ts +15 -0
  83. package/dist/utils/request.d.ts +19 -0
  84. package/dist/utils/requestAnimateFrames.d.ts +31 -0
  85. package/dist/utils/time.d.ts +9 -0
  86. package/dist/view/DebugOverlay.d.ts +54 -0
  87. package/dist/worker/streaming-video.d.ts +1 -0
  88. package/package.json +82 -0
  89. package/src/agent/__tests__/agent.test.ts +3787 -0
  90. package/src/agent/audio-debug.ts +36 -0
  91. package/src/agent/audio-uplink.ts +392 -0
  92. package/src/agent/e2e-client.ts +215 -0
  93. package/src/agent/fixed-audio-track.ts +547 -0
  94. package/src/agent/index.ts +1393 -0
  95. package/src/agent/microphone.ts +534 -0
  96. package/src/agent/types.ts +197 -0
  97. package/src/baseRender/AudioRenderer.ts +317 -0
  98. package/src/baseRender/AudioWorklet.ts +776 -0
  99. package/src/baseRender/AvatarRenderer.ts +1559 -0
  100. package/src/baseRender/MSEAudioPlayer.ts +243 -0
  101. package/src/baseRender/UIRenderer.ts +195 -0
  102. package/src/baseRender/pcm-audio-processor.js +267 -0
  103. package/src/control/APIForwarder.ts +67 -0
  104. package/src/control/DataCacheQueue.ts +372 -0
  105. package/src/control/EventDispatcher.ts +106 -0
  106. package/src/control/RenderScheduler.ts +945 -0
  107. package/src/control/SaveAndDownload.js +77 -0
  108. package/src/control/StreamingClient.ts +138 -0
  109. package/src/control/VideoPlayer.ts +306 -0
  110. package/src/control/ttsa.ts +676 -0
  111. package/src/encoding/media-recorder-encoder.ts +175 -0
  112. package/src/encoding/pcm-webm-encoder.ts +72 -0
  113. package/src/encoding/webcodec-opus-encoder.ts +258 -0
  114. package/src/encoding/webm-muxer.ts +337 -0
  115. package/src/global.d.ts +105 -0
  116. package/src/index.ts +1920 -0
  117. package/src/modules/Composition.ts +79 -0
  118. package/src/modules/ResourceManager.ts +899 -0
  119. package/src/modules/TrackRenderer/base-track.ts +7 -0
  120. package/src/modules/TrackRenderer/index.ts +40 -0
  121. package/src/modules/TrackRenderer/render-implements.ts +269 -0
  122. package/src/modules/TrackRenderer/track-pic.ts +19 -0
  123. package/src/modules/TrackRenderer/track-subtitle.ts +16 -0
  124. package/src/modules/cache-manager.ts +10 -0
  125. package/src/modules/decoder.ts +515 -0
  126. package/src/modules/error-handle.ts +20 -0
  127. package/src/modules/network.ts +71 -0
  128. package/src/proto/face_data.proto +45 -0
  129. package/src/proto/face_data_pb.js +1297 -0
  130. package/src/proto/protobuf.min.js +8 -0
  131. package/src/types/capability.ts +23 -0
  132. package/src/types/error.ts +211 -0
  133. package/src/types/event.ts +38 -0
  134. package/src/types/frame-data.ts +121 -0
  135. package/src/types/index.ts +207 -0
  136. package/src/types/render.ts +18 -0
  137. package/src/utils/DataInterface.ts +746 -0
  138. package/src/utils/GLDevice.ts +286 -0
  139. package/src/utils/GLPipeline.ts +1293 -0
  140. package/src/utils/GLPipelineDebugTools.ts +174 -0
  141. package/src/utils/Math.ts +266 -0
  142. package/src/utils/__tests__/capability-checker.test.ts +159 -0
  143. package/src/utils/audio-session.ts +144 -0
  144. package/src/utils/audio.ts +44 -0
  145. package/src/utils/blueimp-md5.d.ts +3 -0
  146. package/src/utils/capability-checker.ts +379 -0
  147. package/src/utils/encodeToken.ts +100 -0
  148. package/src/utils/face.ts +29 -0
  149. package/src/utils/float32-decoder.js +130 -0
  150. package/src/utils/fpsTracker.ts +100 -0
  151. package/src/utils/index.ts +182 -0
  152. package/src/utils/logger.js +36 -0
  153. package/src/utils/media-recorder-timestamp.ts +79 -0
  154. package/src/utils/pcmAnalyzer.ts +154 -0
  155. package/src/utils/perfermance.ts +141 -0
  156. package/src/utils/request.ts +95 -0
  157. package/src/utils/requestAnimateFrames.ts +195 -0
  158. package/src/utils/time.ts +32 -0
  159. package/src/view/DebugOverlay.ts +404 -0
  160. package/src/worker/streaming-video.ts +4647 -0
@@ -0,0 +1,197 @@
1
+ import type { IAvatarOptions, IInitParams, INetworkInfo, RenderState } from "../types";
2
+
3
+ export type AgentState =
4
+ | "idle"
5
+ | "initializing"
6
+ | "ready"
7
+ | "running"
8
+ | "reconnecting"
9
+ | "stopped"
10
+ | "destroyed"
11
+ | "failed";
12
+ export type AgentSocketState = "idle" | "connecting" | "open" | "closed" | "error";
13
+ export type AgentASRState = "idle" | "requesting-permission" | "starting" | "listening" | "stopping" | "failed";
14
+ export type AgentConversationState =
15
+ | "idle"
16
+ | "asking"
17
+ | "speaking-directly"
18
+ | "speaking"
19
+ | "completed"
20
+ | "interrupted"
21
+ | "failed";
22
+
23
+ export type AgentErrorDomain = "permission" | "network" | "asr" | "brain" | "ttsa" | "quota" | "sdk";
24
+
25
+ export interface AgentAudioOptions {
26
+ chunkMs?: number;
27
+ inputEnabled?: boolean;
28
+ /**
29
+ * @deprecated Session 返回 features.speech_frontend.enable_aec 后,以服务端返回值为准。
30
+ */
31
+ echoCancellationEnabled?: boolean;
32
+ }
33
+
34
+ export interface AgentReconnectOptions {
35
+ enabled?: boolean;
36
+ maxAttempts?: number;
37
+ initialDelayMs?: number;
38
+ maxDelayMs?: number;
39
+ }
40
+
41
+ export interface AgentError {
42
+ code: string;
43
+ message: string;
44
+ domain: AgentErrorDomain;
45
+ retryable?: boolean;
46
+ cause?: unknown;
47
+ }
48
+
49
+ export interface AgentASRResult {
50
+ text: string;
51
+ isFinal: boolean;
52
+ raw?: unknown;
53
+ }
54
+
55
+ export interface AgentASRStartOptions {
56
+ /**
57
+ * Optional caller-owned audio stream used instead of requesting the physical microphone.
58
+ * The Agent clones one live audio track and never stops the caller-owned stream.
59
+ */
60
+ inputStream?: MediaStream;
61
+ }
62
+
63
+ export interface AgentLLMUsage {
64
+ promptTokens: number;
65
+ completionTokens: number;
66
+ totalTokens: number;
67
+ cachedTokens: number;
68
+ }
69
+
70
+ export interface AgentLLMResponse {
71
+ event: "chunk" | "done";
72
+ text?: string;
73
+ isFirst?: boolean;
74
+ usage?: AgentLLMUsage;
75
+ raw?: unknown;
76
+ }
77
+
78
+ export interface AgentSemanticJudgeResult {
79
+ query: string;
80
+ meaningful: boolean;
81
+ action: string;
82
+ raw?: unknown;
83
+ }
84
+
85
+ export interface AgentSpeakStateEvent {
86
+ state: string;
87
+ clientSpeakId?: string | number;
88
+ raw?: unknown;
89
+ }
90
+
91
+ export interface AgentConversationEvent {
92
+ state: AgentConversationState;
93
+ text?: string;
94
+ raw?: unknown;
95
+ }
96
+
97
+ export interface XingyunAvatarAgentCallbacks {
98
+ onAgentStateChange?: (state: AgentState) => void;
99
+ onSocketStateChange?: (state: AgentSocketState) => void;
100
+ onASRStateChange?: (state: AgentASRState) => void;
101
+ onASRResult?: (result: AgentASRResult) => void;
102
+ onLLMResponse?: (response: AgentLLMResponse) => void;
103
+ onSpeakStateChange?: (event: AgentSpeakStateEvent) => void;
104
+ onConversationChange?: (event: AgentConversationEvent) => void;
105
+ onSemanticJudgeResult?: (result: AgentSemanticJudgeResult) => void;
106
+ onRenderChange?: (state: RenderState) => void;
107
+ onNetworkInfo?: (info: INetworkInfo) => void;
108
+ onError?: (error: AgentError) => void;
109
+ }
110
+
111
+ export type AgentServiceConfig = Record<string, unknown>;
112
+
113
+ export interface AgentBrainConfig extends AgentServiceConfig {
114
+ extra_body?: Record<string, unknown>;
115
+ }
116
+
117
+ export interface AgentFeatures extends AgentServiceConfig {
118
+ anti_interference?: {
119
+ semantic_judge_enabled?: boolean;
120
+ };
121
+ speech_frontend?: {
122
+ enabled?: boolean;
123
+ enable_aec?: boolean;
124
+ enable_speech_separation?: boolean;
125
+ };
126
+ vad_merge_mode?: boolean;
127
+ volume_and_repetition_text_detection?: boolean;
128
+ }
129
+
130
+ export interface XingyunAvatarAgentOptions extends IAvatarOptions {
131
+ asr_id?: number;
132
+ asr_config?: AgentServiceConfig;
133
+ tts_config?: AgentServiceConfig;
134
+ features?: AgentFeatures;
135
+ extras?: AgentServiceConfig;
136
+ llm_id?: number;
137
+ brain_config?: AgentBrainConfig;
138
+ session_speak_req_id?: 0 | 1;
139
+ e2eServer?: string;
140
+ authToken?: string;
141
+ audio?: AgentAudioOptions;
142
+ reconnect?: AgentReconnectOptions;
143
+ agentCallbacks?: XingyunAvatarAgentCallbacks;
144
+ webSocketCtor?: typeof WebSocket;
145
+ }
146
+
147
+ export type XingyunAvatarAgentInitParams = Omit<IInitParams, "onDownloadProgress"> & {
148
+ onDownloadProgress?: IInitParams["onDownloadProgress"];
149
+ };
150
+
151
+ export interface AgentSessionInfo {
152
+ ws_url?: string;
153
+ e2e_token?: string;
154
+ monitor_ws_url?: string;
155
+ }
156
+
157
+ export interface AgentClientMessage {
158
+ type: string;
159
+ message?: unknown;
160
+ [key: string]: unknown;
161
+ }
162
+
163
+ export interface AgentServerEvent {
164
+ type: string;
165
+ domain?: string;
166
+ code?: string | number;
167
+ event?: string;
168
+ message?: unknown;
169
+ text?: string;
170
+ is_first?: boolean;
171
+ is_final?: boolean;
172
+ usage?: {
173
+ prompt_tokens?: number;
174
+ completion_tokens?: number;
175
+ total_tokens?: number;
176
+ cached_tokens?: number;
177
+ };
178
+ [key: string]: unknown;
179
+ }
180
+
181
+ export interface AgentAudioMetadata {
182
+ format: "webm_opus";
183
+ mimeType: "audio/webm;codecs=opus";
184
+ /** Fixed output track sample rate passed to the MediaRecorder pipeline. */
185
+ sampleRate: number;
186
+ /** Fixed output track channel count passed to the MediaRecorder pipeline. */
187
+ channels: number;
188
+ chunkMs: number;
189
+ }
190
+
191
+ export const DEFAULT_AGENT_AUDIO: AgentAudioMetadata = {
192
+ format: "webm_opus",
193
+ mimeType: "audio/webm;codecs=opus",
194
+ sampleRate: 16000,
195
+ channels: 1,
196
+ chunkMs: 100,
197
+ };
@@ -0,0 +1,317 @@
1
+ /**
2
+ * 音频渲染
3
+ * 使用 AudioContext 控制
4
+ */
5
+
6
+ import ResourceManager from "../modules/ResourceManager";
7
+ import { DataCacheQueue } from "../control/DataCacheQueue";
8
+ // import PCMAudioPlayer from "./Audio";
9
+ import AudioWorklet from "./AudioWorklet";
10
+ import { performanceConstant } from "../utils/perfermance";
11
+ import XmovAvatar from "../index";
12
+ import { AvatarStatus } from "../types";
13
+ import MediaSourceAudioPlayer from "./MSEAudioPlayer";
14
+ import { isSupportMES } from "../utils";
15
+
16
+ type Option = {
17
+ sdk: XmovAvatar;
18
+ dataCacheQueue: DataCacheQueue;
19
+ resourceManager: ResourceManager;
20
+ };
21
+
22
+ export default class AudioRender {
23
+ private TAG = "[AudioRenderer]";
24
+ private options: Option;
25
+ isPlaying = false;
26
+ lastFrameIndex: number = -1;
27
+ audio: AudioWorklet | null = null;
28
+ firstFrameIndex: number = -1;
29
+ resourceManager: ResourceManager;
30
+ valume: number = 1;
31
+ speech_id: number = -1; // 当前播放的音频id
32
+ cacheFirstFrameIndex: number = -1; // 缓存下来的第一个音频的索引
33
+ cacheAudioData: Uint8Array[] = []; // 缓存下来的音频数据
34
+ oldSpeechId: number = -1; // 上一个播放的音频id
35
+ mseCacheAudioData: Array<{ data: ArrayBuffer; frameIndex?: number; speechId?: number }> = [];
36
+ private mseAudioPlayer: MediaSourceAudioPlayer | null = null;
37
+
38
+ constructor(options: Option) {
39
+ this.options = options;
40
+ this.resourceManager = options.resourceManager;
41
+ if(this.resourceManager.config.raw_audio) {
42
+ this.audio = new AudioWorklet();
43
+ this.audio.init();
44
+ } else {
45
+ this.mseAudioPlayer = MediaSourceAudioPlayer.getInstance(options.sdk);
46
+ }
47
+ }
48
+ updateAudioData(audioList: any) {
49
+ if (
50
+ !this.resourceManager.config.raw_audio ||
51
+ audioList[0].sid === this.oldSpeechId
52
+ )
53
+ return;
54
+ if (this.firstFrameIndex === -1) {
55
+ this.firstFrameIndex = audioList[0].sf;
56
+ }
57
+ let totalLength = 0;
58
+ for (const audio of audioList) {
59
+ totalLength += audio.ad?.length || 0;
60
+ }
61
+
62
+ // 创建合并后的Uint8Array
63
+ const mergedData = new Uint8Array(totalLength);
64
+ let offset = 0;
65
+
66
+ // 填充合并数据
67
+ for (const audio of audioList) {
68
+ if (audio.ad) {
69
+ mergedData.set(new Uint8Array(audio.ad), offset);
70
+ offset += audio.ad.length;
71
+ }
72
+ }
73
+ if (this.speech_id === -1) {
74
+ this.speech_id = audioList[0].sid;
75
+ this.audio?.receivePCMStream(mergedData);
76
+ } else {
77
+ if (this.speech_id !== audioList[0].sid) {
78
+ // 先缓存下来
79
+ if (this.cacheFirstFrameIndex === -1) {
80
+ this.cacheFirstFrameIndex = audioList[0].sf;
81
+ }
82
+ this.cacheAudioData.push(mergedData);
83
+ } else {
84
+ // 直接播放
85
+ this.audio?.receivePCMStream(mergedData);
86
+ }
87
+ }
88
+ }
89
+
90
+ _updateAudio(audioList: any) {
91
+ if (!this.mseAudioPlayer || audioList.length === 0) return;
92
+
93
+ const firstAudio = audioList[0];
94
+
95
+ // 检查是否是旧的 speech_id
96
+ if (firstAudio.sid === this.oldSpeechId) {
97
+ return;
98
+ }
99
+
100
+ // 如果 speech_id 变化,停止当前播放并重置
101
+ if (this.speech_id !== -1 && firstAudio.sid !== this.speech_id) {
102
+ (window as any).avatarSDKLogger?.log(
103
+ this.TAG,
104
+ `检测到新的 speech_id: ${firstAudio.sid}, 当前 WebM 播放 (旧: ${this.speech_id}), 新: ${firstAudio.sid}, 缓存数据长度: ${this.mseCacheAudioData.length}`
105
+ );
106
+ if(this.cacheFirstFrameIndex === -1) {
107
+ this.cacheFirstFrameIndex = firstAudio.sf;
108
+ }
109
+ for(const item of audioList) {
110
+ this.mseCacheAudioData.push({
111
+ data: item.ad,
112
+ frameIndex: item.sf,
113
+ speechId: item.sid,
114
+ })
115
+ }
116
+ } else {
117
+ // 记录第一个音频段的帧索引
118
+ if (this.firstFrameIndex === -1) {
119
+ this.firstFrameIndex = firstAudio.sf;
120
+ }
121
+
122
+ // 更新 speech_id
123
+ if (this.speech_id === -1) {
124
+ this.speech_id = firstAudio.sid;
125
+ } else if (this.speech_id !== firstAudio.sid) {
126
+ this.speech_id = firstAudio.sid;
127
+ }
128
+
129
+ // 处理每个音频段
130
+ for (const audio of audioList) {
131
+ if (audio.ad) {
132
+ // 将 Uint8Array 转换为 ArrayBuffer
133
+ const audioData = audio.ad instanceof ArrayBuffer
134
+ ? audio.ad
135
+ : audio.ad.buffer.slice(audio.ad.byteOffset, audio.ad.byteOffset + audio.ad.byteLength);
136
+
137
+ this.mseAudioPlayer.addAudioSegment(
138
+ audioData,
139
+ );
140
+ }
141
+ }
142
+ }
143
+ }
144
+
145
+ async render(frameIndex: number) {
146
+ (window as any).avatarSDKLogger.log("当前帧索引:", frameIndex, "音频开始播放帧:", this.firstFrameIndex);
147
+ if (this.lastFrameIndex === -1) {
148
+ this.lastFrameIndex = frameIndex;
149
+ }
150
+ if (this.options.sdk.getStatus() === AvatarStatus.offline) return;
151
+ // PCM 流式播放(raw_audio = true)
152
+ if (
153
+ frameIndex >= this.firstFrameIndex &&
154
+ this.firstFrameIndex != -1 &&
155
+ !this.isPlaying &&
156
+ this.resourceManager.config.raw_audio
157
+ ) {
158
+ (window as any).performanceTracker.markEnd(
159
+ performanceConstant.start_action_render,
160
+ "speak"
161
+ );
162
+ this.isPlaying = true;
163
+ this.audio?.start();
164
+ return;
165
+ }
166
+
167
+ // WebM 流式播放(raw_audio = false)
168
+ if (!this.resourceManager.config.raw_audio && this.mseAudioPlayer) {
169
+ // 检查是否应该开始播放(帧对齐)
170
+ if (
171
+ this.firstFrameIndex !== -1 &&
172
+ frameIndex >= this.firstFrameIndex &&
173
+ !this.mseAudioPlayer.isPlaying &&
174
+ !this.isPlaying
175
+ ) {
176
+ // 优化:确保有足够的数据缓冲再开始播放
177
+ const stats = this.mseAudioPlayer.getStats();
178
+ const hasEnoughData = stats.queueLength > 0 || stats.bufferedRanges !== '-';
179
+ if (hasEnoughData) {
180
+ try {
181
+ (window as any).avatarSDKLogger.log("开始播放", frameIndex);
182
+ this.isPlaying = true;
183
+ await this.mseAudioPlayer.start();
184
+ (window as any).performanceTracker.markEnd(
185
+ performanceConstant.start_action_render,
186
+ "speak"
187
+ );
188
+ } catch (error) {
189
+ (window as any).avatarSDKLogger.error("[AudioRenderer] WebM 音频播放失败:", {
190
+ error,
191
+ frameIndex,
192
+ firstFrameIndex: this.firstFrameIndex,
193
+ queueLength: stats.queueLength,
194
+ });
195
+ }
196
+ } else {
197
+ // 数据不足,等待更多数据
198
+ (window as any).avatarSDKLogger.debug?.(
199
+ "[AudioRenderer]",
200
+ `等待更多 WebM 音频数据 (frameIndex: ${frameIndex}, queueLength: ${stats.queueLength})`
201
+ );
202
+ }
203
+ }
204
+ return; // WebM 模式下不需要继续处理 PCM 播放逻辑
205
+ }
206
+ }
207
+
208
+ resume() {
209
+ if (this.audio && this.resourceManager.config.raw_audio) {
210
+ this.audio?.resume();
211
+ } else {
212
+ // if (this.audioCtx.state === "suspended") {
213
+ // this.audioCtx
214
+ // .resume()
215
+ // .then(() => {
216
+ // (window as any).avatarSDKLogger.info("音频上下文恢复成功");
217
+ // })
218
+ // .catch((error) => {
219
+ // (window as any).avatarSDKLogger.error("音频上下文恢复失败:", error);
220
+ // });
221
+ // }
222
+ }
223
+ }
224
+
225
+ pause() {
226
+ this.isPlaying = false;
227
+ this.options.dataCacheQueue._clearAudio(this.speech_id);
228
+ this.firstFrameIndex = -1;
229
+ // 停止 PCM 播放
230
+ this.audio?.stop();
231
+
232
+ // 停止 WebM 播放
233
+ this.mseAudioPlayer?.stop(this.addNewAudioSegment.bind(this));
234
+ this.speech_id = -1;
235
+ }
236
+
237
+ stop(speech_id: number) {
238
+ // 如果当前需要结束的是旧的语音ID,直接返回
239
+ if(this.oldSpeechId >= speech_id) return;
240
+
241
+ this.isPlaying = false;
242
+ this.options.dataCacheQueue._clearAudio(speech_id);
243
+ this.firstFrameIndex = -1;
244
+ // 处理缓存的音频数据
245
+ (window as any).avatarSDKLogger?.log(
246
+ this.TAG,
247
+ `清除缓存数据 (speech_id: ${this.speech_id}, PCM缓存数据长度: ${this.cacheAudioData.length}, WebM缓存数据长度: ${this.mseCacheAudioData.length})`
248
+ );
249
+ if (this.resourceManager.config.raw_audio) {
250
+ // 停止 PCM 播放
251
+ this.audio?.stop();
252
+ } else {
253
+ // 停止 WebM 播放
254
+ this.mseAudioPlayer?.stop(this.addNewAudioSegment.bind(this));
255
+ }
256
+ this.oldSpeechId = speech_id;
257
+ this.speech_id = -1;
258
+ if (this.cacheAudioData.length > 0) {
259
+ this.firstFrameIndex = this.cacheFirstFrameIndex;
260
+ this.cacheFirstFrameIndex = -1;
261
+ // 新的缓存下来的数据,推入音频流
262
+ for (const data of this.cacheAudioData) {
263
+ this.audio?.receivePCMStream(data);
264
+ }
265
+ this.cacheAudioData = [];
266
+ }
267
+ }
268
+
269
+ addNewAudioSegment() {
270
+ if(this.mseCacheAudioData.length > 0) {
271
+ this.firstFrameIndex = this.cacheFirstFrameIndex;
272
+ this.cacheFirstFrameIndex = -1;
273
+ // 新的缓存下来的数据,推入音频流
274
+ for (const audio of this.mseCacheAudioData) {
275
+ if (audio.data) {
276
+ this.mseAudioPlayer?.addAudioSegment(
277
+ audio.data,
278
+ );
279
+ }
280
+ }
281
+ this.mseCacheAudioData = [];
282
+ }
283
+ }
284
+
285
+ setVolume(valume: number) {
286
+ this.valume = valume;
287
+ if (this.resourceManager.config.raw_audio) {
288
+ this.audio?.setVolume(valume);
289
+ } else {
290
+ // WebM 模式:使用 MSEAudioPlayer 设置音量
291
+ this.mseAudioPlayer?.setVolume(valume);
292
+ }
293
+ }
294
+
295
+ setSpeed(speed: number) {
296
+ if (this.resourceManager.config.raw_audio) {
297
+ this.audio?.setSpeed(speed);
298
+ } else {
299
+ // WebM 模式:使用 MSEAudioPlayer 设置播放速度
300
+ this.mseAudioPlayer?.setSpeed(speed);
301
+ }
302
+ }
303
+
304
+ getAudioPlayer(): AudioWorklet | null {
305
+ return this.audio;
306
+ }
307
+
308
+ destroy() {
309
+ this.stop(-1);
310
+ this.cacheAudioData = [];
311
+ this.mseCacheAudioData = [];
312
+ this.audio?.destroy();
313
+ // 直接调用 destroy(),不需要先 stop()(stop 会重建 MediaSource,浪费资源)
314
+ this.mseAudioPlayer?.destroy();
315
+ this.mseAudioPlayer = null;
316
+ }
317
+ }