@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.js
CHANGED
|
@@ -387,6 +387,9 @@ var SFUClient = class {
|
|
|
387
387
|
this.videoSender = null;
|
|
388
388
|
this.audioSender = null;
|
|
389
389
|
this.screenSender = null;
|
|
390
|
+
this.audioTransceiver = null;
|
|
391
|
+
this.cameraTransceiver = null;
|
|
392
|
+
this.screenTransceiver = null;
|
|
390
393
|
this.iceServers = [];
|
|
391
394
|
this.requestId = 0;
|
|
392
395
|
this.publisherRevision = 0;
|
|
@@ -418,6 +421,7 @@ var SFUClient = class {
|
|
|
418
421
|
this.trackMetadata = /* @__PURE__ */ new Map();
|
|
419
422
|
this.participants = /* @__PURE__ */ new Map();
|
|
420
423
|
this.remoteStreams = /* @__PURE__ */ new Map();
|
|
424
|
+
this.remoteScreenStreams = /* @__PURE__ */ new Map();
|
|
421
425
|
this.trackToParticipant = /* @__PURE__ */ new Map();
|
|
422
426
|
this.trackToSource = /* @__PURE__ */ new Map();
|
|
423
427
|
this.localTrackIds = /* @__PURE__ */ new Map();
|
|
@@ -900,16 +904,18 @@ var SFUClient = class {
|
|
|
900
904
|
}
|
|
901
905
|
case "screen_share_stopped": {
|
|
902
906
|
const participantId = payload.participant_id;
|
|
903
|
-
const
|
|
907
|
+
const screenStream = participantId ? this.remoteScreenStreams.get(participantId) : null;
|
|
904
908
|
const trackId = payload.track_id;
|
|
905
|
-
if (
|
|
906
|
-
for (const track of
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
stream.removeTrack(track);
|
|
909
|
+
if (screenStream) {
|
|
910
|
+
for (const track of screenStream.getVideoTracks()) {
|
|
911
|
+
if (!trackId || this.normalizeRemoteTrackId(track.id) === String(trackId)) {
|
|
912
|
+
screenStream.removeTrack(track);
|
|
910
913
|
track.stop();
|
|
911
914
|
}
|
|
912
915
|
}
|
|
916
|
+
if (screenStream.getTracks().length === 0 && participantId) {
|
|
917
|
+
this.remoteScreenStreams.delete(participantId);
|
|
918
|
+
}
|
|
913
919
|
}
|
|
914
920
|
this.onScreenShareStopped(participantId, trackId);
|
|
915
921
|
break;
|
|
@@ -978,6 +984,18 @@ var SFUClient = class {
|
|
|
978
984
|
const config = { iceServers: this.iceServers };
|
|
979
985
|
this.publisher = new RTCPeerConnection(config);
|
|
980
986
|
this.subscriber = new RTCPeerConnection(config);
|
|
987
|
+
this.audioTransceiver = this.publisher.addTransceiver("audio", {
|
|
988
|
+
direction: "sendonly"
|
|
989
|
+
});
|
|
990
|
+
this.cameraTransceiver = this.publisher.addTransceiver("video", {
|
|
991
|
+
direction: "sendonly"
|
|
992
|
+
});
|
|
993
|
+
this.screenTransceiver = this.publisher.addTransceiver("video", {
|
|
994
|
+
direction: "inactive"
|
|
995
|
+
});
|
|
996
|
+
this.audioSender = this.audioTransceiver.sender;
|
|
997
|
+
this.videoSender = this.cameraTransceiver.sender;
|
|
998
|
+
this.screenSender = this.screenTransceiver.sender;
|
|
981
999
|
this.publisher.onicecandidate = (event) => {
|
|
982
1000
|
if (event.candidate) this.sendCandidate("publisher", event.candidate);
|
|
983
1001
|
};
|
|
@@ -1017,25 +1035,61 @@ var SFUClient = class {
|
|
|
1017
1035
|
return;
|
|
1018
1036
|
}
|
|
1019
1037
|
const participantId = metadata.participant_id;
|
|
1038
|
+
const source = String(
|
|
1039
|
+
metadata.source || this.trackToSource.get(metadata.track_id) || ""
|
|
1040
|
+
).trim().toLowerCase();
|
|
1041
|
+
if (source === "screen" || source === "screen_share") {
|
|
1042
|
+
let screenStream = this.remoteScreenStreams.get(participantId);
|
|
1043
|
+
if (!screenStream) {
|
|
1044
|
+
screenStream = new MediaStream();
|
|
1045
|
+
this.remoteScreenStreams.set(participantId, screenStream);
|
|
1046
|
+
}
|
|
1047
|
+
for (const existing of screenStream.getVideoTracks()) {
|
|
1048
|
+
if (existing.id !== event.track.id) {
|
|
1049
|
+
screenStream.removeTrack(existing);
|
|
1050
|
+
existing.stop();
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (!screenStream.getTracks().some((track) => track.id === event.track.id)) {
|
|
1054
|
+
screenStream.addTrack(event.track);
|
|
1055
|
+
}
|
|
1056
|
+
event.track.onended = () => {
|
|
1057
|
+
try {
|
|
1058
|
+
screenStream?.removeTrack(event.track);
|
|
1059
|
+
} catch {
|
|
1060
|
+
}
|
|
1061
|
+
if (screenStream?.getTracks().length === 0) {
|
|
1062
|
+
this.remoteScreenStreams.delete(participantId);
|
|
1063
|
+
this.onScreenShareStopped(
|
|
1064
|
+
participantId,
|
|
1065
|
+
metadata.track_id
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
this.onScreenShareStarted(participantId, metadata);
|
|
1070
|
+
this.onRemoteTrack(event.track, screenStream, {
|
|
1071
|
+
...metadata,
|
|
1072
|
+
source: "screen"
|
|
1073
|
+
});
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
if (event.track.kind === "video" && source !== "camera") {
|
|
1077
|
+
this.onError(
|
|
1078
|
+
new Error(
|
|
1079
|
+
`Refusing to route unknown remote video source to camera: ${metadata.track_id}`
|
|
1080
|
+
)
|
|
1081
|
+
);
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1020
1084
|
let remoteStream = this.remoteStreams.get(participantId);
|
|
1021
1085
|
if (!remoteStream) {
|
|
1022
1086
|
remoteStream = new MediaStream();
|
|
1023
1087
|
this.remoteStreams.set(participantId, remoteStream);
|
|
1024
1088
|
}
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
remoteStream.removeTrack(existing);
|
|
1030
|
-
existing.stop();
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
} else {
|
|
1034
|
-
for (const existing of remoteStream.getTracks()) {
|
|
1035
|
-
if (existing.kind === event.track.kind && existing.id !== event.track.id) {
|
|
1036
|
-
remoteStream.removeTrack(existing);
|
|
1037
|
-
existing.stop();
|
|
1038
|
-
}
|
|
1089
|
+
for (const existing of remoteStream.getTracks()) {
|
|
1090
|
+
if (existing.kind === event.track.kind && existing.id !== event.track.id) {
|
|
1091
|
+
remoteStream.removeTrack(existing);
|
|
1092
|
+
existing.stop();
|
|
1039
1093
|
}
|
|
1040
1094
|
}
|
|
1041
1095
|
if (!remoteStream.getTracks().some((track) => track.id === event.track.id)) {
|
|
@@ -1047,31 +1101,24 @@ var SFUClient = class {
|
|
|
1047
1101
|
} catch {
|
|
1048
1102
|
}
|
|
1049
1103
|
};
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1104
|
+
this.onRemoteTrack(event.track, remoteStream, {
|
|
1105
|
+
...metadata,
|
|
1106
|
+
source: event.track.kind === "audio" ? "audio" : "camera"
|
|
1107
|
+
});
|
|
1054
1108
|
}
|
|
1055
1109
|
normalizeRemoteTrackId(browserTrackId) {
|
|
1056
1110
|
return browserTrackId?.startsWith("track-") ? browserTrackId.slice("track-".length) : browserTrackId;
|
|
1057
1111
|
}
|
|
1058
1112
|
resolveRemoteTrackMetadata(track, stream) {
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
if (stream?.id) {
|
|
1069
|
-
const participant = this.participants.get(stream.id);
|
|
1070
|
-
if (participant) {
|
|
1071
|
-
return [...this.trackMetadata.values()].find(
|
|
1072
|
-
(entry) => entry.participant_id === participant.participant_id && entry.kind === track.kind
|
|
1073
|
-
) || null;
|
|
1074
|
-
}
|
|
1113
|
+
const candidates = [
|
|
1114
|
+
this.normalizeRemoteTrackId(track.id),
|
|
1115
|
+
track.id,
|
|
1116
|
+
stream?.id ? this.normalizeRemoteTrackId(stream.id) : null,
|
|
1117
|
+
stream?.id || null
|
|
1118
|
+
].filter(Boolean);
|
|
1119
|
+
for (const id of candidates) {
|
|
1120
|
+
const metadata = this.trackMetadata.get(id);
|
|
1121
|
+
if (metadata) return metadata;
|
|
1075
1122
|
}
|
|
1076
1123
|
return null;
|
|
1077
1124
|
}
|
|
@@ -1202,20 +1249,19 @@ var SFUClient = class {
|
|
|
1202
1249
|
}
|
|
1203
1250
|
async publishExistingTracks(audio = true, video = true) {
|
|
1204
1251
|
if (!this.publisher || !this.localStream) return;
|
|
1205
|
-
this.
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
if (!this.publisher.getSenders().length) return;
|
|
1252
|
+
if (!this.audioTransceiver || !this.cameraTransceiver || !this.screenTransceiver) {
|
|
1253
|
+
throw new Error("Publisher transceivers are not initialized");
|
|
1254
|
+
}
|
|
1255
|
+
const audioTrack = this.localStream.getAudioTracks().find((track) => track.readyState === "live");
|
|
1256
|
+
const cameraTrack = this.localStream.getVideoTracks().find((track) => track.readyState === "live");
|
|
1257
|
+
const screenTrack = this.screenStream?.getVideoTracks().find((track) => track.readyState === "live");
|
|
1258
|
+
await this.audioSender?.replaceTrack(audio && audioTrack ? audioTrack : null);
|
|
1259
|
+
this.audioTransceiver.direction = audio && audioTrack ? "sendonly" : "inactive";
|
|
1260
|
+
await this.videoSender?.replaceTrack(video && cameraTrack ? cameraTrack : null);
|
|
1261
|
+
this.cameraTransceiver.direction = video && cameraTrack ? "sendonly" : "inactive";
|
|
1262
|
+
await this.screenSender?.replaceTrack(screenTrack || null);
|
|
1263
|
+
this.screenTransceiver.direction = screenTrack ? "sendonly" : "inactive";
|
|
1264
|
+
if (!audioTrack && !cameraTrack && !screenTrack) return;
|
|
1219
1265
|
const offer = await this.publisher.createOffer();
|
|
1220
1266
|
await this.publisher.setLocalDescription(offer);
|
|
1221
1267
|
this.send({
|
|
@@ -1225,44 +1271,86 @@ var SFUClient = class {
|
|
|
1225
1271
|
sdp: offer.sdp
|
|
1226
1272
|
}
|
|
1227
1273
|
});
|
|
1228
|
-
if (
|
|
1274
|
+
if (screenTrack) {
|
|
1229
1275
|
this.send({ type: "start_screen_share" });
|
|
1230
1276
|
}
|
|
1231
1277
|
}
|
|
1232
1278
|
async shareScreen() {
|
|
1233
|
-
if (!this.publisher)
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
sdp: offer.sdp
|
|
1246
|
-
}
|
|
1279
|
+
if (!this.publisher) {
|
|
1280
|
+
throw new Error("Publisher PeerConnection is not initialized");
|
|
1281
|
+
}
|
|
1282
|
+
if (!this.screenTransceiver || !this.screenSender) {
|
|
1283
|
+
throw new Error("Screen transceiver is not initialized");
|
|
1284
|
+
}
|
|
1285
|
+
if (this.screenStream) {
|
|
1286
|
+
return this.screenStream;
|
|
1287
|
+
}
|
|
1288
|
+
const screenStream = await navigator.mediaDevices.getDisplayMedia({
|
|
1289
|
+
video: true,
|
|
1290
|
+
audio: false
|
|
1247
1291
|
});
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1292
|
+
const screenTrack = screenStream.getVideoTracks()[0];
|
|
1293
|
+
if (!screenTrack) {
|
|
1294
|
+
screenStream.getTracks().forEach((track) => track.stop());
|
|
1295
|
+
throw new Error("Screen share returned no video track");
|
|
1296
|
+
}
|
|
1297
|
+
this.screenStream = screenStream;
|
|
1298
|
+
try {
|
|
1299
|
+
if (!this.send({ type: "start_screen_share" })) {
|
|
1300
|
+
throw new Error("Failed to signal start_screen_share");
|
|
1301
|
+
}
|
|
1302
|
+
await this.screenSender.replaceTrack(screenTrack);
|
|
1303
|
+
this.screenTransceiver.direction = "sendonly";
|
|
1304
|
+
const offer = await this.publisher.createOffer();
|
|
1305
|
+
await this.publisher.setLocalDescription(offer);
|
|
1306
|
+
this.send({
|
|
1307
|
+
type: "publish_offer",
|
|
1308
|
+
payload: {
|
|
1309
|
+
revision: ++this.publisherRevision,
|
|
1310
|
+
sdp: offer.sdp
|
|
1311
|
+
}
|
|
1312
|
+
});
|
|
1313
|
+
screenTrack.onended = () => {
|
|
1314
|
+
void this.stopScreenShare();
|
|
1315
|
+
};
|
|
1316
|
+
console.info("[publisher] Screen track attached", {
|
|
1317
|
+
screenTrackId: screenTrack.id,
|
|
1318
|
+
cameraTrackId: this.videoSender?.track?.id,
|
|
1319
|
+
screenMid: this.screenTransceiver.mid,
|
|
1320
|
+
cameraMid: this.cameraTransceiver?.mid
|
|
1321
|
+
});
|
|
1322
|
+
return this.screenStream;
|
|
1323
|
+
} catch (error) {
|
|
1259
1324
|
try {
|
|
1260
|
-
this.
|
|
1325
|
+
await this.screenSender.replaceTrack(null);
|
|
1261
1326
|
} catch {
|
|
1262
1327
|
}
|
|
1263
|
-
this.
|
|
1328
|
+
this.screenTransceiver.direction = "inactive";
|
|
1329
|
+
screenStream.getTracks().forEach((track) => track.stop());
|
|
1330
|
+
this.screenStream = null;
|
|
1264
1331
|
try {
|
|
1265
|
-
|
|
1332
|
+
this.send({ type: "stop_screen_share" });
|
|
1333
|
+
} catch {
|
|
1334
|
+
}
|
|
1335
|
+
throw error;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
async stopScreenShare() {
|
|
1339
|
+
if (!this.screenStream && !this.screenSender?.track) return;
|
|
1340
|
+
const screenTrack = this.screenSender?.track || null;
|
|
1341
|
+
if (screenTrack) screenTrack.onended = null;
|
|
1342
|
+
try {
|
|
1343
|
+
await this.screenSender?.replaceTrack(null);
|
|
1344
|
+
} catch (error) {
|
|
1345
|
+
this.onError(error);
|
|
1346
|
+
}
|
|
1347
|
+
if (this.screenTransceiver) {
|
|
1348
|
+
this.screenTransceiver.direction = "inactive";
|
|
1349
|
+
}
|
|
1350
|
+
this.send({ type: "stop_screen_share" });
|
|
1351
|
+
try {
|
|
1352
|
+
const offer = await this.publisher?.createOffer();
|
|
1353
|
+
if (offer && this.publisher) {
|
|
1266
1354
|
await this.publisher.setLocalDescription(offer);
|
|
1267
1355
|
this.send({
|
|
1268
1356
|
type: "publish_offer",
|
|
@@ -1271,13 +1359,15 @@ var SFUClient = class {
|
|
|
1271
1359
|
sdp: offer.sdp
|
|
1272
1360
|
}
|
|
1273
1361
|
});
|
|
1274
|
-
this.send({ type: "stop_screen_share" });
|
|
1275
|
-
} catch (error) {
|
|
1276
|
-
this.onError(error);
|
|
1277
1362
|
}
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
this.onError(error);
|
|
1278
1365
|
}
|
|
1279
|
-
this.screenStream
|
|
1366
|
+
this.screenStream?.getTracks().forEach((track) => track.stop());
|
|
1280
1367
|
this.screenStream = null;
|
|
1368
|
+
if (this.currentParticipantId) {
|
|
1369
|
+
this.onScreenShareStopped(this.currentParticipantId);
|
|
1370
|
+
}
|
|
1281
1371
|
}
|
|
1282
1372
|
muteAudio(muted) {
|
|
1283
1373
|
this.localStream?.getAudioTracks().forEach((track) => {
|
|
@@ -1363,6 +1453,11 @@ var SFUClient = class {
|
|
|
1363
1453
|
stream.getTracks().forEach((track) => track.stop());
|
|
1364
1454
|
this.remoteStreams.delete(participantId);
|
|
1365
1455
|
}
|
|
1456
|
+
const screenStream = this.remoteScreenStreams.get(participantId);
|
|
1457
|
+
if (screenStream) {
|
|
1458
|
+
screenStream.getTracks().forEach((track) => track.stop());
|
|
1459
|
+
this.remoteScreenStreams.delete(participantId);
|
|
1460
|
+
}
|
|
1366
1461
|
for (const [trackId, metadata] of this.trackMetadata) {
|
|
1367
1462
|
if (metadata.participant_id === participantId) {
|
|
1368
1463
|
this.trackMetadata.delete(trackId);
|
|
@@ -1438,6 +1533,9 @@ var SFUClient = class {
|
|
|
1438
1533
|
this.videoSender = null;
|
|
1439
1534
|
this.audioSender = null;
|
|
1440
1535
|
this.screenSender = null;
|
|
1536
|
+
this.audioTransceiver = null;
|
|
1537
|
+
this.cameraTransceiver = null;
|
|
1538
|
+
this.screenTransceiver = null;
|
|
1441
1539
|
this.pendingIce = { publisher: [], subscriber: [] };
|
|
1442
1540
|
this.offerQueue = [];
|
|
1443
1541
|
this.subscriberNegotiating = false;
|
|
@@ -1460,6 +1558,10 @@ var SFUClient = class {
|
|
|
1460
1558
|
stream.getTracks().forEach((track) => track.stop());
|
|
1461
1559
|
}
|
|
1462
1560
|
this.remoteStreams.clear();
|
|
1561
|
+
for (const stream of this.remoteScreenStreams.values()) {
|
|
1562
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
1563
|
+
}
|
|
1564
|
+
this.remoteScreenStreams.clear();
|
|
1463
1565
|
this.trackMetadata.clear();
|
|
1464
1566
|
this.trackToParticipant.clear();
|
|
1465
1567
|
this.trackToSource.clear();
|
|
@@ -1500,7 +1602,7 @@ var SFUClient = class {
|
|
|
1500
1602
|
|
|
1501
1603
|
// src/core/VideoCore.ts
|
|
1502
1604
|
var VideoSDKCore = class {
|
|
1503
|
-
constructor(events = {}, apiBase = SDK_CONFIG.apiBase
|
|
1605
|
+
constructor(events = {}, apiBase = SDK_CONFIG.apiBase, wsBase = SDK_CONFIG.wsBase) {
|
|
1504
1606
|
this.events = events;
|
|
1505
1607
|
this.apiBase = apiBase;
|
|
1506
1608
|
this.wsBase = wsBase;
|
|
@@ -1516,6 +1618,8 @@ var VideoSDKCore = class {
|
|
|
1516
1618
|
this.intentionalDisconnect = false;
|
|
1517
1619
|
this.remoteStreams = /* @__PURE__ */ new Map();
|
|
1518
1620
|
this.remoteScreenStreams = /* @__PURE__ */ new Map();
|
|
1621
|
+
this.joinPromise = null;
|
|
1622
|
+
this.disconnectPromise = null;
|
|
1519
1623
|
this.state = new MeetingState();
|
|
1520
1624
|
this.events = events;
|
|
1521
1625
|
this.activeSpeakerDetector = new ActiveSpeakerDetector({
|
|
@@ -1549,7 +1653,7 @@ var VideoSDKCore = class {
|
|
|
1549
1653
|
this.events.onError?.(err);
|
|
1550
1654
|
console.error("[MeetingSDK Error]", err);
|
|
1551
1655
|
}
|
|
1552
|
-
//
|
|
1656
|
+
// LOCAL MEDIA
|
|
1553
1657
|
async initLocal(video, name) {
|
|
1554
1658
|
this.participantName = name;
|
|
1555
1659
|
try {
|
|
@@ -1598,7 +1702,7 @@ var VideoSDKCore = class {
|
|
|
1598
1702
|
});
|
|
1599
1703
|
this.state.localStream = this.localStream;
|
|
1600
1704
|
}
|
|
1601
|
-
//
|
|
1705
|
+
// ROOM MANAGEMENT
|
|
1602
1706
|
async createRoom(input) {
|
|
1603
1707
|
const client = this.sfuClient ?? new SFUClient({
|
|
1604
1708
|
apiBase: this.apiBase,
|
|
@@ -1623,6 +1727,7 @@ var VideoSDKCore = class {
|
|
|
1623
1727
|
});
|
|
1624
1728
|
return client.deleteRoom(roomCode);
|
|
1625
1729
|
}
|
|
1730
|
+
// JOIN
|
|
1626
1731
|
async joinMeeting(config) {
|
|
1627
1732
|
const {
|
|
1628
1733
|
roomId,
|
|
@@ -1637,6 +1742,28 @@ var VideoSDKCore = class {
|
|
|
1637
1742
|
if (userId) {
|
|
1638
1743
|
this.myId = userId;
|
|
1639
1744
|
}
|
|
1745
|
+
if (this.sfuClient && this.room.id === roomId && this.sfuClient.getConnectionState() === "connected") {
|
|
1746
|
+
console.info("[SDK] Already connected; duplicate join ignored", {
|
|
1747
|
+
roomId,
|
|
1748
|
+
userId: this.myId
|
|
1749
|
+
});
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1752
|
+
if (this.joinPromise) {
|
|
1753
|
+
console.info(
|
|
1754
|
+
"[SDK] Join already in progress; waiting for existing join",
|
|
1755
|
+
{
|
|
1756
|
+
roomId,
|
|
1757
|
+
userId: this.myId
|
|
1758
|
+
}
|
|
1759
|
+
);
|
|
1760
|
+
return this.joinPromise;
|
|
1761
|
+
}
|
|
1762
|
+
if (this.sfuClient && this.room.id && this.room.id !== roomId) {
|
|
1763
|
+
throw new Error(
|
|
1764
|
+
`Already connected to room ${this.room.id}. Leave the current meeting before joining another room.`
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1640
1767
|
this.intentionalDisconnect = false;
|
|
1641
1768
|
this.participantName = name;
|
|
1642
1769
|
this.room.id = roomId;
|
|
@@ -1658,19 +1785,43 @@ var VideoSDKCore = class {
|
|
|
1658
1785
|
track.enabled = !videoMuted;
|
|
1659
1786
|
});
|
|
1660
1787
|
this.updateLocalMedia(audioMuted, videoMuted);
|
|
1661
|
-
|
|
1788
|
+
const promise = this.connect(roomId, name);
|
|
1789
|
+
this.joinPromise = promise;
|
|
1790
|
+
try {
|
|
1791
|
+
await promise;
|
|
1792
|
+
} finally {
|
|
1793
|
+
if (this.joinPromise === promise) {
|
|
1794
|
+
this.joinPromise = null;
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1662
1797
|
}
|
|
1798
|
+
// SFU CONNECTION
|
|
1663
1799
|
async connect(roomId, name) {
|
|
1664
1800
|
if (this.sfuClient) {
|
|
1665
|
-
|
|
1666
|
-
this.
|
|
1801
|
+
const state = this.sfuClient.getConnectionState();
|
|
1802
|
+
if (this.room.id === roomId && (state === "connecting" || state === "connected" || state === "reconnecting")) {
|
|
1803
|
+
console.info(
|
|
1804
|
+
"[SDK] Existing SFU connection is active; connect ignored",
|
|
1805
|
+
{
|
|
1806
|
+
roomId,
|
|
1807
|
+
state
|
|
1808
|
+
}
|
|
1809
|
+
);
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
throw new Error(
|
|
1813
|
+
`SFU client already exists in state "${state}". Disconnect explicitly before creating another connection.`
|
|
1814
|
+
);
|
|
1667
1815
|
}
|
|
1668
1816
|
const client = new SFUClient({
|
|
1669
1817
|
apiBase: this.apiBase,
|
|
1670
1818
|
wsBase: this.wsBase,
|
|
1671
1819
|
userId: this.myId,
|
|
1672
1820
|
autoSubscribe: true,
|
|
1673
|
-
|
|
1821
|
+
// CONNECTION STATE
|
|
1822
|
+
onStateChange: (state) => {
|
|
1823
|
+
this.handleSFUState(state);
|
|
1824
|
+
},
|
|
1674
1825
|
onConnected: (payload, resumed) => {
|
|
1675
1826
|
console.log(`[SFU] ${resumed ? "Resumed" : "Joined"} room`, payload);
|
|
1676
1827
|
this.state.updateLocalParticipant({
|
|
@@ -1684,6 +1835,7 @@ var VideoSDKCore = class {
|
|
|
1684
1835
|
onReconnecting: (attempt) => {
|
|
1685
1836
|
console.warn(`[SFU] Reconnecting, attempt ${attempt}`);
|
|
1686
1837
|
},
|
|
1838
|
+
// ADMISSION CONTROL
|
|
1687
1839
|
onJoinPending: (info) => {
|
|
1688
1840
|
console.log("[SFU] Join request pending", info);
|
|
1689
1841
|
this.events.onEntryRequested?.({
|
|
@@ -1696,7 +1848,10 @@ var VideoSDKCore = class {
|
|
|
1696
1848
|
console.log("[SFU] Join approved", requestId);
|
|
1697
1849
|
this.events.onJoinApproved?.(requestId);
|
|
1698
1850
|
this.events.onEntryResponded?.(
|
|
1699
|
-
{
|
|
1851
|
+
{
|
|
1852
|
+
participantId: this.myId,
|
|
1853
|
+
decision: "approved"
|
|
1854
|
+
},
|
|
1700
1855
|
"approved"
|
|
1701
1856
|
);
|
|
1702
1857
|
},
|
|
@@ -1704,13 +1859,19 @@ var VideoSDKCore = class {
|
|
|
1704
1859
|
console.warn("[SFU] Join rejected", requestId, reason);
|
|
1705
1860
|
this.events.onJoinRejected?.(requestId);
|
|
1706
1861
|
this.events.onEntryResponded?.(
|
|
1707
|
-
{
|
|
1862
|
+
{
|
|
1863
|
+
participantId: this.myId,
|
|
1864
|
+
decision: "rejected"
|
|
1865
|
+
},
|
|
1708
1866
|
"rejected"
|
|
1709
1867
|
);
|
|
1710
1868
|
this.emitError(
|
|
1711
1869
|
"JOIN_REJECTED",
|
|
1712
1870
|
reason || "Your request to join was rejected",
|
|
1713
|
-
{
|
|
1871
|
+
{
|
|
1872
|
+
requestId,
|
|
1873
|
+
reason
|
|
1874
|
+
},
|
|
1714
1875
|
false
|
|
1715
1876
|
);
|
|
1716
1877
|
},
|
|
@@ -1718,16 +1879,21 @@ var VideoSDKCore = class {
|
|
|
1718
1879
|
const requestId = request?.request_id || request?.requestId;
|
|
1719
1880
|
const userId = request?.user_id || request?.userId;
|
|
1720
1881
|
const nameValue = request?.user_metadata?.name || request?.display_name || request?.name || "Participant";
|
|
1721
|
-
if (!requestId || !userId)
|
|
1882
|
+
if (!requestId || !userId) {
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1722
1885
|
this.events.onEntryRequested?.({
|
|
1723
1886
|
requestId,
|
|
1724
1887
|
userId,
|
|
1725
1888
|
name: nameValue
|
|
1726
1889
|
});
|
|
1727
1890
|
},
|
|
1891
|
+
// PARTICIPANTS
|
|
1728
1892
|
onParticipantJoined: (participant) => {
|
|
1729
1893
|
const pid = participant.participant_id;
|
|
1730
|
-
if (!pid || pid === this.myId)
|
|
1894
|
+
if (!pid || pid === this.myId) {
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1731
1897
|
const pname = participant.user_metadata?.name || participant.display_name || "Participant";
|
|
1732
1898
|
this.state.addParticipant({
|
|
1733
1899
|
id: pid,
|
|
@@ -1746,9 +1912,13 @@ var VideoSDKCore = class {
|
|
|
1746
1912
|
});
|
|
1747
1913
|
},
|
|
1748
1914
|
onParticipantPresence: (participantId, presence) => {
|
|
1749
|
-
if (participantId === this.myId)
|
|
1915
|
+
if (participantId === this.myId) {
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1750
1918
|
const current = this.state.getParticipant(participantId);
|
|
1751
|
-
if (!current)
|
|
1919
|
+
if (!current) {
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1752
1922
|
const connectionState = presence === "reconnecting" ? "reconnecting" : presence === "connected" || presence === "online" ? "connected" : presence === "disconnected" ? "disconnected" : current.connectionState;
|
|
1753
1923
|
this.state.updateParticipantMedia(participantId, {
|
|
1754
1924
|
stream: current.media?.stream ?? null
|
|
@@ -1764,19 +1934,24 @@ var VideoSDKCore = class {
|
|
|
1764
1934
|
},
|
|
1765
1935
|
onParticipantLeft: (participant) => {
|
|
1766
1936
|
const pid = typeof participant === "string" ? participant : participant.participant_id;
|
|
1767
|
-
if (!pid)
|
|
1937
|
+
if (!pid) {
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1768
1940
|
console.log("[SFU] Participant permanently left", pid);
|
|
1769
1941
|
this.cleanupRemoteParticipant(pid);
|
|
1770
1942
|
this.state.removeParticipant(pid);
|
|
1771
1943
|
this.events.onUserLeft?.(pid);
|
|
1772
1944
|
},
|
|
1945
|
+
// TRACKS
|
|
1773
1946
|
onTrackPublished: (track) => {
|
|
1774
1947
|
console.log("[SFU] Track published", track);
|
|
1775
1948
|
},
|
|
1776
1949
|
onTrackUnpublished: (track) => {
|
|
1777
1950
|
console.log("[SFU] Track unpublished", track);
|
|
1778
1951
|
const pid = track.participant_id;
|
|
1779
|
-
if (!pid)
|
|
1952
|
+
if (!pid) {
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1780
1955
|
const current = this.state.getParticipant(pid);
|
|
1781
1956
|
if (track.source === "screen") {
|
|
1782
1957
|
const screen = this.remoteScreenStreams.get(pid);
|
|
@@ -1790,7 +1965,9 @@ var VideoSDKCore = class {
|
|
|
1790
1965
|
this.events.onScreenShareStopped?.(pid);
|
|
1791
1966
|
return;
|
|
1792
1967
|
}
|
|
1793
|
-
if (!current)
|
|
1968
|
+
if (!current) {
|
|
1969
|
+
return;
|
|
1970
|
+
}
|
|
1794
1971
|
this.state.updateParticipantMedia(pid, {
|
|
1795
1972
|
stream: current.media?.stream ?? null,
|
|
1796
1973
|
micEnabled: track.kind === "audio" ? false : current.media?.micEnabled ?? true,
|
|
@@ -1799,9 +1976,13 @@ var VideoSDKCore = class {
|
|
|
1799
1976
|
},
|
|
1800
1977
|
onTrackStateChanged: (track) => {
|
|
1801
1978
|
const pid = track.participant_id;
|
|
1802
|
-
if (!pid || pid === this.myId)
|
|
1979
|
+
if (!pid || pid === this.myId) {
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1803
1982
|
const current = this.state.getParticipant(pid);
|
|
1804
|
-
if (!current)
|
|
1983
|
+
if (!current) {
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1805
1986
|
if (track.source === "audio") {
|
|
1806
1987
|
this.state.updateParticipantMedia(pid, {
|
|
1807
1988
|
stream: current.media?.stream ?? null,
|
|
@@ -1817,17 +1998,22 @@ var VideoSDKCore = class {
|
|
|
1817
1998
|
}
|
|
1818
1999
|
this.events.onTrackStateChanged?.(track);
|
|
1819
2000
|
},
|
|
2001
|
+
// RECORDING
|
|
1820
2002
|
onRecordingStarted: (recording) => {
|
|
1821
2003
|
this.events.onRecordingStarted?.(recording);
|
|
1822
2004
|
},
|
|
1823
2005
|
onRecordingStopped: (recording) => {
|
|
1824
2006
|
this.events.onRecordingStopped?.(recording);
|
|
1825
2007
|
},
|
|
2008
|
+
// REMOTE MEDIA
|
|
1826
2009
|
onRemoteTrack: (track, stream, metadata) => {
|
|
1827
2010
|
this.handleRemoteTrack(track, stream, metadata);
|
|
1828
2011
|
},
|
|
2012
|
+
// SCREEN SHARE
|
|
1829
2013
|
onScreenShareStarted: (participantId, track) => {
|
|
1830
|
-
if (participantId === this.myId)
|
|
2014
|
+
if (participantId === this.myId) {
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
1831
2017
|
console.log("[SFU] Screen share started", participantId, track);
|
|
1832
2018
|
const current = this.state.getParticipant(participantId);
|
|
1833
2019
|
if (current) {
|
|
@@ -1838,7 +2024,9 @@ var VideoSDKCore = class {
|
|
|
1838
2024
|
}
|
|
1839
2025
|
},
|
|
1840
2026
|
onScreenShareStopped: (participantId) => {
|
|
1841
|
-
if (participantId === this.myId)
|
|
2027
|
+
if (participantId === this.myId) {
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
1842
2030
|
const screen = this.remoteScreenStreams.get(participantId);
|
|
1843
2031
|
screen?.getTracks().forEach((track) => track.stop());
|
|
1844
2032
|
this.remoteScreenStreams.delete(participantId);
|
|
@@ -1849,6 +2037,7 @@ var VideoSDKCore = class {
|
|
|
1849
2037
|
});
|
|
1850
2038
|
this.events.onScreenShareStopped?.(participantId);
|
|
1851
2039
|
},
|
|
2040
|
+
// ERROR
|
|
1852
2041
|
onError: (error) => {
|
|
1853
2042
|
console.error("[SFU] Error:", error);
|
|
1854
2043
|
this.emitError(
|
|
@@ -1861,11 +2050,12 @@ var VideoSDKCore = class {
|
|
|
1861
2050
|
});
|
|
1862
2051
|
this.sfuClient = client;
|
|
1863
2052
|
client.setLocalStream(this.localStream);
|
|
1864
|
-
|
|
2053
|
+
await client.getToken(roomId, {
|
|
1865
2054
|
displayName: name,
|
|
1866
2055
|
userId: this.myId
|
|
1867
2056
|
});
|
|
1868
2057
|
}
|
|
2058
|
+
// SFU STATE
|
|
1869
2059
|
handleSFUState(state) {
|
|
1870
2060
|
this.events.onConnectionStateChanged?.(state);
|
|
1871
2061
|
if (state === "reconnecting") {
|
|
@@ -1884,9 +2074,12 @@ var VideoSDKCore = class {
|
|
|
1884
2074
|
);
|
|
1885
2075
|
}
|
|
1886
2076
|
}
|
|
2077
|
+
// REMOTE TRACKS
|
|
1887
2078
|
handleRemoteTrack(track, stream, metadata) {
|
|
1888
2079
|
const participantId = metadata?.participant_id;
|
|
1889
|
-
if (!participantId || participantId === this.myId)
|
|
2080
|
+
if (!participantId || participantId === this.myId) {
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
1890
2083
|
const source = metadata.source;
|
|
1891
2084
|
if (source === "screen") {
|
|
1892
2085
|
let screenStream = this.remoteScreenStreams.get(participantId);
|
|
@@ -1937,36 +2130,41 @@ var VideoSDKCore = class {
|
|
|
1937
2130
|
this.events.onTrack?.(remoteStream, participantId);
|
|
1938
2131
|
track.onended = () => {
|
|
1939
2132
|
if (track.kind === "video") {
|
|
1940
|
-
const participant2 = this.state.getParticipant(participantId);
|
|
1941
2133
|
this.state.updateParticipantMedia(participantId, {
|
|
1942
2134
|
camEnabled: false,
|
|
1943
2135
|
stream: remoteStream
|
|
1944
2136
|
});
|
|
1945
2137
|
return;
|
|
1946
2138
|
}
|
|
1947
|
-
const participant = this.state.getParticipant(participantId);
|
|
1948
2139
|
this.state.updateParticipantMedia(participantId, {
|
|
1949
2140
|
micEnabled: false,
|
|
1950
2141
|
stream: remoteStream
|
|
1951
2142
|
});
|
|
1952
2143
|
};
|
|
1953
2144
|
}
|
|
2145
|
+
// MEETING
|
|
1954
2146
|
getMeeting() {
|
|
1955
2147
|
return this.room;
|
|
1956
2148
|
}
|
|
1957
|
-
//
|
|
2149
|
+
// ADMISSION CONTROL
|
|
1958
2150
|
async approveJoinRequest(requestId) {
|
|
1959
|
-
if (!this.sfuClient)
|
|
2151
|
+
if (!this.sfuClient) {
|
|
2152
|
+
throw new Error("SFU client is not connected");
|
|
2153
|
+
}
|
|
1960
2154
|
return this.sfuClient.approveJoinRequest(requestId);
|
|
1961
2155
|
}
|
|
1962
2156
|
async rejectJoinRequest(requestId, reason) {
|
|
1963
|
-
if (!this.sfuClient)
|
|
2157
|
+
if (!this.sfuClient) {
|
|
2158
|
+
throw new Error("SFU client is not connected");
|
|
2159
|
+
}
|
|
1964
2160
|
return this.sfuClient.rejectJoinRequest(requestId, reason);
|
|
1965
2161
|
}
|
|
1966
|
-
//
|
|
2162
|
+
// MEDIA CONTROL
|
|
1967
2163
|
toggleMic() {
|
|
1968
2164
|
const mediaState = this.state.localParticipant?.media;
|
|
1969
|
-
if (!mediaState || !this.localStream)
|
|
2165
|
+
if (!mediaState || !this.localStream) {
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
1970
2168
|
const nextEnabled = !mediaState.micEnabled;
|
|
1971
2169
|
this.localStream.getAudioTracks().forEach((track) => {
|
|
1972
2170
|
track.enabled = nextEnabled;
|
|
@@ -1974,14 +2172,18 @@ var VideoSDKCore = class {
|
|
|
1974
2172
|
this.state.updateLocalParticipant({
|
|
1975
2173
|
id: this.myId,
|
|
1976
2174
|
name: this.participantName,
|
|
1977
|
-
media: {
|
|
2175
|
+
media: {
|
|
2176
|
+
micEnabled: nextEnabled
|
|
2177
|
+
}
|
|
1978
2178
|
});
|
|
1979
2179
|
this.sfuClient?.muteAudio(!nextEnabled);
|
|
1980
2180
|
this.events.onMicToggled?.(this.myId, nextEnabled);
|
|
1981
2181
|
}
|
|
1982
2182
|
toggleCam() {
|
|
1983
2183
|
const mediaState = this.state.localParticipant?.media;
|
|
1984
|
-
if (!mediaState || !this.localStream)
|
|
2184
|
+
if (!mediaState || !this.localStream) {
|
|
2185
|
+
return;
|
|
2186
|
+
}
|
|
1985
2187
|
const nextEnabled = !mediaState.camEnabled;
|
|
1986
2188
|
this.localStream.getVideoTracks().forEach((track) => {
|
|
1987
2189
|
track.enabled = nextEnabled;
|
|
@@ -1989,12 +2191,14 @@ var VideoSDKCore = class {
|
|
|
1989
2191
|
this.state.updateLocalParticipant({
|
|
1990
2192
|
id: this.myId,
|
|
1991
2193
|
name: this.participantName,
|
|
1992
|
-
media: {
|
|
2194
|
+
media: {
|
|
2195
|
+
camEnabled: nextEnabled
|
|
2196
|
+
}
|
|
1993
2197
|
});
|
|
1994
2198
|
this.sfuClient?.muteVideo(!nextEnabled);
|
|
1995
2199
|
this.events.onCamToggled?.(this.myId, nextEnabled);
|
|
1996
2200
|
}
|
|
1997
|
-
//
|
|
2201
|
+
// SCREEN SHARING
|
|
1998
2202
|
async startScreenShare() {
|
|
1999
2203
|
try {
|
|
2000
2204
|
if (!navigator.mediaDevices?.getDisplayMedia) {
|
|
@@ -2026,7 +2230,9 @@ var VideoSDKCore = class {
|
|
|
2026
2230
|
}
|
|
2027
2231
|
}
|
|
2028
2232
|
stopScreenShare() {
|
|
2029
|
-
if (!this.screenStream)
|
|
2233
|
+
if (!this.screenStream) {
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
2030
2236
|
void this.sfuClient?.stopScreenShare();
|
|
2031
2237
|
this.screenStream.getTracks().forEach((track) => track.stop());
|
|
2032
2238
|
this.screenStream = null;
|
|
@@ -2039,7 +2245,7 @@ var VideoSDKCore = class {
|
|
|
2039
2245
|
}
|
|
2040
2246
|
});
|
|
2041
2247
|
}
|
|
2042
|
-
//
|
|
2248
|
+
// RECORDING
|
|
2043
2249
|
async startRecording() {
|
|
2044
2250
|
if (!this.sfuClient) {
|
|
2045
2251
|
throw new Error("SFU client is not connected");
|
|
@@ -2058,7 +2264,7 @@ var VideoSDKCore = class {
|
|
|
2058
2264
|
}
|
|
2059
2265
|
return this.sfuClient.getRecordingStatus();
|
|
2060
2266
|
}
|
|
2061
|
-
//
|
|
2267
|
+
// CHAT
|
|
2062
2268
|
sendChatMessage(payload) {
|
|
2063
2269
|
if (!this.sfuClient) {
|
|
2064
2270
|
console.warn("SFU Client not connected");
|
|
@@ -2077,7 +2283,7 @@ var VideoSDKCore = class {
|
|
|
2077
2283
|
this.state.addChatMessage(msg);
|
|
2078
2284
|
this.events.onChatMessage?.(msg);
|
|
2079
2285
|
}
|
|
2080
|
-
//
|
|
2286
|
+
// CLEANUP
|
|
2081
2287
|
cleanupRemoteParticipant(participantId) {
|
|
2082
2288
|
this.activeSpeakerDetector.removeParticipant(participantId);
|
|
2083
2289
|
const stream = this.remoteStreams.get(participantId);
|
|
@@ -2087,16 +2293,34 @@ var VideoSDKCore = class {
|
|
|
2087
2293
|
screen?.getTracks().forEach((track) => track.stop());
|
|
2088
2294
|
this.remoteScreenStreams.delete(participantId);
|
|
2089
2295
|
}
|
|
2296
|
+
// DISCONNECT
|
|
2090
2297
|
async disconnect() {
|
|
2298
|
+
if (this.disconnectPromise) {
|
|
2299
|
+
return this.disconnectPromise;
|
|
2300
|
+
}
|
|
2301
|
+
this.disconnectPromise = this.performDisconnect();
|
|
2302
|
+
try {
|
|
2303
|
+
await this.disconnectPromise;
|
|
2304
|
+
} finally {
|
|
2305
|
+
this.disconnectPromise = null;
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
async performDisconnect() {
|
|
2091
2309
|
this.intentionalDisconnect = true;
|
|
2092
|
-
|
|
2310
|
+
this.joinPromise = null;
|
|
2311
|
+
const client = this.sfuClient;
|
|
2093
2312
|
this.sfuClient = null;
|
|
2313
|
+
if (client) {
|
|
2314
|
+
await client.leave();
|
|
2315
|
+
}
|
|
2094
2316
|
if (this.localStream) {
|
|
2095
2317
|
this.localStream.getTracks().forEach((track) => track.stop());
|
|
2096
2318
|
this.localStream = null;
|
|
2097
2319
|
}
|
|
2098
|
-
this.screenStream
|
|
2099
|
-
|
|
2320
|
+
if (this.screenStream) {
|
|
2321
|
+
this.screenStream.getTracks().forEach((track) => track.stop());
|
|
2322
|
+
this.screenStream = null;
|
|
2323
|
+
}
|
|
2100
2324
|
this.isScreenSharing = false;
|
|
2101
2325
|
for (const stream of this.remoteStreams.values()) {
|
|
2102
2326
|
stream.getTracks().forEach((track) => track.stop());
|