videocall-client-socket 0.1.0 β 0.1.2
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 +124 -0
- package/package.json +38 -21
- package/src/index.d.ts +1 -1
- package/src/index.js +94 -42
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# π videocall-client-socket
|
|
2
|
+
|
|
3
|
+
A simple WebRTC client library for peer-to-peer video calls using [simple-peer](https://www.npmjs.com/package/simple-peer) and [socket.io-client](https://www.npmjs.com/package/socket.io-client).
|
|
4
|
+
|
|
5
|
+
> β
Designed to work with [videocall-server](https://github.com/EmersonJaraG28/videocall-server)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## π¦ Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install videocall-client-socket
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## π Quick Start
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
import * as VideoClient from "videocall-client-socket";
|
|
21
|
+
import { v4 as uuidv4 } from "uuid";
|
|
22
|
+
|
|
23
|
+
const userId = uuidv4();
|
|
24
|
+
const roomName = "roomABC";
|
|
25
|
+
|
|
26
|
+
// Step 1: Subscribe to events before anything else
|
|
27
|
+
VideoClient.on("user-published", handleUserPublished);
|
|
28
|
+
VideoClient.on("user-unpublished", handleUserUnpublished);
|
|
29
|
+
VideoClient.on("user-media-toggled", (data) => {
|
|
30
|
+
console.log(data.user.uuid, data.type, data.enabled);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Step 2: Create media stream
|
|
34
|
+
await VideoClient.createMediaStream();
|
|
35
|
+
|
|
36
|
+
// Step 3: Play local stream in <video> tag
|
|
37
|
+
VideoClient.playVideoTrack("localVideo");
|
|
38
|
+
|
|
39
|
+
// Step 4: Join the signaling channel
|
|
40
|
+
VideoClient.setServerURL("http://localhost:3000");
|
|
41
|
+
VideoClient.joinChannel(userId, roomName);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## π Execution Order
|
|
47
|
+
|
|
48
|
+
> To use this library correctly, follow this sequence of steps:
|
|
49
|
+
|
|
50
|
+
| Step | Function | Why? |
|
|
51
|
+
| ---- | -------------------------------- | -------------------------------------------------------------- |
|
|
52
|
+
| 1οΈβ£ | `on(...)` | You must subscribe to events **before** joining the channel. |
|
|
53
|
+
| 2οΈβ£ | `createMediaStream()` | This requests access to camera and microphone. |
|
|
54
|
+
| 3οΈβ£ | `playVideoTrack(videoElementId)` | This shows your local stream in a `<video>` tag. |
|
|
55
|
+
| 4οΈβ£ | `joinChannel(userId, room)` | Finally, join the signaling server and start peer connections. |
|
|
56
|
+
|
|
57
|
+
> π§ Skipping or reordering these steps may cause video/audio to not work properly or event listeners to be missed.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## π API Reference
|
|
62
|
+
|
|
63
|
+
### π Connection
|
|
64
|
+
|
|
65
|
+
- `setServerURL(url: string): void`
|
|
66
|
+
- `joinChannel(userId: string, room: string): void`
|
|
67
|
+
- `leaveChannel(): void`
|
|
68
|
+
|
|
69
|
+
### π₯ Media Controls
|
|
70
|
+
|
|
71
|
+
- `createMediaStream(): Promise<MediaStream>`
|
|
72
|
+
- `playVideoTrack(localVideoId: string): void`
|
|
73
|
+
- `toggleCamera(on: boolean): void`
|
|
74
|
+
- `isCameraOn(): boolean`
|
|
75
|
+
- `toggleAudio(on: boolean): void`
|
|
76
|
+
- `isAudioOn(): boolean`
|
|
77
|
+
|
|
78
|
+
### π‘ Events System
|
|
79
|
+
|
|
80
|
+
Use `on(event, callback)` and `off(event, callback)`.
|
|
81
|
+
|
|
82
|
+
- `user-published`: `{ user: { uuid, videotrack }, mediaType }`
|
|
83
|
+
- `user-unpublished`: `{ user: { uuid }, mediaType }`
|
|
84
|
+
- `user-media-toggled`: `{ user: { uuid }, type, enabled }`
|
|
85
|
+
|
|
86
|
+
#### Example:
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
VideoClient.on("user-published", ({ user }) => {
|
|
90
|
+
const video = document.createElement("video");
|
|
91
|
+
video.srcObject = user.videotrack;
|
|
92
|
+
video.autoplay = true;
|
|
93
|
+
video.id = user.uuid;
|
|
94
|
+
document.body.appendChild(video);
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## π§© Backend Integration
|
|
101
|
+
|
|
102
|
+
This library is designed to work with the following signaling server:
|
|
103
|
+
|
|
104
|
+
π [`videocall-server`](https://github.com/EmersonJaraG28/videocall-server)
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
git clone https://github.com/EmersonJaraG28/videocall-server.git
|
|
108
|
+
cd videocall-server
|
|
109
|
+
npm install
|
|
110
|
+
npm run dev
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## π Related Libraries
|
|
116
|
+
|
|
117
|
+
- [simple-peer](https://www.npmjs.com/package/simple-peer)
|
|
118
|
+
- [socket.io-client](https://www.npmjs.com/package/socket.io-client)
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## π§Ύ License
|
|
123
|
+
|
|
124
|
+
ISC Β© [Jara](https://github.com/EmersonJaraG28)
|
package/package.json
CHANGED
|
@@ -1,21 +1,38 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "videocall-client-socket",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"main": "src/index.js",
|
|
5
|
-
"types": "src/index.d.ts",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
-
},
|
|
9
|
-
"keywords": [
|
|
10
|
-
"video",
|
|
11
|
-
"socket",
|
|
12
|
-
"peer"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "videocall-client-socket",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"main": "src/index.js",
|
|
5
|
+
"types": "src/index.d.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"video",
|
|
11
|
+
"socket",
|
|
12
|
+
"peer",
|
|
13
|
+
"webrtc",
|
|
14
|
+
"rtc",
|
|
15
|
+
"videocall",
|
|
16
|
+
"p2p",
|
|
17
|
+
"simple-peer"
|
|
18
|
+
],
|
|
19
|
+
"author": {
|
|
20
|
+
"name": "Jara",
|
|
21
|
+
"url": "https://github.com/EmersonJaraG28"
|
|
22
|
+
},
|
|
23
|
+
"license": "ISC",
|
|
24
|
+
"description": "WebRTC client for peer-to-peer video calls using socket.io and simple-peer.",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/EmersonJaraG28/videocall-client.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/EmersonJaraG28/videocall-client/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/EmersonJaraG28/videocall-client#readme",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"socket.io-client": "^4.8.1",
|
|
35
|
+
"videocall-client": "file:",
|
|
36
|
+
"videocall-client-socket": "file:"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
declare module 'videocall-client-socket' {
|
|
2
2
|
|
|
3
|
-
export function
|
|
3
|
+
export function setServerURL(url: string): void;
|
|
4
4
|
|
|
5
5
|
export function on(event: string, callback: (data: any) => void): void;
|
|
6
6
|
export function off(event: string, callback: (data: any) => void): void;
|
package/src/index.js
CHANGED
|
@@ -1,60 +1,81 @@
|
|
|
1
|
+
import { io } from 'socket.io-client';
|
|
2
|
+
import SimplePeer from 'simple-peer';
|
|
1
3
|
|
|
2
4
|
var peers = {};
|
|
3
|
-
|
|
4
5
|
var socket = null;
|
|
5
6
|
var mediastream = null;
|
|
6
7
|
var localVideoId = "";
|
|
7
|
-
|
|
8
|
+
var userId = "";
|
|
8
9
|
var url = 'http://localhost:3000';
|
|
9
|
-
|
|
10
10
|
let events = {};
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Registers a custom event listener.
|
|
14
|
+
* @param {string} _event - Event name.
|
|
15
|
+
* @param {(data: any) => void} listener - Callback to execute when event occurs.
|
|
16
|
+
*/
|
|
12
17
|
export var on = (_event, listener) => {
|
|
13
18
|
if (!events[_event]) {
|
|
14
19
|
events[_event] = [];
|
|
15
20
|
}
|
|
16
21
|
events[_event].push(listener);
|
|
17
|
-
}
|
|
22
|
+
};
|
|
18
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Removes a listener for a specific custom event.
|
|
26
|
+
* @param {string} _event - Event name.
|
|
27
|
+
* @param {(data: any) => void} listener - Callback to remove.
|
|
28
|
+
*/
|
|
19
29
|
export var off = (_event, listener) => {
|
|
20
30
|
if (events[_event]) {
|
|
21
|
-
events[_event] = events[_event].filter((existingListener) =>
|
|
22
|
-
return existingListener !== listener;
|
|
23
|
-
});
|
|
31
|
+
events[_event] = events[_event].filter((existingListener) => existingListener !== listener);
|
|
24
32
|
}
|
|
25
|
-
}
|
|
33
|
+
};
|
|
26
34
|
|
|
27
35
|
let emit = (_event, data) => {
|
|
28
36
|
if (events[_event]) {
|
|
29
|
-
events[_event].forEach((listener) =>
|
|
30
|
-
listener(data);
|
|
31
|
-
});
|
|
37
|
+
events[_event].forEach((listener) => listener(data));
|
|
32
38
|
}
|
|
33
|
-
}
|
|
39
|
+
};
|
|
34
40
|
|
|
35
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Sets the signaling server URL.
|
|
43
|
+
* @param {string} _url - Server URL (e.g. "http://localhost:3000").
|
|
44
|
+
*/
|
|
45
|
+
export function setServerURL(_url) {
|
|
36
46
|
url = _url;
|
|
37
47
|
}
|
|
38
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Requests access to the user's camera and microphone.
|
|
51
|
+
* @returns {Promise<MediaStream>} - Resolves with a MediaStream containing audio and video.
|
|
52
|
+
*/
|
|
39
53
|
export function createMediaStream() {
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
resolve(stream);
|
|
44
|
-
});
|
|
54
|
+
return navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(stream => {
|
|
55
|
+
mediastream = stream;
|
|
56
|
+
return stream;
|
|
45
57
|
});
|
|
46
58
|
}
|
|
47
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Plays the local video stream into a specified <video> element.
|
|
62
|
+
* @param {string} _localVideoId - The ID of the <video> element in the DOM.
|
|
63
|
+
*/
|
|
48
64
|
export function playVideoTrack(_localVideoId) {
|
|
49
65
|
localVideoId = _localVideoId;
|
|
50
66
|
const localVideo = document.getElementById(localVideoId);
|
|
51
67
|
if (localVideo && mediastream) {
|
|
52
68
|
localVideo.srcObject = mediastream;
|
|
53
69
|
}
|
|
54
|
-
|
|
55
70
|
}
|
|
56
71
|
|
|
57
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Connects to the signaling server and joins a room.
|
|
74
|
+
* @param {string} _userId - Unique identifier for the user.
|
|
75
|
+
* @param {string} channelName - The name of the room to join.
|
|
76
|
+
*/
|
|
77
|
+
export function joinChannel(_userId, channelName) {
|
|
78
|
+
userId = _userId;
|
|
58
79
|
|
|
59
80
|
socket = io(url, {
|
|
60
81
|
query: { room: channelName, userId }
|
|
@@ -68,15 +89,11 @@ export function joinChannel(userId, channelName) {
|
|
|
68
89
|
});
|
|
69
90
|
|
|
70
91
|
socket.on('user-joined', ({ userId: newUserId, socketId: newSocketId }) => {
|
|
71
|
-
console.log("newUserId", newUserId);
|
|
72
|
-
|
|
73
92
|
const peer = createPeer(socket, mediastream, newSocketId, newUserId, false);
|
|
74
93
|
peers[newUserId] = peer;
|
|
75
94
|
});
|
|
76
95
|
|
|
77
96
|
socket.on('signal', ({ fromUserId, signal }) => {
|
|
78
|
-
console.log("signal", signal);
|
|
79
|
-
|
|
80
97
|
const peer = peers[fromUserId];
|
|
81
98
|
if (peer) {
|
|
82
99
|
peer.signal(signal);
|
|
@@ -87,20 +104,27 @@ export function joinChannel(userId, channelName) {
|
|
|
87
104
|
if (peers[leftUserId]) {
|
|
88
105
|
peers[leftUserId].destroy();
|
|
89
106
|
delete peers[leftUserId];
|
|
90
|
-
console.log(`Usuario ${leftUserId} se fue`);
|
|
91
107
|
}
|
|
92
108
|
|
|
93
|
-
|
|
94
|
-
user: {
|
|
95
|
-
uuid: leftUserId,
|
|
96
|
-
// videotrack: stream
|
|
97
|
-
},
|
|
109
|
+
emit("user-unpublished", {
|
|
110
|
+
user: { uuid: leftUserId },
|
|
98
111
|
mediaType: "video"
|
|
99
|
-
};
|
|
100
|
-
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
socket.on('user-media-toggled', ({ userId, type, enabled }) => {
|
|
116
|
+
emit('user-media-toggled', {
|
|
117
|
+
user: { uuid: userId },
|
|
118
|
+
type,
|
|
119
|
+
enabled
|
|
120
|
+
});
|
|
101
121
|
});
|
|
102
122
|
}
|
|
103
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Leaves the current room and disconnects all peers.
|
|
126
|
+
* Also releases camera/mic resources and clears video streams.
|
|
127
|
+
*/
|
|
104
128
|
export function leaveChannel() {
|
|
105
129
|
for (const peerId in peers) {
|
|
106
130
|
peers[peerId].destroy();
|
|
@@ -121,10 +145,20 @@ export function leaveChannel() {
|
|
|
121
145
|
}
|
|
122
146
|
|
|
123
147
|
const localVideo = document.getElementById(localVideoId);
|
|
124
|
-
localVideo
|
|
148
|
+
if (localVideo) {
|
|
149
|
+
localVideo.srcObject = null;
|
|
150
|
+
}
|
|
125
151
|
}
|
|
126
152
|
|
|
127
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Creates a WebRTC peer connection with another user.
|
|
155
|
+
* @param {import('socket.io-client').Socket} socket - The local user's socket.
|
|
156
|
+
* @param {MediaStream} localStream - The local media stream.
|
|
157
|
+
* @param {string} targetSocketId - The socket ID of the remote user.
|
|
158
|
+
* @param {string} remoteUserId - The ID of the remote user.
|
|
159
|
+
* @param {boolean} initiator - Whether this peer initiates the connection.
|
|
160
|
+
* @returns {SimplePeer.Instance} - The created peer.
|
|
161
|
+
*/
|
|
128
162
|
function createPeer(socket, localStream, targetSocketId, remoteUserId, initiator) {
|
|
129
163
|
const peer = new SimplePeer({
|
|
130
164
|
initiator,
|
|
@@ -140,46 +174,64 @@ function createPeer(socket, localStream, targetSocketId, remoteUserId, initiator
|
|
|
140
174
|
});
|
|
141
175
|
|
|
142
176
|
peer.on('stream', stream => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
var data = {
|
|
177
|
+
emit("user-published", {
|
|
146
178
|
user: {
|
|
147
179
|
uuid: remoteUserId,
|
|
148
180
|
videotrack: stream
|
|
149
181
|
},
|
|
150
182
|
mediaType: "video"
|
|
151
|
-
};
|
|
152
|
-
emit("user-published", data);
|
|
183
|
+
});
|
|
153
184
|
});
|
|
154
185
|
|
|
155
186
|
peer.on('error', err => {
|
|
156
|
-
console.error('
|
|
187
|
+
console.error('Peer error', remoteUserId, err);
|
|
157
188
|
});
|
|
158
189
|
|
|
159
190
|
return peer;
|
|
160
191
|
}
|
|
161
192
|
|
|
162
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Enables or disables the local video track and notifies the server.
|
|
195
|
+
* @param {boolean} on - `true` to turn on the camera, `false` to turn it off.
|
|
196
|
+
*/
|
|
163
197
|
export function toggleCamera(on) {
|
|
164
198
|
if (!mediastream) return;
|
|
165
199
|
mediastream.getVideoTracks().forEach(track => {
|
|
166
200
|
track.enabled = on;
|
|
167
201
|
});
|
|
202
|
+
if (socket) {
|
|
203
|
+
socket.emit('media-toggle', { userId, type: 'video', enabled: on });
|
|
204
|
+
}
|
|
168
205
|
}
|
|
169
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Checks if the local video track is currently enabled.
|
|
209
|
+
* @returns {boolean} - `true` if the camera is on.
|
|
210
|
+
*/
|
|
170
211
|
export function isCameraOn() {
|
|
171
212
|
if (!mediastream) return false;
|
|
172
213
|
return mediastream.getVideoTracks().some(track => track.enabled);
|
|
173
214
|
}
|
|
174
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Enables or disables the local audio track and notifies the server.
|
|
218
|
+
* @param {boolean} on - `true` to unmute the mic, `false` to mute.
|
|
219
|
+
*/
|
|
175
220
|
export function toggleAudio(on) {
|
|
176
221
|
if (!mediastream) return;
|
|
177
222
|
mediastream.getAudioTracks().forEach(track => {
|
|
178
223
|
track.enabled = on;
|
|
179
224
|
});
|
|
225
|
+
if (socket) {
|
|
226
|
+
socket.emit('media-toggle', { userId, type: 'audio', enabled: on });
|
|
227
|
+
}
|
|
180
228
|
}
|
|
181
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Checks if the local audio track is currently enabled.
|
|
232
|
+
* @returns {boolean} - `true` if the microphone is on.
|
|
233
|
+
*/
|
|
182
234
|
export function isAudioOn() {
|
|
183
235
|
if (!mediastream) return false;
|
|
184
236
|
return mediastream.getAudioTracks().some(track => track.enabled);
|
|
185
|
-
}
|
|
237
|
+
}
|