@defcomm/meetingsdk 0.1.1 → 0.1.3
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 +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +352 -128
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +352 -128
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -351,6 +351,9 @@ var SFUClient = class {
|
|
|
351
351
|
this.videoSender = null;
|
|
352
352
|
this.audioSender = null;
|
|
353
353
|
this.screenSender = null;
|
|
354
|
+
this.audioTransceiver = null;
|
|
355
|
+
this.cameraTransceiver = null;
|
|
356
|
+
this.screenTransceiver = null;
|
|
354
357
|
this.iceServers = [];
|
|
355
358
|
this.requestId = 0;
|
|
356
359
|
this.publisherRevision = 0;
|
|
@@ -382,6 +385,7 @@ var SFUClient = class {
|
|
|
382
385
|
this.trackMetadata = /* @__PURE__ */ new Map();
|
|
383
386
|
this.participants = /* @__PURE__ */ new Map();
|
|
384
387
|
this.remoteStreams = /* @__PURE__ */ new Map();
|
|
388
|
+
this.remoteScreenStreams = /* @__PURE__ */ new Map();
|
|
385
389
|
this.trackToParticipant = /* @__PURE__ */ new Map();
|
|
386
390
|
this.trackToSource = /* @__PURE__ */ new Map();
|
|
387
391
|
this.localTrackIds = /* @__PURE__ */ new Map();
|
|
@@ -864,16 +868,18 @@ var SFUClient = class {
|
|
|
864
868
|
}
|
|
865
869
|
case "screen_share_stopped": {
|
|
866
870
|
const participantId = payload.participant_id;
|
|
867
|
-
const
|
|
871
|
+
const screenStream = participantId ? this.remoteScreenStreams.get(participantId) : null;
|
|
868
872
|
const trackId = payload.track_id;
|
|
869
|
-
if (
|
|
870
|
-
for (const track of
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
stream.removeTrack(track);
|
|
873
|
+
if (screenStream) {
|
|
874
|
+
for (const track of screenStream.getVideoTracks()) {
|
|
875
|
+
if (!trackId || this.normalizeRemoteTrackId(track.id) === String(trackId)) {
|
|
876
|
+
screenStream.removeTrack(track);
|
|
874
877
|
track.stop();
|
|
875
878
|
}
|
|
876
879
|
}
|
|
880
|
+
if (screenStream.getTracks().length === 0 && participantId) {
|
|
881
|
+
this.remoteScreenStreams.delete(participantId);
|
|
882
|
+
}
|
|
877
883
|
}
|
|
878
884
|
this.onScreenShareStopped(participantId, trackId);
|
|
879
885
|
break;
|
|
@@ -942,6 +948,18 @@ var SFUClient = class {
|
|
|
942
948
|
const config = { iceServers: this.iceServers };
|
|
943
949
|
this.publisher = new RTCPeerConnection(config);
|
|
944
950
|
this.subscriber = new RTCPeerConnection(config);
|
|
951
|
+
this.audioTransceiver = this.publisher.addTransceiver("audio", {
|
|
952
|
+
direction: "sendonly"
|
|
953
|
+
});
|
|
954
|
+
this.cameraTransceiver = this.publisher.addTransceiver("video", {
|
|
955
|
+
direction: "sendonly"
|
|
956
|
+
});
|
|
957
|
+
this.screenTransceiver = this.publisher.addTransceiver("video", {
|
|
958
|
+
direction: "inactive"
|
|
959
|
+
});
|
|
960
|
+
this.audioSender = this.audioTransceiver.sender;
|
|
961
|
+
this.videoSender = this.cameraTransceiver.sender;
|
|
962
|
+
this.screenSender = this.screenTransceiver.sender;
|
|
945
963
|
this.publisher.onicecandidate = (event) => {
|
|
946
964
|
if (event.candidate) this.sendCandidate("publisher", event.candidate);
|
|
947
965
|
};
|
|
@@ -981,25 +999,61 @@ var SFUClient = class {
|
|
|
981
999
|
return;
|
|
982
1000
|
}
|
|
983
1001
|
const participantId = metadata.participant_id;
|
|
1002
|
+
const source = String(
|
|
1003
|
+
metadata.source || this.trackToSource.get(metadata.track_id) || ""
|
|
1004
|
+
).trim().toLowerCase();
|
|
1005
|
+
if (source === "screen" || source === "screen_share") {
|
|
1006
|
+
let screenStream = this.remoteScreenStreams.get(participantId);
|
|
1007
|
+
if (!screenStream) {
|
|
1008
|
+
screenStream = new MediaStream();
|
|
1009
|
+
this.remoteScreenStreams.set(participantId, screenStream);
|
|
1010
|
+
}
|
|
1011
|
+
for (const existing of screenStream.getVideoTracks()) {
|
|
1012
|
+
if (existing.id !== event.track.id) {
|
|
1013
|
+
screenStream.removeTrack(existing);
|
|
1014
|
+
existing.stop();
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
if (!screenStream.getTracks().some((track) => track.id === event.track.id)) {
|
|
1018
|
+
screenStream.addTrack(event.track);
|
|
1019
|
+
}
|
|
1020
|
+
event.track.onended = () => {
|
|
1021
|
+
try {
|
|
1022
|
+
screenStream?.removeTrack(event.track);
|
|
1023
|
+
} catch {
|
|
1024
|
+
}
|
|
1025
|
+
if (screenStream?.getTracks().length === 0) {
|
|
1026
|
+
this.remoteScreenStreams.delete(participantId);
|
|
1027
|
+
this.onScreenShareStopped(
|
|
1028
|
+
participantId,
|
|
1029
|
+
metadata.track_id
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
this.onScreenShareStarted(participantId, metadata);
|
|
1034
|
+
this.onRemoteTrack(event.track, screenStream, {
|
|
1035
|
+
...metadata,
|
|
1036
|
+
source: "screen"
|
|
1037
|
+
});
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
if (event.track.kind === "video" && source !== "camera") {
|
|
1041
|
+
this.onError(
|
|
1042
|
+
new Error(
|
|
1043
|
+
`Refusing to route unknown remote video source to camera: ${metadata.track_id}`
|
|
1044
|
+
)
|
|
1045
|
+
);
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
984
1048
|
let remoteStream = this.remoteStreams.get(participantId);
|
|
985
1049
|
if (!remoteStream) {
|
|
986
1050
|
remoteStream = new MediaStream();
|
|
987
1051
|
this.remoteStreams.set(participantId, remoteStream);
|
|
988
1052
|
}
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
remoteStream.removeTrack(existing);
|
|
994
|
-
existing.stop();
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
} else {
|
|
998
|
-
for (const existing of remoteStream.getTracks()) {
|
|
999
|
-
if (existing.kind === event.track.kind && existing.id !== event.track.id) {
|
|
1000
|
-
remoteStream.removeTrack(existing);
|
|
1001
|
-
existing.stop();
|
|
1002
|
-
}
|
|
1053
|
+
for (const existing of remoteStream.getTracks()) {
|
|
1054
|
+
if (existing.kind === event.track.kind && existing.id !== event.track.id) {
|
|
1055
|
+
remoteStream.removeTrack(existing);
|
|
1056
|
+
existing.stop();
|
|
1003
1057
|
}
|
|
1004
1058
|
}
|
|
1005
1059
|
if (!remoteStream.getTracks().some((track) => track.id === event.track.id)) {
|
|
@@ -1011,31 +1065,24 @@ var SFUClient = class {
|
|
|
1011
1065
|
} catch {
|
|
1012
1066
|
}
|
|
1013
1067
|
};
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1068
|
+
this.onRemoteTrack(event.track, remoteStream, {
|
|
1069
|
+
...metadata,
|
|
1070
|
+
source: event.track.kind === "audio" ? "audio" : "camera"
|
|
1071
|
+
});
|
|
1018
1072
|
}
|
|
1019
1073
|
normalizeRemoteTrackId(browserTrackId) {
|
|
1020
1074
|
return browserTrackId?.startsWith("track-") ? browserTrackId.slice("track-".length) : browserTrackId;
|
|
1021
1075
|
}
|
|
1022
1076
|
resolveRemoteTrackMetadata(track, stream) {
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
if (stream?.id) {
|
|
1033
|
-
const participant = this.participants.get(stream.id);
|
|
1034
|
-
if (participant) {
|
|
1035
|
-
return [...this.trackMetadata.values()].find(
|
|
1036
|
-
(entry) => entry.participant_id === participant.participant_id && entry.kind === track.kind
|
|
1037
|
-
) || null;
|
|
1038
|
-
}
|
|
1077
|
+
const candidates = [
|
|
1078
|
+
this.normalizeRemoteTrackId(track.id),
|
|
1079
|
+
track.id,
|
|
1080
|
+
stream?.id ? this.normalizeRemoteTrackId(stream.id) : null,
|
|
1081
|
+
stream?.id || null
|
|
1082
|
+
].filter(Boolean);
|
|
1083
|
+
for (const id of candidates) {
|
|
1084
|
+
const metadata = this.trackMetadata.get(id);
|
|
1085
|
+
if (metadata) return metadata;
|
|
1039
1086
|
}
|
|
1040
1087
|
return null;
|
|
1041
1088
|
}
|
|
@@ -1166,20 +1213,19 @@ var SFUClient = class {
|
|
|
1166
1213
|
}
|
|
1167
1214
|
async publishExistingTracks(audio = true, video = true) {
|
|
1168
1215
|
if (!this.publisher || !this.localStream) return;
|
|
1169
|
-
this.
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
if (!this.publisher.getSenders().length) return;
|
|
1216
|
+
if (!this.audioTransceiver || !this.cameraTransceiver || !this.screenTransceiver) {
|
|
1217
|
+
throw new Error("Publisher transceivers are not initialized");
|
|
1218
|
+
}
|
|
1219
|
+
const audioTrack = this.localStream.getAudioTracks().find((track) => track.readyState === "live");
|
|
1220
|
+
const cameraTrack = this.localStream.getVideoTracks().find((track) => track.readyState === "live");
|
|
1221
|
+
const screenTrack = this.screenStream?.getVideoTracks().find((track) => track.readyState === "live");
|
|
1222
|
+
await this.audioSender?.replaceTrack(audio && audioTrack ? audioTrack : null);
|
|
1223
|
+
this.audioTransceiver.direction = audio && audioTrack ? "sendonly" : "inactive";
|
|
1224
|
+
await this.videoSender?.replaceTrack(video && cameraTrack ? cameraTrack : null);
|
|
1225
|
+
this.cameraTransceiver.direction = video && cameraTrack ? "sendonly" : "inactive";
|
|
1226
|
+
await this.screenSender?.replaceTrack(screenTrack || null);
|
|
1227
|
+
this.screenTransceiver.direction = screenTrack ? "sendonly" : "inactive";
|
|
1228
|
+
if (!audioTrack && !cameraTrack && !screenTrack) return;
|
|
1183
1229
|
const offer = await this.publisher.createOffer();
|
|
1184
1230
|
await this.publisher.setLocalDescription(offer);
|
|
1185
1231
|
this.send({
|
|
@@ -1189,44 +1235,86 @@ var SFUClient = class {
|
|
|
1189
1235
|
sdp: offer.sdp
|
|
1190
1236
|
}
|
|
1191
1237
|
});
|
|
1192
|
-
if (
|
|
1238
|
+
if (screenTrack) {
|
|
1193
1239
|
this.send({ type: "start_screen_share" });
|
|
1194
1240
|
}
|
|
1195
1241
|
}
|
|
1196
1242
|
async shareScreen() {
|
|
1197
|
-
if (!this.publisher)
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
sdp: offer.sdp
|
|
1210
|
-
}
|
|
1243
|
+
if (!this.publisher) {
|
|
1244
|
+
throw new Error("Publisher PeerConnection is not initialized");
|
|
1245
|
+
}
|
|
1246
|
+
if (!this.screenTransceiver || !this.screenSender) {
|
|
1247
|
+
throw new Error("Screen transceiver is not initialized");
|
|
1248
|
+
}
|
|
1249
|
+
if (this.screenStream) {
|
|
1250
|
+
return this.screenStream;
|
|
1251
|
+
}
|
|
1252
|
+
const screenStream = await navigator.mediaDevices.getDisplayMedia({
|
|
1253
|
+
video: true,
|
|
1254
|
+
audio: false
|
|
1211
1255
|
});
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1256
|
+
const screenTrack = screenStream.getVideoTracks()[0];
|
|
1257
|
+
if (!screenTrack) {
|
|
1258
|
+
screenStream.getTracks().forEach((track) => track.stop());
|
|
1259
|
+
throw new Error("Screen share returned no video track");
|
|
1260
|
+
}
|
|
1261
|
+
this.screenStream = screenStream;
|
|
1262
|
+
try {
|
|
1263
|
+
if (!this.send({ type: "start_screen_share" })) {
|
|
1264
|
+
throw new Error("Failed to signal start_screen_share");
|
|
1265
|
+
}
|
|
1266
|
+
await this.screenSender.replaceTrack(screenTrack);
|
|
1267
|
+
this.screenTransceiver.direction = "sendonly";
|
|
1268
|
+
const offer = await this.publisher.createOffer();
|
|
1269
|
+
await this.publisher.setLocalDescription(offer);
|
|
1270
|
+
this.send({
|
|
1271
|
+
type: "publish_offer",
|
|
1272
|
+
payload: {
|
|
1273
|
+
revision: ++this.publisherRevision,
|
|
1274
|
+
sdp: offer.sdp
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
screenTrack.onended = () => {
|
|
1278
|
+
void this.stopScreenShare();
|
|
1279
|
+
};
|
|
1280
|
+
console.info("[publisher] Screen track attached", {
|
|
1281
|
+
screenTrackId: screenTrack.id,
|
|
1282
|
+
cameraTrackId: this.videoSender?.track?.id,
|
|
1283
|
+
screenMid: this.screenTransceiver.mid,
|
|
1284
|
+
cameraMid: this.cameraTransceiver?.mid
|
|
1285
|
+
});
|
|
1286
|
+
return this.screenStream;
|
|
1287
|
+
} catch (error) {
|
|
1223
1288
|
try {
|
|
1224
|
-
this.
|
|
1289
|
+
await this.screenSender.replaceTrack(null);
|
|
1225
1290
|
} catch {
|
|
1226
1291
|
}
|
|
1227
|
-
this.
|
|
1292
|
+
this.screenTransceiver.direction = "inactive";
|
|
1293
|
+
screenStream.getTracks().forEach((track) => track.stop());
|
|
1294
|
+
this.screenStream = null;
|
|
1228
1295
|
try {
|
|
1229
|
-
|
|
1296
|
+
this.send({ type: "stop_screen_share" });
|
|
1297
|
+
} catch {
|
|
1298
|
+
}
|
|
1299
|
+
throw error;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
async stopScreenShare() {
|
|
1303
|
+
if (!this.screenStream && !this.screenSender?.track) return;
|
|
1304
|
+
const screenTrack = this.screenSender?.track || null;
|
|
1305
|
+
if (screenTrack) screenTrack.onended = null;
|
|
1306
|
+
try {
|
|
1307
|
+
await this.screenSender?.replaceTrack(null);
|
|
1308
|
+
} catch (error) {
|
|
1309
|
+
this.onError(error);
|
|
1310
|
+
}
|
|
1311
|
+
if (this.screenTransceiver) {
|
|
1312
|
+
this.screenTransceiver.direction = "inactive";
|
|
1313
|
+
}
|
|
1314
|
+
this.send({ type: "stop_screen_share" });
|
|
1315
|
+
try {
|
|
1316
|
+
const offer = await this.publisher?.createOffer();
|
|
1317
|
+
if (offer && this.publisher) {
|
|
1230
1318
|
await this.publisher.setLocalDescription(offer);
|
|
1231
1319
|
this.send({
|
|
1232
1320
|
type: "publish_offer",
|
|
@@ -1235,13 +1323,15 @@ var SFUClient = class {
|
|
|
1235
1323
|
sdp: offer.sdp
|
|
1236
1324
|
}
|
|
1237
1325
|
});
|
|
1238
|
-
this.send({ type: "stop_screen_share" });
|
|
1239
|
-
} catch (error) {
|
|
1240
|
-
this.onError(error);
|
|
1241
1326
|
}
|
|
1327
|
+
} catch (error) {
|
|
1328
|
+
this.onError(error);
|
|
1242
1329
|
}
|
|
1243
|
-
this.screenStream
|
|
1330
|
+
this.screenStream?.getTracks().forEach((track) => track.stop());
|
|
1244
1331
|
this.screenStream = null;
|
|
1332
|
+
if (this.currentParticipantId) {
|
|
1333
|
+
this.onScreenShareStopped(this.currentParticipantId);
|
|
1334
|
+
}
|
|
1245
1335
|
}
|
|
1246
1336
|
muteAudio(muted) {
|
|
1247
1337
|
this.localStream?.getAudioTracks().forEach((track) => {
|
|
@@ -1327,6 +1417,11 @@ var SFUClient = class {
|
|
|
1327
1417
|
stream.getTracks().forEach((track) => track.stop());
|
|
1328
1418
|
this.remoteStreams.delete(participantId);
|
|
1329
1419
|
}
|
|
1420
|
+
const screenStream = this.remoteScreenStreams.get(participantId);
|
|
1421
|
+
if (screenStream) {
|
|
1422
|
+
screenStream.getTracks().forEach((track) => track.stop());
|
|
1423
|
+
this.remoteScreenStreams.delete(participantId);
|
|
1424
|
+
}
|
|
1330
1425
|
for (const [trackId, metadata] of this.trackMetadata) {
|
|
1331
1426
|
if (metadata.participant_id === participantId) {
|
|
1332
1427
|
this.trackMetadata.delete(trackId);
|
|
@@ -1402,6 +1497,9 @@ var SFUClient = class {
|
|
|
1402
1497
|
this.videoSender = null;
|
|
1403
1498
|
this.audioSender = null;
|
|
1404
1499
|
this.screenSender = null;
|
|
1500
|
+
this.audioTransceiver = null;
|
|
1501
|
+
this.cameraTransceiver = null;
|
|
1502
|
+
this.screenTransceiver = null;
|
|
1405
1503
|
this.pendingIce = { publisher: [], subscriber: [] };
|
|
1406
1504
|
this.offerQueue = [];
|
|
1407
1505
|
this.subscriberNegotiating = false;
|
|
@@ -1424,6 +1522,10 @@ var SFUClient = class {
|
|
|
1424
1522
|
stream.getTracks().forEach((track) => track.stop());
|
|
1425
1523
|
}
|
|
1426
1524
|
this.remoteStreams.clear();
|
|
1525
|
+
for (const stream of this.remoteScreenStreams.values()) {
|
|
1526
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
1527
|
+
}
|
|
1528
|
+
this.remoteScreenStreams.clear();
|
|
1427
1529
|
this.trackMetadata.clear();
|
|
1428
1530
|
this.trackToParticipant.clear();
|
|
1429
1531
|
this.trackToSource.clear();
|
|
@@ -1464,7 +1566,7 @@ var SFUClient = class {
|
|
|
1464
1566
|
|
|
1465
1567
|
// src/core/VideoCore.ts
|
|
1466
1568
|
var VideoSDKCore = class {
|
|
1467
|
-
constructor(events = {}, apiBase = SDK_CONFIG.apiBase
|
|
1569
|
+
constructor(events = {}, apiBase = SDK_CONFIG.apiBase, wsBase = SDK_CONFIG.wsBase) {
|
|
1468
1570
|
this.events = events;
|
|
1469
1571
|
this.apiBase = apiBase;
|
|
1470
1572
|
this.wsBase = wsBase;
|
|
@@ -1480,6 +1582,8 @@ var VideoSDKCore = class {
|
|
|
1480
1582
|
this.intentionalDisconnect = false;
|
|
1481
1583
|
this.remoteStreams = /* @__PURE__ */ new Map();
|
|
1482
1584
|
this.remoteScreenStreams = /* @__PURE__ */ new Map();
|
|
1585
|
+
this.joinPromise = null;
|
|
1586
|
+
this.disconnectPromise = null;
|
|
1483
1587
|
this.state = new MeetingState();
|
|
1484
1588
|
this.events = events;
|
|
1485
1589
|
this.activeSpeakerDetector = new ActiveSpeakerDetector({
|
|
@@ -1513,7 +1617,7 @@ var VideoSDKCore = class {
|
|
|
1513
1617
|
this.events.onError?.(err);
|
|
1514
1618
|
console.error("[MeetingSDK Error]", err);
|
|
1515
1619
|
}
|
|
1516
|
-
//
|
|
1620
|
+
// LOCAL MEDIA
|
|
1517
1621
|
async initLocal(video, name) {
|
|
1518
1622
|
this.participantName = name;
|
|
1519
1623
|
try {
|
|
@@ -1562,7 +1666,7 @@ var VideoSDKCore = class {
|
|
|
1562
1666
|
});
|
|
1563
1667
|
this.state.localStream = this.localStream;
|
|
1564
1668
|
}
|
|
1565
|
-
//
|
|
1669
|
+
// ROOM MANAGEMENT
|
|
1566
1670
|
async createRoom(input) {
|
|
1567
1671
|
const client = this.sfuClient ?? new SFUClient({
|
|
1568
1672
|
apiBase: this.apiBase,
|
|
@@ -1587,6 +1691,7 @@ var VideoSDKCore = class {
|
|
|
1587
1691
|
});
|
|
1588
1692
|
return client.deleteRoom(roomCode);
|
|
1589
1693
|
}
|
|
1694
|
+
// JOIN
|
|
1590
1695
|
async joinMeeting(config) {
|
|
1591
1696
|
const {
|
|
1592
1697
|
roomId,
|
|
@@ -1601,6 +1706,28 @@ var VideoSDKCore = class {
|
|
|
1601
1706
|
if (userId) {
|
|
1602
1707
|
this.myId = userId;
|
|
1603
1708
|
}
|
|
1709
|
+
if (this.sfuClient && this.room.id === roomId && this.sfuClient.getConnectionState() === "connected") {
|
|
1710
|
+
console.info("[SDK] Already connected; duplicate join ignored", {
|
|
1711
|
+
roomId,
|
|
1712
|
+
userId: this.myId
|
|
1713
|
+
});
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
if (this.joinPromise) {
|
|
1717
|
+
console.info(
|
|
1718
|
+
"[SDK] Join already in progress; waiting for existing join",
|
|
1719
|
+
{
|
|
1720
|
+
roomId,
|
|
1721
|
+
userId: this.myId
|
|
1722
|
+
}
|
|
1723
|
+
);
|
|
1724
|
+
return this.joinPromise;
|
|
1725
|
+
}
|
|
1726
|
+
if (this.sfuClient && this.room.id && this.room.id !== roomId) {
|
|
1727
|
+
throw new Error(
|
|
1728
|
+
`Already connected to room ${this.room.id}. Leave the current meeting before joining another room.`
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1604
1731
|
this.intentionalDisconnect = false;
|
|
1605
1732
|
this.participantName = name;
|
|
1606
1733
|
this.room.id = roomId;
|
|
@@ -1622,19 +1749,43 @@ var VideoSDKCore = class {
|
|
|
1622
1749
|
track.enabled = !videoMuted;
|
|
1623
1750
|
});
|
|
1624
1751
|
this.updateLocalMedia(audioMuted, videoMuted);
|
|
1625
|
-
|
|
1752
|
+
const promise = this.connect(roomId, name);
|
|
1753
|
+
this.joinPromise = promise;
|
|
1754
|
+
try {
|
|
1755
|
+
await promise;
|
|
1756
|
+
} finally {
|
|
1757
|
+
if (this.joinPromise === promise) {
|
|
1758
|
+
this.joinPromise = null;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1626
1761
|
}
|
|
1762
|
+
// SFU CONNECTION
|
|
1627
1763
|
async connect(roomId, name) {
|
|
1628
1764
|
if (this.sfuClient) {
|
|
1629
|
-
|
|
1630
|
-
this.
|
|
1765
|
+
const state = this.sfuClient.getConnectionState();
|
|
1766
|
+
if (this.room.id === roomId && (state === "connecting" || state === "connected" || state === "reconnecting")) {
|
|
1767
|
+
console.info(
|
|
1768
|
+
"[SDK] Existing SFU connection is active; connect ignored",
|
|
1769
|
+
{
|
|
1770
|
+
roomId,
|
|
1771
|
+
state
|
|
1772
|
+
}
|
|
1773
|
+
);
|
|
1774
|
+
return;
|
|
1775
|
+
}
|
|
1776
|
+
throw new Error(
|
|
1777
|
+
`SFU client already exists in state "${state}". Disconnect explicitly before creating another connection.`
|
|
1778
|
+
);
|
|
1631
1779
|
}
|
|
1632
1780
|
const client = new SFUClient({
|
|
1633
1781
|
apiBase: this.apiBase,
|
|
1634
1782
|
wsBase: this.wsBase,
|
|
1635
1783
|
userId: this.myId,
|
|
1636
1784
|
autoSubscribe: true,
|
|
1637
|
-
|
|
1785
|
+
// CONNECTION STATE
|
|
1786
|
+
onStateChange: (state) => {
|
|
1787
|
+
this.handleSFUState(state);
|
|
1788
|
+
},
|
|
1638
1789
|
onConnected: (payload, resumed) => {
|
|
1639
1790
|
console.log(`[SFU] ${resumed ? "Resumed" : "Joined"} room`, payload);
|
|
1640
1791
|
this.state.updateLocalParticipant({
|
|
@@ -1648,6 +1799,7 @@ var VideoSDKCore = class {
|
|
|
1648
1799
|
onReconnecting: (attempt) => {
|
|
1649
1800
|
console.warn(`[SFU] Reconnecting, attempt ${attempt}`);
|
|
1650
1801
|
},
|
|
1802
|
+
// ADMISSION CONTROL
|
|
1651
1803
|
onJoinPending: (info) => {
|
|
1652
1804
|
console.log("[SFU] Join request pending", info);
|
|
1653
1805
|
this.events.onEntryRequested?.({
|
|
@@ -1660,7 +1812,10 @@ var VideoSDKCore = class {
|
|
|
1660
1812
|
console.log("[SFU] Join approved", requestId);
|
|
1661
1813
|
this.events.onJoinApproved?.(requestId);
|
|
1662
1814
|
this.events.onEntryResponded?.(
|
|
1663
|
-
{
|
|
1815
|
+
{
|
|
1816
|
+
participantId: this.myId,
|
|
1817
|
+
decision: "approved"
|
|
1818
|
+
},
|
|
1664
1819
|
"approved"
|
|
1665
1820
|
);
|
|
1666
1821
|
},
|
|
@@ -1668,13 +1823,19 @@ var VideoSDKCore = class {
|
|
|
1668
1823
|
console.warn("[SFU] Join rejected", requestId, reason);
|
|
1669
1824
|
this.events.onJoinRejected?.(requestId);
|
|
1670
1825
|
this.events.onEntryResponded?.(
|
|
1671
|
-
{
|
|
1826
|
+
{
|
|
1827
|
+
participantId: this.myId,
|
|
1828
|
+
decision: "rejected"
|
|
1829
|
+
},
|
|
1672
1830
|
"rejected"
|
|
1673
1831
|
);
|
|
1674
1832
|
this.emitError(
|
|
1675
1833
|
"JOIN_REJECTED",
|
|
1676
1834
|
reason || "Your request to join was rejected",
|
|
1677
|
-
{
|
|
1835
|
+
{
|
|
1836
|
+
requestId,
|
|
1837
|
+
reason
|
|
1838
|
+
},
|
|
1678
1839
|
false
|
|
1679
1840
|
);
|
|
1680
1841
|
},
|
|
@@ -1682,16 +1843,21 @@ var VideoSDKCore = class {
|
|
|
1682
1843
|
const requestId = request?.request_id || request?.requestId;
|
|
1683
1844
|
const userId = request?.user_id || request?.userId;
|
|
1684
1845
|
const nameValue = request?.user_metadata?.name || request?.display_name || request?.name || "Participant";
|
|
1685
|
-
if (!requestId || !userId)
|
|
1846
|
+
if (!requestId || !userId) {
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1686
1849
|
this.events.onEntryRequested?.({
|
|
1687
1850
|
requestId,
|
|
1688
1851
|
userId,
|
|
1689
1852
|
name: nameValue
|
|
1690
1853
|
});
|
|
1691
1854
|
},
|
|
1855
|
+
// PARTICIPANTS
|
|
1692
1856
|
onParticipantJoined: (participant) => {
|
|
1693
1857
|
const pid = participant.participant_id;
|
|
1694
|
-
if (!pid || pid === this.myId)
|
|
1858
|
+
if (!pid || pid === this.myId) {
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1695
1861
|
const pname = participant.user_metadata?.name || participant.display_name || "Participant";
|
|
1696
1862
|
this.state.addParticipant({
|
|
1697
1863
|
id: pid,
|
|
@@ -1710,9 +1876,13 @@ var VideoSDKCore = class {
|
|
|
1710
1876
|
});
|
|
1711
1877
|
},
|
|
1712
1878
|
onParticipantPresence: (participantId, presence) => {
|
|
1713
|
-
if (participantId === this.myId)
|
|
1879
|
+
if (participantId === this.myId) {
|
|
1880
|
+
return;
|
|
1881
|
+
}
|
|
1714
1882
|
const current = this.state.getParticipant(participantId);
|
|
1715
|
-
if (!current)
|
|
1883
|
+
if (!current) {
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1716
1886
|
const connectionState = presence === "reconnecting" ? "reconnecting" : presence === "connected" || presence === "online" ? "connected" : presence === "disconnected" ? "disconnected" : current.connectionState;
|
|
1717
1887
|
this.state.updateParticipantMedia(participantId, {
|
|
1718
1888
|
stream: current.media?.stream ?? null
|
|
@@ -1728,19 +1898,24 @@ var VideoSDKCore = class {
|
|
|
1728
1898
|
},
|
|
1729
1899
|
onParticipantLeft: (participant) => {
|
|
1730
1900
|
const pid = typeof participant === "string" ? participant : participant.participant_id;
|
|
1731
|
-
if (!pid)
|
|
1901
|
+
if (!pid) {
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1732
1904
|
console.log("[SFU] Participant permanently left", pid);
|
|
1733
1905
|
this.cleanupRemoteParticipant(pid);
|
|
1734
1906
|
this.state.removeParticipant(pid);
|
|
1735
1907
|
this.events.onUserLeft?.(pid);
|
|
1736
1908
|
},
|
|
1909
|
+
// TRACKS
|
|
1737
1910
|
onTrackPublished: (track) => {
|
|
1738
1911
|
console.log("[SFU] Track published", track);
|
|
1739
1912
|
},
|
|
1740
1913
|
onTrackUnpublished: (track) => {
|
|
1741
1914
|
console.log("[SFU] Track unpublished", track);
|
|
1742
1915
|
const pid = track.participant_id;
|
|
1743
|
-
if (!pid)
|
|
1916
|
+
if (!pid) {
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1744
1919
|
const current = this.state.getParticipant(pid);
|
|
1745
1920
|
if (track.source === "screen") {
|
|
1746
1921
|
const screen = this.remoteScreenStreams.get(pid);
|
|
@@ -1754,7 +1929,9 @@ var VideoSDKCore = class {
|
|
|
1754
1929
|
this.events.onScreenShareStopped?.(pid);
|
|
1755
1930
|
return;
|
|
1756
1931
|
}
|
|
1757
|
-
if (!current)
|
|
1932
|
+
if (!current) {
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1758
1935
|
this.state.updateParticipantMedia(pid, {
|
|
1759
1936
|
stream: current.media?.stream ?? null,
|
|
1760
1937
|
micEnabled: track.kind === "audio" ? false : current.media?.micEnabled ?? true,
|
|
@@ -1763,9 +1940,13 @@ var VideoSDKCore = class {
|
|
|
1763
1940
|
},
|
|
1764
1941
|
onTrackStateChanged: (track) => {
|
|
1765
1942
|
const pid = track.participant_id;
|
|
1766
|
-
if (!pid || pid === this.myId)
|
|
1943
|
+
if (!pid || pid === this.myId) {
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1767
1946
|
const current = this.state.getParticipant(pid);
|
|
1768
|
-
if (!current)
|
|
1947
|
+
if (!current) {
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1769
1950
|
if (track.source === "audio") {
|
|
1770
1951
|
this.state.updateParticipantMedia(pid, {
|
|
1771
1952
|
stream: current.media?.stream ?? null,
|
|
@@ -1781,17 +1962,22 @@ var VideoSDKCore = class {
|
|
|
1781
1962
|
}
|
|
1782
1963
|
this.events.onTrackStateChanged?.(track);
|
|
1783
1964
|
},
|
|
1965
|
+
// RECORDING
|
|
1784
1966
|
onRecordingStarted: (recording) => {
|
|
1785
1967
|
this.events.onRecordingStarted?.(recording);
|
|
1786
1968
|
},
|
|
1787
1969
|
onRecordingStopped: (recording) => {
|
|
1788
1970
|
this.events.onRecordingStopped?.(recording);
|
|
1789
1971
|
},
|
|
1972
|
+
// REMOTE MEDIA
|
|
1790
1973
|
onRemoteTrack: (track, stream, metadata) => {
|
|
1791
1974
|
this.handleRemoteTrack(track, stream, metadata);
|
|
1792
1975
|
},
|
|
1976
|
+
// SCREEN SHARE
|
|
1793
1977
|
onScreenShareStarted: (participantId, track) => {
|
|
1794
|
-
if (participantId === this.myId)
|
|
1978
|
+
if (participantId === this.myId) {
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1795
1981
|
console.log("[SFU] Screen share started", participantId, track);
|
|
1796
1982
|
const current = this.state.getParticipant(participantId);
|
|
1797
1983
|
if (current) {
|
|
@@ -1802,7 +1988,9 @@ var VideoSDKCore = class {
|
|
|
1802
1988
|
}
|
|
1803
1989
|
},
|
|
1804
1990
|
onScreenShareStopped: (participantId) => {
|
|
1805
|
-
if (participantId === this.myId)
|
|
1991
|
+
if (participantId === this.myId) {
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1806
1994
|
const screen = this.remoteScreenStreams.get(participantId);
|
|
1807
1995
|
screen?.getTracks().forEach((track) => track.stop());
|
|
1808
1996
|
this.remoteScreenStreams.delete(participantId);
|
|
@@ -1813,6 +2001,7 @@ var VideoSDKCore = class {
|
|
|
1813
2001
|
});
|
|
1814
2002
|
this.events.onScreenShareStopped?.(participantId);
|
|
1815
2003
|
},
|
|
2004
|
+
// ERROR
|
|
1816
2005
|
onError: (error) => {
|
|
1817
2006
|
console.error("[SFU] Error:", error);
|
|
1818
2007
|
this.emitError(
|
|
@@ -1825,11 +2014,12 @@ var VideoSDKCore = class {
|
|
|
1825
2014
|
});
|
|
1826
2015
|
this.sfuClient = client;
|
|
1827
2016
|
client.setLocalStream(this.localStream);
|
|
1828
|
-
|
|
2017
|
+
await client.getToken(roomId, {
|
|
1829
2018
|
displayName: name,
|
|
1830
2019
|
userId: this.myId
|
|
1831
2020
|
});
|
|
1832
2021
|
}
|
|
2022
|
+
// SFU STATE
|
|
1833
2023
|
handleSFUState(state) {
|
|
1834
2024
|
this.events.onConnectionStateChanged?.(state);
|
|
1835
2025
|
if (state === "reconnecting") {
|
|
@@ -1848,9 +2038,12 @@ var VideoSDKCore = class {
|
|
|
1848
2038
|
);
|
|
1849
2039
|
}
|
|
1850
2040
|
}
|
|
2041
|
+
// REMOTE TRACKS
|
|
1851
2042
|
handleRemoteTrack(track, stream, metadata) {
|
|
1852
2043
|
const participantId = metadata?.participant_id;
|
|
1853
|
-
if (!participantId || participantId === this.myId)
|
|
2044
|
+
if (!participantId || participantId === this.myId) {
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
1854
2047
|
const source = metadata.source;
|
|
1855
2048
|
if (source === "screen") {
|
|
1856
2049
|
let screenStream = this.remoteScreenStreams.get(participantId);
|
|
@@ -1901,36 +2094,41 @@ var VideoSDKCore = class {
|
|
|
1901
2094
|
this.events.onTrack?.(remoteStream, participantId);
|
|
1902
2095
|
track.onended = () => {
|
|
1903
2096
|
if (track.kind === "video") {
|
|
1904
|
-
const participant2 = this.state.getParticipant(participantId);
|
|
1905
2097
|
this.state.updateParticipantMedia(participantId, {
|
|
1906
2098
|
camEnabled: false,
|
|
1907
2099
|
stream: remoteStream
|
|
1908
2100
|
});
|
|
1909
2101
|
return;
|
|
1910
2102
|
}
|
|
1911
|
-
const participant = this.state.getParticipant(participantId);
|
|
1912
2103
|
this.state.updateParticipantMedia(participantId, {
|
|
1913
2104
|
micEnabled: false,
|
|
1914
2105
|
stream: remoteStream
|
|
1915
2106
|
});
|
|
1916
2107
|
};
|
|
1917
2108
|
}
|
|
2109
|
+
// MEETING
|
|
1918
2110
|
getMeeting() {
|
|
1919
2111
|
return this.room;
|
|
1920
2112
|
}
|
|
1921
|
-
//
|
|
2113
|
+
// ADMISSION CONTROL
|
|
1922
2114
|
async approveJoinRequest(requestId) {
|
|
1923
|
-
if (!this.sfuClient)
|
|
2115
|
+
if (!this.sfuClient) {
|
|
2116
|
+
throw new Error("SFU client is not connected");
|
|
2117
|
+
}
|
|
1924
2118
|
return this.sfuClient.approveJoinRequest(requestId);
|
|
1925
2119
|
}
|
|
1926
2120
|
async rejectJoinRequest(requestId, reason) {
|
|
1927
|
-
if (!this.sfuClient)
|
|
2121
|
+
if (!this.sfuClient) {
|
|
2122
|
+
throw new Error("SFU client is not connected");
|
|
2123
|
+
}
|
|
1928
2124
|
return this.sfuClient.rejectJoinRequest(requestId, reason);
|
|
1929
2125
|
}
|
|
1930
|
-
//
|
|
2126
|
+
// MEDIA CONTROL
|
|
1931
2127
|
toggleMic() {
|
|
1932
2128
|
const mediaState = this.state.localParticipant?.media;
|
|
1933
|
-
if (!mediaState || !this.localStream)
|
|
2129
|
+
if (!mediaState || !this.localStream) {
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
1934
2132
|
const nextEnabled = !mediaState.micEnabled;
|
|
1935
2133
|
this.localStream.getAudioTracks().forEach((track) => {
|
|
1936
2134
|
track.enabled = nextEnabled;
|
|
@@ -1938,14 +2136,18 @@ var VideoSDKCore = class {
|
|
|
1938
2136
|
this.state.updateLocalParticipant({
|
|
1939
2137
|
id: this.myId,
|
|
1940
2138
|
name: this.participantName,
|
|
1941
|
-
media: {
|
|
2139
|
+
media: {
|
|
2140
|
+
micEnabled: nextEnabled
|
|
2141
|
+
}
|
|
1942
2142
|
});
|
|
1943
2143
|
this.sfuClient?.muteAudio(!nextEnabled);
|
|
1944
2144
|
this.events.onMicToggled?.(this.myId, nextEnabled);
|
|
1945
2145
|
}
|
|
1946
2146
|
toggleCam() {
|
|
1947
2147
|
const mediaState = this.state.localParticipant?.media;
|
|
1948
|
-
if (!mediaState || !this.localStream)
|
|
2148
|
+
if (!mediaState || !this.localStream) {
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
1949
2151
|
const nextEnabled = !mediaState.camEnabled;
|
|
1950
2152
|
this.localStream.getVideoTracks().forEach((track) => {
|
|
1951
2153
|
track.enabled = nextEnabled;
|
|
@@ -1953,12 +2155,14 @@ var VideoSDKCore = class {
|
|
|
1953
2155
|
this.state.updateLocalParticipant({
|
|
1954
2156
|
id: this.myId,
|
|
1955
2157
|
name: this.participantName,
|
|
1956
|
-
media: {
|
|
2158
|
+
media: {
|
|
2159
|
+
camEnabled: nextEnabled
|
|
2160
|
+
}
|
|
1957
2161
|
});
|
|
1958
2162
|
this.sfuClient?.muteVideo(!nextEnabled);
|
|
1959
2163
|
this.events.onCamToggled?.(this.myId, nextEnabled);
|
|
1960
2164
|
}
|
|
1961
|
-
//
|
|
2165
|
+
// SCREEN SHARING
|
|
1962
2166
|
async startScreenShare() {
|
|
1963
2167
|
try {
|
|
1964
2168
|
if (!navigator.mediaDevices?.getDisplayMedia) {
|
|
@@ -1990,7 +2194,9 @@ var VideoSDKCore = class {
|
|
|
1990
2194
|
}
|
|
1991
2195
|
}
|
|
1992
2196
|
stopScreenShare() {
|
|
1993
|
-
if (!this.screenStream)
|
|
2197
|
+
if (!this.screenStream) {
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
1994
2200
|
void this.sfuClient?.stopScreenShare();
|
|
1995
2201
|
this.screenStream.getTracks().forEach((track) => track.stop());
|
|
1996
2202
|
this.screenStream = null;
|
|
@@ -2003,7 +2209,7 @@ var VideoSDKCore = class {
|
|
|
2003
2209
|
}
|
|
2004
2210
|
});
|
|
2005
2211
|
}
|
|
2006
|
-
//
|
|
2212
|
+
// RECORDING
|
|
2007
2213
|
async startRecording() {
|
|
2008
2214
|
if (!this.sfuClient) {
|
|
2009
2215
|
throw new Error("SFU client is not connected");
|
|
@@ -2022,7 +2228,7 @@ var VideoSDKCore = class {
|
|
|
2022
2228
|
}
|
|
2023
2229
|
return this.sfuClient.getRecordingStatus();
|
|
2024
2230
|
}
|
|
2025
|
-
//
|
|
2231
|
+
// CHAT
|
|
2026
2232
|
sendChatMessage(payload) {
|
|
2027
2233
|
if (!this.sfuClient) {
|
|
2028
2234
|
console.warn("SFU Client not connected");
|
|
@@ -2041,7 +2247,7 @@ var VideoSDKCore = class {
|
|
|
2041
2247
|
this.state.addChatMessage(msg);
|
|
2042
2248
|
this.events.onChatMessage?.(msg);
|
|
2043
2249
|
}
|
|
2044
|
-
//
|
|
2250
|
+
// CLEANUP
|
|
2045
2251
|
cleanupRemoteParticipant(participantId) {
|
|
2046
2252
|
this.activeSpeakerDetector.removeParticipant(participantId);
|
|
2047
2253
|
const stream = this.remoteStreams.get(participantId);
|
|
@@ -2051,16 +2257,34 @@ var VideoSDKCore = class {
|
|
|
2051
2257
|
screen?.getTracks().forEach((track) => track.stop());
|
|
2052
2258
|
this.remoteScreenStreams.delete(participantId);
|
|
2053
2259
|
}
|
|
2260
|
+
// DISCONNECT
|
|
2054
2261
|
async disconnect() {
|
|
2262
|
+
if (this.disconnectPromise) {
|
|
2263
|
+
return this.disconnectPromise;
|
|
2264
|
+
}
|
|
2265
|
+
this.disconnectPromise = this.performDisconnect();
|
|
2266
|
+
try {
|
|
2267
|
+
await this.disconnectPromise;
|
|
2268
|
+
} finally {
|
|
2269
|
+
this.disconnectPromise = null;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
async performDisconnect() {
|
|
2055
2273
|
this.intentionalDisconnect = true;
|
|
2056
|
-
|
|
2274
|
+
this.joinPromise = null;
|
|
2275
|
+
const client = this.sfuClient;
|
|
2057
2276
|
this.sfuClient = null;
|
|
2277
|
+
if (client) {
|
|
2278
|
+
await client.leave();
|
|
2279
|
+
}
|
|
2058
2280
|
if (this.localStream) {
|
|
2059
2281
|
this.localStream.getTracks().forEach((track) => track.stop());
|
|
2060
2282
|
this.localStream = null;
|
|
2061
2283
|
}
|
|
2062
|
-
this.screenStream
|
|
2063
|
-
|
|
2284
|
+
if (this.screenStream) {
|
|
2285
|
+
this.screenStream.getTracks().forEach((track) => track.stop());
|
|
2286
|
+
this.screenStream = null;
|
|
2287
|
+
}
|
|
2064
2288
|
this.isScreenSharing = false;
|
|
2065
2289
|
for (const stream of this.remoteStreams.values()) {
|
|
2066
2290
|
stream.getTracks().forEach((track) => track.stop());
|