@voicemaster/core 1.0.9 → 1.0.11
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/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +45 -13
- package/dist/index.mjs +45 -13
- package/package.json +1 -1
- package/src/VoiceClient.ts +49 -13
package/dist/index.d.mts
CHANGED
|
@@ -17,6 +17,8 @@ declare class VoiceClient {
|
|
|
17
17
|
private iceServers;
|
|
18
18
|
private isConnectedFlag;
|
|
19
19
|
private targetUserId;
|
|
20
|
+
private connectingPromise;
|
|
21
|
+
private peers;
|
|
20
22
|
constructor(config: VoiceClientConfig);
|
|
21
23
|
on(event: string, callback: Function): void;
|
|
22
24
|
private emit;
|
|
@@ -31,6 +33,7 @@ declare class VoiceClient {
|
|
|
31
33
|
private handleCandidate;
|
|
32
34
|
private send;
|
|
33
35
|
disconnect(): void;
|
|
36
|
+
getPeers(): string[];
|
|
34
37
|
toggleMute(): void;
|
|
35
38
|
isMuted(): boolean;
|
|
36
39
|
getUserId(): string;
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ declare class VoiceClient {
|
|
|
17
17
|
private iceServers;
|
|
18
18
|
private isConnectedFlag;
|
|
19
19
|
private targetUserId;
|
|
20
|
+
private connectingPromise;
|
|
21
|
+
private peers;
|
|
20
22
|
constructor(config: VoiceClientConfig);
|
|
21
23
|
on(event: string, callback: Function): void;
|
|
22
24
|
private emit;
|
|
@@ -31,6 +33,7 @@ declare class VoiceClient {
|
|
|
31
33
|
private handleCandidate;
|
|
32
34
|
private send;
|
|
33
35
|
disconnect(): void;
|
|
36
|
+
getPeers(): string[];
|
|
34
37
|
toggleMute(): void;
|
|
35
38
|
isMuted(): boolean;
|
|
36
39
|
getUserId(): string;
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,8 @@ var VoiceClient = class {
|
|
|
34
34
|
this.eventHandlers = /* @__PURE__ */ new Map();
|
|
35
35
|
this.isConnectedFlag = false;
|
|
36
36
|
this.targetUserId = null;
|
|
37
|
+
this.connectingPromise = null;
|
|
38
|
+
this.peers = /* @__PURE__ */ new Set();
|
|
37
39
|
this.signalingUrl = config.signalingUrl;
|
|
38
40
|
this.roomId = config.roomId;
|
|
39
41
|
this.userId = config.userId;
|
|
@@ -41,7 +43,7 @@ var VoiceClient = class {
|
|
|
41
43
|
{ urls: "stun:stun.l.google.com:19302" }
|
|
42
44
|
];
|
|
43
45
|
if (config.autoConnect !== false) {
|
|
44
|
-
this.connect();
|
|
46
|
+
Promise.resolve().then(() => this.connect());
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
49
|
on(event, callback) {
|
|
@@ -57,13 +59,22 @@ var VoiceClient = class {
|
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
async connect() {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
this.initPeerConnection();
|
|
63
|
-
this.initWebSocket();
|
|
64
|
-
} catch (error) {
|
|
65
|
-
this.emit("error", error);
|
|
62
|
+
if (this.connectingPromise) {
|
|
63
|
+
return this.connectingPromise;
|
|
66
64
|
}
|
|
65
|
+
this.connectingPromise = (async () => {
|
|
66
|
+
try {
|
|
67
|
+
await this.initMicrophone();
|
|
68
|
+
this.initPeerConnection();
|
|
69
|
+
this.initWebSocket();
|
|
70
|
+
} catch (error) {
|
|
71
|
+
this.emit("error", error);
|
|
72
|
+
throw error;
|
|
73
|
+
} finally {
|
|
74
|
+
this.connectingPromise = null;
|
|
75
|
+
}
|
|
76
|
+
})();
|
|
77
|
+
return this.connectingPromise;
|
|
67
78
|
}
|
|
68
79
|
async initMicrophone() {
|
|
69
80
|
this.localStream = await navigator.mediaDevices.getUserMedia({
|
|
@@ -81,7 +92,9 @@ var VoiceClient = class {
|
|
|
81
92
|
this.pc.addTrack(track, this.localStream);
|
|
82
93
|
});
|
|
83
94
|
this.pc.ontrack = (event) => {
|
|
84
|
-
|
|
95
|
+
const stream = event.streams?.[0] || new MediaStream([event.track]);
|
|
96
|
+
this.remoteStream = stream;
|
|
97
|
+
console.log("[VoiceClient] Remote track received", { track: event.track, stream });
|
|
85
98
|
this.emit("remoteStream", this.remoteStream);
|
|
86
99
|
};
|
|
87
100
|
this.pc.onicecandidate = (event) => {
|
|
@@ -105,43 +118,56 @@ var VoiceClient = class {
|
|
|
105
118
|
const url = `${this.signalingUrl}?userId=${this.userId}&roomId=${this.roomId}`;
|
|
106
119
|
this.ws = new WebSocket(url);
|
|
107
120
|
this.ws.onopen = () => {
|
|
121
|
+
console.log("[VoiceClient] WebSocket connected");
|
|
108
122
|
this.send({ type: "join", roomId: this.roomId, userId: this.userId });
|
|
109
123
|
};
|
|
110
124
|
this.ws.onmessage = async (event) => {
|
|
111
125
|
const message = JSON.parse(event.data);
|
|
112
126
|
await this.handleSignalingMessage(message);
|
|
113
127
|
};
|
|
128
|
+
this.ws.onerror = (event) => {
|
|
129
|
+
console.error("[VoiceClient] WebSocket error", event);
|
|
130
|
+
this.emit("error", new Error("WebSocket error"));
|
|
131
|
+
};
|
|
114
132
|
this.ws.onclose = () => {
|
|
133
|
+
console.log("[VoiceClient] WebSocket closed");
|
|
115
134
|
this.emit("disconnected");
|
|
116
135
|
};
|
|
117
136
|
}
|
|
118
137
|
async handleSignalingMessage(message) {
|
|
119
|
-
console.log("Received:", message.type, message);
|
|
138
|
+
console.log("[VoiceClient] Received message:", message.type, message);
|
|
120
139
|
switch (message.type) {
|
|
121
140
|
case "user-joined":
|
|
141
|
+
console.log("[VoiceClient] User joined, myId:", this.userId, "theirId:", message.userId);
|
|
122
142
|
if (message.userId !== this.userId) {
|
|
123
|
-
|
|
143
|
+
this.peers.add(message.userId);
|
|
144
|
+
console.log("[VoiceClient] Creating offer...");
|
|
124
145
|
this.targetUserId = message.userId;
|
|
125
146
|
await this.createOffer();
|
|
126
147
|
this.emit("userJoined", message.userId);
|
|
148
|
+
} else {
|
|
149
|
+
console.log("[VoiceClient] Ignoring my own join");
|
|
127
150
|
}
|
|
128
151
|
break;
|
|
129
152
|
case "offer":
|
|
130
|
-
console.log("Received offer
|
|
153
|
+
console.log("[VoiceClient] Received offer");
|
|
131
154
|
this.targetUserId = message.userId;
|
|
132
155
|
await this.handleOffer(message);
|
|
133
156
|
break;
|
|
134
157
|
case "answer":
|
|
135
|
-
console.log("Received answer
|
|
158
|
+
console.log("[VoiceClient] Received answer");
|
|
136
159
|
await this.handleAnswer(message);
|
|
137
160
|
break;
|
|
138
161
|
case "candidate":
|
|
139
|
-
console.log("Received candidate
|
|
162
|
+
console.log("[VoiceClient] Received candidate");
|
|
140
163
|
await this.handleCandidate(message);
|
|
141
164
|
break;
|
|
142
165
|
case "user-left":
|
|
166
|
+
this.peers.delete(message.userId);
|
|
143
167
|
this.emit("userLeft", message.userId);
|
|
144
168
|
break;
|
|
169
|
+
default:
|
|
170
|
+
console.log("[VoiceClient] Unknown message type:", message.type);
|
|
145
171
|
}
|
|
146
172
|
}
|
|
147
173
|
async createOffer() {
|
|
@@ -188,9 +214,12 @@ var VoiceClient = class {
|
|
|
188
214
|
send(data) {
|
|
189
215
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
190
216
|
this.ws.send(JSON.stringify(data));
|
|
217
|
+
} else {
|
|
218
|
+
console.warn("[VoiceClient] WebSocket is not open, cannot send message", data);
|
|
191
219
|
}
|
|
192
220
|
}
|
|
193
221
|
disconnect() {
|
|
222
|
+
this.peers.clear();
|
|
194
223
|
if (this.ws) {
|
|
195
224
|
this.send({ type: "leave", roomId: this.roomId, userId: this.userId });
|
|
196
225
|
this.ws.close();
|
|
@@ -202,6 +231,9 @@ var VoiceClient = class {
|
|
|
202
231
|
this.isConnectedFlag = false;
|
|
203
232
|
this.emit("disconnected");
|
|
204
233
|
}
|
|
234
|
+
getPeers() {
|
|
235
|
+
return Array.from(this.peers);
|
|
236
|
+
}
|
|
205
237
|
toggleMute() {
|
|
206
238
|
if (this.localStream) {
|
|
207
239
|
const audioTrack = this.localStream.getAudioTracks()[0];
|
package/dist/index.mjs
CHANGED
|
@@ -8,6 +8,8 @@ var VoiceClient = class {
|
|
|
8
8
|
this.eventHandlers = /* @__PURE__ */ new Map();
|
|
9
9
|
this.isConnectedFlag = false;
|
|
10
10
|
this.targetUserId = null;
|
|
11
|
+
this.connectingPromise = null;
|
|
12
|
+
this.peers = /* @__PURE__ */ new Set();
|
|
11
13
|
this.signalingUrl = config.signalingUrl;
|
|
12
14
|
this.roomId = config.roomId;
|
|
13
15
|
this.userId = config.userId;
|
|
@@ -15,7 +17,7 @@ var VoiceClient = class {
|
|
|
15
17
|
{ urls: "stun:stun.l.google.com:19302" }
|
|
16
18
|
];
|
|
17
19
|
if (config.autoConnect !== false) {
|
|
18
|
-
this.connect();
|
|
20
|
+
Promise.resolve().then(() => this.connect());
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
on(event, callback) {
|
|
@@ -31,13 +33,22 @@ var VoiceClient = class {
|
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
async connect() {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
this.initPeerConnection();
|
|
37
|
-
this.initWebSocket();
|
|
38
|
-
} catch (error) {
|
|
39
|
-
this.emit("error", error);
|
|
36
|
+
if (this.connectingPromise) {
|
|
37
|
+
return this.connectingPromise;
|
|
40
38
|
}
|
|
39
|
+
this.connectingPromise = (async () => {
|
|
40
|
+
try {
|
|
41
|
+
await this.initMicrophone();
|
|
42
|
+
this.initPeerConnection();
|
|
43
|
+
this.initWebSocket();
|
|
44
|
+
} catch (error) {
|
|
45
|
+
this.emit("error", error);
|
|
46
|
+
throw error;
|
|
47
|
+
} finally {
|
|
48
|
+
this.connectingPromise = null;
|
|
49
|
+
}
|
|
50
|
+
})();
|
|
51
|
+
return this.connectingPromise;
|
|
41
52
|
}
|
|
42
53
|
async initMicrophone() {
|
|
43
54
|
this.localStream = await navigator.mediaDevices.getUserMedia({
|
|
@@ -55,7 +66,9 @@ var VoiceClient = class {
|
|
|
55
66
|
this.pc.addTrack(track, this.localStream);
|
|
56
67
|
});
|
|
57
68
|
this.pc.ontrack = (event) => {
|
|
58
|
-
|
|
69
|
+
const stream = event.streams?.[0] || new MediaStream([event.track]);
|
|
70
|
+
this.remoteStream = stream;
|
|
71
|
+
console.log("[VoiceClient] Remote track received", { track: event.track, stream });
|
|
59
72
|
this.emit("remoteStream", this.remoteStream);
|
|
60
73
|
};
|
|
61
74
|
this.pc.onicecandidate = (event) => {
|
|
@@ -79,43 +92,56 @@ var VoiceClient = class {
|
|
|
79
92
|
const url = `${this.signalingUrl}?userId=${this.userId}&roomId=${this.roomId}`;
|
|
80
93
|
this.ws = new WebSocket(url);
|
|
81
94
|
this.ws.onopen = () => {
|
|
95
|
+
console.log("[VoiceClient] WebSocket connected");
|
|
82
96
|
this.send({ type: "join", roomId: this.roomId, userId: this.userId });
|
|
83
97
|
};
|
|
84
98
|
this.ws.onmessage = async (event) => {
|
|
85
99
|
const message = JSON.parse(event.data);
|
|
86
100
|
await this.handleSignalingMessage(message);
|
|
87
101
|
};
|
|
102
|
+
this.ws.onerror = (event) => {
|
|
103
|
+
console.error("[VoiceClient] WebSocket error", event);
|
|
104
|
+
this.emit("error", new Error("WebSocket error"));
|
|
105
|
+
};
|
|
88
106
|
this.ws.onclose = () => {
|
|
107
|
+
console.log("[VoiceClient] WebSocket closed");
|
|
89
108
|
this.emit("disconnected");
|
|
90
109
|
};
|
|
91
110
|
}
|
|
92
111
|
async handleSignalingMessage(message) {
|
|
93
|
-
console.log("Received:", message.type, message);
|
|
112
|
+
console.log("[VoiceClient] Received message:", message.type, message);
|
|
94
113
|
switch (message.type) {
|
|
95
114
|
case "user-joined":
|
|
115
|
+
console.log("[VoiceClient] User joined, myId:", this.userId, "theirId:", message.userId);
|
|
96
116
|
if (message.userId !== this.userId) {
|
|
97
|
-
|
|
117
|
+
this.peers.add(message.userId);
|
|
118
|
+
console.log("[VoiceClient] Creating offer...");
|
|
98
119
|
this.targetUserId = message.userId;
|
|
99
120
|
await this.createOffer();
|
|
100
121
|
this.emit("userJoined", message.userId);
|
|
122
|
+
} else {
|
|
123
|
+
console.log("[VoiceClient] Ignoring my own join");
|
|
101
124
|
}
|
|
102
125
|
break;
|
|
103
126
|
case "offer":
|
|
104
|
-
console.log("Received offer
|
|
127
|
+
console.log("[VoiceClient] Received offer");
|
|
105
128
|
this.targetUserId = message.userId;
|
|
106
129
|
await this.handleOffer(message);
|
|
107
130
|
break;
|
|
108
131
|
case "answer":
|
|
109
|
-
console.log("Received answer
|
|
132
|
+
console.log("[VoiceClient] Received answer");
|
|
110
133
|
await this.handleAnswer(message);
|
|
111
134
|
break;
|
|
112
135
|
case "candidate":
|
|
113
|
-
console.log("Received candidate
|
|
136
|
+
console.log("[VoiceClient] Received candidate");
|
|
114
137
|
await this.handleCandidate(message);
|
|
115
138
|
break;
|
|
116
139
|
case "user-left":
|
|
140
|
+
this.peers.delete(message.userId);
|
|
117
141
|
this.emit("userLeft", message.userId);
|
|
118
142
|
break;
|
|
143
|
+
default:
|
|
144
|
+
console.log("[VoiceClient] Unknown message type:", message.type);
|
|
119
145
|
}
|
|
120
146
|
}
|
|
121
147
|
async createOffer() {
|
|
@@ -162,9 +188,12 @@ var VoiceClient = class {
|
|
|
162
188
|
send(data) {
|
|
163
189
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
164
190
|
this.ws.send(JSON.stringify(data));
|
|
191
|
+
} else {
|
|
192
|
+
console.warn("[VoiceClient] WebSocket is not open, cannot send message", data);
|
|
165
193
|
}
|
|
166
194
|
}
|
|
167
195
|
disconnect() {
|
|
196
|
+
this.peers.clear();
|
|
168
197
|
if (this.ws) {
|
|
169
198
|
this.send({ type: "leave", roomId: this.roomId, userId: this.userId });
|
|
170
199
|
this.ws.close();
|
|
@@ -176,6 +205,9 @@ var VoiceClient = class {
|
|
|
176
205
|
this.isConnectedFlag = false;
|
|
177
206
|
this.emit("disconnected");
|
|
178
207
|
}
|
|
208
|
+
getPeers() {
|
|
209
|
+
return Array.from(this.peers);
|
|
210
|
+
}
|
|
179
211
|
toggleMute() {
|
|
180
212
|
if (this.localStream) {
|
|
181
213
|
const audioTrack = this.localStream.getAudioTracks()[0];
|
package/package.json
CHANGED
package/src/VoiceClient.ts
CHANGED
|
@@ -18,6 +18,8 @@ export class VoiceClient {
|
|
|
18
18
|
private iceServers: RTCIceServer[];
|
|
19
19
|
private isConnectedFlag = false;
|
|
20
20
|
private targetUserId: string | null = null;
|
|
21
|
+
private connectingPromise: Promise<void> | null = null;
|
|
22
|
+
private peers: Set<string> = new Set();
|
|
21
23
|
|
|
22
24
|
constructor(config: VoiceClientConfig) {
|
|
23
25
|
this.signalingUrl = config.signalingUrl;
|
|
@@ -28,7 +30,7 @@ export class VoiceClient {
|
|
|
28
30
|
];
|
|
29
31
|
|
|
30
32
|
if (config.autoConnect !== false) {
|
|
31
|
-
this.connect();
|
|
33
|
+
Promise.resolve().then(() => this.connect());
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -49,13 +51,24 @@ export class VoiceClient {
|
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
async connect(): Promise<void> {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
this.initPeerConnection();
|
|
55
|
-
this.initWebSocket();
|
|
56
|
-
} catch (error) {
|
|
57
|
-
this.emit('error', error);
|
|
54
|
+
if (this.connectingPromise) {
|
|
55
|
+
return this.connectingPromise;
|
|
58
56
|
}
|
|
57
|
+
|
|
58
|
+
this.connectingPromise = (async () => {
|
|
59
|
+
try {
|
|
60
|
+
await this.initMicrophone();
|
|
61
|
+
this.initPeerConnection();
|
|
62
|
+
this.initWebSocket();
|
|
63
|
+
} catch (error) {
|
|
64
|
+
this.emit('error', error);
|
|
65
|
+
throw error;
|
|
66
|
+
} finally {
|
|
67
|
+
this.connectingPromise = null;
|
|
68
|
+
}
|
|
69
|
+
})();
|
|
70
|
+
|
|
71
|
+
return this.connectingPromise;
|
|
59
72
|
}
|
|
60
73
|
|
|
61
74
|
private async initMicrophone(): Promise<void> {
|
|
@@ -77,7 +90,9 @@ export class VoiceClient {
|
|
|
77
90
|
});
|
|
78
91
|
|
|
79
92
|
this.pc.ontrack = (event) => {
|
|
80
|
-
|
|
93
|
+
const stream = event.streams?.[0] || new MediaStream([event.track]);
|
|
94
|
+
this.remoteStream = stream;
|
|
95
|
+
console.log('[VoiceClient] Remote track received', { track: event.track, stream });
|
|
81
96
|
this.emit('remoteStream', this.remoteStream);
|
|
82
97
|
};
|
|
83
98
|
|
|
@@ -105,6 +120,7 @@ export class VoiceClient {
|
|
|
105
120
|
this.ws = new WebSocket(url);
|
|
106
121
|
|
|
107
122
|
this.ws.onopen = () => {
|
|
123
|
+
console.log('[VoiceClient] WebSocket connected');
|
|
108
124
|
this.send({ type: 'join', roomId: this.roomId, userId: this.userId });
|
|
109
125
|
};
|
|
110
126
|
|
|
@@ -113,39 +129,52 @@ export class VoiceClient {
|
|
|
113
129
|
await this.handleSignalingMessage(message);
|
|
114
130
|
};
|
|
115
131
|
|
|
132
|
+
this.ws.onerror = (event) => {
|
|
133
|
+
console.error('[VoiceClient] WebSocket error', event);
|
|
134
|
+
this.emit('error', new Error('WebSocket error'));
|
|
135
|
+
};
|
|
136
|
+
|
|
116
137
|
this.ws.onclose = () => {
|
|
138
|
+
console.log('[VoiceClient] WebSocket closed');
|
|
117
139
|
this.emit('disconnected');
|
|
118
140
|
};
|
|
119
141
|
}
|
|
120
142
|
|
|
121
143
|
private async handleSignalingMessage(message: any): Promise<void> {
|
|
122
|
-
console.log('Received:', message.type, message);
|
|
144
|
+
console.log('[VoiceClient] Received message:', message.type, message);
|
|
123
145
|
|
|
124
146
|
switch (message.type) {
|
|
125
147
|
case 'user-joined':
|
|
148
|
+
console.log('[VoiceClient] User joined, myId:', this.userId, 'theirId:', message.userId);
|
|
126
149
|
if (message.userId !== this.userId) {
|
|
127
|
-
|
|
150
|
+
this.peers.add(message.userId);
|
|
151
|
+
console.log('[VoiceClient] Creating offer...');
|
|
128
152
|
this.targetUserId = message.userId;
|
|
129
153
|
await this.createOffer();
|
|
130
154
|
this.emit('userJoined', message.userId);
|
|
155
|
+
} else {
|
|
156
|
+
console.log('[VoiceClient] Ignoring my own join');
|
|
131
157
|
}
|
|
132
158
|
break;
|
|
133
159
|
case 'offer':
|
|
134
|
-
console.log('Received offer
|
|
160
|
+
console.log('[VoiceClient] Received offer');
|
|
135
161
|
this.targetUserId = message.userId;
|
|
136
162
|
await this.handleOffer(message);
|
|
137
163
|
break;
|
|
138
164
|
case 'answer':
|
|
139
|
-
console.log('Received answer
|
|
165
|
+
console.log('[VoiceClient] Received answer');
|
|
140
166
|
await this.handleAnswer(message);
|
|
141
167
|
break;
|
|
142
168
|
case 'candidate':
|
|
143
|
-
console.log('Received candidate
|
|
169
|
+
console.log('[VoiceClient] Received candidate');
|
|
144
170
|
await this.handleCandidate(message);
|
|
145
171
|
break;
|
|
146
172
|
case 'user-left':
|
|
173
|
+
this.peers.delete(message.userId);
|
|
147
174
|
this.emit('userLeft', message.userId);
|
|
148
175
|
break;
|
|
176
|
+
default:
|
|
177
|
+
console.log('[VoiceClient] Unknown message type:', message.type);
|
|
149
178
|
}
|
|
150
179
|
}
|
|
151
180
|
|
|
@@ -200,10 +229,13 @@ export class VoiceClient {
|
|
|
200
229
|
private send(data: any): void {
|
|
201
230
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
202
231
|
this.ws.send(JSON.stringify(data));
|
|
232
|
+
} else {
|
|
233
|
+
console.warn('[VoiceClient] WebSocket is not open, cannot send message', data);
|
|
203
234
|
}
|
|
204
235
|
}
|
|
205
236
|
|
|
206
237
|
disconnect(): void {
|
|
238
|
+
this.peers.clear();
|
|
207
239
|
if (this.ws) {
|
|
208
240
|
this.send({ type: 'leave', roomId: this.roomId, userId: this.userId });
|
|
209
241
|
this.ws.close();
|
|
@@ -216,6 +248,10 @@ export class VoiceClient {
|
|
|
216
248
|
this.emit('disconnected');
|
|
217
249
|
}
|
|
218
250
|
|
|
251
|
+
getPeers(): string[] {
|
|
252
|
+
return Array.from(this.peers);
|
|
253
|
+
}
|
|
254
|
+
|
|
219
255
|
toggleMute(): void {
|
|
220
256
|
if (this.localStream) {
|
|
221
257
|
const audioTrack = this.localStream.getAudioTracks()[0];
|