@afosecure/meetingsdk 1.3.0 → 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);
@@ -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,17 @@ var VideoSDKCore = class {
652
651
  }
653
652
  }
654
653
  }
655
- // ---------------- PEER ----------------
656
- 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) {
657
665
  if (!this.localStream) throw new Error("No local stream");
658
666
  if (!this.iceServers || this.iceServers.length === 0) {
659
667
  throw new Error(
@@ -661,7 +669,9 @@ var VideoSDKCore = class {
661
669
  );
662
670
  }
663
671
  console.log(
664
- "Adding tracks",
672
+ "Creating peer connection for",
673
+ id,
674
+ "with tracks:",
665
675
  this.localStream.getTracks().map((t) => ({
666
676
  kind: t.kind,
667
677
  enabled: t.enabled,
@@ -671,20 +681,52 @@ var VideoSDKCore = class {
671
681
  const pc = new RTCPeerConnection({
672
682
  iceServers: this.iceServers
673
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
+ };
674
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
+ );
675
719
  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
720
  if (event.track.muted) {
679
721
  event.track.onunmute = () => {
680
- console.log(`${event.track.kind} track unmuted for ${id}`);
722
+ console.log(`[ontrack] ${event.track.kind} track unmuted for ${id}`);
681
723
  };
682
724
  }
683
- if (isScreenStream) {
684
- 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];
685
727
  this.state.updateParticipantMedia(id, {
686
728
  screenStream: incomingStream,
687
- screenTrack: videoTrack,
729
+ screenTrack: videoTrack2,
688
730
  isScreenSharing: true
689
731
  });
690
732
  if (!this.state.presenterId) {
@@ -710,29 +752,41 @@ var VideoSDKCore = class {
710
752
  });
711
753
  };
712
754
  pc.oniceconnectionstatechange = () => {
713
- console.log(`ICE Connection State: ${pc.iceConnectionState}`);
755
+ console.log(`[ICE Connection] ${id}: ${pc.iceConnectionState}`);
714
756
  };
715
757
  pc.onconnectionstatechange = () => {
758
+ console.log(`[Connection] ${id}: ${pc.connectionState}`);
716
759
  if (pc.connectionState === "failed") {
717
760
  try {
718
761
  pc.restartIce();
719
- } catch {
762
+ } catch (e) {
763
+ console.warn("Failed to restart ICE:", e);
720
764
  }
721
765
  }
722
766
  };
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
767
  return pc;
734
768
  }
735
- // ---------------- 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
736
790
  async createOffer(id, isRenegotiation = false) {
737
791
  if (!isRenegotiation && !this.shouldInitiate(id)) {
738
792
  console.debug(
@@ -750,12 +804,13 @@ var VideoSDKCore = class {
750
804
  this.initiators.add(id);
751
805
  }
752
806
  if (!this.peers[id]) {
753
- this.peers[id] = this.createPeer(id);
807
+ this.peers[id] = await this.createPeer(id);
754
808
  }
755
809
  const pc = this.peers[id];
756
810
  try {
757
811
  const offer = await pc.createOffer();
758
812
  await pc.setLocalDescription(offer);
813
+ this.captureScreenMid(id);
759
814
  this.send({
760
815
  type: "OFFER",
761
816
  payload: offer.sdp,
@@ -765,12 +820,18 @@ var VideoSDKCore = class {
765
820
  console.debug(`[Offer] Sent to ${id}`);
766
821
  } catch (err) {
767
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
+ );
768
829
  }
769
830
  }
770
831
  shouldInitiate(peerId) {
771
832
  return this.myId < peerId;
772
833
  }
773
- // ---------------- ANSWER ----------------
834
+ // ANSWER
774
835
  async handleOffer(sdp, id) {
775
836
  if (!this.iceServers || this.iceServers.length === 0) {
776
837
  console.warn("[Offer] Waiting for iceServers, queuing offer from", id);
@@ -778,7 +839,7 @@ var VideoSDKCore = class {
778
839
  return;
779
840
  }
780
841
  if (!this.peers[id]) {
781
- this.peers[id] = this.createPeer(id);
842
+ this.peers[id] = await this.createPeer(id);
782
843
  }
783
844
  const pc = this.peers[id];
784
845
  try {
@@ -794,8 +855,9 @@ var VideoSDKCore = class {
794
855
  );
795
856
  pc.close();
796
857
  delete this.peers[id];
858
+ delete this.peerTransceivers[id];
797
859
  this.initiators.delete(id);
798
- this.peers[id] = this.createPeer(id);
860
+ this.peers[id] = await this.createPeer(id);
799
861
  }
800
862
  }
801
863
  if (this.peers[id].signalingState !== "stable" && this.peers[id].signalingState !== "have-local-offer") {
@@ -808,6 +870,7 @@ var VideoSDKCore = class {
808
870
  type: "offer",
809
871
  sdp
810
872
  });
873
+ this.captureScreenMid(id);
811
874
  const pending = this.pendingIceCandidates[id] || [];
812
875
  for (const candidate of pending) {
813
876
  try {
@@ -837,18 +900,26 @@ var VideoSDKCore = class {
837
900
  );
838
901
  }
839
902
  }
840
- // ---------------- CLEANUP ----------------
903
+ // CLEANUP
841
904
  closePeer(id) {
842
905
  const pc = this.peers[id];
843
906
  if (!pc) return;
844
907
  pc.ontrack = null;
845
908
  pc.onicecandidate = null;
846
909
  pc.onconnectionstatechange = null;
910
+ pc.oniceconnectionstatechange = null;
847
911
  pc.close();
848
912
  delete this.peers[id];
913
+ delete this.peerTransceivers[id];
849
914
  this.initiators.delete(id);
850
915
  this.state.removeParticipant(id);
851
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
+ */
852
923
  async startScreenShare() {
853
924
  try {
854
925
  if (this.state.presenterId && this.state.presenterId !== this.myId) {
@@ -859,34 +930,55 @@ var VideoSDKCore = class {
859
930
  }
860
931
  this.screenStream = await navigator.mediaDevices.getDisplayMedia({
861
932
  video: true
862
- // audio: true,
863
933
  });
934
+ const screenTrack = this.screenStream.getVideoTracks()[0];
935
+ if (!screenTrack) {
936
+ throw new Error("No video track in screen stream");
937
+ }
864
938
  this.isScreenSharing = true;
865
939
  this.state.updateLocalParticipant({
866
940
  media: {
867
941
  isScreenSharing: true,
868
942
  screenStream: this.screenStream,
869
- screenTrack: this.screenStream.getVideoTracks()[0]
943
+ screenTrack
870
944
  }
871
945
  });
872
946
  this.state.setPresenterId(this.myId);
873
- this.screenStream.getVideoTracks()[0].onended = () => {
947
+ screenTrack.onended = () => {
948
+ console.log("[Screen Share] User stopped via browser button");
874
949
  this.stopScreenShare();
875
950
  };
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
- });
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
+ }
884
975
  this.send({
885
976
  type: "SCREEN_SHARE_START",
886
977
  sender: this.myId,
887
978
  room_id: this.room.id,
888
979
  stream_id: this.screenStream.id.replace(/[{}]/g, "")
889
980
  });
981
+ console.log("[Screen Share] Started successfully");
890
982
  return this.screenStream;
891
983
  } catch (err) {
892
984
  this.emitError(
@@ -900,21 +992,29 @@ var VideoSDKCore = class {
900
992
  throw err;
901
993
  }
902
994
  }
903
- stopScreenShare() {
995
+ async stopScreenShare() {
904
996
  if (!this.screenStream) return;
997
+ console.log("[Screen Share] Stopping...");
905
998
  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);
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);
913
1010
  }
914
- });
915
- delete this.screenSenders[peerId];
916
- this.createOffer(peerId, true);
917
- });
1011
+ } catch (err) {
1012
+ console.error(
1013
+ `[Screen Share] Failed to clear transceiver for ${peerId}:`,
1014
+ err
1015
+ );
1016
+ }
1017
+ }
918
1018
  this.screenStream = null;
919
1019
  this.isScreenSharing = false;
920
1020
  this.state.updateLocalParticipant({
@@ -932,7 +1032,9 @@ var VideoSDKCore = class {
932
1032
  sender: this.myId,
933
1033
  room_id: this.room.id
934
1034
  });
1035
+ console.log("[Screen Share] Stopped");
935
1036
  }
1037
+ // CHAT
936
1038
  sendChatMessage(payload) {
937
1039
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
938
1040
  console.warn("WS not connected");
@@ -965,11 +1067,13 @@ var VideoSDKCore = class {
965
1067
  client_ts: Date.now()
966
1068
  });
967
1069
  }
1070
+ // DISCONNECT
968
1071
  disconnect() {
969
1072
  this.intentionalDisconnect = true;
970
1073
  this.stopScreenShare();
971
1074
  Object.values(this.peers).forEach((pc) => pc.close());
972
1075
  this.peers = {};
1076
+ this.peerTransceivers = {};
973
1077
  this.initiators.clear();
974
1078
  this.stopHeartbeat();
975
1079
  if (this.ws?.readyState === WebSocket.OPEN) {