@livo-tv/sdk 1.6.0 → 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.
@@ -4085,227 +4085,223 @@ function StudioToolbar({
4085
4085
  const showStageModerator = session.role === "moderator" && canManageStage;
4086
4086
  const showStageGuest = session.role === "guest" && stageEnabled;
4087
4087
  const captionsLabel = !captionsAvailable ? labels.captions : !captionsReady ? labels.captionsNotReady : captionsOn ? labels.captionsOn : labels.captionsOff;
4088
- return /* @__PURE__ */ jsx("footer", { className: "flex flex-wrap items-center justify-center gap-3 border-t px-3 py-3 sm:justify-between", children: /* @__PURE__ */ jsxs(TooltipProvider, { children: [
4089
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
4090
- showMediaControls ? /* @__PURE__ */ jsxs(Fragment, { children: [
4091
- /* @__PURE__ */ jsx(
4092
- MediaSplit,
4093
- {
4094
- label: muted ? labels.unmute : labels.mute,
4095
- pressed: muted,
4096
- tone: muted ? "danger" : "default",
4097
- onClick: onToggleMute,
4098
- pickerLabel: labels.micDevice,
4099
- options: devices.audio,
4100
- selected: effective.audio,
4101
- onSelect: (id) => void onDeviceChange("audio", id),
4102
- children: muted ? /* @__PURE__ */ jsx(MicOff, {}) : /* @__PURE__ */ jsx(Mic, {})
4103
- }
4104
- ),
4105
- /* @__PURE__ */ jsx(
4106
- MediaSplit,
4107
- {
4108
- label: labels.camera,
4109
- pressed: cameraOn,
4110
- onClick: onToggleCamera,
4111
- pickerLabel: labels.cameraDevice,
4112
- options: devices.video,
4113
- selected: effective.video,
4114
- onSelect: (id) => void onDeviceChange("video", id),
4115
- children: cameraOn ? /* @__PURE__ */ jsx(Video, {}) : /* @__PURE__ */ jsx(VideoOff, {})
4116
- }
4117
- )
4118
- ] }) : null,
4119
- showStageModerator ? /* @__PURE__ */ jsx(
4120
- ControlIcon,
4121
- {
4122
- label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : labels.goOnAir,
4123
- pressed: selfStageStatus !== "ON_STAGE",
4124
- disabled: selfStageStatus === "ON_STAGE" && !canLeaveStage,
4125
- onClick: () => {
4126
- if (selfStageStatus === "ON_STAGE") {
4127
- if (!canLeaveStage) {
4128
- toast.message(labels.offAirLastVisible);
4129
- return;
4130
- }
4131
- onLeaveStage();
4132
- return;
4133
- }
4134
- onAskStage();
4135
- },
4136
- children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
4137
- }
4138
- ) : null,
4139
- showStageGuest ? /* @__PURE__ */ jsx(
4140
- ControlIcon,
4141
- {
4142
- label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
4143
- pressed: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
4144
- disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" || selfStageStatus === "ON_STAGE" && !canLeaveStage,
4145
- onClick: () => {
4146
- if (selfStageStatus === "ON_STAGE") {
4147
- if (!canLeaveStage) {
4148
- toast.message(labels.offAirLastVisible);
4149
- return;
4150
- }
4151
- onLeaveStage();
4152
- return;
4153
- }
4154
- onAskStage();
4155
- },
4156
- children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
4157
- }
4158
- ) : null,
4159
- showScreenControl ? canShareFreely ? /* @__PURE__ */ jsx(
4160
- ControlIcon,
4088
+ return /* @__PURE__ */ jsx("footer", { className: "border-t px-3 py-3", children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
4089
+ showMediaControls ? /* @__PURE__ */ jsxs(Fragment, { children: [
4090
+ /* @__PURE__ */ jsx(
4091
+ MediaSplit,
4161
4092
  {
4162
- label: labels.screen,
4163
- pressed: screenOn,
4164
- disabled: screenPending || !screenAvailable && !screenOn,
4165
- pending: screenPending,
4166
- onClick: onToggleScreen,
4167
- children: /* @__PURE__ */ jsx(MonitorUp, {})
4093
+ label: muted ? labels.unmute : labels.mute,
4094
+ pressed: muted,
4095
+ tone: muted ? "danger" : "default",
4096
+ onClick: onToggleMute,
4097
+ pickerLabel: labels.micDevice,
4098
+ options: devices.audio,
4099
+ selected: effective.audio,
4100
+ onSelect: (id) => void onDeviceChange("audio", id),
4101
+ children: muted ? /* @__PURE__ */ jsx(MicOff, {}) : /* @__PURE__ */ jsx(Mic, {})
4168
4102
  }
4169
- ) : shareGrant === "requested" ? /* @__PURE__ */ jsxs(Fragment, { children: [
4170
- /* @__PURE__ */ jsx(
4171
- ControlIcon,
4172
- {
4173
- label: labels.shareRequested,
4174
- pressed: true,
4175
- disabled: true,
4176
- onClick: () => void 0,
4177
- children: /* @__PURE__ */ jsx(MonitorUp, {})
4178
- }
4179
- ),
4180
- /* @__PURE__ */ jsx(
4181
- ControlIcon,
4182
- {
4183
- label: labels.cancelShareRequest,
4184
- onClick: onCancelShareRequest,
4185
- children: /* @__PURE__ */ jsx(X, {})
4186
- }
4187
- )
4188
- ] }) : /* @__PURE__ */ jsx(ControlIcon, { label: labels.askToShare, onClick: onAskToShare, children: /* @__PURE__ */ jsx(MonitorUp, {}) }) : null,
4103
+ ),
4189
4104
  /* @__PURE__ */ jsx(
4190
- ControlIcon,
4105
+ MediaSplit,
4191
4106
  {
4192
- label: captionsLabel,
4193
- pressed: captionsOn,
4194
- disabled: !captionsAvailable,
4195
- onClick: onToggleCaptions,
4196
- children: /* @__PURE__ */ jsx(Captions, {})
4107
+ label: labels.camera,
4108
+ pressed: cameraOn,
4109
+ onClick: onToggleCamera,
4110
+ pickerLabel: labels.cameraDevice,
4111
+ options: devices.video,
4112
+ selected: effective.video,
4113
+ onSelect: (id) => void onDeviceChange("video", id),
4114
+ children: cameraOn ? /* @__PURE__ */ jsx(Video, {}) : /* @__PURE__ */ jsx(VideoOff, {})
4197
4115
  }
4198
- ),
4199
- /* @__PURE__ */ jsxs(DropdownMenu, { children: [
4200
- /* @__PURE__ */ jsx(
4201
- DropdownMenuTrigger,
4202
- {
4203
- render: /* @__PURE__ */ jsx(
4204
- Button,
4205
- {
4206
- type: "button",
4207
- size: "icon-lg",
4208
- variant: "secondary",
4209
- "aria-label": labels.more
4210
- }
4211
- ),
4212
- children: /* @__PURE__ */ jsx(MoreVertical, {})
4116
+ )
4117
+ ] }) : null,
4118
+ showStageModerator ? /* @__PURE__ */ jsx(
4119
+ ControlIcon,
4120
+ {
4121
+ label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : labels.goOnAir,
4122
+ pressed: selfStageStatus !== "ON_STAGE",
4123
+ disabled: selfStageStatus === "ON_STAGE" && !canLeaveStage,
4124
+ onClick: () => {
4125
+ if (selfStageStatus === "ON_STAGE") {
4126
+ if (!canLeaveStage) {
4127
+ toast.message(labels.offAirLastVisible);
4128
+ return;
4129
+ }
4130
+ onLeaveStage();
4131
+ return;
4213
4132
  }
4214
- ),
4215
- /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "center", className: "min-w-44", children: [
4216
- fullscreenSupported ? /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: () => void enterFullscreen(), children: [
4217
- /* @__PURE__ */ jsx(Maximize, {}),
4218
- labels.fullscreen
4219
- ] }) : null,
4220
- pipSupported ? /* @__PURE__ */ jsxs(
4221
- DropdownMenuItem,
4222
- {
4223
- onClick: () => {
4224
- if (pipOpen) onClosePip();
4225
- else onOpenPip();
4226
- },
4227
- children: [
4228
- /* @__PURE__ */ jsx(PictureInPicture2, {}),
4229
- pipOpen ? labels.closePip : labels.openPip
4230
- ]
4133
+ onAskStage();
4134
+ },
4135
+ children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
4136
+ }
4137
+ ) : null,
4138
+ showStageGuest ? /* @__PURE__ */ jsx(
4139
+ ControlIcon,
4140
+ {
4141
+ label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
4142
+ pressed: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
4143
+ disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" || selfStageStatus === "ON_STAGE" && !canLeaveStage,
4144
+ onClick: () => {
4145
+ if (selfStageStatus === "ON_STAGE") {
4146
+ if (!canLeaveStage) {
4147
+ toast.message(labels.offAirLastVisible);
4148
+ return;
4231
4149
  }
4232
- ) : null,
4233
- /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: onOpenSettings, children: [
4234
- /* @__PURE__ */ jsx(Settings, {}),
4235
- labels.settings
4236
- ] })
4237
- ] })
4238
- ] })
4239
- ] }),
4240
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
4150
+ onLeaveStage();
4151
+ return;
4152
+ }
4153
+ onAskStage();
4154
+ },
4155
+ children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
4156
+ }
4157
+ ) : null,
4158
+ showScreenControl ? canShareFreely ? /* @__PURE__ */ jsx(
4159
+ ControlIcon,
4160
+ {
4161
+ label: labels.screen,
4162
+ pressed: screenOn,
4163
+ disabled: screenPending || !screenAvailable && !screenOn,
4164
+ pending: screenPending,
4165
+ onClick: onToggleScreen,
4166
+ children: /* @__PURE__ */ jsx(MonitorUp, {})
4167
+ }
4168
+ ) : shareGrant === "requested" ? /* @__PURE__ */ jsxs(Fragment, { children: [
4241
4169
  /* @__PURE__ */ jsx(
4242
4170
  ControlIcon,
4243
4171
  {
4244
- label: unreadChat > 0 ? `${labels.chat} (${unreadChat})` : labels.chat,
4245
- pressed: chatOpen,
4246
- badge: unreadChat,
4247
- onClick: onToggleChat,
4248
- children: /* @__PURE__ */ jsx(MessageSquare, {})
4172
+ label: labels.shareRequested,
4173
+ pressed: true,
4174
+ disabled: true,
4175
+ onClick: () => void 0,
4176
+ children: /* @__PURE__ */ jsx(MonitorUp, {})
4249
4177
  }
4250
4178
  ),
4251
- session.role === "moderator" ? /* @__PURE__ */ jsx(
4252
- ControlIcon,
4253
- {
4254
- label: peopleBadge > 0 ? `${labels.people} (${peopleBadge})` : labels.people,
4255
- pressed: peopleOpen,
4256
- badge: peopleBadge,
4257
- onClick: onTogglePeople,
4258
- children: /* @__PURE__ */ jsx(Users, {})
4259
- }
4260
- ) : null,
4261
4179
  /* @__PURE__ */ jsx(
4262
4180
  ControlIcon,
4263
4181
  {
4264
- label: labels.settings,
4265
- pressed: settingsOpen,
4266
- onClick: onOpenSettings,
4267
- children: /* @__PURE__ */ jsx(Settings, {})
4182
+ label: labels.cancelShareRequest,
4183
+ onClick: onCancelShareRequest,
4184
+ children: /* @__PURE__ */ jsx(X, {})
4268
4185
  }
4269
- ),
4186
+ )
4187
+ ] }) : /* @__PURE__ */ jsx(ControlIcon, { label: labels.askToShare, onClick: onAskToShare, children: /* @__PURE__ */ jsx(MonitorUp, {}) }) : null,
4188
+ /* @__PURE__ */ jsx(
4189
+ ControlIcon,
4190
+ {
4191
+ label: captionsLabel,
4192
+ pressed: captionsOn,
4193
+ disabled: !captionsAvailable,
4194
+ onClick: onToggleCaptions,
4195
+ children: /* @__PURE__ */ jsx(Captions, {})
4196
+ }
4197
+ ),
4198
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [
4270
4199
  /* @__PURE__ */ jsx(
4271
- StudioSettingsDialog,
4272
- {
4273
- open: settingsOpen,
4274
- onOpenChange: onCloseSettings,
4275
- meeting,
4276
- reducedMotion,
4277
- cameraOn,
4278
- labels,
4279
- captionPrefs,
4280
- onCaptionPrefsChange,
4281
- captionsAvailable,
4282
- effectBackgrounds
4283
- }
4284
- ),
4285
- session.role === "guest" ? /* @__PURE__ */ jsx(
4286
- ConfirmAction,
4200
+ DropdownMenuTrigger,
4287
4201
  {
4288
- triggerLabel: labels.leave,
4289
- title: labels.leaveTitle,
4290
- description: labels.leaveDescription,
4291
- confirmLabel: labels.leaveConfirm,
4292
- pending,
4293
- onConfirm: onLeave,
4294
- trigger: /* @__PURE__ */ jsx(
4202
+ render: /* @__PURE__ */ jsx(
4295
4203
  Button,
4296
4204
  {
4297
4205
  type: "button",
4298
- variant: "destructive",
4299
4206
  size: "icon-lg",
4300
- disabled: pending,
4301
- "aria-label": labels.leave,
4302
- children: /* @__PURE__ */ jsx(PhoneOff, {})
4207
+ variant: "secondary",
4208
+ "aria-label": labels.more
4303
4209
  }
4304
- )
4210
+ ),
4211
+ children: /* @__PURE__ */ jsx(MoreVertical, {})
4305
4212
  }
4306
- ) : null
4307
- ] })
4308
- ] }) });
4213
+ ),
4214
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "center", className: "min-w-44", children: [
4215
+ fullscreenSupported ? /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: () => void enterFullscreen(), children: [
4216
+ /* @__PURE__ */ jsx(Maximize, {}),
4217
+ labels.fullscreen
4218
+ ] }) : null,
4219
+ pipSupported ? /* @__PURE__ */ jsxs(
4220
+ DropdownMenuItem,
4221
+ {
4222
+ onClick: () => {
4223
+ if (pipOpen) onClosePip();
4224
+ else onOpenPip();
4225
+ },
4226
+ children: [
4227
+ /* @__PURE__ */ jsx(PictureInPicture2, {}),
4228
+ pipOpen ? labels.closePip : labels.openPip
4229
+ ]
4230
+ }
4231
+ ) : null,
4232
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: onOpenSettings, children: [
4233
+ /* @__PURE__ */ jsx(Settings, {}),
4234
+ labels.settings
4235
+ ] })
4236
+ ] })
4237
+ ] }),
4238
+ /* @__PURE__ */ jsx(
4239
+ ControlIcon,
4240
+ {
4241
+ label: unreadChat > 0 ? `${labels.chat} (${unreadChat})` : labels.chat,
4242
+ pressed: chatOpen,
4243
+ badge: unreadChat,
4244
+ onClick: onToggleChat,
4245
+ children: /* @__PURE__ */ jsx(MessageSquare, {})
4246
+ }
4247
+ ),
4248
+ session.role === "moderator" ? /* @__PURE__ */ jsx(
4249
+ ControlIcon,
4250
+ {
4251
+ label: peopleBadge > 0 ? `${labels.people} (${peopleBadge})` : labels.people,
4252
+ pressed: peopleOpen,
4253
+ badge: peopleBadge,
4254
+ onClick: onTogglePeople,
4255
+ children: /* @__PURE__ */ jsx(Users, {})
4256
+ }
4257
+ ) : null,
4258
+ /* @__PURE__ */ jsx(
4259
+ ControlIcon,
4260
+ {
4261
+ label: labels.settings,
4262
+ pressed: settingsOpen,
4263
+ onClick: onOpenSettings,
4264
+ children: /* @__PURE__ */ jsx(Settings, {})
4265
+ }
4266
+ ),
4267
+ /* @__PURE__ */ jsx(
4268
+ StudioSettingsDialog,
4269
+ {
4270
+ open: settingsOpen,
4271
+ onOpenChange: onCloseSettings,
4272
+ meeting,
4273
+ reducedMotion,
4274
+ cameraOn,
4275
+ labels,
4276
+ captionPrefs,
4277
+ onCaptionPrefsChange,
4278
+ captionsAvailable,
4279
+ effectBackgrounds
4280
+ }
4281
+ ),
4282
+ session.role === "guest" ? /* @__PURE__ */ jsx(
4283
+ ConfirmAction,
4284
+ {
4285
+ triggerLabel: labels.leave,
4286
+ title: labels.leaveTitle,
4287
+ description: labels.leaveDescription,
4288
+ confirmLabel: labels.leaveConfirm,
4289
+ pending,
4290
+ onConfirm: onLeave,
4291
+ trigger: /* @__PURE__ */ jsx(
4292
+ Button,
4293
+ {
4294
+ type: "button",
4295
+ variant: "destructive",
4296
+ size: "icon-lg",
4297
+ disabled: pending,
4298
+ "aria-label": labels.leave,
4299
+ children: /* @__PURE__ */ jsx(PhoneOff, {})
4300
+ }
4301
+ )
4302
+ }
4303
+ ) : null
4304
+ ] }) }) });
4309
4305
  }
4310
4306
  function MediaSplit({
4311
4307
  label,
@@ -4318,8 +4314,8 @@ function MediaSplit({
4318
4314
  onSelect,
4319
4315
  children
4320
4316
  }) {
4321
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
4322
- /* @__PURE__ */ jsx(
4317
+ if (options.length === 0) {
4318
+ return /* @__PURE__ */ jsx(
4323
4319
  ControlIcon,
4324
4320
  {
4325
4321
  label,
@@ -4328,8 +4324,11 @@ function MediaSplit({
4328
4324
  onClick,
4329
4325
  children
4330
4326
  }
4331
- ),
4332
- options.length > 0 ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
4327
+ );
4328
+ }
4329
+ const iconVariant = tone === "danger" ? "destructive" : pressed ? "default" : "secondary";
4330
+ return /* @__PURE__ */ jsxs("div", { className: "bg-secondary inline-flex h-10 items-center rounded-full p-0.5", children: [
4331
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [
4333
4332
  /* @__PURE__ */ jsx(
4334
4333
  DropdownMenuTrigger,
4335
4334
  {
@@ -4338,8 +4337,8 @@ function MediaSplit({
4338
4337
  {
4339
4338
  type: "button",
4340
4339
  size: "icon-xs",
4341
- variant: "secondary",
4342
- className: "-ml-1",
4340
+ variant: "ghost",
4341
+ className: "text-muted-foreground hover:text-foreground h-9 w-8 rounded-full hover:bg-transparent",
4343
4342
  "aria-label": pickerLabel
4344
4343
  }
4345
4344
  ),
@@ -4357,7 +4356,28 @@ function MediaSplit({
4357
4356
  },
4358
4357
  option.deviceId
4359
4358
  )) })
4360
- ] }) : null
4359
+ ] }),
4360
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
4361
+ /* @__PURE__ */ jsx(
4362
+ TooltipTrigger,
4363
+ {
4364
+ render: /* @__PURE__ */ jsx(
4365
+ Button,
4366
+ {
4367
+ type: "button",
4368
+ size: "icon-lg",
4369
+ variant: iconVariant,
4370
+ className: iconVariant === "secondary" ? "rounded-full bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_10%)] hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_16%)]" : "rounded-full",
4371
+ "aria-label": label,
4372
+ "aria-pressed": pressed,
4373
+ onClick
4374
+ }
4375
+ ),
4376
+ children
4377
+ }
4378
+ ),
4379
+ /* @__PURE__ */ jsx(TooltipContent, { children: label })
4380
+ ] })
4361
4381
  ] });
4362
4382
  }
4363
4383
  var POLL_MS = 2e3;
@@ -5003,10 +5023,11 @@ function StudioRoom({
5003
5023
  ]);
5004
5024
  const verifyGrantTookEffect = useCallback(
5005
5025
  (userId, retried = false) => {
5026
+ if (!userId) return;
5006
5027
  const id = window.setTimeout(() => {
5007
- const target = meeting.participants().find((row) => row.userId === userId);
5008
- if (!target || isOnStage(target.stageStatus)) return;
5009
- 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) {
5010
5031
  void meeting.setStageAccess(userId, true).then(() => {
5011
5032
  verifyGrantTookEffect(userId, true);
5012
5033
  }).catch(() => {
@@ -5020,6 +5041,12 @@ function StudioRoom({
5020
5041
  },
5021
5042
  [labels.stageGrantNoEffect, meeting]
5022
5043
  );
5044
+ useEffect(
5045
+ () => meeting.subscribeStageGrant((userId) => {
5046
+ verifyGrantTookEffect(userId);
5047
+ }),
5048
+ [meeting, verifyGrantTookEffect]
5049
+ );
5023
5050
  const onSetStage = useCallback(
5024
5051
  async (tile, on) => {
5025
5052
  const userId = tile.userId;
@@ -5344,20 +5371,13 @@ function StudioRoom({
5344
5371
  peopleBadge,
5345
5372
  labels,
5346
5373
  onAdmitAs: (id, as) => {
5347
- const guest = waitlisted.find((row) => row.id === id);
5348
- void Promise.resolve(meeting.admitAs(id, as)).then(() => {
5349
- if (as === "panelist" && guest?.userId) {
5350
- verifyGrantTookEffect(guest.userId);
5351
- }
5374
+ void Promise.resolve(meeting.admitAs(id, as)).catch(() => {
5375
+ toast.error(labels.stageError);
5352
5376
  });
5353
5377
  },
5354
5378
  onAdmitAll: (as) => {
5355
- const waiting = waitlisted;
5356
- void Promise.resolve(meeting.admitAll(as)).then(() => {
5357
- if (as !== "panelist") return;
5358
- for (const guest of waiting) {
5359
- if (guest.userId) verifyGrantTookEffect(guest.userId);
5360
- }
5379
+ void Promise.resolve(meeting.admitAll(as)).catch(() => {
5380
+ toast.error(labels.stageError);
5361
5381
  });
5362
5382
  },
5363
5383
  onDeny: (id) => {
@@ -6060,6 +6080,7 @@ function activeSpeakerFrom(payload, fallback) {
6060
6080
  }
6061
6081
  var STAGE_JOIN_RETRY_MS = 1e3;
6062
6082
  var STAGE_JOIN_MAX_ATTEMPTS = 3;
6083
+ var STAGE_GRANT_JOIN_MAX_FAILURES = 8;
6063
6084
  function isStageDisabledError(error) {
6064
6085
  if (!error || typeof error !== "object") return false;
6065
6086
  const row = error;
@@ -6132,12 +6153,15 @@ function studioMeetingFromRtk(raw) {
6132
6153
  const chatListeners = /* @__PURE__ */ new Set();
6133
6154
  const participantListeners = /* @__PURE__ */ new Set();
6134
6155
  const stageListeners = /* @__PURE__ */ new Set();
6156
+ const stageGrantListeners = /* @__PURE__ */ new Set();
6135
6157
  const pinListeners = /* @__PURE__ */ new Set();
6136
6158
  const pendingPanelists = /* @__PURE__ */ new Set();
6137
6159
  const chatSend = /* @__PURE__ */ new Map();
6138
6160
  const nameOverrides = /* @__PURE__ */ new Map();
6139
6161
  let joinStageInFlight = null;
6140
6162
  let joinedStageLocally = false;
6163
+ let stageGrantPending = false;
6164
+ let stageGrantJoinFailures = 0;
6141
6165
  let lastMeetingError = null;
6142
6166
  const historyMessages = [];
6143
6167
  let speakerSinkId = null;
@@ -6236,6 +6260,21 @@ function studioMeetingFromRtk(raw) {
6236
6260
  function currentStageStatus() {
6237
6261
  return meeting.self?.stageStatus ?? meeting.stage?.status;
6238
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
+ }
6239
6278
  function resolveConnectionState() {
6240
6279
  const self = meeting.self;
6241
6280
  const roomState = self?.roomState ?? null;
@@ -6327,22 +6366,21 @@ function studioMeetingFromRtk(raw) {
6327
6366
  joinStageInFlight = null;
6328
6367
  }
6329
6368
  }
6330
- async function autoJoinStage() {
6369
+ async function ensureStageJoined() {
6331
6370
  if (currentStageStatus() === "ON_STAGE") {
6332
- notifyStage();
6371
+ clearStageGrantPending();
6333
6372
  return;
6334
6373
  }
6374
+ if (!stageGrantPending && !isStageAccepted()) return;
6375
+ stageGrantPending = true;
6376
+ if (stageGrantJoinFailures >= STAGE_GRANT_JOIN_MAX_FAILURES) return;
6335
6377
  try {
6336
6378
  await joinStageWithRetry();
6379
+ if (currentStageStatus() === "ON_STAGE") {
6380
+ clearStageGrantPending();
6381
+ }
6337
6382
  } catch {
6338
- notifyStage();
6339
- }
6340
- }
6341
- async function ensureStageJoined() {
6342
- if (currentStageStatus() !== "ACCEPTED_TO_JOIN_STAGE") return;
6343
- try {
6344
- await joinStageWithRetry();
6345
- } catch {
6383
+ stageGrantJoinFailures += 1;
6346
6384
  notifyStage();
6347
6385
  }
6348
6386
  }
@@ -6373,6 +6411,7 @@ function studioMeetingFromRtk(raw) {
6373
6411
  const grantId = takePendingGrantId(joined2);
6374
6412
  if (!grantId) return;
6375
6413
  await meeting.stage?.grantAccess?.([grantId]);
6414
+ notifyStageGrant(joined2?.userId || grantId);
6376
6415
  notifyStage();
6377
6416
  }
6378
6417
  async function grantPendingIfJoined(participantId, userId) {
@@ -6381,6 +6420,7 @@ function studioMeetingFromRtk(raw) {
6381
6420
  if (!grantId) return;
6382
6421
  pendingPanelists.delete(participantId);
6383
6422
  await meeting.stage?.grantAccess?.([grantId]);
6423
+ notifyStageGrant(already?.userId || grantId);
6384
6424
  notifyStage();
6385
6425
  }
6386
6426
  listen(meeting.self, "videoUpdate", rebindMedia);
@@ -6430,15 +6470,15 @@ function studioMeetingFromRtk(raw) {
6430
6470
  listen(meeting.stage, "stageAccessRequestUpdate", notifyStage);
6431
6471
  listen(meeting.stage, "newStageRequest", notifyStage);
6432
6472
  listen(meeting.stage, "stageRequestApproved", () => {
6433
- void autoJoinStage();
6473
+ markStageGranted();
6474
+ });
6475
+ listen(meeting.stage, "acceptPresentRequests", () => {
6476
+ markStageGranted();
6434
6477
  });
6435
6478
  listen(meeting.stage, "stageRequestRejected", notifyStage);
6436
- listen(meeting.stage, "stageStatusUpdate", (status) => {
6437
- if (status === "ACCEPTED_TO_JOIN_STAGE") {
6438
- void autoJoinStage();
6439
- return;
6440
- }
6441
- if (status === "ON_STAGE") {
6479
+ listen(meeting.stage, "stageStatusUpdate", () => {
6480
+ if (currentStageStatus() === "ON_STAGE") {
6481
+ clearStageGrantPending();
6442
6482
  if (joinedStageLocally) {
6443
6483
  joinedStageLocally = false;
6444
6484
  notifyStage();
@@ -6449,12 +6489,17 @@ function studioMeetingFromRtk(raw) {
6449
6489
  });
6450
6490
  return;
6451
6491
  }
6492
+ if (isStageAccepted()) {
6493
+ markStageGranted();
6494
+ return;
6495
+ }
6452
6496
  notifyStage();
6453
6497
  });
6454
6498
  return {
6455
6499
  async join() {
6456
6500
  await meeting.join?.();
6457
6501
  await joinStageIfAllowed();
6502
+ await ensureStageJoined();
6458
6503
  await applyCurrentVideoEffects();
6459
6504
  },
6460
6505
  async leave() {
@@ -6550,7 +6595,12 @@ function studioMeetingFromRtk(raw) {
6550
6595
  return;
6551
6596
  }
6552
6597
  if (on) await meeting.stage?.grantAccess?.([userId]);
6553
- else await meeting.stage?.kick?.([userId]);
6598
+ else {
6599
+ await meeting.stage?.kick?.([userId]);
6600
+ if (meeting.self?.userId === userId) {
6601
+ clearStageGrantPending();
6602
+ }
6603
+ }
6554
6604
  notifyStage();
6555
6605
  },
6556
6606
  async denyStageAccess(userId) {
@@ -6581,6 +6631,7 @@ function studioMeetingFromRtk(raw) {
6581
6631
  async leaveStage() {
6582
6632
  await meeting.stage?.leave?.();
6583
6633
  joinedStageLocally = false;
6634
+ clearStageGrantPending();
6584
6635
  notifyStage();
6585
6636
  },
6586
6637
  stageStatus() {
@@ -6612,6 +6663,12 @@ function studioMeetingFromRtk(raw) {
6612
6663
  stageListeners.delete(cb);
6613
6664
  };
6614
6665
  },
6666
+ subscribeStageGrant(cb) {
6667
+ stageGrantListeners.add(cb);
6668
+ return () => {
6669
+ stageGrantListeners.delete(cb);
6670
+ };
6671
+ },
6615
6672
  canManageStage() {
6616
6673
  return meeting.self?.permissions?.acceptStageRequests === true;
6617
6674
  },