@afosecure/meetingsdk 1.4.6 → 1.4.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/dist/index.mjs CHANGED
@@ -6,8 +6,8 @@ import { createContext, useContext, useMemo, useRef } from "react";
6
6
 
7
7
  // src/config/ws.ts
8
8
  var SDK_CONFIG = {
9
- wsUrl: "wss://localhost:8080/ws",
10
- baseUrl: "https://localhost:8080"
9
+ wsUrl: "wss://sfuserver-production.up.railway.app/ws",
10
+ baseUrl: "https://sfuserver-production.up.railway.app"
11
11
  };
12
12
 
13
13
  // src/core/MeetingState.ts
@@ -166,7 +166,9 @@ var VideoSDKCore = class {
166
166
  this.ws = null;
167
167
  this.pubPC = null;
168
168
  this.subPC = null;
169
- this.pendingTracks = [];
169
+ this.pendingTracks = /* @__PURE__ */ new Map();
170
+ this.subscriberNegotiating = false;
171
+ this.subscriberOfferQueue = [];
170
172
  this.iceServers = [];
171
173
  this.lastPong = Date.now();
172
174
  this.intentionalDisconnect = false;
@@ -190,33 +192,84 @@ var VideoSDKCore = class {
190
192
  this.myId = localStorage.getItem("vsdk_id") || crypto.randomUUID();
191
193
  localStorage.setItem("vsdk_id", this.myId);
192
194
  }
195
+ async acquireLocalMedia(options) {
196
+ const { videoConstraints = true, audioConstraints = true } = options;
197
+ try {
198
+ const stream = await navigator.mediaDevices.getUserMedia({
199
+ video: videoConstraints,
200
+ audio: audioConstraints
201
+ });
202
+ const hasVideo = stream.getVideoTracks().some((t) => t.readyState === "live");
203
+ const hasAudio = stream.getAudioTracks().some((t) => t.readyState === "live");
204
+ return { stream, camEnabled: hasVideo, micEnabled: hasAudio };
205
+ } catch (err) {
206
+ console.warn(
207
+ "[VideoSDKCore] Primary getUserMedia failed:",
208
+ err?.name || err?.message
209
+ );
210
+ const isDeviceLocked = err?.name === "NotReadableError" || err?.name === "TrackStartError" || err?.message?.toLowerCase().includes("allocate videosource") || err?.message?.toLowerCase().includes("could not start video source");
211
+ if (isDeviceLocked) {
212
+ console.warn(
213
+ "[VideoSDKCore] Camera is locked by another browser/app. Falling back to Audio-Only."
214
+ );
215
+ try {
216
+ const audioOnlyStream = await navigator.mediaDevices.getUserMedia({
217
+ video: false,
218
+ audio: audioConstraints
219
+ });
220
+ return {
221
+ stream: audioOnlyStream,
222
+ camEnabled: false,
223
+ micEnabled: audioOnlyStream.getAudioTracks().some((t) => t.readyState === "live")
224
+ };
225
+ } catch (audioErr) {
226
+ console.warn(
227
+ "[VideoSDKCore] Audio acquisition also failed. Falling back to View-Only stream."
228
+ );
229
+ }
230
+ }
231
+ return {
232
+ stream: new MediaStream(),
233
+ camEnabled: false,
234
+ micEnabled: false
235
+ };
236
+ }
237
+ }
193
238
  // ---------------- MEDIA SETUP ----------------
194
239
  async initLocal(video, name) {
195
240
  this.participantName = name;
196
241
  try {
197
- this.localStream = await navigator.mediaDevices.getUserMedia({
198
- video: { width: { ideal: 1280 }, height: { ideal: 720 } },
199
- audio: {
242
+ const { stream, camEnabled, micEnabled } = await this.acquireLocalMedia({
243
+ videoConstraints: { width: { ideal: 1280 }, height: { ideal: 720 } },
244
+ audioConstraints: {
200
245
  echoCancellation: true,
201
246
  noiseSuppression: true,
202
247
  autoGainControl: true
203
248
  }
204
249
  });
205
- const hasVideo = this.localStream.getVideoTracks().some((t) => t.readyState === "live");
206
- const hasAudio = this.localStream.getAudioTracks().some((t) => t.readyState === "live");
207
- if (!hasVideo || !hasAudio) {
208
- throw new Error(`Missing tracks: video=${hasVideo}, audio=${hasAudio}`);
250
+ this.localStream = stream;
251
+ console.log(
252
+ "[LOCAL MEDIA]",
253
+ this.localStream.getTracks().map((t) => ({
254
+ kind: t.kind,
255
+ enabled: t.enabled,
256
+ ready: t.readyState
257
+ }))
258
+ );
259
+ if (video && this.localStream.getVideoTracks().length > 0) {
260
+ video.srcObject = this.localStream;
209
261
  }
210
- video.srcObject = this.localStream;
262
+ const cameraTrack = this.localStream.getVideoTracks()[0] || void 0;
263
+ const audioTrack = this.localStream.getAudioTracks()[0] || void 0;
211
264
  this.state.updateLocalParticipant({
212
265
  id: this.myId,
213
266
  name: this.participantName,
214
267
  media: {
215
268
  stream: this.localStream,
216
- cameraTrack: this.localStream.getVideoTracks()[0],
217
- audioTrack: this.localStream.getAudioTracks()[0],
218
- micEnabled: true,
219
- camEnabled: true,
269
+ cameraTrack,
270
+ audioTrack,
271
+ micEnabled,
272
+ camEnabled,
220
273
  isScreenSharing: false
221
274
  }
222
275
  });
@@ -232,23 +285,38 @@ var VideoSDKCore = class {
232
285
  throw new Error("roomId and name are required to join meeting");
233
286
  }
234
287
  this.participantName = name;
288
+ let camEnabled = !videoMuted;
289
+ let micEnabled = !audioMuted;
235
290
  if (!this.localStream) {
236
- this.localStream = await navigator.mediaDevices.getUserMedia({
237
- video: true,
238
- audio: true
291
+ const acquired = await this.acquireLocalMedia({
292
+ videoConstraints: !videoMuted,
293
+ audioConstraints: !audioMuted
239
294
  });
295
+ this.localStream = acquired.stream;
296
+ console.log(
297
+ "[LOCAL MEDIA]",
298
+ this.localStream.getTracks().map((t) => ({
299
+ kind: t.kind,
300
+ enabled: t.enabled,
301
+ ready: t.readyState
302
+ }))
303
+ );
304
+ camEnabled = acquired.camEnabled && !videoMuted;
305
+ micEnabled = acquired.micEnabled && !audioMuted;
240
306
  }
241
- this.localStream.getAudioTracks().forEach((t) => t.enabled = !audioMuted);
242
- this.localStream.getVideoTracks().forEach((t) => t.enabled = !videoMuted);
307
+ this.localStream.getAudioTracks().forEach((t) => t.enabled = micEnabled);
308
+ this.localStream.getVideoTracks().forEach((t) => t.enabled = camEnabled);
309
+ const cameraTrack = this.localStream.getVideoTracks()[0] || void 0;
310
+ const audioTrack = this.localStream.getAudioTracks()[0] || void 0;
243
311
  this.state.updateLocalParticipant({
244
312
  id: this.myId,
245
313
  name: this.participantName,
246
314
  media: {
247
315
  stream: this.localStream,
248
- cameraTrack: this.localStream.getVideoTracks()[0],
249
- audioTrack: this.localStream.getAudioTracks()[0],
250
- micEnabled: !audioMuted,
251
- camEnabled: !videoMuted,
316
+ cameraTrack,
317
+ audioTrack,
318
+ micEnabled,
319
+ camEnabled,
252
320
  isScreenSharing: false
253
321
  }
254
322
  });
@@ -262,9 +330,39 @@ var VideoSDKCore = class {
262
330
  iceServers: this.iceServers,
263
331
  iceTransportPolicy: this.iceTransportPolicy
264
332
  });
333
+ const tracksByKind = /* @__PURE__ */ new Map();
265
334
  this.localStream.getTracks().forEach((track) => {
266
- this.pubPC?.addTrack(track, this.localStream);
335
+ tracksByKind.set(track.kind, track);
267
336
  });
337
+ const audioTrack = this.localStream.getAudioTracks()[0];
338
+ if (audioTrack) {
339
+ console.log("[Publisher] Adding audio track:", {
340
+ kind: audioTrack.kind,
341
+ id: audioTrack.id,
342
+ enabled: audioTrack.enabled,
343
+ state: audioTrack.readyState
344
+ });
345
+ try {
346
+ this.pubPC?.addTrack(audioTrack, this.localStream);
347
+ } catch (e) {
348
+ console.error("[Publisher] Failed to add audio track:", e);
349
+ }
350
+ }
351
+ const videoTrack = this.localStream.getVideoTracks()[0];
352
+ if (videoTrack) {
353
+ console.log("[Publisher] Adding video track:", {
354
+ kind: videoTrack.kind,
355
+ id: videoTrack.id,
356
+ enabled: videoTrack.enabled,
357
+ // Can be false, that's OK
358
+ state: videoTrack.readyState
359
+ });
360
+ try {
361
+ this.pubPC?.addTrack(videoTrack, this.localStream);
362
+ } catch (e) {
363
+ console.error("[Publisher] Failed to add video track:", e);
364
+ }
365
+ }
268
366
  this.pubPC.onicecandidate = (e) => {
269
367
  if (e.candidate) {
270
368
  this.send({
@@ -275,8 +373,13 @@ var VideoSDKCore = class {
275
373
  }
276
374
  };
277
375
  this.pubPC.onconnectionstatechange = () => {
278
- console.log(`[SFU Publisher PC State]`, this.pubPC?.connectionState);
376
+ console.log("[Publisher PC State]", {
377
+ connection: this.pubPC?.connectionState,
378
+ ice: this.pubPC?.iceConnectionState,
379
+ signaling: this.pubPC?.signalingState
380
+ });
279
381
  if (this.pubPC?.connectionState === "failed") {
382
+ console.warn("[Publisher] Connection failed, restarting ICE");
280
383
  this.restartPublisherIce();
281
384
  }
282
385
  };
@@ -296,36 +399,70 @@ var VideoSDKCore = class {
296
399
  }
297
400
  };
298
401
  this.subPC.ontrack = (event) => {
299
- const descriptor = this.pendingTracks.shift();
402
+ console.log("[SFU ontrack Event]", {
403
+ kind: event.track.kind,
404
+ id: event.track.id,
405
+ mid: event.transceiver.mid,
406
+ streams: event.streams.length
407
+ });
408
+ const stream = event.streams[0] || new MediaStream([event.track]);
409
+ const mid = event.transceiver.mid;
410
+ if (!mid) {
411
+ console.warn("[Subscriber] Track received without MID", event.track);
412
+ return;
413
+ }
414
+ console.log("[Subscriber] Looking for descriptor with MID:", mid, {
415
+ available: [...this.pendingTracks.keys()]
416
+ });
417
+ const descriptor = this.pendingTracks.get(mid);
300
418
  if (!descriptor) {
301
- console.warn("Unknown incoming track");
419
+ console.warn("[Subscriber] No descriptor found for MID:", mid);
420
+ console.log("[Subscriber] Pending tracks:", [
421
+ ...this.pendingTracks.entries()
422
+ ]);
302
423
  return;
303
424
  }
304
- const stream = event.streams[0] || new MediaStream([event.track]);
425
+ console.log("[Subscriber] Found descriptor:", descriptor);
426
+ this.pendingTracks.delete(mid);
305
427
  switch (descriptor.source) {
306
428
  case "camera":
429
+ console.log(
430
+ "[Subscriber] Updating camera for:",
431
+ descriptor.publisher_id
432
+ );
307
433
  this.state.updateParticipantMedia(descriptor.publisher_id, {
308
434
  stream,
309
435
  cameraTrack: event.track
310
436
  });
311
437
  break;
438
+ case "audio":
439
+ console.log(
440
+ "[Subscriber] Updating audio for:",
441
+ descriptor.publisher_id
442
+ );
443
+ this.state.updateParticipantMedia(descriptor.publisher_id, {
444
+ stream,
445
+ audioTrack: event.track
446
+ });
447
+ break;
312
448
  case "screen":
449
+ console.log(
450
+ "[Subscriber] Updating screen for:",
451
+ descriptor.publisher_id
452
+ );
313
453
  this.state.updateParticipantMedia(descriptor.publisher_id, {
314
454
  screenStream: stream,
315
455
  screenTrack: event.track,
316
456
  isScreenSharing: true
317
457
  });
318
458
  break;
319
- case "audio":
320
- this.state.updateParticipantMedia(descriptor.publisher_id, {
321
- stream,
322
- audioTrack: event.track
323
- });
324
- break;
325
459
  }
326
460
  };
327
461
  this.subPC.onconnectionstatechange = () => {
328
- console.log(`[SFU Subscriber PC State]`, this.subPC?.connectionState);
462
+ console.log("[SFU Subscriber PC State]", this.subPC?.connectionState);
463
+ if (this.subPC?.connectionState === "failed") {
464
+ console.warn("Subscriber connection failed");
465
+ }
329
466
  };
330
467
  }
331
468
  // ---------------- WEBSOCKET CONNECTION & SIGNALING ----------------
@@ -388,6 +525,19 @@ var VideoSDKCore = class {
388
525
  this.setupPublisherPC();
389
526
  this.setupSubscriberPC();
390
527
  await this.createPublisherOffer();
528
+ const media = this.state.localParticipant?.media;
529
+ if (media) {
530
+ this.send({
531
+ type: "MEDIA_STATE",
532
+ kind: "audio",
533
+ enabled: !!media.micEnabled
534
+ });
535
+ this.send({
536
+ type: "MEDIA_STATE",
537
+ kind: "video",
538
+ enabled: !!media.camEnabled
539
+ });
540
+ }
391
541
  this.startHeartbeat();
392
542
  this.joinResolver?.();
393
543
  this.joinResolver = void 0;
@@ -404,7 +554,6 @@ var VideoSDKCore = class {
404
554
  break;
405
555
  }
406
556
  case "SUB_OFFER": {
407
- this.pendingTracks.push(msg.track);
408
557
  if (this.subPC) {
409
558
  await this.subPC.setRemoteDescription({
410
559
  type: "offer",
@@ -418,6 +567,7 @@ var VideoSDKCore = class {
418
567
  user_id: this.myId
419
568
  });
420
569
  }
570
+ await this.handleSubscriberOffer(msg);
421
571
  break;
422
572
  }
423
573
  case "PUB_ICE": {
@@ -564,6 +714,31 @@ var VideoSDKCore = class {
564
714
  }
565
715
  }
566
716
  }
717
+ async handleSubscriberOffer(msg) {
718
+ if (!this.subPC) return;
719
+ try {
720
+ this.subscriberNegotiating = true;
721
+ await this.subPC.setRemoteDescription({
722
+ type: "offer",
723
+ sdp: msg.payload
724
+ });
725
+ const answer = await this.subPC.createAnswer();
726
+ await this.subPC.setLocalDescription(answer);
727
+ this.send({
728
+ type: "SUB_ANSWER",
729
+ payload: answer.sdp,
730
+ user_id: this.myId
731
+ });
732
+ } catch (err) {
733
+ console.error("[SUB OFFER ERROR]", err);
734
+ } finally {
735
+ this.subscriberNegotiating = false;
736
+ const next = this.subscriberOfferQueue.shift();
737
+ if (next) {
738
+ await this.handleSubscriberOffer(next);
739
+ }
740
+ }
741
+ }
567
742
  // ---------------- PUBLISHER RENEGOTIATION ----------------
568
743
  async createPublisherOffer() {
569
744
  if (!this.pubPC) return;