@coze/realtime-api 1.3.0 → 1.3.2-alpha.f7fe14
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 +3 -0
- package/README.zh-CN.md +3 -0
- package/dist/cjs/event-names/index.js +220 -210
- package/dist/cjs/index.js +762 -624
- package/dist/cjs/live/index.js +289 -0
- package/dist/esm/event-names/index.js +216 -169
- package/dist/esm/index.js +757 -591
- package/dist/esm/live/index.js +287 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/event-names/event-names.d.ts +8 -2
- package/dist/types/index.d.ts +330 -9
- package/dist/types/live/live/index.d.ts +91 -0
- package/package.json +26 -6
- package/dist/types/client.d.ts +0 -49
- package/dist/types/error.d.ts +0 -25
- package/dist/types/event-handler.d.ts +0 -12
- package/dist/types/event-names/client.d.ts +0 -49
- package/dist/types/event-names/error.d.ts +0 -25
- package/dist/types/event-names/event-handler.d.ts +0 -12
- package/dist/types/event-names/index.d.ts +0 -163
- package/dist/types/event-names/utils.d.ts +0 -49
- package/dist/types/event-names.d.ts +0 -208
- package/dist/types/utils.d.ts +0 -49
- package/dist/umd/index.js +0 -987
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var api = require('@coze/api');
|
|
4
|
+
|
|
5
|
+
// WTN服务基础URL
|
|
6
|
+
const WTN_BASE_URL = 'https://wtn.volcvideo.com';
|
|
7
|
+
/**
|
|
8
|
+
* WebRTC资源状态
|
|
9
|
+
*/
|
|
10
|
+
exports.ResourceStatus = void 0;
|
|
11
|
+
(function (ResourceStatus) {
|
|
12
|
+
ResourceStatus["IDLE"] = "idle";
|
|
13
|
+
ResourceStatus["CONNECTING"] = "connecting";
|
|
14
|
+
ResourceStatus["CONNECTED"] = "connected";
|
|
15
|
+
ResourceStatus["FAILED"] = "failed";
|
|
16
|
+
ResourceStatus["CLOSING"] = "closing";
|
|
17
|
+
ResourceStatus["CLOSED"] = "closed";
|
|
18
|
+
})(exports.ResourceStatus || (exports.ResourceStatus = {}));
|
|
19
|
+
/**
|
|
20
|
+
* 同声传译客户端
|
|
21
|
+
*/
|
|
22
|
+
class WebLiveClient {
|
|
23
|
+
constructor(liveId) {
|
|
24
|
+
this.peerConnection = null;
|
|
25
|
+
this.resourceUrl = '';
|
|
26
|
+
this.status = exports.ResourceStatus.IDLE;
|
|
27
|
+
this.statusListeners = [];
|
|
28
|
+
/**
|
|
29
|
+
* 获取直播信息
|
|
30
|
+
*/
|
|
31
|
+
this.getLiveData = async () => {
|
|
32
|
+
const api$1 = new api.CozeAPI({
|
|
33
|
+
baseURL: api.COZE_CN_BASE_URL,
|
|
34
|
+
token: '', // 免登录
|
|
35
|
+
});
|
|
36
|
+
return await api$1.audio.live.retrieve(this.liveId);
|
|
37
|
+
};
|
|
38
|
+
this.setupPeerConnectionListeners();
|
|
39
|
+
this.player = document.createElement('audio');
|
|
40
|
+
this.liveId = liveId;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 获取当前连接状态
|
|
44
|
+
*/
|
|
45
|
+
getStatus() {
|
|
46
|
+
return this.status;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 添加状态变化监听器
|
|
50
|
+
* @param callback 状态变化回调函数
|
|
51
|
+
*/
|
|
52
|
+
onStatusChange(callback) {
|
|
53
|
+
this.statusListeners.push(callback);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 移除状态变化监听器
|
|
57
|
+
* @param callback 要移除的回调函数
|
|
58
|
+
*/
|
|
59
|
+
offStatusChange(callback) {
|
|
60
|
+
this.removeStatusListener(callback);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 移除状态变化监听器
|
|
64
|
+
* @param callback 要移除的回调函数
|
|
65
|
+
*/
|
|
66
|
+
removeStatusListener(callback) {
|
|
67
|
+
const index = this.statusListeners.indexOf(callback);
|
|
68
|
+
if (index !== -1) {
|
|
69
|
+
this.statusListeners.splice(index, 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 订阅音频资源
|
|
74
|
+
* @param appId 应用ID
|
|
75
|
+
* @param streamId 流ID
|
|
76
|
+
* @param clientId 客户端ID
|
|
77
|
+
*/
|
|
78
|
+
async subscribe(appId, streamId, clientId = '') {
|
|
79
|
+
var _a;
|
|
80
|
+
try {
|
|
81
|
+
// 先清理现有连接
|
|
82
|
+
if (this.peerConnection) {
|
|
83
|
+
this.peerConnection.close();
|
|
84
|
+
this.peerConnection = null;
|
|
85
|
+
}
|
|
86
|
+
this.setStatus(exports.ResourceStatus.CONNECTING);
|
|
87
|
+
// 1. 创建RTCPeerConnection
|
|
88
|
+
const rtcConfig = {
|
|
89
|
+
// iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
|
|
90
|
+
};
|
|
91
|
+
const pc = new RTCPeerConnection(rtcConfig);
|
|
92
|
+
pc.ontrack = (event) => {
|
|
93
|
+
// 音频流
|
|
94
|
+
this.player.onloadeddata = () => {
|
|
95
|
+
this.player.play();
|
|
96
|
+
};
|
|
97
|
+
this.player.srcObject = event.streams[0];
|
|
98
|
+
};
|
|
99
|
+
this.peerConnection = pc;
|
|
100
|
+
this.setupPeerConnectionListeners();
|
|
101
|
+
pc.addTransceiver('audio', {
|
|
102
|
+
direction: 'recvonly',
|
|
103
|
+
});
|
|
104
|
+
// 2. 创建Offer (SDP)
|
|
105
|
+
const offer = await pc.createOffer();
|
|
106
|
+
// 设置本地描述
|
|
107
|
+
await pc.setLocalDescription(offer);
|
|
108
|
+
// 等待ICE收集完成再继续
|
|
109
|
+
await this.waitForIceGathering(pc);
|
|
110
|
+
if (!((_a = pc.localDescription) === null || _a === void 0 ? void 0 : _a.sdp)) {
|
|
111
|
+
throw new Error('Failed to create SDP offer');
|
|
112
|
+
}
|
|
113
|
+
// 3. 发送Offer到WTN服务订阅资源
|
|
114
|
+
let subscribeUrl = `${WTN_BASE_URL}/sub/${appId}/${streamId}?MuteVideo=true`;
|
|
115
|
+
if (clientId) {
|
|
116
|
+
subscribeUrl += `&clientid=${clientId}`;
|
|
117
|
+
}
|
|
118
|
+
const response = await fetch(subscribeUrl, {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: {
|
|
121
|
+
'Content-Type': 'application/sdp',
|
|
122
|
+
},
|
|
123
|
+
body: offer.sdp,
|
|
124
|
+
});
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
127
|
+
}
|
|
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
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
this.status = exports.ResourceStatus.FAILED;
|
|
146
|
+
console.error('Failed to subscribe WebRTC stream:', error);
|
|
147
|
+
return Promise.reject(error);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 销毁订阅资源
|
|
152
|
+
*/
|
|
153
|
+
async unsubscribe() {
|
|
154
|
+
try {
|
|
155
|
+
// 销毁订阅资源
|
|
156
|
+
if (!this.resourceUrl) {
|
|
157
|
+
throw new Error('No valid subscription resource URL to unsubscribe');
|
|
158
|
+
}
|
|
159
|
+
this.setStatus(exports.ResourceStatus.CLOSING);
|
|
160
|
+
const response = await fetch(this.resourceUrl, {
|
|
161
|
+
method: 'DELETE',
|
|
162
|
+
});
|
|
163
|
+
if (!response.ok) {
|
|
164
|
+
throw new Error(`Failed to unsubscribe: ${response.status} ${response.statusText}`);
|
|
165
|
+
}
|
|
166
|
+
// 关闭RTC连接
|
|
167
|
+
if (this.peerConnection) {
|
|
168
|
+
this.peerConnection.close();
|
|
169
|
+
this.peerConnection = null;
|
|
170
|
+
}
|
|
171
|
+
this.resourceUrl = '';
|
|
172
|
+
this.status = exports.ResourceStatus.CLOSED;
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
console.error('Error unsubscribing resource:', error);
|
|
177
|
+
this.status = exports.ResourceStatus.FAILED;
|
|
178
|
+
return Promise.reject(error);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* 静音/取消静音
|
|
183
|
+
* @param muted 是否静音
|
|
184
|
+
*/
|
|
185
|
+
setMuted(muted) {
|
|
186
|
+
this.player.muted = muted;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* 关闭并清理资源
|
|
190
|
+
*/
|
|
191
|
+
close() {
|
|
192
|
+
// 关闭PeerConnection
|
|
193
|
+
this.closePeerConnection();
|
|
194
|
+
// Clean up audio element
|
|
195
|
+
if (this.player) {
|
|
196
|
+
this.player.pause();
|
|
197
|
+
this.player.srcObject = null;
|
|
198
|
+
this.player.remove();
|
|
199
|
+
}
|
|
200
|
+
// 重置状态
|
|
201
|
+
this.resourceUrl = '';
|
|
202
|
+
this.setStatus(exports.ResourceStatus.IDLE);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 等待ICE收集完成
|
|
206
|
+
* @param pc RTCPeerConnection实例
|
|
207
|
+
*/
|
|
208
|
+
waitForIceGathering(pc) {
|
|
209
|
+
return new Promise(resolve => {
|
|
210
|
+
// 如果已经收集完成,直接返回
|
|
211
|
+
if (pc.iceGatheringState === 'complete') {
|
|
212
|
+
resolve();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
// 设置收集完成时的回调
|
|
216
|
+
const checkState = () => {
|
|
217
|
+
if (pc.iceGatheringState === 'complete') {
|
|
218
|
+
pc.removeEventListener('icegatheringstatechange', checkState);
|
|
219
|
+
resolve();
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
// 监听收集状态变化
|
|
223
|
+
pc.addEventListener('icegatheringstatechange', checkState);
|
|
224
|
+
// 添加超时处理,防止永远等待
|
|
225
|
+
setTimeout(() => resolve(), 5000);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
setupPeerConnectionListeners() {
|
|
229
|
+
if (!this.peerConnection) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
this.peerConnection.oniceconnectionstatechange = () => {
|
|
233
|
+
var _a, _b, _c;
|
|
234
|
+
console.log('ICE connection state changed:', (_a = this.peerConnection) === null || _a === void 0 ? void 0 : _a.iceConnectionState);
|
|
235
|
+
switch ((_b = this.peerConnection) === null || _b === void 0 ? void 0 : _b.iceConnectionState) {
|
|
236
|
+
case 'connected':
|
|
237
|
+
case 'completed':
|
|
238
|
+
this.setStatus(exports.ResourceStatus.CONNECTED);
|
|
239
|
+
break;
|
|
240
|
+
case 'failed':
|
|
241
|
+
case 'disconnected':
|
|
242
|
+
this.setStatus(exports.ResourceStatus.FAILED);
|
|
243
|
+
break;
|
|
244
|
+
case 'closed':
|
|
245
|
+
this.setStatus(exports.ResourceStatus.CLOSED);
|
|
246
|
+
break;
|
|
247
|
+
default:
|
|
248
|
+
console.log('ICE connection state changed:', (_c = this.peerConnection) === null || _c === void 0 ? void 0 : _c.iceConnectionState);
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
this.peerConnection.onicecandidate = event => {
|
|
253
|
+
if (event.candidate) {
|
|
254
|
+
console.log('New ICE candidate:', event.candidate);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* 关闭PeerConnection
|
|
260
|
+
*/
|
|
261
|
+
closePeerConnection() {
|
|
262
|
+
if (this.peerConnection) {
|
|
263
|
+
this.peerConnection.close();
|
|
264
|
+
this.peerConnection = null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* 设置状态并触发监听回调
|
|
269
|
+
* @param newStatus 新状态
|
|
270
|
+
* @private 私有方法,仅内部使用
|
|
271
|
+
*/
|
|
272
|
+
setStatus(newStatus) {
|
|
273
|
+
const oldStatus = this.status;
|
|
274
|
+
if (oldStatus !== newStatus) {
|
|
275
|
+
this.status = newStatus;
|
|
276
|
+
// 触发所有监听器
|
|
277
|
+
for (const listener of this.statusListeners) {
|
|
278
|
+
try {
|
|
279
|
+
listener(newStatus);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
console.error('Error in status listener:', error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
exports.WebLiveClient = WebLiveClient;
|