@livo-tv/sdk 1.6.1 → 1.7.0

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.
@@ -180,6 +180,7 @@ type StudioMeetingHandle = {
180
180
  stageStatus: () => StudioStageStatus;
181
181
  stageRequests: () => StudioStageRequest[];
182
182
  subscribeStage: (cb: () => void) => () => void;
183
+ subscribeStageGrant: (cb: (userId: string) => void) => () => void;
183
184
  canManageStage: () => boolean;
184
185
  stageEnabled: () => boolean;
185
186
  ensureStageJoined: () => Promise<void>;
@@ -180,6 +180,7 @@ type StudioMeetingHandle = {
180
180
  stageStatus: () => StudioStageStatus;
181
181
  stageRequests: () => StudioStageRequest[];
182
182
  subscribeStage: (cb: () => void) => () => void;
183
+ subscribeStageGrant: (cb: (userId: string) => void) => () => void;
183
184
  canManageStage: () => boolean;
184
185
  stageEnabled: () => boolean;
185
186
  ensureStageJoined: () => Promise<void>;
@@ -5023,10 +5023,11 @@ function StudioRoom({
5023
5023
  ]);
5024
5024
  const verifyGrantTookEffect = useCallback(
5025
5025
  (userId, retried = false) => {
5026
+ if (!userId) return;
5026
5027
  const id = window.setTimeout(() => {
5027
- const target = meeting.participants().find((row) => row.userId === userId);
5028
- if (!target || isOnStage(target.stageStatus)) return;
5029
- if (!retried && target.stageStatus === "ACCEPTED_TO_JOIN_STAGE") {
5028
+ const target = meeting.participants().find((row) => row.userId === userId || row.id === userId);
5029
+ if (target && isOnStage(target.stageStatus)) return;
5030
+ if (!retried) {
5030
5031
  void meeting.setStageAccess(userId, true).then(() => {
5031
5032
  verifyGrantTookEffect(userId, true);
5032
5033
  }).catch(() => {
@@ -5040,6 +5041,12 @@ function StudioRoom({
5040
5041
  },
5041
5042
  [labels.stageGrantNoEffect, meeting]
5042
5043
  );
5044
+ useEffect(
5045
+ () => meeting.subscribeStageGrant((userId) => {
5046
+ verifyGrantTookEffect(userId);
5047
+ }),
5048
+ [meeting, verifyGrantTookEffect]
5049
+ );
5043
5050
  const onSetStage = useCallback(
5044
5051
  async (tile, on) => {
5045
5052
  const userId = tile.userId;
@@ -5364,20 +5371,13 @@ function StudioRoom({
5364
5371
  peopleBadge,
5365
5372
  labels,
5366
5373
  onAdmitAs: (id, as) => {
5367
- const guest = waitlisted.find((row) => row.id === id);
5368
- void Promise.resolve(meeting.admitAs(id, as)).then(() => {
5369
- if (as === "panelist" && guest?.userId) {
5370
- verifyGrantTookEffect(guest.userId);
5371
- }
5374
+ void Promise.resolve(meeting.admitAs(id, as)).catch(() => {
5375
+ toast.error(labels.stageError);
5372
5376
  });
5373
5377
  },
5374
5378
  onAdmitAll: (as) => {
5375
- const waiting = waitlisted;
5376
- void Promise.resolve(meeting.admitAll(as)).then(() => {
5377
- if (as !== "panelist") return;
5378
- for (const guest of waiting) {
5379
- if (guest.userId) verifyGrantTookEffect(guest.userId);
5380
- }
5379
+ void Promise.resolve(meeting.admitAll(as)).catch(() => {
5380
+ toast.error(labels.stageError);
5381
5381
  });
5382
5382
  },
5383
5383
  onDeny: (id) => {
@@ -6080,6 +6080,7 @@ function activeSpeakerFrom(payload, fallback) {
6080
6080
  }
6081
6081
  var STAGE_JOIN_RETRY_MS = 1e3;
6082
6082
  var STAGE_JOIN_MAX_ATTEMPTS = 3;
6083
+ var STAGE_GRANT_JOIN_MAX_FAILURES = 8;
6083
6084
  function isStageDisabledError(error) {
6084
6085
  if (!error || typeof error !== "object") return false;
6085
6086
  const row = error;
@@ -6152,12 +6153,15 @@ function studioMeetingFromRtk(raw) {
6152
6153
  const chatListeners = /* @__PURE__ */ new Set();
6153
6154
  const participantListeners = /* @__PURE__ */ new Set();
6154
6155
  const stageListeners = /* @__PURE__ */ new Set();
6156
+ const stageGrantListeners = /* @__PURE__ */ new Set();
6155
6157
  const pinListeners = /* @__PURE__ */ new Set();
6156
6158
  const pendingPanelists = /* @__PURE__ */ new Set();
6157
6159
  const chatSend = /* @__PURE__ */ new Map();
6158
6160
  const nameOverrides = /* @__PURE__ */ new Map();
6159
6161
  let joinStageInFlight = null;
6160
6162
  let joinedStageLocally = false;
6163
+ let stageGrantPending = false;
6164
+ let stageGrantJoinFailures = 0;
6161
6165
  let lastMeetingError = null;
6162
6166
  const historyMessages = [];
6163
6167
  let speakerSinkId = null;
@@ -6256,6 +6260,21 @@ function studioMeetingFromRtk(raw) {
6256
6260
  function currentStageStatus() {
6257
6261
  return meeting.self?.stageStatus ?? meeting.stage?.status;
6258
6262
  }
6263
+ function isStageAccepted() {
6264
+ return meeting.self?.stageStatus === "ACCEPTED_TO_JOIN_STAGE" || meeting.stage?.status === "ACCEPTED_TO_JOIN_STAGE";
6265
+ }
6266
+ function clearStageGrantPending() {
6267
+ stageGrantPending = false;
6268
+ stageGrantJoinFailures = 0;
6269
+ }
6270
+ function notifyStageGrant(userId) {
6271
+ if (!userId) return;
6272
+ for (const listener of stageGrantListeners) listener(userId);
6273
+ }
6274
+ function markStageGranted() {
6275
+ stageGrantPending = true;
6276
+ void ensureStageJoined();
6277
+ }
6259
6278
  function resolveConnectionState() {
6260
6279
  const self = meeting.self;
6261
6280
  const roomState = self?.roomState ?? null;
@@ -6347,22 +6366,21 @@ function studioMeetingFromRtk(raw) {
6347
6366
  joinStageInFlight = null;
6348
6367
  }
6349
6368
  }
6350
- async function autoJoinStage() {
6369
+ async function ensureStageJoined() {
6351
6370
  if (currentStageStatus() === "ON_STAGE") {
6352
- notifyStage();
6371
+ clearStageGrantPending();
6353
6372
  return;
6354
6373
  }
6374
+ if (!stageGrantPending && !isStageAccepted()) return;
6375
+ stageGrantPending = true;
6376
+ if (stageGrantJoinFailures >= STAGE_GRANT_JOIN_MAX_FAILURES) return;
6355
6377
  try {
6356
6378
  await joinStageWithRetry();
6379
+ if (currentStageStatus() === "ON_STAGE") {
6380
+ clearStageGrantPending();
6381
+ }
6357
6382
  } catch {
6358
- notifyStage();
6359
- }
6360
- }
6361
- async function ensureStageJoined() {
6362
- if (currentStageStatus() !== "ACCEPTED_TO_JOIN_STAGE") return;
6363
- try {
6364
- await joinStageWithRetry();
6365
- } catch {
6383
+ stageGrantJoinFailures += 1;
6366
6384
  notifyStage();
6367
6385
  }
6368
6386
  }
@@ -6393,6 +6411,7 @@ function studioMeetingFromRtk(raw) {
6393
6411
  const grantId = takePendingGrantId(joined2);
6394
6412
  if (!grantId) return;
6395
6413
  await meeting.stage?.grantAccess?.([grantId]);
6414
+ notifyStageGrant(joined2?.userId || grantId);
6396
6415
  notifyStage();
6397
6416
  }
6398
6417
  async function grantPendingIfJoined(participantId, userId) {
@@ -6401,6 +6420,7 @@ function studioMeetingFromRtk(raw) {
6401
6420
  if (!grantId) return;
6402
6421
  pendingPanelists.delete(participantId);
6403
6422
  await meeting.stage?.grantAccess?.([grantId]);
6423
+ notifyStageGrant(already?.userId || grantId);
6404
6424
  notifyStage();
6405
6425
  }
6406
6426
  listen(meeting.self, "videoUpdate", rebindMedia);
@@ -6450,15 +6470,15 @@ function studioMeetingFromRtk(raw) {
6450
6470
  listen(meeting.stage, "stageAccessRequestUpdate", notifyStage);
6451
6471
  listen(meeting.stage, "newStageRequest", notifyStage);
6452
6472
  listen(meeting.stage, "stageRequestApproved", () => {
6453
- void autoJoinStage();
6473
+ markStageGranted();
6474
+ });
6475
+ listen(meeting.stage, "acceptPresentRequests", () => {
6476
+ markStageGranted();
6454
6477
  });
6455
6478
  listen(meeting.stage, "stageRequestRejected", notifyStage);
6456
- listen(meeting.stage, "stageStatusUpdate", (status) => {
6457
- if (status === "ACCEPTED_TO_JOIN_STAGE") {
6458
- void autoJoinStage();
6459
- return;
6460
- }
6461
- if (status === "ON_STAGE") {
6479
+ listen(meeting.stage, "stageStatusUpdate", () => {
6480
+ if (currentStageStatus() === "ON_STAGE") {
6481
+ clearStageGrantPending();
6462
6482
  if (joinedStageLocally) {
6463
6483
  joinedStageLocally = false;
6464
6484
  notifyStage();
@@ -6469,12 +6489,17 @@ function studioMeetingFromRtk(raw) {
6469
6489
  });
6470
6490
  return;
6471
6491
  }
6492
+ if (isStageAccepted()) {
6493
+ markStageGranted();
6494
+ return;
6495
+ }
6472
6496
  notifyStage();
6473
6497
  });
6474
6498
  return {
6475
6499
  async join() {
6476
6500
  await meeting.join?.();
6477
6501
  await joinStageIfAllowed();
6502
+ await ensureStageJoined();
6478
6503
  await applyCurrentVideoEffects();
6479
6504
  },
6480
6505
  async leave() {
@@ -6570,7 +6595,12 @@ function studioMeetingFromRtk(raw) {
6570
6595
  return;
6571
6596
  }
6572
6597
  if (on) await meeting.stage?.grantAccess?.([userId]);
6573
- else await meeting.stage?.kick?.([userId]);
6598
+ else {
6599
+ await meeting.stage?.kick?.([userId]);
6600
+ if (meeting.self?.userId === userId) {
6601
+ clearStageGrantPending();
6602
+ }
6603
+ }
6574
6604
  notifyStage();
6575
6605
  },
6576
6606
  async denyStageAccess(userId) {
@@ -6601,6 +6631,7 @@ function studioMeetingFromRtk(raw) {
6601
6631
  async leaveStage() {
6602
6632
  await meeting.stage?.leave?.();
6603
6633
  joinedStageLocally = false;
6634
+ clearStageGrantPending();
6604
6635
  notifyStage();
6605
6636
  },
6606
6637
  stageStatus() {
@@ -6632,6 +6663,12 @@ function studioMeetingFromRtk(raw) {
6632
6663
  stageListeners.delete(cb);
6633
6664
  };
6634
6665
  },
6666
+ subscribeStageGrant(cb) {
6667
+ stageGrantListeners.add(cb);
6668
+ return () => {
6669
+ stageGrantListeners.delete(cb);
6670
+ };
6671
+ },
6635
6672
  canManageStage() {
6636
6673
  return meeting.self?.permissions?.acceptStageRequests === true;
6637
6674
  },