@xmov/avatar 2.0.0-alpha.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +139 -0
  2. package/dist/agent/__tests__/agent.test.d.ts +1 -0
  3. package/dist/agent/audio-debug.d.ts +3 -0
  4. package/dist/agent/audio-uplink.d.ts +62 -0
  5. package/dist/agent/avatar.cjs +2 -0
  6. package/dist/agent/avatar.cjs.map +1 -0
  7. package/dist/agent/avatar.modern.js +2 -0
  8. package/dist/agent/avatar.modern.js.map +1 -0
  9. package/dist/agent/avatar.module.js +2 -0
  10. package/dist/agent/avatar.module.js.map +1 -0
  11. package/dist/agent/avatar.umd.js +2 -0
  12. package/dist/agent/avatar.umd.js.map +1 -0
  13. package/dist/agent/e2e-client.d.ts +32 -0
  14. package/dist/agent/fixed-audio-track.d.ts +48 -0
  15. package/dist/agent/index.cjs +5 -0
  16. package/dist/agent/index.d.ts +104 -0
  17. package/dist/agent/index.umd.js +10 -0
  18. package/dist/agent/microphone.d.ts +49 -0
  19. package/dist/agent/types.d.ts +153 -0
  20. package/dist/baseRender/AudioRenderer.d.ts +46 -0
  21. package/dist/baseRender/AudioWorklet.d.ts +78 -0
  22. package/dist/baseRender/AvatarRenderer.d.ts +226 -0
  23. package/dist/baseRender/MSEAudioPlayer.d.ts +51 -0
  24. package/dist/baseRender/UIRenderer.d.ts +39 -0
  25. package/dist/baseRender/pcm-audio-processor.d.ts +63 -0
  26. package/dist/control/APIForwarder.d.ts +23 -0
  27. package/dist/control/DataCacheQueue.d.ts +69 -0
  28. package/dist/control/EventDispatcher.d.ts +35 -0
  29. package/dist/control/RenderScheduler.d.ts +162 -0
  30. package/dist/control/SaveAndDownload.d.ts +14 -0
  31. package/dist/control/StreamingClient.d.ts +33 -0
  32. package/dist/control/VideoPlayer.d.ts +47 -0
  33. package/dist/control/ttsa.d.ts +111 -0
  34. package/dist/encoding/media-recorder-encoder.d.ts +33 -0
  35. package/dist/encoding/pcm-webm-encoder.d.ts +13 -0
  36. package/dist/encoding/webcodec-opus-encoder.d.ts +41 -0
  37. package/dist/encoding/webm-muxer.d.ts +90 -0
  38. package/dist/index.cjs +2 -0
  39. package/dist/index.cjs.map +1 -0
  40. package/dist/index.d.ts +231 -0
  41. package/dist/index.modern.js +2 -0
  42. package/dist/index.modern.js.map +1 -0
  43. package/dist/index.module.js +2 -0
  44. package/dist/index.module.js.map +1 -0
  45. package/dist/index.umd.js +2 -0
  46. package/dist/index.umd.js.map +1 -0
  47. package/dist/modules/Composition.d.ts +28 -0
  48. package/dist/modules/ResourceManager.d.ts +295 -0
  49. package/dist/modules/TrackRenderer/base-track.d.ts +4 -0
  50. package/dist/modules/TrackRenderer/index.d.ts +12 -0
  51. package/dist/modules/TrackRenderer/render-implements.d.ts +19 -0
  52. package/dist/modules/TrackRenderer/track-pic.d.ts +8 -0
  53. package/dist/modules/TrackRenderer/track-subtitle.d.ts +8 -0
  54. package/dist/modules/cache-manager.d.ts +5 -0
  55. package/dist/modules/decoder.d.ts +84 -0
  56. package/dist/modules/error-handle.d.ts +17 -0
  57. package/dist/modules/network.d.ts +21 -0
  58. package/dist/proto/face_data_pb.d.ts +0 -0
  59. package/dist/types/capability.d.ts +15 -0
  60. package/dist/types/error.d.ts +70 -0
  61. package/dist/types/event.d.ts +25 -0
  62. package/dist/types/frame-data.d.ts +107 -0
  63. package/dist/types/index.d.ts +187 -0
  64. package/dist/types/render.d.ts +18 -0
  65. package/dist/utils/DataInterface.d.ts +113 -0
  66. package/dist/utils/GLDevice.d.ts +46 -0
  67. package/dist/utils/GLPipeline.d.ts +84 -0
  68. package/dist/utils/GLPipelineDebugTools.d.ts +5 -0
  69. package/dist/utils/Math.d.ts +21 -0
  70. package/dist/utils/__tests__/capability-checker.test.d.ts +1 -0
  71. package/dist/utils/audio-session.d.ts +11 -0
  72. package/dist/utils/audio.d.ts +1 -0
  73. package/dist/utils/capability-checker.d.ts +23 -0
  74. package/dist/utils/encodeToken.d.ts +7 -0
  75. package/dist/utils/face.d.ts +1 -0
  76. package/dist/utils/float32-decoder.d.ts +26 -0
  77. package/dist/utils/fpsTracker.d.ts +27 -0
  78. package/dist/utils/index.d.ts +22 -0
  79. package/dist/utils/logger.d.ts +9 -0
  80. package/dist/utils/media-recorder-timestamp.d.ts +15 -0
  81. package/dist/utils/pcmAnalyzer.d.ts +40 -0
  82. package/dist/utils/perfermance.d.ts +15 -0
  83. package/dist/utils/request.d.ts +19 -0
  84. package/dist/utils/requestAnimateFrames.d.ts +31 -0
  85. package/dist/utils/time.d.ts +9 -0
  86. package/dist/view/DebugOverlay.d.ts +54 -0
  87. package/dist/worker/streaming-video.d.ts +1 -0
  88. package/package.json +82 -0
  89. package/src/agent/__tests__/agent.test.ts +3787 -0
  90. package/src/agent/audio-debug.ts +36 -0
  91. package/src/agent/audio-uplink.ts +392 -0
  92. package/src/agent/e2e-client.ts +215 -0
  93. package/src/agent/fixed-audio-track.ts +547 -0
  94. package/src/agent/index.ts +1393 -0
  95. package/src/agent/microphone.ts +534 -0
  96. package/src/agent/types.ts +197 -0
  97. package/src/baseRender/AudioRenderer.ts +317 -0
  98. package/src/baseRender/AudioWorklet.ts +776 -0
  99. package/src/baseRender/AvatarRenderer.ts +1559 -0
  100. package/src/baseRender/MSEAudioPlayer.ts +243 -0
  101. package/src/baseRender/UIRenderer.ts +195 -0
  102. package/src/baseRender/pcm-audio-processor.js +267 -0
  103. package/src/control/APIForwarder.ts +67 -0
  104. package/src/control/DataCacheQueue.ts +372 -0
  105. package/src/control/EventDispatcher.ts +106 -0
  106. package/src/control/RenderScheduler.ts +945 -0
  107. package/src/control/SaveAndDownload.js +77 -0
  108. package/src/control/StreamingClient.ts +138 -0
  109. package/src/control/VideoPlayer.ts +306 -0
  110. package/src/control/ttsa.ts +676 -0
  111. package/src/encoding/media-recorder-encoder.ts +175 -0
  112. package/src/encoding/pcm-webm-encoder.ts +72 -0
  113. package/src/encoding/webcodec-opus-encoder.ts +258 -0
  114. package/src/encoding/webm-muxer.ts +337 -0
  115. package/src/global.d.ts +105 -0
  116. package/src/index.ts +1920 -0
  117. package/src/modules/Composition.ts +79 -0
  118. package/src/modules/ResourceManager.ts +899 -0
  119. package/src/modules/TrackRenderer/base-track.ts +7 -0
  120. package/src/modules/TrackRenderer/index.ts +40 -0
  121. package/src/modules/TrackRenderer/render-implements.ts +269 -0
  122. package/src/modules/TrackRenderer/track-pic.ts +19 -0
  123. package/src/modules/TrackRenderer/track-subtitle.ts +16 -0
  124. package/src/modules/cache-manager.ts +10 -0
  125. package/src/modules/decoder.ts +515 -0
  126. package/src/modules/error-handle.ts +20 -0
  127. package/src/modules/network.ts +71 -0
  128. package/src/proto/face_data.proto +45 -0
  129. package/src/proto/face_data_pb.js +1297 -0
  130. package/src/proto/protobuf.min.js +8 -0
  131. package/src/types/capability.ts +23 -0
  132. package/src/types/error.ts +211 -0
  133. package/src/types/event.ts +38 -0
  134. package/src/types/frame-data.ts +121 -0
  135. package/src/types/index.ts +207 -0
  136. package/src/types/render.ts +18 -0
  137. package/src/utils/DataInterface.ts +746 -0
  138. package/src/utils/GLDevice.ts +286 -0
  139. package/src/utils/GLPipeline.ts +1293 -0
  140. package/src/utils/GLPipelineDebugTools.ts +174 -0
  141. package/src/utils/Math.ts +266 -0
  142. package/src/utils/__tests__/capability-checker.test.ts +159 -0
  143. package/src/utils/audio-session.ts +144 -0
  144. package/src/utils/audio.ts +44 -0
  145. package/src/utils/blueimp-md5.d.ts +3 -0
  146. package/src/utils/capability-checker.ts +379 -0
  147. package/src/utils/encodeToken.ts +100 -0
  148. package/src/utils/face.ts +29 -0
  149. package/src/utils/float32-decoder.js +130 -0
  150. package/src/utils/fpsTracker.ts +100 -0
  151. package/src/utils/index.ts +182 -0
  152. package/src/utils/logger.js +36 -0
  153. package/src/utils/media-recorder-timestamp.ts +79 -0
  154. package/src/utils/pcmAnalyzer.ts +154 -0
  155. package/src/utils/perfermance.ts +141 -0
  156. package/src/utils/request.ts +95 -0
  157. package/src/utils/requestAnimateFrames.ts +195 -0
  158. package/src/utils/time.ts +32 -0
  159. package/src/view/DebugOverlay.ts +404 -0
  160. package/src/worker/streaming-video.ts +4647 -0
@@ -0,0 +1,2 @@
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