@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.d.mts +21 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.js +167 -59
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +167 -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);
|
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
//
|
|
652
|
-
|
|
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
|
-
"
|
|
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(
|
|
722
|
+
console.log(`[ontrack] ${event.track.kind} track unmuted for ${id}`);
|
|
677
723
|
};
|
|
678
724
|
}
|
|
679
|
-
if (
|
|
680
|
-
const
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
943
|
+
screenTrack
|
|
866
944
|
}
|
|
867
945
|
});
|
|
868
946
|
this.state.setPresenterId(this.myId);
|
|
869
|
-
|
|
947
|
+
screenTrack.onended = () => {
|
|
948
|
+
console.log("[Screen Share] User stopped via browser button");
|
|
870
949
|
this.stopScreenShare();
|
|
871
950
|
};
|
|
872
|
-
Object.entries(this.peers)
|
|
873
|
-
this.
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
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)
|
|
903
|
-
const
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
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
|
-
|
|
912
|
-
|
|
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) {
|