@voicemaster/core 1.0.5 → 1.0.7
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 +2 -12
- package/dist/index.d.ts +2 -12
- package/dist/index.js +18 -7
- package/dist/index.mjs +18 -7
- package/package.json +37 -38
- package/src/VoiceClient.ts +210 -209
- package/src/index.ts +2 -2
- package/tsconfig.json +15 -15
package/dist/index.d.mts
CHANGED
|
@@ -16,6 +16,7 @@ declare class VoiceClient {
|
|
|
16
16
|
private userId;
|
|
17
17
|
private iceServers;
|
|
18
18
|
private isConnectedFlag;
|
|
19
|
+
private targetUserId;
|
|
19
20
|
constructor(config: VoiceClientConfig);
|
|
20
21
|
on(event: string, callback: Function): void;
|
|
21
22
|
private emit;
|
|
@@ -34,16 +35,5 @@ declare class VoiceClient {
|
|
|
34
35
|
isMuted(): boolean;
|
|
35
36
|
getUserId(): string;
|
|
36
37
|
}
|
|
37
|
-
interface VoiceEvents {
|
|
38
|
-
connected: () => void;
|
|
39
|
-
disconnected: () => void;
|
|
40
|
-
remoteStream: (stream: MediaStream) => void;
|
|
41
|
-
localStream: (stream: MediaStream) => void;
|
|
42
|
-
error: (error: Error) => void;
|
|
43
|
-
userJoined: (userId: string) => void;
|
|
44
|
-
userLeft: (userId: string) => void;
|
|
45
|
-
speaking: (userId: string) => void;
|
|
46
|
-
stoppedSpeaking: (userId: string) => void;
|
|
47
|
-
}
|
|
48
38
|
|
|
49
|
-
export { VoiceClient, type VoiceClientConfig
|
|
39
|
+
export { VoiceClient, type VoiceClientConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ declare class VoiceClient {
|
|
|
16
16
|
private userId;
|
|
17
17
|
private iceServers;
|
|
18
18
|
private isConnectedFlag;
|
|
19
|
+
private targetUserId;
|
|
19
20
|
constructor(config: VoiceClientConfig);
|
|
20
21
|
on(event: string, callback: Function): void;
|
|
21
22
|
private emit;
|
|
@@ -34,16 +35,5 @@ declare class VoiceClient {
|
|
|
34
35
|
isMuted(): boolean;
|
|
35
36
|
getUserId(): string;
|
|
36
37
|
}
|
|
37
|
-
interface VoiceEvents {
|
|
38
|
-
connected: () => void;
|
|
39
|
-
disconnected: () => void;
|
|
40
|
-
remoteStream: (stream: MediaStream) => void;
|
|
41
|
-
localStream: (stream: MediaStream) => void;
|
|
42
|
-
error: (error: Error) => void;
|
|
43
|
-
userJoined: (userId: string) => void;
|
|
44
|
-
userLeft: (userId: string) => void;
|
|
45
|
-
speaking: (userId: string) => void;
|
|
46
|
-
stoppedSpeaking: (userId: string) => void;
|
|
47
|
-
}
|
|
48
38
|
|
|
49
|
-
export { VoiceClient, type VoiceClientConfig
|
|
39
|
+
export { VoiceClient, type VoiceClientConfig };
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ var VoiceClient = class {
|
|
|
33
33
|
this.remoteStream = null;
|
|
34
34
|
this.eventHandlers = /* @__PURE__ */ new Map();
|
|
35
35
|
this.isConnectedFlag = false;
|
|
36
|
+
this.targetUserId = null;
|
|
36
37
|
this.signalingUrl = config.signalingUrl;
|
|
37
38
|
this.roomId = config.roomId;
|
|
38
39
|
this.userId = config.userId;
|
|
@@ -82,10 +83,6 @@ var VoiceClient = class {
|
|
|
82
83
|
this.pc.ontrack = (event) => {
|
|
83
84
|
this.remoteStream = event.streams[0];
|
|
84
85
|
this.emit("remoteStream", this.remoteStream);
|
|
85
|
-
if (!this.isConnectedFlag) {
|
|
86
|
-
this.isConnectedFlag = true;
|
|
87
|
-
this.emit("connected");
|
|
88
|
-
}
|
|
89
86
|
};
|
|
90
87
|
this.pc.onicecandidate = (event) => {
|
|
91
88
|
if (event.candidate) {
|
|
@@ -95,6 +92,14 @@ var VoiceClient = class {
|
|
|
95
92
|
});
|
|
96
93
|
}
|
|
97
94
|
};
|
|
95
|
+
this.pc.onconnectionstatechange = () => {
|
|
96
|
+
if (this.pc?.connectionState === "connected") {
|
|
97
|
+
if (!this.isConnectedFlag) {
|
|
98
|
+
this.isConnectedFlag = true;
|
|
99
|
+
this.emit("connected");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
98
103
|
}
|
|
99
104
|
initWebSocket() {
|
|
100
105
|
const url = `${this.signalingUrl}?userId=${this.userId}&roomId=${this.roomId}`;
|
|
@@ -114,11 +119,13 @@ var VoiceClient = class {
|
|
|
114
119
|
switch (message.type) {
|
|
115
120
|
case "user-joined":
|
|
116
121
|
if (message.userId !== this.userId) {
|
|
122
|
+
this.targetUserId = message.userId;
|
|
117
123
|
await this.createOffer();
|
|
118
124
|
this.emit("userJoined", message.userId);
|
|
119
125
|
}
|
|
120
126
|
break;
|
|
121
127
|
case "offer":
|
|
128
|
+
this.targetUserId = message.userId;
|
|
122
129
|
await this.handleOffer(message);
|
|
123
130
|
break;
|
|
124
131
|
case "answer":
|
|
@@ -155,8 +162,10 @@ var VoiceClient = class {
|
|
|
155
162
|
await this.pc.setRemoteDescription(answer);
|
|
156
163
|
}
|
|
157
164
|
async handleCandidate(message) {
|
|
158
|
-
|
|
159
|
-
|
|
165
|
+
if (message.candidate) {
|
|
166
|
+
const candidate = new RTCIceCandidate(message.candidate);
|
|
167
|
+
await this.pc.addIceCandidate(candidate);
|
|
168
|
+
}
|
|
160
169
|
}
|
|
161
170
|
send(data) {
|
|
162
171
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
@@ -169,7 +178,9 @@ var VoiceClient = class {
|
|
|
169
178
|
this.ws.close();
|
|
170
179
|
}
|
|
171
180
|
this.pc?.close();
|
|
172
|
-
this.localStream
|
|
181
|
+
if (this.localStream) {
|
|
182
|
+
this.localStream.getTracks().forEach((track) => track.stop());
|
|
183
|
+
}
|
|
173
184
|
this.isConnectedFlag = false;
|
|
174
185
|
this.emit("disconnected");
|
|
175
186
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -7,6 +7,7 @@ var VoiceClient = class {
|
|
|
7
7
|
this.remoteStream = null;
|
|
8
8
|
this.eventHandlers = /* @__PURE__ */ new Map();
|
|
9
9
|
this.isConnectedFlag = false;
|
|
10
|
+
this.targetUserId = null;
|
|
10
11
|
this.signalingUrl = config.signalingUrl;
|
|
11
12
|
this.roomId = config.roomId;
|
|
12
13
|
this.userId = config.userId;
|
|
@@ -56,10 +57,6 @@ var VoiceClient = class {
|
|
|
56
57
|
this.pc.ontrack = (event) => {
|
|
57
58
|
this.remoteStream = event.streams[0];
|
|
58
59
|
this.emit("remoteStream", this.remoteStream);
|
|
59
|
-
if (!this.isConnectedFlag) {
|
|
60
|
-
this.isConnectedFlag = true;
|
|
61
|
-
this.emit("connected");
|
|
62
|
-
}
|
|
63
60
|
};
|
|
64
61
|
this.pc.onicecandidate = (event) => {
|
|
65
62
|
if (event.candidate) {
|
|
@@ -69,6 +66,14 @@ var VoiceClient = class {
|
|
|
69
66
|
});
|
|
70
67
|
}
|
|
71
68
|
};
|
|
69
|
+
this.pc.onconnectionstatechange = () => {
|
|
70
|
+
if (this.pc?.connectionState === "connected") {
|
|
71
|
+
if (!this.isConnectedFlag) {
|
|
72
|
+
this.isConnectedFlag = true;
|
|
73
|
+
this.emit("connected");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
72
77
|
}
|
|
73
78
|
initWebSocket() {
|
|
74
79
|
const url = `${this.signalingUrl}?userId=${this.userId}&roomId=${this.roomId}`;
|
|
@@ -88,11 +93,13 @@ var VoiceClient = class {
|
|
|
88
93
|
switch (message.type) {
|
|
89
94
|
case "user-joined":
|
|
90
95
|
if (message.userId !== this.userId) {
|
|
96
|
+
this.targetUserId = message.userId;
|
|
91
97
|
await this.createOffer();
|
|
92
98
|
this.emit("userJoined", message.userId);
|
|
93
99
|
}
|
|
94
100
|
break;
|
|
95
101
|
case "offer":
|
|
102
|
+
this.targetUserId = message.userId;
|
|
96
103
|
await this.handleOffer(message);
|
|
97
104
|
break;
|
|
98
105
|
case "answer":
|
|
@@ -129,8 +136,10 @@ var VoiceClient = class {
|
|
|
129
136
|
await this.pc.setRemoteDescription(answer);
|
|
130
137
|
}
|
|
131
138
|
async handleCandidate(message) {
|
|
132
|
-
|
|
133
|
-
|
|
139
|
+
if (message.candidate) {
|
|
140
|
+
const candidate = new RTCIceCandidate(message.candidate);
|
|
141
|
+
await this.pc.addIceCandidate(candidate);
|
|
142
|
+
}
|
|
134
143
|
}
|
|
135
144
|
send(data) {
|
|
136
145
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
@@ -143,7 +152,9 @@ var VoiceClient = class {
|
|
|
143
152
|
this.ws.close();
|
|
144
153
|
}
|
|
145
154
|
this.pc?.close();
|
|
146
|
-
this.localStream
|
|
155
|
+
if (this.localStream) {
|
|
156
|
+
this.localStream.getTracks().forEach((track) => track.stop());
|
|
157
|
+
}
|
|
147
158
|
this.isConnectedFlag = false;
|
|
148
159
|
this.emit("disconnected");
|
|
149
160
|
}
|
package/package.json
CHANGED
|
@@ -1,38 +1,37 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@voicemaster/core",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "WebRTC voice communication core library",
|
|
5
|
-
"main": "./dist/index.js",
|
|
6
|
-
"module": "./dist/index.mjs",
|
|
7
|
-
"types": "./dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"import": "./dist/index.mjs",
|
|
12
|
-
"require": "./dist/index.js"
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
17
|
-
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
18
|
-
"prepublishOnly": "npm run build"
|
|
19
|
-
},
|
|
20
|
-
"keywords": [
|
|
21
|
-
"webrtc",
|
|
22
|
-
"voice",
|
|
23
|
-
"audio",
|
|
24
|
-
"p2p",
|
|
25
|
-
"typescript"
|
|
26
|
-
],
|
|
27
|
-
"author": "Sergey Minasyan",
|
|
28
|
-
"license": "MIT",
|
|
29
|
-
"dependencies": {
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@voicemaster/core",
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"description": "WebRTC voice communication core library",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
17
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"webrtc",
|
|
22
|
+
"voice",
|
|
23
|
+
"audio",
|
|
24
|
+
"p2p",
|
|
25
|
+
"typescript"
|
|
26
|
+
],
|
|
27
|
+
"author": "Sergey Minasyan",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"simple-peer": "^9.11.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/simple-peer": "^9.11.8",
|
|
34
|
+
"tsup": "^8.0.0",
|
|
35
|
+
"typescript": "^5.3.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/VoiceClient.ts
CHANGED
|
@@ -1,210 +1,211 @@
|
|
|
1
|
-
export interface VoiceClientConfig {
|
|
2
|
-
signalingUrl: string;
|
|
3
|
-
roomId: string;
|
|
4
|
-
userId: string;
|
|
5
|
-
autoConnect?: boolean;
|
|
6
|
-
iceServers?: RTCIceServer[];
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export class VoiceClient {
|
|
10
|
-
private ws: WebSocket | null = null;
|
|
11
|
-
private pc: RTCPeerConnection | null = null;
|
|
12
|
-
private localStream: MediaStream | null = null;
|
|
13
|
-
private remoteStream: MediaStream | null = null;
|
|
14
|
-
private eventHandlers: Map<string, Function[]> = new Map();
|
|
15
|
-
private signalingUrl: string;
|
|
16
|
-
private roomId: string;
|
|
17
|
-
private userId: string;
|
|
18
|
-
private iceServers: RTCIceServer[];
|
|
19
|
-
private isConnectedFlag = false;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
25
|
-
this.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
this.
|
|
52
|
-
this.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
this.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
this.ws
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
case '
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
this.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
1
|
+
export interface VoiceClientConfig {
|
|
2
|
+
signalingUrl: string;
|
|
3
|
+
roomId: string;
|
|
4
|
+
userId: string;
|
|
5
|
+
autoConnect?: boolean;
|
|
6
|
+
iceServers?: RTCIceServer[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class VoiceClient {
|
|
10
|
+
private ws: WebSocket | null = null;
|
|
11
|
+
private pc: RTCPeerConnection | null = null;
|
|
12
|
+
private localStream: MediaStream | null = null;
|
|
13
|
+
private remoteStream: MediaStream | null = null;
|
|
14
|
+
private eventHandlers: Map<string, Function[]> = new Map();
|
|
15
|
+
private signalingUrl: string;
|
|
16
|
+
private roomId: string;
|
|
17
|
+
private userId: string;
|
|
18
|
+
private iceServers: RTCIceServer[];
|
|
19
|
+
private isConnectedFlag = false;
|
|
20
|
+
private targetUserId: string | null = null;
|
|
21
|
+
|
|
22
|
+
constructor(config: VoiceClientConfig) {
|
|
23
|
+
this.signalingUrl = config.signalingUrl;
|
|
24
|
+
this.roomId = config.roomId;
|
|
25
|
+
this.userId = config.userId;
|
|
26
|
+
this.iceServers = config.iceServers || [
|
|
27
|
+
{ urls: 'stun:stun.l.google.com:19302' }
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
if (config.autoConnect !== false) {
|
|
31
|
+
this.connect();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
on(event: string, callback: Function): void {
|
|
36
|
+
if (!this.eventHandlers.has(event)) {
|
|
37
|
+
this.eventHandlers.set(event, []);
|
|
38
|
+
}
|
|
39
|
+
this.eventHandlers.get(event)!.push(callback);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private emit(event: string, ...args: any[]): void {
|
|
43
|
+
const handlers = this.eventHandlers.get(event);
|
|
44
|
+
if (handlers) {
|
|
45
|
+
handlers.forEach(handler => handler(...args));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async connect(): Promise<void> {
|
|
50
|
+
try {
|
|
51
|
+
await this.initMicrophone();
|
|
52
|
+
this.initPeerConnection();
|
|
53
|
+
this.initWebSocket();
|
|
54
|
+
} catch (error) {
|
|
55
|
+
this.emit('error', error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private async initMicrophone(): Promise<void> {
|
|
60
|
+
this.localStream = await navigator.mediaDevices.getUserMedia({
|
|
61
|
+
audio: {
|
|
62
|
+
echoCancellation: true,
|
|
63
|
+
noiseSuppression: true,
|
|
64
|
+
autoGainControl: true
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
this.emit('localStream', this.localStream);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private initPeerConnection(): void {
|
|
71
|
+
this.pc = new RTCPeerConnection({ iceServers: this.iceServers });
|
|
72
|
+
|
|
73
|
+
this.localStream?.getTracks().forEach(track => {
|
|
74
|
+
this.pc!.addTrack(track, this.localStream!);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
this.pc.ontrack = (event) => {
|
|
78
|
+
this.remoteStream = event.streams[0];
|
|
79
|
+
this.emit('remoteStream', this.remoteStream);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
this.pc.onicecandidate = (event) => {
|
|
83
|
+
if (event.candidate) {
|
|
84
|
+
this.send({
|
|
85
|
+
type: 'candidate',
|
|
86
|
+
candidate: event.candidate
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
this.pc.onconnectionstatechange = () => {
|
|
92
|
+
if (this.pc?.connectionState === 'connected') {
|
|
93
|
+
if (!this.isConnectedFlag) {
|
|
94
|
+
this.isConnectedFlag = true;
|
|
95
|
+
this.emit('connected');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private initWebSocket(): void {
|
|
102
|
+
const url = `${this.signalingUrl}?userId=${this.userId}&roomId=${this.roomId}`;
|
|
103
|
+
this.ws = new WebSocket(url);
|
|
104
|
+
|
|
105
|
+
this.ws.onopen = () => {
|
|
106
|
+
this.send({ type: 'join', roomId: this.roomId, userId: this.userId });
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
this.ws.onmessage = async (event) => {
|
|
110
|
+
const message = JSON.parse(event.data);
|
|
111
|
+
await this.handleSignalingMessage(message);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
this.ws.onclose = () => {
|
|
115
|
+
this.emit('disconnected');
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private async handleSignalingMessage(message: any): Promise<void> {
|
|
120
|
+
switch (message.type) {
|
|
121
|
+
case 'user-joined':
|
|
122
|
+
if (message.userId !== this.userId) {
|
|
123
|
+
this.targetUserId = message.userId;
|
|
124
|
+
await this.createOffer();
|
|
125
|
+
this.emit('userJoined', message.userId);
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
case 'offer':
|
|
129
|
+
this.targetUserId = message.userId;
|
|
130
|
+
await this.handleOffer(message);
|
|
131
|
+
break;
|
|
132
|
+
case 'answer':
|
|
133
|
+
await this.handleAnswer(message);
|
|
134
|
+
break;
|
|
135
|
+
case 'candidate':
|
|
136
|
+
await this.handleCandidate(message);
|
|
137
|
+
break;
|
|
138
|
+
case 'user-left':
|
|
139
|
+
this.emit('userLeft', message.userId);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private async createOffer(): Promise<void> {
|
|
145
|
+
const offer = await this.pc!.createOffer();
|
|
146
|
+
await this.pc!.setLocalDescription(offer);
|
|
147
|
+
this.send({
|
|
148
|
+
type: 'offer',
|
|
149
|
+
sdp: this.pc!.localDescription
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private async handleOffer(message: any): Promise<void> {
|
|
154
|
+
const offer = new RTCSessionDescription(message.sdp);
|
|
155
|
+
await this.pc!.setRemoteDescription(offer);
|
|
156
|
+
const answer = await this.pc!.createAnswer();
|
|
157
|
+
await this.pc!.setLocalDescription(answer);
|
|
158
|
+
this.send({
|
|
159
|
+
type: 'answer',
|
|
160
|
+
sdp: this.pc!.localDescription
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private async handleAnswer(message: any): Promise<void> {
|
|
165
|
+
const answer = new RTCSessionDescription(message.sdp);
|
|
166
|
+
await this.pc!.setRemoteDescription(answer);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private async handleCandidate(message: any): Promise<void> {
|
|
170
|
+
if (message.candidate) {
|
|
171
|
+
const candidate = new RTCIceCandidate(message.candidate);
|
|
172
|
+
await this.pc!.addIceCandidate(candidate);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private send(data: any): void {
|
|
177
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
178
|
+
this.ws.send(JSON.stringify(data));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
disconnect(): void {
|
|
183
|
+
if (this.ws) {
|
|
184
|
+
this.send({ type: 'leave', roomId: this.roomId, userId: this.userId });
|
|
185
|
+
this.ws.close();
|
|
186
|
+
}
|
|
187
|
+
this.pc?.close();
|
|
188
|
+
if (this.localStream) {
|
|
189
|
+
this.localStream.getTracks().forEach(track => track.stop());
|
|
190
|
+
}
|
|
191
|
+
this.isConnectedFlag = false;
|
|
192
|
+
this.emit('disconnected');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
toggleMute(): void {
|
|
196
|
+
if (this.localStream) {
|
|
197
|
+
const audioTrack = this.localStream.getAudioTracks()[0];
|
|
198
|
+
if (audioTrack) {
|
|
199
|
+
audioTrack.enabled = !audioTrack.enabled;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
isMuted(): boolean {
|
|
205
|
+
return this.localStream?.getAudioTracks()[0]?.enabled === false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
getUserId(): string {
|
|
209
|
+
return this.userId;
|
|
210
|
+
}
|
|
210
211
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { VoiceClient } from './VoiceClient';
|
|
2
|
-
export type { VoiceClientConfig
|
|
1
|
+
export { VoiceClient } from './VoiceClient';
|
|
2
|
+
export type { VoiceClientConfig } from './VoiceClient';
|
package/tsconfig.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"lib": ["ES2020", "DOM"],
|
|
6
|
-
"moduleResolution": "bundler",
|
|
7
|
-
"strict": true,
|
|
8
|
-
"esModuleInterop": true,
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
"forceConsistentCasingInFileNames": true,
|
|
11
|
-
"resolveJsonModule": true,
|
|
12
|
-
"noEmit": true
|
|
13
|
-
},
|
|
14
|
-
"include": ["src/**/*"],
|
|
15
|
-
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ES2020", "DOM"],
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"noEmit": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
16
16
|
}
|