@afosecure/meetingsdk 1.4.6 → 1.4.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 CHANGED
@@ -128,6 +128,8 @@ declare class VideoSDKCore {
128
128
  private pubPC;
129
129
  private subPC;
130
130
  private pendingTracks;
131
+ private subscriberNegotiating;
132
+ private subscriberOfferQueue;
131
133
  private iceServers;
132
134
  private lastPong;
133
135
  private intentionalDisconnect;
@@ -148,12 +150,14 @@ declare class VideoSDKCore {
148
150
  private pendingRequestId;
149
151
  private iceTransportPolicy;
150
152
  constructor(events?: Events, url?: string);
153
+ private acquireLocalMedia;
151
154
  initLocal(video: HTMLVideoElement, name: string): Promise<void>;
152
155
  joinMeeting(config: MeetingConfig): Promise<void>;
153
156
  private setupPublisherPC;
154
157
  private setupSubscriberPC;
155
158
  connect(roomId: string, name: string): Promise<void>;
156
159
  private handle;
160
+ private handleSubscriberOffer;
157
161
  private createPublisherOffer;
158
162
  toggleMic(): void;
159
163
  toggleCam(): void;
package/dist/index.d.ts CHANGED
@@ -128,6 +128,8 @@ declare class VideoSDKCore {
128
128
  private pubPC;
129
129
  private subPC;
130
130
  private pendingTracks;
131
+ private subscriberNegotiating;
132
+ private subscriberOfferQueue;
131
133
  private iceServers;
132
134
  private lastPong;
133
135
  private intentionalDisconnect;
@@ -148,12 +150,14 @@ declare class VideoSDKCore {
148
150
  private pendingRequestId;
149
151
  private iceTransportPolicy;
150
152
  constructor(events?: Events, url?: string);
153
+ private acquireLocalMedia;
151
154
  initLocal(video: HTMLVideoElement, name: string): Promise<void>;
152
155
  joinMeeting(config: MeetingConfig): Promise<void>;
153
156
  private setupPublisherPC;
154
157
  private setupSubscriberPC;
155
158
  connect(roomId: string, name: string): Promise<void>;
156
159
  private handle;
160
+ private handleSubscriberOffer;
157
161
  private createPublisherOffer;
158
162
  toggleMic(): void;
159
163
  toggleCam(): void;
package/dist/index.js CHANGED
@@ -40,8 +40,8 @@ var import_react2 = require("react");
40
40
 
41
41
  // src/config/ws.ts
42
42
  var SDK_CONFIG = {
43
- wsUrl: "wss://localhost:8080/ws",
44
- baseUrl: "https://localhost:8080"
43
+ wsUrl: "wss://sfuserver-production.up.railway.app/ws",
44
+ baseUrl: "https://sfuserver-production.up.railway.app"
45
45
  };
46
46
 
47
47
  // src/core/MeetingState.ts
@@ -200,7 +200,9 @@ var VideoSDKCore = class {
200
200
  this.ws = null;
201
201
  this.pubPC = null;
202
202
  this.subPC = null;
203
- this.pendingTracks = [];
203
+ this.pendingTracks = /* @__PURE__ */ new Map();
204
+ this.subscriberNegotiating = false;
205
+ this.subscriberOfferQueue = [];
204
206
  this.iceServers = [];
205
207
  this.lastPong = Date.now();
206
208
  this.intentionalDisconnect = false;
@@ -224,33 +226,84 @@ var VideoSDKCore = class {
224
226
  this.myId = localStorage.getItem("vsdk_id") || crypto.randomUUID();
225
227
  localStorage.setItem("vsdk_id", this.myId);
226
228
  }
229
+ async acquireLocalMedia(options) {
230
+ const { videoConstraints = true, audioConstraints = true } = options;
231
+ try {
232
+ const stream = await navigator.mediaDevices.getUserMedia({
233
+ video: videoConstraints,
234
+ audio: audioConstraints
235
+ });
236
+ const hasVideo = stream.getVideoTracks().some((t) => t.readyState === "live");
237
+ const hasAudio = stream.getAudioTracks().some((t) => t.readyState === "live");
238
+ return { stream, camEnabled: hasVideo, micEnabled: hasAudio };
239
+ } catch (err) {
240
+ console.warn(
241
+ "[VideoSDKCore] Primary getUserMedia failed:",
242
+ err?.name || err?.message
243
+ );
244
+ const isDeviceLocked = err?.name === "NotReadableError" || err?.name === "TrackStartError" || err?.message?.toLowerCase().includes("allocate videosource") || err?.message?.toLowerCase().includes("could not start video source");
245
+ if (isDeviceLocked) {
246
+ console.warn(
247
+ "[VideoSDKCore] Camera is locked by another browser/app. Falling back to Audio-Only."
248
+ );
249
+ try {
250
+ const audioOnlyStream = await navigator.mediaDevices.getUserMedia({
251
+ video: false,
252
+ audio: audioConstraints
253
+ });
254
+ return {
255
+ stream: audioOnlyStream,
256
+ camEnabled: false,
257
+ micEnabled: audioOnlyStream.getAudioTracks().some((t) => t.readyState === "live")
258
+ };
259
+ } catch (audioErr) {
260
+ console.warn(
261
+ "[VideoSDKCore] Audio acquisition also failed. Falling back to View-Only stream."
262
+ );
263
+ }
264
+ }
265
+ return {
266
+ stream: new MediaStream(),
267
+ camEnabled: false,
268
+ micEnabled: false
269
+ };
270
+ }
271
+ }
227
272
  // ---------------- MEDIA SETUP ----------------
228
273
  async initLocal(video, name) {
229
274
  this.participantName = name;
230
275
  try {
231
- this.localStream = await navigator.mediaDevices.getUserMedia({
232
- video: { width: { ideal: 1280 }, height: { ideal: 720 } },
233
- audio: {
276
+ const { stream, camEnabled, micEnabled } = await this.acquireLocalMedia({
277
+ videoConstraints: { width: { ideal: 1280 }, height: { ideal: 720 } },
278
+ audioConstraints: {
234
279
  echoCancellation: true,
235
280
  noiseSuppression: true,
236
281
  autoGainControl: true
237
282
  }
238
283
  });
239
- const hasVideo = this.localStream.getVideoTracks().some((t) => t.readyState === "live");
240
- const hasAudio = this.localStream.getAudioTracks().some((t) => t.readyState === "live");
241
- if (!hasVideo || !hasAudio) {
242
- throw new Error(`Missing tracks: video=${hasVideo}, audio=${hasAudio}`);
284
+ this.localStream = stream;
285
+ console.log(
286
+ "[LOCAL MEDIA]",
287
+ this.localStream.getTracks().map((t) => ({
288
+ kind: t.kind,
289
+ enabled: t.enabled,
290
+ ready: t.readyState
291
+ }))
292
+ );
293
+ if (video && this.localStream.getVideoTracks().length > 0) {
294
+ video.srcObject = this.localStream;
243
295
  }
244
- video.srcObject = this.localStream;
296
+ const cameraTrack = this.localStream.getVideoTracks()[0] || void 0;
297
+ const audioTrack = this.localStream.getAudioTracks()[0] || void 0;
245
298
  this.state.updateLocalParticipant({
246
299
  id: this.myId,
247
300
  name: this.participantName,
248
301
  media: {
249
302
  stream: this.localStream,
250
- cameraTrack: this.localStream.getVideoTracks()[0],
251
- audioTrack: this.localStream.getAudioTracks()[0],
252
- micEnabled: true,
253
- camEnabled: true,
303
+ cameraTrack,
304
+ audioTrack,
305
+ micEnabled,
306
+ camEnabled,
254
307
  isScreenSharing: false
255
308
  }
256
309
  });
@@ -266,23 +319,38 @@ var VideoSDKCore = class {
266
319
  throw new Error("roomId and name are required to join meeting");
267
320
  }
268
321
  this.participantName = name;
322
+ let camEnabled = !videoMuted;
323
+ let micEnabled = !audioMuted;
269
324
  if (!this.localStream) {
270
- this.localStream = await navigator.mediaDevices.getUserMedia({
271
- video: true,
272
- audio: true
325
+ const acquired = await this.acquireLocalMedia({
326
+ videoConstraints: !videoMuted,
327
+ audioConstraints: !audioMuted
273
328
  });
329
+ this.localStream = acquired.stream;
330
+ console.log(
331
+ "[LOCAL MEDIA]",
332
+ this.localStream.getTracks().map((t) => ({
333
+ kind: t.kind,
334
+ enabled: t.enabled,
335
+ ready: t.readyState
336
+ }))
337
+ );
338
+ camEnabled = acquired.camEnabled && !videoMuted;
339
+ micEnabled = acquired.micEnabled && !audioMuted;
274
340
  }
275
- this.localStream.getAudioTracks().forEach((t) => t.enabled = !audioMuted);
276
- this.localStream.getVideoTracks().forEach((t) => t.enabled = !videoMuted);
341
+ this.localStream.getAudioTracks().forEach((t) => t.enabled = micEnabled);
342
+ this.localStream.getVideoTracks().forEach((t) => t.enabled = camEnabled);
343
+ const cameraTrack = this.localStream.getVideoTracks()[0] || void 0;
344
+ const audioTrack = this.localStream.getAudioTracks()[0] || void 0;
277
345
  this.state.updateLocalParticipant({
278
346
  id: this.myId,
279
347
  name: this.participantName,
280
348
  media: {
281
349
  stream: this.localStream,
282
- cameraTrack: this.localStream.getVideoTracks()[0],
283
- audioTrack: this.localStream.getAudioTracks()[0],
284
- micEnabled: !audioMuted,
285
- camEnabled: !videoMuted,
350
+ cameraTrack,
351
+ audioTrack,
352
+ micEnabled,
353
+ camEnabled,
286
354
  isScreenSharing: false
287
355
  }
288
356
  });
@@ -296,9 +364,39 @@ var VideoSDKCore = class {
296
364
  iceServers: this.iceServers,
297
365
  iceTransportPolicy: this.iceTransportPolicy
298
366
  });
367
+ const tracksByKind = /* @__PURE__ */ new Map();
299
368
  this.localStream.getTracks().forEach((track) => {
300
- this.pubPC?.addTrack(track, this.localStream);
369
+ tracksByKind.set(track.kind, track);
301
370
  });
371
+ const audioTrack = this.localStream.getAudioTracks()[0];
372
+ if (audioTrack) {
373
+ console.log("[Publisher] Adding audio track:", {
374
+ kind: audioTrack.kind,
375
+ id: audioTrack.id,
376
+ enabled: audioTrack.enabled,
377
+ state: audioTrack.readyState
378
+ });
379
+ try {
380
+ this.pubPC?.addTrack(audioTrack, this.localStream);
381
+ } catch (e) {
382
+ console.error("[Publisher] Failed to add audio track:", e);
383
+ }
384
+ }
385
+ const videoTrack = this.localStream.getVideoTracks()[0];
386
+ if (videoTrack) {
387
+ console.log("[Publisher] Adding video track:", {
388
+ kind: videoTrack.kind,
389
+ id: videoTrack.id,
390
+ enabled: videoTrack.enabled,
391
+ // Can be false, that's OK
392
+ state: videoTrack.readyState
393
+ });
394
+ try {
395
+ this.pubPC?.addTrack(videoTrack, this.localStream);
396
+ } catch (e) {
397
+ console.error("[Publisher] Failed to add video track:", e);
398
+ }
399
+ }
302
400
  this.pubPC.onicecandidate = (e) => {
303
401
  if (e.candidate) {
304
402
  this.send({
@@ -309,8 +407,13 @@ var VideoSDKCore = class {
309
407
  }
310
408
  };
311
409
  this.pubPC.onconnectionstatechange = () => {
312
- console.log(`[SFU Publisher PC State]`, this.pubPC?.connectionState);
410
+ console.log("[Publisher PC State]", {
411
+ connection: this.pubPC?.connectionState,
412
+ ice: this.pubPC?.iceConnectionState,
413
+ signaling: this.pubPC?.signalingState
414
+ });
313
415
  if (this.pubPC?.connectionState === "failed") {
416
+ console.warn("[Publisher] Connection failed, restarting ICE");
314
417
  this.restartPublisherIce();
315
418
  }
316
419
  };
@@ -330,36 +433,70 @@ var VideoSDKCore = class {
330
433
  }
331
434
  };
332
435
  this.subPC.ontrack = (event) => {
333
- const descriptor = this.pendingTracks.shift();
436
+ console.log("[SFU ontrack Event]", {
437
+ kind: event.track.kind,
438
+ id: event.track.id,
439
+ mid: event.transceiver.mid,
440
+ streams: event.streams.length
441
+ });
442
+ const stream = event.streams[0] || new MediaStream([event.track]);
443
+ const mid = event.transceiver.mid;
444
+ if (!mid) {
445
+ console.warn("[Subscriber] Track received without MID", event.track);
446
+ return;
447
+ }
448
+ console.log("[Subscriber] Looking for descriptor with MID:", mid, {
449
+ available: [...this.pendingTracks.keys()]
450
+ });
451
+ const descriptor = this.pendingTracks.get(mid);
334
452
  if (!descriptor) {
335
- console.warn("Unknown incoming track");
453
+ console.warn("[Subscriber] No descriptor found for MID:", mid);
454
+ console.log("[Subscriber] Pending tracks:", [
455
+ ...this.pendingTracks.entries()
456
+ ]);
336
457
  return;
337
458
  }
338
- const stream = event.streams[0] || new MediaStream([event.track]);
459
+ console.log("[Subscriber] Found descriptor:", descriptor);
460
+ this.pendingTracks.delete(mid);
339
461
  switch (descriptor.source) {
340
462
  case "camera":
463
+ console.log(
464
+ "[Subscriber] Updating camera for:",
465
+ descriptor.publisher_id
466
+ );
341
467
  this.state.updateParticipantMedia(descriptor.publisher_id, {
342
468
  stream,
343
469
  cameraTrack: event.track
344
470
  });
345
471
  break;
472
+ case "audio":
473
+ console.log(
474
+ "[Subscriber] Updating audio for:",
475
+ descriptor.publisher_id
476
+ );
477
+ this.state.updateParticipantMedia(descriptor.publisher_id, {
478
+ stream,
479
+ audioTrack: event.track
480
+ });
481
+ break;
346
482
  case "screen":
483
+ console.log(
484
+ "[Subscriber] Updating screen for:",
485
+ descriptor.publisher_id
486
+ );
347
487
  this.state.updateParticipantMedia(descriptor.publisher_id, {
348
488
  screenStream: stream,
349
489
  screenTrack: event.track,
350
490
  isScreenSharing: true
351
491
  });
352
492
  break;
353
- case "audio":
354
- this.state.updateParticipantMedia(descriptor.publisher_id, {
355
- stream,
356
- audioTrack: event.track
357
- });
358
- break;
359
493
  }
360
494
  };
361
495
  this.subPC.onconnectionstatechange = () => {
362
- console.log(`[SFU Subscriber PC State]`, this.subPC?.connectionState);
496
+ console.log("[SFU Subscriber PC State]", this.subPC?.connectionState);
497
+ if (this.subPC?.connectionState === "failed") {
498
+ console.warn("Subscriber connection failed");
499
+ }
363
500
  };
364
501
  }
365
502
  // ---------------- WEBSOCKET CONNECTION & SIGNALING ----------------
@@ -422,6 +559,19 @@ var VideoSDKCore = class {
422
559
  this.setupPublisherPC();
423
560
  this.setupSubscriberPC();
424
561
  await this.createPublisherOffer();
562
+ const media = this.state.localParticipant?.media;
563
+ if (media) {
564
+ this.send({
565
+ type: "MEDIA_STATE",
566
+ kind: "audio",
567
+ enabled: !!media.micEnabled
568
+ });
569
+ this.send({
570
+ type: "MEDIA_STATE",
571
+ kind: "video",
572
+ enabled: !!media.camEnabled
573
+ });
574
+ }
425
575
  this.startHeartbeat();
426
576
  this.joinResolver?.();
427
577
  this.joinResolver = void 0;
@@ -438,20 +588,26 @@ var VideoSDKCore = class {
438
588
  break;
439
589
  }
440
590
  case "SUB_OFFER": {
441
- this.pendingTracks.push(msg.track);
442
- if (this.subPC) {
443
- await this.subPC.setRemoteDescription({
444
- type: "offer",
445
- sdp: msg.payload
446
- });
447
- const answer = await this.subPC.createAnswer();
448
- await this.subPC.setLocalDescription(answer);
449
- this.send({
450
- type: "SUB_ANSWER",
451
- payload: answer.sdp,
452
- user_id: this.myId
453
- });
591
+ if (!this.subPC) {
592
+ console.warn("Subscriber PC not ready");
593
+ return;
594
+ }
595
+ const descriptor = msg.track;
596
+ const mid = descriptor.mid;
597
+ console.log(
598
+ "[Signaling] Received SUB_OFFER with descriptor:",
599
+ descriptor
600
+ );
601
+ if (mid) {
602
+ this.pendingTracks.set(mid, descriptor);
603
+ console.log("[Signaling] Stored pending track for MID:", mid);
604
+ }
605
+ if (this.subscriberNegotiating) {
606
+ console.warn("Subscriber negotiating, queueing offer");
607
+ this.subscriberOfferQueue.push(msg);
608
+ return;
454
609
  }
610
+ await this.handleSubscriberOffer(msg);
455
611
  break;
456
612
  }
457
613
  case "PUB_ICE": {
@@ -598,6 +754,31 @@ var VideoSDKCore = class {
598
754
  }
599
755
  }
600
756
  }
757
+ async handleSubscriberOffer(msg) {
758
+ if (!this.subPC) return;
759
+ try {
760
+ this.subscriberNegotiating = true;
761
+ await this.subPC.setRemoteDescription({
762
+ type: "offer",
763
+ sdp: msg.payload
764
+ });
765
+ const answer = await this.subPC.createAnswer();
766
+ await this.subPC.setLocalDescription(answer);
767
+ this.send({
768
+ type: "SUB_ANSWER",
769
+ payload: answer.sdp,
770
+ user_id: this.myId
771
+ });
772
+ } catch (err) {
773
+ console.error("[SUB OFFER ERROR]", err);
774
+ } finally {
775
+ this.subscriberNegotiating = false;
776
+ const next = this.subscriberOfferQueue.shift();
777
+ if (next) {
778
+ await this.handleSubscriberOffer(next);
779
+ }
780
+ }
781
+ }
601
782
  // ---------------- PUBLISHER RENEGOTIATION ----------------
602
783
  async createPublisherOffer() {
603
784
  if (!this.pubPC) return;