wrplayer 1.0.0
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 +413 -0
- package/adapters/react/index.js +272 -0
- package/adapters/vanilla/index.js +392 -0
- package/adapters/vue/index.js +378 -0
- package/core/PTZController.js +462 -0
- package/core/VoiceIntercomController.js +492 -0
- package/core/WRPlayer.js +924 -0
- package/core/ZLMRTCClient-v1.0.1.js +8227 -0
- package/core/ZLMRTCClient-v1.1.2.js +9474 -0
- package/dist/wrplayer.esm.js +11343 -0
- package/dist/wrplayer.esm.js.map +1 -0
- package/dist/wrplayer.umd.js +11351 -0
- package/dist/wrplayer.umd.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voice Intercom Controller for WRPlayer SDK
|
|
3
|
+
* 语音对讲控制器 - 支持GB28181协议的音视频设备语音对讲功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class VoiceIntercomController {
|
|
7
|
+
constructor(config = {}) {
|
|
8
|
+
this.config = {
|
|
9
|
+
apiUrl: config.apiUrl || 'http://localhost:18080',
|
|
10
|
+
apiKey: config.apiKey || '',
|
|
11
|
+
debug: config.debug || false,
|
|
12
|
+
timeout: config.timeout || 30,
|
|
13
|
+
...config
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// 语音对讲状态
|
|
17
|
+
this.status = {
|
|
18
|
+
IDLE: -1, // 空闲状态
|
|
19
|
+
CONNECTING: 0, // 连接中
|
|
20
|
+
CONNECTED: 1, // 已连接
|
|
21
|
+
RELEASING: -2 // 释放资源中
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// 当前状态
|
|
25
|
+
this.currentStatus = this.status.IDLE;
|
|
26
|
+
|
|
27
|
+
// 对讲模式
|
|
28
|
+
this.modes = {
|
|
29
|
+
BROADCAST: true, // 喊话模式(单向)
|
|
30
|
+
TALK: false // 对讲模式(双向)
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// 当前模式
|
|
34
|
+
this.currentMode = this.modes.TALK;
|
|
35
|
+
|
|
36
|
+
// WebRTC客户端
|
|
37
|
+
this.rtcClient = null;
|
|
38
|
+
|
|
39
|
+
// 当前设备信息
|
|
40
|
+
this.currentDevice = null;
|
|
41
|
+
this.currentChannel = null;
|
|
42
|
+
|
|
43
|
+
// 事件监听器
|
|
44
|
+
this.eventListeners = {};
|
|
45
|
+
|
|
46
|
+
// 绑定方法上下文
|
|
47
|
+
this._bindMethods();
|
|
48
|
+
|
|
49
|
+
if (this.config.debug) {
|
|
50
|
+
console.log('VoiceIntercomController initialized:', this.config);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 绑定方法上下文
|
|
56
|
+
*/
|
|
57
|
+
_bindMethods() {
|
|
58
|
+
this.startIntercom = this.startIntercom.bind(this);
|
|
59
|
+
this.stopIntercom = this.stopIntercom.bind(this);
|
|
60
|
+
this.setMode = this.setMode.bind(this);
|
|
61
|
+
this.getStatus = this.getStatus.bind(this);
|
|
62
|
+
this._handleRTCEvents = this._handleRTCEvents.bind(this);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 开始语音对讲
|
|
67
|
+
* @param {string} deviceId - 设备ID
|
|
68
|
+
* @param {string} channelId - 通道ID
|
|
69
|
+
* @param {boolean} mode - 对讲模式 (true: 喊话, false: 对讲)
|
|
70
|
+
* @returns {Promise<void>}
|
|
71
|
+
*/
|
|
72
|
+
async startIntercom(deviceId, channelId, mode = this.currentMode) {
|
|
73
|
+
if (this.currentStatus !== this.status.IDLE) {
|
|
74
|
+
throw new Error('Voice intercom is already active or connecting');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
this.currentDevice = deviceId;
|
|
79
|
+
this.currentChannel = channelId;
|
|
80
|
+
this.currentMode = mode;
|
|
81
|
+
this.currentStatus = this.status.CONNECTING;
|
|
82
|
+
|
|
83
|
+
this._emit('statusChange', {
|
|
84
|
+
status: this.currentStatus,
|
|
85
|
+
statusText: '正在连接...',
|
|
86
|
+
mode: this.currentMode
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// 调用开始对讲API
|
|
90
|
+
const response = await this._callAPI('GET', `play/broadcast/${deviceId}/${channelId}`, {
|
|
91
|
+
timeout: this.config.timeout,
|
|
92
|
+
broadcastMode: mode
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (response.code === 0) {
|
|
96
|
+
// 获取WebRTC流URL
|
|
97
|
+
const streamData = response.data;
|
|
98
|
+
await this._setupWebRTC(streamData);
|
|
99
|
+
} else {
|
|
100
|
+
throw new Error(response.msg || 'Failed to start voice intercom');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
} catch (error) {
|
|
104
|
+
this.currentStatus = this.status.IDLE;
|
|
105
|
+
this._emit('error', {
|
|
106
|
+
type: 'start_failed',
|
|
107
|
+
message: error.message,
|
|
108
|
+
error
|
|
109
|
+
});
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 停止语音对讲
|
|
116
|
+
* @returns {Promise<void>}
|
|
117
|
+
*/
|
|
118
|
+
async stopIntercom() {
|
|
119
|
+
if (this.currentStatus === this.status.IDLE) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
this.currentStatus = this.status.RELEASING;
|
|
125
|
+
this._emit('statusChange', {
|
|
126
|
+
status: this.currentStatus,
|
|
127
|
+
statusText: '正在释放资源...',
|
|
128
|
+
mode: this.currentMode
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// 关闭WebRTC连接
|
|
132
|
+
if (this.rtcClient) {
|
|
133
|
+
this.rtcClient.close();
|
|
134
|
+
this.rtcClient = null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 调用停止对讲API
|
|
138
|
+
if (this.currentDevice && this.currentChannel) {
|
|
139
|
+
await this._callAPI('GET', `play/broadcast/stop/${this.currentDevice}/${this.currentChannel}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.currentStatus = this.status.IDLE;
|
|
143
|
+
this.currentDevice = null;
|
|
144
|
+
this.currentChannel = null;
|
|
145
|
+
|
|
146
|
+
this._emit('statusChange', {
|
|
147
|
+
status: this.currentStatus,
|
|
148
|
+
statusText: '对讲已停止',
|
|
149
|
+
mode: this.currentMode
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
} catch (error) {
|
|
153
|
+
this.currentStatus = this.status.IDLE;
|
|
154
|
+
this._emit('error', {
|
|
155
|
+
type: 'stop_failed',
|
|
156
|
+
message: error.message,
|
|
157
|
+
error
|
|
158
|
+
});
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 设置对讲模式
|
|
165
|
+
* @param {boolean} mode - 对讲模式 (true: 喊话, false: 对讲)
|
|
166
|
+
*/
|
|
167
|
+
setMode(mode) {
|
|
168
|
+
if (this.currentStatus !== this.status.IDLE) {
|
|
169
|
+
throw new Error('Cannot change mode while intercom is active');
|
|
170
|
+
}
|
|
171
|
+
this.currentMode = mode;
|
|
172
|
+
this._emit('modeChange', { mode });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 获取当前状态
|
|
177
|
+
* @returns {Object}
|
|
178
|
+
*/
|
|
179
|
+
getStatus() {
|
|
180
|
+
return {
|
|
181
|
+
status: this.currentStatus,
|
|
182
|
+
mode: this.currentMode,
|
|
183
|
+
deviceId: this.currentDevice,
|
|
184
|
+
channelId: this.currentChannel,
|
|
185
|
+
isActive: this.currentStatus !== this.status.IDLE
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 获取状态文本
|
|
191
|
+
* @returns {string}
|
|
192
|
+
*/
|
|
193
|
+
getStatusText() {
|
|
194
|
+
switch (this.currentStatus) {
|
|
195
|
+
case this.status.IDLE:
|
|
196
|
+
return '点击开始对讲';
|
|
197
|
+
case this.status.CONNECTING:
|
|
198
|
+
return '等待接通中...';
|
|
199
|
+
case this.status.CONNECTED:
|
|
200
|
+
return '请说话';
|
|
201
|
+
case this.status.RELEASING:
|
|
202
|
+
return '正在释放资源';
|
|
203
|
+
default:
|
|
204
|
+
return '未知状态';
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 获取模式文本
|
|
210
|
+
* @returns {string}
|
|
211
|
+
*/
|
|
212
|
+
getModeText() {
|
|
213
|
+
return this.currentMode ? '喊话模式' : '对讲模式';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 设置WebRTC连接
|
|
218
|
+
* @param {Object} streamData - 流数据
|
|
219
|
+
*/
|
|
220
|
+
async _setupWebRTC(streamData) {
|
|
221
|
+
if (typeof ZLMRTCClient === 'undefined') {
|
|
222
|
+
throw new Error('ZLMRTCClient library is not loaded');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
// 构建WebRTC连接URL
|
|
227
|
+
let rtcUrl = streamData.rtmp || streamData.flv || streamData.ws_flv;
|
|
228
|
+
if (!rtcUrl) {
|
|
229
|
+
throw new Error('No valid stream URL found');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 添加认证签名
|
|
233
|
+
if (streamData.pushKey) {
|
|
234
|
+
const sign = await this._generateMD5Hash(streamData.pushKey);
|
|
235
|
+
rtcUrl += '&sign=' + sign;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 创建WebRTC客户端
|
|
239
|
+
this.rtcClient = new ZLMRTCClient.Endpoint({
|
|
240
|
+
debug: this.config.debug,
|
|
241
|
+
zlmsdpUrl: rtcUrl,
|
|
242
|
+
simulecast: false,
|
|
243
|
+
useCamera: false,
|
|
244
|
+
audioEnable: true,
|
|
245
|
+
videoEnable: false,
|
|
246
|
+
recvOnly: false
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// 设置事件监听
|
|
250
|
+
this._handleRTCEvents();
|
|
251
|
+
|
|
252
|
+
// 开始连接
|
|
253
|
+
await this.rtcClient.start();
|
|
254
|
+
|
|
255
|
+
} catch (error) {
|
|
256
|
+
this.currentStatus = this.status.IDLE;
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 处理WebRTC事件
|
|
263
|
+
*/
|
|
264
|
+
_handleRTCEvents() {
|
|
265
|
+
if (!this.rtcClient) return;
|
|
266
|
+
|
|
267
|
+
// WebRTC不支持
|
|
268
|
+
this.rtcClient.on('WEBRTC_NOT_SUPPORT', (e) => {
|
|
269
|
+
this._emit('error', {
|
|
270
|
+
type: 'webrtc_not_support',
|
|
271
|
+
message: '浏览器不支持WebRTC,无法进行语音对讲',
|
|
272
|
+
error: e
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// ICE协商错误
|
|
277
|
+
this.rtcClient.on('WEBRTC_ICE_CANDIDATE_ERROR', (e) => {
|
|
278
|
+
this._emit('error', {
|
|
279
|
+
type: 'ice_error',
|
|
280
|
+
message: 'ICE协商出错',
|
|
281
|
+
error: e
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// 连接状态变化
|
|
286
|
+
this.rtcClient.on('WEBRTC_ON_CONNECTION_STATE_CHANGE', (state) => {
|
|
287
|
+
if (state === 'connecting') {
|
|
288
|
+
this.currentStatus = this.status.CONNECTING;
|
|
289
|
+
} else if (state === 'connected') {
|
|
290
|
+
this.currentStatus = this.status.CONNECTED;
|
|
291
|
+
} else if (state === 'disconnected') {
|
|
292
|
+
this.currentStatus = this.status.IDLE;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
this._emit('statusChange', {
|
|
296
|
+
status: this.currentStatus,
|
|
297
|
+
statusText: this.getStatusText(),
|
|
298
|
+
mode: this.currentMode,
|
|
299
|
+
connectionState: state
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// 媒体流事件
|
|
304
|
+
this.rtcClient.on('WEBRTC_ON_LOCAL_STREAM', (stream) => {
|
|
305
|
+
this._emit('localStream', { stream });
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
this.rtcClient.on('WEBRTC_ON_REMOTE_STREAM', (stream) => {
|
|
309
|
+
this._emit('remoteStream', { stream });
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* 调用API
|
|
315
|
+
* @param {string} method - HTTP方法
|
|
316
|
+
* @param {string} endpoint - API端点
|
|
317
|
+
* @param {Object} params - 请求参数
|
|
318
|
+
* @returns {Promise<Object>}
|
|
319
|
+
*/
|
|
320
|
+
async _callAPI(method, endpoint, params = {}) {
|
|
321
|
+
// 构建完整URL
|
|
322
|
+
let fullUrl;
|
|
323
|
+
try {
|
|
324
|
+
// 如果apiUrl是相对路径(代理模式),直接拼接
|
|
325
|
+
if (this.config.apiUrl.startsWith('/')) {
|
|
326
|
+
// 移除endpoint开头的斜杠避免双斜杠
|
|
327
|
+
const cleanEndpoint = endpoint.startsWith('/') ? endpoint.slice(1) : endpoint;
|
|
328
|
+
fullUrl = `${this.config.apiUrl}/${cleanEndpoint}`;
|
|
329
|
+
} else {
|
|
330
|
+
// 绝对URL,使用URL构造函数
|
|
331
|
+
fullUrl = new URL(endpoint, this.config.apiUrl).toString();
|
|
332
|
+
}
|
|
333
|
+
} catch (error) {
|
|
334
|
+
// 回退方案:直接拼接
|
|
335
|
+
const baseUrl = this.config.apiUrl.endsWith('/') ? this.config.apiUrl.slice(0, -1) : this.config.apiUrl;
|
|
336
|
+
const cleanEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
|
337
|
+
fullUrl = `${baseUrl}${cleanEndpoint}`;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 构建URL对象以添加查询参数
|
|
341
|
+
const url = new URL(fullUrl, window.location.origin);
|
|
342
|
+
|
|
343
|
+
// 添加查询参数
|
|
344
|
+
if (method === 'GET' && params) {
|
|
345
|
+
Object.keys(params).forEach(key => {
|
|
346
|
+
if (params[key] !== undefined && params[key] !== null) {
|
|
347
|
+
url.searchParams.append(key, params[key]);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const options = {
|
|
353
|
+
method,
|
|
354
|
+
headers: {
|
|
355
|
+
'Content-Type': 'application/json'
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// 添加API Key认证
|
|
360
|
+
if (this.config.apiKey) {
|
|
361
|
+
options.headers['api-key'] = this.config.apiKey;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 添加请求体
|
|
365
|
+
if (method !== 'GET' && params) {
|
|
366
|
+
options.body = JSON.stringify(params);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const response = await fetch(url.toString(), options);
|
|
370
|
+
|
|
371
|
+
if (!response.ok) {
|
|
372
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return await response.json();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* 触发事件
|
|
380
|
+
* @param {string} eventName - 事件名称
|
|
381
|
+
* @param {Object} data - 事件数据
|
|
382
|
+
*/
|
|
383
|
+
_emit(eventName, data) {
|
|
384
|
+
if (this.eventListeners[eventName]) {
|
|
385
|
+
this.eventListeners[eventName].forEach(callback => {
|
|
386
|
+
try {
|
|
387
|
+
callback(data);
|
|
388
|
+
} catch (error) {
|
|
389
|
+
console.error(`Error in ${eventName} event listener:`, error);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (this.config.debug) {
|
|
395
|
+
console.log(`VoiceIntercom Event [${eventName}]:`, data);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* 添加事件监听器
|
|
401
|
+
* @param {string} eventName - 事件名称
|
|
402
|
+
* @param {Function} callback - 回调函数
|
|
403
|
+
*/
|
|
404
|
+
on(eventName, callback) {
|
|
405
|
+
if (!this.eventListeners[eventName]) {
|
|
406
|
+
this.eventListeners[eventName] = [];
|
|
407
|
+
}
|
|
408
|
+
this.eventListeners[eventName].push(callback);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* 移除事件监听器
|
|
413
|
+
* @param {string} eventName - 事件名称
|
|
414
|
+
* @param {Function} callback - 回调函数
|
|
415
|
+
*/
|
|
416
|
+
off(eventName, callback) {
|
|
417
|
+
if (this.eventListeners[eventName]) {
|
|
418
|
+
const index = this.eventListeners[eventName].indexOf(callback);
|
|
419
|
+
if (index > -1) {
|
|
420
|
+
this.eventListeners[eventName].splice(index, 1);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* 生成MD5哈希值 (浏览器环境兼容)
|
|
427
|
+
* @param {string} text - 要哈希的文本
|
|
428
|
+
* @returns {Promise<string>} MD5哈希值
|
|
429
|
+
* @private
|
|
430
|
+
*/
|
|
431
|
+
async _generateMD5Hash(text) {
|
|
432
|
+
// 如果在Node.js环境中,使用crypto模块
|
|
433
|
+
if (typeof require !== 'undefined' && typeof window === 'undefined') {
|
|
434
|
+
const crypto = require('crypto');
|
|
435
|
+
return crypto.createHash('md5').update(text, 'utf8').digest('hex');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// 浏览器环境:使用Web Crypto API的简化MD5实现
|
|
439
|
+
// 注意:Web Crypto API不支持MD5,这里使用简单的替代方案
|
|
440
|
+
// 在生产环境中,建议服务器端生成签名
|
|
441
|
+
if (typeof crypto !== 'undefined' && crypto.subtle) {
|
|
442
|
+
const encoder = new TextEncoder();
|
|
443
|
+
const data = encoder.encode(text);
|
|
444
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
445
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
446
|
+
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('').substring(0, 32);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// 回退方案:简单哈希函数(仅用于演示)
|
|
450
|
+
let hash = 0;
|
|
451
|
+
for (let i = 0; i < text.length; i++) {
|
|
452
|
+
const char = text.charCodeAt(i);
|
|
453
|
+
hash = ((hash << 5) - hash) + char;
|
|
454
|
+
hash = hash & hash; // 转换为32位整数
|
|
455
|
+
}
|
|
456
|
+
return Math.abs(hash).toString(16).padStart(32, '0').substring(0, 32);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* 销毁控制器
|
|
461
|
+
*/
|
|
462
|
+
destroy() {
|
|
463
|
+
// 停止对讲
|
|
464
|
+
if (this.currentStatus !== this.status.IDLE) {
|
|
465
|
+
this.stopIntercom().catch(console.error);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// 清理事件监听器
|
|
469
|
+
this.eventListeners = {};
|
|
470
|
+
|
|
471
|
+
// 清理WebRTC客户端
|
|
472
|
+
if (this.rtcClient) {
|
|
473
|
+
this.rtcClient.close();
|
|
474
|
+
this.rtcClient = null;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (this.config.debug) {
|
|
478
|
+
console.log('VoiceIntercomController destroyed');
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// 兼容不同模块系统
|
|
484
|
+
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
|
485
|
+
module.exports = VoiceIntercomController;
|
|
486
|
+
} else if (typeof define === 'function' && define.amd) {
|
|
487
|
+
define([], function() { return VoiceIntercomController; });
|
|
488
|
+
} else if (typeof window !== 'undefined') {
|
|
489
|
+
window.VoiceIntercomController = VoiceIntercomController;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export default VoiceIntercomController;
|