@defcomm/meetingsdk 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -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 stream = participantId ? this.remoteStreams.get(participantId) : null;
871
+ const screenStream = participantId ? this.remoteScreenStreams.get(participantId) : null;
868
872
  const trackId = payload.track_id;
869
- if (stream) {
870
- for (const track of stream.getVideoTracks()) {
871
- const source = this.trackToSource.get(this.normalizeRemoteTrackId(track.id));
872
- if (!trackId || this.normalizeRemoteTrackId(track.id) === String(trackId) || source === "screen") {
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 source = metadata.source || this.trackToSource.get(metadata.track_id);
990
- if (source === "screen") {
991
- for (const existing of remoteStream.getVideoTracks()) {
992
- if (existing.id !== event.track.id && this.trackToSource.get(existing.id) === "screen") {
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
- if (source === "screen") {
1015
- this.onScreenShareStarted(participantId, metadata);
1016
- }
1017
- this.onRemoteTrack(event.track, remoteStream, metadata);
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 sfuTrackId = this.normalizeRemoteTrackId(track.id);
1024
- const exact = this.trackMetadata.get(sfuTrackId);
1025
- if (exact) return exact;
1026
- const byTrackId = this.trackMetadata.get(track.id);
1027
- if (byTrackId) return byTrackId;
1028
- const matching = [...this.trackMetadata.values()].find(
1029
- (entry) => entry.kind === track.kind
1030
- );
1031
- if (matching) return matching;
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.audioSender = null;
1170
- this.videoSender = null;
1171
- for (const track of this.localStream.getTracks()) {
1172
- if (track.kind === "audio" && !audio) continue;
1173
- if (track.kind === "video" && !video) continue;
1174
- const sender = this.publisher.addTrack(track, this.localStream);
1175
- if (track.kind === "audio") this.audioSender = sender;
1176
- if (track.kind === "video") this.videoSender = sender;
1177
- }
1178
- if (this.screenStream?.getVideoTracks()[0]) {
1179
- const screenTrack = this.screenStream.getVideoTracks()[0];
1180
- this.screenSender = this.publisher.addTrack(screenTrack, this.screenStream);
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 (this.screenStream?.getVideoTracks()[0]) {
1238
+ if (screenTrack) {
1193
1239
  this.send({ type: "start_screen_share" });
1194
1240
  }
1195
1241
  }
1196
1242
  async shareScreen() {
1197
- if (!this.publisher) throw new Error("Publisher PeerConnection is not initialized");
1198
- if (this.screenStream) return this.screenStream;
1199
- this.screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true });
1200
- const screenTrack = this.screenStream.getVideoTracks()[0];
1201
- if (!screenTrack) throw new Error("Screen share returned no video track");
1202
- this.screenSender = this.publisher.addTrack(screenTrack, this.screenStream);
1203
- const offer = await this.publisher.createOffer();
1204
- await this.publisher.setLocalDescription(offer);
1205
- this.send({
1206
- type: "publish_offer",
1207
- payload: {
1208
- revision: ++this.publisherRevision,
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
- this.send({ type: "start_screen_share" });
1213
- screenTrack.onended = () => {
1214
- void this.stopScreenShare();
1215
- };
1216
- return this.screenStream;
1217
- }
1218
- async stopScreenShare() {
1219
- if (!this.screenStream) return;
1220
- const screenTrack = this.screenStream.getVideoTracks()[0];
1221
- if (screenTrack) screenTrack.onended = null;
1222
- if (this.screenSender && this.publisher) {
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.publisher.removeTrack(this.screenSender);
1289
+ await this.screenSender.replaceTrack(null);
1225
1290
  } catch {
1226
1291
  }
1227
- this.screenSender = null;
1292
+ this.screenTransceiver.direction = "inactive";
1293
+ screenStream.getTracks().forEach((track) => track.stop());
1294
+ this.screenStream = null;
1228
1295
  try {
1229
- const offer = await this.publisher.createOffer();
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.getTracks().forEach((track) => track.stop());
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();
@@ -1872,11 +1974,12 @@ var VideoSDKCore = class {
1872
1974
  this.handleRemoteTrack(track, stream, metadata);
1873
1975
  },
1874
1976
  // SCREEN SHARE
1875
- onScreenShareStarted: (participantId, track) => {
1977
+ onScreenShareStarted: (participantId, _track) => {
1876
1978
  if (participantId === this.myId) {
1877
1979
  return;
1878
1980
  }
1879
- console.log("[SFU] Screen share started", participantId, track);
1981
+ console.log("[SFU] Screen share started", participantId);
1982
+ this.state.setPresenterId(participantId);
1880
1983
  const current = this.state.getParticipant(participantId);
1881
1984
  if (current) {
1882
1985
  this.state.updateParticipantMedia(participantId, {