@xmov/avatar 2.0.1 → 2.1.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.
Files changed (53) hide show
  1. package/README.md +3 -1
  2. package/dist/agent/avatar.cjs +1 -2
  3. package/dist/agent/avatar.modern.js +1 -2
  4. package/dist/agent/avatar.module.js +1 -2
  5. package/dist/agent/avatar.umd.js +1 -2
  6. package/dist/agent/index.cjs +1 -4
  7. package/dist/agent/index.d.ts +12 -5
  8. package/dist/agent/index.umd.js +3 -5
  9. package/dist/agent/types.d.ts +5 -43
  10. package/dist/baseRender/AvatarRenderer.d.ts +0 -1
  11. package/dist/control/RenderScheduler.d.ts +17 -1
  12. package/dist/control/ttsa.d.ts +8 -1
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.ts +5 -1
  16. package/dist/index.modern.js +1 -1
  17. package/dist/index.modern.js.map +1 -1
  18. package/dist/index.module.js +1 -1
  19. package/dist/index.module.js.map +1 -1
  20. package/dist/index.umd.js +1 -1
  21. package/dist/index.umd.js.map +1 -1
  22. package/dist/modules/ResourceManager.d.ts +2 -1
  23. package/dist/utils/request.d.ts +1 -0
  24. package/package.json +29 -7
  25. package/src/baseRender/AudioRenderer.ts +16 -5
  26. package/src/baseRender/AvatarRenderer.ts +11 -20
  27. package/src/baseRender/MSEAudioPlayer.ts +18 -7
  28. package/src/control/RenderScheduler.ts +74 -8
  29. package/src/control/ttsa.ts +47 -11
  30. package/src/index.ts +66 -45
  31. package/src/modules/ResourceManager.ts +19 -6
  32. package/src/modules/decoder.ts +34 -1
  33. package/src/utils/capability-checker.ts +95 -21
  34. package/src/utils/request.ts +14 -2
  35. package/src/view/DebugOverlay.ts +9 -4
  36. package/dist/agent/__tests__/agent.test.d.ts +0 -1
  37. package/dist/agent/audio-debug.d.ts +0 -3
  38. package/dist/agent/audio-uplink.d.ts +0 -62
  39. package/dist/agent/avatar.cjs.map +0 -1
  40. package/dist/agent/avatar.modern.js.map +0 -1
  41. package/dist/agent/avatar.module.js.map +0 -1
  42. package/dist/agent/avatar.umd.js.map +0 -1
  43. package/dist/agent/e2e-client.d.ts +0 -32
  44. package/dist/agent/fixed-audio-track.d.ts +0 -48
  45. package/dist/agent/microphone.d.ts +0 -49
  46. package/src/agent/__tests__/agent.test.ts +0 -3787
  47. package/src/agent/audio-debug.ts +0 -36
  48. package/src/agent/audio-uplink.ts +0 -392
  49. package/src/agent/e2e-client.ts +0 -215
  50. package/src/agent/fixed-audio-track.ts +0 -547
  51. package/src/agent/index.ts +0 -1393
  52. package/src/agent/microphone.ts +0 -534
  53. package/src/agent/types.ts +0 -197
@@ -1,2 +1 @@
1
- import e from"@xmov/avatar";function t(){return t=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var s in n)({}).hasOwnProperty.call(n,s)&&(e[s]=n[s])}return e},t.apply(null,arguments)}const n=2097152;class s{constructor(e){var t;this.ws=null,this.connectPromise=null,this.rejectConnect=null,this.options=e,this.audioInputEnabled=null==(t=e.audioInputEnabled)||t;const n=e.WebSocketCtor||globalThis.WebSocket;if(!n)throw new Error("当前环境不支持 WebSocket");this.WebSocketCtor=n}get isOpen(){var e;return(null==(e=this.ws)?void 0:e.readyState)===this.WebSocketCtor.OPEN}connect(){var e,t;if(this.isOpen)return Promise.resolve();if(this.connectPromise)return this.connectPromise;let n;return null==(e=(t=this.options).onStateChange)||e.call(t,"connecting"),n=new Promise((e,t)=>{this.rejectConnect=t;let s=!1;const i=new this.WebSocketCtor(this.buildWsUrl(this.options.wsUrl,this.options.token));this.ws=i,i.onopen=()=>{var t,r;this.ws===i&&(s=!0,this.connectPromise===n&&(this.connectPromise=null),this.rejectConnect=null,null==(t=(r=this.options).onStateChange)||t.call(r,"open"),e())},i.onmessage=e=>{if(this.ws!==i)return;const t=this.parseMessage(e.data);var n,s;t&&(null==(n=(s=this.options).onEvent)||n.call(s,t))},i.onerror=()=>{var e,r,a,o;if(this.ws!==i)return;const l=new Error("E2E WebSocket 连接异常");s||(s=!0,this.connectPromise===n&&(this.connectPromise=null),this.rejectConnect=null,t(l)),null==(e=(r=this.options).onStateChange)||e.call(r,"error"),null==(a=(o=this.options).onError)||a.call(o,l)},i.onclose=e=>{var r,a,o,l;this.ws===i&&(this.ws=null,s||(s=!0,this.connectPromise===n&&(this.connectPromise=null),this.rejectConnect=null,t(new Error(`E2E WebSocket 已关闭: ${e.code} ${e.reason||""}`.trim()))),null==(r=(a=this.options).onStateChange)||r.call(a,"closed"),null==(o=(l=this.options).onClose)||o.call(l,e))}}),this.connectPromise=n,this.connectPromise}disconnect(e=1e3,t="client_close"){if(!this.ws)return;const n=this.ws;this.ws=null,this.connectPromise=null;const s=this.rejectConnect;this.rejectConnect=null,null==s||s(new Error("E2E WebSocket 连接已取消")),n.readyState!==this.WebSocketCtor.OPEN&&n.readyState!==this.WebSocketCtor.CONNECTING||n.close(e,t)}send(e){var t;this.assertOpen(),null==(t=this.ws)||t.send(JSON.stringify(e))}trySend(e){var t;return this.assertOpen(),!(this.ws&&this.ws.bufferedAmount>n||(null==(t=this.ws)||t.send(JSON.stringify(e)),0))}sendBinary(e){var t;if(this.assertOpen(),this.ws&&this.ws.bufferedAmount>n){const e=new Error("E2E WebSocket 发送缓冲区超限");throw e.name="WebSocketBackpressureError",e}null==(t=this.ws)||t.send(e)}ping(){this.send({type:"ping"})}setAudioInputEnabled(e){this.audioInputEnabled=e}assertOpen(){if(!this.isOpen)throw new Error("E2E WebSocket 未连接")}parseMessage(e){if("string"!=typeof e)return null;try{const t=JSON.parse(e);return t&&"string"==typeof t.type?t:null}catch(e){var t,n;return null==(t=(n=this.options).onError)||t.call(n,new Error("E2E WebSocket 消息不是合法 JSON")),null}}buildWsUrl(e,t){try{const n=new URL(e);return!t||n.searchParams.has("token")||n.searchParams.has("e2e_token")||n.searchParams.set("token",t),this.updateAudioInputQuery(n.searchParams),n.toString()}catch(n){const[s,i=""]=e.split("?",2),r=new URLSearchParams(i);!t||r.has("token")||r.has("e2e_token")||r.set("token",t),this.updateAudioInputQuery(r);const a=r.toString();return a?`${s}?${a}`:s}}updateAudioInputQuery(e){this.audioInputEnabled?e.delete("audio_input_off"):e.set("audio_input_off","1")}}const i=16e3;class r{constructor(e={}){this.inputTrack=null,this.reader=null,this.writer=null,this.generator=null,this.audioContext=null,this.audioSource=null,this.audioDestination=null,this.pumpPromise=null,this.pumpFinished=!1,this.outputClosed=!1,this.draining=!1,this.inputSampleRate=null,this.resampleBuffer=[],this.resamplePosition=0,this.outputBuffer=[],this.outputTimestamp=null,this.startGeneration=0,this.options=e}async start(e){const t=++this.startGeneration;this.inputTrack=e,this.inputSampleRate=null,this.resampleBuffer=[],this.resamplePosition=0,this.outputBuffer=[],this.outputTimestamp=null,this.pumpFinished=!1,this.outputClosed=!1,this.draining=!1;const n=globalThis,s=n.MediaStreamTrackProcessor,r=n.MediaStreamTrackGenerator,a=n.AudioData;if(!s||!r||!a)return this.startWebAudio(e,n,t);const o=new s({track:e}),l=new r({kind:"audio"}),h=new MediaStream([l]),c=h.getAudioTracks()[0],u=null==c?void 0:c.getSettings();if(void 0!==(null==u?void 0:u.sampleRate)&&u.sampleRate!==i||void 0!==(null==u?void 0:u.channelCount)&&1!==u.channelCount)throw l.stop(),this.unsupported(`浏览器音频输出格式不符合要求: ${(null==u?void 0:u.sampleRate)||"unknown"} Hz / ${(null==u?void 0:u.channelCount)||"unknown"} channel`);try{this.reader=o.readable.getReader(),this.writer=l.writable.getWriter()}catch(t){throw l.stop(),"ended"!==e.readyState&&e.stop(),this.inputTrack=null,t}return this.generator=l,this.pumpPromise=this.pump(a),h}async startWebAudio(e,t,n){const s=globalThis.AudioContext||t.webkitAudioContext;if(!s)throw this.unsupported("当前浏览器不支持 16 kHz mono 音频处理");let r=null,a=null,o=null;try{if(r=new s({sampleRate:i}),this.audioContext=r,r.sampleRate!==i)throw this.unsupported(`浏览器无法创建 16 kHz AudioContext: ${r.sampleRate} Hz`);if("suspended"===r.state)try{await r.resume()}catch(e){throw this.contextSuspended(e)}if(this.assertStartActive(n),"running"!==r.state)throw this.contextSuspended();a=r.createMediaStreamSource(new MediaStream([e])),this.audioSource=a,o=r.createMediaStreamDestination(),this.audioDestination=o,o.channelCount=1,o.channelCountMode="explicit",o.channelInterpretation="speakers",a.connect(o);const t=o.stream.getAudioTracks()[0];if(!t)throw new Error("Web Audio 未返回音频输出轨道");return this.validateOutputTrack(t),this.assertStartActive(n),o.stream}catch(t){var l,h;throw null==(l=a)||l.disconnect(),null==(h=o)||h.stream.getTracks().forEach(e=>e.stop()),r&&"closed"!==r.state&&await r.close().catch(()=>{}),"ended"!==e.readyState&&e.stop(),this.inputTrack=null,this.audioContext===r&&(this.audioContext=null,this.audioSource=null,this.audioDestination=null),this.normalizeWebAudioError(t)}}async stop(){await this.drain(),this.audioContext?await this.closeWebAudioOutput():await this.closeOutput()}async drain(){var e;if(this.startGeneration+=1,this.draining=!0,null==(e=this.inputTrack)||e.stop(),this.inputTrack=null,this.audioContext)return;const t=this.pumpPromise;if(t){const e=t.then(()=>!0,()=>!0);var n,s;await Promise.race([e,this.delay(1e3)])||(await Promise.race([null==(n=this.reader)?void 0:n.cancel().catch(()=>{}),this.delay(100)]),await Promise.race([null==(s=this.writer)?void 0:s.abort(new Error("16 kHz mono 音频处理停止超时")).catch(()=>{}),this.delay(100)]),this.stopGenerator())}}async closeOutput(){const e=this.writer;this.outputClosed=!0,e&&this.pumpFinished?await Promise.race([e.close().catch(()=>{}),this.delay(100)]):await Promise.race([null==e?void 0:e.abort(new Error("16 kHz mono 音频处理停止超时")).catch(()=>{}),this.delay(100)]),this.stopGenerator(),e&&this.pumpFinished&&e.releaseLock(),this.reader=null,this.writer=null,this.generator=null,this.pumpPromise=null}async closeWebAudioOutput(){const e=this.audioContext,t=this.audioSource,n=this.audioDestination;this.audioContext=null,this.audioSource=null,this.audioDestination=null,null==t||t.disconnect(),null==n||n.disconnect(),null==n||n.stream.getTracks().forEach(e=>e.stop()),e&&"closed"!==e.state&&await e.close().catch(()=>{})}async pump(e){const t=this.reader,n=this.writer;if(t&&n)try{var s;let o=!1;for(;;){const s=await t.read();if(s.done){o=!0;break}if(!s.value)continue;const i=s.value;try{this.validateAudioData(i);const t=this.toMono(i),s=this.resample(t,i.sampleRate,!1);null===this.outputTimestamp&&(this.outputTimestamp=Number.isFinite(i.timestamp)?i.timestamp:0);for(const t of s)await this.writeChunk(n,e,t)}finally{i.close()}}for(const t of this.resample(new Float32Array,this.inputSampleRate||i,!0))await this.writeChunk(n,e,t);if(o&&!this.draining&&"ended"===(null==(s=this.inputTrack)?void 0:s.readyState)){var r,a;const e=new Error("麦克风音频轨道已结束");e.name="AudioTransformError",null==(r=(a=this.options).onError)||r.call(a,e),this.stopGenerator()}}catch(e){var o,l;const t=e instanceof Error?e:new Error("16 kHz mono 音频处理失败");t.name="AudioTransformError",null==(o=(l=this.options).onError)||o.call(l,t)}finally{t.releaseLock(),this.pumpFinished=!0,this.outputClosed&&n.releaseLock()}}toMono(e){const t=Math.max(1,e.numberOfChannels),n=new Float32Array(e.numberOfFrames);for(let s=0;s<t;s+=1){const i=new Float32Array(e.numberOfFrames);e.copyTo(i,{format:"f32-planar",planeIndex:s});for(let e=0;e<i.length;e+=1)n[e]+=i[e]/t}return n}resample(e,t,n){if(null===this.inputSampleRate)this.inputSampleRate=t;else if(this.inputSampleRate!==t)throw new Error(`输入音频采样率发生变化: ${this.inputSampleRate} -> ${t}`);for(const t of e)this.resampleBuffer.push(t);if(this.resampleBuffer.length>192e3)throw new Error("输入音频重采样缓冲区超限");n&&this.resampleBuffer.length>0&&this.resampleBuffer.push(this.resampleBuffer[this.resampleBuffer.length-1]);const s=t/i,r=[];for(;this.resamplePosition+1<this.resampleBuffer.length;){const e=Math.floor(this.resamplePosition),t=Math.min(e+1,this.resampleBuffer.length-1),n=this.resamplePosition-e;this.outputBuffer.push(this.resampleBuffer[e]*(1-n)+this.resampleBuffer[t]*n),this.resamplePosition+=s;const i=Math.floor(this.resamplePosition);i>0&&(this.resampleBuffer.splice(0,i),this.resamplePosition-=i),this.outputBuffer.length>=320&&r.push(new Float32Array(this.outputBuffer.splice(0,320)))}return n&&this.outputBuffer.length>0&&r.push(new Float32Array(this.outputBuffer.splice(0))),r}async writeChunk(e,t,n){var s;if(!n.length)return;const r=this.outputTimestamp||0,a=new t({format:"f32-planar",sampleRate:i,numberOfFrames:n.length,numberOfChannels:1,timestamp:r,data:n});this.outputTimestamp=r+Math.round(1e6*n.length/i),await e.write(a),a.close();const o=null==(s=this.generator)?void 0:s.getSettings();if(o&&(void 0!==o.sampleRate&&o.sampleRate!==i||void 0!==o.channelCount&&1!==o.channelCount))throw this.unsupported(`浏览器音频输出格式不符合要求: ${o.sampleRate||"unknown"} Hz / ${o.channelCount||"unknown"} channel`)}validateAudioData(e){if(!Number.isFinite(e.sampleRate)||!Number.isInteger(e.sampleRate)||e.sampleRate<8e3||e.sampleRate>192e3)throw new Error(`输入音频采样率无效: ${e.sampleRate}`);if(!Number.isInteger(e.numberOfChannels)||e.numberOfChannels<1||e.numberOfChannels>8)throw new Error(`输入音频声道数无效: ${e.numberOfChannels}`);if(!Number.isInteger(e.numberOfFrames)||e.numberOfFrames<1||e.numberOfFrames>96e3)throw new Error(`输入音频帧数无效: ${e.numberOfFrames}`);if(!Number.isFinite(e.timestamp)||Math.abs(e.timestamp)>1e15)throw new Error(`输入音频时间戳无效: ${e.timestamp}`)}delay(e){return new Promise(t=>{globalThis.setTimeout(()=>t(!1),e)})}stopGenerator(){this.generator&&"ended"!==this.generator.readyState&&this.generator.stop()}unsupported(e,t){const n=new Error(e);return n.name="NotSupportedError",n.agentCode="AUDIO_FIXED_TRACK_UNSUPPORTED",n.retryable=!1,n.cause=t,n}contextSuspended(e){const t=new Error("AudioContext 未运行,请在用户操作后重试");return t.name="NotAllowedError",t.agentCode="AUDIO_CONTEXT_SUSPENDED",t.retryable=!0,t.cause=e,t}assertStartActive(e){if(e!==this.startGeneration){const e=new Error("16 kHz mono 音频处理启动已取消");throw e.name="AbortError",e.agentCode="AUDIO_FIXED_TRACK_CANCELLED",e.retryable=!0,e}}normalizeWebAudioError(e){const t=e instanceof Error?e:new Error(String(e));if(t.agentCode)return t;if("NotSupportedError"===t.name)return this.unsupported(`浏览器无法初始化 16 kHz mono Web Audio: ${t.message}`,t);const n=new Error(`16 kHz mono Web Audio 初始化失败: ${t.message}`);return n.name=t.name,n.agentCode="AUDIO_FIXED_TRACK_FAILED",n.retryable=!0,n.cause=t,n}validateOutputTrack(e){const t=e.getSettings();if(void 0!==t.sampleRate&&t.sampleRate!==i||void 0!==t.channelCount&&1!==t.channelCount)throw this.unsupported(`浏览器音频输出格式不符合要求: ${t.sampleRate||"unknown"} Hz / ${t.channelCount||"unknown"} channel`)}}const a={format:"webm_opus",mimeType:"audio/webm;codecs=opus",sampleRate:16e3,channels:1,chunkMs:100};function o(e){return e.toString(16).padStart(2,"0")}async function l(e,n,s={},i=64){try{const r=n instanceof Blob?await n.arrayBuffer():ArrayBuffer.isView(n)?n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength):n,a=new Uint8Array(r),l=Array.from(a.slice(0,i)),h=n instanceof Blob?n.type:s.type;console.log(e,t({},s,{size:a.byteLength,type:h,previewLength:l.length,bytes:l,hex:l.map(o).join(" ")}))}catch(t){console.warn(e,"failed to read audio chunk",t)}}const h=new WeakMap,c=new WeakMap;function u(e){return e.audioSession}function d(e,t){try{return e.type=t,e.type===t}catch(e){return!1}}function p(){const e=globalThis.performance;return e&&Number.isFinite(e.timeOrigin)&&"function"==typeof e.now?e.timeOrigin+e.now():Date.now()}class m{constructor(e,t=p){this.timecodeScale=null,this.firstTimecode=null,this.lastTimecode=null,this.lastObservedAt=null,this.lastTimestamp=null,this.chunkMs=e,this.now=t,this.startedAt=t()}resolve(e){const t=this.now(),n=Number.isFinite(e)&&e>=0;if(n&&null===this.firstTimecode&&(this.firstTimecode=e),n&&null===this.timecodeScale&&null!==this.lastTimecode&&e>this.lastTimecode){const n=e-this.lastTimecode,s=null===this.lastObservedAt?0:t-this.lastObservedAt,i=s>=this.chunkMs/4?s:this.chunkMs,r=Math.abs(n-i),a=Math.abs(1e3*n-i);this.timecodeScale=a<r?1e3:1}let s=t-this.chunkMs;const i=n&&null!==this.firstTimecode?e-this.firstTimecode:null;var r;null!==i&&i>=0&&(null!==this.timecodeScale||0===i)&&(s=this.startedAt+i*(null!=(r=this.timecodeScale)?r:1)),null!==this.lastTimestamp&&(s=Math.max(s,this.lastTimestamp+this.chunkMs));const a=Math.max(Math.round(this.startedAt),Math.round(s));return this.lastTimecode=n?e:this.lastTimecode,this.lastObservedAt=t,this.lastTimestamp=a,a}}const f=2e4;function g(e){return{codec:"opus",sampleRate:e.sampleRate,numberOfChannels:e.channels,bitrate:e.bitrate,opus:{format:"opus",frameDuration:f}}}class S{constructor(e={}){this.encoder=null,this.isInitialized=!1,this.pendingEncode=null,this.timestamp=0,this.config=t({sampleRate:16e3,channels:1,bitrate:24e3,frameSize:1600},e)}static async isSupported(){if("undefined"==typeof AudioEncoder)return!1;try{var e;const t=await AudioEncoder.isConfigSupported(g({sampleRate:16e3,channels:1,bitrate:24e3,frameSize:1600}));return!0===t.supported&&(null==(e=t.config)||null==(e=e.opus)?void 0:e.frameDuration)===f}catch(e){return!1}}async init(){this.isInitialized||(this.encoder=this.createEncoder(),this.isInitialized=!0)}async encode(e){if(!this.isInitialized||!this.encoder)return Promise.reject(new Error("WebCodecOpusEncoder 未初始化"));if(this.pendingEncode)return Promise.reject(new Error("WebCodecs Opus 编码器仍在处理上一音频块"));const t=new Int16Array(e),n=this.timestamp,s=Math.round(1e6*e.length/this.config.sampleRate),i=n+s,r=s/f;if(!Number.isInteger(r))return Promise.reject(new Error("PCM 音频块无法按 20ms Opus packet 完整分包"));const a=new AudioData({format:"s16",sampleRate:this.config.sampleRate,numberOfFrames:t.length,numberOfChannels:this.config.channels,timestamp:n,data:t}),o=new Promise((e,t)=>{const s=globalThis.setTimeout(()=>{this.failPending(new Error("WebCodecs Opus 编码输出超过 2 秒"))},2e3);this.pendingEncode={startTimestampUs:n,endTimestampUs:i,expectedFrameCount:r,chunks:[],resolve:e,reject:t,timeoutId:s}});try{this.encoder.encode(a),this.timestamp=i}catch(e){this.failPending(e instanceof Error?e:new Error("WebCodecs Opus 编码失败"))}finally{a.close()}return o}reset(){if(this.timestamp=0,this.failPending(new Error("WebCodecs Opus 编码已重置")),this.encoder){try{this.encoder.close()}catch(e){}this.encoder=this.createEncoder()}}async destroy(){const e=this.encoder;if(e){try{await e.flush()}catch(e){}this.failPending(new Error("WebCodecs Opus 编码器已销毁"));try{e.close()}catch(e){}}this.encoder=null,this.isInitialized=!1}createEncoder(){let e;return e=new AudioEncoder({output:t=>{this.encoder===e&&this.handleOutput(t)},error:t=>{if(this.encoder!==e)return;const n=t instanceof Error?t:new Error("WebCodecs Opus 编码失败");(globalThis.avatarSDKLogger||console).error("[WebCodecOpusEncoder] 编码错误:",n),this.failPending(n)}}),e.configure(g(this.config)),e}handleOutput(e){const t=this.pendingEncode;if(!t)return;if(e.timestamp<t.startTimestampUs||e.timestamp>=t.endTimestampUs||null!==e.duration&&e.duration!==f)return void this.failPending(new Error("WebCodecs Opus 未按 20ms 分包,无法生成固定 100ms AU 音频"));if(t.chunks.push(e),t.chunks.length>t.expectedFrameCount)return void this.failPending(new Error("WebCodecs Opus 未按 20ms 分包,无法生成固定 100ms AU 音频"));if(t.chunks.length<t.expectedFrameCount)return;const n=t.chunks.map((e,n)=>{var s,i;const r=new Uint8Array(e.byteLength);e.copyTo(r);const a=null!=(s=null==(i=t.chunks[n+1])?void 0:i.timestamp)?s:t.endTimestampUs;return{data:r,durationMs:(e.duration&&e.duration>0?e.duration:Math.max(1,a-e.timestamp))/1e3}}),s=n.reduce((e,t)=>e+t.durationMs,0);n.some(e=>20!==e.durationMs)||s!==(t.endTimestampUs-t.startTimestampUs)/1e3?this.failPending(new Error("WebCodecs Opus 未按 20ms 分包,无法生成固定 100ms AU 音频")):(globalThis.clearTimeout(t.timeoutId),this.pendingEncode=null,t.resolve(n))}failPending(e){const t=this.pendingEncode;t&&(globalThis.clearTimeout(t.timeoutId),this.pendingEncode=null,t.reject(e))}}class E{constructor(e=16e3){this.chunks=[],this.hasHeader=!1,this.clusterTimestamp=0,this.frameDuration=100,this.sampleRate=e}writeHeader(){if(this.hasHeader)return;const e=this.buildEBMLHeader(),t=this.buildSegmentInfo(),n=this.buildOpusTracks();this.chunks.push(e),this.chunks.push(new Uint8Array([24,83,128,103,1,255,255,255,255,255,255,255])),this.chunks.push(t),this.chunks.push(n),this.hasHeader=!0,this.clusterTimestamp=0}feedOpusFrame(e,t=this.frameDuration){this.hasHeader||this.writeHeader();const n=this.buildCluster(Math.round(this.clusterTimestamp),e);this.chunks.push(n),this.clusterTimestamp+=t}flush(){if(0===this.chunks.length)return new ArrayBuffer(0);const e=this.chunks.reduce((e,t)=>e+t.length,0),t=new Uint8Array(e);let n=0;for(const e of this.chunks)t.set(e,n),n+=e.length;return this.chunks=[],t.buffer}reset(){this.chunks=[],this.hasHeader=!1,this.clusterTimestamp=0}encodeVINT(e,t){if(void 0!==t){const n=new Uint8Array(t);for(let s=t-1;s>=0;s--)n[s]=255&e,e>>>=8;return n[0]|=1<<8-t,n}if(e<127)return new Uint8Array([128|e]);if(e<16383)return new Uint8Array([64|e>>8,255&e]);if(e<2097151)return new Uint8Array([32|e>>16,e>>8&255,255&e]);if(e<268435455)return new Uint8Array([16|e>>24,e>>16&255,e>>8&255,255&e]);throw new Error(`VINT value too large: ${e}`)}encodeID(e){return e<=255?new Uint8Array([e]):e<=65535?new Uint8Array([e>>8,255&e]):e<=16777215?new Uint8Array([e>>16,e>>8&255,255&e]):new Uint8Array([e>>>24,e>>16&255,e>>8&255,255&e])}encodeUint(e){if(0===e)return new Uint8Array([0]);const t=[];let n=e;for(;n>0;)t.unshift(255&n),n=Math.floor(n/256);return new Uint8Array(t)}encodeString(e){return(new TextEncoder).encode(e)}encodeFloat32(e){const t=new ArrayBuffer(4);return new DataView(t).setFloat32(0,e,!1),new Uint8Array(t)}encodeElement(e,t){const n=this.encodeID(e),s=this.encodeVINT(t.length),i=new Uint8Array(n.length+s.length+t.length);return i.set(n,0),i.set(s,n.length),i.set(t,n.length+s.length),i}encodeUintElement(e,t){return this.encodeElement(e,this.encodeUint(t))}encodeStringElement(e,t){return this.encodeElement(e,this.encodeString(t))}encodeFloat32Element(e,t){return this.encodeElement(e,this.encodeFloat32(t))}concatArrays(e){const t=e.reduce((e,t)=>e+t.length,0),n=new Uint8Array(t);let s=0;for(const t of e)n.set(t,s),s+=t.length;return n}buildEBMLHeader(){const e=this.concatArrays([this.encodeUintElement(17030,1),this.encodeUintElement(17143,1),this.encodeUintElement(17138,4),this.encodeUintElement(17139,8),this.encodeStringElement(17026,"webm"),this.encodeUintElement(17031,4),this.encodeUintElement(17029,2)]);return this.encodeElement(440786851,e)}buildSegmentInfo(){const e=this.concatArrays([this.encodeUintElement(2807729,1e6),this.encodeStringElement(19840,"xmov-avatar-sdk"),this.encodeStringElement(22337,"xmov-avatar-sdk")]);return this.encodeElement(357149030,e)}buildOpusTracks(){const e=new Uint8Array([79,112,117,115,72,101,97,100,1,1,0,0,128,62,0,0,0,0,0]),t=this.concatArrays([this.encodeFloat32Element(181,this.sampleRate),this.encodeUintElement(159,1)]),n=this.concatArrays([this.encodeUintElement(215,1),this.encodeUintElement(29637,1),this.encodeUintElement(131,2),this.encodeUintElement(156,0),this.encodeStringElement(2274716,"und"),this.encodeStringElement(134,"A_OPUS"),this.encodeElement(25506,e),this.encodeUintElement(22186,0),this.encodeUintElement(22203,8e7),this.encodeElement(225,t)]),s=this.encodeElement(174,n);return this.encodeElement(374648427,s)}buildCluster(e,t){const n=this.encodeUintElement(231,e),s=this.encodeVINT(1),i=new Uint8Array([0,0]),r=new Uint8Array([128]),a=this.concatArrays([s,i,r,t]),o=this.encodeElement(163,a),l=this.concatArrays([n,o]);return this.encodeElement(524531317,l)}}class y{constructor(){this.opusEncoder=new S({sampleRate:16e3,channels:1,bitrate:24e3,frameSize:1600}),this.muxer=new E(16e3),this.queue=Promise.resolve(),this.generation=0,this.destroyed=!1,this.pendingBlocks=0}static isSupported(){return S.isSupported()}async init(){await this.opusEncoder.init()}async encode(e){var t=this;if(1600!==e.length)throw new Error(`PCM 音频块必须为 1600 samples,实际为 ${e.length}`);if(this.pendingBlocks>=20)throw new Error("PCM/Opus 编码队列超过 2 秒");this.pendingBlocks+=1;const n=this.generation,s=new Int16Array(e),i=this.queue.then(async function(){if(t.destroyed||n!==t.generation)return new ArrayBuffer(0);const e=await t.opusEncoder.encode(s);if(t.destroyed||n!==t.generation)return new ArrayBuffer(0);for(const n of e)t.muxer.feedOpusFrame(n.data,n.durationMs);return t.muxer.flush()}).finally(()=>{this.pendingBlocks-=1});return this.queue=i.then(()=>{},()=>{}),i}reset(){const e=++this.generation;this.queue=this.queue.then(()=>{this.destroyed||e!==this.generation||(this.opusEncoder.reset(),this.muxer.reset())})}async destroy(){this.destroyed=!0,this.generation+=1,await this.opusEncoder.destroy(),await this.queue,this.muxer.reset()}}const w="audio/webm;codecs=opus",b=16e3,A="agent-pcm-capture-processor",k=`\nclass AgentPcmCaptureProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.buffer = new Float32Array(1600);\n this.bufferIndex = 0;\n this.sequence = 0;\n this.port.onmessage = (event) => {\n if (event.data?.type === 'flush') {\n this.emitBlock(true);\n this.port.postMessage({ type: 'flushed' });\n }\n };\n }\n\n emitBlock(padPartial = false) {\n if (this.bufferIndex === 0 || (!padPartial && this.bufferIndex < 1600)) return;\n const pcm = new Int16Array(1600);\n for (let index = 0; index < this.bufferIndex; index += 1) {\n const sample = Math.max(-1, Math.min(1, this.buffer[index]));\n pcm[index] = sample < 0 ? sample * 0x8000 : sample * 0x7fff;\n }\n this.port.postMessage(\n { type: 'frame', pcm: pcm.buffer, sequence: this.sequence++ },\n [pcm.buffer],\n );\n this.buffer.fill(0);\n this.bufferIndex = 0;\n }\n\n process(inputs, outputs) {\n for (const output of outputs) {\n for (const channel of output) channel.fill(0);\n }\n const channels = inputs[0];\n if (!channels?.length || !channels[0]?.length) return true;\n for (let frame = 0; frame < channels[0].length; frame += 1) {\n let sample = 0;\n for (let channel = 0; channel < channels.length; channel += 1) {\n sample += channels[channel][frame] || 0;\n }\n this.buffer[this.bufferIndex++] = sample / channels.length;\n if (this.bufferIndex === 1600) this.emitBlock();\n }\n return true;\n }\n}\nregisterProcessor('${A}', AgentPcmCaptureProcessor);\n`;class v{constructor(e){var n,s;this.stream=null,this.recordingStream=null,this.audioTransformer=null,this.mediaRecorder=null,this.pcmAudioContext=null,this.pcmSource=null,this.pcmWorklet=null,this.pcmEncoder=null,this.pcmEncodeQueue=Promise.resolve(),this.pcmStartedAt=0,this.pcmGeneration=0,this.resolvePcmFlush=null,this.startGeneration=0,this.starting=!1,this.hasAudioSessionCapture=!1,this.options=e,this.pendingInputStream=null!=(n=e.inputStream)?n:null,this.usesProvidedInput=Boolean(e.inputStream),this.metadata=t({},a,{chunkMs:(null==(s=e.audio)?void 0:s.chunkMs)||a.chunkMs})}get isRecording(){return this.starting||Boolean(this.stream)||Boolean(this.pendingInputStream)}getAudioMetadata(){return this.metadata}async start(){if(this.stream||this.starting)return;const e=++this.startGeneration;this.starting=!0;try{var n,s;const o=this.pendingInputStream;this.stream=null!=o?o:null,this.pendingInputStream=null;const l=navigator.mediaDevices;if(!(this.stream||null!=l&&l.getUserMedia))throw new Error("当前浏览器不支持麦克风采集");const c=globalThis.MediaRecorder;if(this.options.usePcmWebCodecs){if(!await y.isSupported())throw this.webCodecsOpusUnsupported()}else if(null==c||null==c.isTypeSupported||!c.isTypeSupported(w))throw this.webMOpusUnsupported();if(this.assertStartActive(e),!this.stream){if(this.usesProvidedInput)throw o?this.cancelled():new Error("ASR 输入流已释放");this.hasAudioSessionCapture=function(e=navigator){var t,n;const s=u(e);if(!s)return!1;const i=h.get(s),r=null!=(t=null==i?void 0:i.previousType)?t:s.type;return!!d(s,"play-and-record")&&(h.set(s,{count:(null!=(n=null==i?void 0:i.count)?n:0)+1,previousType:r}),!0)}(),this.stream=await l.getUserMedia({audio:{sampleRate:{ideal:b},channelCount:{ideal:1},echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}})}this.assertStartActive(e);const p=this.stream.getAudioTracks()[0];if(!p)throw new Error("麦克风未返回音频轨道");var i,a;if(this.options.usePcmWebCodecs)return await this.startPcmWebMRecorder(this.stream),this.assertStartActive(e),this.metadata=t({},this.metadata,{sampleRate:b,channels:1,chunkMs:100}),void(null==(i=(a=this.options).onMetadata)||i.call(a,this.metadata));const m=new r({onError:e=>{var t,n;const s=e instanceof Error?e:new Error("16 kHz mono 音频处理失败");s.name="MediaRecorderError",null==(t=(n=this.options).onError)||t.call(n,s)}});this.audioTransformer=m,this.recordingStream=await m.start(p),this.assertStartActive(e);const f=this.recordingStream.getAudioTracks()[0],g=null==f?void 0:f.getSettings();this.metadata=t({},this.metadata,{sampleRate:(null==g?void 0:g.sampleRate)||b,channels:(null==g?void 0:g.channelCount)||1}),this.startWebMRecorder(c),null==(n=(s=this.options).onMetadata)||n.call(s,this.metadata)}catch(t){const n=e!==this.startGeneration;await this.stop();const s=n?this.cancelled():t instanceof Error?t:new Error("麦克风启动失败");var o,l;throw"AUDIO_FIXED_TRACK_CANCELLED"!==s.agentCode&&(null==(o=(l=this.options).onError)||o.call(l,s)),s}finally{this.startGeneration===e&&(this.starting=!1)}}async stop(){this.startGeneration+=1,this.starting=!1;try{var e;const t=this.audioTransformer;try{await(null==t?void 0:t.drain())}finally{await this.stopPcmWebMRecorder(),await this.stopMediaRecorder(),await(null==t?void 0:t.stop()),this.audioTransformer=null}this.recordingStream=null,this.stream&&!t&&this.stream.getTracks().forEach(e=>e.stop()),this.stream=null,null==(e=this.pendingInputStream)||e.getTracks().forEach(e=>e.stop()),this.pendingInputStream=null}finally{this.hasAudioSessionCapture&&(this.hasAudioSessionCapture=!1,function(e=navigator){const t=u(e);if(!t)return!1;const n=h.get(t);if(!n)return function(e=navigator){const t=u(e);if(!t)return!1;const n=h.get(t);return d(t,null!=n&&n.count||"play-and-record"===t.type?"play-and-record":"playback")}(e);if(n.count-=1,n.count>0)return d(t,"play-and-record");h.delete(t);const s="play-and-record"===n.previousType?"play-and-record":"playback";d(t,s)&&"playback"===s&&function(e){const t=c.get(e);if(t)for(const e of[...t])try{e()}catch(e){}}(t)}())}}startWebMRecorder(e){if(!this.recordingStream)throw new Error("16 kHz mono 音频流未就绪");let t;try{t=new e(this.recordingStream,{mimeType:w,audioBitsPerSecond:24e3})}catch(e){if(e instanceof Error&&"NotSupportedError"===e.name)throw this.webMOpusUnsupported(e);throw e}const n=new m(this.metadata.chunkMs);t.ondataavailable=e=>{if(e.data.size>0){var s;const i=n.resolve(e.timecode);null==(s=globalThis.avatarSDKLogger)||null==s.log||s.log("[Agent][Microphone]","audio chunk",{size:e.data.size,type:e.data.type,chunkMs:this.metadata.chunkMs,sampleRate:this.metadata.sampleRate,channels:this.metadata.channels,recorderState:t.state,timecode:e.timecode}),this.options.debugAudioChunks&&l("[Agent][Microphone] nearend WebM/Opus chunk",e.data,{chunkMs:this.metadata.chunkMs,sampleRate:this.metadata.sampleRate,channels:this.metadata.channels,recorderState:t.state,timecode:e.timecode}),this.options.onFrame(e.data,i)}},t.onerror=e=>{var t,n;const s=e.error,i=new Error(s instanceof Error?s.message:"WebM/Opus 麦克风录制失败");i.name="MediaRecorderError",null==(t=(n=this.options).onError)||t.call(n,i)},this.mediaRecorder=t;try{t.start(this.metadata.chunkMs)}catch(e){if(e instanceof Error&&"NotSupportedError"===e.name)throw this.webMOpusUnsupported(e);throw e}}assertStartActive(e){if(e!==this.startGeneration)throw this.cancelled()}cancelled(){const e=new Error("麦克风启动已取消");return e.name="AbortError",e.agentCode="AUDIO_FIXED_TRACK_CANCELLED",e.retryable=!0,e}webMOpusUnsupported(e){const t=new Error("当前浏览器不支持 audio/webm;codecs=opus");return t.name="NotSupportedError",t.agentCode="AUDIO_WEBM_OPUS_UNSUPPORTED",t.retryable=!1,t.cause=e,t}webCodecsOpusUnsupported(e){const t=new Error("当前浏览器不支持 WebCodecs AudioEncoder Opus");return t.name="NotSupportedError",t.agentCode="AUDIO_WEBCODECS_OPUS_UNSUPPORTED",t.retryable=!1,t.cause=e,t}async startPcmWebMRecorder(e){var t=this;const n=globalThis,s=globalThis.AudioContext||n.webkitAudioContext;if(!s||void 0===globalThis.AudioWorkletNode)throw this.webCodecsOpusUnsupported(new Error("AudioWorklet 不可用"));const i=new y;await i.init();const r=new s({sampleRate:b});if(r.sampleRate!==b)throw await i.destroy(),await r.close().catch(()=>{}),this.webCodecsOpusUnsupported(new Error("浏览器无法创建 16000 Hz AudioContext"));this.pcmAudioContext=r,this.pcmEncoder=i;const a=URL.createObjectURL(new Blob([k],{type:"application/javascript"}));try{await r.audioWorklet.addModule(a)}finally{URL.revokeObjectURL(a)}if("suspended"===r.state&&await r.resume(),"running"!==r.state)throw this.audioContextSuspended();const o=r.createMediaStreamSource(e),h=new AudioWorkletNode(r,A,{numberOfInputs:1,numberOfOutputs:1,outputChannelCount:[1]}),c=++this.pcmGeneration;this.pcmSource=o,this.pcmWorklet=h,this.pcmEncodeQueue=Promise.resolve(),this.pcmStartedAt=this.monotonicEpochNow(),h.port.onmessage=e=>{var n,s,r;if("flushed"===(null==(n=e.data)?void 0:n.type))return null==(r=this.resolvePcmFlush)||r.call(this),void(this.resolvePcmFlush=null);if("frame"!==(null==(s=e.data)?void 0:s.type)||!(e.data.pcm instanceof ArrayBuffer))return;const a=this.pcmStartedAt+100*Number(e.data.sequence||0),o=new Int16Array(e.data.pcm);this.pcmEncodeQueue=this.pcmEncodeQueue.then(async function(){const n=await i.encode(o);if(c!==t.pcmGeneration||0===n.byteLength)return;const s=new Blob([n],{type:w});t.options.debugAudioChunks&&l("[Agent][Microphone] nearend PCM/WebCodecs chunk",s,{chunkMs:100,sampleRate:b,channels:1,sequence:e.data.sequence}),t.options.onFrame(s,a)}).catch(e=>{var t,n;const s=e instanceof Error?e:new Error("PCM/Opus 麦克风编码失败");s.name="AudioEncoderError",null==(t=(n=this.options).onError)||t.call(n,s)})},o.connect(h),h.connect(r.destination)}async stopPcmWebMRecorder(){const e=this.pcmWorklet,t=this.pcmSource,n=this.pcmAudioContext,s=this.pcmEncoder;if(!e&&!n&&!s)return;null==t||t.disconnect();let i=!0;e&&(i=await Promise.race([new Promise(t=>{this.resolvePcmFlush=()=>t(!0),e.port.postMessage({type:"flush"})}),new Promise(e=>globalThis.setTimeout(()=>e(!1),100))]),i||(this.resolvePcmFlush=null,e.port.onmessage=null)),await this.pcmEncodeQueue,this.pcmGeneration+=1,this.resolvePcmFlush=null,e&&(e.port.onmessage=null,e.disconnect(),e.port.close()),n&&"closed"!==n.state&&await n.close().catch(()=>{}),await(null==s?void 0:s.destroy()),this.pcmWorklet=null,this.pcmSource=null,this.pcmAudioContext=null,this.pcmEncoder=null,this.pcmEncodeQueue=Promise.resolve()}monotonicEpochNow(){const e=globalThis.performance;return e&&Number.isFinite(e.timeOrigin)?Math.round(e.timeOrigin+e.now()):Date.now()}audioContextSuspended(){const e=new Error("AudioContext 未运行,请在用户操作后重试");return e.name="NotAllowedError",e.agentCode="AUDIO_CONTEXT_SUSPENDED",e.retryable=!0,e}async stopMediaRecorder(){const e=this.mediaRecorder;if(this.mediaRecorder=null,!e)return;const t="inactive"!==e.state;await new Promise(n=>{let s=!1;const i=()=>{s||(s=!0,globalThis.clearTimeout(a),n())},r=e.onerror,a=globalThis.setTimeout(i,1e3);if(e.onstop=i,e.onerror=t=>{null==r||r.call(e,t),i()},t)try{e.stop()}catch(e){i()}}),e.ondataavailable=null,e.onerror=null,e.onstop=null}}const C=new Uint8Array([26,69,223,163]);function R(e,t){const n=globalThis.avatarSDKLogger;null==n||null==n.log||n.log("[Agent][AudioUplink]",e,t)}function T(e,t,n){if(1!==e&&2!==e)throw new Error(`AU stream_id 无效: ${e}`);if(!Number.isFinite(t)||t<0||t>4294967295)throw new Error(`AU ts_ms 无效: ${t}`);const s=n instanceof ArrayBuffer?new Uint8Array(n):new Uint8Array(n.buffer,n.byteOffset,n.byteLength),i=new ArrayBuffer(8+s.byteLength),r=new DataView(i);return r.setUint8(0,65),r.setUint8(1,85),r.setUint8(2,1),r.setUint8(3,e),r.setUint32(4,Math.floor(t),!1),new Uint8Array(i,8).set(s),i}async function O(e){if(Array.isArray(e)){const t=await Promise.all(e.map(O)),n=new Uint8Array(t.reduce((e,t)=>e+t.byteLength,0));let s=0;for(const e of t)n.set(e,s),s+=e.byteLength;return n}return e instanceof Blob?new Uint8Array(await e.arrayBuffer()):e instanceof ArrayBuffer?new Uint8Array(e):new Uint8Array(e.buffer,e.byteOffset,e.byteLength)}class P{constructor(e){this.connectionStartedAt=null,this.connectionGeneration=0,this.sendChain=Promise.resolve(),this.failed=!1,this.pendingFarend=new Map,this.pendingNearend=new Map,this.closedNearendBuckets=new Set,this.lastTimestampByStream=new Map,this.lastSentTimestampByStream=new Map,this.pendingWebmPrefixByStream=new Map,this.farendStreamState="unknown",this.options=e}reset(e=null){this.connectionGeneration+=1,this.connectionStartedAt=e,this.pendingFarend.clear(),this.pendingNearend.clear(),this.closedNearendBuckets.clear(),this.lastTimestampByStream.clear(),this.lastSentTimestampByStream.clear(),this.pendingWebmPrefixByStream.clear(),this.farendStreamState="unknown",this.failed=!1,this.sendChain=Promise.resolve()}startInput(){this.failed=!1,this.pendingFarend.clear(),this.pendingNearend.clear(),this.closedNearendBuckets.clear(),this.pendingWebmPrefixByStream.clear(),this.farendStreamState="unknown"}clearFarend(){this.pendingFarend.clear(),this.pendingWebmPrefixByStream.delete(2),this.farendStreamState="unknown"}offerFarend(e){var t;if(!1===e.isFirstChunk&&"ready"!==this.farendStreamState)return void R("farend.drop",{reason:"awaiting_webm_header",timestamp:e.timestamp,isFirstChunk:!1});const n=this.normalizeBlock(2,e);if(!n)return void(e.isFirstChunk&&this.abandonFarendStream());e.isFirstChunk&&(this.pendingFarend.clear(),this.pendingWebmPrefixByStream.delete(2),this.farendStreamState="ready");const s=null!=(t=this.pendingFarend.get(n.frameKey))?t:[];s.push(n),this.pendingFarend.set(n.frameKey,s),R("farend.offer",{tsMs:n.tsMs,frameKey:n.frameKey,isFirstChunk:Boolean(n.isFirstChunk),pendingNearend:[...this.pendingNearend.keys()]}),this.flushOlderNearend(n.frameKey),this.prune(n.frameKey)}offerNearend(e){var t;const n=this.normalizeBlock(1,e);if(!n||this.closedNearendBuckets.has(n.frameKey))return;const s=null!=(t=this.pendingNearend.get(n.frameKey))?t:[];s.push(n),this.pendingNearend.set(n.frameKey,s),this.flushExpiredNearend(n.frameKey),this.prune(n.frameKey)}drain(){for(const e of[...this.pendingNearend.keys()].sort((e,t)=>e-t))this.flushBucket(e,!0);return this.sendChain}normalizeBlock(e,n){const s=this.connectionStartedAt;if(null===s||!Number.isFinite(n.timestamp))return null;const i=Math.max(0,Math.floor(n.timestamp-s)),r=Math.floor(i/100),a=this.lastTimestampByStream.get(e);return void 0!==a&&i<=a?null:(this.lastTimestampByStream.set(e,i),t({},n,{streamId:e,tsMs:i,frameKey:r}))}enqueue(e){var t=this;if(this.failed)return;const n=this.connectionGeneration;this.sendChain=this.sendChain.then(async function(){if(!t.failed)for(const i of e){var s;const e=await O(null!=(s=i.dataParts)?s:i.data);if(t.failed||n!==t.connectionGeneration||null===t.connectionStartedAt||0===e.byteLength)continue;2===i.streamId&&i.isFirstChunk&&t.pendingWebmPrefixByStream.delete(2);const r=t.coalesceSplitWebmMagic(i.streamId,e);if(!r)continue;const a=t.lastSentTimestampByStream.get(i.streamId);void 0!==a&&i.tsMs<=a||(t.options.sendBinary(T(i.streamId,i.tsMs,r)),t.lastSentTimestampByStream.set(i.streamId,i.tsMs),R("frame.send",{streamId:i.streamId,tsMs:i.tsMs,bytes:r.byteLength}))}}).catch(e=>{var t,s;if(n!==this.connectionGeneration)return;this.failed=!0,this.pendingFarend.clear(),this.pendingNearend.clear();const i=e instanceof Error?e:new Error("AU 音频上行失败");null==(t=(s=this.options).onError)||t.call(s,i)})}coalesceSplitWebmMagic(e,t){const n=this.pendingWebmPrefixByStream.get(e);if(n){const s=new Uint8Array(n.byteLength+t.byteLength);s.set(n),s.set(t,n.byteLength),this.pendingWebmPrefixByStream.delete(e),t=s}return t.byteLength<C.byteLength&&t.every((e,t)=>e===C[t])?(this.pendingWebmPrefixByStream.set(e,t.slice()),null):t}flushOlderNearend(e){for(const t of[...this.pendingNearend.keys()].sort((e,t)=>e-t)){if(t>=e)break;this.flushBucket(t,!0)}}flushExpiredNearend(e){this.flushOlderNearend(e-1)}flushBucket(e,t=!1){const n=this.takeNearend(e);if(!n)return;const s=e-1,i=this.pendingFarend.has(e)||t&&this.pendingFarend.has(s)?this.takeFarendThrough(e):null;this.closedNearendBuckets.add(e),R("bucket.flush",{nearendFrameKey:e,pairedFarend:Boolean(i),pendingFarend:[...this.pendingFarend.keys()]}),this.enqueue(i?[i,n]:[n])}takeNearend(e){const n=this.pendingNearend.get(e);return this.pendingNearend.delete(e),null!=n&&n.length?t({},n[0],{dataParts:n.flatMap(e=>{var t;return null!=(t=e.dataParts)?t:[e.data]})}):null}takeFarendThrough(e){const n=[];for(const t of[...this.pendingFarend.keys()].sort((e,t)=>e-t)){if(t>e)break;n.push(...this.pendingFarend.get(t)),this.pendingFarend.delete(t)}return 0===n.length?null:t({},n[n.length-1],{dataParts:n.map(e=>e.data),isFirstChunk:n.some(e=>e.isFirstChunk)})}abandonFarendStream(){R("farend.drop",{reason:"stale_bucket_abandons_webm_stream",pendingFarend:[...this.pendingFarend.keys()]}),this.pendingFarend.clear(),this.pendingWebmPrefixByStream.delete(2),this.farendStreamState="awaiting-header"}prune(e){const t=e-3;[...this.pendingFarend.keys()].some(e=>e<t)&&this.abandonFarendStream();for(const e of this.pendingNearend.keys())e<t&&this.flushBucket(e,!0);for(const e of this.closedNearendBuckets)e<t&&this.closedNearendBuckets.delete(e)}}const _=["e2eServer","authToken","audio","reconnect","agentCallbacks","webSocketCtor","asr_id","asr_config","tts_config","features","extras","llm_id","brain_config","session_speak_req_id"];function I(e){if(void 0===e)return!1;const t=JSON.stringify(e);return void 0!==t&&"{}"!==t}const F={1006:"E2E WebSocket 异常断开:未收到 Close frame,请检查 E2EMPServer 日志或中间网络",4e3:"E2E WebSocket 路径未知",4001:"E2E Token 无效",4002:"E2E Session 不存在",4009:"E2E 配额超限",4010:"E2E 服务端主动断开"},D=new Set([1001,1006,1011,1012,1013,1014]);function M(e,t,n){if(void 0!==n&&(!n||Array.isArray(n)||"object"!=typeof n))throw new Error(`Agent ${e} JSON Config 必须是对象`);if(void 0!==t&&I(n))throw new Error(`Agent ${e} 配置不能同时提供 ID 和 JSON Config`)}function N(e,t){if(void 0!==t&&(!t||Array.isArray(t)||"object"!=typeof t))throw new Error(`Agent ${e} 必须是对象`)}class U extends e{constructor(e){var n,s,i;M("ASR",e.asr_id,e.asr_config),M("TTS",void 0,e.tts_config),M("Brain",e.llm_id,e.brain_config),N("features",e.features),N("extras",e.extras);const{e2eServer:r,authToken:a,audio:o,reconnect:l,agentCallbacks:h,webSocketCtor:c,asr_id:u,asr_config:d,tts_config:p,features:m,extras:f,llm_id:g,brain_config:S,session_speak_req_id:E}=e,y=function(e,t){if(null==e)return{};var n={};for(var s in e)if({}.hasOwnProperty.call(e,s)){if(-1!==t.indexOf(s))continue;n[s]=e[s]}return n}(e,_),w=y.onMessage,b=y.onRenderChange,A=y.onNetworkInfo,k=y.onSpeakStateChange,v=y.onVoiceStateChange,C=y.onAudioPlaybackData,R=e=>{var t;null==(t=F)||t.forwardFarendAudio(e),null==C||C(e)},T=null!=E?E:1,O=function(e){if(!e||!I(e))return;const n=e.extra_body;if(void 0!==n&&(!n||Array.isArray(n)||"object"!=typeof n))throw new Error("Agent Brain extra_body 必须是 JSON 对象");return t({},e)}(S);let F;super(t({},y,{session_speak_req_id:T,sessionRequestData:t({},void 0!==u?{asr_id:u}:{},I(d)?{asr_config:d}:{},I(p)?{tts_config:p}:{},I(m)?{features:m}:{},I(f)?{extras:f}:{},void 0!==g?{llm_id:g}:{},void 0!==O?{brain_config:O}:{},{session_speak_req_id:T}),enableClientInterrupt:null==(n=y.enableClientInterrupt)||n,onMessage:e=>{var t,n;w(e),null!=(t=F)&&t.reportingInitializationFailure||null==(n=F)||n.emitError("sdk",String(e.code||"AVATAR_ERROR"),e.message||"Avatar SDK 错误",!0,e)},onRenderChange:e=>{var t;null==b||b(e),null==(t=F)||t.safeEmit(()=>null==h||null==h.onRenderChange?void 0:h.onRenderChange(e))},onNetworkInfo:e=>{var t;null==A||A(e),null==(t=F)||t.safeEmit(()=>null==h||null==h.onNetworkInfo?void 0:h.onNetworkInfo(e))},onSpeakStateChange:(e,t)=>{var n,s,i;null==k||k(e,t),null==(n=F)||n.safeEmit(()=>null==h||null==h.onSpeakStateChange?void 0:h.onSpeakStateChange({state:e,clientSpeakId:t})),["speak_start","start","started"].includes(e)&&(null==(s=F)||s.emitConversation({state:"speaking"})),["speak_end","end","ended","completed","finish","finished"].includes(e)&&(null==(i=F)||i.emitConversation({state:"completed"}))},onVoiceStateChange:(e,t,n)=>{var s;"end"===e&&(null==(s=F)||s.forwardVoiceEnd()),null==v||v(e,t,n)}},C?{onAudioPlaybackData:R}:{})),this.e2eClient=null,this.microphone=null,this.stopASRPromise=null,this.initializationPromise=null,this.rejectInitialization=null,this.initialSessionAttempt=null,this.initialSessionGeneration=0,this.initialTtsaReadySessionIds=new Set,this.initialTtsaReadyBeforeSession=!1,this.asrStartGeneration=0,this.agentState="idle",this.asrState="idle",this.agentDestroyed=!1,this.agentDestroying=!1,this.suppressNextVoiceEnd=!1,this.farendAudioEnabled=!1,this.reconnectTimer=null,this.reconnectAttempt=0,this.reconnectInFlight=!1,this.sessionReloadFallback=!1,this.waitingForTtsaReload=!1,this.stateBeforeReconnect="ready",this.reportingInitializationFailure=!1,this.handleOnlineForReconnect=()=>{"reconnecting"!==this.agentState||this.reconnectInFlight||this.sessionReloadFallback||(this.clearReconnectTimer(),this.scheduleE2EReconnect(!0))},F=this,this.agentOptions={e2eServer:r,authToken:a,audio:o,requestedEnableAec:"boolean"==typeof(null==m||null==(s=m.speech_frontend)?void 0:s.enable_aec)?m.speech_frontend.enable_aec:void 0,callbacks:h,webSocketCtor:c,debugAudioChunks:Boolean(y.enableDebugger)},this.audioInputEnabled=null!=(i=null==o?void 0:o.inputEnabled)&&i,this.originalOnAudioPlaybackData=C,this.playbackDataHandler=R,this.reconnectOptions=this.normalizeReconnectOptions(l),this.audioUplink=new P({sendBinary:e=>{var t;return null==(t=this.e2eClient)?void 0:t.sendBinary(e)},onError:e=>this.handleAudioUplinkError(e)})}getAgentState(){return this.agentState}getState(){return this.getAgentState()}getASRState(){return this.asrState}init(e={}){if(this.initializationPromise)return this.initializationPromise;const t=this.initialize(e).finally(()=>{this.initializationPromise===t&&(this.initializationPromise=null)});return this.initializationPromise=t,t}async initialize(e){this.assertNotDestroyed(),this.setAgentState("initializing");const n=new Promise((e,t)=>{this.rejectInitialization=t});n.catch(()=>{});try{var s;const i=t({},e,{onDownloadProgress:e.onDownloadProgress||(()=>{})}),r=await super.init(i);if(this.assertNotDestroyed(),!r)throw new Error("XmovAvatar 初始化未返回 sessionInfo");for(this.applySessionFeatures(r),this.assertNotDestroyed(),this.beginInitialSession(r);;){const e=this.initialSessionAttempt;if(!e)throw new Error("Agent 初始化缺少会话连接任务");if(await Promise.race([Promise.all([e.e2ePromise,e.ttsaReadyPromise]),n]),e===this.initialSessionAttempt)break}this.assertNotDestroyed(),this.assertE2EOpen();const a=(null==(s=this.initialSessionAttempt)?void 0:s.sessionInfo)||r;return this.setAgentState("ready"),this.start(),a}catch(e){if(!this.agentDestroyed&&!this.agentDestroying){var i;null==(i=this.e2eClient)||i.disconnect(1e3,"agent_init_failed"),this.e2eClient=null,await this.cleanupBeforeInitComplete("agent_init_failed");try{this.setAgentState("failed")}catch(e){}this.reportingInitializationFailure=!0;try{this.reportInitializationFailure("Agent 初始化失败",e)}catch(e){}finally{this.reportingInitializationFailure=!1}try{this.emitError("sdk","AGENT_INIT_FAILED","Agent 初始化失败",!0,e)}catch(e){}this.agentDestroyed=!0}throw e}finally{var r;this.rejectInitialization=null,this.initialSessionGeneration+=1,null==(r=this.initialSessionAttempt)||r.resolveTtsaReady(),this.initialSessionAttempt=null,this.initialTtsaReadySessionIds.clear(),this.initialTtsaReadyBeforeSession=!1}}beginInitialSession(e){const t=this.initialSessionAttempt,n=++this.initialSessionGeneration;null==t||t.resolveTtsaReady();let s=()=>{};const i=new Promise(e=>{s=e}),r=this.replaceE2EConnection(e.e2e_resp).catch(e=>{if(n===this.initialSessionGeneration)throw e}),a={sessionInfo:e,e2ePromise:r,ttsaReadyPromise:i,resolveTtsaReady:s};return this.initialSessionAttempt=a,this.initialTtsaReadySessionIds.delete(e.session_id)?a.resolveTtsaReady():!t&&this.initialTtsaReadyBeforeSession&&(this.initialTtsaReadyBeforeSession=!1,a.resolveTtsaReady()),r.catch(()=>{}),r}start(){var e;"initializing"!==this.agentState&&"running"!==this.agentState&&("reconnecting"!==this.agentState?(this.assertE2EOpen(),super.start(),this.setAgentState("running")):this.waitingForTtsaReload&&null!=(e=this.e2eClient)&&e.isOpen&&("running"===this.stateBeforeReconnect&&super.start(),this.finishReconnect()))}onTtsaReady(e){if(this.rejectInitialization){const t=this.initialSessionAttempt,n=null==e?void 0:e.session_id;!t||n&&t.sessionInfo.session_id!==n?n?this.initialTtsaReadySessionIds.add(n):this.initialTtsaReadyBeforeSession=!0:t.resolveTtsaReady()}else"reconnecting"!==this.agentState?"running"===this.agentState&&super.start():this.start()}abortInitialization(e){var t;null==(t=this.rejectInitialization)||t.call(this,e)}async stop(){if(this.agentDestroyed)return;const e="reconnecting"===this.agentState;e&&(this.stateBeforeReconnect="stopped"),await this.stopASR(),await super.stop(),e||this.setAgentState("stopped")}speak(...e){var t;return(null==(t=e[2])||t)&&(this.suppressNextVoiceEnd=!0),super.speak(...e)}async destroy(e="user"){var t;if(this.agentDestroyed)"destroyed"!==this.agentState&&this.setAgentState("destroyed");else if(!this.agentDestroying){this.agentDestroying=!0,this.abortInitialization(new Error("Agent 初始化已取消")),this.cancelReconnect(),this.cancelSessionRestart();try{await this.stopASR()}catch(e){this.emitError("sdk","AGENT_ASR_STOP_FAILED","Agent 销毁时停止 ASR 失败",!1,e)}null==(t=this.e2eClient)||t.disconnect(1e3,e),this.e2eClient=null,this.audioUplink.reset();try{await super.destroy(e)}finally{this.agentDestroyed=!0,this.agentDestroying=!1,this.setAgentState("destroyed")}}}async ask(e){this.assertRunning(),this.suppressNextVoiceEnd=!1,this.emitConversation({state:"asking",text:e}),this.sendControl({type:"ask",message:{text:e}})}async speakByE2E(e){this.assertRunning(),this.suppressNextVoiceEnd=!1,this.emitConversation({state:"speaking-directly",text:e}),this.sendControl({type:"speak",message:{text:e,is_start:!0,is_end:!0}})}async startASR(e={}){var t;if(this.assertRunning(),this.suppressNextVoiceEnd=!1,this.stopASRPromise&&(await this.stopASRPromise,this.assertRunning()),null!=(t=this.microphone)&&t.isRecording)return;this.assertAudioChunkDuration();const n=e.inputStream?this.cloneASRInputStream(e.inputStream):void 0,s=++this.asrStartGeneration,i=!this.audioInputEnabled,r=new v({audio:this.agentOptions.audio,debugAudioChunks:this.agentOptions.debugAudioChunks,inputStream:n,usePcmWebCodecs:this.farendAudioEnabled,onFrame:(e,t)=>{var n;if("running"===this.agentState&&null!=(n=this.e2eClient)&&n.isOpen)try{var s;this.sendAudioFrame({data:e,timestamp:null!=t?t:Date.now()-((null==(s=this.agentOptions.audio)?void 0:s.chunkMs)||a.chunkMs)})}catch(e){this.stopASRAfterSocketClose()}},onError:e=>this.handleMicrophoneError(r,e)});this.microphone=r;try{this.audioUplink.startInput(),i&&this.enableAudioInput(),this.emitConversation({state:"asking"}),this.assertASRStartActive(s),this.setASRState("requesting-permission"),this.assertASRStartActive(s),this.setASRState("starting"),this.assertASRStartActive(s),await r.start(),this.assertRunning(),this.farendAudioEnabled&&this.restartAudioPlaybackCapture(),this.setASRState("listening")}catch(e){var o,l;await r.stop();const t="AUDIO_FIXED_TRACK_CANCELLED"===(null==e?void 0:e.agentCode),n=s===this.asrStartGeneration,a=!this.agentDestroyed&&!this.agentDestroying&&"running"===this.agentState&&Boolean(null==(o=this.e2eClient)?void 0:o.isOpen);if(this.microphone===r&&(this.microphone=null),n&&!t&&i&&null!=(l=this.e2eClient)&&l.isOpen)try{this.disableAudioInput()}catch(e){}throw!n||this.agentDestroyed||this.agentDestroying||(t&&a?this.setASRState("idle"):t||this.setASRState("failed")),t&&!a&&this.assertRunning(),e}}cloneASRInputStream(e){const t=e.getAudioTracks().find(e=>"ended"!==e.readyState);if(!t)throw new Error("ASR 输入流不包含有效音频轨道");return new MediaStream([t.clone()])}assertASRStartActive(e){if(e===this.asrStartGeneration)return;const t=new Error("麦克风启动已取消");throw t.name="AbortError",t.agentCode="AUDIO_FIXED_TRACK_CANCELLED",t.retryable=!0,t}stopASR(){if(this.asrStartGeneration+=1,this.stopASRPromise)return this.stopASRPromise;const e=this.microphone;var t;return null!=e&&e.isRecording?(this.microphone=null,this.stopASRPromise=this.finishStopASR(e).finally(()=>{this.stopASRPromise=null}),this.stopASRPromise):(null!=(t=this.e2eClient)&&t.isOpen&&this.disableAudioInput(),Promise.resolve())}async finishStopASR(e,t=!0){this.setASRState("stopping"),await e.stop(),await this.audioUplink.drain(),t?(this.disableAudioInput(),this.setASRState("idle")):this.setASRState("failed")}async interruptConversation(e="user"){this.agentDestroyed||(this.sendControl({type:"interrupt"}),super.interrupt("speak"),this.emitConversation({state:"interrupted"}))}async onSessionReloaded(e){if(!this.agentDestroyed&&!this.agentDestroying){if(this.initializationPromise&&"initializing"===this.agentState)return this.applySessionFeatures(e),void await this.beginInitialSession(e);"reconnecting"!==this.agentState&&(this.stateBeforeReconnect=this.readRestorableState(),this.setAgentState("reconnecting")),this.clearReconnectTimer(),this.sessionReloadFallback=!0,this.applySessionFeatures(e),await this.replaceE2EConnection(e.e2e_resp),this.waitingForTtsaReload=!0,this.sessionReloadFallback=!1}}onSessionReloadExhausted(e){this.rejectInitialization?this.abortInitialization(e instanceof Error?e:new Error("Agent 初始化期间 TTSA Session 恢复失败")):"reconnecting"===this.agentState&&this.failReconnect(e)}reloadSuccess(){super.reloadSuccess()}async replaceE2EConnection(e){const{wsUrl:t,token:n}=this.resolveE2EConnection(e),i=this.e2eClient;let r;this.audioUplink.reset(),r=new s({wsUrl:t,token:n,audioInputEnabled:this.audioInputEnabled,WebSocketCtor:this.agentOptions.webSocketCtor,onStateChange:e=>{this.e2eClient===r&&("open"===e&&this.audioUplink.reset(Date.now()),this.safeEmit(()=>{var t;return null==(t=this.agentOptions.callbacks)||null==t.onSocketStateChange?void 0:t.onSocketStateChange(e)}))},onEvent:e=>{this.e2eClient===r&&this.handleServerEvent(e)},onError:e=>{this.e2eClient===r&&"initializing"!==this.agentState&&"reconnecting"!==this.agentState&&this.emitError("network","E2E_SOCKET_ERROR",e.message,!0,e)},onClose:e=>{this.e2eClient===r&&this.handleSocketClose(e)}}),this.e2eClient=r,null==i||i.disconnect(1e3,"e2e_connection_replaced"),await r.connect()}resolveE2EConnection(e){const t=Boolean(null==e?void 0:e.ws_url),n=Boolean(null==e?void 0:e.e2e_token);if(t!==n)throw new Error("统一 session 响应的 e2e_resp 必须同时返回 ws_url 和 e2e_token");if(t&&n)return{wsUrl:null==e?void 0:e.ws_url,token:null==e?void 0:e.e2e_token};const s=Boolean(this.agentOptions.e2eServer);if(s!==Boolean(this.agentOptions.authToken))throw new Error("e2eServer 和 authToken 必须同时配置");if(!s)throw new Error("统一 session 响应未返回 e2e_resp.ws_url,且未配置 e2eServer");return{wsUrl:this.agentOptions.e2eServer,token:this.agentOptions.authToken}}sendControl(e){try{var t;null==(t=this.e2eClient)||t.send(e)}catch(e){throw this.emitError("network","E2E_SEND_FAILED","E2E WebSocket 发送失败",!0,e),e}}assertAudioChunkDuration(){var e;const t=(null==(e=this.agentOptions.audio)?void 0:e.chunkMs)||a.chunkMs;if(!this.farendAudioEnabled||t===a.chunkMs)return;const n=new Error("AU v1 音频分片固定为 100ms");throw n.name="NotSupportedError",n.agentCode="AUDIO_CHUNK_DURATION_UNSUPPORTED",n.retryable=!1,this.emitError("sdk",n.agentCode,n.message,n.retryable,n),n}sendAudioFrame(e){if(this.farendAudioEnabled)this.agentOptions.debugAudioChunks&&l("[Agent][E2E] nearend AU chunk",e.data,{timestamp:e.timestamp,stream_id:1}),this.audioUplink.offerNearend(e);else{this.agentOptions.debugAudioChunks&&l("[Agent][E2E] nearend raw WebM/Opus chunk",e.data,{timestamp:e.timestamp});try{var t;null==(t=this.e2eClient)||t.sendBinary(e.data)}catch(e){this.handleAudioUplinkError(e instanceof Error?e:new Error("E2E nearend 音频帧发送失败"))}}}handleAudioUplinkError(e){this.emitError("network","E2E_AUDIO_SEND_FAILED",this.farendAudioEnabled?"E2E AU 音频帧发送失败":"E2E 音频帧发送失败",!0,e);const t=this.microphone;if(null==t||!t.isRecording||this.stopASRPromise)return;this.setASRState("failed"),this.microphone=null;const n=this.finishFailedASR(t).finally(()=>{this.stopASRPromise===n&&(this.stopASRPromise=null)});this.stopASRPromise=n}applySessionFeatures(e){var t,n,s,i,r,a;const o=null!=(t=null==(n=e.e2e_resp)||null==(n=n.features)||null==(n=n.speech_frontend)?void 0:n.enable_aec)?t:null==(s=e.features)||null==(s=s.speech_frontend)?void 0:s.enable_aec;if(this.farendAudioEnabled="boolean"==typeof o?o:null!=(i=null!=(r=this.agentOptions.requestedEnableAec)?r:null==(a=this.agentOptions.audio)?void 0:a.echoCancellationEnabled)&&i,this.farendAudioEnabled||this.audioUplink.clearFarend(),this.farendAudioEnabled||this.originalOnAudioPlaybackData)return this.setAudioPlaybackDataHandler(this.playbackDataHandler),void(this.farendAudioEnabled&&this.enableAudioPlaybackCapture());this.setAudioPlaybackDataHandler(void 0)}forwardFarendAudio(e){var t,n;const s=Boolean(null==(t=this.microphone)?void 0:t.isRecording),i=Boolean(null==(n=this.e2eClient)?void 0:n.isOpen);var r;this.farendAudioEnabled&&s&&i?(this.agentOptions.debugAudioChunks&&l("[Agent][E2E] farend AU chunk",e.data,{timestamp:e.timestamp,stream_id:2,codec:e.codec,sampleRate:e.sampleRate,channels:1,samples:e.samples,speech_id:e.speech_id,isFirstChunk:e.isFirstChunk}),this.audioUplink.offerFarend({data:e.data,timestamp:e.timestamp,isFirstChunk:e.isFirstChunk})):null==(r=globalThis.avatarSDKLogger)||null==r.log||r.log("[Agent][AudioUplink]","farend.drop",{reason:this.farendAudioEnabled?s?"e2e_not_open":"microphone_not_recording":"aec_disabled",timestamp:e.timestamp,isFirstChunk:e.isFirstChunk})}handleMicrophoneError(e,t){var n;const s=t,i="MediaRecorderError"===t.name,r="AudioEncoderError"===t.name,a=i||r,o="NotSupportedError"===t.name,l=Boolean(s.agentCode);if(a&&this.microphone===e&&"stopping"!==this.asrState){this.microphone=null;const t=this.finishFailedASR(e).finally(()=>{this.stopASRPromise===t&&(this.stopASRPromise=null)});this.stopASRPromise=t}this.emitError(a?"asr":l||o?"sdk":"permission",s.agentCode||(i?"MEDIA_RECORDER_ERROR":r?"AUDIO_ENCODER_ERROR":"MICROPHONE_ERROR"),t.message,null!=(n=s.retryable)?n:!o,t)}async finishFailedASR(e){try{await e.stop(),await this.audioUplink.drain()}catch(e){this.emitError("sdk","MICROPHONE_CLEANUP_FAILED","麦克风异常后的资源清理失败",!1,e)}try{this.disableAudioInput()}catch(e){}}enableAudioInput(){var e;this.audioInputEnabled||(this.sendControl({type:"event",message:"audio_input_on"}),this.audioInputEnabled=!0,null==(e=this.e2eClient)||e.setAudioInputEnabled(!0))}disableAudioInput(){var e;this.audioInputEnabled&&(this.sendControl({type:"event",message:"audio_input_off"}),this.audioInputEnabled=!1,null==(e=this.e2eClient)||e.setAudioInputEnabled(!1))}handleServerEvent(e){if(!this.agentDestroyed&&!this.agentDestroying)switch(e.type){case"pong":default:return;case"asr_result":return void this.handleASRResult(e);case"llm_response":return void this.handleLLMResponse(e);case"semantic_judge_round_result":return void this.handleSemanticJudgeResult(e);case"error":return void this.handleBackendError(e)}}handleASRResult(e){const t={text:"string"==typeof e.text?e.text:"",isFinal:!0===e.is_final,raw:e};this.safeEmit(()=>{var e;return null==(e=this.agentOptions.callbacks)||null==e.onASRResult?void 0:e.onASRResult(t)})}handleLLMResponse(e){if("chunk"!==e.event&&"done"!==e.event)return;const n=e.usage,s=t({event:e.event},"string"==typeof e.text?{text:e.text}:{},!0===e.is_first?{isFirst:!0}:{},n?{usage:{promptTokens:this.readFiniteNumber(n.prompt_tokens),completionTokens:this.readFiniteNumber(n.completion_tokens),totalTokens:this.readFiniteNumber(n.total_tokens),cachedTokens:this.readFiniteNumber(n.cached_tokens)}}:{},{raw:e});this.safeEmit(()=>{var e;return null==(e=this.agentOptions.callbacks)||null==e.onLLMResponse?void 0:e.onLLMResponse(s)})}handleSemanticJudgeResult(e){if(!e.message||"object"!=typeof e.message)return;const t=e.message,n={query:"string"==typeof t.query?t.query:"",meaningful:!0===t.meaningful,action:"string"==typeof t.action?t.action:"",raw:e};this.safeEmit(()=>{var e;return null==(e=this.agentOptions.callbacks)||null==e.onSemanticJudgeResult?void 0:e.onSemanticJudgeResult(n)})}forwardVoiceEnd(){var e;if(this.suppressNextVoiceEnd)this.suppressNextVoiceEnd=!1;else if(!this.agentDestroyed&&!this.agentDestroying&&"running"===this.agentState&&null!=(e=this.e2eClient)&&e.isOpen)try{this.sendControl({type:"event",message:"voice_end"})}catch(e){}}readFiniteNumber(e){return"number"==typeof e&&Number.isFinite(e)?e:0}handleBackendError(e){var t;const n=["permission","network","asr","brain","ttsa","quota","sdk"].includes(e.domain)?e.domain:"sdk",s=String(null!=(t=e.code)?t:"BACKEND_ERROR"),i="string"==typeof e.message?e.message:"E2E 后端错误";"asr"===n&&this.stopASRAfterSocketClose(),this.emitConversation({state:"failed"}),this.emitError(n,s,i,"quota"!==n,e)}handleSocketClose(e){if(this.agentDestroyed||this.agentDestroying)return;if(this.rejectInitialization)return void this.abortInitialization(new Error(`Agent 初始化期间 E2E WebSocket 已关闭: ${e.code} ${e.reason||""}`.trim()));if(this.audioUplink.reset(),this.stopASRAfterSocketClose(),this.shouldReconnect(e))return void this.beginReconnect(e);this.setAgentState("failed");const t=4009===e.code?"quota":"network";this.emitError(t,`E2E_CLOSE_${e.code}`,e.reason||F[e.code]||"E2E WebSocket 已关闭","quota"!==t,{code:e.code,reason:e.reason,wasClean:e.wasClean})}shouldReconnect(e){return this.reconnectOptions.enabled&&"idle"!==this.agentState&&"initializing"!==this.agentState&&"destroyed"!==this.agentState&&D.has(e.code)}beginReconnect(e){var t;"reconnecting"!==this.agentState&&(this.stateBeforeReconnect=this.readRestorableState(),this.reconnectAttempt=0,this.sessionReloadFallback=!1,this.setAgentState("reconnecting"),null==globalThis.addEventListener||globalThis.addEventListener("online",this.handleOnlineForReconnect),this.emitError("network","E2E_RECONNECTING","E2E WebSocket 已断开,正在重连",!0,{code:e.code,reason:e.reason,wasClean:e.wasClean})),this.audioInputEnabled=!1,null==(t=this.e2eClient)||t.setAudioInputEnabled(!1),this.scheduleE2EReconnect(!1)}scheduleE2EReconnect(e){if(this.agentDestroyed||this.agentDestroying||"reconnecting"!==this.agentState||null!==this.reconnectTimer||this.reconnectInFlight||this.sessionReloadFallback)return;if(this.reconnectAttempt>=this.reconnectOptions.maxAttempts)return void this.fallbackToSessionReload();const t=e?0:this.getReconnectDelay(this.reconnectAttempt);this.reconnectTimer=globalThis.setTimeout(()=>{this.reconnectTimer=null,this.attemptE2EReconnect()},t)}async attemptE2EReconnect(){const e=this.e2eClient;if(e&&!this.agentDestroyed&&!this.agentDestroying&&"reconnecting"===this.agentState){this.reconnectInFlight=!0,this.reconnectAttempt+=1;try{await e.connect(),this.e2eClient!==e||this.agentDestroyed||this.agentDestroying||this.waitingForTtsaReload||this.finishReconnect()}catch(t){this.e2eClient!==e||this.agentDestroyed||this.agentDestroying||this.scheduleE2EReconnect(!1)}finally{this.reconnectInFlight=!1,this.e2eClient!==e||"reconnecting"!==this.agentState||null!==this.reconnectTimer||this.sessionReloadFallback||this.scheduleE2EReconnect(!1)}}}async fallbackToSessionReload(){if(!(this.sessionReloadFallback||this.agentDestroyed||this.agentDestroying)){this.sessionReloadFallback=!0,this.clearReconnectTimer();try{await this.restartSessionForTransport("e2e_reconnect_exhausted")}catch(e){this.failReconnect(e)}}}finishReconnect(){const e=this.stateBeforeReconnect;this.cancelReconnect(),"idle"!==this.asrState&&this.setASRState("idle"),this.setAgentState(e)}failReconnect(e){this.cancelReconnect(),this.setAgentState("failed"),this.emitConversation({state:"failed"}),this.emitError("network","E2E_RECONNECT_EXHAUSTED","E2E WebSocket 重连失败,请重新初始化 Agent",!1,e)}cancelReconnect(){this.clearReconnectTimer(),null==globalThis.removeEventListener||globalThis.removeEventListener("online",this.handleOnlineForReconnect),this.reconnectAttempt=0,this.reconnectInFlight=!1,this.sessionReloadFallback=!1,this.waitingForTtsaReload=!1}clearReconnectTimer(){null!==this.reconnectTimer&&(globalThis.clearTimeout(this.reconnectTimer),this.reconnectTimer=null)}getReconnectDelay(e){const t=Math.min(this.reconnectOptions.maxDelayMs,this.reconnectOptions.initialDelayMs*2**e);return 0===t?0:Math.round(t*(.8+.4*Math.random()))}readRestorableState(){return"running"===this.agentState||"stopped"===this.agentState||"ready"===this.agentState?this.agentState:"ready"}normalizeReconnectOptions(e){var t;const n=this.readNonNegativeNumber(null==e?void 0:e.initialDelayMs,500);return{enabled:null==(t=null==e?void 0:e.enabled)||t,maxAttempts:Math.max(1,Math.floor(this.readNonNegativeNumber(null==e?void 0:e.maxAttempts,6))),initialDelayMs:n,maxDelayMs:Math.max(n,this.readNonNegativeNumber(null==e?void 0:e.maxDelayMs,8e3))}}readNonNegativeNumber(e,t){return"number"==typeof e&&Number.isFinite(e)&&e>=0?e:t}stopASRAfterSocketClose(){if(this.asrStartGeneration+=1,this.stopASRPromise)return;const e=this.microphone;null!=e&&e.isRecording&&(this.microphone=null,this.stopASRPromise=this.finishStopASR(e,!1).catch(e=>{this.emitError("sdk","MICROPHONE_CLEANUP_FAILED","WebSocket 断开后清理麦克风失败",!1,e)}).finally(()=>{this.stopASRPromise=null}))}setAgentState(e){var t;this.agentState=e,this.agentDestroyed&&"destroyed"!==e||null==(t=this.agentOptions.callbacks)||null==t.onAgentStateChange||t.onAgentStateChange(e)}setASRState(e){this.asrState=e,this.safeEmit(()=>{var t;return null==(t=this.agentOptions.callbacks)||null==t.onASRStateChange?void 0:t.onASRStateChange(e)})}emitConversation(e){this.safeEmit(()=>{var t;return null==(t=this.agentOptions.callbacks)||null==t.onConversationChange?void 0:t.onConversationChange(e)})}emitError(e,t,n,s,i){const r={domain:e,code:t,message:n,retryable:s,cause:i};"asr"===e&&this.setASRState("failed"),this.safeEmit(()=>{var e;return null==(e=this.agentOptions.callbacks)||null==e.onError?void 0:e.onError(r)})}safeEmit(e){this.agentDestroyed||this.agentDestroying||e()}assertRunning(){if(this.assertE2EOpen(),"running"!==this.agentState)throw new Error("Agent 尚未启动,请先调用 start()")}assertE2EOpen(){var e;if(this.assertNotDestroyed(),null==(e=this.e2eClient)||!e.isOpen)throw new Error("Agent 尚未连接 E2E WebSocket")}assertNotDestroyed(){if(this.agentDestroyed||this.agentDestroying)throw new Error("Agent 已销毁")}}export{s as AgentE2EClient,a as DEFAULT_AGENT_AUDIO,v as MicrophoneController,U as default};
2
- //# sourceMappingURL=avatar.modern.js.map
1
+ import e from"@xmov/avatar";function t(){return t=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var s in n)({}).hasOwnProperty.call(n,s)&&(e[s]=n[s])}return e},t.apply(null,arguments)}const n=2097152;class s{constructor(e){var t;this.ws=null,this.connectPromise=null,this.rejectConnect=null,this.options=e,this.audioInputEnabled=null==(t=e.audioInputEnabled)||t;const n=e.WebSocketCtor||globalThis.WebSocket;if(!n)throw new Error("当前环境不支持 WebSocket");this.WebSocketCtor=n}get isOpen(){var e;return(null==(e=this.ws)?void 0:e.readyState)===this.WebSocketCtor.OPEN}connect(){var e,t;if(this.isOpen)return Promise.resolve();if(this.connectPromise)return this.connectPromise;let n;return null==(e=(t=this.options).onStateChange)||e.call(t,"connecting"),n=new Promise((e,t)=>{this.rejectConnect=t;let s=!1;const i=new this.WebSocketCtor(this.buildWsUrl(this.options.wsUrl,this.options.token));this.ws=i,i.onopen=()=>{var t,r;this.ws===i&&(s=!0,this.connectPromise===n&&(this.connectPromise=null),this.rejectConnect=null,null==(t=(r=this.options).onStateChange)||t.call(r,"open"),e())},i.onmessage=e=>{if(this.ws!==i)return;const t=this.parseMessage(e.data);var n,s;t&&(null==(n=(s=this.options).onEvent)||n.call(s,t))},i.onerror=()=>{var e,r,a,o;if(this.ws!==i)return;const l=new Error("E2E WebSocket 连接异常");s||(s=!0,this.connectPromise===n&&(this.connectPromise=null),this.rejectConnect=null,t(l)),null==(e=(r=this.options).onStateChange)||e.call(r,"error"),null==(a=(o=this.options).onError)||a.call(o,l)},i.onclose=e=>{var r,a,o,l;this.ws===i&&(this.ws=null,s||(s=!0,this.connectPromise===n&&(this.connectPromise=null),this.rejectConnect=null,t(new Error(`E2E WebSocket 已关闭: ${e.code} ${e.reason||""}`.trim()))),null==(r=(a=this.options).onStateChange)||r.call(a,"closed"),null==(o=(l=this.options).onClose)||o.call(l,e))}}),this.connectPromise=n,this.connectPromise}disconnect(e=1e3,t="client_close"){if(!this.ws)return;const n=this.ws;this.ws=null,this.connectPromise=null;const s=this.rejectConnect;this.rejectConnect=null,null==s||s(new Error("E2E WebSocket 连接已取消")),n.readyState!==this.WebSocketCtor.OPEN&&n.readyState!==this.WebSocketCtor.CONNECTING||n.close(e,t)}send(e){var t;this.assertOpen(),null==(t=this.ws)||t.send(JSON.stringify(e))}trySend(e){var t;return this.assertOpen(),!(this.ws&&this.ws.bufferedAmount>n||(null==(t=this.ws)||t.send(JSON.stringify(e)),0))}sendBinary(e){var t;if(this.assertOpen(),this.ws&&this.ws.bufferedAmount>n){const e=new Error("E2E WebSocket 发送缓冲区超限");throw e.name="WebSocketBackpressureError",e}null==(t=this.ws)||t.send(e)}ping(){this.send({type:"ping"})}setAudioInputEnabled(e){this.audioInputEnabled=e}assertOpen(){if(!this.isOpen)throw new Error("E2E WebSocket 未连接")}parseMessage(e){if("string"!=typeof e)return null;try{const t=JSON.parse(e);return t&&"string"==typeof t.type?t:null}catch(e){var t,n;return null==(t=(n=this.options).onError)||t.call(n,new Error("E2E WebSocket 消息不是合法 JSON")),null}}buildWsUrl(e,t){try{const n=new URL(e);return!t||n.searchParams.has("token")||n.searchParams.has("e2e_token")||n.searchParams.set("token",t),this.updateAudioInputQuery(n.searchParams),n.toString()}catch(n){const[s,i=""]=e.split("?",2),r=new URLSearchParams(i);!t||r.has("token")||r.has("e2e_token")||r.set("token",t),this.updateAudioInputQuery(r);const a=r.toString();return a?`${s}?${a}`:s}}updateAudioInputQuery(e){this.audioInputEnabled?e.delete("audio_input_off"):e.set("audio_input_off","1")}}const i={format:"webm_opus",mimeType:"audio/webm;codecs=opus",sampleRate:16e3,channels:1,chunkMs:100},r=16e3;class a{constructor(e={}){this.inputTrack=null,this.reader=null,this.writer=null,this.generator=null,this.audioContext=null,this.audioSource=null,this.audioDestination=null,this.pumpPromise=null,this.pumpFinished=!1,this.outputClosed=!1,this.draining=!1,this.inputSampleRate=null,this.resampleBuffer=[],this.resamplePosition=0,this.outputBuffer=[],this.outputTimestamp=null,this.startGeneration=0,this.options=e}async start(e){const t=++this.startGeneration;this.inputTrack=e,this.inputSampleRate=null,this.resampleBuffer=[],this.resamplePosition=0,this.outputBuffer=[],this.outputTimestamp=null,this.pumpFinished=!1,this.outputClosed=!1,this.draining=!1;const n=globalThis,s=n.MediaStreamTrackProcessor,i=n.MediaStreamTrackGenerator,a=n.AudioData;if(!s||!i||!a)return this.startWebAudio(e,n,t);const o=new s({track:e}),l=new i({kind:"audio"}),h=new MediaStream([l]),c=h.getAudioTracks()[0],u=null==c?void 0:c.getSettings();if(void 0!==(null==u?void 0:u.sampleRate)&&u.sampleRate!==r||void 0!==(null==u?void 0:u.channelCount)&&1!==u.channelCount)throw l.stop(),this.unsupported(`浏览器音频输出格式不符合要求: ${(null==u?void 0:u.sampleRate)||"unknown"} Hz / ${(null==u?void 0:u.channelCount)||"unknown"} channel`);try{this.reader=o.readable.getReader(),this.writer=l.writable.getWriter()}catch(t){throw l.stop(),"ended"!==e.readyState&&e.stop(),this.inputTrack=null,t}return this.generator=l,this.pumpPromise=this.pump(a),h}async startWebAudio(e,t,n){const s=globalThis.AudioContext||t.webkitAudioContext;if(!s)throw this.unsupported("当前浏览器不支持 16 kHz mono 音频处理");let i=null,a=null,o=null;try{if(i=new s({sampleRate:r}),this.audioContext=i,i.sampleRate!==r)throw this.unsupported(`浏览器无法创建 16 kHz AudioContext: ${i.sampleRate} Hz`);if("suspended"===i.state)try{await i.resume()}catch(e){throw this.contextSuspended(e)}if(this.assertStartActive(n),"running"!==i.state)throw this.contextSuspended();a=i.createMediaStreamSource(new MediaStream([e])),this.audioSource=a,o=i.createMediaStreamDestination(),this.audioDestination=o,o.channelCount=1,o.channelCountMode="explicit",o.channelInterpretation="speakers",a.connect(o);const t=o.stream.getAudioTracks()[0];if(!t)throw new Error("Web Audio 未返回音频输出轨道");return this.validateOutputTrack(t),this.assertStartActive(n),o.stream}catch(t){var l,h;throw null==(l=a)||l.disconnect(),null==(h=o)||h.stream.getTracks().forEach(e=>e.stop()),i&&"closed"!==i.state&&await i.close().catch(()=>{}),"ended"!==e.readyState&&e.stop(),this.inputTrack=null,this.audioContext===i&&(this.audioContext=null,this.audioSource=null,this.audioDestination=null),this.normalizeWebAudioError(t)}}async stop(){await this.drain(),this.audioContext?await this.closeWebAudioOutput():await this.closeOutput()}async drain(){var e;if(this.startGeneration+=1,this.draining=!0,null==(e=this.inputTrack)||e.stop(),this.inputTrack=null,this.audioContext)return;const t=this.pumpPromise;if(t){const e=t.then(()=>!0,()=>!0);var n,s;await Promise.race([e,this.delay(1e3)])||(await Promise.race([null==(n=this.reader)?void 0:n.cancel().catch(()=>{}),this.delay(100)]),await Promise.race([null==(s=this.writer)?void 0:s.abort(new Error("16 kHz mono 音频处理停止超时")).catch(()=>{}),this.delay(100)]),this.stopGenerator())}}async closeOutput(){const e=this.writer;this.outputClosed=!0,e&&this.pumpFinished?await Promise.race([e.close().catch(()=>{}),this.delay(100)]):await Promise.race([null==e?void 0:e.abort(new Error("16 kHz mono 音频处理停止超时")).catch(()=>{}),this.delay(100)]),this.stopGenerator(),e&&this.pumpFinished&&e.releaseLock(),this.reader=null,this.writer=null,this.generator=null,this.pumpPromise=null}async closeWebAudioOutput(){const e=this.audioContext,t=this.audioSource,n=this.audioDestination;this.audioContext=null,this.audioSource=null,this.audioDestination=null,null==t||t.disconnect(),null==n||n.disconnect(),null==n||n.stream.getTracks().forEach(e=>e.stop()),e&&"closed"!==e.state&&await e.close().catch(()=>{})}async pump(e){const t=this.reader,n=this.writer;if(t&&n)try{var s;let o=!1;for(;;){const s=await t.read();if(s.done){o=!0;break}if(!s.value)continue;const i=s.value;try{this.validateAudioData(i);const t=this.toMono(i),s=this.resample(t,i.sampleRate,!1);null===this.outputTimestamp&&(this.outputTimestamp=Number.isFinite(i.timestamp)?i.timestamp:0);for(const t of s)await this.writeChunk(n,e,t)}finally{i.close()}}for(const t of this.resample(new Float32Array,this.inputSampleRate||r,!0))await this.writeChunk(n,e,t);if(o&&!this.draining&&"ended"===(null==(s=this.inputTrack)?void 0:s.readyState)){var i,a;const e=new Error("麦克风音频轨道已结束");e.name="AudioTransformError",null==(i=(a=this.options).onError)||i.call(a,e),this.stopGenerator()}}catch(e){var o,l;const t=e instanceof Error?e:new Error("16 kHz mono 音频处理失败");t.name="AudioTransformError",null==(o=(l=this.options).onError)||o.call(l,t)}finally{t.releaseLock(),this.pumpFinished=!0,this.outputClosed&&n.releaseLock()}}toMono(e){const t=Math.max(1,e.numberOfChannels),n=new Float32Array(e.numberOfFrames);for(let s=0;s<t;s+=1){const i=new Float32Array(e.numberOfFrames);e.copyTo(i,{format:"f32-planar",planeIndex:s});for(let e=0;e<i.length;e+=1)n[e]+=i[e]/t}return n}resample(e,t,n){if(null===this.inputSampleRate)this.inputSampleRate=t;else if(this.inputSampleRate!==t)throw new Error(`输入音频采样率发生变化: ${this.inputSampleRate} -> ${t}`);for(const t of e)this.resampleBuffer.push(t);if(this.resampleBuffer.length>192e3)throw new Error("输入音频重采样缓冲区超限");n&&this.resampleBuffer.length>0&&this.resampleBuffer.push(this.resampleBuffer[this.resampleBuffer.length-1]);const s=t/r,i=[];for(;this.resamplePosition+1<this.resampleBuffer.length;){const e=Math.floor(this.resamplePosition),t=Math.min(e+1,this.resampleBuffer.length-1),n=this.resamplePosition-e;this.outputBuffer.push(this.resampleBuffer[e]*(1-n)+this.resampleBuffer[t]*n),this.resamplePosition+=s;const r=Math.floor(this.resamplePosition);r>0&&(this.resampleBuffer.splice(0,r),this.resamplePosition-=r),this.outputBuffer.length>=320&&i.push(new Float32Array(this.outputBuffer.splice(0,320)))}return n&&this.outputBuffer.length>0&&i.push(new Float32Array(this.outputBuffer.splice(0))),i}async writeChunk(e,t,n){var s;if(!n.length)return;const i=this.outputTimestamp||0,a=new t({format:"f32-planar",sampleRate:r,numberOfFrames:n.length,numberOfChannels:1,timestamp:i,data:n});this.outputTimestamp=i+Math.round(1e6*n.length/r),await e.write(a),a.close();const o=null==(s=this.generator)?void 0:s.getSettings();if(o&&(void 0!==o.sampleRate&&o.sampleRate!==r||void 0!==o.channelCount&&1!==o.channelCount))throw this.unsupported(`浏览器音频输出格式不符合要求: ${o.sampleRate||"unknown"} Hz / ${o.channelCount||"unknown"} channel`)}validateAudioData(e){if(!Number.isFinite(e.sampleRate)||!Number.isInteger(e.sampleRate)||e.sampleRate<8e3||e.sampleRate>192e3)throw new Error(`输入音频采样率无效: ${e.sampleRate}`);if(!Number.isInteger(e.numberOfChannels)||e.numberOfChannels<1||e.numberOfChannels>8)throw new Error(`输入音频声道数无效: ${e.numberOfChannels}`);if(!Number.isInteger(e.numberOfFrames)||e.numberOfFrames<1||e.numberOfFrames>96e3)throw new Error(`输入音频帧数无效: ${e.numberOfFrames}`);if(!Number.isFinite(e.timestamp)||Math.abs(e.timestamp)>1e15)throw new Error(`输入音频时间戳无效: ${e.timestamp}`)}delay(e){return new Promise(t=>{globalThis.setTimeout(()=>t(!1),e)})}stopGenerator(){this.generator&&"ended"!==this.generator.readyState&&this.generator.stop()}unsupported(e,t){const n=new Error(e);return n.name="NotSupportedError",n.agentCode="AUDIO_FIXED_TRACK_UNSUPPORTED",n.retryable=!1,n.cause=t,n}contextSuspended(e){const t=new Error("AudioContext 未运行,请在用户操作后重试");return t.name="NotAllowedError",t.agentCode="AUDIO_CONTEXT_SUSPENDED",t.retryable=!0,t.cause=e,t}assertStartActive(e){if(e!==this.startGeneration){const e=new Error("16 kHz mono 音频处理启动已取消");throw e.name="AbortError",e.agentCode="AUDIO_FIXED_TRACK_CANCELLED",e.retryable=!0,e}}normalizeWebAudioError(e){const t=e instanceof Error?e:new Error(String(e));if(t.agentCode)return t;if("NotSupportedError"===t.name)return this.unsupported(`浏览器无法初始化 16 kHz mono Web Audio: ${t.message}`,t);const n=new Error(`16 kHz mono Web Audio 初始化失败: ${t.message}`);return n.name=t.name,n.agentCode="AUDIO_FIXED_TRACK_FAILED",n.retryable=!0,n.cause=t,n}validateOutputTrack(e){const t=e.getSettings();if(void 0!==t.sampleRate&&t.sampleRate!==r||void 0!==t.channelCount&&1!==t.channelCount)throw this.unsupported(`浏览器音频输出格式不符合要求: ${t.sampleRate||"unknown"} Hz / ${t.channelCount||"unknown"} channel`)}}function o(e){return e.toString(16).padStart(2,"0")}async function l(e,n,s={},i=64){try{const r=n instanceof Blob?await n.arrayBuffer():ArrayBuffer.isView(n)?n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength):n,a=new Uint8Array(r),l=Array.from(a.slice(0,i)),h=n instanceof Blob?n.type:s.type;console.log(e,t({},s,{size:a.byteLength,type:h,previewLength:l.length,bytes:l,hex:l.map(o).join(" ")}))}catch(t){console.warn(e,"failed to read audio chunk",t)}}const h=new WeakMap,c=new WeakMap;function u(e){return e.audioSession}function d(e,t){try{return e.type=t,e.type===t}catch(e){return!1}}function p(){const e=globalThis.performance;return e&&Number.isFinite(e.timeOrigin)&&"function"==typeof e.now?e.timeOrigin+e.now():Date.now()}class m{constructor(e,t=p){this.timecodeScale=null,this.firstTimecode=null,this.lastTimecode=null,this.lastObservedAt=null,this.lastTimestamp=null,this.chunkMs=e,this.now=t,this.startedAt=t()}resolve(e){const t=this.now(),n=Number.isFinite(e)&&e>=0;if(n&&null===this.firstTimecode&&(this.firstTimecode=e),n&&null===this.timecodeScale&&null!==this.lastTimecode&&e>this.lastTimecode){const n=e-this.lastTimecode,s=null===this.lastObservedAt?0:t-this.lastObservedAt,i=s>=this.chunkMs/4?s:this.chunkMs,r=Math.abs(n-i),a=Math.abs(1e3*n-i);this.timecodeScale=a<r?1e3:1}let s=t-this.chunkMs;const i=n&&null!==this.firstTimecode?e-this.firstTimecode:null;var r;null!==i&&i>=0&&(null!==this.timecodeScale||0===i)&&(s=this.startedAt+i*(null!=(r=this.timecodeScale)?r:1)),null!==this.lastTimestamp&&(s=Math.max(s,this.lastTimestamp+this.chunkMs));const a=Math.max(Math.round(this.startedAt),Math.round(s));return this.lastTimecode=n?e:this.lastTimecode,this.lastObservedAt=t,this.lastTimestamp=a,a}}const f=2e4;function g(e){return{codec:"opus",sampleRate:e.sampleRate,numberOfChannels:e.channels,bitrate:e.bitrate,opus:{format:"opus",frameDuration:f}}}class S{constructor(e={}){this.encoder=null,this.isInitialized=!1,this.pendingEncode=null,this.timestamp=0,this.config=t({sampleRate:16e3,channels:1,bitrate:24e3,frameSize:1600},e)}static async isSupported(){if("undefined"==typeof AudioEncoder)return!1;try{var e;const t=await AudioEncoder.isConfigSupported(g({sampleRate:16e3,channels:1,bitrate:24e3,frameSize:1600}));return!0===t.supported&&(null==(e=t.config)||null==(e=e.opus)?void 0:e.frameDuration)===f}catch(e){return!1}}async init(){this.isInitialized||(this.encoder=this.createEncoder(),this.isInitialized=!0)}async encode(e){if(!this.isInitialized||!this.encoder)return Promise.reject(new Error("WebCodecOpusEncoder 未初始化"));if(this.pendingEncode)return Promise.reject(new Error("WebCodecs Opus 编码器仍在处理上一音频块"));const t=new Int16Array(e),n=this.timestamp,s=Math.round(1e6*e.length/this.config.sampleRate),i=n+s,r=s/f;if(!Number.isInteger(r))return Promise.reject(new Error("PCM 音频块无法按 20ms Opus packet 完整分包"));const a=new AudioData({format:"s16",sampleRate:this.config.sampleRate,numberOfFrames:t.length,numberOfChannels:this.config.channels,timestamp:n,data:t}),o=new Promise((e,t)=>{const s=globalThis.setTimeout(()=>{this.failPending(new Error("WebCodecs Opus 编码输出超过 2 秒"))},2e3);this.pendingEncode={startTimestampUs:n,endTimestampUs:i,expectedFrameCount:r,chunks:[],resolve:e,reject:t,timeoutId:s}});try{this.encoder.encode(a),this.timestamp=i}catch(e){this.failPending(e instanceof Error?e:new Error("WebCodecs Opus 编码失败"))}finally{a.close()}return o}reset(){if(this.timestamp=0,this.failPending(new Error("WebCodecs Opus 编码已重置")),this.encoder){try{this.encoder.close()}catch(e){}this.encoder=this.createEncoder()}}async destroy(){const e=this.encoder;if(e){try{await e.flush()}catch(e){}this.failPending(new Error("WebCodecs Opus 编码器已销毁"));try{e.close()}catch(e){}}this.encoder=null,this.isInitialized=!1}createEncoder(){let e;return e=new AudioEncoder({output:t=>{this.encoder===e&&this.handleOutput(t)},error:t=>{if(this.encoder!==e)return;const n=t instanceof Error?t:new Error("WebCodecs Opus 编码失败");(globalThis.avatarSDKLogger||console).error("[WebCodecOpusEncoder] 编码错误:",n),this.failPending(n)}}),e.configure(g(this.config)),e}handleOutput(e){const t=this.pendingEncode;if(!t)return;if(e.timestamp<t.startTimestampUs||e.timestamp>=t.endTimestampUs||null!==e.duration&&e.duration!==f)return void this.failPending(new Error("WebCodecs Opus 未按 20ms 分包,无法生成固定 100ms AU 音频"));if(t.chunks.push(e),t.chunks.length>t.expectedFrameCount)return void this.failPending(new Error("WebCodecs Opus 未按 20ms 分包,无法生成固定 100ms AU 音频"));if(t.chunks.length<t.expectedFrameCount)return;const n=t.chunks.map((e,n)=>{var s,i;const r=new Uint8Array(e.byteLength);e.copyTo(r);const a=null!=(s=null==(i=t.chunks[n+1])?void 0:i.timestamp)?s:t.endTimestampUs;return{data:r,durationMs:(e.duration&&e.duration>0?e.duration:Math.max(1,a-e.timestamp))/1e3}}),s=n.reduce((e,t)=>e+t.durationMs,0);n.some(e=>20!==e.durationMs)||s!==(t.endTimestampUs-t.startTimestampUs)/1e3?this.failPending(new Error("WebCodecs Opus 未按 20ms 分包,无法生成固定 100ms AU 音频")):(globalThis.clearTimeout(t.timeoutId),this.pendingEncode=null,t.resolve(n))}failPending(e){const t=this.pendingEncode;t&&(globalThis.clearTimeout(t.timeoutId),this.pendingEncode=null,t.reject(e))}}class E{constructor(e=16e3){this.chunks=[],this.hasHeader=!1,this.clusterTimestamp=0,this.frameDuration=100,this.sampleRate=e}writeHeader(){if(this.hasHeader)return;const e=this.buildEBMLHeader(),t=this.buildSegmentInfo(),n=this.buildOpusTracks();this.chunks.push(e),this.chunks.push(new Uint8Array([24,83,128,103,1,255,255,255,255,255,255,255])),this.chunks.push(t),this.chunks.push(n),this.hasHeader=!0,this.clusterTimestamp=0}feedOpusFrame(e,t=this.frameDuration){this.hasHeader||this.writeHeader();const n=this.buildCluster(Math.round(this.clusterTimestamp),e);this.chunks.push(n),this.clusterTimestamp+=t}flush(){if(0===this.chunks.length)return new ArrayBuffer(0);const e=this.chunks.reduce((e,t)=>e+t.length,0),t=new Uint8Array(e);let n=0;for(const e of this.chunks)t.set(e,n),n+=e.length;return this.chunks=[],t.buffer}reset(){this.chunks=[],this.hasHeader=!1,this.clusterTimestamp=0}encodeVINT(e,t){if(void 0!==t){const n=new Uint8Array(t);for(let s=t-1;s>=0;s--)n[s]=255&e,e>>>=8;return n[0]|=1<<8-t,n}if(e<127)return new Uint8Array([128|e]);if(e<16383)return new Uint8Array([64|e>>8,255&e]);if(e<2097151)return new Uint8Array([32|e>>16,e>>8&255,255&e]);if(e<268435455)return new Uint8Array([16|e>>24,e>>16&255,e>>8&255,255&e]);throw new Error(`VINT value too large: ${e}`)}encodeID(e){return e<=255?new Uint8Array([e]):e<=65535?new Uint8Array([e>>8,255&e]):e<=16777215?new Uint8Array([e>>16,e>>8&255,255&e]):new Uint8Array([e>>>24,e>>16&255,e>>8&255,255&e])}encodeUint(e){if(0===e)return new Uint8Array([0]);const t=[];let n=e;for(;n>0;)t.unshift(255&n),n=Math.floor(n/256);return new Uint8Array(t)}encodeString(e){return(new TextEncoder).encode(e)}encodeFloat32(e){const t=new ArrayBuffer(4);return new DataView(t).setFloat32(0,e,!1),new Uint8Array(t)}encodeElement(e,t){const n=this.encodeID(e),s=this.encodeVINT(t.length),i=new Uint8Array(n.length+s.length+t.length);return i.set(n,0),i.set(s,n.length),i.set(t,n.length+s.length),i}encodeUintElement(e,t){return this.encodeElement(e,this.encodeUint(t))}encodeStringElement(e,t){return this.encodeElement(e,this.encodeString(t))}encodeFloat32Element(e,t){return this.encodeElement(e,this.encodeFloat32(t))}concatArrays(e){const t=e.reduce((e,t)=>e+t.length,0),n=new Uint8Array(t);let s=0;for(const t of e)n.set(t,s),s+=t.length;return n}buildEBMLHeader(){const e=this.concatArrays([this.encodeUintElement(17030,1),this.encodeUintElement(17143,1),this.encodeUintElement(17138,4),this.encodeUintElement(17139,8),this.encodeStringElement(17026,"webm"),this.encodeUintElement(17031,4),this.encodeUintElement(17029,2)]);return this.encodeElement(440786851,e)}buildSegmentInfo(){const e=this.concatArrays([this.encodeUintElement(2807729,1e6),this.encodeStringElement(19840,"xmov-avatar-sdk"),this.encodeStringElement(22337,"xmov-avatar-sdk")]);return this.encodeElement(357149030,e)}buildOpusTracks(){const e=new Uint8Array([79,112,117,115,72,101,97,100,1,1,0,0,128,62,0,0,0,0,0]),t=this.concatArrays([this.encodeFloat32Element(181,this.sampleRate),this.encodeUintElement(159,1)]),n=this.concatArrays([this.encodeUintElement(215,1),this.encodeUintElement(29637,1),this.encodeUintElement(131,2),this.encodeUintElement(156,0),this.encodeStringElement(2274716,"und"),this.encodeStringElement(134,"A_OPUS"),this.encodeElement(25506,e),this.encodeUintElement(22186,0),this.encodeUintElement(22203,8e7),this.encodeElement(225,t)]),s=this.encodeElement(174,n);return this.encodeElement(374648427,s)}buildCluster(e,t){const n=this.encodeUintElement(231,e),s=this.encodeVINT(1),i=new Uint8Array([0,0]),r=new Uint8Array([128]),a=this.concatArrays([s,i,r,t]),o=this.encodeElement(163,a),l=this.concatArrays([n,o]);return this.encodeElement(524531317,l)}}class y{constructor(){this.opusEncoder=new S({sampleRate:16e3,channels:1,bitrate:24e3,frameSize:1600}),this.muxer=new E(16e3),this.queue=Promise.resolve(),this.generation=0,this.destroyed=!1,this.pendingBlocks=0}static isSupported(){return S.isSupported()}async init(){await this.opusEncoder.init()}async encode(e){var t=this;if(1600!==e.length)throw new Error(`PCM 音频块必须为 1600 samples,实际为 ${e.length}`);if(this.pendingBlocks>=20)throw new Error("PCM/Opus 编码队列超过 2 秒");this.pendingBlocks+=1;const n=this.generation,s=new Int16Array(e),i=this.queue.then(async function(){if(t.destroyed||n!==t.generation)return new ArrayBuffer(0);const e=await t.opusEncoder.encode(s);if(t.destroyed||n!==t.generation)return new ArrayBuffer(0);for(const n of e)t.muxer.feedOpusFrame(n.data,n.durationMs);return t.muxer.flush()}).finally(()=>{this.pendingBlocks-=1});return this.queue=i.then(()=>{},()=>{}),i}reset(){const e=++this.generation;this.queue=this.queue.then(()=>{this.destroyed||e!==this.generation||(this.opusEncoder.reset(),this.muxer.reset())})}async destroy(){this.destroyed=!0,this.generation+=1,await this.opusEncoder.destroy(),await this.queue,this.muxer.reset()}}const w="audio/webm;codecs=opus",b=16e3,A="agent-pcm-capture-processor",k=`\nclass AgentPcmCaptureProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.buffer = new Float32Array(1600);\n this.bufferIndex = 0;\n this.sequence = 0;\n this.port.onmessage = (event) => {\n if (event.data?.type === 'flush') {\n this.emitBlock(true);\n this.port.postMessage({ type: 'flushed' });\n }\n };\n }\n\n emitBlock(padPartial = false) {\n if (this.bufferIndex === 0 || (!padPartial && this.bufferIndex < 1600)) return;\n const pcm = new Int16Array(1600);\n for (let index = 0; index < this.bufferIndex; index += 1) {\n const sample = Math.max(-1, Math.min(1, this.buffer[index]));\n pcm[index] = sample < 0 ? sample * 0x8000 : sample * 0x7fff;\n }\n this.port.postMessage(\n { type: 'frame', pcm: pcm.buffer, sequence: this.sequence++ },\n [pcm.buffer],\n );\n this.buffer.fill(0);\n this.bufferIndex = 0;\n }\n\n process(inputs, outputs) {\n for (const output of outputs) {\n for (const channel of output) channel.fill(0);\n }\n const channels = inputs[0];\n if (!channels?.length || !channels[0]?.length) return true;\n for (let frame = 0; frame < channels[0].length; frame += 1) {\n let sample = 0;\n for (let channel = 0; channel < channels.length; channel += 1) {\n sample += channels[channel][frame] || 0;\n }\n this.buffer[this.bufferIndex++] = sample / channels.length;\n if (this.bufferIndex === 1600) this.emitBlock();\n }\n return true;\n }\n}\nregisterProcessor('${A}', AgentPcmCaptureProcessor);\n`;class v{constructor(e){var n,s;this.stream=null,this.recordingStream=null,this.audioTransformer=null,this.mediaRecorder=null,this.pcmAudioContext=null,this.pcmSource=null,this.pcmWorklet=null,this.pcmEncoder=null,this.pcmEncodeQueue=Promise.resolve(),this.pcmStartedAt=0,this.pcmGeneration=0,this.resolvePcmFlush=null,this.startGeneration=0,this.starting=!1,this.hasAudioSessionCapture=!1,this.options=e,this.pendingInputStream=null!=(n=e.inputStream)?n:null,this.usesProvidedInput=Boolean(e.inputStream),this.metadata=t({},i,{chunkMs:(null==(s=e.audio)?void 0:s.chunkMs)||i.chunkMs})}get isRecording(){return this.starting||Boolean(this.stream)||Boolean(this.pendingInputStream)}getAudioMetadata(){return this.metadata}async start(){if(this.stream||this.starting)return;const e=++this.startGeneration;this.starting=!0;try{var n,s;const o=this.pendingInputStream;this.stream=null!=o?o:null,this.pendingInputStream=null;const l=navigator.mediaDevices;if(!(this.stream||null!=l&&l.getUserMedia))throw new Error("当前浏览器不支持麦克风采集");const c=globalThis.MediaRecorder;if(this.options.usePcmWebCodecs){if(!await y.isSupported())throw this.webCodecsOpusUnsupported()}else if(null==c||null==c.isTypeSupported||!c.isTypeSupported(w))throw this.webMOpusUnsupported();if(this.assertStartActive(e),!this.stream){if(this.usesProvidedInput)throw o?this.cancelled():new Error("ASR 输入流已释放");this.hasAudioSessionCapture=function(e=navigator){var t,n;const s=u(e);if(!s)return!1;const i=h.get(s),r=null!=(t=null==i?void 0:i.previousType)?t:s.type;return!!d(s,"play-and-record")&&(h.set(s,{count:(null!=(n=null==i?void 0:i.count)?n:0)+1,previousType:r}),!0)}(),this.stream=await l.getUserMedia({audio:{sampleRate:{ideal:b},channelCount:{ideal:1},echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}})}this.assertStartActive(e);const p=this.stream.getAudioTracks()[0];if(!p)throw new Error("麦克风未返回音频轨道");var i,r;if(this.options.usePcmWebCodecs)return await this.startPcmWebMRecorder(this.stream),this.assertStartActive(e),this.metadata=t({},this.metadata,{sampleRate:b,channels:1,chunkMs:100}),void(null==(i=(r=this.options).onMetadata)||i.call(r,this.metadata));const m=new a({onError:e=>{var t,n;const s=e instanceof Error?e:new Error("16 kHz mono 音频处理失败");s.name="MediaRecorderError",null==(t=(n=this.options).onError)||t.call(n,s)}});this.audioTransformer=m,this.recordingStream=await m.start(p),this.assertStartActive(e);const f=this.recordingStream.getAudioTracks()[0],g=null==f?void 0:f.getSettings();this.metadata=t({},this.metadata,{sampleRate:(null==g?void 0:g.sampleRate)||b,channels:(null==g?void 0:g.channelCount)||1}),this.startWebMRecorder(c),null==(n=(s=this.options).onMetadata)||n.call(s,this.metadata)}catch(t){const n=e!==this.startGeneration;await this.stop();const s=n?this.cancelled():t instanceof Error?t:new Error("麦克风启动失败");var o,l;throw"AUDIO_FIXED_TRACK_CANCELLED"!==s.agentCode&&(null==(o=(l=this.options).onError)||o.call(l,s)),s}finally{this.startGeneration===e&&(this.starting=!1)}}async stop(){this.startGeneration+=1,this.starting=!1;try{var e;const t=this.audioTransformer;try{await(null==t?void 0:t.drain())}finally{await this.stopPcmWebMRecorder(),await this.stopMediaRecorder(),await(null==t?void 0:t.stop()),this.audioTransformer=null}this.recordingStream=null,this.stream&&!t&&this.stream.getTracks().forEach(e=>e.stop()),this.stream=null,null==(e=this.pendingInputStream)||e.getTracks().forEach(e=>e.stop()),this.pendingInputStream=null}finally{this.hasAudioSessionCapture&&(this.hasAudioSessionCapture=!1,function(e=navigator){const t=u(e);if(!t)return!1;const n=h.get(t);if(!n)return function(e=navigator){const t=u(e);if(!t)return!1;const n=h.get(t);return d(t,null!=n&&n.count||"play-and-record"===t.type?"play-and-record":"playback")}(e);if(n.count-=1,n.count>0)return d(t,"play-and-record");h.delete(t);const s="play-and-record"===n.previousType?"play-and-record":"playback";d(t,s)&&"playback"===s&&function(e){const t=c.get(e);if(t)for(const e of[...t])try{e()}catch(e){}}(t)}())}}startWebMRecorder(e){if(!this.recordingStream)throw new Error("16 kHz mono 音频流未就绪");let t;try{t=new e(this.recordingStream,{mimeType:w,audioBitsPerSecond:24e3})}catch(e){if(e instanceof Error&&"NotSupportedError"===e.name)throw this.webMOpusUnsupported(e);throw e}const n=new m(this.metadata.chunkMs);t.ondataavailable=e=>{if(e.data.size>0){var s;const i=n.resolve(e.timecode);null==(s=globalThis.avatarSDKLogger)||null==s.log||s.log("[Agent][Microphone]","audio chunk",{size:e.data.size,type:e.data.type,chunkMs:this.metadata.chunkMs,sampleRate:this.metadata.sampleRate,channels:this.metadata.channels,recorderState:t.state,timecode:e.timecode}),this.options.debugAudioChunks&&l("[Agent][Microphone] nearend WebM/Opus chunk",e.data,{chunkMs:this.metadata.chunkMs,sampleRate:this.metadata.sampleRate,channels:this.metadata.channels,recorderState:t.state,timecode:e.timecode}),this.options.onFrame(e.data,i)}},t.onerror=e=>{var t,n;const s=e.error,i=new Error(s instanceof Error?s.message:"WebM/Opus 麦克风录制失败");i.name="MediaRecorderError",null==(t=(n=this.options).onError)||t.call(n,i)},this.mediaRecorder=t;try{t.start(this.metadata.chunkMs)}catch(e){if(e instanceof Error&&"NotSupportedError"===e.name)throw this.webMOpusUnsupported(e);throw e}}assertStartActive(e){if(e!==this.startGeneration)throw this.cancelled()}cancelled(){const e=new Error("麦克风启动已取消");return e.name="AbortError",e.agentCode="AUDIO_FIXED_TRACK_CANCELLED",e.retryable=!0,e}webMOpusUnsupported(e){const t=new Error("当前浏览器不支持 audio/webm;codecs=opus");return t.name="NotSupportedError",t.agentCode="AUDIO_WEBM_OPUS_UNSUPPORTED",t.retryable=!1,t.cause=e,t}webCodecsOpusUnsupported(e){const t=new Error("当前浏览器不支持 WebCodecs AudioEncoder Opus");return t.name="NotSupportedError",t.agentCode="AUDIO_WEBCODECS_OPUS_UNSUPPORTED",t.retryable=!1,t.cause=e,t}async startPcmWebMRecorder(e){var t=this;const n=globalThis,s=globalThis.AudioContext||n.webkitAudioContext;if(!s||void 0===globalThis.AudioWorkletNode)throw this.webCodecsOpusUnsupported(new Error("AudioWorklet 不可用"));const i=new y;await i.init();const r=new s({sampleRate:b});if(r.sampleRate!==b)throw await i.destroy(),await r.close().catch(()=>{}),this.webCodecsOpusUnsupported(new Error("浏览器无法创建 16000 Hz AudioContext"));this.pcmAudioContext=r,this.pcmEncoder=i;const a=URL.createObjectURL(new Blob([k],{type:"application/javascript"}));try{await r.audioWorklet.addModule(a)}finally{URL.revokeObjectURL(a)}if("suspended"===r.state&&await r.resume(),"running"!==r.state)throw this.audioContextSuspended();const o=r.createMediaStreamSource(e),h=new AudioWorkletNode(r,A,{numberOfInputs:1,numberOfOutputs:1,outputChannelCount:[1]}),c=++this.pcmGeneration;this.pcmSource=o,this.pcmWorklet=h,this.pcmEncodeQueue=Promise.resolve(),this.pcmStartedAt=this.monotonicEpochNow(),h.port.onmessage=e=>{var n,s,r;if("flushed"===(null==(n=e.data)?void 0:n.type))return null==(r=this.resolvePcmFlush)||r.call(this),void(this.resolvePcmFlush=null);if("frame"!==(null==(s=e.data)?void 0:s.type)||!(e.data.pcm instanceof ArrayBuffer))return;const a=this.pcmStartedAt+100*Number(e.data.sequence||0),o=new Int16Array(e.data.pcm);this.pcmEncodeQueue=this.pcmEncodeQueue.then(async function(){const n=await i.encode(o);if(c!==t.pcmGeneration||0===n.byteLength)return;const s=new Blob([n],{type:w});t.options.debugAudioChunks&&l("[Agent][Microphone] nearend PCM/WebCodecs chunk",s,{chunkMs:100,sampleRate:b,channels:1,sequence:e.data.sequence}),t.options.onFrame(s,a)}).catch(e=>{var t,n;const s=e instanceof Error?e:new Error("PCM/Opus 麦克风编码失败");s.name="AudioEncoderError",null==(t=(n=this.options).onError)||t.call(n,s)})},o.connect(h),h.connect(r.destination)}async stopPcmWebMRecorder(){const e=this.pcmWorklet,t=this.pcmSource,n=this.pcmAudioContext,s=this.pcmEncoder;if(!e&&!n&&!s)return;null==t||t.disconnect();let i=!0;e&&(i=await Promise.race([new Promise(t=>{this.resolvePcmFlush=()=>t(!0),e.port.postMessage({type:"flush"})}),new Promise(e=>globalThis.setTimeout(()=>e(!1),100))]),i||(this.resolvePcmFlush=null,e.port.onmessage=null)),await this.pcmEncodeQueue,this.pcmGeneration+=1,this.resolvePcmFlush=null,e&&(e.port.onmessage=null,e.disconnect(),e.port.close()),n&&"closed"!==n.state&&await n.close().catch(()=>{}),await(null==s?void 0:s.destroy()),this.pcmWorklet=null,this.pcmSource=null,this.pcmAudioContext=null,this.pcmEncoder=null,this.pcmEncodeQueue=Promise.resolve()}monotonicEpochNow(){const e=globalThis.performance;return e&&Number.isFinite(e.timeOrigin)?Math.round(e.timeOrigin+e.now()):Date.now()}audioContextSuspended(){const e=new Error("AudioContext 未运行,请在用户操作后重试");return e.name="NotAllowedError",e.agentCode="AUDIO_CONTEXT_SUSPENDED",e.retryable=!0,e}async stopMediaRecorder(){const e=this.mediaRecorder;if(this.mediaRecorder=null,!e)return;const t="inactive"!==e.state;await new Promise(n=>{let s=!1;const i=()=>{s||(s=!0,globalThis.clearTimeout(a),n())},r=e.onerror,a=globalThis.setTimeout(i,1e3);if(e.onstop=i,e.onerror=t=>{null==r||r.call(e,t),i()},t)try{e.stop()}catch(e){i()}}),e.ondataavailable=null,e.onerror=null,e.onstop=null}}const C=new Uint8Array([26,69,223,163]);function R(e,t){const n=globalThis.avatarSDKLogger;null==n||null==n.log||n.log("[Agent][AudioUplink]",e,t)}function T(e,t,n){if(1!==e&&2!==e)throw new Error(`AU stream_id 无效: ${e}`);if(!Number.isFinite(t)||t<0||t>4294967295)throw new Error(`AU ts_ms 无效: ${t}`);const s=n instanceof ArrayBuffer?new Uint8Array(n):new Uint8Array(n.buffer,n.byteOffset,n.byteLength),i=new ArrayBuffer(8+s.byteLength),r=new DataView(i);return r.setUint8(0,65),r.setUint8(1,85),r.setUint8(2,1),r.setUint8(3,e),r.setUint32(4,Math.floor(t),!1),new Uint8Array(i,8).set(s),i}async function O(e){if(Array.isArray(e)){const t=await Promise.all(e.map(O)),n=new Uint8Array(t.reduce((e,t)=>e+t.byteLength,0));let s=0;for(const e of t)n.set(e,s),s+=e.byteLength;return n}return e instanceof Blob?new Uint8Array(await e.arrayBuffer()):e instanceof ArrayBuffer?new Uint8Array(e):new Uint8Array(e.buffer,e.byteOffset,e.byteLength)}class P{constructor(e){this.connectionStartedAt=null,this.connectionGeneration=0,this.sendChain=Promise.resolve(),this.failed=!1,this.pendingFarend=new Map,this.pendingNearend=new Map,this.closedNearendBuckets=new Set,this.lastTimestampByStream=new Map,this.lastSentTimestampByStream=new Map,this.pendingWebmPrefixByStream=new Map,this.farendStreamState="unknown",this.options=e}reset(e=null){this.connectionGeneration+=1,this.connectionStartedAt=e,this.pendingFarend.clear(),this.pendingNearend.clear(),this.closedNearendBuckets.clear(),this.lastTimestampByStream.clear(),this.lastSentTimestampByStream.clear(),this.pendingWebmPrefixByStream.clear(),this.farendStreamState="unknown",this.failed=!1,this.sendChain=Promise.resolve()}startInput(){this.failed=!1,this.pendingFarend.clear(),this.pendingNearend.clear(),this.closedNearendBuckets.clear(),this.pendingWebmPrefixByStream.clear(),this.farendStreamState="unknown"}clearFarend(){this.pendingFarend.clear(),this.pendingWebmPrefixByStream.delete(2),this.farendStreamState="unknown"}offerFarend(e){var t;if(!1===e.isFirstChunk&&"ready"!==this.farendStreamState)return void R("farend.drop",{reason:"awaiting_webm_header",timestamp:e.timestamp,isFirstChunk:!1});const n=this.normalizeBlock(2,e);if(!n)return void(e.isFirstChunk&&this.abandonFarendStream());e.isFirstChunk&&(this.pendingFarend.clear(),this.pendingWebmPrefixByStream.delete(2),this.farendStreamState="ready");const s=null!=(t=this.pendingFarend.get(n.frameKey))?t:[];s.push(n),this.pendingFarend.set(n.frameKey,s),R("farend.offer",{tsMs:n.tsMs,frameKey:n.frameKey,isFirstChunk:Boolean(n.isFirstChunk),pendingNearend:[...this.pendingNearend.keys()]}),this.flushOlderNearend(n.frameKey),this.prune(n.frameKey)}offerNearend(e){var t;const n=this.normalizeBlock(1,e);if(!n||this.closedNearendBuckets.has(n.frameKey))return;const s=null!=(t=this.pendingNearend.get(n.frameKey))?t:[];s.push(n),this.pendingNearend.set(n.frameKey,s),this.flushExpiredNearend(n.frameKey),this.prune(n.frameKey)}drain(){for(const e of[...this.pendingNearend.keys()].sort((e,t)=>e-t))this.flushBucket(e,!0);return this.sendChain}normalizeBlock(e,n){const s=this.connectionStartedAt;if(null===s||!Number.isFinite(n.timestamp))return null;const i=Math.max(0,Math.floor(n.timestamp-s)),r=Math.floor(i/100),a=this.lastTimestampByStream.get(e);return void 0!==a&&i<=a?null:(this.lastTimestampByStream.set(e,i),t({},n,{streamId:e,tsMs:i,frameKey:r}))}enqueue(e){var t=this;if(this.failed)return;const n=this.connectionGeneration;this.sendChain=this.sendChain.then(async function(){if(!t.failed)for(const i of e){var s;const e=await O(null!=(s=i.dataParts)?s:i.data);if(t.failed||n!==t.connectionGeneration||null===t.connectionStartedAt||0===e.byteLength)continue;2===i.streamId&&i.isFirstChunk&&t.pendingWebmPrefixByStream.delete(2);const r=t.coalesceSplitWebmMagic(i.streamId,e);if(!r)continue;const a=t.lastSentTimestampByStream.get(i.streamId);void 0!==a&&i.tsMs<=a||(t.options.sendBinary(T(i.streamId,i.tsMs,r)),t.lastSentTimestampByStream.set(i.streamId,i.tsMs),R("frame.send",{streamId:i.streamId,tsMs:i.tsMs,bytes:r.byteLength}))}}).catch(e=>{var t,s;if(n!==this.connectionGeneration)return;this.failed=!0,this.pendingFarend.clear(),this.pendingNearend.clear();const i=e instanceof Error?e:new Error("AU 音频上行失败");null==(t=(s=this.options).onError)||t.call(s,i)})}coalesceSplitWebmMagic(e,t){const n=this.pendingWebmPrefixByStream.get(e);if(n){const s=new Uint8Array(n.byteLength+t.byteLength);s.set(n),s.set(t,n.byteLength),this.pendingWebmPrefixByStream.delete(e),t=s}return t.byteLength<C.byteLength&&t.every((e,t)=>e===C[t])?(this.pendingWebmPrefixByStream.set(e,t.slice()),null):t}flushOlderNearend(e){for(const t of[...this.pendingNearend.keys()].sort((e,t)=>e-t)){if(t>=e)break;this.flushBucket(t,!0)}}flushExpiredNearend(e){this.flushOlderNearend(e-1)}flushBucket(e,t=!1){const n=this.takeNearend(e);if(!n)return;const s=e-1,i=this.pendingFarend.has(e)||t&&this.pendingFarend.has(s)?this.takeFarendThrough(e):null;this.closedNearendBuckets.add(e),R("bucket.flush",{nearendFrameKey:e,pairedFarend:Boolean(i),pendingFarend:[...this.pendingFarend.keys()]}),this.enqueue(i?[i,n]:[n])}takeNearend(e){const n=this.pendingNearend.get(e);return this.pendingNearend.delete(e),null!=n&&n.length?t({},n[0],{dataParts:n.flatMap(e=>{var t;return null!=(t=e.dataParts)?t:[e.data]})}):null}takeFarendThrough(e){const n=[];for(const t of[...this.pendingFarend.keys()].sort((e,t)=>e-t)){if(t>e)break;n.push(...this.pendingFarend.get(t)),this.pendingFarend.delete(t)}return 0===n.length?null:t({},n[n.length-1],{dataParts:n.map(e=>e.data),isFirstChunk:n.some(e=>e.isFirstChunk)})}abandonFarendStream(){R("farend.drop",{reason:"stale_bucket_abandons_webm_stream",pendingFarend:[...this.pendingFarend.keys()]}),this.pendingFarend.clear(),this.pendingWebmPrefixByStream.delete(2),this.farendStreamState="awaiting-header"}prune(e){const t=e-3;[...this.pendingFarend.keys()].some(e=>e<t)&&this.abandonFarendStream();for(const e of this.pendingNearend.keys())e<t&&this.flushBucket(e,!0);for(const e of this.closedNearendBuckets)e<t&&this.closedNearendBuckets.delete(e)}}const _=["e2eServer","authToken","audio","reconnect","agentCallbacks","webSocketCtor","asr_id","asr_config","tts_config","features","extras","llm_id","brain_config","session_speak_req_id"];function I(e){if(void 0===e)return!1;const t=JSON.stringify(e);return void 0!==t&&"{}"!==t}const F={1006:"E2E WebSocket 异常断开:未收到 Close frame,请检查 E2EMPServer 日志或中间网络",4e3:"E2E WebSocket 路径未知",4001:"E2E Token 无效",4002:"E2E Session 不存在",4009:"E2E 配额超限",4010:"E2E 服务端主动断开"},D=new Set([1001,1006,1011,1012,1013,1014]);function M(e,t,n){if(void 0!==n&&(!n||Array.isArray(n)||"object"!=typeof n))throw new Error(`Agent ${e} JSON Config 必须是对象`);if(void 0!==t&&I(n))throw new Error(`Agent ${e} 配置不能同时提供 ID 和 JSON Config`)}function N(e,t){if(void 0!==t&&(!t||Array.isArray(t)||"object"!=typeof t))throw new Error(`Agent ${e} 必须是对象`)}class U extends e{constructor(e){var n,s,i;M("ASR",e.asr_id,e.asr_config),M("TTS",void 0,e.tts_config),M("Brain",e.llm_id,e.brain_config),N("features",e.features),N("extras",e.extras);const{e2eServer:r,authToken:a,audio:o,reconnect:l,agentCallbacks:h,webSocketCtor:c,asr_id:u,asr_config:d,tts_config:p,features:m,extras:f,llm_id:g,brain_config:S,session_speak_req_id:E}=e,y=function(e,t){if(null==e)return{};var n={};for(var s in e)if({}.hasOwnProperty.call(e,s)){if(-1!==t.indexOf(s))continue;n[s]=e[s]}return n}(e,_),w=y.onMessage,b=y.onRenderChange,A=y.onNetworkInfo,k=y.onSpeakStateChange,v=y.onVoiceStateChange,C=y.onAudioPlaybackData,R=e=>{var t;null==(t=F)||t.forwardFarendAudio(e),null==C||C(e)},T=null!=E?E:1,O=function(e){if(!e||!I(e))return;const n=e.extra_body;if(void 0!==n&&(!n||Array.isArray(n)||"object"!=typeof n))throw new Error("Agent Brain extra_body 必须是 JSON 对象");return t({},e)}(S);let F;super(t({},y,{session_speak_req_id:T,sessionRequestData:t({},void 0!==u?{asr_id:u}:{},I(d)?{asr_config:d}:{},I(p)?{tts_config:p}:{},I(m)?{features:m}:{},I(f)?{extras:f}:{},void 0!==g?{llm_id:g}:{},void 0!==O?{brain_config:O}:{},{session_speak_req_id:T}),enableClientInterrupt:null==(n=y.enableClientInterrupt)||n,onMessage:e=>{var t,n;w(e),null!=(t=F)&&t.reportingInitializationFailure||null==(n=F)||n.emitError("sdk",String(e.code||"AVATAR_ERROR"),e.message||"Avatar SDK 错误",!0,e)},onRenderChange:e=>{var t;null==b||b(e),null==(t=F)||t.safeEmit(()=>null==h||null==h.onRenderChange?void 0:h.onRenderChange(e))},onNetworkInfo:e=>{var t;null==A||A(e),null==(t=F)||t.safeEmit(()=>null==h||null==h.onNetworkInfo?void 0:h.onNetworkInfo(e))},onSpeakStateChange:(e,t)=>{var n,s,i;null==k||k(e,t),null==(n=F)||n.safeEmit(()=>null==h||null==h.onSpeakStateChange?void 0:h.onSpeakStateChange({state:e,clientSpeakId:t})),["speak_start","start","started"].includes(e)&&(null==(s=F)||s.emitConversation({state:"speaking"})),["speak_end","end","ended","completed","finish","finished"].includes(e)&&(null==(i=F)||i.emitConversation({state:"completed"}))},onVoiceStateChange:(e,t,n)=>{var s;"end"===e&&(null==(s=F)||s.forwardVoiceEnd()),null==v||v(e,t,n)}},C?{onAudioPlaybackData:R}:{})),this.e2eClient=null,this.microphone=null,this.stopASRPromise=null,this.initializationPromise=null,this.rejectInitialization=null,this.initialSessionAttempt=null,this.initialSessionGeneration=0,this.initialTtsaReadySessionIds=new Set,this.initialTtsaReadyBeforeSession=!1,this.asrStartGeneration=0,this.agentState="idle",this.asrState="idle",this.agentDestroyed=!1,this.agentDestroying=!1,this.suppressNextVoiceEnd=!1,this.farendAudioEnabled=!1,this.reconnectTimer=null,this.reconnectAttempt=0,this.reconnectInFlight=!1,this.sessionReloadFallback=!1,this.waitingForTtsaReload=!1,this.stateBeforeReconnect="ready",this.reportingInitializationFailure=!1,this.handleOnlineForReconnect=()=>{"reconnecting"!==this.agentState||this.reconnectInFlight||this.sessionReloadFallback||(this.clearReconnectTimer(),this.scheduleE2EReconnect(!0))},F=this,this.agentOptions={e2eServer:r,authToken:a,audio:o,requestedEnableAec:"boolean"==typeof(null==m||null==(s=m.speech_frontend)?void 0:s.enable_aec)?m.speech_frontend.enable_aec:void 0,callbacks:h,webSocketCtor:c,debugAudioChunks:Boolean(y.enableDebugger)},this.audioInputEnabled=null!=(i=null==o?void 0:o.inputEnabled)&&i,this.originalOnAudioPlaybackData=C,this.playbackDataHandler=R,this.reconnectOptions=this.normalizeReconnectOptions(l),this.audioUplink=new P({sendBinary:e=>{var t;return null==(t=this.e2eClient)?void 0:t.sendBinary(e)},onError:e=>this.handleAudioUplinkError(e)})}getAgentState(){return this.agentState}getState(){return this.getAgentState()}getASRState(){return this.asrState}init(e={}){if(this.initializationPromise)return this.initializationPromise;const t=this.initialize(e).finally(()=>{this.initializationPromise===t&&(this.initializationPromise=null)});return this.initializationPromise=t,t}async initialize(e){this.assertNotDestroyed(),this.setAgentState("initializing");const n=new Promise((e,t)=>{this.rejectInitialization=t});n.catch(()=>{});try{var s;const i=t({},e,{onDownloadProgress:e.onDownloadProgress||(()=>{})}),r=await super.init(i);if(this.assertNotDestroyed(),!r)throw new Error("XmovAvatar 初始化未返回 sessionInfo");for(this.applySessionFeatures(r),this.assertNotDestroyed(),this.beginInitialSession(r);;){const e=this.initialSessionAttempt;if(!e)throw new Error("Agent 初始化缺少会话连接任务");if(await Promise.race([Promise.all([e.e2ePromise,e.ttsaReadyPromise]),n]),e===this.initialSessionAttempt)break}this.assertNotDestroyed(),this.assertE2EOpen();const a=(null==(s=this.initialSessionAttempt)?void 0:s.sessionInfo)||r;return this.setAgentState("ready"),this.start(),a}catch(e){if(!this.agentDestroyed&&!this.agentDestroying){var i;null==(i=this.e2eClient)||i.disconnect(1e3,"agent_init_failed"),this.e2eClient=null,await this.cleanupBeforeInitComplete("agent_init_failed");try{this.setAgentState("failed")}catch(e){}this.reportingInitializationFailure=!0;try{this.reportInitializationFailure("Agent 初始化失败",e)}catch(e){}finally{this.reportingInitializationFailure=!1}try{this.emitError("sdk","AGENT_INIT_FAILED","Agent 初始化失败",!0,e)}catch(e){}this.agentDestroyed=!0}throw e}finally{var r;this.rejectInitialization=null,this.initialSessionGeneration+=1,null==(r=this.initialSessionAttempt)||r.resolveTtsaReady(),this.initialSessionAttempt=null,this.initialTtsaReadySessionIds.clear(),this.initialTtsaReadyBeforeSession=!1}}beginInitialSession(e){const t=this.initialSessionAttempt,n=++this.initialSessionGeneration;null==t||t.resolveTtsaReady();let s=()=>{};const i=new Promise(e=>{s=e}),r=this.replaceE2EConnection(e.e2e_resp).catch(e=>{if(n===this.initialSessionGeneration)throw e}),a={sessionInfo:e,e2ePromise:r,ttsaReadyPromise:i,resolveTtsaReady:s};return this.initialSessionAttempt=a,this.initialTtsaReadySessionIds.delete(e.session_id)?a.resolveTtsaReady():!t&&this.initialTtsaReadyBeforeSession&&(this.initialTtsaReadyBeforeSession=!1,a.resolveTtsaReady()),r.catch(()=>{}),r}start(){var e;"initializing"!==this.agentState&&"running"!==this.agentState&&("reconnecting"!==this.agentState?(this.assertE2EOpen(),super.start(),this.setAgentState("running")):this.waitingForTtsaReload&&null!=(e=this.e2eClient)&&e.isOpen&&("running"===this.stateBeforeReconnect&&super.start(),this.finishReconnect()))}onTtsaReady(e){if(this.rejectInitialization){const t=this.initialSessionAttempt,n=null==e?void 0:e.session_id;!t||n&&t.sessionInfo.session_id!==n?n?this.initialTtsaReadySessionIds.add(n):this.initialTtsaReadyBeforeSession=!0:t.resolveTtsaReady()}else"reconnecting"!==this.agentState?"running"===this.agentState&&super.start():this.start()}abortInitialization(e){var t;null==(t=this.rejectInitialization)||t.call(this,e)}async stop(){if(this.agentDestroyed)return;const e="reconnecting"===this.agentState;e&&(this.stateBeforeReconnect="stopped"),await this.stopASR(),await super.stop(),e||this.setAgentState("stopped")}async destroy(e="user"){var t;if(this.agentDestroyed)"destroyed"!==this.agentState&&this.setAgentState("destroyed");else if(!this.agentDestroying){this.agentDestroying=!0,this.abortInitialization(new Error("Agent 初始化已取消")),this.cancelReconnect(),this.cancelSessionRestart();try{await this.stopASR()}catch(e){this.emitError("sdk","AGENT_ASR_STOP_FAILED","Agent 销毁时停止 ASR 失败",!1,e)}null==(t=this.e2eClient)||t.disconnect(1e3,e),this.e2eClient=null,this.audioUplink.reset();try{await super.destroy(e)}finally{this.agentDestroyed=!0,this.agentDestroying=!1,this.setAgentState("destroyed")}}}async ask(e){this.assertRunning(),this.suppressNextVoiceEnd=!1,this.emitConversation({state:"asking",text:e}),this.sendControl({type:"ask",message:{text:e}})}speak(e){var t;if(arguments.length>1)return(null==(t=arguments[2])||t)&&(this.suppressNextVoiceEnd=!0),super.speak(e,arguments[1],arguments[2],arguments[3]);this.assertRunning(),this.suppressNextVoiceEnd=!1,this.emitConversation({state:"speaking-directly",text:e}),this.sendControl({type:"speak",message:{text:e,is_start:!0,is_end:!0}})}async speakByE2E(e){this.speak(e)}async startASR(e={}){var t;if(this.assertRunning(),this.suppressNextVoiceEnd=!1,this.stopASRPromise&&(await this.stopASRPromise,this.assertRunning()),null!=(t=this.microphone)&&t.isRecording)return;this.assertAudioChunkDuration();const n=e.inputStream?this.cloneASRInputStream(e.inputStream):void 0,s=++this.asrStartGeneration,r=!this.audioInputEnabled,a=new v({audio:this.agentOptions.audio,debugAudioChunks:this.agentOptions.debugAudioChunks,inputStream:n,usePcmWebCodecs:this.farendAudioEnabled,onFrame:(e,t)=>{var n;if("running"===this.agentState&&null!=(n=this.e2eClient)&&n.isOpen)try{var s;this.sendAudioFrame({data:e,timestamp:null!=t?t:Date.now()-((null==(s=this.agentOptions.audio)?void 0:s.chunkMs)||i.chunkMs)})}catch(e){this.stopASRAfterSocketClose()}},onError:e=>this.handleMicrophoneError(a,e)});this.microphone=a;try{this.audioUplink.startInput(),r&&this.enableAudioInput(),this.emitConversation({state:"asking"}),this.assertASRStartActive(s),this.setASRState("requesting-permission"),this.assertASRStartActive(s),this.setASRState("starting"),this.assertASRStartActive(s),await a.start(),this.assertRunning(),this.farendAudioEnabled&&this.restartAudioPlaybackCapture(),this.setASRState("listening")}catch(e){var o,l;await a.stop();const t="AUDIO_FIXED_TRACK_CANCELLED"===(null==e?void 0:e.agentCode),n=s===this.asrStartGeneration,i=!this.agentDestroyed&&!this.agentDestroying&&"running"===this.agentState&&Boolean(null==(o=this.e2eClient)?void 0:o.isOpen);if(this.microphone===a&&(this.microphone=null),n&&!t&&r&&null!=(l=this.e2eClient)&&l.isOpen)try{this.disableAudioInput()}catch(e){}throw!n||this.agentDestroyed||this.agentDestroying||(t&&i?this.setASRState("idle"):t||this.setASRState("failed")),t&&!i&&this.assertRunning(),e}}cloneASRInputStream(e){const t=e.getAudioTracks().find(e=>"ended"!==e.readyState);if(!t)throw new Error("ASR 输入流不包含有效音频轨道");return new MediaStream([t.clone()])}assertASRStartActive(e){if(e===this.asrStartGeneration)return;const t=new Error("麦克风启动已取消");throw t.name="AbortError",t.agentCode="AUDIO_FIXED_TRACK_CANCELLED",t.retryable=!0,t}stopASR(){if(this.asrStartGeneration+=1,this.stopASRPromise)return this.stopASRPromise;const e=this.microphone;var t;return null!=e&&e.isRecording?(this.microphone=null,this.stopASRPromise=this.finishStopASR(e).finally(()=>{this.stopASRPromise=null}),this.stopASRPromise):(null!=(t=this.e2eClient)&&t.isOpen&&this.disableAudioInput(),Promise.resolve())}async finishStopASR(e,t=!0){this.setASRState("stopping"),await e.stop(),await this.audioUplink.drain(),t?(this.disableAudioInput(),this.setASRState("idle")):this.setASRState("failed")}interrupt(){if(this.agentDestroyed)return 0;this.sendControl({type:"interrupt"});const e=super.interrupt("speak");return this.emitConversation({state:"interrupted"}),e}async interruptConversation(e="user"){this.interrupt()}async onSessionReloaded(e){if(!this.agentDestroyed&&!this.agentDestroying){if(this.initializationPromise&&"initializing"===this.agentState)return this.applySessionFeatures(e),void await this.beginInitialSession(e);"reconnecting"!==this.agentState&&(this.stateBeforeReconnect=this.readRestorableState(),this.setAgentState("reconnecting")),this.clearReconnectTimer(),this.sessionReloadFallback=!0,this.applySessionFeatures(e),await this.replaceE2EConnection(e.e2e_resp),this.waitingForTtsaReload=!0,this.sessionReloadFallback=!1}}onSessionReloadExhausted(e){this.rejectInitialization?this.abortInitialization(e instanceof Error?e:new Error("Agent 初始化期间 TTSA Session 恢复失败")):"reconnecting"===this.agentState&&this.failReconnect(e)}async replaceE2EConnection(e){const{wsUrl:t,token:n}=this.resolveE2EConnection(e),i=this.e2eClient;let r;this.audioUplink.reset(),r=new s({wsUrl:t,token:n,audioInputEnabled:this.audioInputEnabled,WebSocketCtor:this.agentOptions.webSocketCtor,onStateChange:e=>{this.e2eClient===r&&("open"===e&&this.audioUplink.reset(Date.now()),this.safeEmit(()=>{var t;return null==(t=this.agentOptions.callbacks)||null==t.onSocketStateChange?void 0:t.onSocketStateChange(e)}))},onEvent:e=>{this.e2eClient===r&&this.handleServerEvent(e)},onError:e=>{this.e2eClient===r&&"initializing"!==this.agentState&&"reconnecting"!==this.agentState&&this.emitError("network","E2E_SOCKET_ERROR",e.message,!0,e)},onClose:e=>{this.e2eClient===r&&this.handleSocketClose(e)}}),this.e2eClient=r,null==i||i.disconnect(1e3,"e2e_connection_replaced"),await r.connect()}resolveE2EConnection(e){const t=Boolean(null==e?void 0:e.ws_url),n=Boolean(null==e?void 0:e.e2e_token);if(t!==n)throw new Error("统一 session 响应的 e2e_resp 必须同时返回 ws_url 和 e2e_token");if(t&&n)return{wsUrl:null==e?void 0:e.ws_url,token:null==e?void 0:e.e2e_token};const s=Boolean(this.agentOptions.e2eServer);if(s!==Boolean(this.agentOptions.authToken))throw new Error("e2eServer 和 authToken 必须同时配置");if(!s)throw new Error("统一 session 响应未返回 e2e_resp.ws_url,且未配置 e2eServer");return{wsUrl:this.agentOptions.e2eServer,token:this.agentOptions.authToken}}sendControl(e){try{var t;null==(t=this.e2eClient)||t.send(e)}catch(e){throw this.emitError("network","E2E_SEND_FAILED","E2E WebSocket 发送失败",!0,e),e}}assertAudioChunkDuration(){var e;const t=(null==(e=this.agentOptions.audio)?void 0:e.chunkMs)||i.chunkMs;if(!this.farendAudioEnabled||t===i.chunkMs)return;const n=new Error("AU v1 音频分片固定为 100ms");throw n.name="NotSupportedError",n.agentCode="AUDIO_CHUNK_DURATION_UNSUPPORTED",n.retryable=!1,this.emitError("sdk",n.agentCode,n.message,n.retryable,n),n}sendAudioFrame(e){if(this.farendAudioEnabled)this.agentOptions.debugAudioChunks&&l("[Agent][E2E] nearend AU chunk",e.data,{timestamp:e.timestamp,stream_id:1}),this.audioUplink.offerNearend(e);else{this.agentOptions.debugAudioChunks&&l("[Agent][E2E] nearend raw WebM/Opus chunk",e.data,{timestamp:e.timestamp});try{var t;null==(t=this.e2eClient)||t.sendBinary(e.data)}catch(e){this.handleAudioUplinkError(e instanceof Error?e:new Error("E2E nearend 音频帧发送失败"))}}}handleAudioUplinkError(e){this.emitError("network","E2E_AUDIO_SEND_FAILED",this.farendAudioEnabled?"E2E AU 音频帧发送失败":"E2E 音频帧发送失败",!0,e);const t=this.microphone;if(null==t||!t.isRecording||this.stopASRPromise)return;this.setASRState("failed"),this.microphone=null;const n=this.finishFailedASR(t).finally(()=>{this.stopASRPromise===n&&(this.stopASRPromise=null)});this.stopASRPromise=n}applySessionFeatures(e){var t,n,s,i,r,a;const o=null!=(t=null==(n=e.e2e_resp)||null==(n=n.features)||null==(n=n.speech_frontend)?void 0:n.enable_aec)?t:null==(s=e.features)||null==(s=s.speech_frontend)?void 0:s.enable_aec;if(this.farendAudioEnabled="boolean"==typeof o?o:null!=(i=null!=(r=this.agentOptions.requestedEnableAec)?r:null==(a=this.agentOptions.audio)?void 0:a.echoCancellationEnabled)&&i,this.farendAudioEnabled||this.audioUplink.clearFarend(),this.farendAudioEnabled||this.originalOnAudioPlaybackData)return this.setAudioPlaybackDataHandler(this.playbackDataHandler),void(this.farendAudioEnabled&&this.enableAudioPlaybackCapture());this.setAudioPlaybackDataHandler(void 0)}forwardFarendAudio(e){var t,n;const s=Boolean(null==(t=this.microphone)?void 0:t.isRecording),i=Boolean(null==(n=this.e2eClient)?void 0:n.isOpen);var r;this.farendAudioEnabled&&s&&i?(this.agentOptions.debugAudioChunks&&l("[Agent][E2E] farend AU chunk",e.data,{timestamp:e.timestamp,stream_id:2,codec:e.codec,sampleRate:e.sampleRate,channels:1,samples:e.samples,speech_id:e.speech_id,isFirstChunk:e.isFirstChunk}),this.audioUplink.offerFarend({data:e.data,timestamp:e.timestamp,isFirstChunk:e.isFirstChunk})):null==(r=globalThis.avatarSDKLogger)||null==r.log||r.log("[Agent][AudioUplink]","farend.drop",{reason:this.farendAudioEnabled?s?"e2e_not_open":"microphone_not_recording":"aec_disabled",timestamp:e.timestamp,isFirstChunk:e.isFirstChunk})}handleMicrophoneError(e,t){var n;const s=t,i="MediaRecorderError"===t.name,r="AudioEncoderError"===t.name,a=i||r,o="NotSupportedError"===t.name,l=Boolean(s.agentCode);if(a&&this.microphone===e&&"stopping"!==this.asrState){this.microphone=null;const t=this.finishFailedASR(e).finally(()=>{this.stopASRPromise===t&&(this.stopASRPromise=null)});this.stopASRPromise=t}this.emitError(a?"asr":l||o?"sdk":"permission",s.agentCode||(i?"MEDIA_RECORDER_ERROR":r?"AUDIO_ENCODER_ERROR":"MICROPHONE_ERROR"),t.message,null!=(n=s.retryable)?n:!o,t)}async finishFailedASR(e){try{await e.stop(),await this.audioUplink.drain()}catch(e){this.emitError("sdk","MICROPHONE_CLEANUP_FAILED","麦克风异常后的资源清理失败",!1,e)}try{this.disableAudioInput()}catch(e){}}enableAudioInput(){var e;this.audioInputEnabled||(this.sendControl({type:"event",message:"audio_input_on"}),this.audioInputEnabled=!0,null==(e=this.e2eClient)||e.setAudioInputEnabled(!0))}disableAudioInput(){var e;this.audioInputEnabled&&(this.sendControl({type:"event",message:"audio_input_off"}),this.audioInputEnabled=!1,null==(e=this.e2eClient)||e.setAudioInputEnabled(!1))}handleServerEvent(e){if(!this.agentDestroyed&&!this.agentDestroying)switch(e.type){case"pong":default:return;case"asr_result":return void this.handleASRResult(e);case"llm_response":return void this.handleLLMResponse(e);case"semantic_judge_round_result":return void this.handleSemanticJudgeResult(e);case"error":return void this.handleBackendError(e)}}handleASRResult(e){const t={text:"string"==typeof e.text?e.text:"",isFinal:!0===e.is_final,raw:e};this.safeEmit(()=>{var e;return null==(e=this.agentOptions.callbacks)||null==e.onASRResult?void 0:e.onASRResult(t)})}handleLLMResponse(e){if("chunk"!==e.event&&"done"!==e.event)return;const n=e.usage,s=t({event:e.event},"string"==typeof e.text?{text:e.text}:{},!0===e.is_first?{isFirst:!0}:{},n?{usage:{promptTokens:this.readFiniteNumber(n.prompt_tokens),completionTokens:this.readFiniteNumber(n.completion_tokens),totalTokens:this.readFiniteNumber(n.total_tokens),cachedTokens:this.readFiniteNumber(n.cached_tokens)}}:{},{raw:e});this.safeEmit(()=>{var e;return null==(e=this.agentOptions.callbacks)||null==e.onLLMResponse?void 0:e.onLLMResponse(s)})}handleSemanticJudgeResult(e){if(!e.message||"object"!=typeof e.message)return;const t=e.message,n={query:"string"==typeof t.query?t.query:"",meaningful:!0===t.meaningful,action:"string"==typeof t.action?t.action:"",raw:e};this.safeEmit(()=>{var e;return null==(e=this.agentOptions.callbacks)||null==e.onSemanticJudgeResult?void 0:e.onSemanticJudgeResult(n)})}forwardVoiceEnd(){var e;if(this.suppressNextVoiceEnd)this.suppressNextVoiceEnd=!1;else if(!this.agentDestroyed&&!this.agentDestroying&&"running"===this.agentState&&null!=(e=this.e2eClient)&&e.isOpen)try{this.sendControl({type:"event",message:"voice_end"})}catch(e){}}readFiniteNumber(e){return"number"==typeof e&&Number.isFinite(e)?e:0}handleBackendError(e){var t;const n=["permission","network","asr","brain","ttsa","quota","sdk"].includes(e.domain)?e.domain:"sdk",s=String(null!=(t=e.code)?t:"BACKEND_ERROR"),i="string"==typeof e.message?e.message:"E2E 后端错误";"asr"===n&&this.stopASRAfterSocketClose(),this.emitConversation({state:"failed"}),this.emitError(n,s,i,"quota"!==n,e)}handleSocketClose(e){if(this.agentDestroyed||this.agentDestroying)return;if(this.rejectInitialization)return void this.abortInitialization(new Error(`Agent 初始化期间 E2E WebSocket 已关闭: ${e.code} ${e.reason||""}`.trim()));if(this.audioUplink.reset(),this.stopASRAfterSocketClose(),this.shouldReconnect(e))return void this.beginReconnect(e);this.setAgentState("failed");const t=4009===e.code?"quota":"network";this.emitError(t,`E2E_CLOSE_${e.code}`,e.reason||F[e.code]||"E2E WebSocket 已关闭","quota"!==t,{code:e.code,reason:e.reason,wasClean:e.wasClean})}shouldReconnect(e){return this.reconnectOptions.enabled&&"idle"!==this.agentState&&"initializing"!==this.agentState&&"destroyed"!==this.agentState&&D.has(e.code)}beginReconnect(e){var t;"reconnecting"!==this.agentState&&(this.stateBeforeReconnect=this.readRestorableState(),this.reconnectAttempt=0,this.sessionReloadFallback=!1,this.setAgentState("reconnecting"),null==globalThis.addEventListener||globalThis.addEventListener("online",this.handleOnlineForReconnect),this.emitError("network","E2E_RECONNECTING","E2E WebSocket 已断开,正在重连",!0,{code:e.code,reason:e.reason,wasClean:e.wasClean})),this.audioInputEnabled=!1,null==(t=this.e2eClient)||t.setAudioInputEnabled(!1),this.scheduleE2EReconnect(!1)}scheduleE2EReconnect(e){if(this.agentDestroyed||this.agentDestroying||"reconnecting"!==this.agentState||null!==this.reconnectTimer||this.reconnectInFlight||this.sessionReloadFallback)return;if(this.reconnectAttempt>=this.reconnectOptions.maxAttempts)return void this.fallbackToSessionReload();const t=e?0:this.getReconnectDelay(this.reconnectAttempt);this.reconnectTimer=globalThis.setTimeout(()=>{this.reconnectTimer=null,this.attemptE2EReconnect()},t)}async attemptE2EReconnect(){const e=this.e2eClient;if(e&&!this.agentDestroyed&&!this.agentDestroying&&"reconnecting"===this.agentState){this.reconnectInFlight=!0,this.reconnectAttempt+=1;try{await e.connect(),this.e2eClient!==e||this.agentDestroyed||this.agentDestroying||this.waitingForTtsaReload||this.finishReconnect()}catch(t){this.e2eClient!==e||this.agentDestroyed||this.agentDestroying||this.scheduleE2EReconnect(!1)}finally{this.reconnectInFlight=!1,this.e2eClient!==e||"reconnecting"!==this.agentState||null!==this.reconnectTimer||this.sessionReloadFallback||this.scheduleE2EReconnect(!1)}}}async fallbackToSessionReload(){if(!(this.sessionReloadFallback||this.agentDestroyed||this.agentDestroying)){this.sessionReloadFallback=!0,this.clearReconnectTimer();try{await this.restartSessionForTransport("e2e_reconnect_exhausted")}catch(e){this.failReconnect(e)}}}finishReconnect(){const e=this.stateBeforeReconnect;this.cancelReconnect(),"idle"!==this.asrState&&this.setASRState("idle"),this.setAgentState(e)}failReconnect(e){this.cancelReconnect(),this.setAgentState("failed"),this.emitConversation({state:"failed"}),this.emitError("network","E2E_RECONNECT_EXHAUSTED","E2E WebSocket 重连失败,请重新初始化 Agent",!1,e)}cancelReconnect(){this.clearReconnectTimer(),null==globalThis.removeEventListener||globalThis.removeEventListener("online",this.handleOnlineForReconnect),this.reconnectAttempt=0,this.reconnectInFlight=!1,this.sessionReloadFallback=!1,this.waitingForTtsaReload=!1}clearReconnectTimer(){null!==this.reconnectTimer&&(globalThis.clearTimeout(this.reconnectTimer),this.reconnectTimer=null)}getReconnectDelay(e){const t=Math.min(this.reconnectOptions.maxDelayMs,this.reconnectOptions.initialDelayMs*2**e);return 0===t?0:Math.round(t*(.8+.4*Math.random()))}readRestorableState(){return"running"===this.agentState||"stopped"===this.agentState||"ready"===this.agentState?this.agentState:"ready"}normalizeReconnectOptions(e){var t;const n=this.readNonNegativeNumber(null==e?void 0:e.initialDelayMs,500);return{enabled:null==(t=null==e?void 0:e.enabled)||t,maxAttempts:Math.max(1,Math.floor(this.readNonNegativeNumber(null==e?void 0:e.maxAttempts,6))),initialDelayMs:n,maxDelayMs:Math.max(n,this.readNonNegativeNumber(null==e?void 0:e.maxDelayMs,8e3))}}readNonNegativeNumber(e,t){return"number"==typeof e&&Number.isFinite(e)&&e>=0?e:t}stopASRAfterSocketClose(){if(this.asrStartGeneration+=1,this.stopASRPromise)return;const e=this.microphone;null!=e&&e.isRecording&&(this.microphone=null,this.stopASRPromise=this.finishStopASR(e,!1).catch(e=>{this.emitError("sdk","MICROPHONE_CLEANUP_FAILED","WebSocket 断开后清理麦克风失败",!1,e)}).finally(()=>{this.stopASRPromise=null}))}setAgentState(e){var t;this.agentState=e,this.agentDestroyed&&"destroyed"!==e||null==(t=this.agentOptions.callbacks)||null==t.onAgentStateChange||t.onAgentStateChange(e)}setASRState(e){this.asrState=e,this.safeEmit(()=>{var t;return null==(t=this.agentOptions.callbacks)||null==t.onASRStateChange?void 0:t.onASRStateChange(e)})}emitConversation(e){this.safeEmit(()=>{var t;return null==(t=this.agentOptions.callbacks)||null==t.onConversationChange?void 0:t.onConversationChange(e)})}emitError(e,t,n,s,i){const r={domain:e,code:t,message:n,retryable:s,cause:i};"asr"===e&&this.setASRState("failed"),this.safeEmit(()=>{var e;return null==(e=this.agentOptions.callbacks)||null==e.onError?void 0:e.onError(r)})}safeEmit(e){this.agentDestroyed||this.agentDestroying||e()}assertRunning(){if(this.assertE2EOpen(),"running"!==this.agentState)throw new Error("Agent 尚未启动,请先调用 start()")}assertE2EOpen(){var e;if(this.assertNotDestroyed(),null==(e=this.e2eClient)||!e.isOpen)throw new Error("Agent 尚未连接 E2E WebSocket")}assertNotDestroyed(){if(this.agentDestroyed||this.agentDestroying)throw new Error("Agent 已销毁")}}export{U as default};