@coze/realtime-api 1.3.0 → 1.3.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 CHANGED
@@ -117,3 +117,6 @@ function setupEventListeners() {
117
117
 
118
118
  ## Example
119
119
  For a complete working example, check out our [realtime console demo](../../examples/realtime-console).
120
+
121
+ ## Simultaneous Interpretation
122
+ [Documentation](./live.md)
package/README.zh-CN.md CHANGED
@@ -113,3 +113,6 @@ function setupEventListeners() {
113
113
 
114
114
  ## 示例
115
115
  查看完整的示例,请参考我们的[实时语音控制台DEMO](../../examples/realtime-console)。
116
+
117
+ ## 同声传译
118
+ [使用文档](./live.md)
@@ -200,6 +200,10 @@ var EventNames = /*#__PURE__*/ function(EventNames) {
200
200
  * en: Mode updated
201
201
  * zh: 更新房间模式成功
202
202
  */ EventNames["MODE_UPDATED"] = "server.mode.updated";
203
+ /**
204
+ * en: Live created
205
+ * zh: 直播创建
206
+ */ EventNames["LIVE_CREATED"] = "server.live.created";
203
207
  return EventNames;
204
208
  }(EventNames || {});
205
209
  /* ESM default export */ const __WEBPACK_DEFAULT_EXPORT__ = EventNames;
package/dist/cjs/index.js CHANGED
@@ -313,6 +313,10 @@ var event_names_EventNames = /*#__PURE__*/ function(EventNames) {
313
313
  * en: Mode updated
314
314
  * zh: 更新房间模式成功
315
315
  */ EventNames["MODE_UPDATED"] = "server.mode.updated";
316
+ /**
317
+ * en: Live created
318
+ * zh: 直播创建
319
+ */ EventNames["LIVE_CREATED"] = "server.live.created";
316
320
  return EventNames;
317
321
  }(event_names_EventNames || {});
318
322
  /* ESM default export */ const event_names = event_names_EventNames;
@@ -654,6 +658,7 @@ class EngineClient extends RealtimeEventHandler {
654
658
  if (isTestEnv) rtc_default().setParameter('ICE_CONFIG_REQUEST_URLS', [
655
659
  'rtc-test.bytedance.com'
656
660
  ]);
661
+ else localStorage.removeItem('RTC_ACCESS_URLS-VolcEngine');
657
662
  this.engine = rtc_default().createEngine(appId);
658
663
  this.handleMessage = this.handleMessage.bind(this);
659
664
  this.handleUserJoin = this.handleUserJoin.bind(this);
@@ -687,7 +692,7 @@ class RealtimeClient extends RealtimeEventHandler {
687
692
  else {
688
693
  const config = {};
689
694
  if (this._config.prologueContent) config.prologue_content = this._config.prologueContent;
690
- if (void 0 !== this._config.roomMode && null !== this._config.roomMode) config.room_mode = this._config.roomMode;
695
+ if (void 0 !== this._config.roomMode && null !== this._config.roomMode) config.room_mode = this._config.roomMode || api_namespaceObject.RoomMode.Default;
691
696
  if (this._config.videoConfig) {
692
697
  if (isScreenShareDevice(this._config.videoConfig.videoInputDeviceId)) config.video_config = {
693
698
  stream_video_type: 'screen'
@@ -696,7 +701,8 @@ class RealtimeClient extends RealtimeEventHandler {
696
701
  stream_video_type: 'main'
697
702
  };
698
703
  }
699
- roomInfo = await this._api.audio.rooms.create({
704
+ if (this._config.translateConfig) config.translate_config = this._config.translateConfig;
705
+ const params = {
700
706
  bot_id: botId,
701
707
  conversation_id: conversationId || void 0,
702
708
  voice_id: voiceId && voiceId.length > 0 ? voiceId : void 0,
@@ -704,7 +710,8 @@ class RealtimeClient extends RealtimeEventHandler {
704
710
  uid: this._config.userId || void 0,
705
711
  workflow_id: this._config.workflowId || void 0,
706
712
  config
707
- });
713
+ };
714
+ roomInfo = await this._api.audio.rooms.create(params);
708
715
  }
709
716
  } catch (error) {
710
717
  this.dispatch(event_names.ERROR, error);
@@ -0,0 +1,292 @@
1
+ "use strict";
2
+ // The require scope
3
+ var __webpack_require__ = {};
4
+ /************************************************************************/ // webpack/runtime/define_property_getters
5
+ (()=>{
6
+ __webpack_require__.d = function(exports1, definition) {
7
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
8
+ enumerable: true,
9
+ get: definition[key]
10
+ });
11
+ };
12
+ })();
13
+ // webpack/runtime/has_own_property
14
+ (()=>{
15
+ __webpack_require__.o = function(obj, prop) {
16
+ return Object.prototype.hasOwnProperty.call(obj, prop);
17
+ };
18
+ })();
19
+ // webpack/runtime/make_namespace_object
20
+ (()=>{
21
+ // define __esModule on exports
22
+ __webpack_require__.r = function(exports1) {
23
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
24
+ value: 'Module'
25
+ });
26
+ Object.defineProperty(exports1, '__esModule', {
27
+ value: true
28
+ });
29
+ };
30
+ })();
31
+ /************************************************************************/ var __webpack_exports__ = {};
32
+ // ESM COMPAT FLAG
33
+ __webpack_require__.r(__webpack_exports__);
34
+ // EXPORTS
35
+ __webpack_require__.d(__webpack_exports__, {
36
+ WebLiveClient: ()=>/* binding */ WebLiveClient,
37
+ ResourceStatus: ()=>/* binding */ live_ResourceStatus
38
+ });
39
+ const api_namespaceObject = require("@coze/api");
40
+ // WTN服务基础URL
41
+ const WTN_BASE_URL = 'https://wtn.volcvideo.com';
42
+ /**
43
+ * WebRTC资源状态
44
+ */ var live_ResourceStatus = /*#__PURE__*/ function(ResourceStatus) {
45
+ ResourceStatus["IDLE"] = "idle";
46
+ ResourceStatus["CONNECTING"] = "connecting";
47
+ ResourceStatus["CONNECTED"] = "connected";
48
+ ResourceStatus["FAILED"] = "failed";
49
+ ResourceStatus["CLOSING"] = "closing";
50
+ ResourceStatus["CLOSED"] = "closed";
51
+ return ResourceStatus;
52
+ }({});
53
+ /**
54
+ * 同声传译客户端
55
+ */ class WebLiveClient {
56
+ /**
57
+ * 获取当前连接状态
58
+ */ getStatus() {
59
+ return this.status;
60
+ }
61
+ /**
62
+ * 添加状态变化监听器
63
+ * @param callback 状态变化回调函数
64
+ */ onStatusChange(callback) {
65
+ this.statusListeners.push(callback);
66
+ }
67
+ /**
68
+ * 移除状态变化监听器
69
+ * @param callback 要移除的回调函数
70
+ */ offStatusChange(callback) {
71
+ this.removeStatusListener(callback);
72
+ }
73
+ /**
74
+ * 移除状态变化监听器
75
+ * @param callback 要移除的回调函数
76
+ */ removeStatusListener(callback) {
77
+ const index = this.statusListeners.indexOf(callback);
78
+ if (-1 !== index) this.statusListeners.splice(index, 1);
79
+ }
80
+ /**
81
+ * 订阅音频资源
82
+ * @param appId 应用ID
83
+ * @param streamId 流ID
84
+ * @param clientId 客户端ID
85
+ */ async subscribe(appId, streamId) {
86
+ let clientId = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : '';
87
+ try {
88
+ var _pc_localDescription;
89
+ // 先清理现有连接
90
+ if (this.peerConnection) {
91
+ this.peerConnection.close();
92
+ this.peerConnection = null;
93
+ }
94
+ this.setStatus("connecting");
95
+ // 1. 创建RTCPeerConnection
96
+ const rtcConfig = {};
97
+ const pc = new RTCPeerConnection(rtcConfig);
98
+ pc.ontrack = (event)=>{
99
+ // 音频流
100
+ this.player.onloadeddata = ()=>{
101
+ this.player.play();
102
+ };
103
+ this.player.srcObject = event.streams[0];
104
+ };
105
+ this.peerConnection = pc;
106
+ this.setupPeerConnectionListeners();
107
+ pc.addTransceiver('audio', {
108
+ direction: 'recvonly'
109
+ });
110
+ // 2. 创建Offer (SDP)
111
+ const offer = await pc.createOffer();
112
+ // 设置本地描述
113
+ await pc.setLocalDescription(offer);
114
+ // 等待ICE收集完成再继续
115
+ await this.waitForIceGathering(pc);
116
+ if (!(null === (_pc_localDescription = pc.localDescription) || void 0 === _pc_localDescription ? void 0 : _pc_localDescription.sdp)) throw new Error('Failed to create SDP offer');
117
+ // 3. 发送Offer到WTN服务订阅资源
118
+ let subscribeUrl = `${WTN_BASE_URL}/sub/${appId}/${streamId}?MuteVideo=true`;
119
+ if (clientId) subscribeUrl += `&clientid=${clientId}`;
120
+ const response = await fetch(subscribeUrl, {
121
+ method: 'POST',
122
+ headers: {
123
+ 'Content-Type': 'application/sdp'
124
+ },
125
+ body: offer.sdp
126
+ });
127
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
128
+ // 4. 保存资源URL (用于销毁资源)
129
+ this.resourceUrl = response.headers.get('location') || '';
130
+ // 5. 设置远程SDP (Answer)
131
+ // 直接获取文本响应,因为服务器返回的是application/sdp格式而非json
132
+ const answerSdp = await response.text();
133
+ const answer = new RTCSessionDescription({
134
+ type: 'answer',
135
+ sdp: answerSdp
136
+ });
137
+ await this.peerConnection.setRemoteDescription(answer);
138
+ // 7. 返回结果
139
+ return {
140
+ status: this.status,
141
+ peerConnection: this.peerConnection
142
+ };
143
+ } catch (error) {
144
+ this.status = "failed";
145
+ console.error('Failed to subscribe WebRTC stream:', error);
146
+ return Promise.reject(error);
147
+ }
148
+ }
149
+ /**
150
+ * 销毁订阅资源
151
+ */ async unsubscribe() {
152
+ try {
153
+ // 销毁订阅资源
154
+ if (!this.resourceUrl) throw new Error('No valid subscription resource URL to unsubscribe');
155
+ this.setStatus("closing");
156
+ const response = await fetch(this.resourceUrl, {
157
+ method: 'DELETE'
158
+ });
159
+ if (!response.ok) throw new Error(`Failed to unsubscribe: ${response.status} ${response.statusText}`);
160
+ // 关闭RTC连接
161
+ if (this.peerConnection) {
162
+ this.peerConnection.close();
163
+ this.peerConnection = null;
164
+ }
165
+ this.resourceUrl = '';
166
+ this.status = "closed";
167
+ return true;
168
+ } catch (error) {
169
+ console.error('Error unsubscribing resource:', error);
170
+ this.status = "failed";
171
+ return Promise.reject(error);
172
+ }
173
+ }
174
+ /**
175
+ * 静音/取消静音
176
+ * @param muted 是否静音
177
+ */ setMuted(muted) {
178
+ this.player.muted = muted;
179
+ }
180
+ /**
181
+ * 关闭并清理资源
182
+ */ close() {
183
+ // 关闭PeerConnection
184
+ this.closePeerConnection();
185
+ // Clean up audio element
186
+ if (this.player) {
187
+ this.player.pause();
188
+ this.player.srcObject = null;
189
+ this.player.remove();
190
+ }
191
+ // 重置状态
192
+ this.resourceUrl = '';
193
+ this.setStatus("idle");
194
+ }
195
+ /**
196
+ * 等待ICE收集完成
197
+ * @param pc RTCPeerConnection实例
198
+ */ waitForIceGathering(pc) {
199
+ return new Promise((resolve)=>{
200
+ // 如果已经收集完成,直接返回
201
+ if ('complete' === pc.iceGatheringState) {
202
+ resolve();
203
+ return;
204
+ }
205
+ // 设置收集完成时的回调
206
+ const checkState = ()=>{
207
+ if ('complete' === pc.iceGatheringState) {
208
+ pc.removeEventListener('icegatheringstatechange', checkState);
209
+ resolve();
210
+ }
211
+ };
212
+ // 监听收集状态变化
213
+ pc.addEventListener('icegatheringstatechange', checkState);
214
+ // 添加超时处理,防止永远等待
215
+ setTimeout(()=>resolve(), 5000);
216
+ });
217
+ }
218
+ setupPeerConnectionListeners() {
219
+ if (!this.peerConnection) return;
220
+ this.peerConnection.oniceconnectionstatechange = ()=>{
221
+ var _this_peerConnection, _this_peerConnection1;
222
+ console.log('ICE connection state changed:', null === (_this_peerConnection = this.peerConnection) || void 0 === _this_peerConnection ? void 0 : _this_peerConnection.iceConnectionState);
223
+ switch(null === (_this_peerConnection1 = this.peerConnection) || void 0 === _this_peerConnection1 ? void 0 : _this_peerConnection1.iceConnectionState){
224
+ case 'connected':
225
+ case 'completed':
226
+ this.setStatus("connected");
227
+ break;
228
+ case 'failed':
229
+ case 'disconnected':
230
+ this.setStatus("failed");
231
+ break;
232
+ case 'closed':
233
+ this.setStatus("closed");
234
+ break;
235
+ default:
236
+ var _this_peerConnection2;
237
+ console.log('ICE connection state changed:', null === (_this_peerConnection2 = this.peerConnection) || void 0 === _this_peerConnection2 ? void 0 : _this_peerConnection2.iceConnectionState);
238
+ break;
239
+ }
240
+ };
241
+ this.peerConnection.onicecandidate = (event)=>{
242
+ if (event.candidate) console.log('New ICE candidate:', event.candidate);
243
+ };
244
+ }
245
+ /**
246
+ * 关闭PeerConnection
247
+ */ closePeerConnection() {
248
+ if (this.peerConnection) {
249
+ this.peerConnection.close();
250
+ this.peerConnection = null;
251
+ }
252
+ }
253
+ /**
254
+ * 设置状态并触发监听回调
255
+ * @param newStatus 新状态
256
+ * @private 私有方法,仅内部使用
257
+ */ setStatus(newStatus) {
258
+ const oldStatus = this.status;
259
+ if (oldStatus !== newStatus) {
260
+ this.status = newStatus;
261
+ // 触发所有监听器
262
+ for (const listener of this.statusListeners)try {
263
+ listener(newStatus);
264
+ } catch (error) {
265
+ console.error('Error in status listener:', error);
266
+ }
267
+ }
268
+ }
269
+ constructor(liveId){
270
+ this.peerConnection = null;
271
+ this.resourceUrl = '';
272
+ this.status = "idle";
273
+ this.statusListeners = [];
274
+ /**
275
+ * 获取直播信息
276
+ */ this.getLiveData = async ()=>{
277
+ const api = new api_namespaceObject.CozeAPI({
278
+ baseURL: api_namespaceObject.COZE_CN_BASE_URL,
279
+ token: ''
280
+ });
281
+ return await api.audio.live.retrieve(this.liveId);
282
+ };
283
+ this.setupPeerConnectionListeners();
284
+ this.player = document.createElement('audio');
285
+ this.liveId = liveId;
286
+ }
287
+ }
288
+ var __webpack_export_target__ = exports;
289
+ for(var i in __webpack_exports__)__webpack_export_target__[i] = __webpack_exports__[i];
290
+ if (__webpack_exports__.__esModule) Object.defineProperty(__webpack_export_target__, '__esModule', {
291
+ value: true
292
+ });
@@ -163,6 +163,10 @@ var event_names_EventNames = /*#__PURE__*/ function(EventNames) {
163
163
  * en: Mode updated
164
164
  * zh: 更新房间模式成功
165
165
  */ EventNames["MODE_UPDATED"] = "server.mode.updated";
166
+ /**
167
+ * en: Live created
168
+ * zh: 直播创建
169
+ */ EventNames["LIVE_CREATED"] = "server.live.created";
166
170
  return EventNames;
167
171
  }(event_names_EventNames || {});
168
172
  /* ESM default export */ const event_names = event_names_EventNames;
package/dist/esm/index.js CHANGED
@@ -286,6 +286,10 @@ var event_names_EventNames = /*#__PURE__*/ function(EventNames) {
286
286
  * en: Mode updated
287
287
  * zh: 更新房间模式成功
288
288
  */ EventNames["MODE_UPDATED"] = "server.mode.updated";
289
+ /**
290
+ * en: Live created
291
+ * zh: 直播创建
292
+ */ EventNames["LIVE_CREATED"] = "server.live.created";
289
293
  return EventNames;
290
294
  }(event_names_EventNames || {});
291
295
  /* ESM default export */ const event_names = event_names_EventNames;
@@ -625,6 +629,7 @@ class EngineClient extends RealtimeEventHandler {
625
629
  if (isTestEnv) __WEBPACK_EXTERNAL_MODULE__volcengine_rtc__["default"].setParameter('ICE_CONFIG_REQUEST_URLS', [
626
630
  'rtc-test.bytedance.com'
627
631
  ]);
632
+ else localStorage.removeItem('RTC_ACCESS_URLS-VolcEngine');
628
633
  this.engine = __WEBPACK_EXTERNAL_MODULE__volcengine_rtc__["default"].createEngine(appId);
629
634
  this.handleMessage = this.handleMessage.bind(this);
630
635
  this.handleUserJoin = this.handleUserJoin.bind(this);
@@ -658,7 +663,7 @@ class RealtimeClient extends RealtimeEventHandler {
658
663
  else {
659
664
  const config = {};
660
665
  if (this._config.prologueContent) config.prologue_content = this._config.prologueContent;
661
- if (void 0 !== this._config.roomMode && null !== this._config.roomMode) config.room_mode = this._config.roomMode;
666
+ if (void 0 !== this._config.roomMode && null !== this._config.roomMode) config.room_mode = this._config.roomMode || __WEBPACK_EXTERNAL_MODULE__coze_api__.RoomMode.Default;
662
667
  if (this._config.videoConfig) {
663
668
  if (isScreenShareDevice(this._config.videoConfig.videoInputDeviceId)) config.video_config = {
664
669
  stream_video_type: 'screen'
@@ -667,7 +672,8 @@ class RealtimeClient extends RealtimeEventHandler {
667
672
  stream_video_type: 'main'
668
673
  };
669
674
  }
670
- roomInfo = await this._api.audio.rooms.create({
675
+ if (this._config.translateConfig) config.translate_config = this._config.translateConfig;
676
+ const params = {
671
677
  bot_id: botId,
672
678
  conversation_id: conversationId || void 0,
673
679
  voice_id: voiceId && voiceId.length > 0 ? voiceId : void 0,
@@ -675,7 +681,8 @@ class RealtimeClient extends RealtimeEventHandler {
675
681
  uid: this._config.userId || void 0,
676
682
  workflow_id: this._config.workflowId || void 0,
677
683
  config
678
- });
684
+ };
685
+ roomInfo = await this._api.audio.rooms.create(params);
679
686
  }
680
687
  } catch (error) {
681
688
  this.dispatch(event_names.ERROR, error);
@@ -0,0 +1,250 @@
1
+ import * as __WEBPACK_EXTERNAL_MODULE__coze_api__ from "@coze/api";
2
+ // WTN服务基础URL
3
+ const WTN_BASE_URL = 'https://wtn.volcvideo.com';
4
+ /**
5
+ * WebRTC资源状态
6
+ */ var live_ResourceStatus = /*#__PURE__*/ function(ResourceStatus) {
7
+ ResourceStatus["IDLE"] = "idle";
8
+ ResourceStatus["CONNECTING"] = "connecting";
9
+ ResourceStatus["CONNECTED"] = "connected";
10
+ ResourceStatus["FAILED"] = "failed";
11
+ ResourceStatus["CLOSING"] = "closing";
12
+ ResourceStatus["CLOSED"] = "closed";
13
+ return ResourceStatus;
14
+ }({});
15
+ /**
16
+ * 同声传译客户端
17
+ */ class WebLiveClient {
18
+ /**
19
+ * 获取当前连接状态
20
+ */ getStatus() {
21
+ return this.status;
22
+ }
23
+ /**
24
+ * 添加状态变化监听器
25
+ * @param callback 状态变化回调函数
26
+ */ onStatusChange(callback) {
27
+ this.statusListeners.push(callback);
28
+ }
29
+ /**
30
+ * 移除状态变化监听器
31
+ * @param callback 要移除的回调函数
32
+ */ offStatusChange(callback) {
33
+ this.removeStatusListener(callback);
34
+ }
35
+ /**
36
+ * 移除状态变化监听器
37
+ * @param callback 要移除的回调函数
38
+ */ removeStatusListener(callback) {
39
+ const index = this.statusListeners.indexOf(callback);
40
+ if (-1 !== index) this.statusListeners.splice(index, 1);
41
+ }
42
+ /**
43
+ * 订阅音频资源
44
+ * @param appId 应用ID
45
+ * @param streamId 流ID
46
+ * @param clientId 客户端ID
47
+ */ async subscribe(appId, streamId) {
48
+ let clientId = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : '';
49
+ try {
50
+ var _pc_localDescription;
51
+ // 先清理现有连接
52
+ if (this.peerConnection) {
53
+ this.peerConnection.close();
54
+ this.peerConnection = null;
55
+ }
56
+ this.setStatus("connecting");
57
+ // 1. 创建RTCPeerConnection
58
+ const rtcConfig = {};
59
+ const pc = new RTCPeerConnection(rtcConfig);
60
+ pc.ontrack = (event)=>{
61
+ // 音频流
62
+ this.player.onloadeddata = ()=>{
63
+ this.player.play();
64
+ };
65
+ this.player.srcObject = event.streams[0];
66
+ };
67
+ this.peerConnection = pc;
68
+ this.setupPeerConnectionListeners();
69
+ pc.addTransceiver('audio', {
70
+ direction: 'recvonly'
71
+ });
72
+ // 2. 创建Offer (SDP)
73
+ const offer = await pc.createOffer();
74
+ // 设置本地描述
75
+ await pc.setLocalDescription(offer);
76
+ // 等待ICE收集完成再继续
77
+ await this.waitForIceGathering(pc);
78
+ if (!(null === (_pc_localDescription = pc.localDescription) || void 0 === _pc_localDescription ? void 0 : _pc_localDescription.sdp)) throw new Error('Failed to create SDP offer');
79
+ // 3. 发送Offer到WTN服务订阅资源
80
+ let subscribeUrl = `${WTN_BASE_URL}/sub/${appId}/${streamId}?MuteVideo=true`;
81
+ if (clientId) subscribeUrl += `&clientid=${clientId}`;
82
+ const response = await fetch(subscribeUrl, {
83
+ method: 'POST',
84
+ headers: {
85
+ 'Content-Type': 'application/sdp'
86
+ },
87
+ body: offer.sdp
88
+ });
89
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
90
+ // 4. 保存资源URL (用于销毁资源)
91
+ this.resourceUrl = response.headers.get('location') || '';
92
+ // 5. 设置远程SDP (Answer)
93
+ // 直接获取文本响应,因为服务器返回的是application/sdp格式而非json
94
+ const answerSdp = await response.text();
95
+ const answer = new RTCSessionDescription({
96
+ type: 'answer',
97
+ sdp: answerSdp
98
+ });
99
+ await this.peerConnection.setRemoteDescription(answer);
100
+ // 7. 返回结果
101
+ return {
102
+ status: this.status,
103
+ peerConnection: this.peerConnection
104
+ };
105
+ } catch (error) {
106
+ this.status = "failed";
107
+ console.error('Failed to subscribe WebRTC stream:', error);
108
+ return Promise.reject(error);
109
+ }
110
+ }
111
+ /**
112
+ * 销毁订阅资源
113
+ */ async unsubscribe() {
114
+ try {
115
+ // 销毁订阅资源
116
+ if (!this.resourceUrl) throw new Error('No valid subscription resource URL to unsubscribe');
117
+ this.setStatus("closing");
118
+ const response = await fetch(this.resourceUrl, {
119
+ method: 'DELETE'
120
+ });
121
+ if (!response.ok) throw new Error(`Failed to unsubscribe: ${response.status} ${response.statusText}`);
122
+ // 关闭RTC连接
123
+ if (this.peerConnection) {
124
+ this.peerConnection.close();
125
+ this.peerConnection = null;
126
+ }
127
+ this.resourceUrl = '';
128
+ this.status = "closed";
129
+ return true;
130
+ } catch (error) {
131
+ console.error('Error unsubscribing resource:', error);
132
+ this.status = "failed";
133
+ return Promise.reject(error);
134
+ }
135
+ }
136
+ /**
137
+ * 静音/取消静音
138
+ * @param muted 是否静音
139
+ */ setMuted(muted) {
140
+ this.player.muted = muted;
141
+ }
142
+ /**
143
+ * 关闭并清理资源
144
+ */ close() {
145
+ // 关闭PeerConnection
146
+ this.closePeerConnection();
147
+ // Clean up audio element
148
+ if (this.player) {
149
+ this.player.pause();
150
+ this.player.srcObject = null;
151
+ this.player.remove();
152
+ }
153
+ // 重置状态
154
+ this.resourceUrl = '';
155
+ this.setStatus("idle");
156
+ }
157
+ /**
158
+ * 等待ICE收集完成
159
+ * @param pc RTCPeerConnection实例
160
+ */ waitForIceGathering(pc) {
161
+ return new Promise((resolve)=>{
162
+ // 如果已经收集完成,直接返回
163
+ if ('complete' === pc.iceGatheringState) {
164
+ resolve();
165
+ return;
166
+ }
167
+ // 设置收集完成时的回调
168
+ const checkState = ()=>{
169
+ if ('complete' === pc.iceGatheringState) {
170
+ pc.removeEventListener('icegatheringstatechange', checkState);
171
+ resolve();
172
+ }
173
+ };
174
+ // 监听收集状态变化
175
+ pc.addEventListener('icegatheringstatechange', checkState);
176
+ // 添加超时处理,防止永远等待
177
+ setTimeout(()=>resolve(), 5000);
178
+ });
179
+ }
180
+ setupPeerConnectionListeners() {
181
+ if (!this.peerConnection) return;
182
+ this.peerConnection.oniceconnectionstatechange = ()=>{
183
+ var _this_peerConnection, _this_peerConnection1;
184
+ console.log('ICE connection state changed:', null === (_this_peerConnection = this.peerConnection) || void 0 === _this_peerConnection ? void 0 : _this_peerConnection.iceConnectionState);
185
+ switch(null === (_this_peerConnection1 = this.peerConnection) || void 0 === _this_peerConnection1 ? void 0 : _this_peerConnection1.iceConnectionState){
186
+ case 'connected':
187
+ case 'completed':
188
+ this.setStatus("connected");
189
+ break;
190
+ case 'failed':
191
+ case 'disconnected':
192
+ this.setStatus("failed");
193
+ break;
194
+ case 'closed':
195
+ this.setStatus("closed");
196
+ break;
197
+ default:
198
+ var _this_peerConnection2;
199
+ console.log('ICE connection state changed:', null === (_this_peerConnection2 = this.peerConnection) || void 0 === _this_peerConnection2 ? void 0 : _this_peerConnection2.iceConnectionState);
200
+ break;
201
+ }
202
+ };
203
+ this.peerConnection.onicecandidate = (event)=>{
204
+ if (event.candidate) console.log('New ICE candidate:', event.candidate);
205
+ };
206
+ }
207
+ /**
208
+ * 关闭PeerConnection
209
+ */ closePeerConnection() {
210
+ if (this.peerConnection) {
211
+ this.peerConnection.close();
212
+ this.peerConnection = null;
213
+ }
214
+ }
215
+ /**
216
+ * 设置状态并触发监听回调
217
+ * @param newStatus 新状态
218
+ * @private 私有方法,仅内部使用
219
+ */ setStatus(newStatus) {
220
+ const oldStatus = this.status;
221
+ if (oldStatus !== newStatus) {
222
+ this.status = newStatus;
223
+ // 触发所有监听器
224
+ for (const listener of this.statusListeners)try {
225
+ listener(newStatus);
226
+ } catch (error) {
227
+ console.error('Error in status listener:', error);
228
+ }
229
+ }
230
+ }
231
+ constructor(liveId){
232
+ this.peerConnection = null;
233
+ this.resourceUrl = '';
234
+ this.status = "idle";
235
+ this.statusListeners = [];
236
+ /**
237
+ * 获取直播信息
238
+ */ this.getLiveData = async ()=>{
239
+ const api = new __WEBPACK_EXTERNAL_MODULE__coze_api__.CozeAPI({
240
+ baseURL: __WEBPACK_EXTERNAL_MODULE__coze_api__.COZE_CN_BASE_URL,
241
+ token: ''
242
+ });
243
+ return await api.audio.live.retrieve(this.liveId);
244
+ };
245
+ this.setupPeerConnectionListeners();
246
+ this.player = document.createElement('audio');
247
+ this.liveId = liveId;
248
+ }
249
+ }
250
+ export { live_ResourceStatus as ResourceStatus, WebLiveClient };