@defcomm/meetingsdk 0.2.7 → 0.2.8

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
@@ -357,6 +357,7 @@ var SFUClient = class {
357
357
  this.iceServers = [];
358
358
  this.requestId = 0;
359
359
  this.publisherRevision = 0;
360
+ this.screenShareGeneration = 0;
360
361
  this.currentParticipantId = null;
361
362
  this.token = null;
362
363
  this.room = null;
@@ -439,6 +440,8 @@ var SFUClient = class {
439
440
  this.onError = options.onError || console.error;
440
441
  this.onChatMessage = options.onChatMessage || (() => {
441
442
  });
443
+ this.onReaction = options.onReaction || (() => {
444
+ });
442
445
  this.onRoomUpdate = options.onRoomUpdate || (() => {
443
446
  });
444
447
  this.onParticipantMediaState = options.onParticipantMediaState || (() => {
@@ -565,8 +568,10 @@ var SFUClient = class {
565
568
  avatarUrl: options.avatarUrl
566
569
  };
567
570
  if (options.participantId) this.participantId = options.participantId;
568
- if (typeof options.audioMuted === "boolean") this.desiredAudioMuted = options.audioMuted;
569
- if (typeof options.videoMuted === "boolean") this.desiredVideoMuted = options.videoMuted;
571
+ if (typeof options.audioMuted === "boolean")
572
+ this.desiredAudioMuted = options.audioMuted;
573
+ if (typeof options.videoMuted === "boolean")
574
+ this.desiredVideoMuted = options.videoMuted;
570
575
  if (!this.resumeToken) {
571
576
  this.resumeToken = this.loadResumeToken(this.room);
572
577
  }
@@ -951,8 +956,15 @@ var SFUClient = class {
951
956
  if (!pid) continue;
952
957
  if (pid !== this.currentParticipantId) {
953
958
  this.participants.set(pid, participant);
954
- this.onParticipantPresence(pid, String(participant.presence || "connected"));
955
- this.onParticipantMediaState(pid, Boolean(participant.audio_muted), Boolean(participant.video_muted));
959
+ this.onParticipantPresence(
960
+ pid,
961
+ String(participant.presence || "connected")
962
+ );
963
+ this.onParticipantMediaState(
964
+ pid,
965
+ Boolean(participant.audio_muted),
966
+ Boolean(participant.video_muted)
967
+ );
956
968
  }
957
969
  }
958
970
  for (const [pid] of this.participants) {
@@ -964,7 +976,8 @@ var SFUClient = class {
964
976
  }
965
977
  for (const track of payload.tracks || []) this.rememberTrack(track);
966
978
  if (Array.isArray(payload.chat_history)) {
967
- for (const message2 of payload.chat_history) this.onChatMessage(message2);
979
+ for (const message2 of payload.chat_history)
980
+ this.onChatMessage(message2);
968
981
  }
969
982
  break;
970
983
  }
@@ -973,6 +986,11 @@ var SFUClient = class {
973
986
  if (msg?.id) this.onChatMessage(msg);
974
987
  break;
975
988
  }
989
+ case "reaction": {
990
+ const reaction = payload.reaction || payload;
991
+ if (reaction?.id && reaction?.emoji) this.onReaction(reaction);
992
+ break;
993
+ }
976
994
  case "error": {
977
995
  const message2 = payload.message || "SFU signaling error";
978
996
  if (this.state === "reconnecting" && message2.includes("participant session is stale")) {
@@ -997,7 +1015,9 @@ var SFUClient = class {
997
1015
  }
998
1016
  onRoomStateReconciled(payload) {
999
1017
  const participants = payload.participants || [];
1000
- const local = participants.find((p) => p?.participant_id === this.currentParticipantId);
1018
+ const local = participants.find(
1019
+ (p) => p?.participant_id === this.currentParticipantId
1020
+ );
1001
1021
  if (local) {
1002
1022
  this.desiredAudioMuted = Boolean(local.audio_muted);
1003
1023
  this.desiredVideoMuted = Boolean(local.video_muted);
@@ -1411,9 +1431,27 @@ var SFUClient = class {
1411
1431
  if (!this.screenTransceiver || !this.screenSender) {
1412
1432
  throw new Error("Screen transceiver is not initialized");
1413
1433
  }
1414
- if (this.screenStream) {
1434
+ const existingTrack = this.screenSender.track;
1435
+ if (this.screenStream && existingTrack && existingTrack.readyState === "live") {
1415
1436
  return this.screenStream;
1416
1437
  }
1438
+ const generation = ++this.screenShareGeneration;
1439
+ try {
1440
+ await this.screenSender.replaceTrack(null);
1441
+ } catch (err) {
1442
+ console.warn("[SFU] Failed to clear previous screen track", err);
1443
+ }
1444
+ if (this.screenStream) {
1445
+ for (const track of this.screenStream.getTracks()) {
1446
+ try {
1447
+ track.onended = null;
1448
+ track.stop();
1449
+ } catch {
1450
+ }
1451
+ }
1452
+ }
1453
+ this.screenStream = null;
1454
+ this.screenTransceiver.direction = "inactive";
1417
1455
  const screenStream = await navigator.mediaDevices.getDisplayMedia({
1418
1456
  video: true,
1419
1457
  audio: false
@@ -1427,74 +1465,87 @@ var SFUClient = class {
1427
1465
  try {
1428
1466
  await this.screenSender.replaceTrack(screenTrack);
1429
1467
  this.screenTransceiver.direction = "sendonly";
1468
+ this.send({
1469
+ type: "start_screen_share"
1470
+ });
1471
+ await new Promise((resolve) => {
1472
+ queueMicrotask(resolve);
1473
+ });
1430
1474
  const offer = await this.publisher.createOffer();
1431
1475
  await this.publisher.setLocalDescription(offer);
1476
+ const localDescription = this.publisher.localDescription;
1477
+ if (!localDescription?.sdp) {
1478
+ throw new Error("Publisher did not produce a valid screen-share SDP");
1479
+ }
1480
+ const revision = ++this.publisherRevision;
1432
1481
  this.send({
1433
1482
  type: "publish_offer",
1434
1483
  payload: {
1435
- revision: ++this.publisherRevision,
1436
- sdp: offer.sdp
1484
+ revision,
1485
+ sdp: localDescription.sdp
1437
1486
  }
1438
1487
  });
1439
- this.send({ type: "start_screen_share" });
1440
1488
  screenTrack.onended = () => {
1489
+ if (generation !== this.screenShareGeneration) {
1490
+ return;
1491
+ }
1441
1492
  void this.stopScreenShare();
1442
1493
  };
1443
- console.info("[publisher] Screen track attached", {
1444
- screenTrackId: screenTrack.id,
1445
- cameraTrackId: this.videoSender?.track?.id,
1446
- screenMid: this.screenTransceiver.mid,
1447
- cameraMid: this.cameraTransceiver?.mid
1448
- });
1449
- return this.screenStream;
1494
+ return screenStream;
1450
1495
  } catch (error) {
1451
- try {
1452
- await this.screenSender.replaceTrack(null);
1453
- } catch {
1454
- }
1455
- this.screenTransceiver.direction = "inactive";
1456
- screenStream.getTracks().forEach((track) => track.stop());
1457
- this.screenStream = null;
1458
- try {
1459
- this.send({ type: "stop_screen_share" });
1460
- } catch {
1496
+ if (generation === this.screenShareGeneration) {
1497
+ try {
1498
+ await this.screenSender.replaceTrack(null);
1499
+ } catch {
1500
+ }
1501
+ this.screenTransceiver.direction = "inactive";
1502
+ screenTrack.onended = null;
1503
+ screenStream.getTracks().forEach((track) => {
1504
+ try {
1505
+ track.stop();
1506
+ } catch {
1507
+ }
1508
+ });
1509
+ this.screenStream = null;
1510
+ try {
1511
+ this.send({
1512
+ type: "stop_screen_share"
1513
+ });
1514
+ } catch {
1515
+ }
1461
1516
  }
1462
1517
  throw error;
1463
1518
  }
1464
1519
  }
1465
1520
  async stopScreenShare() {
1466
- if (!this.screenStream && !this.screenSender?.track) return;
1467
- const screenTrack = this.screenSender?.track || null;
1468
- if (screenTrack) screenTrack.onended = null;
1521
+ const stream = this.screenStream;
1522
+ const senderTrack = this.screenSender?.track || null;
1523
+ if (!stream && !senderTrack) return;
1524
+ ++this.screenShareGeneration;
1525
+ if (senderTrack) senderTrack.onended = null;
1469
1526
  try {
1470
1527
  await this.screenSender?.replaceTrack(null);
1471
1528
  } catch (error) {
1472
1529
  this.onError(error);
1473
1530
  }
1474
- if (this.screenTransceiver) {
1475
- this.screenTransceiver.direction = "inactive";
1476
- }
1477
- this.send({ type: "stop_screen_share" });
1531
+ if (this.screenTransceiver) this.screenTransceiver.direction = "inactive";
1478
1532
  try {
1479
1533
  const offer = await this.publisher?.createOffer();
1480
1534
  if (offer && this.publisher) {
1481
1535
  await this.publisher.setLocalDescription(offer);
1482
1536
  this.send({
1483
1537
  type: "publish_offer",
1484
- payload: {
1485
- revision: ++this.publisherRevision,
1486
- sdp: offer.sdp
1487
- }
1538
+ payload: { revision: ++this.publisherRevision, sdp: offer.sdp }
1488
1539
  });
1489
1540
  }
1490
1541
  } catch (error) {
1491
1542
  this.onError(error);
1492
1543
  }
1493
- this.screenStream?.getTracks().forEach((track) => track.stop());
1544
+ this.send({ type: "stop_screen_share" });
1545
+ stream?.getTracks().forEach((track) => track.stop());
1494
1546
  this.screenStream = null;
1495
- if (this.currentParticipantId) {
1547
+ if (this.currentParticipantId)
1496
1548
  this.onScreenShareStopped(this.currentParticipantId);
1497
- }
1498
1549
  }
1499
1550
  muteAudio(muted) {
1500
1551
  this.desiredAudioMuted = muted;
@@ -1597,6 +1648,12 @@ var SFUClient = class {
1597
1648
  );
1598
1649
  return true;
1599
1650
  }
1651
+ sendReaction(reaction) {
1652
+ this.send({
1653
+ type: "send_reaction",
1654
+ reaction
1655
+ });
1656
+ }
1600
1657
  removeRemoteParticipant(participantId) {
1601
1658
  const stream = this.remoteStreams.get(participantId);
1602
1659
  if (stream) {
@@ -2015,7 +2072,9 @@ var VideoSDKCore = class {
2015
2072
  // ADMISSION CONTROL
2016
2073
  onRoomUpdate: (payload) => {
2017
2074
  const participants = payload?.participants || [];
2018
- const local = participants.find((p) => p?.participant_id === this.myId);
2075
+ const local = participants.find(
2076
+ (p) => p?.participant_id === this.myId
2077
+ );
2019
2078
  if (local) {
2020
2079
  const media = this.state.localParticipant?.media;
2021
2080
  this.state.updateLocalParticipant({
@@ -2038,7 +2097,8 @@ var VideoSDKCore = class {
2038
2097
  });
2039
2098
  }
2040
2099
  if (local.screen_sharing) this.state.setPresenterId(this.myId);
2041
- else if (this.state.presenterId === this.myId) this.state.setPresenterId(null);
2100
+ else if (this.state.presenterId === this.myId)
2101
+ this.state.setPresenterId(null);
2042
2102
  }
2043
2103
  const presenter = participants.find((p) => p?.screen_sharing);
2044
2104
  if (presenter) this.state.setPresenterId(presenter.participant_id);
@@ -2469,7 +2529,7 @@ var VideoSDKCore = class {
2469
2529
  media: {
2470
2530
  isScreenSharing: true,
2471
2531
  screenStream: this.screenStream,
2472
- screenTrack: this.screenStream.getVideoTracks()[0]
2532
+ screenTrack: this.screenStream?.getVideoTracks()[0]
2473
2533
  }
2474
2534
  });
2475
2535
  return this.screenStream;
@@ -2534,6 +2594,13 @@ var VideoSDKCore = class {
2534
2594
  target: payload.target ?? null
2535
2595
  });
2536
2596
  }
2597
+ sendReaction(reaction) {
2598
+ if (!this.sfuClient) {
2599
+ console.warn("SFU Client not connected");
2600
+ return;
2601
+ }
2602
+ this.sfuClient.sendReaction(reaction);
2603
+ }
2537
2604
  // CLEANUP
2538
2605
  cleanupRemoteParticipant(participantId) {
2539
2606
  this.activeSpeakerDetector.removeParticipant(participantId);
@@ -2620,6 +2687,7 @@ var MeetingProvider = ({
2620
2687
  const trackStateListeners = useRef(/* @__PURE__ */ new Set());
2621
2688
  const recordingStartedListeners = useRef(/* @__PURE__ */ new Set());
2622
2689
  const recordingStoppedListeners = useRef(/* @__PURE__ */ new Set());
2690
+ const reactionListeners = useRef(/* @__PURE__ */ new Set());
2623
2691
  const activeSpeakerListeners = useRef(
2624
2692
  /* @__PURE__ */ new Set()
2625
2693
  );
@@ -2649,6 +2717,9 @@ var MeetingProvider = ({
2649
2717
  },
2650
2718
  onRecordingStopped: (recording) => {
2651
2719
  recordingStoppedListeners.current.forEach((fn) => fn(recording));
2720
+ },
2721
+ onReaction: (reaction) => {
2722
+ reactionListeners.current.forEach((fn) => fn(reaction));
2652
2723
  }
2653
2724
  }
2654
2725
  );
@@ -2695,6 +2766,7 @@ var MeetingProvider = ({
2695
2766
  startScreenShare: sdk.startScreenShare.bind(sdk),
2696
2767
  stopScreenShare: sdk.stopScreenShare.bind(sdk),
2697
2768
  sendMessage: sdk.sendChatMessage.bind(sdk),
2769
+ sendReaction: sdk.sendReaction.bind(sdk),
2698
2770
  room: sdk.getMeeting(),
2699
2771
  localParticipant,
2700
2772
  participants,
@@ -2751,6 +2823,10 @@ var MeetingProvider = ({
2751
2823
  recordingStoppedListeners.current.add(cb);
2752
2824
  return () => recordingStoppedListeners.current.delete(cb);
2753
2825
  },
2826
+ onReaction: (cb) => {
2827
+ reactionListeners.current.add(cb);
2828
+ return () => reactionListeners.current.delete(cb);
2829
+ },
2754
2830
  onActiveSpeakersChanged: (cb) => {
2755
2831
  activeSpeakerListeners.current.add(cb);
2756
2832
  return () => {
@@ -2847,6 +2923,10 @@ var useMeeting = (handlers) => {
2847
2923
  if (!handlers?.onRecordingStopped) return;
2848
2924
  return ctx.onRecordingStopped(handlers.onRecordingStopped);
2849
2925
  }, [handlers?.onRecordingStopped]);
2926
+ useEffect3(() => {
2927
+ if (!handlers?.onReaction) return;
2928
+ return ctx.onReaction(handlers.onReaction);
2929
+ }, [handlers?.onReaction]);
2850
2930
  const { sdk: _, ...publicApi } = ctx;
2851
2931
  return publicApi;
2852
2932
  };