@afosecure/meetingsdk 1.4.5 → 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
@@ -127,6 +127,9 @@ declare class VideoSDKCore {
127
127
  private ws;
128
128
  private pubPC;
129
129
  private subPC;
130
+ private pendingTracks;
131
+ private subscriberNegotiating;
132
+ private subscriberOfferQueue;
130
133
  private iceServers;
131
134
  private lastPong;
132
135
  private intentionalDisconnect;
@@ -147,17 +150,19 @@ declare class VideoSDKCore {
147
150
  private pendingRequestId;
148
151
  private iceTransportPolicy;
149
152
  constructor(events?: Events, url?: string);
153
+ private acquireLocalMedia;
150
154
  initLocal(video: HTMLVideoElement, name: string): Promise<void>;
151
155
  joinMeeting(config: MeetingConfig): Promise<void>;
152
156
  private setupPublisherPC;
153
157
  private setupSubscriberPC;
154
158
  connect(roomId: string, name: string): Promise<void>;
155
159
  private handle;
160
+ private handleSubscriberOffer;
156
161
  private createPublisherOffer;
157
162
  toggleMic(): void;
158
163
  toggleCam(): void;
159
164
  startScreenShare(): Promise<MediaStream>;
160
- stopScreenShare(): void;
165
+ stopScreenShare(): Promise<void>;
161
166
  sendChatMessage(payload: ChatInput): void;
162
167
  private scheduleReconnect;
163
168
  private startHeartbeat;
package/dist/index.d.ts CHANGED
@@ -127,6 +127,9 @@ declare class VideoSDKCore {
127
127
  private ws;
128
128
  private pubPC;
129
129
  private subPC;
130
+ private pendingTracks;
131
+ private subscriberNegotiating;
132
+ private subscriberOfferQueue;
130
133
  private iceServers;
131
134
  private lastPong;
132
135
  private intentionalDisconnect;
@@ -147,17 +150,19 @@ declare class VideoSDKCore {
147
150
  private pendingRequestId;
148
151
  private iceTransportPolicy;
149
152
  constructor(events?: Events, url?: string);
153
+ private acquireLocalMedia;
150
154
  initLocal(video: HTMLVideoElement, name: string): Promise<void>;
151
155
  joinMeeting(config: MeetingConfig): Promise<void>;
152
156
  private setupPublisherPC;
153
157
  private setupSubscriberPC;
154
158
  connect(roomId: string, name: string): Promise<void>;
155
159
  private handle;
160
+ private handleSubscriberOffer;
156
161
  private createPublisherOffer;
157
162
  toggleMic(): void;
158
163
  toggleCam(): void;
159
164
  startScreenShare(): Promise<MediaStream>;
160
- stopScreenShare(): void;
165
+ stopScreenShare(): Promise<void>;
161
166
  sendChatMessage(payload: ChatInput): void;
162
167
  private scheduleReconnect;
163
168
  private startHeartbeat;
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://rust-video-server-sfyf.onrender.com/ws",
44
- baseUrl: "https://rust-video-server-sfyf.onrender.com"
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,6 +200,9 @@ var VideoSDKCore = class {
200
200
  this.ws = null;
201
201
  this.pubPC = null;
202
202
  this.subPC = null;
203
+ this.pendingTracks = /* @__PURE__ */ new Map();
204
+ this.subscriberNegotiating = false;
205
+ this.subscriberOfferQueue = [];
203
206
  this.iceServers = [];
204
207
  this.lastPong = Date.now();
205
208
  this.intentionalDisconnect = false;
@@ -223,31 +226,84 @@ var VideoSDKCore = class {
223
226
  this.myId = localStorage.getItem("vsdk_id") || crypto.randomUUID();
224
227
  localStorage.setItem("vsdk_id", this.myId);
225
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
+ }
226
272
  // ---------------- MEDIA SETUP ----------------
227
273
  async initLocal(video, name) {
228
274
  this.participantName = name;
229
275
  try {
230
- this.localStream = await navigator.mediaDevices.getUserMedia({
231
- video: { width: { ideal: 1280 }, height: { ideal: 720 } },
232
- audio: {
276
+ const { stream, camEnabled, micEnabled } = await this.acquireLocalMedia({
277
+ videoConstraints: { width: { ideal: 1280 }, height: { ideal: 720 } },
278
+ audioConstraints: {
233
279
  echoCancellation: true,
234
280
  noiseSuppression: true,
235
281
  autoGainControl: true
236
282
  }
237
283
  });
238
- const hasVideo = this.localStream.getVideoTracks().some((t) => t.readyState === "live");
239
- const hasAudio = this.localStream.getAudioTracks().some((t) => t.readyState === "live");
240
- if (!hasVideo || !hasAudio) {
241
- 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;
242
295
  }
243
- video.srcObject = this.localStream;
296
+ const cameraTrack = this.localStream.getVideoTracks()[0] || void 0;
297
+ const audioTrack = this.localStream.getAudioTracks()[0] || void 0;
244
298
  this.state.updateLocalParticipant({
245
299
  id: this.myId,
246
300
  name: this.participantName,
247
301
  media: {
248
302
  stream: this.localStream,
249
- micEnabled: true,
250
- camEnabled: true,
303
+ cameraTrack,
304
+ audioTrack,
305
+ micEnabled,
306
+ camEnabled,
251
307
  isScreenSharing: false
252
308
  }
253
309
  });
@@ -263,21 +319,38 @@ var VideoSDKCore = class {
263
319
  throw new Error("roomId and name are required to join meeting");
264
320
  }
265
321
  this.participantName = name;
322
+ let camEnabled = !videoMuted;
323
+ let micEnabled = !audioMuted;
266
324
  if (!this.localStream) {
267
- this.localStream = await navigator.mediaDevices.getUserMedia({
268
- video: true,
269
- audio: true
325
+ const acquired = await this.acquireLocalMedia({
326
+ videoConstraints: !videoMuted,
327
+ audioConstraints: !audioMuted
270
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;
271
340
  }
272
- this.localStream.getAudioTracks().forEach((t) => t.enabled = !audioMuted);
273
- 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;
274
345
  this.state.updateLocalParticipant({
275
346
  id: this.myId,
276
347
  name: this.participantName,
277
348
  media: {
278
349
  stream: this.localStream,
279
- micEnabled: !audioMuted,
280
- camEnabled: !videoMuted,
350
+ cameraTrack,
351
+ audioTrack,
352
+ micEnabled,
353
+ camEnabled,
281
354
  isScreenSharing: false
282
355
  }
283
356
  });
@@ -291,9 +364,39 @@ var VideoSDKCore = class {
291
364
  iceServers: this.iceServers,
292
365
  iceTransportPolicy: this.iceTransportPolicy
293
366
  });
367
+ const tracksByKind = /* @__PURE__ */ new Map();
294
368
  this.localStream.getTracks().forEach((track) => {
295
- this.pubPC?.addTrack(track, this.localStream);
369
+ tracksByKind.set(track.kind, track);
296
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
+ }
297
400
  this.pubPC.onicecandidate = (e) => {
298
401
  if (e.candidate) {
299
402
  this.send({
@@ -304,8 +407,13 @@ var VideoSDKCore = class {
304
407
  }
305
408
  };
306
409
  this.pubPC.onconnectionstatechange = () => {
307
- 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
+ });
308
415
  if (this.pubPC?.connectionState === "failed") {
416
+ console.warn("[Publisher] Connection failed, restarting ICE");
309
417
  this.restartPublisherIce();
310
418
  }
311
419
  };
@@ -325,44 +433,70 @@ var VideoSDKCore = class {
325
433
  }
326
434
  };
327
435
  this.subPC.ontrack = (event) => {
328
- const incomingStream = event.streams[0] || new MediaStream([event.track]);
329
- const streamId = incomingStream.id.replace(/[{}]/g, "");
330
- let matchedParticipant;
331
- for (const p of this.state.participants.values()) {
332
- if (p.media?.cameraStreamId === streamId || p.media?.remoteScreenStreamId === streamId) {
333
- matchedParticipant = p;
334
- break;
335
- }
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;
336
447
  }
337
- if (!matchedParticipant) {
338
- console.warn(
339
- `[SFU ontrack] Dynamic track received for stream ${streamId}`
340
- );
448
+ console.log("[Subscriber] Looking for descriptor with MID:", mid, {
449
+ available: [...this.pendingTracks.keys()]
450
+ });
451
+ const descriptor = this.pendingTracks.get(mid);
452
+ if (!descriptor) {
453
+ console.warn("[Subscriber] No descriptor found for MID:", mid);
454
+ console.log("[Subscriber] Pending tracks:", [
455
+ ...this.pendingTracks.entries()
456
+ ]);
341
457
  return;
342
458
  }
343
- const pId = matchedParticipant.id;
344
- const isScreen = streamId === matchedParticipant.media?.remoteScreenStreamId;
345
- if (isScreen) {
346
- this.state.updateParticipantMedia(pId, {
347
- screenStream: incomingStream,
348
- screenTrack: event.track,
349
- isScreenSharing: true
350
- });
351
- if (!this.state.presenterId) {
352
- this.state.setPresenterId(pId);
353
- }
354
- this.events.onScreenShareStarted?.(pId, incomingStream);
355
- } else {
356
- this.state.updateParticipantMedia(pId, {
357
- stream: incomingStream,
358
- cameraTrack: incomingStream.getVideoTracks()[0],
359
- audioTrack: incomingStream.getAudioTracks()[0]
360
- });
361
- this.events.onTrack?.(incomingStream, pId);
459
+ console.log("[Subscriber] Found descriptor:", descriptor);
460
+ this.pendingTracks.delete(mid);
461
+ switch (descriptor.source) {
462
+ case "camera":
463
+ console.log(
464
+ "[Subscriber] Updating camera for:",
465
+ descriptor.publisher_id
466
+ );
467
+ this.state.updateParticipantMedia(descriptor.publisher_id, {
468
+ stream,
469
+ cameraTrack: event.track
470
+ });
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;
482
+ case "screen":
483
+ console.log(
484
+ "[Subscriber] Updating screen for:",
485
+ descriptor.publisher_id
486
+ );
487
+ this.state.updateParticipantMedia(descriptor.publisher_id, {
488
+ screenStream: stream,
489
+ screenTrack: event.track,
490
+ isScreenSharing: true
491
+ });
492
+ break;
362
493
  }
363
494
  };
364
495
  this.subPC.onconnectionstatechange = () => {
365
- 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
+ }
366
500
  };
367
501
  }
368
502
  // ---------------- WEBSOCKET CONNECTION & SIGNALING ----------------
@@ -425,13 +559,26 @@ var VideoSDKCore = class {
425
559
  this.setupPublisherPC();
426
560
  this.setupSubscriberPC();
427
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
+ }
428
575
  this.startHeartbeat();
429
576
  this.joinResolver?.();
430
577
  this.joinResolver = void 0;
431
578
  this.joinRejecter = void 0;
432
579
  break;
433
580
  }
434
- case "SFU_PUB_ANSWER": {
581
+ case "PUB_ANSWER": {
435
582
  if (this.pubPC) {
436
583
  await this.pubPC.setRemoteDescription({
437
584
  type: "answer",
@@ -440,20 +587,27 @@ var VideoSDKCore = class {
440
587
  }
441
588
  break;
442
589
  }
443
- case "SFU_SUB_OFFER": {
444
- if (this.subPC) {
445
- await this.subPC.setRemoteDescription({
446
- type: "offer",
447
- sdp: msg.payload
448
- });
449
- const answer = await this.subPC.createAnswer();
450
- await this.subPC.setLocalDescription(answer);
451
- this.send({
452
- type: "SUB_ANSWER",
453
- payload: answer.sdp,
454
- user_id: this.myId
455
- });
590
+ case "SUB_OFFER": {
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;
456
609
  }
610
+ await this.handleSubscriberOffer(msg);
457
611
  break;
458
612
  }
459
613
  case "PUB_ICE": {
@@ -600,6 +754,31 @@ var VideoSDKCore = class {
600
754
  }
601
755
  }
602
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
+ }
603
782
  // ---------------- PUBLISHER RENEGOTIATION ----------------
604
783
  async createPublisherOffer() {
605
784
  if (!this.pubPC) return;
@@ -652,6 +831,9 @@ var VideoSDKCore = class {
652
831
  });
653
832
  this.isScreenSharing = true;
654
833
  const screenTrack = this.screenStream.getVideoTracks()[0];
834
+ Object.defineProperty(screenTrack, "contentHint", {
835
+ value: "detail"
836
+ });
655
837
  if (this.pubPC) {
656
838
  this.screenSender = this.pubPC.addTrack(screenTrack, this.screenStream);
657
839
  await this.createPublisherOffer();
@@ -671,7 +853,6 @@ var VideoSDKCore = class {
671
853
  type: "SCREEN_SHARE_START",
672
854
  sender: this.myId,
673
855
  room_id: this.room.id,
674
- camera_id: this.localStream?.id.replace(/[{}]/g, ""),
675
856
  stream_id: this.screenStream.id.replace(/[{}]/g, "")
676
857
  });
677
858
  return this.screenStream;
@@ -687,17 +868,13 @@ var VideoSDKCore = class {
687
868
  throw err;
688
869
  }
689
870
  }
690
- stopScreenShare() {
871
+ async stopScreenShare() {
691
872
  if (!this.screenStream) return;
692
873
  this.screenStream.getTracks().forEach((t) => t.stop());
693
874
  if (this.pubPC && this.screenSender) {
694
- try {
695
- this.pubPC.removeTrack(this.screenSender);
696
- this.createPublisherOffer();
697
- } catch (e) {
698
- console.warn("Failed removing screen sender", e);
699
- }
875
+ this.pubPC.removeTrack(this.screenSender);
700
876
  this.screenSender = null;
877
+ await this.createPublisherOffer();
701
878
  }
702
879
  this.screenStream = null;
703
880
  this.isScreenSharing = false;