@afosecure/meetingsdk 1.2.9 → 1.3.1

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
@@ -177,7 +177,8 @@ var VideoSDKCore = class {
177
177
  this.localStream = null;
178
178
  this.screenStream = null;
179
179
  this.isScreenSharing = false;
180
- this.screenSenders = {};
180
+ // Transceiver-based tracking: maps peerId -> { cameraTransceiver, screenTransceiver, screenMid }
181
+ this.peerTransceivers = {};
181
182
  this.pingInterval = null;
182
183
  this.pendingIceCandidates = {};
183
184
  this.pendingOffers = {};
@@ -206,7 +207,7 @@ var VideoSDKCore = class {
206
207
  this.joinRejecter = void 0;
207
208
  console.error("[MeetingSDK Error]", err);
208
209
  }
209
- // ---------------- STREAM ----------------
210
+ // STREAM
210
211
  async initLocal(video, name) {
211
212
  this.participantName = name;
212
213
  try {
@@ -243,7 +244,7 @@ var VideoSDKCore = class {
243
244
  throw err;
244
245
  }
245
246
  }
246
- // ---------------- CONNECT ----------------
247
+ // CONNECT
247
248
  async connect(roomId, name) {
248
249
  this.room.id = roomId;
249
250
  this.reset();
@@ -389,10 +390,11 @@ var VideoSDKCore = class {
389
390
  this.pingInterval = null;
390
391
  }
391
392
  }
392
- // ---------------- RESET ----------------
393
+ // RESET
393
394
  reset() {
394
395
  Object.values(this.peers).forEach((pc) => pc.close());
395
396
  this.peers = {};
397
+ this.peerTransceivers = {};
396
398
  this.initiators.clear();
397
399
  this.pendingIceCandidates = {};
398
400
  this.state.resetRemoteState();
@@ -447,6 +449,7 @@ var VideoSDKCore = class {
447
449
  type: "answer",
448
450
  sdp: msg.payload
449
451
  });
452
+ this.captureScreenMid(msg.sender);
450
453
  await this.flushIce(msg.sender, pc);
451
454
  } catch (err) {
452
455
  console.error("[Signaling] Failed to apply answer:", err);
@@ -492,11 +495,14 @@ var VideoSDKCore = class {
492
495
  console.log(
493
496
  `[Existing Users] ${p.name} is sharing screen (stream: ${p.remoteScreenStreamId})`
494
497
  );
495
- this.state.setPresenterId(p.presenterId || null);
498
+ this.state.setPresenterId(p.id);
496
499
  this.state.updateParticipantMedia(p.id, {
497
500
  isScreenSharing: true,
498
501
  remoteScreenStreamId: p.remoteScreenStreamId
499
502
  });
503
+ console.log(
504
+ `[EXISTING_USERS] Pre-seeded screen state for ${p.id}, waiting for ontrack`
505
+ );
500
506
  }
501
507
  if (this.shouldInitiate(p.id)) {
502
508
  await this.createOffer(p.id);
@@ -562,12 +568,10 @@ var VideoSDKCore = class {
562
568
  });
563
569
  break;
564
570
  }
565
- // ============ NEW: HANDLE JOIN_APPROVED WITH RECONNECT ============
566
571
  case "JOIN_APPROVED": {
567
572
  await this.handleJoinApproved(msg);
568
573
  break;
569
574
  }
570
- // ============ END: JOIN_APPROVED ============
571
575
  case "JOIN_REJECTED": {
572
576
  const decision = "rejected";
573
577
  console.log("JOIN_REJECTED - user not allowed to join");
@@ -620,8 +624,7 @@ var VideoSDKCore = class {
620
624
  if (!this.state.presenterId) {
621
625
  this.state.setPresenterId(peerId2);
622
626
  }
623
- const screenStream = this.state.getParticipant(peerId2)?.media?.screenStream;
624
- this.events.onScreenShareStarted?.(peerId2, screenStream || null);
627
+ this.events.onScreenShareStarted?.(peerId2, null);
625
628
  break;
626
629
  }
627
630
  case "SCREEN_SHARE_STOP": {
@@ -648,8 +651,17 @@ var VideoSDKCore = class {
648
651
  }
649
652
  }
650
653
  }
651
- // ---------------- PEER ----------------
652
- createPeer(id) {
654
+ // PEER
655
+ /**
656
+ * Create a peer connection with pre-established transceiver layout:
657
+ * - Audio transceiver (sendrecv)
658
+ * - Camera video transceiver (sendrecv)
659
+ * - Screen video transceiver (initially recvonly, becomes sendrecv when sharing)
660
+ *
661
+ * This fixed layout ensures late joiners get the screen transceiver m-line
662
+ * negotiated from the very first offer, even if no one is sharing yet.
663
+ */
664
+ async createPeer(id) {
653
665
  if (!this.localStream) throw new Error("No local stream");
654
666
  if (!this.iceServers || this.iceServers.length === 0) {
655
667
  throw new Error(
@@ -657,7 +669,9 @@ var VideoSDKCore = class {
657
669
  );
658
670
  }
659
671
  console.log(
660
- "Adding tracks",
672
+ "Creating peer connection for",
673
+ id,
674
+ "with tracks:",
661
675
  this.localStream.getTracks().map((t) => ({
662
676
  kind: t.kind,
663
677
  enabled: t.enabled,
@@ -667,20 +681,52 @@ var VideoSDKCore = class {
667
681
  const pc = new RTCPeerConnection({
668
682
  iceServers: this.iceServers
669
683
  });
684
+ const audioTransceiver = pc.addTransceiver("audio", {
685
+ direction: "sendrecv"
686
+ });
687
+ const audioTrack = this.localStream.getAudioTracks()[0];
688
+ if (audioTrack) {
689
+ await audioTransceiver.sender.replaceTrack(audioTrack);
690
+ }
691
+ const cameraTransceiver = pc.addTransceiver("video", {
692
+ direction: "sendrecv"
693
+ });
694
+ const videoTrack = this.localStream.getVideoTracks()[0];
695
+ if (videoTrack) {
696
+ await cameraTransceiver.sender.replaceTrack(videoTrack);
697
+ }
698
+ const screenTransceiver = pc.addTransceiver("video", {
699
+ direction: this.isScreenSharing ? "sendrecv" : "recvonly"
700
+ });
701
+ if (this.isScreenSharing && this.screenStream) {
702
+ const screenTrack = this.screenStream.getVideoTracks()[0];
703
+ if (screenTrack) {
704
+ await screenTransceiver.sender.replaceTrack(screenTrack);
705
+ }
706
+ }
707
+ this.peerTransceivers[id] = {
708
+ cameraTransceiver,
709
+ screenTransceiver,
710
+ screenMid: null
711
+ // will be populated after negotiation
712
+ };
670
713
  pc.ontrack = (event) => {
714
+ const transceiver = event.transceiver;
715
+ const isScreenTrack = transceiver.mid === this.peerTransceivers[id]?.screenMid;
716
+ console.log(
717
+ `[ontrack] ${id}: kind=${event.track.kind}, mid=${transceiver.mid}, isScreen=${isScreenTrack}`
718
+ );
671
719
  const incomingStream = event.streams?.[0] || new MediaStream([event.track]);
672
- const participant = this.state.getParticipant(id);
673
- const isScreenStream = incomingStream.id === participant?.media?.remoteScreenStreamId;
674
720
  if (event.track.muted) {
675
721
  event.track.onunmute = () => {
676
- console.log(`${event.track.kind} track unmuted for ${id}`);
722
+ console.log(`[ontrack] ${event.track.kind} track unmuted for ${id}`);
677
723
  };
678
724
  }
679
- if (isScreenStream) {
680
- const videoTrack = event.track.kind === "video" ? event.track : incomingStream.getVideoTracks()[0] || participant?.media?.screenTrack;
725
+ if (isScreenTrack) {
726
+ const videoTrack2 = event.track.kind === "video" ? event.track : incomingStream.getVideoTracks()[0];
681
727
  this.state.updateParticipantMedia(id, {
682
728
  screenStream: incomingStream,
683
- screenTrack: videoTrack,
729
+ screenTrack: videoTrack2,
684
730
  isScreenSharing: true
685
731
  });
686
732
  if (!this.state.presenterId) {
@@ -706,29 +752,41 @@ var VideoSDKCore = class {
706
752
  });
707
753
  };
708
754
  pc.oniceconnectionstatechange = () => {
709
- console.log(`ICE Connection State: ${pc.iceConnectionState}`);
755
+ console.log(`[ICE Connection] ${id}: ${pc.iceConnectionState}`);
710
756
  };
711
757
  pc.onconnectionstatechange = () => {
758
+ console.log(`[Connection] ${id}: ${pc.connectionState}`);
712
759
  if (pc.connectionState === "failed") {
713
760
  try {
714
761
  pc.restartIce();
715
- } catch {
762
+ } catch (e) {
763
+ console.warn("Failed to restart ICE:", e);
716
764
  }
717
765
  }
718
766
  };
719
- this.localStream.getTracks().forEach((track) => {
720
- pc.addTrack(track, this.localStream);
721
- });
722
- if (this.isScreenSharing && this.screenStream) {
723
- this.screenSenders[id] = [];
724
- this.screenStream.getTracks().forEach((track) => {
725
- const sender = pc.addTrack(track, this.screenStream);
726
- this.screenSenders[id].push(sender);
727
- });
728
- }
729
767
  return pc;
730
768
  }
731
- // ---------------- OFFER ----------------
769
+ /**
770
+ * Capture the screen transceiver's MID after SDP negotiation completes.
771
+ * The MID is assigned during negotiation and is stable for the life of the connection.
772
+ */
773
+ captureScreenMid(peerId) {
774
+ const pc = this.peers[peerId];
775
+ if (!pc) return;
776
+ const transceivers = pc.getTransceivers();
777
+ const screenTransceiver = this.peerTransceivers[peerId]?.screenTransceiver;
778
+ if (!screenTransceiver) return;
779
+ const negotiatedTransceiver = transceivers.find(
780
+ (t) => t === screenTransceiver
781
+ );
782
+ if (negotiatedTransceiver?.mid) {
783
+ this.peerTransceivers[peerId].screenMid = negotiatedTransceiver.mid;
784
+ console.log(
785
+ `[Negotiation] Captured screenMid for ${peerId}: ${negotiatedTransceiver.mid}`
786
+ );
787
+ }
788
+ }
789
+ // OFFER
732
790
  async createOffer(id, isRenegotiation = false) {
733
791
  if (!isRenegotiation && !this.shouldInitiate(id)) {
734
792
  console.debug(
@@ -746,12 +804,13 @@ var VideoSDKCore = class {
746
804
  this.initiators.add(id);
747
805
  }
748
806
  if (!this.peers[id]) {
749
- this.peers[id] = this.createPeer(id);
807
+ this.peers[id] = await this.createPeer(id);
750
808
  }
751
809
  const pc = this.peers[id];
752
810
  try {
753
811
  const offer = await pc.createOffer();
754
812
  await pc.setLocalDescription(offer);
813
+ this.captureScreenMid(id);
755
814
  this.send({
756
815
  type: "OFFER",
757
816
  payload: offer.sdp,
@@ -761,12 +820,18 @@ var VideoSDKCore = class {
761
820
  console.debug(`[Offer] Sent to ${id}`);
762
821
  } catch (err) {
763
822
  console.error(`[Offer] Failed for ${id}:`, err);
823
+ this.emitError(
824
+ "OFFER_CREATION_FAILED",
825
+ `Failed to create offer for ${id}`,
826
+ err,
827
+ true
828
+ );
764
829
  }
765
830
  }
766
831
  shouldInitiate(peerId) {
767
832
  return this.myId < peerId;
768
833
  }
769
- // ---------------- ANSWER ----------------
834
+ // ANSWER
770
835
  async handleOffer(sdp, id) {
771
836
  if (!this.iceServers || this.iceServers.length === 0) {
772
837
  console.warn("[Offer] Waiting for iceServers, queuing offer from", id);
@@ -774,7 +839,7 @@ var VideoSDKCore = class {
774
839
  return;
775
840
  }
776
841
  if (!this.peers[id]) {
777
- this.peers[id] = this.createPeer(id);
842
+ this.peers[id] = await this.createPeer(id);
778
843
  }
779
844
  const pc = this.peers[id];
780
845
  try {
@@ -790,8 +855,9 @@ var VideoSDKCore = class {
790
855
  );
791
856
  pc.close();
792
857
  delete this.peers[id];
858
+ delete this.peerTransceivers[id];
793
859
  this.initiators.delete(id);
794
- this.peers[id] = this.createPeer(id);
860
+ this.peers[id] = await this.createPeer(id);
795
861
  }
796
862
  }
797
863
  if (this.peers[id].signalingState !== "stable" && this.peers[id].signalingState !== "have-local-offer") {
@@ -804,6 +870,7 @@ var VideoSDKCore = class {
804
870
  type: "offer",
805
871
  sdp
806
872
  });
873
+ this.captureScreenMid(id);
807
874
  const pending = this.pendingIceCandidates[id] || [];
808
875
  for (const candidate of pending) {
809
876
  try {
@@ -833,18 +900,26 @@ var VideoSDKCore = class {
833
900
  );
834
901
  }
835
902
  }
836
- // ---------------- CLEANUP ----------------
903
+ // CLEANUP
837
904
  closePeer(id) {
838
905
  const pc = this.peers[id];
839
906
  if (!pc) return;
840
907
  pc.ontrack = null;
841
908
  pc.onicecandidate = null;
842
909
  pc.onconnectionstatechange = null;
910
+ pc.oniceconnectionstatechange = null;
843
911
  pc.close();
844
912
  delete this.peers[id];
913
+ delete this.peerTransceivers[id];
845
914
  this.initiators.delete(id);
846
915
  this.state.removeParticipant(id);
847
916
  }
917
+ // SCREEN SHARE (TRANSCEIVER-BASED)
918
+ /**
919
+ * Start screen sharing using replaceTrack on the pre-established screen transceiver.
920
+ * No need to add/remove tracks, no renegotiation needed (transceiver already in SDP).
921
+ * Just swap the track and update direction if needed.
922
+ */
848
923
  async startScreenShare() {
849
924
  try {
850
925
  if (this.state.presenterId && this.state.presenterId !== this.myId) {
@@ -855,34 +930,55 @@ var VideoSDKCore = class {
855
930
  }
856
931
  this.screenStream = await navigator.mediaDevices.getDisplayMedia({
857
932
  video: true
858
- // audio: true,
859
933
  });
934
+ const screenTrack = this.screenStream.getVideoTracks()[0];
935
+ if (!screenTrack) {
936
+ throw new Error("No video track in screen stream");
937
+ }
860
938
  this.isScreenSharing = true;
861
939
  this.state.updateLocalParticipant({
862
940
  media: {
863
941
  isScreenSharing: true,
864
942
  screenStream: this.screenStream,
865
- screenTrack: this.screenStream.getVideoTracks()[0]
943
+ screenTrack
866
944
  }
867
945
  });
868
946
  this.state.setPresenterId(this.myId);
869
- this.screenStream.getVideoTracks()[0].onended = () => {
947
+ screenTrack.onended = () => {
948
+ console.log("[Screen Share] User stopped via browser button");
870
949
  this.stopScreenShare();
871
950
  };
872
- Object.entries(this.peers).forEach(([peerId, pc]) => {
873
- this.screenSenders[peerId] = [];
874
- this.screenStream.getTracks().forEach((track) => {
875
- const sender = pc.addTrack(track, this.screenStream);
876
- this.screenSenders[peerId].push(sender);
877
- });
878
- this.createOffer(peerId, true);
879
- });
951
+ for (const [peerId, pc] of Object.entries(this.peers)) {
952
+ const txInfo = this.peerTransceivers[peerId];
953
+ if (!txInfo) {
954
+ console.warn(
955
+ `[Screen Share] No transceiver info for ${peerId}, skipping`
956
+ );
957
+ continue;
958
+ }
959
+ try {
960
+ await txInfo.screenTransceiver.sender.replaceTrack(screenTrack);
961
+ if (txInfo.screenTransceiver.currentDirection === "recvonly") {
962
+ txInfo.screenTransceiver.direction = "sendrecv";
963
+ console.log(
964
+ `[Screen Share] Flipped ${peerId} screen transceiver to sendrecv`
965
+ );
966
+ await this.createOffer(peerId, true);
967
+ }
968
+ } catch (err) {
969
+ console.error(
970
+ `[Screen Share] Failed to update transceiver for ${peerId}:`,
971
+ err
972
+ );
973
+ }
974
+ }
880
975
  this.send({
881
976
  type: "SCREEN_SHARE_START",
882
977
  sender: this.myId,
883
978
  room_id: this.room.id,
884
979
  stream_id: this.screenStream.id.replace(/[{}]/g, "")
885
980
  });
981
+ console.log("[Screen Share] Started successfully");
886
982
  return this.screenStream;
887
983
  } catch (err) {
888
984
  this.emitError(
@@ -896,21 +992,29 @@ var VideoSDKCore = class {
896
992
  throw err;
897
993
  }
898
994
  }
899
- stopScreenShare() {
995
+ async stopScreenShare() {
900
996
  if (!this.screenStream) return;
997
+ console.log("[Screen Share] Stopping...");
901
998
  this.screenStream.getTracks().forEach((t) => t.stop());
902
- Object.entries(this.peers).forEach(([peerId, pc]) => {
903
- const senders = this.screenSenders[peerId] || [];
904
- senders.forEach((sender) => {
905
- try {
906
- pc.removeTrack(sender);
907
- } catch (err) {
908
- console.warn(err);
999
+ for (const [peerId, pc] of Object.entries(this.peers)) {
1000
+ const txInfo = this.peerTransceivers[peerId];
1001
+ if (!txInfo) continue;
1002
+ try {
1003
+ await txInfo.screenTransceiver.sender.replaceTrack(null);
1004
+ if (txInfo.screenTransceiver.currentDirection === "sendrecv") {
1005
+ txInfo.screenTransceiver.direction = "recvonly";
1006
+ console.log(
1007
+ `[Screen Share] Flipped ${peerId} screen transceiver to recvonly`
1008
+ );
1009
+ await this.createOffer(peerId, true);
909
1010
  }
910
- });
911
- delete this.screenSenders[peerId];
912
- this.createOffer(peerId, true);
913
- });
1011
+ } catch (err) {
1012
+ console.error(
1013
+ `[Screen Share] Failed to clear transceiver for ${peerId}:`,
1014
+ err
1015
+ );
1016
+ }
1017
+ }
914
1018
  this.screenStream = null;
915
1019
  this.isScreenSharing = false;
916
1020
  this.state.updateLocalParticipant({
@@ -928,7 +1032,9 @@ var VideoSDKCore = class {
928
1032
  sender: this.myId,
929
1033
  room_id: this.room.id
930
1034
  });
1035
+ console.log("[Screen Share] Stopped");
931
1036
  }
1037
+ // CHAT
932
1038
  sendChatMessage(payload) {
933
1039
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
934
1040
  console.warn("WS not connected");
@@ -961,11 +1067,13 @@ var VideoSDKCore = class {
961
1067
  client_ts: Date.now()
962
1068
  });
963
1069
  }
1070
+ // DISCONNECT
964
1071
  disconnect() {
965
1072
  this.intentionalDisconnect = true;
966
1073
  this.stopScreenShare();
967
1074
  Object.values(this.peers).forEach((pc) => pc.close());
968
1075
  this.peers = {};
1076
+ this.peerTransceivers = {};
969
1077
  this.initiators.clear();
970
1078
  this.stopHeartbeat();
971
1079
  if (this.ws?.readyState === WebSocket.OPEN) {