@afosecure/meetingsdk 1.3.0 → 1.3.2

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);
@@ -483,7 +486,6 @@ var VideoSDKCore = class {
483
486
  if (msg.presenterId) {
484
487
  this.state.setPresenterId(msg.presenterId);
485
488
  this.events.onScreenShareStarted?.(msg.presenterId, null);
486
- this.state.setPresenterId(msg.presenterId);
487
489
  }
488
490
  for (const p of msg.participants || []) {
489
491
  if (!p?.id || p.id === this.myId) continue;
@@ -566,12 +568,10 @@ var VideoSDKCore = class {
566
568
  });
567
569
  break;
568
570
  }
569
- // ============ NEW: HANDLE JOIN_APPROVED WITH RECONNECT ============
570
571
  case "JOIN_APPROVED": {
571
572
  await this.handleJoinApproved(msg);
572
573
  break;
573
574
  }
574
- // ============ END: JOIN_APPROVED ============
575
575
  case "JOIN_REJECTED": {
576
576
  const decision = "rejected";
577
577
  console.log("JOIN_REJECTED - user not allowed to join");
@@ -624,8 +624,7 @@ var VideoSDKCore = class {
624
624
  if (!this.state.presenterId) {
625
625
  this.state.setPresenterId(peerId2);
626
626
  }
627
- const screenStream = this.state.getParticipant(peerId2)?.media?.screenStream;
628
- this.events.onScreenShareStarted?.(peerId2, screenStream || null);
627
+ this.events.onScreenShareStarted?.(peerId2, null);
629
628
  break;
630
629
  }
631
630
  case "SCREEN_SHARE_STOP": {
@@ -652,8 +651,7 @@ var VideoSDKCore = class {
652
651
  }
653
652
  }
654
653
  }
655
- // ---------------- PEER ----------------
656
- createPeer(id) {
654
+ async createPeer(id) {
657
655
  if (!this.localStream) throw new Error("No local stream");
658
656
  if (!this.iceServers || this.iceServers.length === 0) {
659
657
  throw new Error(
@@ -661,7 +659,9 @@ var VideoSDKCore = class {
661
659
  );
662
660
  }
663
661
  console.log(
664
- "Adding tracks",
662
+ "Creating peer connection for",
663
+ id,
664
+ "with tracks:",
665
665
  this.localStream.getTracks().map((t) => ({
666
666
  kind: t.kind,
667
667
  enabled: t.enabled,
@@ -671,20 +671,52 @@ var VideoSDKCore = class {
671
671
  const pc = new RTCPeerConnection({
672
672
  iceServers: this.iceServers
673
673
  });
674
+ const audioTransceiver = pc.addTransceiver("audio", {
675
+ direction: "sendrecv"
676
+ });
677
+ const audioTrack = this.localStream.getAudioTracks()[0];
678
+ if (audioTrack) {
679
+ await audioTransceiver.sender.replaceTrack(audioTrack);
680
+ }
681
+ const cameraTransceiver = pc.addTransceiver("video", {
682
+ direction: "sendrecv"
683
+ });
684
+ const videoTrack = this.localStream.getVideoTracks()[0];
685
+ if (videoTrack) {
686
+ await cameraTransceiver.sender.replaceTrack(videoTrack);
687
+ }
688
+ const screenTransceiver = pc.addTransceiver("video", {
689
+ direction: this.isScreenSharing ? "sendrecv" : "recvonly"
690
+ });
691
+ if (this.isScreenSharing && this.screenStream) {
692
+ const screenTrack = this.screenStream.getVideoTracks()[0];
693
+ if (screenTrack) {
694
+ await screenTransceiver.sender.replaceTrack(screenTrack);
695
+ }
696
+ }
697
+ this.peerTransceivers[id] = {
698
+ cameraTransceiver,
699
+ screenTransceiver,
700
+ screenMid: null
701
+ // will be populated after negotiation
702
+ };
674
703
  pc.ontrack = (event) => {
704
+ const transceiver = event.transceiver;
705
+ const isScreenTrack = transceiver === this.peerTransceivers[id]?.screenTransceiver;
706
+ console.log(
707
+ `[ontrack] ${id}: kind=${event.track.kind}, mid=${transceiver.mid}, isScreen=${isScreenTrack}`
708
+ );
675
709
  const incomingStream = event.streams?.[0] || new MediaStream([event.track]);
676
- const participant = this.state.getParticipant(id);
677
- const isScreenStream = participant?.media?.isScreenSharing && incomingStream.id === participant?.media?.remoteScreenStreamId;
678
710
  if (event.track.muted) {
679
711
  event.track.onunmute = () => {
680
- console.log(`${event.track.kind} track unmuted for ${id}`);
712
+ console.log(`[ontrack] ${event.track.kind} track unmuted for ${id}`);
681
713
  };
682
714
  }
683
- if (isScreenStream) {
684
- const videoTrack = event.track.kind === "video" ? event.track : incomingStream.getVideoTracks()[0] || participant?.media?.screenTrack;
715
+ if (isScreenTrack) {
716
+ const videoTrack2 = event.track.kind === "video" ? event.track : incomingStream.getVideoTracks()[0];
685
717
  this.state.updateParticipantMedia(id, {
686
718
  screenStream: incomingStream,
687
- screenTrack: videoTrack,
719
+ screenTrack: videoTrack2,
688
720
  isScreenSharing: true
689
721
  });
690
722
  if (!this.state.presenterId) {
@@ -710,29 +742,41 @@ var VideoSDKCore = class {
710
742
  });
711
743
  };
712
744
  pc.oniceconnectionstatechange = () => {
713
- console.log(`ICE Connection State: ${pc.iceConnectionState}`);
745
+ console.log(`[ICE Connection] ${id}: ${pc.iceConnectionState}`);
714
746
  };
715
747
  pc.onconnectionstatechange = () => {
748
+ console.log(`[Connection] ${id}: ${pc.connectionState}`);
716
749
  if (pc.connectionState === "failed") {
717
750
  try {
718
751
  pc.restartIce();
719
- } catch {
752
+ } catch (e) {
753
+ console.warn("Failed to restart ICE:", e);
720
754
  }
721
755
  }
722
756
  };
723
- this.localStream.getTracks().forEach((track) => {
724
- pc.addTrack(track, this.localStream);
725
- });
726
- if (this.isScreenSharing && this.screenStream) {
727
- this.screenSenders[id] = [];
728
- this.screenStream.getTracks().forEach((track) => {
729
- const sender = pc.addTrack(track, this.screenStream);
730
- this.screenSenders[id].push(sender);
731
- });
732
- }
733
757
  return pc;
734
758
  }
735
- // ---------------- OFFER ----------------
759
+ /**
760
+ * Capture the screen transceiver's MID after SDP negotiation completes.
761
+ * The MID is assigned during negotiation and is stable for the life of the connection.
762
+ */
763
+ captureScreenMid(peerId) {
764
+ const pc = this.peers[peerId];
765
+ if (!pc) return;
766
+ const transceivers = pc.getTransceivers();
767
+ const screenTransceiver = this.peerTransceivers[peerId]?.screenTransceiver;
768
+ if (!screenTransceiver) return;
769
+ const negotiatedTransceiver = transceivers.find(
770
+ (t) => t === screenTransceiver
771
+ );
772
+ if (negotiatedTransceiver?.mid) {
773
+ this.peerTransceivers[peerId].screenMid = negotiatedTransceiver.mid;
774
+ console.log(
775
+ `[Negotiation] Captured screenMid for ${peerId}: ${negotiatedTransceiver.mid}`
776
+ );
777
+ }
778
+ }
779
+ // OFFER
736
780
  async createOffer(id, isRenegotiation = false) {
737
781
  if (!isRenegotiation && !this.shouldInitiate(id)) {
738
782
  console.debug(
@@ -750,12 +794,13 @@ var VideoSDKCore = class {
750
794
  this.initiators.add(id);
751
795
  }
752
796
  if (!this.peers[id]) {
753
- this.peers[id] = this.createPeer(id);
797
+ this.peers[id] = await this.createPeer(id);
754
798
  }
755
799
  const pc = this.peers[id];
756
800
  try {
757
801
  const offer = await pc.createOffer();
758
802
  await pc.setLocalDescription(offer);
803
+ this.captureScreenMid(id);
759
804
  this.send({
760
805
  type: "OFFER",
761
806
  payload: offer.sdp,
@@ -765,12 +810,18 @@ var VideoSDKCore = class {
765
810
  console.debug(`[Offer] Sent to ${id}`);
766
811
  } catch (err) {
767
812
  console.error(`[Offer] Failed for ${id}:`, err);
813
+ this.emitError(
814
+ "OFFER_CREATION_FAILED",
815
+ `Failed to create offer for ${id}`,
816
+ err,
817
+ true
818
+ );
768
819
  }
769
820
  }
770
821
  shouldInitiate(peerId) {
771
822
  return this.myId < peerId;
772
823
  }
773
- // ---------------- ANSWER ----------------
824
+ // ANSWER
774
825
  async handleOffer(sdp, id) {
775
826
  if (!this.iceServers || this.iceServers.length === 0) {
776
827
  console.warn("[Offer] Waiting for iceServers, queuing offer from", id);
@@ -778,7 +829,7 @@ var VideoSDKCore = class {
778
829
  return;
779
830
  }
780
831
  if (!this.peers[id]) {
781
- this.peers[id] = this.createPeer(id);
832
+ this.peers[id] = await this.createPeer(id);
782
833
  }
783
834
  const pc = this.peers[id];
784
835
  try {
@@ -794,8 +845,9 @@ var VideoSDKCore = class {
794
845
  );
795
846
  pc.close();
796
847
  delete this.peers[id];
848
+ delete this.peerTransceivers[id];
797
849
  this.initiators.delete(id);
798
- this.peers[id] = this.createPeer(id);
850
+ this.peers[id] = await this.createPeer(id);
799
851
  }
800
852
  }
801
853
  if (this.peers[id].signalingState !== "stable" && this.peers[id].signalingState !== "have-local-offer") {
@@ -808,6 +860,7 @@ var VideoSDKCore = class {
808
860
  type: "offer",
809
861
  sdp
810
862
  });
863
+ this.captureScreenMid(id);
811
864
  const pending = this.pendingIceCandidates[id] || [];
812
865
  for (const candidate of pending) {
813
866
  try {
@@ -837,18 +890,26 @@ var VideoSDKCore = class {
837
890
  );
838
891
  }
839
892
  }
840
- // ---------------- CLEANUP ----------------
893
+ // CLEANUP
841
894
  closePeer(id) {
842
895
  const pc = this.peers[id];
843
896
  if (!pc) return;
844
897
  pc.ontrack = null;
845
898
  pc.onicecandidate = null;
846
899
  pc.onconnectionstatechange = null;
900
+ pc.oniceconnectionstatechange = null;
847
901
  pc.close();
848
902
  delete this.peers[id];
903
+ delete this.peerTransceivers[id];
849
904
  this.initiators.delete(id);
850
905
  this.state.removeParticipant(id);
851
906
  }
907
+ // SCREEN SHARE (TRANSCEIVER-BASED)
908
+ /**
909
+ * Start screen sharing using replaceTrack on the pre-established screen transceiver.
910
+ * No need to add/remove tracks, no renegotiation needed (transceiver already in SDP).
911
+ * Just swap the track and update direction if needed.
912
+ */
852
913
  async startScreenShare() {
853
914
  try {
854
915
  if (this.state.presenterId && this.state.presenterId !== this.myId) {
@@ -859,34 +920,55 @@ var VideoSDKCore = class {
859
920
  }
860
921
  this.screenStream = await navigator.mediaDevices.getDisplayMedia({
861
922
  video: true
862
- // audio: true,
863
923
  });
924
+ const screenTrack = this.screenStream.getVideoTracks()[0];
925
+ if (!screenTrack) {
926
+ throw new Error("No video track in screen stream");
927
+ }
864
928
  this.isScreenSharing = true;
865
929
  this.state.updateLocalParticipant({
866
930
  media: {
867
931
  isScreenSharing: true,
868
932
  screenStream: this.screenStream,
869
- screenTrack: this.screenStream.getVideoTracks()[0]
933
+ screenTrack
870
934
  }
871
935
  });
872
936
  this.state.setPresenterId(this.myId);
873
- this.screenStream.getVideoTracks()[0].onended = () => {
937
+ screenTrack.onended = () => {
938
+ console.log("[Screen Share] User stopped via browser button");
874
939
  this.stopScreenShare();
875
940
  };
876
- Object.entries(this.peers).forEach(([peerId, pc]) => {
877
- this.screenSenders[peerId] = [];
878
- this.screenStream.getTracks().forEach((track) => {
879
- const sender = pc.addTrack(track, this.screenStream);
880
- this.screenSenders[peerId].push(sender);
881
- });
882
- this.createOffer(peerId, true);
883
- });
941
+ for (const [peerId, pc] of Object.entries(this.peers)) {
942
+ const txInfo = this.peerTransceivers[peerId];
943
+ if (!txInfo) {
944
+ console.warn(
945
+ `[Screen Share] No transceiver info for ${peerId}, skipping`
946
+ );
947
+ continue;
948
+ }
949
+ try {
950
+ await txInfo.screenTransceiver.sender.replaceTrack(screenTrack);
951
+ if (txInfo.screenTransceiver.currentDirection === "recvonly") {
952
+ txInfo.screenTransceiver.direction = "sendrecv";
953
+ console.log(
954
+ `[Screen Share] Flipped ${peerId} screen transceiver to sendrecv`
955
+ );
956
+ await this.createOffer(peerId, true);
957
+ }
958
+ } catch (err) {
959
+ console.error(
960
+ `[Screen Share] Failed to update transceiver for ${peerId}:`,
961
+ err
962
+ );
963
+ }
964
+ }
884
965
  this.send({
885
966
  type: "SCREEN_SHARE_START",
886
967
  sender: this.myId,
887
968
  room_id: this.room.id,
888
969
  stream_id: this.screenStream.id.replace(/[{}]/g, "")
889
970
  });
971
+ console.log("[Screen Share] Started successfully");
890
972
  return this.screenStream;
891
973
  } catch (err) {
892
974
  this.emitError(
@@ -900,21 +982,32 @@ var VideoSDKCore = class {
900
982
  throw err;
901
983
  }
902
984
  }
903
- stopScreenShare() {
985
+ /**
986
+ * Stop screen sharing: clear the screen transceiver track and flip direction back to recvonly.
987
+ */
988
+ async stopScreenShare() {
904
989
  if (!this.screenStream) return;
990
+ console.log("[Screen Share] Stopping...");
905
991
  this.screenStream.getTracks().forEach((t) => t.stop());
906
- Object.entries(this.peers).forEach(([peerId, pc]) => {
907
- const senders = this.screenSenders[peerId] || [];
908
- senders.forEach((sender) => {
909
- try {
910
- pc.removeTrack(sender);
911
- } catch (err) {
912
- console.warn(err);
992
+ for (const [peerId, pc] of Object.entries(this.peers)) {
993
+ const txInfo = this.peerTransceivers[peerId];
994
+ if (!txInfo) continue;
995
+ try {
996
+ await txInfo.screenTransceiver.sender.replaceTrack(null);
997
+ if (txInfo.screenTransceiver.currentDirection === "sendrecv") {
998
+ txInfo.screenTransceiver.direction = "recvonly";
999
+ console.log(
1000
+ `[Screen Share] Flipped ${peerId} screen transceiver to recvonly`
1001
+ );
1002
+ await this.createOffer(peerId, true);
913
1003
  }
914
- });
915
- delete this.screenSenders[peerId];
916
- this.createOffer(peerId, true);
917
- });
1004
+ } catch (err) {
1005
+ console.error(
1006
+ `[Screen Share] Failed to clear transceiver for ${peerId}:`,
1007
+ err
1008
+ );
1009
+ }
1010
+ }
918
1011
  this.screenStream = null;
919
1012
  this.isScreenSharing = false;
920
1013
  this.state.updateLocalParticipant({
@@ -932,7 +1025,9 @@ var VideoSDKCore = class {
932
1025
  sender: this.myId,
933
1026
  room_id: this.room.id
934
1027
  });
1028
+ console.log("[Screen Share] Stopped");
935
1029
  }
1030
+ // CHAT
936
1031
  sendChatMessage(payload) {
937
1032
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
938
1033
  console.warn("WS not connected");
@@ -965,11 +1060,13 @@ var VideoSDKCore = class {
965
1060
  client_ts: Date.now()
966
1061
  });
967
1062
  }
968
- disconnect() {
1063
+ // DISCONNECT
1064
+ async disconnect() {
969
1065
  this.intentionalDisconnect = true;
970
- this.stopScreenShare();
1066
+ await this.stopScreenShare();
971
1067
  Object.values(this.peers).forEach((pc) => pc.close());
972
1068
  this.peers = {};
1069
+ this.peerTransceivers = {};
973
1070
  this.initiators.clear();
974
1071
  this.stopHeartbeat();
975
1072
  if (this.ws?.readyState === WebSocket.OPEN) {