videocall-client-socket 0.1.6 → 0.1.8

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 CHANGED
@@ -1,6 +1,6 @@
1
- # 📞 videocall-client-socket
1
+ # 📞 videocall-socket-client
2
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).
3
+ A WebRTC client library for multi-user video calls (mesh per room) using [simple-peer](https://www.npmjs.com/package/simple-peer) and [socket.io-client](https://www.npmjs.com/package/socket.io-client).
4
4
 
5
5
  > ✅ Designed to work with [videocall-server](https://github.com/EmersonJaraG28/videocall-server)
6
6
 
@@ -30,7 +30,10 @@ This makes SimplePeer available globally in the browser.
30
30
  import * as VideoClient from "videocall-client-socket";
31
31
  import { v4 as uuidv4 } from "uuid";
32
32
 
33
- const userId = uuidv4();
33
+ // Stable user identity (same user across devices/sessions)
34
+ const userId = "user-123";
35
+ // Unique device/session identity
36
+ const userUUID = uuidv4();
34
37
  const channelName = "roomABC";
35
38
 
36
39
  // Step 1: Subscribe to events before anything else
@@ -39,31 +42,34 @@ VideoClient.on("user-published", (data) => {
39
42
  const video = document.createElement("video");
40
43
  video.srcObject = user.videotrack;
41
44
  video.autoplay = true;
42
- video.id = user.uuid;
45
+ video.id = user.userUUID;
43
46
  document.body.appendChild(video);
44
47
  });
45
48
  VideoClient.on("user-unpublished", (data) => {
46
49
  const { user, mediaType } = data;
47
50
  if (mediaType === "video") {
48
- const video = document.getElementById(`${user.uuid}`);
51
+ const video = document.getElementById(`${user.userUUID}`);
49
52
  if (video) {
50
53
  video.remove();
51
54
  }
52
55
  }
53
56
  });
54
57
  VideoClient.on("user-media-toggled", (data) => {
55
- console.log(data.user.uuid, data.type, data.enabled);
58
+ console.log(data.user.userId, data.user.userUUID, data.type, data.enabled);
56
59
  });
57
60
 
58
61
  // Step 2: Create media stream
59
- await VideoClient.createMediaStream();
60
-
61
- // Step 3: Play local stream in <video> tag
62
- VideoClient.playVideoTrack("localVideo");
63
-
64
- // Step 4: Join the signaling channel
65
- VideoClient.setServerURL("http://localhost:3000");
66
- VideoClient.joinChannel(userId, channelName);
62
+ VideoClient.createMediaStream()
63
+ .then(() => {
64
+ // Step 3: Play local stream in <video> tag
65
+ VideoClient.playVideoTrack("localVideo");
66
+ VideoClient.setServerURL("http://localhost:3000");
67
+ // Step 4: Join the signaling channel
68
+ VideoClient.joinChannel(userId, userUUID, channelName);
69
+ })
70
+ .catch((err) => {
71
+ console.error(err);
72
+ });
67
73
  ```
68
74
 
69
75
  ---
@@ -77,7 +83,7 @@ VideoClient.joinChannel(userId, channelName);
77
83
  | 1️⃣ | `on(...)` | You must subscribe to events **before** joining the channel. |
78
84
  | 2️⃣ | `createMediaStream()` | This requests access to camera and microphone. |
79
85
  | 3️⃣ | `playVideoTrack(videoElementId)` | This shows your local stream in a `<video>` tag. |
80
- | 4️⃣ | `joinChannel(userId, room)` | Finally, join the signaling server and start peer connections. |
86
+ | 4️⃣ | `joinChannel(userId, userUUID, room)` | Finally, join the signaling server and start peer connections. |
81
87
 
82
88
  > 🧠 Skipping or reordering these steps may cause video/audio to not work properly or event listeners to be missed.
83
89
 
@@ -88,7 +94,7 @@ VideoClient.joinChannel(userId, channelName);
88
94
  ### 🔌 Connection
89
95
 
90
96
  - `setServerURL(url: string): void`
91
- - `joinChannel(userId: string, room: string): void`
97
+ - `joinChannel(userId: string, userUUID: string, room: string): void`
92
98
  - `leaveChannel(): void`
93
99
 
94
100
  ### 🎥 Media Controls
@@ -104,9 +110,9 @@ VideoClient.joinChannel(userId, channelName);
104
110
 
105
111
  Use `on(event, callback)` and `off(event, callback)`.
106
112
 
107
- - `user-published`: `{ user: { uuid, videotrack }, mediaType }`
108
- - `user-unpublished`: `{ user: { uuid }, mediaType }`
109
- - `user-media-toggled`: `{ user: { uuid }, type, enabled }`
113
+ - `user-published`: `{ user: { userId, userUUID, videotrack }, mediaType }`
114
+ - `user-unpublished`: `{ user: { userId, userUUID }, mediaType }`
115
+ - `user-media-toggled`: `{ user: { userId, userUUID }, type, enabled }`
110
116
 
111
117
  #### Example:
112
118
 
@@ -115,13 +121,22 @@ VideoClient.on("user-published", ({ user }) => {
115
121
  const video = document.createElement("video");
116
122
  video.srcObject = user.videotrack;
117
123
  video.autoplay = true;
118
- video.id = user.uuid;
124
+ video.id = user.userUUID;
119
125
  document.body.appendChild(video);
120
126
  });
121
127
  ```
122
128
 
123
129
  ---
124
130
 
131
+ ## 🕸 Multi-user Model
132
+
133
+ - Each room supports multiple participants.
134
+ - WebRTC connections are created in mesh mode (one peer connection per remote participant).
135
+ - `userId` identifies the person.
136
+ - `userUUID` identifies a specific device/session for that person.
137
+
138
+ ---
139
+
125
140
  ## 🧩 Backend Integration
126
141
 
127
142
  This library is designed to work with the following signaling server:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "videocall-client-socket",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "main": "src/index.js",
5
5
  "types": "src/index.d.ts",
6
6
  "scripts": {
package/src/index.d.ts CHANGED
@@ -14,6 +14,6 @@ declare module 'videocall-client-socket' {
14
14
 
15
15
  export function playVideoTrack(localVideoId: string): void;
16
16
 
17
- export function joinChannel(userId: string, channelName: string): void;
17
+ export function joinChannel(userId: string, userUUID: string, channelName: string): void;
18
18
  export function leaveChannel(): void;
19
- }
19
+ }
package/src/index.js CHANGED
@@ -5,7 +5,8 @@ var socket = null;
5
5
  var mediastream = null;
6
6
  var localVideoId = "";
7
7
  var userId = "";
8
- var url = 'http://localhost:3000';
8
+ var userUUID = "";
9
+ var serverURL = 'http://localhost:3000';
9
10
  let events = {};
10
11
 
11
12
  /**
@@ -42,20 +43,39 @@ let emit = (_event, data) => {
42
43
  * @param {string} _url - Server URL (e.g. "http://localhost:3000").
43
44
  */
44
45
  export function setServerURL(_url) {
45
- url = _url;
46
+ serverURL = _url;
46
47
  }
47
48
 
48
49
  /**
49
- * Requests access to the user's camera and microphone.
50
- * @returns {Promise<MediaStream>} - Resolves with a MediaStream containing audio and video.
50
+ * Requests access to both camera and microphone.
51
+ * Throws an error if either device is missing or denied.
52
+ * @returns {Promise<MediaStream>}
51
53
  */
52
54
  export function createMediaStream() {
53
- return navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(stream => {
54
- mediastream = stream;
55
- return stream;
55
+ return new Promise((resolve, reject) => {
56
+ navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then((stream) => {
57
+ const hasVideo = stream.getVideoTracks().length > 0;
58
+ const hasAudio = stream.getAudioTracks().length > 0;
59
+
60
+ if (!hasVideo || !hasAudio) {
61
+ stream.getTracks().forEach(track => track.stop()); // cleanup
62
+ throw new Error(
63
+ !hasVideo && !hasAudio
64
+ ? "Camera and microphone are not available."
65
+ : !hasVideo
66
+ ? "Camera is not available."
67
+ : "Microphone is not available."
68
+ );
69
+ }
70
+
71
+ mediastream = stream;
72
+ resolve(mediastream);
73
+ }).catch((err) => {
74
+ reject("Camera and microphone are not available.");
75
+ });
56
76
  });
57
- }
58
77
 
78
+ }
59
79
  /**
60
80
  * Plays the local video stream into a specified <video> element.
61
81
  * @param {string} _localVideoId - The ID of the <video> element in the DOM.
@@ -71,53 +91,64 @@ export function playVideoTrack(_localVideoId) {
71
91
  /**
72
92
  * Connects to the signaling server and joins a room.
73
93
  * @param {string} _userId - Unique identifier for the user.
94
+ * @param {string} _userUUID - Unique identifier for this device/session.
74
95
  * @param {string} channelName - The name of the room to join.
75
96
  */
76
- export function joinChannel(_userId, channelName) {
97
+ export function joinChannel(_userId, _userUUID, channelName) {
77
98
  userId = _userId;
99
+ userUUID = _userUUID;
78
100
 
79
- socket = io(url, {
80
- query: { room: channelName, userId }
101
+ socket = io(serverURL, {
102
+ query: { room: channelName, userId, userUUID }
81
103
  });
82
104
 
83
105
  socket.on('all-users', users => {
84
- users.forEach(({ userId: remoteUserId, socketId }) => {
85
- const peer = createPeer(socket, mediastream, socketId, remoteUserId, true);
86
- peers[remoteUserId] = peer;
106
+ users.forEach(({ userId: remoteUserId, userUUID: remoteUserUUID, socketId, }) => {
107
+ const peer = createPeer(socket, mediastream, socketId, remoteUserId, remoteUserUUID, true);
108
+ peers[remoteUserUUID] = peer;
87
109
  });
88
110
  });
89
111
 
90
- socket.on('user-joined', ({ userId: newUserId, socketId: newSocketId }) => {
91
- const peer = createPeer(socket, mediastream, newSocketId, newUserId, false);
92
- peers[newUserId] = peer;
112
+ socket.on('user-joined', ({ userId: newUserId, userUUID: newUserUUID, socketId: newSocketId }) => {
113
+ // console.log("User joined", newUserId, newUserUUID, newSocketId);
114
+
115
+ const peer = createPeer(socket, mediastream, newSocketId, newUserId, newUserUUID, false);
116
+ peers[newUserUUID] = peer;
93
117
  });
94
118
 
95
- socket.on('signal', ({ fromUserId, signal }) => {
96
- const peer = peers[fromUserId];
119
+ socket.on('signal', ({ fromUserId, fromUserUUID, signal }) => {
120
+ const peer = peers[fromUserUUID || fromUserId];
97
121
  if (peer) {
98
122
  peer.signal(signal);
99
123
  }
100
124
  });
101
125
 
102
- socket.on('user-left', ({ userId: leftUserId }) => {
103
- if (peers[leftUserId]) {
104
- peers[leftUserId].destroy();
105
- delete peers[leftUserId];
126
+ socket.on('user-left', ({ userId: leftUserId, userUUID: leftUserUUID }) => {
127
+ if (peers[leftUserUUID]) {
128
+ peers[leftUserUUID].destroy();
129
+ delete peers[leftUserUUID];
106
130
  }
107
131
 
108
132
  emit("user-unpublished", {
109
- user: { uuid: leftUserId },
133
+ user: {
134
+ userId: leftUserId,
135
+ userUUID: leftUserUUID
136
+ },
110
137
  mediaType: "video"
111
138
  });
112
139
  });
113
140
 
114
- socket.on('user-media-toggled', ({ userId, type, enabled }) => {
141
+ socket.on('user-media-toggled', ({ userId, userUUID, type, enabled }) => {
115
142
  emit('user-media-toggled', {
116
- user: { uuid: userId },
143
+ user: {
144
+ userId,
145
+ userUUID
146
+ },
117
147
  type,
118
148
  enabled
119
149
  });
120
150
  });
151
+
121
152
  }
122
153
 
123
154
  /**
@@ -155,10 +186,11 @@ export function leaveChannel() {
155
186
  * @param {MediaStream} localStream - The local media stream.
156
187
  * @param {string} targetSocketId - The socket ID of the remote user.
157
188
  * @param {string} remoteUserId - The ID of the remote user.
189
+ * @param {string} remoteUserUUID - The device/session ID of the remote user.
158
190
  * @param {boolean} initiator - Whether this peer initiates the connection.
159
191
  * @returns {SimplePeer.Instance} - The created peer.
160
192
  */
161
- function createPeer(socket, localStream, targetSocketId, remoteUserId, initiator) {
193
+ function createPeer(socket, localStream, targetSocketId, remoteUserId, remoteUserUUID, initiator) {
162
194
  const peer = new SimplePeer({
163
195
  initiator,
164
196
  trickle: false,
@@ -173,13 +205,23 @@ function createPeer(socket, localStream, targetSocketId, remoteUserId, initiator
173
205
  });
174
206
 
175
207
  peer.on('stream', stream => {
208
+
176
209
  emit("user-published", {
177
210
  user: {
178
- uuid: remoteUserId,
211
+ userId: remoteUserId,
212
+ userUUID: remoteUserUUID,
179
213
  videotrack: stream
180
214
  },
181
215
  mediaType: "video"
182
216
  });
217
+
218
+ setTimeout(() => {
219
+ socket.emit('initial-media-status', {
220
+ targetId: targetSocketId,
221
+ audio: isAudioOn(),
222
+ video: isCameraOn()
223
+ });
224
+ }, 100);
183
225
  });
184
226
 
185
227
  peer.on('error', err => {
@@ -199,7 +241,7 @@ export function toggleCamera(on) {
199
241
  track.enabled = on;
200
242
  });
201
243
  if (socket) {
202
- socket.emit('media-toggle', { userId, type: 'video', enabled: on });
244
+ socket.emit('media-toggle', { userId, userUUID, type: 'video', enabled: on });
203
245
  }
204
246
  }
205
247
 
@@ -222,7 +264,7 @@ export function toggleAudio(on) {
222
264
  track.enabled = on;
223
265
  });
224
266
  if (socket) {
225
- socket.emit('media-toggle', { userId, type: 'audio', enabled: on });
267
+ socket.emit('media-toggle', { userId, userUUID, type: 'audio', enabled: on });
226
268
  }
227
269
  }
228
270