@ezuikit/control-broadcast 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,192 @@
1
+ # @ezuikit/control-broadcast
2
+
3
+ 云广播喊话控制模块
4
+
5
+ ## 功能特性
6
+
7
+ - 🎤 语音录制(3-60秒)
8
+ - 📋 默认语音列表查询
9
+ - 📤 临时语音下发
10
+ - 🔊 默认语音下发
11
+ - 🎵 录音播放预览
12
+ - 🌍 国际化支持(中英文)
13
+ - 🔌 事件驱动架构,解耦接口调用
14
+
15
+ ## 架构说明
16
+
17
+ Broadcast 模块采用**事件驱动架构**,不直接调用接口,而是通过 EventEmitter 与上层通信:
18
+
19
+ 1. Broadcast 触发请求事件(`requestQueryVoiceList`、`requestSendVoiceOnce`、`requestSendVoice`)
20
+ 2. 上层(ezopen)监听这些事件,调用实际的接口方法
21
+ 3. 上层将结果通过事件返回给 Broadcast(`voiceListLoaded`、`voiceSent` 等)
22
+ 4. Broadcast 监听结果事件,更新 UI 和内部状态
23
+
24
+ 详细说明请参考 [EVENT_DRIVEN_ARCHITECTURE.md](./EVENT_DRIVEN_ARCHITECTURE.md)
25
+
26
+ ## 安装
27
+
28
+ ```bash
29
+ npm install @ezuikit/control-broadcast
30
+ # or
31
+ yarn add @ezuikit/control-broadcast
32
+ # or
33
+ pnpm install @ezuikit/control-broadcast
34
+ ```
35
+
36
+ ## 使用
37
+
38
+ ### 基础使用
39
+
40
+ ```typescript
41
+ import Broadcast from '@ezuikit/control-broadcast';
42
+
43
+ const broadcast = new Broadcast({
44
+ deviceSerial: 'BA5551167',
45
+ channelNo: '1',
46
+ accessToken: 'your-access-token',
47
+ env: {
48
+ domain: 'https://open.ys7.com',
49
+ },
50
+ });
51
+
52
+ // 打开云广播界面
53
+ broadcast.open();
54
+
55
+ // 监听事件
56
+ broadcast.on('voiceListLoaded', (data) => {
57
+ console.log('语音列表加载完成', data);
58
+ });
59
+
60
+ broadcast.on('voiceSent', (data) => {
61
+ console.log('语音下发成功', data);
62
+ });
63
+
64
+ // 销毁
65
+ broadcast.destroy();
66
+ ```
67
+
68
+ ### 与上层集成(ezopen)
69
+
70
+ 上层需要监听 Broadcast 的请求事件并调用实际接口:
71
+
72
+ ```javascript
73
+ // 监听查询语音列表请求
74
+ broadcast.eventEmitter.on('requestQueryVoiceList', async (data) => {
75
+ try {
76
+ const result = await player.queryVoiceList({
77
+ deviceSerial: data.params.deviceSerial,
78
+ channelNo: data.params.channelNo,
79
+ pageSize: data.params.pageSize,
80
+ });
81
+
82
+ broadcast.eventEmitter.emit('voiceListLoaded', {
83
+ eventType: 'voiceListLoaded',
84
+ code: 0,
85
+ data: result.data,
86
+ msg: '语音列表加载成功',
87
+ });
88
+ } catch (error) {
89
+ broadcast.eventEmitter.emit('voiceListError', {
90
+ eventType: 'voiceListError',
91
+ code: -1,
92
+ msg: error.message,
93
+ error,
94
+ });
95
+ }
96
+ });
97
+
98
+ // 监听临时语音下发请求
99
+ broadcast.eventEmitter.on('requestSendVoiceOnce', async (data) => {
100
+ try {
101
+ const result = await player.sendVoiceOnce({
102
+ deviceSerial: data.deviceSerial,
103
+ channelNo: data.channelNo,
104
+ voiceFile: data.voiceFile,
105
+ });
106
+
107
+ broadcast.eventEmitter.emit('voiceSent', {
108
+ eventType: 'voiceSent',
109
+ code: 0,
110
+ data: result,
111
+ msg: '临时语音下发成功',
112
+ });
113
+ } catch (error) {
114
+ broadcast.eventEmitter.emit('voiceSendError', {
115
+ eventType: 'voiceSendError',
116
+ code: -1,
117
+ msg: error.message,
118
+ error,
119
+ });
120
+ }
121
+ });
122
+
123
+ // 监听默认语音下发请求
124
+ broadcast.eventEmitter.on('requestSendVoice', async (data) => {
125
+ try {
126
+ const result = await player.sendVoice({
127
+ deviceSerial: data.deviceSerial,
128
+ channelNo: data.channelNo,
129
+ fileUrl: data.fileUrl,
130
+ });
131
+
132
+ broadcast.eventEmitter.emit('voiceSent', {
133
+ eventType: 'voiceSent',
134
+ code: 0,
135
+ data: result,
136
+ msg: '默认语音下发成功',
137
+ });
138
+ } catch (error) {
139
+ broadcast.eventEmitter.emit('voiceSendError', {
140
+ eventType: 'voiceSendError',
141
+ code: -1,
142
+ msg: error.message,
143
+ error,
144
+ });
145
+ }
146
+ });
147
+ ```
148
+
149
+ ## API
150
+
151
+ ### 构造函数
152
+
153
+ ```typescript
154
+ new Broadcast(options: BroadcastOptions)
155
+ ```
156
+
157
+ ### 方法
158
+
159
+ - `open()` - 打开云广播界面
160
+ - `close()` - 关闭云广播界面
161
+ - `destroy()` - 销毁实例
162
+ - `on(event, callback)` - 监听事件
163
+ - `off(event, callback)` - 移除事件监听
164
+ - `updateOptions(options)` - 更新配置
165
+
166
+ ### 请求事件(上层需要监听)
167
+
168
+ - `requestQueryVoiceList` - 请求查询语音列表
169
+ - `requestSendVoiceOnce` - 请求下发临时语音
170
+ - `requestSendVoice` - 请求下发默认语音
171
+
172
+ ### 结果事件(上层需要触发)
173
+
174
+ - `voiceListLoaded` - 语音列表加载成功
175
+ - `voiceListError` - 语音列表加载失败
176
+ - `voiceSent` - 语音下发成功
177
+ - `voiceSendError` - 语音下发失败
178
+
179
+ ### 对外事件(应用层可监听)
180
+
181
+ - `open` - 界面打开
182
+ - `close` - 界面关闭
183
+ - `recordStart` - 开始录音
184
+ - `recordStop` - 停止录音
185
+ - `recordComplete` - 录音完成
186
+ - `recordError` - 录音错误
187
+ - `error` - 通用错误
188
+
189
+
190
+ ## License
191
+
192
+ Copyright © Ezviz-OpenBiz
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ /*
2
+ * @ezuikit/control-broadcast v0.0.1
3
+ * Copyright (c) 2026-04-09 Ezviz-OpenBiz
4
+ * Released under the MIT License.
5
+ */
6
+ "use strict";var EventEmitter=function(){function EventEmitter(){this.events=new Map}var _proto=EventEmitter.prototype;return _proto.on=function(event,callback){this.events.has(event)||this.events.set(event,[]),this.events.get(event).push(callback)},_proto.off=function(event,callback){if(this.events.has(event)){var callbacks=this.events.get(event),index=callbacks.indexOf(callback);index>-1&&callbacks.splice(index,1),0===callbacks.length&&this.events.delete(event)}},_proto.emit=function(event){for(var _len=arguments.length,args=new Array(_len>1?_len-1:0),_key=1;_key<_len;_key++)args[_key-1]=arguments[_key];this.events.has(event)&&this.events.get(event).forEach(function(callback){try{callback.apply(void 0,[].concat(args))}catch(error){}})},_proto.once=function(event,callback){var _this=this,onceCallback=function(){for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++)args[_key]=arguments[_key];callback.apply(void 0,[].concat(args)),_this.off(event,onceCallback)};this.on(event,onceCallback)},_proto.removeAllListeners=function(){this.events.clear()},_proto.listenerCount=function(event){var _this_events_get;return(null==(_this_events_get=this.events.get(event))?void 0:_this_events_get.length)||0},EventEmitter}();function _array_like_to_array(arr,len){(null==len||len>arr.length)&&(len=arr.length);for(var i=0,arr2=new Array(len);i<len;i++)arr2[i]=arr[i];return arr2}function asyncGeneratorStep$2(gen,resolve,reject,_next,_throw,key,arg){try{var info=gen[key](arg),value=info.value}catch(error){return void reject(error)}info.done?resolve(value):Promise.resolve(value).then(_next,_throw)}function _async_to_generator$2(fn){return function(){var self=this,args=arguments;return new Promise(function(resolve,reject){var gen=fn.apply(self,args);function _next(value){asyncGeneratorStep$2(gen,resolve,reject,_next,_throw,"next",value)}function _throw(err){asyncGeneratorStep$2(gen,resolve,reject,_next,_throw,"throw",err)}_next(void 0)})}}function _create_for_of_iterator_helper_loose(o,allowArrayLike){var it="undefined"!=typeof Symbol&&o[Symbol.iterator]||o["@@iterator"];if(it)return(it=it.call(o)).next.bind(it);if(Array.isArray(o)||(it=function(o,minLen){if(o){if("string"==typeof o)return _array_like_to_array(o,minLen);var n=Object.prototype.toString.call(o).slice(8,-1);return"Object"===n&&o.constructor&&(n=o.constructor.name),"Map"===n||"Set"===n?Array.from(n):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?_array_like_to_array(o,minLen):void 0}}(o))||allowArrayLike){it&&(o=it);var i=0;return function(){return i>=o.length?{done:!0}:{done:!1,value:o[i++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function _ts_generator$2(thisArg,body){var f,y,t,_={label:0,sent:function(){if(1&t[0])throw t[1];return t[1]},trys:[],ops:[]},g=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return g.next=verb(0),g.throw=verb(1),g.return=verb(2),"function"==typeof Symbol&&(g[Symbol.iterator]=function(){return this}),g;function verb(n){return function(v){return function(op){if(f)throw new TypeError("Generator is already executing.");for(;g&&(g=0,op[0]&&(_=0)),_;)try{if(f=1,y&&(t=2&op[0]?y.return:op[0]?y.throw||((t=y.return)&&t.call(y),0):y.next)&&!(t=t.call(y,op[1])).done)return t;switch(y=0,t&&(op=[2&op[0],t.value]),op[0]){case 0:case 1:t=op;break;case 4:return _.label++,{value:op[1],done:!1};case 5:_.label++,y=op[1],op=[0];continue;case 7:op=_.ops.pop(),_.trys.pop();continue;default:if(!(t=_.trys,(t=t.length>0&&t[t.length-1])||6!==op[0]&&2!==op[0])){_=0;continue}if(3===op[0]&&(!t||op[1]>t[0]&&op[1]<t[3])){_.label=op[1];break}if(6===op[0]&&_.label<t[1]){_.label=t[1],t=op;break}if(t&&_.label<t[2]){_.label=t[2],_.ops.push(op);break}t[2]&&_.ops.pop(),_.trys.pop();continue}op=body.call(thisArg,_)}catch(e){op=[6,e],y=0}finally{f=t=0}if(5&op[0])throw op[1];return{value:op[0]?op[1]:void 0,done:!0}}([n,v])}}}var VoiceRecorder=function(){function VoiceRecorder(options){void 0===options&&(options={}),this.options={minDuration:options.minDuration||3,maxDuration:options.maxDuration||59,sampleRate:options.sampleRate||16e3,channelCount:options.channelCount||1},this.audioContext=null,this.mediaStreamSource=null,this.scriptProcessor=null,this.stream=null,this.startTime=0,this.maxTimer=null,this.audioBuffers=[],this.isRecordingFlag=!1,this.isIOSDevice=/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream,this.mediaRecorder=null,this.recordedChunks=[]}var _proto=VoiceRecorder.prototype;return _proto.start=function(){return _async_to_generator$2(function(){var error;return _ts_generator$2(this,function(_state){switch(_state.label){case 0:if(this.isRecordingFlag)return[2];_state.label=1;case 1:return _state.trys.push([1,3,,4]),[4,this.startWithScriptProcessor()];case 2:return _state.sent(),this.startTime=Date.now(),this.isRecordingFlag=!0,[3,4];case 3:throw error=_state.sent(),this.isRecordingFlag=!1,error;case 4:return[2]}})}).call(this)},_proto.startWithMediaRecorder=function(){return _async_to_generator$2(function(){var _this,_,mimeType;return _ts_generator$2(this,function(_state){switch(_state.label){case 0:return _this=this,_=this,[4,navigator.mediaDevices.getUserMedia({audio:!0})];case 1:return _.stream=_state.sent(),this.recordedChunks=[],mimeType="",MediaRecorder.isTypeSupported("audio/mp4")?mimeType="audio/mp4":MediaRecorder.isTypeSupported("audio/webm")?mimeType="audio/webm":MediaRecorder.isTypeSupported("audio/wav")&&(mimeType="audio/wav"),this.mediaRecorder=new MediaRecorder(this.stream,{mimeType:mimeType||void 0}),this.mediaRecorder.ondataavailable=function(event){event.data.size>0&&_this.recordedChunks.push(event.data)},this.mediaRecorder.start(),[2]}})}).call(this)},_proto.startWithScriptProcessor=function(){return _async_to_generator$2(function(){var _this,audioConstraints,_;return _ts_generator$2(this,function(_state){switch(_state.label){case 0:return _this=this,audioConstraints=this.isIOSDevice?{audio:!0}:{audio:{sampleRate:this.options.sampleRate,channelCount:this.options.channelCount,echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}},_=this,[4,navigator.mediaDevices.getUserMedia(audioConstraints)];case 1:return _.stream=_state.sent(),this.audioContext=new(window.AudioContext||window.webkitAudioContext)({sampleRate:this.options.sampleRate}),this.mediaStreamSource=this.audioContext.createMediaStreamSource(this.stream),this.scriptProcessor=this.audioContext.createScriptProcessor(4096,1,1),this.audioBuffers=[],this.scriptProcessor.onaudioprocess=function(event){if(_this.isRecordingFlag){var inputData=event.inputBuffer.getChannelData(0);_this.audioBuffers.push(new Float32Array(inputData))}},this.mediaStreamSource.connect(this.scriptProcessor),this.scriptProcessor.connect(this.audioContext.destination),[2]}})}).call(this)},_proto.stop=function(){return _async_to_generator$2(function(){var _this;return _ts_generator$2(this,function(_state){switch(_state.label){case 0:return _this=this,[4,new Promise(function(resolve,reject){if(_this.isRecordingFlag){var duration=(Date.now()-_this.startTime)/1e3;if(duration<_this.options.minDuration)return reject(new Error("录音时长不足"+_this.options.minDuration+"秒")),void _this.cleanup();try{_this.isRecordingFlag=!1,_this.stopWithScriptProcessor(duration,resolve,reject)}catch(error){_this.cleanup(),reject(error)}}else resolve(new Blob([],{type:"audio/wav"}))})];case 1:return[2,_state.sent()]}})}).call(this)},_proto.stopWithMediaRecorder=function(duration,resolve,reject){var _this=this;if(!this.mediaRecorder)return reject(new Error("MediaRecorder 未初始化")),void this.cleanup();this.mediaRecorder.onstop=function(){try{var _this_recordedChunks_,mimeType=(null==(_this_recordedChunks_=_this.recordedChunks[0])?void 0:_this_recordedChunks_.type)||"audio/wav",audioBlob=new Blob(_this.recordedChunks,{type:mimeType});_this.cleanup(),resolve(audioBlob)}catch(error){_this.cleanup(),reject(error)}},this.mediaRecorder.stop()},_proto.stopWithScriptProcessor=function(duration,resolve,reject){try{for(var _step,totalLength=this.audioBuffers.reduce(function(acc,buffer){return acc+buffer.length},0),mergedBuffer=new Float32Array(totalLength),offset=0,_iterator=_create_for_of_iterator_helper_loose(this.audioBuffers);!(_step=_iterator()).done;){var buffer=_step.value;mergedBuffer.set(buffer,offset),offset+=buffer.length}var wavBlob=this.encodeWAV(mergedBuffer,this.options.sampleRate);this.cleanup(),resolve(wavBlob)}catch(error){this.cleanup(),reject(error)}},_proto.encodeWAV=function(samples,sampleRate){var buffer=new ArrayBuffer(44+2*samples.length),view=new DataView(buffer),writeString=function(offset,string){for(var i=0;i<string.length;i++)view.setUint8(offset+i,string.charCodeAt(i))};return writeString(0,"RIFF"),view.setUint32(4,36+2*samples.length,!0),writeString(8,"WAVE"),writeString(12,"fmt "),view.setUint32(16,16,!0),view.setUint16(20,1,!0),view.setUint16(22,1,!0),view.setUint32(24,sampleRate,!0),view.setUint32(28,2*sampleRate,!0),view.setUint16(32,2,!0),view.setUint16(34,16,!0),writeString(36,"data"),view.setUint32(40,2*samples.length,!0),function(output,offset,input){for(var i=0;i<input.length;i++,offset+=2){var s=Math.max(-1,Math.min(1,input[i]));output.setInt16(offset,s<0?32768*s:32767*s,!0)}}(view,44,samples),new Blob([buffer],{type:"audio/wav"})},_proto.cleanup=function(){this.isRecordingFlag=!1,this.mediaRecorder&&("inactive"!==this.mediaRecorder.state&&this.mediaRecorder.stop(),this.mediaRecorder=null),this.recordedChunks=[],this.scriptProcessor&&(this.scriptProcessor.disconnect(),this.scriptProcessor=null),this.mediaStreamSource&&(this.mediaStreamSource.disconnect(),this.mediaStreamSource=null),this.audioContext&&(this.audioContext.close(),this.audioContext=null),this.stream&&(this.stream.getTracks().forEach(function(track){return track.stop()}),this.stream=null),this.audioBuffers=[]},_proto.destroy=function(){this.cleanup(),this.maxTimer&&(clearTimeout(this.maxTimer),this.maxTimer=null)},_proto.getDuration=function(){return 0===this.startTime?0:(Date.now()-this.startTime)/1e3},_proto.isRecording=function(){return this.isRecordingFlag},VoiceRecorder}(),zh_CN={title:"广播喊话",defaultVoice:"默认语音",pressToTalk:"按住说话",clickToTalk:"点击录音",sendVoice:"下发语音",recording:"录音中",stopRecording:"停止录音",loading:"加载中...",noVoice:"暂无语音数据",recordingRemaining:"还可录制 {time} 秒",releaseToStop:"松开结束录制",clickToStop:"再次点击按钮结束录制",voiceSentSuccess:"语音下发成功",recordComplete:"录音完成",voiceSendFailed:"语音下发失败",recordFailed:"录音失败",recordTooShort:"录音时长不足3秒",micPermissionDenied:"请允许麦克风权限"},en_US={title:"Voice Broadcast",defaultVoice:"Default Voice",pressToTalk:"Press to Talk",clickToTalk:"Click to Record",sendVoice:"Send Voice",recording:"Recording",stopRecording:"Stop Recording",loading:"Loading...",noVoice:"No voice data",recordingRemaining:"{time}s remaining",releaseToStop:"Release to stop",clickToStop:"Click again to stop",voiceSentSuccess:"Voice sent successfully",recordComplete:"Recording completed",voiceSendFailed:"Failed to send voice",recordFailed:"Recording failed",recordTooShort:"Recording is too short (min 3s)",micPermissionDenied:"Please allow microphone permission"},i18n=new(function(){function I18n(){this.currentLanguage="zh",this.translations={zh:zh_CN,en:en_US}}var _proto=I18n.prototype;return _proto.setLanguage=function(language){this.currentLanguage=language},_proto.t=function(key){return this.translations[this.currentLanguage][key]||key},_proto.getLanguage=function(){return this.currentLanguage},I18n}()),RecordingToast=function(){function RecordingToast(options){void 0===options&&(options={}),this.options={maxDuration:options.maxDuration||59,minDuration:options.minDuration||3,container:options.container||document.body,isMobile:options.isMobile||!1,language:options.language||"zh"},this.container=this.options.container,this.toastElement=null,this.waveformElement=null,this.timer=null,this.currentTime=this.options.maxDuration,this.waveAnimationFrame=null,i18n.setLanguage(this.options.language),this.injectStyles()}var _proto=RecordingToast.prototype;return _proto.injectStyles=function(){var styleId="ezuikit-recording-toast-styles";if(!document.getElementById(styleId)){var style=document.createElement("style");style.id=styleId,style.textContent="\n .ezuikit-recording-toast {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) translateY(-80px);\n z-index: 9999;\n background: rgba(44, 44, 44, 0.95);\n border-radius: 16px;\n width: 120px;\n height: 120px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 16px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\n animation: ezuikit-toast-fade-in 0.2s ease;\n }\n\n /* 当 RecordingToast 在特定容器内时,使用绝对定位并居中 */\n .ezuikit-broadcast-panel .ezuikit-recording-toast {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 1;\n }\n\n @keyframes ezuikit-toast-fade-in {\n from {\n opacity: 0;\n transform: translate(-50%, -50%) translateY(-80px) scale(0.9);\n }\n to {\n opacity: 1;\n transform: translate(-50%, -50%) translateY(-80px) scale(1);\n }\n }\n\n /* 面板内的 toast 淡入动画 */\n .ezuikit-broadcast-panel .ezuikit-recording-toast {\n animation: ezuikit-toast-fade-in-panel 0.2s ease;\n }\n\n @keyframes ezuikit-toast-fade-in-panel {\n from {\n opacity: 0;\n transform: translate(-50%, -50%) scale(0.9);\n }\n to {\n opacity: 1;\n transform: translate(-50%, -50%) scale(1);\n }\n }\n\n .ezuikit-recording-waveform {\n width: 80px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .ezuikit-recording-svg {\n width: 100%;\n height: 100%;\n object-fit: contain;\n }\n\n #ezuikit-recording-hint {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.9);\n text-align: center;\n line-height: 20px;\n padding: 0 12px;\n }\n ",document.head.appendChild(style)}},_proto.getWaveformDefs=function(){return'\n \x3c!-- 柱子0: 最左,最暗 #CCCCCC --\x3e\n <linearGradient id="waveGradient0" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#CCCCCC" stop-opacity="1" />\n <stop offset="100%" stop-color="#CCCCCC" stop-opacity="1" />\n </linearGradient>\n \n \x3c!-- 柱子1: #D6D6D6 --\x3e\n <linearGradient id="waveGradient1" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#D6D6D6" stop-opacity="1" />\n <stop offset="100%" stop-color="#D6D6D6" stop-opacity="1" />\n </linearGradient>\n \n \x3c!-- 柱子2: #E0E0E0 --\x3e\n <linearGradient id="waveGradient2" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#E0E0E0" stop-opacity="1" />\n <stop offset="100%" stop-color="#E0E0E0" stop-opacity="1" />\n </linearGradient>\n \n \x3c!-- 柱子3: #EBEBEB --\x3e\n <linearGradient id="waveGradient3" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#EBEBEB" stop-opacity="1" />\n <stop offset="100%" stop-color="#EBEBEB" stop-opacity="1" />\n </linearGradient>\n \n \x3c!-- 柱子4: 中间,最亮 #FFFFFF --\x3e\n <linearGradient id="waveGradient4" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#FFFFFF" stop-opacity="1" />\n <stop offset="100%" stop-color="#FFFFFF" stop-opacity="1" />\n </linearGradient>\n \n \x3c!-- 柱子5: #EBEBEB --\x3e\n <linearGradient id="waveGradient5" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#EBEBEB" stop-opacity="1" />\n <stop offset="100%" stop-color="#EBEBEB" stop-opacity="1" />\n </linearGradient>\n \n \x3c!-- 柱子6: #E0E0E0 --\x3e\n <linearGradient id="waveGradient6" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#E0E0E0" stop-opacity="1" />\n <stop offset="100%" stop-color="#E0E0E0" stop-opacity="1" />\n </linearGradient>\n \n \x3c!-- 柱子7: #D6D6D6 --\x3e\n <linearGradient id="waveGradient7" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#D6D6D6" stop-opacity="1" />\n <stop offset="100%" stop-color="#D6D6D6" stop-opacity="1" />\n </linearGradient>\n \n \x3c!-- 柱子8: 最右,最暗 #CCCCCC --\x3e\n <linearGradient id="waveGradient8" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#CCCCCC" stop-opacity="1" />\n <stop offset="100%" stop-color="#CCCCCC" stop-opacity="1" />\n </linearGradient>\n '},_proto.createToastHTML=function(){var getBarX=function(index){return 5+16*index},remainingText=i18n.t("recordingRemaining").replace("{time}",String(this.options.maxDuration));return'\n <div class="ezuikit-recording-waveform">\n <svg id="ezuikit-waveform-svg" viewBox="0 0 150 100" xmlns="http://www.w3.org/2000/svg" width="80" height="40">\n <defs>\n '+this.getWaveformDefs()+'\n </defs>\n \x3c!-- 9个柱子,宽度12px,间距4px,所有柱子高度变化范围都是30~80px,形成波浪效果,颜色从中间向两边渐变 --\x3e\n <rect id="wave-bar-0" x="'+getBarX(0)+'" y="42.5" width="12" height="15" fill="url(#waveGradient0)" rx="6" />\n <rect id="wave-bar-1" x="'+getBarX(1)+'" y="35" width="12" height="30" fill="url(#waveGradient1)" rx="6" />\n <rect id="wave-bar-2" x="'+getBarX(2)+'" y="27.5" width="12" height="45" fill="url(#waveGradient2)" rx="6" />\n <rect id="wave-bar-3" x="'+getBarX(3)+'" y="20" width="12" height="60" fill="url(#waveGradient3)" rx="6" />\n <rect id="wave-bar-4" x="'+getBarX(4)+'" y="7.5" width="12" height="85" fill="url(#waveGradient4)" rx="6" />\n <rect id="wave-bar-5" x="'+getBarX(5)+'" y="20" width="12" height="60" fill="url(#waveGradient5)" rx="6" />\n <rect id="wave-bar-6" x="'+getBarX(6)+'" y="27.5" width="12" height="45" fill="url(#waveGradient6)" rx="6" />\n <rect id="wave-bar-7" x="'+getBarX(7)+'" y="35" width="12" height="30" fill="url(#waveGradient7)" rx="6" />\n <rect id="wave-bar-8" x="'+getBarX(8)+'" y="42.5" width="12" height="15" fill="url(#waveGradient8)" rx="6" />\n </svg>\n </div>\n <div id="ezuikit-recording-hint">'+remainingText+"</div>\n "},_proto.startWaveformAnimation=function(){var frameIndex=0;this.waveAnimationFrame=window.setInterval(function(){for(var i=0;i<9;i++){var rect=document.getElementById("wave-bar-"+i);if(rect){var phase=(.3*frameIndex+.8*i)%(2*Math.PI),height=30+50*((Math.sin(phase)+1)/2),y=50-height/2;rect.setAttribute("height",height.toFixed(2)),rect.setAttribute("y",y.toFixed(2))}}frameIndex++},100)},_proto.stopWaveformAnimation=function(){this.waveAnimationFrame&&(clearInterval(this.waveAnimationFrame),this.waveAnimationFrame=null)},_proto.show=function(){this.toastElement||(this.toastElement=document.createElement("div"),this.toastElement.className="ezuikit-recording-toast",this.toastElement.innerHTML=this.createToastHTML(),this.container.appendChild(this.toastElement),this.waveformElement=this.toastElement.querySelector(".ezuikit-recording-waveform"),this.currentTime=this.options.maxDuration,this.startCountdown(),this.startWaveformAnimation())},_proto.hide=function(){var _this_toastElement;this.stopCountdown(),this.stopWaveformAnimation(),(null==(_this_toastElement=this.toastElement)?void 0:_this_toastElement.parentNode)&&this.toastElement.parentNode.removeChild(this.toastElement),this.toastElement=null,this.waveformElement=null},_proto.startCountdown=function(){var _this=this;this.timer=window.setInterval(function(){_this.currentTime--;var hintElement=document.getElementById("ezuikit-recording-hint");if(hintElement){var remainingMin=Math.max(0,_this.currentTime);hintElement.innerHTML=remainingMin>0?i18n.t("recordingRemaining").replace("{time}",String(remainingMin)):_this.options.isMobile?i18n.t("releaseToStop"):i18n.t("clickToStop")}_this.currentTime<=0&&_this.stopCountdown()},1e3)},_proto.stopCountdown=function(){this.timer&&(clearInterval(this.timer),this.timer=null)},_proto.getCurrentTime=function(){return this.currentTime},_proto.destroy=function(){this.hide()},RecordingToast}();function asyncGeneratorStep$1(gen,resolve,reject,_next,_throw,key,arg){try{var info=gen[key](arg),value=info.value}catch(error){return void reject(error)}info.done?resolve(value):Promise.resolve(value).then(_next,_throw)}function _async_to_generator$1(fn){return function(){var self=this,args=arguments;return new Promise(function(resolve,reject){var gen=fn.apply(self,args);function _next(value){asyncGeneratorStep$1(gen,resolve,reject,_next,_throw,"next",value)}function _throw(err){asyncGeneratorStep$1(gen,resolve,reject,_next,_throw,"throw",err)}_next(void 0)})}}function _instanceof(left,right){return null!=right&&"undefined"!=typeof Symbol&&right[Symbol.hasInstance]?!!right[Symbol.hasInstance](left):left instanceof right}function _ts_generator$1(thisArg,body){var f,y,t,_={label:0,sent:function(){if(1&t[0])throw t[1];return t[1]},trys:[],ops:[]},g=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return g.next=verb(0),g.throw=verb(1),g.return=verb(2),"function"==typeof Symbol&&(g[Symbol.iterator]=function(){return this}),g;function verb(n){return function(v){return function(op){if(f)throw new TypeError("Generator is already executing.");for(;g&&(g=0,op[0]&&(_=0)),_;)try{if(f=1,y&&(t=2&op[0]?y.return:op[0]?y.throw||((t=y.return)&&t.call(y),0):y.next)&&!(t=t.call(y,op[1])).done)return t;switch(y=0,t&&(op=[2&op[0],t.value]),op[0]){case 0:case 1:t=op;break;case 4:return _.label++,{value:op[1],done:!1};case 5:_.label++,y=op[1],op=[0];continue;case 7:op=_.ops.pop(),_.trys.pop();continue;default:if(!(t=_.trys,(t=t.length>0&&t[t.length-1])||6!==op[0]&&2!==op[0])){_=0;continue}if(3===op[0]&&(!t||op[1]>t[0]&&op[1]<t[3])){_.label=op[1];break}if(6===op[0]&&_.label<t[1]){_.label=t[1],t=op;break}if(t&&_.label<t[2]){_.label=t[2],_.ops.push(op);break}t[2]&&_.ops.pop(),_.trys.pop();continue}op=body.call(thisArg,_)}catch(e){op=[6,e],y=0}finally{f=t=0}if(5&op[0])throw op[1];return{value:op[0]?op[1]:void 0,done:!0}}([n,v])}}}var BroadcastUI=function(){function BroadcastUI(container,options){if("string"==typeof container){var element=document.getElementById(container);this.container=element||document.body}else _instanceof(container,HTMLElement)?this.container=container:this.container=document.body;this.options=options,this.modalElement=null,this.voiceListElement=null,this.talkButton=null,this.isRecording=!1,this.isStarting=!1,this.lastTouchTime=0,this.pendingStop=!1,this.voiceList=options.voiceList||[],this.orientationChangeHandler=null,this.createUI();var panel=this.modalElement.querySelector(".ezuikit-broadcast-panel"),toastContainer=_instanceof(panel,HTMLElement)?panel:this.container;this.recordingToast=new RecordingToast({maxDuration:59,minDuration:3,container:toastContainer,isMobile:options.isMobile,language:options.language}),i18n.setLanguage(options.language),this.options.isMobile&&this.setupOrientationListener()}var _proto=BroadcastUI.prototype;return _proto.setupOrientationListener=function(){var _window_screen,_this=this;this.orientationChangeHandler=function(){_this.modalElement&&"none"!==_this.modalElement.style.display&&_this.positionModalBelowContainer()},window.addEventListener("resize",this.orientationChangeHandler),(null==(_window_screen=window.screen)?void 0:_window_screen.orientation)&&window.screen.orientation.addEventListener("change",this.orientationChangeHandler)},_proto.removeOrientationListener=function(){var _window_screen;this.orientationChangeHandler&&(window.removeEventListener("resize",this.orientationChangeHandler),(null==(_window_screen=window.screen)?void 0:_window_screen.orientation)&&window.screen.orientation.removeEventListener("change",this.orientationChangeHandler),this.orientationChangeHandler=null)},_proto.createUI=function(){this.modalElement=document.createElement("div"),this.modalElement.className="ezuikit-broadcast-modal",this.options.isMobile&&this.modalElement.classList.add("ezuikit-broadcast-modal-mobile"),this.modalElement.innerHTML=this.getModalHTML(),this.injectStyles(),this.options.isMobile?(document.body.appendChild(this.modalElement),this.positionModalBelowContainer()):this.container.appendChild(this.modalElement),this.voiceListElement=this.modalElement.querySelector(".ezuikit-broadcast-voice-list"),this.bindEvents(),this.modalElement.style.display="none",this.voiceList.length>0&&this.renderVoiceList()},_proto.positionModalBelowContainer=function(){var _this_options_player;if(this.options.isMobile&&this.modalElement){var isFullscreen=(null==(_this_options_player=this.options.player)?void 0:_this_options_player.isCurrentFullscreen)||!1;if(this.modalElement.classList.remove("normal-mode","fullscreen-landscape-mode"),isFullscreen)this.modalElement.classList.add("fullscreen-landscape-mode"),this.modalElement.style.position="fixed",this.modalElement.style.left="0",this.modalElement.style.right="0",this.modalElement.style.bottom="0",this.modalElement.style.top="auto";else{this.modalElement.classList.add("normal-mode");var containerRect=this.container.getBoundingClientRect();this.modalElement.style.position="fixed",this.modalElement.style.left="0",this.modalElement.style.right="0",this.modalElement.style.top=containerRect.bottom+"px",this.modalElement.style.bottom="0"}}},_proto.getModalHTML=function(){var t=i18n.t.bind(i18n);return'\n <div class="ezuikit-broadcast-overlay"></div>\n <div class="ezuikit-broadcast-panel" data-direction="'+(this.options.direction||"bottom")+'">\n \x3c!-- 标题栏 --\x3e\n <div class="ezuikit-broadcast-header">\n <button class="ezuikit-broadcast-close" type="button">\n <svg width="24" height="24" viewBox="0 0 24 24" fill="none">\n <path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>\n </svg>\n </button>\n <h2 class="ezuikit-broadcast-title">'+t("title")+'</h2>\n </div>\n\n \x3c!-- Tab区域 --\x3e\n <div class="ezuikit-broadcast-tabs">\n <div class="ezuikit-broadcast-tab active">\n '+t("defaultVoice")+'\n </div>\n </div>\n\n \x3c!-- 内容区域 --\x3e\n <div class="ezuikit-broadcast-content">\n \x3c!-- 语音列表 --\x3e\n <div class="ezuikit-broadcast-voice-list">\n \x3c!-- 语音项将通过 updateVoiceList 动态添加 --\x3e\n <div class="ezuikit-broadcast-voice-empty">\n '+t("loading")+'\n </div>\n </div>\n </div>\n\n \x3c!-- 底部按钮 --\x3e\n <div class="ezuikit-broadcast-footer">\n <button class="ezuikit-broadcast-talk-btn" type="button">\n <span class="ezuikit-broadcast-talk-text">'+(this.options.isMobile?t("pressToTalk"):t("clickToTalk"))+"</span>\n </button>\n </div>\n </div>\n "},_proto.injectStyles=function(){var styleId="ezuikit-broadcast-styles";if(!document.getElementById(styleId)){var style=document.createElement("style");style.id=styleId,style.textContent='\n /* 模态框 */\n .ezuikit-broadcast-modal {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 30;\n font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;\n pointer-events: none;\n }\n\n /* 移动端模态框:固定定位,覆盖整个视口 */\n .ezuikit-broadcast-modal-mobile {\n position: fixed;\n top: auto;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 9999;\n }\n\n .ezuikit-broadcast-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n animation: ezuikit-broadcast-fade-in 0.3s ease;\n pointer-events: auto;\n }\n\n /* 移动端遮罩层:覆盖整个视口 */\n .ezuikit-broadcast-modal-mobile .ezuikit-broadcast-overlay {\n position: fixed;\n }\n\n /* 移动端遮罩层:全屏模式(应用与播放器相同的伪全屏变换) */\n .ezuikit-broadcast-modal-mobile.fullscreen-landscape-mode .ezuikit-broadcast-overlay {\n position: fixed !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n top: 0 !important;\n width: 100vh !important;\n width: var(--ezplayer-mobile-inner-height, 100vh) !important;\n height: 100vw !important;\n height: var(--ezplayer-mobile-inner-width, 100vw) !important;\n transform: rotate(90.001deg) translateY(-100%) !important;\n -webkit-transform: rotate(90.001deg) translateY(-100%) !important;\n transform-origin: 0 0 !important;\n -webkit-transform-origin: 0 0 !important;\n }\n\n /* 主面板 */\n .ezuikit-broadcast-panel {\n position: absolute;\n background: #F5F5F5;\n display: flex;\n flex-direction: column;\n box-shadow: 0 -2px 20px rgba(0, 0, 0, 0.1);\n pointer-events: auto;\n overflow: hidden;\n }\n\n /* 移动端面板:固定定位,基础样式 */\n .ezuikit-broadcast-modal-mobile .ezuikit-broadcast-panel {\n position: fixed !important;\n }\n\n /* 移动端面板:普通模式(定位在 container 下方,从下往上展开) */\n .ezuikit-broadcast-modal-mobile.normal-mode .ezuikit-broadcast-panel {\n bottom: 0 !important;\n top: auto !important;\n left: 0 !important;\n right: 0 !important;\n height: 0;\n animation: ezuikit-broadcast-expand-height-mobile 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards !important;\n }\n\n /* 移动端面板:全屏模式(应用与播放器相同的伪全屏变换,从横屏后的下方往上展开) */\n .ezuikit-broadcast-modal-mobile.fullscreen-landscape-mode .ezuikit-broadcast-panel {\n position: fixed !important;\n left: 0 !important;\n right: auto !important;\n bottom: 0 !important;\n top: 0 !important;\n height: 100vw !important;\n height: var(--ezplayer-mobile-inner-width, 100vw) !important;\n transform: rotate(90.001deg) translateY(-100%) !important;\n -webkit-transform: rotate(90.001deg) translateY(-100%) !important;\n transform-origin: 0 0 !important;\n -webkit-transform-origin: 0 0 !important;\n animation: ezuikit-broadcast-expand-width-mobile-fullscreen 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards !important;\n }\n\n /* 不同方向的展开动画 - 从容器边缘展开 */\n .ezuikit-broadcast-panel[data-direction="bottom"] {\n left: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 0;\n animation: ezuikit-broadcast-expand-height 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;\n }\n\n .ezuikit-broadcast-panel[data-direction="top"] {\n left: 0;\n right: 0;\n top: 0;\n width: 100%;\n height: 0;\n animation: ezuikit-broadcast-expand-height 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;\n }\n\n .ezuikit-broadcast-panel[data-direction="left"] {\n left: 0;\n top: 0;\n bottom: 0;\n width: 100%;\n animation: ezuikit-broadcast-expand-width 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;\n }\n\n .ezuikit-broadcast-panel[data-direction="right"] {\n right: 0;\n top: 0;\n bottom: 0;\n width: 100%;\n animation: ezuikit-broadcast-expand-width 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;\n }\n\n /* 标题栏 */\n .ezuikit-broadcast-header {\n position: relative;\n padding: 16px 20px;\n text-align: center;\n background: rgba(245, 247, 250, 1);\n }\n\n .ezuikit-broadcast-title {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n color: #333333;\n }\n\n .ezuikit-broadcast-close {\n position: absolute;\n left: 16px;\n top: 50%;\n transform: translateY(-50%);\n background: none;\n border: none;\n padding: 4px;\n cursor: pointer;\n color: #666666;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: color 0.2s;\n }\n\n .ezuikit-broadcast-close:hover {\n color: #333333;\n }\n\n /* Tab区域 */\n .ezuikit-broadcast-tabs {\n display: flex;\n padding: 0 20px;\n background: rgba(245, 247, 250, 1);\n }\n\n .ezuikit-broadcast-tab {\n position: relative;\n padding: 12px 16px 4px;\n font-size: 20px;\n color: #999999;\n cursor: pointer;\n transition: color 0.2s;\n font-weight: 700;\n }\n\n .ezuikit-broadcast-tab.active {\n color: rgba(100, 143, 252, 1);\n font-weight: 700;\n }\n\n .ezuikit-broadcast-tab.active::after {\n content: \'\';\n position: absolute;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n width: 10px;\n height: 2px;\n background: #6B9EFF;\n }\n\n /* 内容区域 */\n .ezuikit-broadcast-content {\n flex: 1;\n overflow-y: auto;\n padding: 16px 20px;\n background: rgba(245, 247, 250, 1);\n }\n\n /* 语音列表 */\n .ezuikit-broadcast-voice-list {\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .ezuikit-broadcast-voice-empty {\n text-align: center;\n padding: 40px 20px;\n color: #999999;\n font-size: 14px;\n }\n\n .ezuikit-broadcast-voice-item {\n display: flex;\n align-items: center;\n padding: 11px 16px;\n background: rgba(237, 237, 237, 1);\n border-radius: 16px;\n height: 42px;\n box-sizing: border-box;\n cursor: pointer;\n transition: all 0.2s;\n }\n\n .ezuikit-broadcast-voice-item:hover {\n background: rgba(237, 237, 237, 1);\n }\n\n .ezuikit-broadcast-voice-item.selected {\n background: rgba(237, 237, 237, 1);\n }\n\n .ezuikit-broadcast-voice-icon {\n width: 24px;\n height: 24px;\n margin-right: 12px;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #FFFFFF;\n background: #2C2C2C;\n border-radius: 50%;\n padding: 3px;\n box-sizing: border-box;\n transition: background 0.2s;\n }\n\n .ezuikit-broadcast-voice-item.selected .ezuikit-broadcast-voice-icon {\n background: rgba(100, 143, 252, 1);\n color: #FFFFFF;\n }\n\n .ezuikit-broadcast-voice-text {\n flex: 1;\n font-size: 14px;\n color: rgba(102, 102, 102, 1);\n line-height: 20px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n /* 底部按钮 */\n .ezuikit-broadcast-footer {\n padding: 16px 20px 20px;\n background: #F5F5F5;\n }\n\n .ezuikit-broadcast-talk-btn {\n width: 100%;\n padding: 16px;\n background: #6B9EFF;\n color: #FFFFFF;\n border: none;\n font-size: 16px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n user-select: none;\n border-radius: 8px;\n }\n\n .ezuikit-broadcast-talk-btn:hover {\n background: #5A8EEE;\n }\n\n .ezuikit-broadcast-talk-btn:active {\n transform: scale(0.98);\n }\n\n .ezuikit-broadcast-talk-btn.recording {\n background: #FF5252;\n }\n\n .ezuikit-broadcast-talk-btn.disabled {\n background: #CCCCCC;\n cursor: not-allowed;\n }\n\n /* 动画 */\n @keyframes ezuikit-broadcast-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n @keyframes ezuikit-broadcast-expand-height {\n from { height: 0; }\n to { height: 80%; }\n }\n\n @keyframes ezuikit-broadcast-expand-height-mobile {\n from { height: 0; }\n to { height: 70vh; }\n }\n\n /* 全屏时从右侧展开的动画(宽度展开) */\n @keyframes ezuikit-broadcast-expand-width-mobile-fullscreen {\n from { width: 0; }\n to { width: 70vh; }\n }\n\n /* 全屏时从底部展开的动画 */\n @keyframes ezuikit-broadcast-expand-height-mobile-fullscreen {\n from { height: 0; }\n to { height: 85vh; }\n }\n\n @keyframes ezuikit-broadcast-expand-width {\n from { width: 0; }\n to { \n width: 80%;\n max-width: 400px;\n }\n }\n ',document.head.appendChild(style)}},_proto.bindEvents=function(){var _this=this;if(this.modalElement){var closeBtn=this.modalElement.querySelector(".ezuikit-broadcast-close");closeBtn&&closeBtn.addEventListener("click",function(){_this.close()});var overlay=this.modalElement.querySelector(".ezuikit-broadcast-overlay");overlay&&overlay.addEventListener("click",function(){_this.close()}),this.talkButton=this.modalElement.querySelector(".ezuikit-broadcast-talk-btn"),this.talkButton&&(this.options.isMobile?(this.talkButton.addEventListener("touchstart",function(e){e.preventDefault(),e.stopPropagation();var now=Date.now();now-_this.lastTouchTime<300||(_this.lastTouchTime=now,_this.handleRecordStart())},{passive:!1}),this.talkButton.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),_this.handleRecordStop()},{passive:!1}),this.talkButton.addEventListener("touchcancel",function(){_this.isRecording&&_this.handleRecordStop()})):this.talkButton.addEventListener("click",function(e){e.preventDefault(),_this.handleRecordToggle()}))}},_proto.handleRecordToggle=function(){this.isRecording?this.handleRecordStop():this.handleRecordStart()},_proto.handleRecordStart=function(){return _async_to_generator$1(function(){var textElement,error,textElement1;return _ts_generator$1(this,function(_state){switch(_state.label){case 0:if(this.isRecording||this.isStarting)return[2];_state.label=1;case 1:return _state.trys.push([1,5,,6]),this.pendingStop=!1,this.isStarting=!0,this.recordingToast.show(),this.talkButton&&(this.talkButton.classList.add("recording"),(textElement=this.talkButton.querySelector(".ezuikit-broadcast-talk-text"))&&(textElement.textContent=this.options.isMobile?i18n.t("recording"):i18n.t("stopRecording"))),[4,this.options.onRecordStart()];case 2:return _state.sent(),this.isRecording=!0,this.isStarting=!1,this.pendingStop?(this.pendingStop=!1,[4,this.handleRecordStop()]):[3,4];case 3:_state.sent(),_state.label=4;case 4:return[3,6];case 5:return error=_state.sent(),this.isStarting=!1,this.isRecording=!1,this.pendingStop=!1,this.recordingToast.hide(),this.talkButton&&(this.talkButton.classList.remove("recording"),(textElement1=this.talkButton.querySelector(".ezuikit-broadcast-talk-text"))&&(textElement1.textContent=this.options.isMobile?i18n.t("pressToTalk"):i18n.t("clickToTalk"))),this.showError(_instanceof(error,Error)?error.message:"开始录音失败"),[3,6];case 6:return[2]}})}).call(this)},_proto.handleRecordStop=function(){return _async_to_generator$1(function(){var textElement,error,textElement1;return _ts_generator$1(this,function(_state){switch(_state.label){case 0:if(this.isStarting)return this.pendingStop=!0,[2];if(!this.isRecording)return[2];_state.label=1;case 1:return _state.trys.push([1,3,,4]),this.recordingToast.hide(),this.talkButton&&(this.talkButton.classList.remove("recording"),(textElement=this.talkButton.querySelector(".ezuikit-broadcast-talk-text"))&&(textElement.textContent=this.options.isMobile?i18n.t("pressToTalk"):i18n.t("clickToTalk"))),[4,this.options.onRecordStop()];case 2:return _state.sent(),this.isRecording=!1,[3,4];case 3:return error=_state.sent(),this.isRecording=!1,this.recordingToast.hide(),this.talkButton&&(this.talkButton.classList.remove("recording"),(textElement1=this.talkButton.querySelector(".ezuikit-broadcast-talk-text"))&&(textElement1.textContent=this.options.isMobile?i18n.t("pressToTalk"):i18n.t("clickToTalk"))),this.showError(_instanceof(error,Error)?error.message:"停止录音失败"),[3,4];case 4:return[2]}})}).call(this)},_proto.show=function(){this.modalElement&&(this.modalElement.style.display="block",this.options.isMobile&&this.positionModalBelowContainer())},_proto.hide=function(){if(this.isRecording)try{this.handleRecordStop()}catch(error){this.isRecording=!1,this.recordingToast.hide()}this.modalElement&&(this.modalElement.style.display="none")},_proto.close=function(){this.hide(),this.options.onClose()},_proto.updateVoiceList=function(voiceList){this.voiceList=voiceList,this.renderVoiceList()},_proto.renderVoiceList=function(){var _this=this;this.voiceListElement&&(0!==this.voiceList.length?(this.voiceListElement.innerHTML=this.voiceList.map(function(voice,index){return'\n <div class="ezuikit-broadcast-voice-item" data-index="'+index+'">\n <div class="ezuikit-broadcast-voice-icon">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">\n <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>\n </svg>\n </div>\n <div class="ezuikit-broadcast-voice-text" title="'+voice.voiceName+'">'+voice.voiceName+"</div>\n </div>\n "}).join(""),this.voiceListElement.querySelectorAll(".ezuikit-broadcast-voice-item").forEach(function(item){item.addEventListener("click",function(){var index=parseInt(item.getAttribute("data-index")||"-1");_this.selectVoice(index)})})):this.voiceListElement.innerHTML='\n <div class="ezuikit-broadcast-voice-empty">\n '+i18n.t("noVoice")+"\n </div>\n ")},_proto.selectVoice=function(index){if(this.voiceListElement&&(this.voiceListElement.querySelectorAll(".ezuikit-broadcast-voice-item").forEach(function(item){item.classList.remove("selected")}),index>=0&&index<this.voiceList.length)){var selectedItem=this.voiceListElement.querySelector('[data-index="'+index+'"]');selectedItem&&selectedItem.classList.add("selected");var selectedVoice=this.voiceList[index];this.options.onVoiceSend(selectedVoice.fileUrl)}},_proto.setRecordingState=function(isRecording){this.isRecording=isRecording},_proto.showSuccess=function(message){},_proto.showError=function(message){},_proto.destroy=function(){var _this_modalElement;this.removeOrientationListener(),this.recordingToast.destroy(),(null==(_this_modalElement=this.modalElement)?void 0:_this_modalElement.parentNode)&&this.modalElement.parentNode.removeChild(this.modalElement),this.modalElement=null,this.voiceListElement=null,this.talkButton=null},BroadcastUI}();function asyncGeneratorStep(gen,resolve,reject,_next,_throw,key,arg){try{var info=gen[key](arg),value=info.value}catch(error){return void reject(error)}info.done?resolve(value):Promise.resolve(value).then(_next,_throw)}function _async_to_generator(fn){return function(){var self=this,args=arguments;return new Promise(function(resolve,reject){var gen=fn.apply(self,args);function _next(value){asyncGeneratorStep(gen,resolve,reject,_next,_throw,"next",value)}function _throw(err){asyncGeneratorStep(gen,resolve,reject,_next,_throw,"throw",err)}_next(void 0)})}}function _ts_generator(thisArg,body){var f,y,t,_={label:0,sent:function(){if(1&t[0])throw t[1];return t[1]},trys:[],ops:[]},g=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return g.next=verb(0),g.throw=verb(1),g.return=verb(2),"function"==typeof Symbol&&(g[Symbol.iterator]=function(){return this}),g;function verb(n){return function(v){return function(op){if(f)throw new TypeError("Generator is already executing.");for(;g&&(g=0,op[0]&&(_=0)),_;)try{if(f=1,y&&(t=2&op[0]?y.return:op[0]?y.throw||((t=y.return)&&t.call(y),0):y.next)&&!(t=t.call(y,op[1])).done)return t;switch(y=0,t&&(op=[2&op[0],t.value]),op[0]){case 0:case 1:t=op;break;case 4:return _.label++,{value:op[1],done:!1};case 5:_.label++,y=op[1],op=[0];continue;case 7:op=_.ops.pop(),_.trys.pop();continue;default:if(!(t=_.trys,(t=t.length>0&&t[t.length-1])||6!==op[0]&&2!==op[0])){_=0;continue}if(3===op[0]&&(!t||op[1]>t[0]&&op[1]<t[3])){_.label=op[1];break}if(6===op[0]&&_.label<t[1]){_.label=t[1],t=op;break}if(t&&_.label<t[2]){_.label=t[2],_.ops.push(op);break}t[2]&&_.ops.pop(),_.trys.pop();continue}op=body.call(thisArg,_)}catch(e){op=[6,e],y=0}finally{f=t=0}if(5&op[0])throw op[1];return{value:op[0]?op[1]:void 0,done:!0}}([n,v])}}}var EVENTS={open:"open",close:"close",requestQueryVoiceList:"requestQueryVoiceList",voiceListLoaded:"voiceListLoaded",voiceListError:"voiceListError",requestSendVoiceOnce:"requestSendVoiceOnce",requestSendVoice:"requestSendVoice",voiceSelected:"voiceSelected",voiceRecorded:"voiceRecorded",recordStart:"recordStart",recordStop:"recordStop",recordComplete:"recordComplete",recordError:"recordError",voiceSent:"voiceSent",voiceSendError:"voiceSendError",error:"error"},DefaultOptions={channelNo:"1",env:{domain:"https://open.ys7.com"},language:"zh"},Broadcast=function(){function Broadcast(options){var _this=this;this.options=Object.assign({},DefaultOptions,options),this.eventEmitter=new EventEmitter,this.voiceRecorder=null,this.isOpen=!1,this.voiceList=options.voiceList||[],this.maxDurationTimer=null,this.pendingAudioBlob=null;var container=this.options.container||document.body;this.broadcastUI=new BroadcastUI(container,{language:this.options.language||"zh",direction:this.options.direction||"right",voiceList:this.voiceList,isMobile:this.options.isMobile||!1,player:this.options.player,onClose:function(){return _this.close()},onRecordStart:function(){return _async_to_generator(function(){return _ts_generator(this,function(_state){switch(_state.label){case 0:return[4,this.startRecord()];case 1:return[2,_state.sent()]}})}).call(_this)},onRecordStop:function(){return _async_to_generator(function(){return _ts_generator(this,function(_state){switch(_state.label){case 0:return[4,this.stopRecord()];case 1:return[2,_state.sent()]}})}).call(_this)},onVoiceSend:function(fileUrl){return _this.sendVoice(fileUrl)},onVoiceSendOnce:function(){}}),this.setupEventListeners()}var _proto=Broadcast.prototype;return _proto.initMicrophonePermission=function(){return _async_to_generator(function(){var error;return _ts_generator(this,function(_state){switch(_state.label){case 0:return _state.trys.push([0,2,,3]),[4,navigator.mediaDevices.getUserMedia({audio:{sampleRate:16e3,channelCount:1,echoCancellation:!0,noiseSuppression:!0}})];case 1:return _state.sent().getTracks().forEach(function(track){return track.stop()}),[3,3];case 2:return error=_state.sent(),this.eventEmitter.emit(EVENTS.error,{eventType:EVENTS.error,code:-1,msg:"麦克风权限获取失败",error:error}),[3,3];case 3:return[2]}})}).call(this)},_proto.requestMicrophonePermission=function(){return _async_to_generator(function(){var error;return _ts_generator(this,function(_state){switch(_state.label){case 0:return _state.trys.push([0,2,,3]),[4,navigator.mediaDevices.getUserMedia({audio:{sampleRate:16e3,channelCount:1,echoCancellation:!0,noiseSuppression:!0}})];case 1:return _state.sent().getTracks().forEach(function(track){return track.stop()}),[2,!0];case 2:return error=_state.sent(),this.eventEmitter.emit(EVENTS.error,{eventType:EVENTS.error,code:-1,msg:"麦克风权限获取失败",error:error}),[2,!1];case 3:return[2]}})}).call(this)},_proto.setupEventListeners=function(){var _this=this;this.eventEmitter.on(EVENTS.voiceListLoaded,function(data){_this.voiceList=data.data||[],_this.broadcastUI&&_this.broadcastUI.updateVoiceList(_this.voiceList)}),this.eventEmitter.on(EVENTS.voiceListError,function(data){_this.broadcastUI&&_this.broadcastUI.showError(data.msg||"语音列表加载失败")}),this.eventEmitter.on(EVENTS.voiceSent,function(data){_this.broadcastUI&&_this.broadcastUI.showSuccess(data.msg||"语音下发成功")}),this.eventEmitter.on(EVENTS.voiceSendError,function(data){_this.broadcastUI&&_this.broadcastUI.showError(data.msg||"语音下发失败")})},_proto.open=function(){if(!this.isOpen)try{this.broadcastUI.show(),this.isOpen=!0,0===this.voiceList.length&&this.loadVoiceList(),this.eventEmitter.emit(EVENTS.open,{eventType:EVENTS.open,code:0,msg:"云广播界面已打开"})}catch(error){this.eventEmitter.emit(EVENTS.error,{eventType:EVENTS.error,code:-1,msg:"打开云广播界面失败",error:error})}},_proto.close=function(){if(this.isOpen)try{if(this.voiceRecorder)try{this.voiceRecorder.stop()}catch(error){}this.broadcastUI&&this.broadcastUI.hide(),this.isOpen=!1,this.eventEmitter.emit(EVENTS.close,{eventType:EVENTS.close,code:0,msg:"云广播界面已关闭"})}catch(error){}},_proto.loadVoiceList=function(){this.eventEmitter.emit(EVENTS.requestQueryVoiceList,{eventType:EVENTS.requestQueryVoiceList,params:{deviceSerial:this.options.deviceSerial,channelNo:this.options.channelNo,pageSize:50}})},_proto.startRecord=function(){return _async_to_generator(function(){var _this,error;return _ts_generator(this,function(_state){switch(_state.label){case 0:_this=this,_state.label=1;case 1:return _state.trys.push([1,3,,4]),this.pendingAudioBlob=null,this.voiceRecorder||(this.voiceRecorder=new VoiceRecorder({minDuration:3,maxDuration:59,sampleRate:16e3})),[4,this.voiceRecorder.start()];case 2:return _state.sent(),this.maxDurationTimer=window.setTimeout(function(){return _async_to_generator(function(){return _ts_generator(this,function(_state){switch(_state.label){case 0:return[4,this.stopRecordAndCache()];case 1:return _state.sent(),[2]}})}).call(_this)},59e3),this.eventEmitter.emit(EVENTS.recordStart,{eventType:EVENTS.recordStart,code:0,msg:"开始录音"}),this.broadcastUI&&this.broadcastUI.setRecordingState(!0),[3,4];case 3:return error=_state.sent(),this.eventEmitter.emit(EVENTS.recordError,{eventType:EVENTS.recordError,code:-1,msg:"开始录音失败",error:error}),[3,4];case 4:return[2]}})}).call(this)},_proto.stopRecordAndCache=function(){return _async_to_generator(function(){var audioBlob;return _ts_generator(this,function(_state){switch(_state.label){case 0:return _state.trys.push([0,2,,3]),this.voiceRecorder&&this.voiceRecorder.isRecording()?[4,this.voiceRecorder.stop()]:[2];case 1:return 0===(audioBlob=_state.sent()).size?[2]:(this.pendingAudioBlob=audioBlob,this.eventEmitter.emit(EVENTS.recordComplete,{eventType:EVENTS.recordComplete,code:0,data:audioBlob,msg:"录音完成,等待下发"}),[3,3]);case 2:return _state.sent(),this.broadcastUI&&this.broadcastUI.setRecordingState(!1),[3,3];case 3:return[2]}})}).call(this)},_proto.stopRecord=function(){return _async_to_generator(function(){var audioBlob,audioBlob1,error;return _ts_generator(this,function(_state){switch(_state.label){case 0:if(_state.trys.push([0,2,,3]),this.maxDurationTimer&&(clearTimeout(this.maxDurationTimer),this.maxDurationTimer=null),this.pendingAudioBlob)return audioBlob=this.pendingAudioBlob,this.pendingAudioBlob=null,this.eventEmitter.emit(EVENTS.recordStop,{eventType:EVENTS.recordStop,code:0,msg:"停止录音"}),this.broadcastUI&&this.broadcastUI.setRecordingState(!1),this.sendVoiceOnce(audioBlob),[2];if(!this.voiceRecorder)throw new Error("录音器未初始化");return this.voiceRecorder.isRecording()?[4,this.voiceRecorder.stop()]:(this.broadcastUI&&this.broadcastUI.setRecordingState(!1),[2]);case 1:return 0===(audioBlob1=_state.sent()).size?(this.broadcastUI&&this.broadcastUI.setRecordingState(!1),[2]):(this.eventEmitter.emit(EVENTS.recordStop,{eventType:EVENTS.recordStop,code:0,msg:"停止录音"}),this.eventEmitter.emit(EVENTS.recordComplete,{eventType:EVENTS.recordComplete,code:0,data:audioBlob1,msg:"录音完成"}),this.broadcastUI&&this.broadcastUI.setRecordingState(!1),this.sendVoiceOnce(audioBlob1),[3,3]);case 2:return error=_state.sent(),this.eventEmitter.emit(EVENTS.recordError,{eventType:EVENTS.recordError,code:-1,msg:"停止录音失败",error:error}),this.broadcastUI&&this.broadcastUI.setRecordingState(!1),[3,3];case 3:return[2]}})}).call(this)},_proto.sendVoiceOnce=function(voiceFile){this.eventEmitter.emit(EVENTS.voiceRecorded,{eventType:EVENTS.voiceRecorded,voiceFile:voiceFile,deviceSerial:this.options.deviceSerial,channelNo:this.options.channelNo}),this.eventEmitter.emit(EVENTS.requestSendVoiceOnce,{eventType:EVENTS.requestSendVoiceOnce,voiceFile:voiceFile,deviceSerial:this.options.deviceSerial,channelNo:this.options.channelNo})},_proto.sendVoice=function(fileUrl){this.eventEmitter.emit(EVENTS.requestSendVoice,{eventType:EVENTS.requestSendVoice,fileUrl:fileUrl,deviceSerial:this.options.deviceSerial,channelNo:this.options.channelNo})},_proto.updateOptions=function(options){this.options=Object.assign(this.options,options)},_proto.on=function(event,callback){this.eventEmitter.on(event,callback)},_proto.off=function(event,callback){this.eventEmitter.off(event,callback)},_proto.destroy=function(){this.close(),this.voiceRecorder&&(this.voiceRecorder.destroy(),this.voiceRecorder=null),this.broadcastUI&&this.broadcastUI.destroy(),this.voiceList=[],this.eventEmitter.removeAllListeners()},Broadcast}();Broadcast.VERSION="0.0.1",Broadcast.EVENTS=EVENTS,module.exports=Broadcast;
@@ -0,0 +1,6 @@
1
+ /*
2
+ * @ezuikit/control-broadcast v0.0.1
3
+ * Copyright (c) 2026-04-09 Ezviz-OpenBiz
4
+ * Released under the MIT License.
5
+ */
6
+ !function(global,factory){"object"==typeof exports&&"undefined"!=typeof module?module.exports=factory():"function"==typeof define&&define.amd?define(factory):(global="undefined"!=typeof globalThis?globalThis:global||self).Broadcast=factory()}(this,function(){"use strict";var EventEmitter=function(){function EventEmitter(){this.events=new Map}var _proto=EventEmitter.prototype;return _proto.on=function(event,callback){this.events.has(event)||this.events.set(event,[]),this.events.get(event).push(callback)},_proto.off=function(event,callback){if(this.events.has(event)){var callbacks=this.events.get(event),index=callbacks.indexOf(callback);index>-1&&callbacks.splice(index,1),0===callbacks.length&&this.events.delete(event)}},_proto.emit=function(event){for(var _len=arguments.length,args=new Array(_len>1?_len-1:0),_key=1;_key<_len;_key++)args[_key-1]=arguments[_key];this.events.has(event)&&this.events.get(event).forEach(function(callback){try{callback.apply(void 0,[].concat(args))}catch(error){}})},_proto.once=function(event,callback){var _this=this,onceCallback=function(){for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++)args[_key]=arguments[_key];callback.apply(void 0,[].concat(args)),_this.off(event,onceCallback)};this.on(event,onceCallback)},_proto.removeAllListeners=function(){this.events.clear()},_proto.listenerCount=function(event){var _this_events_get;return(null==(_this_events_get=this.events.get(event))?void 0:_this_events_get.length)||0},EventEmitter}();function _array_like_to_array(arr,len){(null==len||len>arr.length)&&(len=arr.length);for(var i=0,arr2=new Array(len);i<len;i++)arr2[i]=arr[i];return arr2}function asyncGeneratorStep$2(gen,resolve,reject,_next,_throw,key,arg){try{var info=gen[key](arg),value=info.value}catch(error){return void reject(error)}info.done?resolve(value):Promise.resolve(value).then(_next,_throw)}function _async_to_generator$2(fn){return function(){var self=this,args=arguments;return new Promise(function(resolve,reject){var gen=fn.apply(self,args);function _next(value){asyncGeneratorStep$2(gen,resolve,reject,_next,_throw,"next",value)}function _throw(err){asyncGeneratorStep$2(gen,resolve,reject,_next,_throw,"throw",err)}_next(void 0)})}}function _create_for_of_iterator_helper_loose(o,allowArrayLike){var it="undefined"!=typeof Symbol&&o[Symbol.iterator]||o["@@iterator"];if(it)return(it=it.call(o)).next.bind(it);if(Array.isArray(o)||(it=function(o,minLen){if(o){if("string"==typeof o)return _array_like_to_array(o,minLen);var n=Object.prototype.toString.call(o).slice(8,-1);return"Object"===n&&o.constructor&&(n=o.constructor.name),"Map"===n||"Set"===n?Array.from(n):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?_array_like_to_array(o,minLen):void 0}}(o))||allowArrayLike){it&&(o=it);var i=0;return function(){return i>=o.length?{done:!0}:{done:!1,value:o[i++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function _ts_generator$2(thisArg,body){var f,y,t,_={label:0,sent:function(){if(1&t[0])throw t[1];return t[1]},trys:[],ops:[]},g=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return g.next=verb(0),g.throw=verb(1),g.return=verb(2),"function"==typeof Symbol&&(g[Symbol.iterator]=function(){return this}),g;function verb(n){return function(v){return function(op){if(f)throw new TypeError("Generator is already executing.");for(;g&&(g=0,op[0]&&(_=0)),_;)try{if(f=1,y&&(t=2&op[0]?y.return:op[0]?y.throw||((t=y.return)&&t.call(y),0):y.next)&&!(t=t.call(y,op[1])).done)return t;switch(y=0,t&&(op=[2&op[0],t.value]),op[0]){case 0:case 1:t=op;break;case 4:return _.label++,{value:op[1],done:!1};case 5:_.label++,y=op[1],op=[0];continue;case 7:op=_.ops.pop(),_.trys.pop();continue;default:if(!(t=_.trys,(t=t.length>0&&t[t.length-1])||6!==op[0]&&2!==op[0])){_=0;continue}if(3===op[0]&&(!t||op[1]>t[0]&&op[1]<t[3])){_.label=op[1];break}if(6===op[0]&&_.label<t[1]){_.label=t[1],t=op;break}if(t&&_.label<t[2]){_.label=t[2],_.ops.push(op);break}t[2]&&_.ops.pop(),_.trys.pop();continue}op=body.call(thisArg,_)}catch(e){op=[6,e],y=0}finally{f=t=0}if(5&op[0])throw op[1];return{value:op[0]?op[1]:void 0,done:!0}}([n,v])}}}var VoiceRecorder=function(){function VoiceRecorder(options){void 0===options&&(options={}),this.options={minDuration:options.minDuration||3,maxDuration:options.maxDuration||59,sampleRate:options.sampleRate||16e3,channelCount:options.channelCount||1},this.audioContext=null,this.mediaStreamSource=null,this.scriptProcessor=null,this.stream=null,this.startTime=0,this.maxTimer=null,this.audioBuffers=[],this.isRecordingFlag=!1,this.isIOSDevice=/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream,this.mediaRecorder=null,this.recordedChunks=[]}var _proto=VoiceRecorder.prototype;return _proto.start=function(){return _async_to_generator$2(function(){var error;return _ts_generator$2(this,function(_state){switch(_state.label){case 0:if(this.isRecordingFlag)return[2];_state.label=1;case 1:return _state.trys.push([1,3,,4]),[4,this.startWithScriptProcessor()];case 2:return _state.sent(),this.startTime=Date.now(),this.isRecordingFlag=!0,[3,4];case 3:throw error=_state.sent(),this.isRecordingFlag=!1,error;case 4:return[2]}})}).call(this)},_proto.startWithMediaRecorder=function(){return _async_to_generator$2(function(){var _this,_,mimeType;return _ts_generator$2(this,function(_state){switch(_state.label){case 0:return _this=this,_=this,[4,navigator.mediaDevices.getUserMedia({audio:!0})];case 1:return _.stream=_state.sent(),this.recordedChunks=[],mimeType="",MediaRecorder.isTypeSupported("audio/mp4")?mimeType="audio/mp4":MediaRecorder.isTypeSupported("audio/webm")?mimeType="audio/webm":MediaRecorder.isTypeSupported("audio/wav")&&(mimeType="audio/wav"),this.mediaRecorder=new MediaRecorder(this.stream,{mimeType:mimeType||void 0}),this.mediaRecorder.ondataavailable=function(event){event.data.size>0&&_this.recordedChunks.push(event.data)},this.mediaRecorder.start(),[2]}})}).call(this)},_proto.startWithScriptProcessor=function(){return _async_to_generator$2(function(){var _this,audioConstraints,_;return _ts_generator$2(this,function(_state){switch(_state.label){case 0:return _this=this,audioConstraints=this.isIOSDevice?{audio:!0}:{audio:{sampleRate:this.options.sampleRate,channelCount:this.options.channelCount,echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}},_=this,[4,navigator.mediaDevices.getUserMedia(audioConstraints)];case 1:return _.stream=_state.sent(),this.audioContext=new(window.AudioContext||window.webkitAudioContext)({sampleRate:this.options.sampleRate}),this.mediaStreamSource=this.audioContext.createMediaStreamSource(this.stream),this.scriptProcessor=this.audioContext.createScriptProcessor(4096,1,1),this.audioBuffers=[],this.scriptProcessor.onaudioprocess=function(event){if(_this.isRecordingFlag){var inputData=event.inputBuffer.getChannelData(0);_this.audioBuffers.push(new Float32Array(inputData))}},this.mediaStreamSource.connect(this.scriptProcessor),this.scriptProcessor.connect(this.audioContext.destination),[2]}})}).call(this)},_proto.stop=function(){return _async_to_generator$2(function(){var _this;return _ts_generator$2(this,function(_state){switch(_state.label){case 0:return _this=this,[4,new Promise(function(resolve,reject){if(_this.isRecordingFlag){var duration=(Date.now()-_this.startTime)/1e3;if(duration<_this.options.minDuration)return reject(new Error("录音时长不足"+_this.options.minDuration+"秒")),void _this.cleanup();try{_this.isRecordingFlag=!1,_this.stopWithScriptProcessor(duration,resolve,reject)}catch(error){_this.cleanup(),reject(error)}}else resolve(new Blob([],{type:"audio/wav"}))})];case 1:return[2,_state.sent()]}})}).call(this)},_proto.stopWithMediaRecorder=function(duration,resolve,reject){var _this=this;if(!this.mediaRecorder)return reject(new Error("MediaRecorder 未初始化")),void this.cleanup();this.mediaRecorder.onstop=function(){try{var _this_recordedChunks_,mimeType=(null==(_this_recordedChunks_=_this.recordedChunks[0])?void 0:_this_recordedChunks_.type)||"audio/wav",audioBlob=new Blob(_this.recordedChunks,{type:mimeType});_this.cleanup(),resolve(audioBlob)}catch(error){_this.cleanup(),reject(error)}},this.mediaRecorder.stop()},_proto.stopWithScriptProcessor=function(duration,resolve,reject){try{for(var _step,totalLength=this.audioBuffers.reduce(function(acc,buffer){return acc+buffer.length},0),mergedBuffer=new Float32Array(totalLength),offset=0,_iterator=_create_for_of_iterator_helper_loose(this.audioBuffers);!(_step=_iterator()).done;){var buffer=_step.value;mergedBuffer.set(buffer,offset),offset+=buffer.length}var wavBlob=this.encodeWAV(mergedBuffer,this.options.sampleRate);this.cleanup(),resolve(wavBlob)}catch(error){this.cleanup(),reject(error)}},_proto.encodeWAV=function(samples,sampleRate){var buffer=new ArrayBuffer(44+2*samples.length),view=new DataView(buffer),writeString=function(offset,string){for(var i=0;i<string.length;i++)view.setUint8(offset+i,string.charCodeAt(i))};return writeString(0,"RIFF"),view.setUint32(4,36+2*samples.length,!0),writeString(8,"WAVE"),writeString(12,"fmt "),view.setUint32(16,16,!0),view.setUint16(20,1,!0),view.setUint16(22,1,!0),view.setUint32(24,sampleRate,!0),view.setUint32(28,2*sampleRate,!0),view.setUint16(32,2,!0),view.setUint16(34,16,!0),writeString(36,"data"),view.setUint32(40,2*samples.length,!0),function(output,offset,input){for(var i=0;i<input.length;i++,offset+=2){var s=Math.max(-1,Math.min(1,input[i]));output.setInt16(offset,s<0?32768*s:32767*s,!0)}}(view,44,samples),new Blob([buffer],{type:"audio/wav"})},_proto.cleanup=function(){this.isRecordingFlag=!1,this.mediaRecorder&&("inactive"!==this.mediaRecorder.state&&this.mediaRecorder.stop(),this.mediaRecorder=null),this.recordedChunks=[],this.scriptProcessor&&(this.scriptProcessor.disconnect(),this.scriptProcessor=null),this.mediaStreamSource&&(this.mediaStreamSource.disconnect(),this.mediaStreamSource=null),this.audioContext&&(this.audioContext.close(),this.audioContext=null),this.stream&&(this.stream.getTracks().forEach(function(track){return track.stop()}),this.stream=null),this.audioBuffers=[]},_proto.destroy=function(){this.cleanup(),this.maxTimer&&(clearTimeout(this.maxTimer),this.maxTimer=null)},_proto.getDuration=function(){return 0===this.startTime?0:(Date.now()-this.startTime)/1e3},_proto.isRecording=function(){return this.isRecordingFlag},VoiceRecorder}(),zh_CN={title:"广播喊话",defaultVoice:"默认语音",pressToTalk:"按住说话",clickToTalk:"点击录音",sendVoice:"下发语音",recording:"录音中",stopRecording:"停止录音",loading:"加载中...",noVoice:"暂无语音数据",recordingRemaining:"还可录制 {time} 秒",releaseToStop:"松开结束录制",clickToStop:"再次点击按钮结束录制",voiceSentSuccess:"语音下发成功",recordComplete:"录音完成",voiceSendFailed:"语音下发失败",recordFailed:"录音失败",recordTooShort:"录音时长不足3秒",micPermissionDenied:"请允许麦克风权限"},en_US={title:"Voice Broadcast",defaultVoice:"Default Voice",pressToTalk:"Press to Talk",clickToTalk:"Click to Record",sendVoice:"Send Voice",recording:"Recording",stopRecording:"Stop Recording",loading:"Loading...",noVoice:"No voice data",recordingRemaining:"{time}s remaining",releaseToStop:"Release to stop",clickToStop:"Click again to stop",voiceSentSuccess:"Voice sent successfully",recordComplete:"Recording completed",voiceSendFailed:"Failed to send voice",recordFailed:"Recording failed",recordTooShort:"Recording is too short (min 3s)",micPermissionDenied:"Please allow microphone permission"},i18n=new(function(){function I18n(){this.currentLanguage="zh",this.translations={zh:zh_CN,en:en_US}}var _proto=I18n.prototype;return _proto.setLanguage=function(language){this.currentLanguage=language},_proto.t=function(key){return this.translations[this.currentLanguage][key]||key},_proto.getLanguage=function(){return this.currentLanguage},I18n}()),RecordingToast=function(){function RecordingToast(options){void 0===options&&(options={}),this.options={maxDuration:options.maxDuration||59,minDuration:options.minDuration||3,container:options.container||document.body,isMobile:options.isMobile||!1,language:options.language||"zh"},this.container=this.options.container,this.toastElement=null,this.waveformElement=null,this.timer=null,this.currentTime=this.options.maxDuration,this.waveAnimationFrame=null,i18n.setLanguage(this.options.language),this.injectStyles()}var _proto=RecordingToast.prototype;return _proto.injectStyles=function(){var styleId="ezuikit-recording-toast-styles";if(!document.getElementById(styleId)){var style=document.createElement("style");style.id=styleId,style.textContent="\n .ezuikit-recording-toast {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%) translateY(-80px);\n z-index: 9999;\n background: rgba(44, 44, 44, 0.95);\n border-radius: 16px;\n width: 120px;\n height: 120px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 16px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\n animation: ezuikit-toast-fade-in 0.2s ease;\n }\n\n /* 当 RecordingToast 在特定容器内时,使用绝对定位并居中 */\n .ezuikit-broadcast-panel .ezuikit-recording-toast {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 1;\n }\n\n @keyframes ezuikit-toast-fade-in {\n from {\n opacity: 0;\n transform: translate(-50%, -50%) translateY(-80px) scale(0.9);\n }\n to {\n opacity: 1;\n transform: translate(-50%, -50%) translateY(-80px) scale(1);\n }\n }\n\n /* 面板内的 toast 淡入动画 */\n .ezuikit-broadcast-panel .ezuikit-recording-toast {\n animation: ezuikit-toast-fade-in-panel 0.2s ease;\n }\n\n @keyframes ezuikit-toast-fade-in-panel {\n from {\n opacity: 0;\n transform: translate(-50%, -50%) scale(0.9);\n }\n to {\n opacity: 1;\n transform: translate(-50%, -50%) scale(1);\n }\n }\n\n .ezuikit-recording-waveform {\n width: 80px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .ezuikit-recording-svg {\n width: 100%;\n height: 100%;\n object-fit: contain;\n }\n\n #ezuikit-recording-hint {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.9);\n text-align: center;\n line-height: 20px;\n padding: 0 12px;\n }\n ",document.head.appendChild(style)}},_proto.getWaveformDefs=function(){return'\n \x3c!-- 柱子0: 最左,最暗 #CCCCCC --\x3e\n <linearGradient id="waveGradient0" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#CCCCCC" stop-opacity="1" />\n <stop offset="100%" stop-color="#CCCCCC" stop-opacity="1" />\n </linearGradient>\n \n \x3c!-- 柱子1: #D6D6D6 --\x3e\n <linearGradient id="waveGradient1" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#D6D6D6" stop-opacity="1" />\n <stop offset="100%" stop-color="#D6D6D6" stop-opacity="1" />\n </linearGradient>\n \n \x3c!-- 柱子2: #E0E0E0 --\x3e\n <linearGradient id="waveGradient2" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#E0E0E0" stop-opacity="1" />\n <stop offset="100%" stop-color="#E0E0E0" stop-opacity="1" />\n </linearGradient>\n \n \x3c!-- 柱子3: #EBEBEB --\x3e\n <linearGradient id="waveGradient3" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#EBEBEB" stop-opacity="1" />\n <stop offset="100%" stop-color="#EBEBEB" stop-opacity="1" />\n </linearGradient>\n \n \x3c!-- 柱子4: 中间,最亮 #FFFFFF --\x3e\n <linearGradient id="waveGradient4" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#FFFFFF" stop-opacity="1" />\n <stop offset="100%" stop-color="#FFFFFF" stop-opacity="1" />\n </linearGradient>\n \n \x3c!-- 柱子5: #EBEBEB --\x3e\n <linearGradient id="waveGradient5" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#EBEBEB" stop-opacity="1" />\n <stop offset="100%" stop-color="#EBEBEB" stop-opacity="1" />\n </linearGradient>\n \n \x3c!-- 柱子6: #E0E0E0 --\x3e\n <linearGradient id="waveGradient6" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#E0E0E0" stop-opacity="1" />\n <stop offset="100%" stop-color="#E0E0E0" stop-opacity="1" />\n </linearGradient>\n \n \x3c!-- 柱子7: #D6D6D6 --\x3e\n <linearGradient id="waveGradient7" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#D6D6D6" stop-opacity="1" />\n <stop offset="100%" stop-color="#D6D6D6" stop-opacity="1" />\n </linearGradient>\n \n \x3c!-- 柱子8: 最右,最暗 #CCCCCC --\x3e\n <linearGradient id="waveGradient8" x1="0%" y1="0%" x2="0%" y2="100%">\n <stop offset="0%" stop-color="#CCCCCC" stop-opacity="1" />\n <stop offset="100%" stop-color="#CCCCCC" stop-opacity="1" />\n </linearGradient>\n '},_proto.createToastHTML=function(){var getBarX=function(index){return 5+16*index},remainingText=i18n.t("recordingRemaining").replace("{time}",String(this.options.maxDuration));return'\n <div class="ezuikit-recording-waveform">\n <svg id="ezuikit-waveform-svg" viewBox="0 0 150 100" xmlns="http://www.w3.org/2000/svg" width="80" height="40">\n <defs>\n '+this.getWaveformDefs()+'\n </defs>\n \x3c!-- 9个柱子,宽度12px,间距4px,所有柱子高度变化范围都是30~80px,形成波浪效果,颜色从中间向两边渐变 --\x3e\n <rect id="wave-bar-0" x="'+getBarX(0)+'" y="42.5" width="12" height="15" fill="url(#waveGradient0)" rx="6" />\n <rect id="wave-bar-1" x="'+getBarX(1)+'" y="35" width="12" height="30" fill="url(#waveGradient1)" rx="6" />\n <rect id="wave-bar-2" x="'+getBarX(2)+'" y="27.5" width="12" height="45" fill="url(#waveGradient2)" rx="6" />\n <rect id="wave-bar-3" x="'+getBarX(3)+'" y="20" width="12" height="60" fill="url(#waveGradient3)" rx="6" />\n <rect id="wave-bar-4" x="'+getBarX(4)+'" y="7.5" width="12" height="85" fill="url(#waveGradient4)" rx="6" />\n <rect id="wave-bar-5" x="'+getBarX(5)+'" y="20" width="12" height="60" fill="url(#waveGradient5)" rx="6" />\n <rect id="wave-bar-6" x="'+getBarX(6)+'" y="27.5" width="12" height="45" fill="url(#waveGradient6)" rx="6" />\n <rect id="wave-bar-7" x="'+getBarX(7)+'" y="35" width="12" height="30" fill="url(#waveGradient7)" rx="6" />\n <rect id="wave-bar-8" x="'+getBarX(8)+'" y="42.5" width="12" height="15" fill="url(#waveGradient8)" rx="6" />\n </svg>\n </div>\n <div id="ezuikit-recording-hint">'+remainingText+"</div>\n "},_proto.startWaveformAnimation=function(){var frameIndex=0;this.waveAnimationFrame=window.setInterval(function(){for(var i=0;i<9;i++){var rect=document.getElementById("wave-bar-"+i);if(rect){var phase=(.3*frameIndex+.8*i)%(2*Math.PI),height=30+50*((Math.sin(phase)+1)/2),y=50-height/2;rect.setAttribute("height",height.toFixed(2)),rect.setAttribute("y",y.toFixed(2))}}frameIndex++},100)},_proto.stopWaveformAnimation=function(){this.waveAnimationFrame&&(clearInterval(this.waveAnimationFrame),this.waveAnimationFrame=null)},_proto.show=function(){this.toastElement||(this.toastElement=document.createElement("div"),this.toastElement.className="ezuikit-recording-toast",this.toastElement.innerHTML=this.createToastHTML(),this.container.appendChild(this.toastElement),this.waveformElement=this.toastElement.querySelector(".ezuikit-recording-waveform"),this.currentTime=this.options.maxDuration,this.startCountdown(),this.startWaveformAnimation())},_proto.hide=function(){var _this_toastElement;this.stopCountdown(),this.stopWaveformAnimation(),(null==(_this_toastElement=this.toastElement)?void 0:_this_toastElement.parentNode)&&this.toastElement.parentNode.removeChild(this.toastElement),this.toastElement=null,this.waveformElement=null},_proto.startCountdown=function(){var _this=this;this.timer=window.setInterval(function(){_this.currentTime--;var hintElement=document.getElementById("ezuikit-recording-hint");if(hintElement){var remainingMin=Math.max(0,_this.currentTime);hintElement.innerHTML=remainingMin>0?i18n.t("recordingRemaining").replace("{time}",String(remainingMin)):_this.options.isMobile?i18n.t("releaseToStop"):i18n.t("clickToStop")}_this.currentTime<=0&&_this.stopCountdown()},1e3)},_proto.stopCountdown=function(){this.timer&&(clearInterval(this.timer),this.timer=null)},_proto.getCurrentTime=function(){return this.currentTime},_proto.destroy=function(){this.hide()},RecordingToast}();function asyncGeneratorStep$1(gen,resolve,reject,_next,_throw,key,arg){try{var info=gen[key](arg),value=info.value}catch(error){return void reject(error)}info.done?resolve(value):Promise.resolve(value).then(_next,_throw)}function _async_to_generator$1(fn){return function(){var self=this,args=arguments;return new Promise(function(resolve,reject){var gen=fn.apply(self,args);function _next(value){asyncGeneratorStep$1(gen,resolve,reject,_next,_throw,"next",value)}function _throw(err){asyncGeneratorStep$1(gen,resolve,reject,_next,_throw,"throw",err)}_next(void 0)})}}function _instanceof(left,right){return null!=right&&"undefined"!=typeof Symbol&&right[Symbol.hasInstance]?!!right[Symbol.hasInstance](left):left instanceof right}function _ts_generator$1(thisArg,body){var f,y,t,_={label:0,sent:function(){if(1&t[0])throw t[1];return t[1]},trys:[],ops:[]},g=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return g.next=verb(0),g.throw=verb(1),g.return=verb(2),"function"==typeof Symbol&&(g[Symbol.iterator]=function(){return this}),g;function verb(n){return function(v){return function(op){if(f)throw new TypeError("Generator is already executing.");for(;g&&(g=0,op[0]&&(_=0)),_;)try{if(f=1,y&&(t=2&op[0]?y.return:op[0]?y.throw||((t=y.return)&&t.call(y),0):y.next)&&!(t=t.call(y,op[1])).done)return t;switch(y=0,t&&(op=[2&op[0],t.value]),op[0]){case 0:case 1:t=op;break;case 4:return _.label++,{value:op[1],done:!1};case 5:_.label++,y=op[1],op=[0];continue;case 7:op=_.ops.pop(),_.trys.pop();continue;default:if(!(t=_.trys,(t=t.length>0&&t[t.length-1])||6!==op[0]&&2!==op[0])){_=0;continue}if(3===op[0]&&(!t||op[1]>t[0]&&op[1]<t[3])){_.label=op[1];break}if(6===op[0]&&_.label<t[1]){_.label=t[1],t=op;break}if(t&&_.label<t[2]){_.label=t[2],_.ops.push(op);break}t[2]&&_.ops.pop(),_.trys.pop();continue}op=body.call(thisArg,_)}catch(e){op=[6,e],y=0}finally{f=t=0}if(5&op[0])throw op[1];return{value:op[0]?op[1]:void 0,done:!0}}([n,v])}}}var BroadcastUI=function(){function BroadcastUI(container,options){if("string"==typeof container){var element=document.getElementById(container);this.container=element||document.body}else _instanceof(container,HTMLElement)?this.container=container:this.container=document.body;this.options=options,this.modalElement=null,this.voiceListElement=null,this.talkButton=null,this.isRecording=!1,this.isStarting=!1,this.lastTouchTime=0,this.pendingStop=!1,this.voiceList=options.voiceList||[],this.orientationChangeHandler=null,this.createUI();var panel=this.modalElement.querySelector(".ezuikit-broadcast-panel"),toastContainer=_instanceof(panel,HTMLElement)?panel:this.container;this.recordingToast=new RecordingToast({maxDuration:59,minDuration:3,container:toastContainer,isMobile:options.isMobile,language:options.language}),i18n.setLanguage(options.language),this.options.isMobile&&this.setupOrientationListener()}var _proto=BroadcastUI.prototype;return _proto.setupOrientationListener=function(){var _window_screen,_this=this;this.orientationChangeHandler=function(){_this.modalElement&&"none"!==_this.modalElement.style.display&&_this.positionModalBelowContainer()},window.addEventListener("resize",this.orientationChangeHandler),(null==(_window_screen=window.screen)?void 0:_window_screen.orientation)&&window.screen.orientation.addEventListener("change",this.orientationChangeHandler)},_proto.removeOrientationListener=function(){var _window_screen;this.orientationChangeHandler&&(window.removeEventListener("resize",this.orientationChangeHandler),(null==(_window_screen=window.screen)?void 0:_window_screen.orientation)&&window.screen.orientation.removeEventListener("change",this.orientationChangeHandler),this.orientationChangeHandler=null)},_proto.createUI=function(){this.modalElement=document.createElement("div"),this.modalElement.className="ezuikit-broadcast-modal",this.options.isMobile&&this.modalElement.classList.add("ezuikit-broadcast-modal-mobile"),this.modalElement.innerHTML=this.getModalHTML(),this.injectStyles(),this.options.isMobile?(document.body.appendChild(this.modalElement),this.positionModalBelowContainer()):this.container.appendChild(this.modalElement),this.voiceListElement=this.modalElement.querySelector(".ezuikit-broadcast-voice-list"),this.bindEvents(),this.modalElement.style.display="none",this.voiceList.length>0&&this.renderVoiceList()},_proto.positionModalBelowContainer=function(){var _this_options_player;if(this.options.isMobile&&this.modalElement){var isFullscreen=(null==(_this_options_player=this.options.player)?void 0:_this_options_player.isCurrentFullscreen)||!1;if(this.modalElement.classList.remove("normal-mode","fullscreen-landscape-mode"),isFullscreen)this.modalElement.classList.add("fullscreen-landscape-mode"),this.modalElement.style.position="fixed",this.modalElement.style.left="0",this.modalElement.style.right="0",this.modalElement.style.bottom="0",this.modalElement.style.top="auto";else{this.modalElement.classList.add("normal-mode");var containerRect=this.container.getBoundingClientRect();this.modalElement.style.position="fixed",this.modalElement.style.left="0",this.modalElement.style.right="0",this.modalElement.style.top=containerRect.bottom+"px",this.modalElement.style.bottom="0"}}},_proto.getModalHTML=function(){var t=i18n.t.bind(i18n);return'\n <div class="ezuikit-broadcast-overlay"></div>\n <div class="ezuikit-broadcast-panel" data-direction="'+(this.options.direction||"bottom")+'">\n \x3c!-- 标题栏 --\x3e\n <div class="ezuikit-broadcast-header">\n <button class="ezuikit-broadcast-close" type="button">\n <svg width="24" height="24" viewBox="0 0 24 24" fill="none">\n <path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>\n </svg>\n </button>\n <h2 class="ezuikit-broadcast-title">'+t("title")+'</h2>\n </div>\n\n \x3c!-- Tab区域 --\x3e\n <div class="ezuikit-broadcast-tabs">\n <div class="ezuikit-broadcast-tab active">\n '+t("defaultVoice")+'\n </div>\n </div>\n\n \x3c!-- 内容区域 --\x3e\n <div class="ezuikit-broadcast-content">\n \x3c!-- 语音列表 --\x3e\n <div class="ezuikit-broadcast-voice-list">\n \x3c!-- 语音项将通过 updateVoiceList 动态添加 --\x3e\n <div class="ezuikit-broadcast-voice-empty">\n '+t("loading")+'\n </div>\n </div>\n </div>\n\n \x3c!-- 底部按钮 --\x3e\n <div class="ezuikit-broadcast-footer">\n <button class="ezuikit-broadcast-talk-btn" type="button">\n <span class="ezuikit-broadcast-talk-text">'+(this.options.isMobile?t("pressToTalk"):t("clickToTalk"))+"</span>\n </button>\n </div>\n </div>\n "},_proto.injectStyles=function(){var styleId="ezuikit-broadcast-styles";if(!document.getElementById(styleId)){var style=document.createElement("style");style.id=styleId,style.textContent='\n /* 模态框 */\n .ezuikit-broadcast-modal {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 30;\n font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;\n pointer-events: none;\n }\n\n /* 移动端模态框:固定定位,覆盖整个视口 */\n .ezuikit-broadcast-modal-mobile {\n position: fixed;\n top: auto;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 9999;\n }\n\n .ezuikit-broadcast-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n animation: ezuikit-broadcast-fade-in 0.3s ease;\n pointer-events: auto;\n }\n\n /* 移动端遮罩层:覆盖整个视口 */\n .ezuikit-broadcast-modal-mobile .ezuikit-broadcast-overlay {\n position: fixed;\n }\n\n /* 移动端遮罩层:全屏模式(应用与播放器相同的伪全屏变换) */\n .ezuikit-broadcast-modal-mobile.fullscreen-landscape-mode .ezuikit-broadcast-overlay {\n position: fixed !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n top: 0 !important;\n width: 100vh !important;\n width: var(--ezplayer-mobile-inner-height, 100vh) !important;\n height: 100vw !important;\n height: var(--ezplayer-mobile-inner-width, 100vw) !important;\n transform: rotate(90.001deg) translateY(-100%) !important;\n -webkit-transform: rotate(90.001deg) translateY(-100%) !important;\n transform-origin: 0 0 !important;\n -webkit-transform-origin: 0 0 !important;\n }\n\n /* 主面板 */\n .ezuikit-broadcast-panel {\n position: absolute;\n background: #F5F5F5;\n display: flex;\n flex-direction: column;\n box-shadow: 0 -2px 20px rgba(0, 0, 0, 0.1);\n pointer-events: auto;\n overflow: hidden;\n }\n\n /* 移动端面板:固定定位,基础样式 */\n .ezuikit-broadcast-modal-mobile .ezuikit-broadcast-panel {\n position: fixed !important;\n }\n\n /* 移动端面板:普通模式(定位在 container 下方,从下往上展开) */\n .ezuikit-broadcast-modal-mobile.normal-mode .ezuikit-broadcast-panel {\n bottom: 0 !important;\n top: auto !important;\n left: 0 !important;\n right: 0 !important;\n height: 0;\n animation: ezuikit-broadcast-expand-height-mobile 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards !important;\n }\n\n /* 移动端面板:全屏模式(应用与播放器相同的伪全屏变换,从横屏后的下方往上展开) */\n .ezuikit-broadcast-modal-mobile.fullscreen-landscape-mode .ezuikit-broadcast-panel {\n position: fixed !important;\n left: 0 !important;\n right: auto !important;\n bottom: 0 !important;\n top: 0 !important;\n height: 100vw !important;\n height: var(--ezplayer-mobile-inner-width, 100vw) !important;\n transform: rotate(90.001deg) translateY(-100%) !important;\n -webkit-transform: rotate(90.001deg) translateY(-100%) !important;\n transform-origin: 0 0 !important;\n -webkit-transform-origin: 0 0 !important;\n animation: ezuikit-broadcast-expand-width-mobile-fullscreen 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards !important;\n }\n\n /* 不同方向的展开动画 - 从容器边缘展开 */\n .ezuikit-broadcast-panel[data-direction="bottom"] {\n left: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 0;\n animation: ezuikit-broadcast-expand-height 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;\n }\n\n .ezuikit-broadcast-panel[data-direction="top"] {\n left: 0;\n right: 0;\n top: 0;\n width: 100%;\n height: 0;\n animation: ezuikit-broadcast-expand-height 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;\n }\n\n .ezuikit-broadcast-panel[data-direction="left"] {\n left: 0;\n top: 0;\n bottom: 0;\n width: 100%;\n animation: ezuikit-broadcast-expand-width 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;\n }\n\n .ezuikit-broadcast-panel[data-direction="right"] {\n right: 0;\n top: 0;\n bottom: 0;\n width: 100%;\n animation: ezuikit-broadcast-expand-width 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;\n }\n\n /* 标题栏 */\n .ezuikit-broadcast-header {\n position: relative;\n padding: 16px 20px;\n text-align: center;\n background: rgba(245, 247, 250, 1);\n }\n\n .ezuikit-broadcast-title {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n color: #333333;\n }\n\n .ezuikit-broadcast-close {\n position: absolute;\n left: 16px;\n top: 50%;\n transform: translateY(-50%);\n background: none;\n border: none;\n padding: 4px;\n cursor: pointer;\n color: #666666;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: color 0.2s;\n }\n\n .ezuikit-broadcast-close:hover {\n color: #333333;\n }\n\n /* Tab区域 */\n .ezuikit-broadcast-tabs {\n display: flex;\n padding: 0 20px;\n background: rgba(245, 247, 250, 1);\n }\n\n .ezuikit-broadcast-tab {\n position: relative;\n padding: 12px 16px 4px;\n font-size: 20px;\n color: #999999;\n cursor: pointer;\n transition: color 0.2s;\n font-weight: 700;\n }\n\n .ezuikit-broadcast-tab.active {\n color: rgba(100, 143, 252, 1);\n font-weight: 700;\n }\n\n .ezuikit-broadcast-tab.active::after {\n content: \'\';\n position: absolute;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n width: 10px;\n height: 2px;\n background: #6B9EFF;\n }\n\n /* 内容区域 */\n .ezuikit-broadcast-content {\n flex: 1;\n overflow-y: auto;\n padding: 16px 20px;\n background: rgba(245, 247, 250, 1);\n }\n\n /* 语音列表 */\n .ezuikit-broadcast-voice-list {\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .ezuikit-broadcast-voice-empty {\n text-align: center;\n padding: 40px 20px;\n color: #999999;\n font-size: 14px;\n }\n\n .ezuikit-broadcast-voice-item {\n display: flex;\n align-items: center;\n padding: 11px 16px;\n background: rgba(237, 237, 237, 1);\n border-radius: 16px;\n height: 42px;\n box-sizing: border-box;\n cursor: pointer;\n transition: all 0.2s;\n }\n\n .ezuikit-broadcast-voice-item:hover {\n background: rgba(237, 237, 237, 1);\n }\n\n .ezuikit-broadcast-voice-item.selected {\n background: rgba(237, 237, 237, 1);\n }\n\n .ezuikit-broadcast-voice-icon {\n width: 24px;\n height: 24px;\n margin-right: 12px;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #FFFFFF;\n background: #2C2C2C;\n border-radius: 50%;\n padding: 3px;\n box-sizing: border-box;\n transition: background 0.2s;\n }\n\n .ezuikit-broadcast-voice-item.selected .ezuikit-broadcast-voice-icon {\n background: rgba(100, 143, 252, 1);\n color: #FFFFFF;\n }\n\n .ezuikit-broadcast-voice-text {\n flex: 1;\n font-size: 14px;\n color: rgba(102, 102, 102, 1);\n line-height: 20px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n /* 底部按钮 */\n .ezuikit-broadcast-footer {\n padding: 16px 20px 20px;\n background: #F5F5F5;\n }\n\n .ezuikit-broadcast-talk-btn {\n width: 100%;\n padding: 16px;\n background: #6B9EFF;\n color: #FFFFFF;\n border: none;\n font-size: 16px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n user-select: none;\n border-radius: 8px;\n }\n\n .ezuikit-broadcast-talk-btn:hover {\n background: #5A8EEE;\n }\n\n .ezuikit-broadcast-talk-btn:active {\n transform: scale(0.98);\n }\n\n .ezuikit-broadcast-talk-btn.recording {\n background: #FF5252;\n }\n\n .ezuikit-broadcast-talk-btn.disabled {\n background: #CCCCCC;\n cursor: not-allowed;\n }\n\n /* 动画 */\n @keyframes ezuikit-broadcast-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n @keyframes ezuikit-broadcast-expand-height {\n from { height: 0; }\n to { height: 80%; }\n }\n\n @keyframes ezuikit-broadcast-expand-height-mobile {\n from { height: 0; }\n to { height: 70vh; }\n }\n\n /* 全屏时从右侧展开的动画(宽度展开) */\n @keyframes ezuikit-broadcast-expand-width-mobile-fullscreen {\n from { width: 0; }\n to { width: 70vh; }\n }\n\n /* 全屏时从底部展开的动画 */\n @keyframes ezuikit-broadcast-expand-height-mobile-fullscreen {\n from { height: 0; }\n to { height: 85vh; }\n }\n\n @keyframes ezuikit-broadcast-expand-width {\n from { width: 0; }\n to { \n width: 80%;\n max-width: 400px;\n }\n }\n ',document.head.appendChild(style)}},_proto.bindEvents=function(){var _this=this;if(this.modalElement){var closeBtn=this.modalElement.querySelector(".ezuikit-broadcast-close");closeBtn&&closeBtn.addEventListener("click",function(){_this.close()});var overlay=this.modalElement.querySelector(".ezuikit-broadcast-overlay");overlay&&overlay.addEventListener("click",function(){_this.close()}),this.talkButton=this.modalElement.querySelector(".ezuikit-broadcast-talk-btn"),this.talkButton&&(this.options.isMobile?(this.talkButton.addEventListener("touchstart",function(e){e.preventDefault(),e.stopPropagation();var now=Date.now();now-_this.lastTouchTime<300||(_this.lastTouchTime=now,_this.handleRecordStart())},{passive:!1}),this.talkButton.addEventListener("touchend",function(e){e.preventDefault(),e.stopPropagation(),_this.handleRecordStop()},{passive:!1}),this.talkButton.addEventListener("touchcancel",function(){_this.isRecording&&_this.handleRecordStop()})):this.talkButton.addEventListener("click",function(e){e.preventDefault(),_this.handleRecordToggle()}))}},_proto.handleRecordToggle=function(){this.isRecording?this.handleRecordStop():this.handleRecordStart()},_proto.handleRecordStart=function(){return _async_to_generator$1(function(){var textElement,error,textElement1;return _ts_generator$1(this,function(_state){switch(_state.label){case 0:if(this.isRecording||this.isStarting)return[2];_state.label=1;case 1:return _state.trys.push([1,5,,6]),this.pendingStop=!1,this.isStarting=!0,this.recordingToast.show(),this.talkButton&&(this.talkButton.classList.add("recording"),(textElement=this.talkButton.querySelector(".ezuikit-broadcast-talk-text"))&&(textElement.textContent=this.options.isMobile?i18n.t("recording"):i18n.t("stopRecording"))),[4,this.options.onRecordStart()];case 2:return _state.sent(),this.isRecording=!0,this.isStarting=!1,this.pendingStop?(this.pendingStop=!1,[4,this.handleRecordStop()]):[3,4];case 3:_state.sent(),_state.label=4;case 4:return[3,6];case 5:return error=_state.sent(),this.isStarting=!1,this.isRecording=!1,this.pendingStop=!1,this.recordingToast.hide(),this.talkButton&&(this.talkButton.classList.remove("recording"),(textElement1=this.talkButton.querySelector(".ezuikit-broadcast-talk-text"))&&(textElement1.textContent=this.options.isMobile?i18n.t("pressToTalk"):i18n.t("clickToTalk"))),this.showError(_instanceof(error,Error)?error.message:"开始录音失败"),[3,6];case 6:return[2]}})}).call(this)},_proto.handleRecordStop=function(){return _async_to_generator$1(function(){var textElement,error,textElement1;return _ts_generator$1(this,function(_state){switch(_state.label){case 0:if(this.isStarting)return this.pendingStop=!0,[2];if(!this.isRecording)return[2];_state.label=1;case 1:return _state.trys.push([1,3,,4]),this.recordingToast.hide(),this.talkButton&&(this.talkButton.classList.remove("recording"),(textElement=this.talkButton.querySelector(".ezuikit-broadcast-talk-text"))&&(textElement.textContent=this.options.isMobile?i18n.t("pressToTalk"):i18n.t("clickToTalk"))),[4,this.options.onRecordStop()];case 2:return _state.sent(),this.isRecording=!1,[3,4];case 3:return error=_state.sent(),this.isRecording=!1,this.recordingToast.hide(),this.talkButton&&(this.talkButton.classList.remove("recording"),(textElement1=this.talkButton.querySelector(".ezuikit-broadcast-talk-text"))&&(textElement1.textContent=this.options.isMobile?i18n.t("pressToTalk"):i18n.t("clickToTalk"))),this.showError(_instanceof(error,Error)?error.message:"停止录音失败"),[3,4];case 4:return[2]}})}).call(this)},_proto.show=function(){this.modalElement&&(this.modalElement.style.display="block",this.options.isMobile&&this.positionModalBelowContainer())},_proto.hide=function(){if(this.isRecording)try{this.handleRecordStop()}catch(error){this.isRecording=!1,this.recordingToast.hide()}this.modalElement&&(this.modalElement.style.display="none")},_proto.close=function(){this.hide(),this.options.onClose()},_proto.updateVoiceList=function(voiceList){this.voiceList=voiceList,this.renderVoiceList()},_proto.renderVoiceList=function(){var _this=this;this.voiceListElement&&(0!==this.voiceList.length?(this.voiceListElement.innerHTML=this.voiceList.map(function(voice,index){return'\n <div class="ezuikit-broadcast-voice-item" data-index="'+index+'">\n <div class="ezuikit-broadcast-voice-icon">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">\n <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>\n </svg>\n </div>\n <div class="ezuikit-broadcast-voice-text" title="'+voice.voiceName+'">'+voice.voiceName+"</div>\n </div>\n "}).join(""),this.voiceListElement.querySelectorAll(".ezuikit-broadcast-voice-item").forEach(function(item){item.addEventListener("click",function(){var index=parseInt(item.getAttribute("data-index")||"-1");_this.selectVoice(index)})})):this.voiceListElement.innerHTML='\n <div class="ezuikit-broadcast-voice-empty">\n '+i18n.t("noVoice")+"\n </div>\n ")},_proto.selectVoice=function(index){if(this.voiceListElement&&(this.voiceListElement.querySelectorAll(".ezuikit-broadcast-voice-item").forEach(function(item){item.classList.remove("selected")}),index>=0&&index<this.voiceList.length)){var selectedItem=this.voiceListElement.querySelector('[data-index="'+index+'"]');selectedItem&&selectedItem.classList.add("selected");var selectedVoice=this.voiceList[index];this.options.onVoiceSend(selectedVoice.fileUrl)}},_proto.setRecordingState=function(isRecording){this.isRecording=isRecording},_proto.showSuccess=function(message){},_proto.showError=function(message){},_proto.destroy=function(){var _this_modalElement;this.removeOrientationListener(),this.recordingToast.destroy(),(null==(_this_modalElement=this.modalElement)?void 0:_this_modalElement.parentNode)&&this.modalElement.parentNode.removeChild(this.modalElement),this.modalElement=null,this.voiceListElement=null,this.talkButton=null},BroadcastUI}();function asyncGeneratorStep(gen,resolve,reject,_next,_throw,key,arg){try{var info=gen[key](arg),value=info.value}catch(error){return void reject(error)}info.done?resolve(value):Promise.resolve(value).then(_next,_throw)}function _async_to_generator(fn){return function(){var self=this,args=arguments;return new Promise(function(resolve,reject){var gen=fn.apply(self,args);function _next(value){asyncGeneratorStep(gen,resolve,reject,_next,_throw,"next",value)}function _throw(err){asyncGeneratorStep(gen,resolve,reject,_next,_throw,"throw",err)}_next(void 0)})}}function _ts_generator(thisArg,body){var f,y,t,_={label:0,sent:function(){if(1&t[0])throw t[1];return t[1]},trys:[],ops:[]},g=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return g.next=verb(0),g.throw=verb(1),g.return=verb(2),"function"==typeof Symbol&&(g[Symbol.iterator]=function(){return this}),g;function verb(n){return function(v){return function(op){if(f)throw new TypeError("Generator is already executing.");for(;g&&(g=0,op[0]&&(_=0)),_;)try{if(f=1,y&&(t=2&op[0]?y.return:op[0]?y.throw||((t=y.return)&&t.call(y),0):y.next)&&!(t=t.call(y,op[1])).done)return t;switch(y=0,t&&(op=[2&op[0],t.value]),op[0]){case 0:case 1:t=op;break;case 4:return _.label++,{value:op[1],done:!1};case 5:_.label++,y=op[1],op=[0];continue;case 7:op=_.ops.pop(),_.trys.pop();continue;default:if(!(t=_.trys,(t=t.length>0&&t[t.length-1])||6!==op[0]&&2!==op[0])){_=0;continue}if(3===op[0]&&(!t||op[1]>t[0]&&op[1]<t[3])){_.label=op[1];break}if(6===op[0]&&_.label<t[1]){_.label=t[1],t=op;break}if(t&&_.label<t[2]){_.label=t[2],_.ops.push(op);break}t[2]&&_.ops.pop(),_.trys.pop();continue}op=body.call(thisArg,_)}catch(e){op=[6,e],y=0}finally{f=t=0}if(5&op[0])throw op[1];return{value:op[0]?op[1]:void 0,done:!0}}([n,v])}}}var EVENTS={open:"open",close:"close",requestQueryVoiceList:"requestQueryVoiceList",voiceListLoaded:"voiceListLoaded",voiceListError:"voiceListError",requestSendVoiceOnce:"requestSendVoiceOnce",requestSendVoice:"requestSendVoice",voiceSelected:"voiceSelected",voiceRecorded:"voiceRecorded",recordStart:"recordStart",recordStop:"recordStop",recordComplete:"recordComplete",recordError:"recordError",voiceSent:"voiceSent",voiceSendError:"voiceSendError",error:"error"},DefaultOptions={channelNo:"1",env:{domain:"https://open.ys7.com"},language:"zh"},Broadcast=function(){function Broadcast(options){var _this=this;this.options=Object.assign({},DefaultOptions,options),this.eventEmitter=new EventEmitter,this.voiceRecorder=null,this.isOpen=!1,this.voiceList=options.voiceList||[],this.maxDurationTimer=null,this.pendingAudioBlob=null;var container=this.options.container||document.body;this.broadcastUI=new BroadcastUI(container,{language:this.options.language||"zh",direction:this.options.direction||"right",voiceList:this.voiceList,isMobile:this.options.isMobile||!1,player:this.options.player,onClose:function(){return _this.close()},onRecordStart:function(){return _async_to_generator(function(){return _ts_generator(this,function(_state){switch(_state.label){case 0:return[4,this.startRecord()];case 1:return[2,_state.sent()]}})}).call(_this)},onRecordStop:function(){return _async_to_generator(function(){return _ts_generator(this,function(_state){switch(_state.label){case 0:return[4,this.stopRecord()];case 1:return[2,_state.sent()]}})}).call(_this)},onVoiceSend:function(fileUrl){return _this.sendVoice(fileUrl)},onVoiceSendOnce:function(){}}),this.setupEventListeners()}var _proto=Broadcast.prototype;return _proto.initMicrophonePermission=function(){return _async_to_generator(function(){var error;return _ts_generator(this,function(_state){switch(_state.label){case 0:return _state.trys.push([0,2,,3]),[4,navigator.mediaDevices.getUserMedia({audio:{sampleRate:16e3,channelCount:1,echoCancellation:!0,noiseSuppression:!0}})];case 1:return _state.sent().getTracks().forEach(function(track){return track.stop()}),[3,3];case 2:return error=_state.sent(),this.eventEmitter.emit(EVENTS.error,{eventType:EVENTS.error,code:-1,msg:"麦克风权限获取失败",error:error}),[3,3];case 3:return[2]}})}).call(this)},_proto.requestMicrophonePermission=function(){return _async_to_generator(function(){var error;return _ts_generator(this,function(_state){switch(_state.label){case 0:return _state.trys.push([0,2,,3]),[4,navigator.mediaDevices.getUserMedia({audio:{sampleRate:16e3,channelCount:1,echoCancellation:!0,noiseSuppression:!0}})];case 1:return _state.sent().getTracks().forEach(function(track){return track.stop()}),[2,!0];case 2:return error=_state.sent(),this.eventEmitter.emit(EVENTS.error,{eventType:EVENTS.error,code:-1,msg:"麦克风权限获取失败",error:error}),[2,!1];case 3:return[2]}})}).call(this)},_proto.setupEventListeners=function(){var _this=this;this.eventEmitter.on(EVENTS.voiceListLoaded,function(data){_this.voiceList=data.data||[],_this.broadcastUI&&_this.broadcastUI.updateVoiceList(_this.voiceList)}),this.eventEmitter.on(EVENTS.voiceListError,function(data){_this.broadcastUI&&_this.broadcastUI.showError(data.msg||"语音列表加载失败")}),this.eventEmitter.on(EVENTS.voiceSent,function(data){_this.broadcastUI&&_this.broadcastUI.showSuccess(data.msg||"语音下发成功")}),this.eventEmitter.on(EVENTS.voiceSendError,function(data){_this.broadcastUI&&_this.broadcastUI.showError(data.msg||"语音下发失败")})},_proto.open=function(){if(!this.isOpen)try{this.broadcastUI.show(),this.isOpen=!0,0===this.voiceList.length&&this.loadVoiceList(),this.eventEmitter.emit(EVENTS.open,{eventType:EVENTS.open,code:0,msg:"云广播界面已打开"})}catch(error){this.eventEmitter.emit(EVENTS.error,{eventType:EVENTS.error,code:-1,msg:"打开云广播界面失败",error:error})}},_proto.close=function(){if(this.isOpen)try{if(this.voiceRecorder)try{this.voiceRecorder.stop()}catch(error){}this.broadcastUI&&this.broadcastUI.hide(),this.isOpen=!1,this.eventEmitter.emit(EVENTS.close,{eventType:EVENTS.close,code:0,msg:"云广播界面已关闭"})}catch(error){}},_proto.loadVoiceList=function(){this.eventEmitter.emit(EVENTS.requestQueryVoiceList,{eventType:EVENTS.requestQueryVoiceList,params:{deviceSerial:this.options.deviceSerial,channelNo:this.options.channelNo,pageSize:50}})},_proto.startRecord=function(){return _async_to_generator(function(){var _this,error;return _ts_generator(this,function(_state){switch(_state.label){case 0:_this=this,_state.label=1;case 1:return _state.trys.push([1,3,,4]),this.pendingAudioBlob=null,this.voiceRecorder||(this.voiceRecorder=new VoiceRecorder({minDuration:3,maxDuration:59,sampleRate:16e3})),[4,this.voiceRecorder.start()];case 2:return _state.sent(),this.maxDurationTimer=window.setTimeout(function(){return _async_to_generator(function(){return _ts_generator(this,function(_state){switch(_state.label){case 0:return[4,this.stopRecordAndCache()];case 1:return _state.sent(),[2]}})}).call(_this)},59e3),this.eventEmitter.emit(EVENTS.recordStart,{eventType:EVENTS.recordStart,code:0,msg:"开始录音"}),this.broadcastUI&&this.broadcastUI.setRecordingState(!0),[3,4];case 3:return error=_state.sent(),this.eventEmitter.emit(EVENTS.recordError,{eventType:EVENTS.recordError,code:-1,msg:"开始录音失败",error:error}),[3,4];case 4:return[2]}})}).call(this)},_proto.stopRecordAndCache=function(){return _async_to_generator(function(){var audioBlob;return _ts_generator(this,function(_state){switch(_state.label){case 0:return _state.trys.push([0,2,,3]),this.voiceRecorder&&this.voiceRecorder.isRecording()?[4,this.voiceRecorder.stop()]:[2];case 1:return 0===(audioBlob=_state.sent()).size?[2]:(this.pendingAudioBlob=audioBlob,this.eventEmitter.emit(EVENTS.recordComplete,{eventType:EVENTS.recordComplete,code:0,data:audioBlob,msg:"录音完成,等待下发"}),[3,3]);case 2:return _state.sent(),this.broadcastUI&&this.broadcastUI.setRecordingState(!1),[3,3];case 3:return[2]}})}).call(this)},_proto.stopRecord=function(){return _async_to_generator(function(){var audioBlob,audioBlob1,error;return _ts_generator(this,function(_state){switch(_state.label){case 0:if(_state.trys.push([0,2,,3]),this.maxDurationTimer&&(clearTimeout(this.maxDurationTimer),this.maxDurationTimer=null),this.pendingAudioBlob)return audioBlob=this.pendingAudioBlob,this.pendingAudioBlob=null,this.eventEmitter.emit(EVENTS.recordStop,{eventType:EVENTS.recordStop,code:0,msg:"停止录音"}),this.broadcastUI&&this.broadcastUI.setRecordingState(!1),this.sendVoiceOnce(audioBlob),[2];if(!this.voiceRecorder)throw new Error("录音器未初始化");return this.voiceRecorder.isRecording()?[4,this.voiceRecorder.stop()]:(this.broadcastUI&&this.broadcastUI.setRecordingState(!1),[2]);case 1:return 0===(audioBlob1=_state.sent()).size?(this.broadcastUI&&this.broadcastUI.setRecordingState(!1),[2]):(this.eventEmitter.emit(EVENTS.recordStop,{eventType:EVENTS.recordStop,code:0,msg:"停止录音"}),this.eventEmitter.emit(EVENTS.recordComplete,{eventType:EVENTS.recordComplete,code:0,data:audioBlob1,msg:"录音完成"}),this.broadcastUI&&this.broadcastUI.setRecordingState(!1),this.sendVoiceOnce(audioBlob1),[3,3]);case 2:return error=_state.sent(),this.eventEmitter.emit(EVENTS.recordError,{eventType:EVENTS.recordError,code:-1,msg:"停止录音失败",error:error}),this.broadcastUI&&this.broadcastUI.setRecordingState(!1),[3,3];case 3:return[2]}})}).call(this)},_proto.sendVoiceOnce=function(voiceFile){this.eventEmitter.emit(EVENTS.voiceRecorded,{eventType:EVENTS.voiceRecorded,voiceFile:voiceFile,deviceSerial:this.options.deviceSerial,channelNo:this.options.channelNo}),this.eventEmitter.emit(EVENTS.requestSendVoiceOnce,{eventType:EVENTS.requestSendVoiceOnce,voiceFile:voiceFile,deviceSerial:this.options.deviceSerial,channelNo:this.options.channelNo})},_proto.sendVoice=function(fileUrl){this.eventEmitter.emit(EVENTS.requestSendVoice,{eventType:EVENTS.requestSendVoice,fileUrl:fileUrl,deviceSerial:this.options.deviceSerial,channelNo:this.options.channelNo})},_proto.updateOptions=function(options){this.options=Object.assign(this.options,options)},_proto.on=function(event,callback){this.eventEmitter.on(event,callback)},_proto.off=function(event,callback){this.eventEmitter.off(event,callback)},_proto.destroy=function(){this.close(),this.voiceRecorder&&(this.voiceRecorder.destroy(),this.voiceRecorder=null),this.broadcastUI&&this.broadcastUI.destroy(),this.voiceList=[],this.eventEmitter.removeAllListeners()},Broadcast}();return Broadcast.VERSION="0.0.1",Broadcast.EVENTS=EVENTS,Broadcast});
@@ -0,0 +1,463 @@
1
+ /**
2
+ * @description 事件发射器
3
+ * @author Ezviz-OpenBiz
4
+ * @since 2026-01-13
5
+ */
6
+ type EventCallback = (...args: any[]) => void;
7
+ declare class EventEmitter {
8
+ private events;
9
+ constructor();
10
+ /**
11
+ * 监听事件
12
+ * @param event 事件名称
13
+ * @param callback 回调函数
14
+ */
15
+ on(event: string, callback: EventCallback): void;
16
+ /**
17
+ * 移除事件监听
18
+ * @param event 事件名称
19
+ * @param callback 回调函数
20
+ */
21
+ off(event: string, callback: EventCallback): void;
22
+ /**
23
+ * 触发事件
24
+ * @param event 事件名称
25
+ * @param args 参数
26
+ */
27
+ emit(event: string, ...args: any[]): void;
28
+ /**
29
+ * 监听一次事件
30
+ * @param event 事件名称
31
+ * @param callback 回调函数
32
+ */
33
+ once(event: string, callback: EventCallback): void;
34
+ /**
35
+ * 移除所有事件监听
36
+ */
37
+ removeAllListeners(): void;
38
+ /**
39
+ * 获取事件监听器数量
40
+ * @param event 事件名称
41
+ */
42
+ listenerCount(event: string): number;
43
+ }
44
+
45
+ /**
46
+ * @description 类型定义
47
+ * @author Ezviz-OpenBiz
48
+ * @since 2026-01-13
49
+ */
50
+ /**
51
+ * 语音列表项
52
+ */
53
+ interface VoiceItem {
54
+ /** 语音名称 */
55
+ voiceName: string;
56
+ /** 语音文件下载地址,有效期1天 */
57
+ fileUrl: string;
58
+ /** 语音文件时长,单位秒 */
59
+ duration: number;
60
+ }
61
+ /**
62
+ * 录音器配置
63
+ */
64
+ interface RecorderOptions {
65
+ /** 最短录音时长(秒),默认3 */
66
+ minDuration?: number;
67
+ /** 最长录音时长(秒),默认60 */
68
+ maxDuration?: number;
69
+ /** 采样率,默认16000 */
70
+ sampleRate?: number;
71
+ /** 声道数,默认1 */
72
+ channelCount?: number;
73
+ }
74
+
75
+ /**
76
+ * @description 语音录制器(WAV格式)
77
+ * @author Ezviz-OpenBiz
78
+ * @since 2026-01-13
79
+ */
80
+
81
+ /**
82
+ * 语音录制器类
83
+ * 使用 Web Audio API 录制原始音频数据,然后转换为 WAV 格式
84
+ * iOS Safari 使用 MediaRecorder API 以提高兼容性
85
+ */
86
+ declare class VoiceRecorder {
87
+ private options;
88
+ private audioContext;
89
+ private mediaStreamSource;
90
+ private scriptProcessor;
91
+ private stream;
92
+ private startTime;
93
+ private maxTimer;
94
+ private audioBuffers;
95
+ private isRecordingFlag;
96
+ private isIOSDevice;
97
+ private mediaRecorder;
98
+ private recordedChunks;
99
+ constructor(options?: RecorderOptions);
100
+ /**
101
+ * 开始录音
102
+ */
103
+ start(): Promise<void>;
104
+ /**
105
+ * 使用 MediaRecorder API 开始录音(iOS Safari)
106
+ */
107
+ private startWithMediaRecorder;
108
+ /**
109
+ * 使用 ScriptProcessor 开始录音(所有平台)
110
+ */
111
+ private startWithScriptProcessor;
112
+ /**
113
+ * 停止录音
114
+ */
115
+ stop(): Promise<Blob>;
116
+ /**
117
+ * 使用 MediaRecorder 停止录音(iOS Safari)
118
+ */
119
+ private stopWithMediaRecorder;
120
+ /**
121
+ * 使用 ScriptProcessor 停止录音(非 iOS)
122
+ */
123
+ private stopWithScriptProcessor;
124
+ /**
125
+ * 将 Float32Array 编码为 WAV 格式
126
+ */
127
+ private encodeWAV;
128
+ /**
129
+ * 清理资源
130
+ */
131
+ private cleanup;
132
+ /**
133
+ * 销毁录音器
134
+ */
135
+ destroy(): void;
136
+ /**
137
+ * 获取录音时长
138
+ */
139
+ getDuration(): number;
140
+ /**
141
+ * 是否正在录音
142
+ */
143
+ isRecording(): boolean;
144
+ }
145
+
146
+ /**
147
+ * @description 云广播UI控制器
148
+ * @author Ezviz-OpenBiz
149
+ * @since 2026-01-13
150
+ */
151
+
152
+ /**
153
+ * UI配置选项
154
+ */
155
+ interface BroadcastUIOptions {
156
+ /** 语言 */
157
+ language: 'zh' | 'en';
158
+ /** 展开方向:top-从上往下, bottom-从下往上, left-从左往右, right-从右往左 */
159
+ direction?: 'top' | 'bottom' | 'left' | 'right';
160
+ /** 初始语音列表 */
161
+ voiceList?: VoiceItem[];
162
+ /** 是否移动端 */
163
+ isMobile?: boolean;
164
+ /** EZUIKitPlayer 实例引用(用于获取全屏状态) */
165
+ player?: any;
166
+ /** 关闭回调 */
167
+ onClose: () => void;
168
+ /** 开始录音回调 */
169
+ onRecordStart: () => void;
170
+ /** 停止录音回调 */
171
+ onRecordStop: () => void;
172
+ /** 下发语音回调 */
173
+ onVoiceSend: (fileUrl: string) => void;
174
+ /** 下发临时语音回调 */
175
+ onVoiceSendOnce: (blob: Blob) => void;
176
+ }
177
+ /**
178
+ * 云广播UI控制器类
179
+ */
180
+ declare class BroadcastUI {
181
+ private options;
182
+ private container;
183
+ private modalElement;
184
+ private voiceListElement;
185
+ private talkButton;
186
+ private isRecording;
187
+ private voiceList;
188
+ private recordingToast;
189
+ private isStarting;
190
+ private lastTouchTime;
191
+ private pendingStop;
192
+ private orientationChangeHandler;
193
+ constructor(container: HTMLElement | string, options: BroadcastUIOptions);
194
+ /**
195
+ * 设置屏幕方向变化监听器(移动端)
196
+ */
197
+ private setupOrientationListener;
198
+ /**
199
+ * 移除屏幕方向变化监听器
200
+ */
201
+ private removeOrientationListener;
202
+ /**
203
+ * 创建UI
204
+ */
205
+ private createUI;
206
+ /**
207
+ * 将模态框定位到 container 下方(移动端)
208
+ */
209
+ private positionModalBelowContainer;
210
+ /**
211
+ * 获取模态框HTML
212
+ */
213
+ private getModalHTML;
214
+ /**
215
+ * 注入样式
216
+ */
217
+ private injectStyles;
218
+ /**
219
+ * 绑定事件
220
+ */
221
+ private bindEvents;
222
+ /**
223
+ * 处理录音状态切换(PC端)
224
+ */
225
+ private handleRecordToggle;
226
+ /**
227
+ * 处理开始录音
228
+ */
229
+ private handleRecordStart;
230
+ /**
231
+ * 处理停止录音
232
+ */
233
+ private handleRecordStop;
234
+ /**
235
+ * 显示UI
236
+ */
237
+ show(): void;
238
+ /**
239
+ * 隐藏UI
240
+ */
241
+ hide(): void;
242
+ /**
243
+ * 关闭弹窗(供上层调用)
244
+ * 效果与内部关闭按钮、遮罩点击一致
245
+ */
246
+ close(): void;
247
+ /**
248
+ * 更新语音列表
249
+ */
250
+ updateVoiceList(voiceList: VoiceItem[]): void;
251
+ /**
252
+ * 渲染语音列表
253
+ */
254
+ private renderVoiceList;
255
+ /**
256
+ * 选中语音
257
+ */
258
+ private selectVoice;
259
+ /**
260
+ * 设置录音状态
261
+ */
262
+ setRecordingState(isRecording: boolean): void;
263
+ /**
264
+ * 显示成功消息
265
+ */
266
+ showSuccess(message: string): void;
267
+ /**
268
+ * 显示错误消息
269
+ */
270
+ showError(message: string): void;
271
+ /**
272
+ * 销毁UI
273
+ */
274
+ destroy(): void;
275
+ }
276
+
277
+ /**
278
+ * @description 云广播喊话控制模块
279
+ * @author Ezviz-OpenBiz
280
+ * @since 2026-01-13
281
+ */
282
+
283
+ /**
284
+ * 云广播配置选项
285
+ */
286
+ interface BroadcastOptions {
287
+ /** 设备序列号 */
288
+ deviceSerial: string;
289
+ /** 通道号,默认为1 */
290
+ channelNo?: string | number;
291
+ /** 访问令牌,如果和 token 一起,优先使用小权限token */
292
+ accessToken?: string;
293
+ /**
294
+ * 优先使用小权限token
295
+ * 如果使用小权限token, 最少需要传token.deviceToken.global、token.deviceToken.video
296
+ */
297
+ token?: {
298
+ deviceToken: {
299
+ global: string;
300
+ video: string;
301
+ };
302
+ };
303
+ /** 环境配置 */
304
+ env?: {
305
+ domain: string;
306
+ };
307
+ /** 设备能力集 */
308
+ capacity?: {
309
+ /** 对讲能力:1或3表示支持云广播 */
310
+ support_talk?: string | number | null;
311
+ };
312
+ /** 语言,默认zh */
313
+ language?: 'zh' | 'en';
314
+ /** 容器,支持传入字符串ID或HTMLElement节点 */
315
+ container?: string | HTMLElement;
316
+ /** 展开方向 */
317
+ direction?: 'top' | 'bottom' | 'left' | 'right';
318
+ /** 初始语音列表 */
319
+ voiceList?: VoiceItem[];
320
+ /** 是否移动端 */
321
+ isMobile?: boolean;
322
+ /** EZUIKitPlayer 实例引用(用于获取全屏状态) */
323
+ player?: any;
324
+ }
325
+ /**
326
+ * 云广播喊话控制类
327
+ */
328
+ declare class Broadcast {
329
+ /** 版本号 */
330
+ static VERSION: string;
331
+ /** 事件常量 */
332
+ static EVENTS: {
333
+ /** 打开云广播界面 */
334
+ open: string;
335
+ /** 关闭云广播界面 */
336
+ close: string;
337
+ /** 请求查询语音列表 */
338
+ requestQueryVoiceList: string;
339
+ /** 语音列表加载完成 */
340
+ voiceListLoaded: string;
341
+ /** 语音列表加载失败 */
342
+ voiceListError: string;
343
+ /** 请求下发临时语音 */
344
+ requestSendVoiceOnce: string;
345
+ /** 请求下发默认语音 */
346
+ requestSendVoice: string;
347
+ /** 选中语音列表项 */
348
+ voiceSelected: string;
349
+ /** 录制语音完成 */
350
+ voiceRecorded: string;
351
+ /** 开始录音 */
352
+ recordStart: string;
353
+ /** 停止录音 */
354
+ recordStop: string;
355
+ /** 录音完成 */
356
+ recordComplete: string;
357
+ /** 录音错误 */
358
+ recordError: string;
359
+ /** 语音下发成功 */
360
+ voiceSent: string;
361
+ /** 语音下发失败 */
362
+ voiceSendError: string;
363
+ /** 错误事件 */
364
+ error: string;
365
+ };
366
+ /** 配置选项 */
367
+ options: BroadcastOptions;
368
+ /** 事件发射器 */
369
+ eventEmitter: EventEmitter;
370
+ /** 语音录制器 */
371
+ voiceRecorder: VoiceRecorder | null;
372
+ /** UI控制器 */
373
+ broadcastUI: BroadcastUI;
374
+ /** 是否已打开 */
375
+ isOpen: boolean;
376
+ /** 语音列表 */
377
+ voiceList: VoiceItem[];
378
+ /** 最大时长定时器 */
379
+ private maxDurationTimer;
380
+ /** 暂存的录音数据(达到最大时长时) */
381
+ private pendingAudioBlob;
382
+ /**
383
+ * 构造函数
384
+ * @param options 配置选项
385
+ */
386
+ constructor(options: BroadcastOptions);
387
+ /**
388
+ * 初始化麦克风权限
389
+ * 提前请求麦克风权限,避免首次录音时延迟
390
+ */
391
+ private initMicrophonePermission;
392
+ /**
393
+ * 请求麦克风权限(公开方法)
394
+ * 可用于用户拒绝权限后重新请求
395
+ */
396
+ requestMicrophonePermission(): Promise<boolean>;
397
+ /**
398
+ * 设置事件监听
399
+ * 监听上层返回的结果事件
400
+ */
401
+ private setupEventListeners;
402
+ /**
403
+ * 打开云广播界面
404
+ */
405
+ open(): void;
406
+ /**
407
+ * 关闭云广播界面
408
+ */
409
+ close(): void;
410
+ /**
411
+ * 加载语音列表
412
+ * 触发请求事件,由上层调用实际接口
413
+ */
414
+ loadVoiceList(): void;
415
+ /**
416
+ * 开始录音
417
+ */
418
+ startRecord(): Promise<void>;
419
+ /**
420
+ * 停止录音并暂存(达到最大时长时调用)
421
+ */
422
+ private stopRecordAndCache;
423
+ /**
424
+ * 停止录音
425
+ */
426
+ stopRecord(): Promise<void>;
427
+ /**
428
+ * 下发临时语音
429
+ * 触发请求事件,由上层调用实际接口
430
+ * @param voiceFile 语音文件
431
+ */
432
+ sendVoiceOnce(voiceFile: Blob): void;
433
+ /**
434
+ * 下发默认语音
435
+ * 触发请求事件,由上层调用实际接口
436
+ * @param fileUrl 语音文件URL
437
+ */
438
+ sendVoice(fileUrl: string): void;
439
+ /**
440
+ * 更新配置
441
+ * @param options 新的配置选项
442
+ */
443
+ updateOptions(options: Partial<BroadcastOptions>): void;
444
+ /**
445
+ * 监听事件
446
+ * @param event 事件名称
447
+ * @param callback 回调函数
448
+ */
449
+ on(event: string, callback: (data: any) => void): void;
450
+ /**
451
+ * 移除事件监听
452
+ * @param event 事件名称
453
+ * @param callback 回调函数
454
+ */
455
+ off(event: string, callback: (data: any) => void): void;
456
+ /**
457
+ * 销毁实例
458
+ */
459
+ destroy(): void;
460
+ }
461
+
462
+ export { Broadcast as default };
463
+ export type { BroadcastOptions };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@ezuikit/control-broadcast",
3
+ "version": "0.0.1",
4
+ "description": "broadcast voice control",
5
+ "title": "broadcast",
6
+ "main": "dist/index.js",
7
+ "types": "dist/types/index.d.ts",
8
+ "keywords": [
9
+ "ezuikit",
10
+ "broadcast",
11
+ "voice"
12
+ ],
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "author": "Ezviz-OpenBiz",
17
+ "license": "",
18
+ "dependencies": {
19
+ "@ezuikit/utils-i18n": "^2.0.0"
20
+ },
21
+ "publishConfig": {
22
+ "registry": "https://registry.npmjs.org/",
23
+ "access": "public"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/Ezviz-OpenBiz/EZUIKit-player-controls.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/Ezviz-OpenBiz/EZUIKit-player-controls/issues"
31
+ },
32
+ "homepage": "https://github.com/Ezviz-OpenBiz/EZUIKit-player-controls#readme"
33
+ }