@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.d.mts +21 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.js +163 -59
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +163 -59
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
656
|
-
|
|
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
|
-
"
|
|
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(
|
|
722
|
+
console.log(`[ontrack] ${event.track.kind} track unmuted for ${id}`);
|
|
681
723
|
};
|
|
682
724
|
}
|
|
683
|
-
if (
|
|
684
|
-
const
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
943
|
+
screenTrack
|
|
870
944
|
}
|
|
871
945
|
});
|
|
872
946
|
this.state.setPresenterId(this.myId);
|
|
873
|
-
|
|
947
|
+
screenTrack.onended = () => {
|
|
948
|
+
console.log("[Screen Share] User stopped via browser button");
|
|
874
949
|
this.stopScreenShare();
|
|
875
950
|
};
|
|
876
|
-
Object.entries(this.peers)
|
|
877
|
-
this.
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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)
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
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
|
-
|
|
916
|
-
|
|
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) {
|