@livo-tv/sdk 1.16.0 → 1.18.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.
@@ -417,6 +417,10 @@ declare function studioRoomLabels(t: (key: string, values?: Record<string, strin
417
417
  shareRequestTimeout: string;
418
418
  shareRespondError: string;
419
419
  goOffAir: string;
420
+ moveToAudience: string;
421
+ moveToAudienceTitle: string;
422
+ moveToAudienceDescription: string;
423
+ moveToAudienceConfirm: string;
420
424
  goOnAir: string;
421
425
  takeOffAir: string;
422
426
  bringOnAir: string;
@@ -525,7 +529,7 @@ type StudioDeviceSelection = {
525
529
  speaker: string;
526
530
  };
527
531
 
528
- declare function StudioRoom({ session, meeting, control, onRefreshStatus, onRejoin, labels, reducedMotion, embed, chrome, headerStart, playerOrigin, windowGate, initialCameraOn, initialMuted, initialDevices, appDownloadUrl, captionsBaseUrl, apiUrl, cdnBase, endedHref, }: {
532
+ declare function StudioRoom({ session, meeting, control, onRefreshStatus, onRejoin, labels, reducedMotion, embed, chrome, headerStart, playerOrigin, windowGate, initialCameraOn, initialMuted, initialDevices, appDownloadUrl, captionsBaseUrl, apiUrl, cdnBase, endedHref, endedExtra, }: {
529
533
  session: StudioSession;
530
534
  meeting: StudioMeetingHandle;
531
535
  control: StudioControlActions;
@@ -550,6 +554,8 @@ declare function StudioRoom({ session, meeting, control, onRefreshStatus, onRejo
550
554
  cdnBase?: string;
551
555
  /** Partner URL for the ended-session action. Defaults to `/streams/:id`. */
552
556
  endedHref?: string;
557
+ /** Partner content under the return link on the session-over card. */
558
+ endedExtra?: ReactNode;
553
559
  }): react.JSX.Element;
554
560
 
555
561
  declare function StudioSkeleton(): react.JSX.Element;
@@ -559,6 +565,7 @@ type StudioGuestLobbyLabels = {
559
565
  hint: string;
560
566
  name: string;
561
567
  join: string;
568
+ joining: string;
562
569
  waitingForHost: string;
563
570
  camera: string;
564
571
  mic: string;
@@ -605,7 +612,7 @@ declare function StudioWindowGate({ lockKey, allowTakeover, labels, children, }:
605
612
  children: ReactNode;
606
613
  }): react.JSX.Element;
607
614
 
608
- declare function LivoHostStudio({ token, apiUrl, labels, redeemError, embed, chrome, headerStart, playerOrigin, theme, appDownloadUrl, guestUrl, endedHref, }: {
615
+ declare function LivoHostStudio({ token, apiUrl, labels, redeemError, embed, chrome, headerStart, playerOrigin, theme, appDownloadUrl, guestUrl, endedHref, endedExtra, }: {
609
616
  token: string;
610
617
  apiUrl?: string;
611
618
  labels: StudioRoomLabels;
@@ -621,6 +628,8 @@ declare function LivoHostStudio({ token, apiUrl, labels, redeemError, embed, chr
621
628
  guestUrl?: string | null;
622
629
  /** Partner URL for the ended-session action. Defaults to `/streams/:id`. */
623
630
  endedHref?: string;
631
+ /** Partner content under the return link on the session-over card. */
632
+ endedExtra?: ReactNode;
624
633
  }): react.JSX.Element;
625
634
 
626
635
  type StudioLobbyLabels = {
@@ -628,6 +637,7 @@ type StudioLobbyLabels = {
628
637
  hint: string;
629
638
  name: string;
630
639
  join: string;
640
+ joining: string;
631
641
  waitingForHost: string;
632
642
  camera: string;
633
643
  mic: string;
@@ -417,6 +417,10 @@ declare function studioRoomLabels(t: (key: string, values?: Record<string, strin
417
417
  shareRequestTimeout: string;
418
418
  shareRespondError: string;
419
419
  goOffAir: string;
420
+ moveToAudience: string;
421
+ moveToAudienceTitle: string;
422
+ moveToAudienceDescription: string;
423
+ moveToAudienceConfirm: string;
420
424
  goOnAir: string;
421
425
  takeOffAir: string;
422
426
  bringOnAir: string;
@@ -525,7 +529,7 @@ type StudioDeviceSelection = {
525
529
  speaker: string;
526
530
  };
527
531
 
528
- declare function StudioRoom({ session, meeting, control, onRefreshStatus, onRejoin, labels, reducedMotion, embed, chrome, headerStart, playerOrigin, windowGate, initialCameraOn, initialMuted, initialDevices, appDownloadUrl, captionsBaseUrl, apiUrl, cdnBase, endedHref, }: {
532
+ declare function StudioRoom({ session, meeting, control, onRefreshStatus, onRejoin, labels, reducedMotion, embed, chrome, headerStart, playerOrigin, windowGate, initialCameraOn, initialMuted, initialDevices, appDownloadUrl, captionsBaseUrl, apiUrl, cdnBase, endedHref, endedExtra, }: {
529
533
  session: StudioSession;
530
534
  meeting: StudioMeetingHandle;
531
535
  control: StudioControlActions;
@@ -550,6 +554,8 @@ declare function StudioRoom({ session, meeting, control, onRefreshStatus, onRejo
550
554
  cdnBase?: string;
551
555
  /** Partner URL for the ended-session action. Defaults to `/streams/:id`. */
552
556
  endedHref?: string;
557
+ /** Partner content under the return link on the session-over card. */
558
+ endedExtra?: ReactNode;
553
559
  }): react.JSX.Element;
554
560
 
555
561
  declare function StudioSkeleton(): react.JSX.Element;
@@ -559,6 +565,7 @@ type StudioGuestLobbyLabels = {
559
565
  hint: string;
560
566
  name: string;
561
567
  join: string;
568
+ joining: string;
562
569
  waitingForHost: string;
563
570
  camera: string;
564
571
  mic: string;
@@ -605,7 +612,7 @@ declare function StudioWindowGate({ lockKey, allowTakeover, labels, children, }:
605
612
  children: ReactNode;
606
613
  }): react.JSX.Element;
607
614
 
608
- declare function LivoHostStudio({ token, apiUrl, labels, redeemError, embed, chrome, headerStart, playerOrigin, theme, appDownloadUrl, guestUrl, endedHref, }: {
615
+ declare function LivoHostStudio({ token, apiUrl, labels, redeemError, embed, chrome, headerStart, playerOrigin, theme, appDownloadUrl, guestUrl, endedHref, endedExtra, }: {
609
616
  token: string;
610
617
  apiUrl?: string;
611
618
  labels: StudioRoomLabels;
@@ -621,6 +628,8 @@ declare function LivoHostStudio({ token, apiUrl, labels, redeemError, embed, chr
621
628
  guestUrl?: string | null;
622
629
  /** Partner URL for the ended-session action. Defaults to `/streams/:id`. */
623
630
  endedHref?: string;
631
+ /** Partner content under the return link on the session-over card. */
632
+ endedExtra?: ReactNode;
624
633
  }): react.JSX.Element;
625
634
 
626
635
  type StudioLobbyLabels = {
@@ -628,6 +637,7 @@ type StudioLobbyLabels = {
628
637
  hint: string;
629
638
  name: string;
630
639
  join: string;
640
+ joining: string;
631
641
  waitingForHost: string;
632
642
  camera: string;
633
643
  mic: string;
@@ -11574,6 +11574,7 @@ function EmptyState({
11574
11574
  actionLabel,
11575
11575
  onAction,
11576
11576
  actionHref,
11577
+ extra,
11577
11578
  role = "status"
11578
11579
  }) {
11579
11580
  const action = actionLabel && actionHref ? /* @__PURE__ */ jsx(Button3, { render: /* @__PURE__ */ jsx("a", { href: actionHref }), children: actionLabel }) : actionLabel ? /* @__PURE__ */ jsx(Button3, { type: "button", onClick: onAction, children: actionLabel }) : null;
@@ -11585,7 +11586,8 @@ function EmptyState({
11585
11586
  children: [
11586
11587
  /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: title }),
11587
11588
  /* @__PURE__ */ jsx("p", { className: "text-muted-foreground max-w-md text-sm", children: description }),
11588
- action
11589
+ action,
11590
+ extra
11589
11591
  ]
11590
11592
  }
11591
11593
  );
@@ -11656,21 +11658,18 @@ function pipSpotlightFrom(tile, kind) {
11656
11658
  }
11657
11659
  function resolveStudioPipSpotlight(tiles, activeSpeakerId) {
11658
11660
  const visible = tiles.filter((tile) => isOnStage(tile.stageStatus));
11661
+ const sharing = visible.find((tile) => tile.screenShareEnabled);
11662
+ if (sharing) {
11663
+ return pipSpotlightFrom(sharing, "screen");
11664
+ }
11659
11665
  const pinned = visible.find((tile) => tile.pinned === true);
11660
11666
  if (pinned) {
11661
- return pipSpotlightFrom(
11662
- pinned,
11663
- pinned.screenShareEnabled ? "screen" : pinned.videoEnabled ? "camera" : "idle"
11664
- );
11667
+ return pipSpotlightFrom(pinned, pinned.videoEnabled ? "camera" : "idle");
11665
11668
  }
11666
11669
  const speaker = activeSpeakerId ? visible.find((tile) => tile.id === activeSpeakerId) : void 0;
11667
11670
  if (speaker) {
11668
11671
  return pipSpotlightFrom(speaker, speaker.videoEnabled ? "camera" : "idle");
11669
11672
  }
11670
- const sharing = visible.find((tile) => tile.screenShareEnabled);
11671
- if (sharing) {
11672
- return pipSpotlightFrom(sharing, "screen");
11673
- }
11674
11673
  const remoteCam = visible.find((tile) => !tile.isSelf && tile.videoEnabled);
11675
11674
  if (remoteCam) {
11676
11675
  return pipSpotlightFrom(remoteCam, "camera");
@@ -22835,6 +22834,7 @@ function StudioDeviceSelectGroup({
22835
22834
  devices,
22836
22835
  selected,
22837
22836
  labels,
22837
+ disabled: disabled2 = false,
22838
22838
  onChange,
22839
22839
  speakerAction
22840
22840
  }) {
@@ -22846,6 +22846,7 @@ function StudioDeviceSelectGroup({
22846
22846
  label: labels.cameraDevice,
22847
22847
  options: devices.video,
22848
22848
  value: selected.video,
22849
+ disabled: disabled2,
22849
22850
  onChange: (id) => onChange("video", id)
22850
22851
  }
22851
22852
  ),
@@ -22856,6 +22857,7 @@ function StudioDeviceSelectGroup({
22856
22857
  label: labels.micDevice,
22857
22858
  options: devices.audio,
22858
22859
  value: selected.audio,
22860
+ disabled: disabled2,
22859
22861
  onChange: (id) => onChange("audio", id)
22860
22862
  }
22861
22863
  ),
@@ -22866,6 +22868,7 @@ function StudioDeviceSelectGroup({
22866
22868
  label: labels.speakerDevice,
22867
22869
  options: devices.speaker,
22868
22870
  value: selected.speaker,
22871
+ disabled: disabled2,
22869
22872
  onChange: (id) => onChange("speaker", id),
22870
22873
  action: speakerAction
22871
22874
  }
@@ -22877,6 +22880,7 @@ function DeviceRow({
22877
22880
  label,
22878
22881
  options,
22879
22882
  value,
22883
+ disabled: disabled2 = false,
22880
22884
  onChange,
22881
22885
  action
22882
22886
  }) {
@@ -22895,6 +22899,7 @@ function DeviceRow({
22895
22899
  "border-input bg-background h-8 min-w-0 flex-1 rounded-md border px-2 text-base md:text-sm"
22896
22900
  ),
22897
22901
  value: display,
22902
+ disabled: disabled2,
22898
22903
  onChange: (event) => onChange(event.target.value),
22899
22904
  children: options.map((option) => /* @__PURE__ */ jsx("option", { value: option.deviceId, children: option.label }, option.deviceId))
22900
22905
  }
@@ -28545,24 +28550,35 @@ function StudioToolbar({
28545
28550
  children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
28546
28551
  }
28547
28552
  ) : null,
28548
- showStageGuest ? /* @__PURE__ */ jsx(
28553
+ showStageGuest ? selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(
28554
+ ConfirmAction,
28555
+ {
28556
+ triggerLabel: labels.moveToAudience,
28557
+ title: labels.moveToAudienceTitle,
28558
+ description: labels.moveToAudienceDescription,
28559
+ confirmLabel: labels.moveToAudienceConfirm,
28560
+ pending,
28561
+ onConfirm: onLeaveStage,
28562
+ trigger: /* @__PURE__ */ jsx(
28563
+ Button3,
28564
+ {
28565
+ type: "button",
28566
+ size: "icon-lg",
28567
+ variant: "secondary",
28568
+ disabled: pending || !canLeaveStage,
28569
+ "aria-label": labels.moveToAudience,
28570
+ children: /* @__PURE__ */ jsx(Users, {})
28571
+ }
28572
+ )
28573
+ }
28574
+ ) : /* @__PURE__ */ jsx(
28549
28575
  ControlIcon,
28550
28576
  {
28551
- label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
28577
+ label: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
28552
28578
  pressed: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
28553
- disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" || selfStageStatus === "ON_STAGE" && !canLeaveStage,
28554
- onClick: () => {
28555
- if (selfStageStatus === "ON_STAGE") {
28556
- if (!canLeaveStage) {
28557
- toast.message(labels.offAirLastVisible);
28558
- return;
28559
- }
28560
- onLeaveStage();
28561
- return;
28562
- }
28563
- onAskStage();
28564
- },
28565
- children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
28579
+ disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
28580
+ onClick: onAskStage,
28581
+ children: /* @__PURE__ */ jsx(Eye, {})
28566
28582
  }
28567
28583
  ) : null,
28568
28584
  showScreenControl ? canShareFreely ? /* @__PURE__ */ jsx(
@@ -28894,7 +28910,8 @@ function StudioRoom({
28894
28910
  captionsBaseUrl,
28895
28911
  apiUrl,
28896
28912
  cdnBase,
28897
- endedHref
28913
+ endedHref,
28914
+ endedExtra
28898
28915
  }) {
28899
28916
  const [cameraOn, setCameraOn] = useState(initialCameraOn);
28900
28917
  const [screenOn, setScreenOn] = useState(false);
@@ -29606,6 +29623,7 @@ function StudioRoom({
29606
29623
  try {
29607
29624
  await meeting.leave();
29608
29625
  setLeaveState("left");
29626
+ } catch {
29609
29627
  } finally {
29610
29628
  setPending(false);
29611
29629
  }
@@ -29626,6 +29644,7 @@ function StudioRoom({
29626
29644
  }
29627
29645
  if (sessionOver) {
29628
29646
  const canRejoin = session.role === "moderator" && onRejoin != null && isStudioActiveStatus(session.stream.status);
29647
+ const returnHref = canRejoin || session.role !== "moderator" ? void 0 : endedHref?.trim() || `/streams/${session.stream.id}`;
29629
29648
  return withStudioToaster(
29630
29649
  /* @__PURE__ */ jsx("div", { className: "flex h-dvh flex-col items-center justify-center p-6", children: /* @__PURE__ */ jsx(
29631
29650
  EmptyState,
@@ -29634,7 +29653,8 @@ function StudioRoom({
29634
29653
  description: labels.endedDescription,
29635
29654
  actionLabel: canRejoin ? labels.rejoin : session.role === "moderator" ? labels.endedAction : void 0,
29636
29655
  onAction: canRejoin ? onRejoin : void 0,
29637
- actionHref: canRejoin || session.role !== "moderator" ? void 0 : endedHref?.trim() || `/streams/${session.stream.id}`
29656
+ actionHref: returnHref,
29657
+ extra: returnHref ? endedExtra : void 0
29638
29658
  }
29639
29659
  ) })
29640
29660
  );
@@ -30242,7 +30262,8 @@ var LOBBY_DEVICE_LABEL_DEFAULTS = {
30242
30262
  speakerDevice: "Speaker",
30243
30263
  testSpeaker: "Test",
30244
30264
  testSpeakerError: "Could not play the speaker test",
30245
- cameraOff: "Camera is off"
30265
+ cameraOff: "Camera is off",
30266
+ joining: "Joining\u2026"
30246
30267
  };
30247
30268
  function StudioGuestLobby({
30248
30269
  reducedMotion,
@@ -30277,7 +30298,9 @@ function StudioGuestLobby({
30277
30298
  testSpeakerError: labels.testSpeakerError ?? LOBBY_DEVICE_LABEL_DEFAULTS.testSpeakerError,
30278
30299
  cameraOff: labels.cameraOff ?? LOBBY_DEVICE_LABEL_DEFAULTS.cameraOff
30279
30300
  };
30301
+ const joiningLabel = labels.joining?.trim() || LOBBY_DEVICE_LABEL_DEFAULTS.joining;
30280
30302
  function changeDevice(kind, deviceId) {
30303
+ if (joining) return;
30281
30304
  persistStudioDeviceSelection(kind, deviceId);
30282
30305
  onDeviceChange?.(kind, deviceId);
30283
30306
  if (!savedDevices) {
@@ -30285,7 +30308,7 @@ function StudioGuestLobby({
30285
30308
  }
30286
30309
  }
30287
30310
  async function onTestSpeaker() {
30288
- if (testingSpeaker) return;
30311
+ if (testingSpeaker || joining) return;
30289
30312
  const speakerId = effective.speaker || devices.speaker[0]?.deviceId || void 0;
30290
30313
  setTestingSpeaker(true);
30291
30314
  try {
@@ -30324,6 +30347,7 @@ function StudioGuestLobby({
30324
30347
  size: "icon-lg",
30325
30348
  "aria-pressed": muted,
30326
30349
  "aria-label": labels.mic,
30350
+ disabled: joining,
30327
30351
  onClick: () => onMutedChange(!muted),
30328
30352
  children: muted ? /* @__PURE__ */ jsx(MicOff, {}) : /* @__PURE__ */ jsx(Mic, {})
30329
30353
  }
@@ -30336,6 +30360,7 @@ function StudioGuestLobby({
30336
30360
  size: "icon-lg",
30337
30361
  "aria-pressed": cameraOn,
30338
30362
  "aria-label": labels.camera,
30363
+ disabled: joining,
30339
30364
  onClick: () => onCameraChange(!cameraOn),
30340
30365
  children: cameraOn ? /* @__PURE__ */ jsx(Video, {}) : /* @__PURE__ */ jsx(VideoOff, {})
30341
30366
  }
@@ -30347,6 +30372,7 @@ function StudioGuestLobby({
30347
30372
  devices,
30348
30373
  selected: effective,
30349
30374
  labels: deviceLabels,
30375
+ disabled: joining,
30350
30376
  onChange: changeDevice,
30351
30377
  speakerAction: /* @__PURE__ */ jsx(
30352
30378
  Button3,
@@ -30354,7 +30380,7 @@ function StudioGuestLobby({
30354
30380
  type: "button",
30355
30381
  size: "sm",
30356
30382
  variant: "outline",
30357
- disabled: testingSpeaker,
30383
+ disabled: testingSpeaker || joining,
30358
30384
  onClick: () => void onTestSpeaker(),
30359
30385
  children: deviceLabels.testSpeaker
30360
30386
  }
@@ -30372,6 +30398,7 @@ function StudioGuestLobby({
30372
30398
  onChange: (event) => onDisplayNameChange(event.target.value),
30373
30399
  maxLength: 80,
30374
30400
  required: true,
30401
+ disabled: joining,
30375
30402
  autoComplete: "name"
30376
30403
  }
30377
30404
  )
@@ -30385,8 +30412,18 @@ function StudioGuestLobby({
30385
30412
  size: "lg",
30386
30413
  className: "w-full",
30387
30414
  disabled: joining || showName && !displayName.trim(),
30415
+ "aria-busy": joining,
30388
30416
  onClick: onJoin,
30389
- children: waitingForHost ? labels.waitingForHost : labels.join
30417
+ children: joining ? /* @__PURE__ */ jsxs(Fragment, { children: [
30418
+ /* @__PURE__ */ jsx(
30419
+ LoaderCircle,
30420
+ {
30421
+ "aria-hidden": true,
30422
+ className: "animate-spin motion-reduce:animate-none"
30423
+ }
30424
+ ),
30425
+ joiningLabel
30426
+ ] }) : waitingForHost ? labels.waitingForHost : labels.join
30390
30427
  }
30391
30428
  )
30392
30429
  ] });
@@ -30701,6 +30738,7 @@ function studioMeetingFromRtk(raw) {
30701
30738
  const chatSend = /* @__PURE__ */ new Map();
30702
30739
  const nameOverrides = /* @__PURE__ */ new Map();
30703
30740
  let joinStageInFlight = null;
30741
+ let leaveInProgress = false;
30704
30742
  let joinedStageLocally = false;
30705
30743
  let stageGrantPending = false;
30706
30744
  let stageGrantJoinFailures = 0;
@@ -30814,6 +30852,7 @@ function studioMeetingFromRtk(raw) {
30814
30852
  for (const listener of stageGrantListeners) listener(userId);
30815
30853
  }
30816
30854
  function markStageGranted() {
30855
+ if (leaveInProgress) return;
30817
30856
  stageGrantPending = true;
30818
30857
  void ensureStageJoined();
30819
30858
  }
@@ -30861,6 +30900,7 @@ function studioMeetingFromRtk(raw) {
30861
30900
  async function runJoinStageAttempts() {
30862
30901
  let lastError;
30863
30902
  for (let attempt = 0; attempt < STAGE_JOIN_MAX_ATTEMPTS; attempt++) {
30903
+ if (leaveInProgress) return;
30864
30904
  if (currentStageStatus() === "ON_STAGE") {
30865
30905
  notifyStage();
30866
30906
  return;
@@ -30909,6 +30949,7 @@ function studioMeetingFromRtk(raw) {
30909
30949
  }
30910
30950
  }
30911
30951
  async function ensureStageJoined() {
30952
+ if (leaveInProgress) return;
30912
30953
  if (currentStageStatus() === "ON_STAGE") {
30913
30954
  clearStageGrantPending();
30914
30955
  return;
@@ -31045,10 +31086,29 @@ function studioMeetingFromRtk(raw) {
31045
31086
  await applyCurrentVideoEffects();
31046
31087
  },
31047
31088
  async leave() {
31048
- if (typeof window !== "undefined") {
31049
- window.removeEventListener("unhandledrejection", onUnhandledRejection);
31089
+ leaveInProgress = true;
31090
+ clearStageGrantPending();
31091
+ try {
31092
+ if (typeof window !== "undefined") {
31093
+ window.removeEventListener(
31094
+ "unhandledrejection",
31095
+ onUnhandledRejection
31096
+ );
31097
+ }
31098
+ let firstFailed = false;
31099
+ try {
31100
+ await meeting.leave?.();
31101
+ } catch {
31102
+ firstFailed = true;
31103
+ }
31104
+ const roomState = meeting.self?.roomState ?? null;
31105
+ if (firstFailed || roomState === "joined" || roomState === "stageLeft") {
31106
+ await meeting.leave?.();
31107
+ }
31108
+ } finally {
31109
+ clearStageGrantPending();
31110
+ leaveInProgress = false;
31050
31111
  }
31051
- await meeting.leave?.();
31052
31112
  },
31053
31113
  async setAudioEnabled(enabled) {
31054
31114
  if (enabled) await meeting.self?.enableAudio?.();
@@ -31661,7 +31721,8 @@ function LivoHostStudio({
31661
31721
  theme,
31662
31722
  appDownloadUrl,
31663
31723
  guestUrl,
31664
- endedHref
31724
+ endedHref,
31725
+ endedExtra
31665
31726
  }) {
31666
31727
  const reducedMotion = usePrefersReducedMotion();
31667
31728
  const [session, setSession] = useState(null);
@@ -31783,7 +31844,8 @@ function LivoHostStudio({
31783
31844
  playerOrigin,
31784
31845
  appDownloadUrl,
31785
31846
  apiUrl,
31786
- endedHref
31847
+ endedHref,
31848
+ endedExtra
31787
31849
  }
31788
31850
  ) });
31789
31851
  }
@@ -31823,6 +31885,9 @@ function LivoGuestStudio({
31823
31885
  session?.authToken ?? null,
31824
31886
  sessionGen
31825
31887
  );
31888
+ useEffect(() => {
31889
+ if (meeting) setJoining(false);
31890
+ }, [meeting]);
31826
31891
  useEffect(() => {
31827
31892
  const identity = readStudioGuestIdentity();
31828
31893
  setDisplayName(identity.displayName);
@@ -31855,6 +31920,7 @@ function LivoGuestStudio({
31855
31920
  setStatus(null);
31856
31921
  setSession(next);
31857
31922
  } catch (err) {
31923
+ setJoining(false);
31858
31924
  const code = studioErrorCode(err);
31859
31925
  if (code === "studio_not_started") {
31860
31926
  setWaitingForHost(true);
@@ -31871,7 +31937,6 @@ function LivoGuestStudio({
31871
31937
  }
31872
31938
  } finally {
31873
31939
  joinInFlight.current = false;
31874
- setJoining(false);
31875
31940
  }
31876
31941
  }, [apiUrl, displayName, guestId, lobbyLabels, token]);
31877
31942
  useEffect(() => {
@@ -32133,6 +32198,10 @@ function studioRoomLabels(t) {
32133
32198
  shareRequestTimeout: t("shareRequestTimeout"),
32134
32199
  shareRespondError: t("shareRespondError"),
32135
32200
  goOffAir: t("goOffAir"),
32201
+ moveToAudience: t("moveToAudience"),
32202
+ moveToAudienceTitle: t("moveToAudienceTitle"),
32203
+ moveToAudienceDescription: t("moveToAudienceDescription"),
32204
+ moveToAudienceConfirm: t("moveToAudienceConfirm"),
32136
32205
  goOnAir: t("goOnAir"),
32137
32206
  takeOffAir: t("takeOffAir"),
32138
32207
  bringOnAir: t("bringOnAir"),