@livo-tv/sdk 1.2.2 → 1.2.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.
@@ -1899,6 +1899,7 @@ function StudioGuestWaiting({
1899
1899
  muted,
1900
1900
  labels,
1901
1901
  settingsOpen,
1902
+ statusLabel,
1902
1903
  onToggleCamera,
1903
1904
  onToggleMute,
1904
1905
  onOpenSettings,
@@ -1930,7 +1931,7 @@ function StudioGuestWaiting({
1930
1931
  reducedMotion
1931
1932
  }
1932
1933
  ),
1933
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-center text-sm", role: "status", children: labels.waitingHost }),
1934
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-center text-sm", role: "status", children: statusLabel ?? labels.waitingHost }),
1934
1935
  /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-center gap-2", children: [
1935
1936
  /* @__PURE__ */ jsxRuntime.jsx(
1936
1937
  ControlIcon,
@@ -2299,13 +2300,13 @@ function BubbleGroup({ className, ...props }) {
2299
2300
  "div",
2300
2301
  {
2301
2302
  "data-slot": "bubble-group",
2302
- className: cn("flex min-w-0 flex-col gap-2", className),
2303
+ className: cn("flex min-w-min flex-col gap-2", className),
2303
2304
  ...props
2304
2305
  }
2305
2306
  );
2306
2307
  }
2307
2308
  var bubbleVariants = classVarianceAuthority.cva(
2308
- "group/bubble relative flex w-fit min-w-0 max-w-[80%] flex-col gap-1 group-data-[align=end]/message:self-end data-[align=end]:self-end data-[variant=ghost]:max-w-full",
2309
+ "group/bubble relative flex w-fit min-w-min max-w-full flex-col gap-1 group-data-[align=end]/message:self-end data-[align=end]:self-end data-[variant=ghost]:max-w-full",
2309
2310
  {
2310
2311
  variants: {
2311
2312
  variant: {
@@ -2350,7 +2351,7 @@ function BubbleContent({
2350
2351
  props: mergeProps.mergeProps(
2351
2352
  {
2352
2353
  className: cn(
2353
- "w-auto max-w-full rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed [overflow-wrap:break-word] group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50",
2354
+ "w-auto max-w-full whitespace-pre-wrap rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed [overflow-wrap:break-word] group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50",
2354
2355
  className
2355
2356
  )
2356
2357
  },
@@ -2534,7 +2535,7 @@ function StudioChatPanel({
2534
2535
  const bubbles = group.rows.map((row) => /* @__PURE__ */ jsxRuntime.jsxs(
2535
2536
  "div",
2536
2537
  {
2537
- className: group.mine ? "ml-auto flex w-fit max-w-full flex-col gap-1" : "flex w-fit max-w-full flex-col gap-1",
2538
+ className: group.mine ? "ml-auto flex max-w-[80%] flex-col gap-1 self-end" : "flex max-w-[80%] flex-col gap-1 self-start",
2538
2539
  children: [
2539
2540
  /* @__PURE__ */ jsxRuntime.jsx(
2540
2541
  Bubble,
@@ -3514,6 +3515,7 @@ function StudioToolbar({
3514
3515
  ] }) });
3515
3516
  }
3516
3517
  var STAGE_GRANT_VERIFY_MS = 4e3;
3518
+ var STUDIO_CONNECT_WATCHDOG_MS = 12e3;
3517
3519
  function StudioRoom({
3518
3520
  session,
3519
3521
  meeting,
@@ -3549,6 +3551,7 @@ function StudioRoom({
3549
3551
  );
3550
3552
  const [egressError, setEgressError] = react.useState(null);
3551
3553
  const [shareGrant, setShareGrant] = react.useState("idle");
3554
+ const [connectTimedOut, setConnectTimedOut] = react.useState(false);
3552
3555
  const [screenPermission, setScreenPermission] = react.useState(
3553
3556
  () => meeting.screenSharePermission()
3554
3557
  );
@@ -3795,6 +3798,14 @@ function StudioRoom({
3795
3798
  [participants]
3796
3799
  );
3797
3800
  const waitlisted = react.useMemo(() => meeting.waitlisted(), [meeting, tick]);
3801
+ const connectionState = react.useMemo(
3802
+ () => meeting.connectionState(),
3803
+ [meeting, tick]
3804
+ );
3805
+ const lastMeetingError = react.useMemo(
3806
+ () => meeting.lastMeetingError(),
3807
+ [meeting, tick]
3808
+ );
3798
3809
  const canManageStage = meeting.canManageStage();
3799
3810
  const stageEnabled = meeting.stageEnabled();
3800
3811
  const stageRequests = react.useMemo(
@@ -3892,6 +3903,16 @@ function StudioRoom({
3892
3903
  }
3893
3904
  }
3894
3905
  }, [labels.knock, session.role, waitlisted]);
3906
+ react.useEffect(() => {
3907
+ if (session.role !== "guest" || connectionState !== "connecting") {
3908
+ setConnectTimedOut(false);
3909
+ return;
3910
+ }
3911
+ const id = window.setTimeout(() => {
3912
+ setConnectTimedOut(true);
3913
+ }, STUDIO_CONNECT_WATCHDOG_MS);
3914
+ return () => window.clearTimeout(id);
3915
+ }, [connectionState, session.role]);
3895
3916
  react.useEffect(() => {
3896
3917
  if (inWaitingRoom || screenNoticePrimed.current) return;
3897
3918
  if (!durableScreenBlock) return;
@@ -4207,7 +4228,20 @@ function StudioRoom({
4207
4228
  }
4208
4229
  ) });
4209
4230
  }
4210
- if (inWaitingRoom && session.role === "guest") {
4231
+ const guestConnectFailed = session.role === "guest" && (connectionState === "failed" || connectionState === "connecting" && connectTimedOut);
4232
+ if (guestConnectFailed) {
4233
+ const detail = [meeting.roomState(), lastMeetingError].filter((value) => Boolean(value)).join(" \u2014 ");
4234
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-dvh flex-col items-center justify-center p-6", children: /* @__PURE__ */ jsxRuntime.jsx(
4235
+ EmptyState,
4236
+ {
4237
+ title: labels.connectFailed,
4238
+ description: detail || labels.connectFailed,
4239
+ actionLabel: onRejoin ? labels.retry : void 0,
4240
+ onAction: onRejoin
4241
+ }
4242
+ ) });
4243
+ }
4244
+ if (session.role === "guest" && (connectionState === "waitlisted" || connectionState === "connecting")) {
4211
4245
  return /* @__PURE__ */ jsxRuntime.jsx(
4212
4246
  StudioGuestWaiting,
4213
4247
  {
@@ -4217,6 +4251,7 @@ function StudioRoom({
4217
4251
  muted,
4218
4252
  labels,
4219
4253
  settingsOpen,
4254
+ statusLabel: connectionState === "connecting" ? labels.connecting : labels.waitingHost,
4220
4255
  onToggleCamera,
4221
4256
  onToggleMute: () => void onToggleMute(),
4222
4257
  onOpenSettings: () => setSettingsOpen(true),
@@ -4742,8 +4777,8 @@ function StudioGuestLobby({
4742
4777
  }
4743
4778
  }
4744
4779
  async function onTestSpeaker() {
4745
- const speakerId = effective.speaker || devices.speaker[0]?.deviceId || "";
4746
- if (!speakerId || testingSpeaker) return;
4780
+ if (testingSpeaker) return;
4781
+ const speakerId = effective.speaker || devices.speaker[0]?.deviceId || void 0;
4747
4782
  setTestingSpeaker(true);
4748
4783
  try {
4749
4784
  await playSpeakerTest(speakerId);
@@ -4811,7 +4846,7 @@ function StudioGuestLobby({
4811
4846
  type: "button",
4812
4847
  size: "sm",
4813
4848
  variant: "outline",
4814
- disabled: !(effective.speaker || devices.speaker[0]?.deviceId) || testingSpeaker,
4849
+ disabled: testingSpeaker,
4815
4850
  onClick: () => void onTestSpeaker(),
4816
4851
  children: deviceLabels.testSpeaker
4817
4852
  }
@@ -4893,7 +4928,11 @@ function isRecorder(participant) {
4893
4928
  function resolvedName(participant, nameOverrides) {
4894
4929
  return nameOverrides.get(participant.id) ?? (participant.userId ? nameOverrides.get(participant.userId) : void 0) ?? participant.name?.trim() ?? "Guest";
4895
4930
  }
4896
- function tileFrom(participant, selfId, pinnedIds, chatSend, nameOverrides) {
4931
+ function resolveTileStageStatus(participant, stageEnabled, fallback) {
4932
+ if (!stageEnabled) return "ON_STAGE";
4933
+ return participant.stageStatus ?? fallback ?? "OFF_STAGE";
4934
+ }
4935
+ function tileFrom(participant, selfId, pinnedIds, chatSend, nameOverrides, stageStatus) {
4897
4936
  return {
4898
4937
  id: participant.id,
4899
4938
  userId: participant.userId ?? "",
@@ -4903,7 +4942,7 @@ function tileFrom(participant, selfId, pinnedIds, chatSend, nameOverrides) {
4903
4942
  videoEnabled: participant.videoEnabled === true,
4904
4943
  screenShareEnabled: participant.screenShareEnabled === true,
4905
4944
  picture: participant.picture ?? null,
4906
- stageStatus: participant.stageStatus ?? "ON_STAGE",
4945
+ stageStatus,
4907
4946
  pinned: participant.isPinned === true || pinnedIds.has(participant.id),
4908
4947
  chatCanSend: chatSend.get(participant.id) ?? true
4909
4948
  };
@@ -5049,6 +5088,38 @@ function isStageDisabledError(error) {
5049
5088
  const message = typeof row.message === "string" ? row.message : "";
5050
5089
  return message.includes("ERR2003") || message.toLowerCase().includes("stage is disabled");
5051
5090
  }
5091
+ function isStageBusyError(error) {
5092
+ if (!error || typeof error !== "object") return false;
5093
+ const row = error;
5094
+ if (row.name === "UnsupportedConcurrentMethodExecution") return true;
5095
+ if (row.code === "2006" || row.code === 2006) return true;
5096
+ const message = typeof row.message === "string" ? row.message : "";
5097
+ return message.includes("ERR2006") || message.includes("Unsupported concurrent calls") || message.includes("Action not permitted in current stage status");
5098
+ }
5099
+ function isRtkMeetingError(error) {
5100
+ if (!error || typeof error !== "object") return false;
5101
+ const row = error;
5102
+ const name = typeof row.name === "string" ? row.name : "";
5103
+ const message = typeof row.message === "string" ? row.message : "";
5104
+ return name === "ClientError" || name === "TransportConnectionError" || message.includes("ERR1608") || message.includes("No speaker found") || message.includes("Socket is not connected");
5105
+ }
5106
+ function meetingErrorText(error) {
5107
+ if (error instanceof Error && error.message.trim()) return error.message;
5108
+ if (error && typeof error === "object") {
5109
+ const message = error.message;
5110
+ if (typeof message === "string" && message.trim()) return message;
5111
+ }
5112
+ return "Studio failed";
5113
+ }
5114
+ async function settledDeviceList(load) {
5115
+ if (!load) return [];
5116
+ try {
5117
+ const [result] = await Promise.allSettled([load()]);
5118
+ return result?.status === "fulfilled" ? result.value : [];
5119
+ } catch {
5120
+ return [];
5121
+ }
5122
+ }
5052
5123
  function waitMs(ms) {
5053
5124
  return new Promise((resolve) => {
5054
5125
  setTimeout(resolve, ms);
@@ -5086,8 +5157,9 @@ function studioMeetingFromRtk(raw) {
5086
5157
  const pendingPanelists = /* @__PURE__ */ new Set();
5087
5158
  const chatSend = /* @__PURE__ */ new Map();
5088
5159
  const nameOverrides = /* @__PURE__ */ new Map();
5089
- let joiningStage = false;
5160
+ let joinStageInFlight = null;
5090
5161
  let joinedStageLocally = false;
5162
+ let lastMeetingError = null;
5091
5163
  const historyMessages = [];
5092
5164
  let speakerSinkId = null;
5093
5165
  let lastSpeakerId = typeof meeting.participants?.lastActiveSpeaker === "string" ? meeting.participants.lastActiveSpeaker : null;
@@ -5183,47 +5255,75 @@ function studioMeetingFromRtk(raw) {
5183
5255
  return [...byId.values()].sort((a, b) => a.time - b.time);
5184
5256
  }
5185
5257
  function currentStageStatus() {
5186
- return meeting.stage?.status ?? meeting.self?.stageStatus;
5258
+ return meeting.self?.stageStatus ?? meeting.stage?.status;
5259
+ }
5260
+ function resolveConnectionState() {
5261
+ const self = meeting.self;
5262
+ const roomState = self?.roomState ?? null;
5263
+ if (roomState === "waitlisted") return "waitlisted";
5264
+ if (roomState === "joined" || self?.roomJoined === true) return "joined";
5265
+ if (lastMeetingError) return "failed";
5266
+ return "connecting";
5267
+ }
5268
+ function recordMeetingError(error) {
5269
+ if (!isRtkMeetingError(error)) return;
5270
+ lastMeetingError = meetingErrorText(error);
5271
+ notifyParticipants();
5272
+ }
5273
+ function onUnhandledRejection(event) {
5274
+ recordMeetingError(event.reason);
5275
+ }
5276
+ if (typeof window !== "undefined") {
5277
+ window.addEventListener("unhandledrejection", onUnhandledRejection);
5187
5278
  }
5188
5279
  async function republishCameraIfOn() {
5189
5280
  if (meeting.self?.videoEnabled !== true) return;
5190
5281
  await meeting.self.disableVideo?.();
5191
5282
  await meeting.self.enableVideo?.();
5192
5283
  }
5193
- async function joinStageWithRetry() {
5194
- if (joiningStage) return;
5195
- joiningStage = true;
5196
- try {
5197
- for (let attempt = 0; attempt < STAGE_JOIN_MAX_ATTEMPTS; attempt++) {
5198
- if (currentStageStatus() === "ON_STAGE") {
5284
+ async function runJoinStageAttempts() {
5285
+ let lastError;
5286
+ for (let attempt = 0; attempt < STAGE_JOIN_MAX_ATTEMPTS; attempt++) {
5287
+ if (currentStageStatus() === "ON_STAGE") {
5288
+ notifyStage();
5289
+ return;
5290
+ }
5291
+ try {
5292
+ await meeting.stage?.join?.();
5293
+ joinedStageLocally = true;
5294
+ await republishCameraIfOn();
5295
+ notifyStage();
5296
+ return;
5297
+ } catch (error) {
5298
+ lastError = error;
5299
+ if (isStageDisabledError(error)) {
5199
5300
  notifyStage();
5200
5301
  return;
5201
5302
  }
5202
- try {
5203
- await meeting.stage?.join?.();
5204
- joinedStageLocally = true;
5205
- await republishCameraIfOn();
5206
- notifyStage();
5207
- return;
5208
- } catch (error) {
5209
- if (isStageDisabledError(error)) {
5210
- notifyStage();
5211
- return;
5212
- }
5213
- console.warn("studio: stage join failed", error);
5214
- if (attempt === STAGE_JOIN_MAX_ATTEMPTS - 1) {
5215
- notifyStage();
5216
- return;
5217
- }
5303
+ if (isStageBusyError(error)) {
5218
5304
  await waitMs(STAGE_JOIN_RETRY_MS);
5219
- if (currentStageStatus() !== "ACCEPTED_TO_JOIN_STAGE") {
5220
- notifyStage();
5221
- return;
5222
- }
5305
+ continue;
5223
5306
  }
5307
+ console.warn("studio: stage join failed", error);
5308
+ if (attempt === STAGE_JOIN_MAX_ATTEMPTS - 1) break;
5309
+ await waitMs(STAGE_JOIN_RETRY_MS);
5224
5310
  }
5311
+ }
5312
+ notifyStage();
5313
+ if (currentStageStatus() !== "ON_STAGE") {
5314
+ throw lastError instanceof Error ? lastError : new Error("stage join failed");
5315
+ }
5316
+ }
5317
+ async function joinStageWithRetry() {
5318
+ if (joinStageInFlight) {
5319
+ await joinStageInFlight;
5320
+ return;
5321
+ }
5322
+ joinStageInFlight = runJoinStageAttempts();
5323
+ try {
5324
+ await joinStageInFlight;
5225
5325
  } finally {
5226
- joiningStage = false;
5326
+ joinStageInFlight = null;
5227
5327
  }
5228
5328
  }
5229
5329
  async function autoJoinStage() {
@@ -5231,18 +5331,27 @@ function studioMeetingFromRtk(raw) {
5231
5331
  notifyStage();
5232
5332
  return;
5233
5333
  }
5234
- await joinStageWithRetry();
5334
+ try {
5335
+ await joinStageWithRetry();
5336
+ } catch {
5337
+ notifyStage();
5338
+ }
5235
5339
  }
5236
5340
  async function ensureStageJoined() {
5237
5341
  if (currentStageStatus() !== "ACCEPTED_TO_JOIN_STAGE") return;
5238
- await joinStageWithRetry();
5342
+ try {
5343
+ await joinStageWithRetry();
5344
+ } catch {
5345
+ notifyStage();
5346
+ }
5239
5347
  }
5240
5348
  async function joinStageIfAllowed() {
5241
5349
  if (meeting.self?.permissions?.stageAccess !== "ALLOWED") return;
5242
5350
  if (currentStageStatus() === "ON_STAGE") return;
5243
5351
  try {
5244
- await meeting.stage?.join?.();
5352
+ await joinStageWithRetry();
5245
5353
  } catch {
5354
+ notifyStage();
5246
5355
  }
5247
5356
  }
5248
5357
  function queuePanelist(guest, fallbackId) {
@@ -5278,6 +5387,8 @@ function studioMeetingFromRtk(raw) {
5278
5387
  listen(meeting.self, "audioUpdate", rebindMedia);
5279
5388
  listen(meeting.self, "pinned", notifyPinned);
5280
5389
  listen(meeting.self, "unpinned", notifyPinned);
5390
+ listen(meeting.self, "waitlisted", notifyParticipants);
5391
+ listen(meeting.self, "roomJoined", notifyParticipants);
5281
5392
  const joined = meeting.participants?.joined;
5282
5393
  listen(joined, "participantJoined", (payload) => {
5283
5394
  void grantIfPending(payload);
@@ -5345,6 +5456,9 @@ function studioMeetingFromRtk(raw) {
5345
5456
  await joinStageIfAllowed();
5346
5457
  },
5347
5458
  async leave() {
5459
+ if (typeof window !== "undefined") {
5460
+ window.removeEventListener("unhandledrejection", onUnhandledRejection);
5461
+ }
5348
5462
  await meeting.leave?.();
5349
5463
  },
5350
5464
  async setAudioEnabled(enabled) {
@@ -5445,7 +5559,7 @@ function studioMeetingFromRtk(raw) {
5445
5559
  notifyStage();
5446
5560
  },
5447
5561
  async joinStage() {
5448
- if (currentStageStatus() === "ON_STAGE") {
5562
+ if (meeting.self?.stageStatus === "ON_STAGE") {
5449
5563
  notifyStage();
5450
5564
  return;
5451
5565
  }
@@ -5454,25 +5568,15 @@ function studioMeetingFromRtk(raw) {
5454
5568
  notifyStage();
5455
5569
  return;
5456
5570
  }
5457
- try {
5458
- await meeting.stage?.join?.();
5459
- } catch {
5460
- const userId = meeting.self?.userId;
5461
- if (userId) {
5462
- await meeting.stage?.grantAccess?.([userId]);
5463
- }
5464
- await meeting.stage?.join?.();
5465
- }
5466
- joinedStageLocally = true;
5467
- await republishCameraIfOn();
5468
- notifyStage();
5571
+ await joinStageWithRetry();
5469
5572
  },
5470
5573
  async leaveStage() {
5471
5574
  await meeting.stage?.leave?.();
5575
+ joinedStageLocally = false;
5472
5576
  notifyStage();
5473
5577
  },
5474
5578
  stageStatus() {
5475
- return meeting.stage?.status ?? meeting.self?.stageStatus ?? "ON_STAGE";
5579
+ return meeting.self?.stageStatus ?? meeting.stage?.status ?? "OFF_STAGE";
5476
5580
  },
5477
5581
  stageRequests() {
5478
5582
  if (meeting.self?.permissions?.acceptStageRequests !== true) {
@@ -5550,10 +5654,27 @@ function studioMeetingFromRtk(raw) {
5550
5654
  participants() {
5551
5655
  const self = meeting.self;
5552
5656
  const pinned = pinnedIdSet();
5553
- const joinedRows = asList(meeting.participants?.joined).filter((row) => row.id !== self?.id && !isRecorder(row)).map((row) => tileFrom(row, self?.id, pinned, chatSend, nameOverrides));
5657
+ const stageEnabled = self?.permissions?.stageEnabled !== false;
5658
+ const joinedRows = asList(meeting.participants?.joined).filter((row) => row.id !== self?.id && !isRecorder(row)).map(
5659
+ (row) => tileFrom(
5660
+ row,
5661
+ self?.id,
5662
+ pinned,
5663
+ chatSend,
5664
+ nameOverrides,
5665
+ resolveTileStageStatus(row, stageEnabled)
5666
+ )
5667
+ );
5554
5668
  if (self?.id && !isRecorder(self)) {
5555
5669
  joinedRows.unshift(
5556
- tileFrom(self, self.id, pinned, chatSend, nameOverrides)
5670
+ tileFrom(
5671
+ self,
5672
+ self.id,
5673
+ pinned,
5674
+ chatSend,
5675
+ nameOverrides,
5676
+ resolveTileStageStatus(self, stageEnabled, meeting.stage?.status)
5677
+ )
5557
5678
  );
5558
5679
  }
5559
5680
  return joinedRows;
@@ -5567,9 +5688,13 @@ function studioMeetingFromRtk(raw) {
5567
5688
  }));
5568
5689
  },
5569
5690
  inWaitingRoom() {
5570
- const self = meeting.self;
5571
- if (isStudioRejected(self?.roomState ?? null)) return false;
5572
- return self?.roomState === "waitlisted" || self?.roomJoined === false;
5691
+ return meeting.self?.roomState === "waitlisted";
5692
+ },
5693
+ connectionState() {
5694
+ return resolveConnectionState();
5695
+ },
5696
+ lastMeetingError() {
5697
+ return lastMeetingError;
5573
5698
  },
5574
5699
  moderatorIds() {
5575
5700
  const ids = [];
@@ -5689,9 +5814,9 @@ function studioMeetingFromRtk(raw) {
5689
5814
  async listDevices() {
5690
5815
  const self = meeting.self;
5691
5816
  const [audio, video, speaker] = await Promise.all([
5692
- self?.getAudioDevices?.() ?? [],
5693
- self?.getVideoDevices?.() ?? [],
5694
- self?.getSpeakerDevices?.() ?? []
5817
+ settledDeviceList(self?.getAudioDevices?.bind(self)),
5818
+ settledDeviceList(self?.getVideoDevices?.bind(self)),
5819
+ settledDeviceList(self?.getSpeakerDevices?.bind(self))
5695
5820
  ]);
5696
5821
  return {
5697
5822
  audio: asOptions(audio),
@@ -5704,6 +5829,10 @@ function studioMeetingFromRtk(raw) {
5704
5829
  if (kind === "speaker") {
5705
5830
  speakerSinkId = deviceId;
5706
5831
  for (const el of audioEls.values()) applySink(el, speakerSinkId);
5832
+ const speakers = await settledDeviceList(
5833
+ meeting.self?.getSpeakerDevices?.bind(meeting.self)
5834
+ );
5835
+ if (speakers.length === 0) return;
5707
5836
  }
5708
5837
  await meeting.self?.setDevice?.({
5709
5838
  deviceId,
@@ -6246,6 +6375,9 @@ function studioRoomLabels(t) {
6246
6375
  preview: t("preview"),
6247
6376
  waiting: t("waiting"),
6248
6377
  waitingHost: t("waitingHost"),
6378
+ connecting: t("connecting"),
6379
+ connectFailed: t("connectFailed"),
6380
+ retry: t("retry"),
6249
6381
  admit: t("admit"),
6250
6382
  deny: t("deny"),
6251
6383
  denied: t("denied"),