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 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.0",
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
- "author": "Jara",
15
- "license": "ISC",
16
- "description": "",
17
- "dependencies": {
18
- "videocall-client": "file:",
19
- "videocall-client-socket": "file:"
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 setUrl(url: string): void;
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
- export function setUrl(_url) {
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 new Promise((resolve, reject) => {
41
- navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(stream => {
42
- mediastream = stream;
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
- export function joinChannel(userId, channelName) {
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
- var data = {
94
- user: {
95
- uuid: leftUserId,
96
- // videotrack: stream
97
- },
109
+ emit("user-unpublished", {
110
+ user: { uuid: leftUserId },
98
111
  mediaType: "video"
99
- };
100
- emit("user-unpublished", data);
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.srcObject = null;
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
- console.log('πŸ“Ί RecibΓ­ stream de', remoteUserId);
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('Error en peer', remoteUserId, err);
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
+ }