@livo-tv/sdk 1.16.0 → 1.17.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;
@@ -559,6 +563,7 @@ type StudioGuestLobbyLabels = {
559
563
  hint: string;
560
564
  name: string;
561
565
  join: string;
566
+ joining: string;
562
567
  waitingForHost: string;
563
568
  camera: string;
564
569
  mic: string;
@@ -628,6 +633,7 @@ type StudioLobbyLabels = {
628
633
  hint: string;
629
634
  name: string;
630
635
  join: string;
636
+ joining: string;
631
637
  waitingForHost: string;
632
638
  camera: string;
633
639
  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;
@@ -559,6 +563,7 @@ type StudioGuestLobbyLabels = {
559
563
  hint: string;
560
564
  name: string;
561
565
  join: string;
566
+ joining: string;
562
567
  waitingForHost: string;
563
568
  camera: string;
564
569
  mic: string;
@@ -628,6 +633,7 @@ type StudioLobbyLabels = {
628
633
  hint: string;
629
634
  name: string;
630
635
  join: string;
636
+ joining: string;
631
637
  waitingForHost: string;
632
638
  camera: string;
633
639
  mic: string;
@@ -11656,21 +11656,18 @@ function pipSpotlightFrom(tile, kind) {
11656
11656
  }
11657
11657
  function resolveStudioPipSpotlight(tiles, activeSpeakerId) {
11658
11658
  const visible = tiles.filter((tile) => isOnStage(tile.stageStatus));
11659
+ const sharing = visible.find((tile) => tile.screenShareEnabled);
11660
+ if (sharing) {
11661
+ return pipSpotlightFrom(sharing, "screen");
11662
+ }
11659
11663
  const pinned = visible.find((tile) => tile.pinned === true);
11660
11664
  if (pinned) {
11661
- return pipSpotlightFrom(
11662
- pinned,
11663
- pinned.screenShareEnabled ? "screen" : pinned.videoEnabled ? "camera" : "idle"
11664
- );
11665
+ return pipSpotlightFrom(pinned, pinned.videoEnabled ? "camera" : "idle");
11665
11666
  }
11666
11667
  const speaker = activeSpeakerId ? visible.find((tile) => tile.id === activeSpeakerId) : void 0;
11667
11668
  if (speaker) {
11668
11669
  return pipSpotlightFrom(speaker, speaker.videoEnabled ? "camera" : "idle");
11669
11670
  }
11670
- const sharing = visible.find((tile) => tile.screenShareEnabled);
11671
- if (sharing) {
11672
- return pipSpotlightFrom(sharing, "screen");
11673
- }
11674
11671
  const remoteCam = visible.find((tile) => !tile.isSelf && tile.videoEnabled);
11675
11672
  if (remoteCam) {
11676
11673
  return pipSpotlightFrom(remoteCam, "camera");
@@ -22835,6 +22832,7 @@ function StudioDeviceSelectGroup({
22835
22832
  devices,
22836
22833
  selected,
22837
22834
  labels,
22835
+ disabled: disabled2 = false,
22838
22836
  onChange,
22839
22837
  speakerAction
22840
22838
  }) {
@@ -22846,6 +22844,7 @@ function StudioDeviceSelectGroup({
22846
22844
  label: labels.cameraDevice,
22847
22845
  options: devices.video,
22848
22846
  value: selected.video,
22847
+ disabled: disabled2,
22849
22848
  onChange: (id) => onChange("video", id)
22850
22849
  }
22851
22850
  ),
@@ -22856,6 +22855,7 @@ function StudioDeviceSelectGroup({
22856
22855
  label: labels.micDevice,
22857
22856
  options: devices.audio,
22858
22857
  value: selected.audio,
22858
+ disabled: disabled2,
22859
22859
  onChange: (id) => onChange("audio", id)
22860
22860
  }
22861
22861
  ),
@@ -22866,6 +22866,7 @@ function StudioDeviceSelectGroup({
22866
22866
  label: labels.speakerDevice,
22867
22867
  options: devices.speaker,
22868
22868
  value: selected.speaker,
22869
+ disabled: disabled2,
22869
22870
  onChange: (id) => onChange("speaker", id),
22870
22871
  action: speakerAction
22871
22872
  }
@@ -22877,6 +22878,7 @@ function DeviceRow({
22877
22878
  label,
22878
22879
  options,
22879
22880
  value,
22881
+ disabled: disabled2 = false,
22880
22882
  onChange,
22881
22883
  action
22882
22884
  }) {
@@ -22895,6 +22897,7 @@ function DeviceRow({
22895
22897
  "border-input bg-background h-8 min-w-0 flex-1 rounded-md border px-2 text-base md:text-sm"
22896
22898
  ),
22897
22899
  value: display,
22900
+ disabled: disabled2,
22898
22901
  onChange: (event) => onChange(event.target.value),
22899
22902
  children: options.map((option) => /* @__PURE__ */ jsx("option", { value: option.deviceId, children: option.label }, option.deviceId))
22900
22903
  }
@@ -28545,24 +28548,35 @@ function StudioToolbar({
28545
28548
  children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
28546
28549
  }
28547
28550
  ) : null,
28548
- showStageGuest ? /* @__PURE__ */ jsx(
28551
+ showStageGuest ? selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(
28552
+ ConfirmAction,
28553
+ {
28554
+ triggerLabel: labels.moveToAudience,
28555
+ title: labels.moveToAudienceTitle,
28556
+ description: labels.moveToAudienceDescription,
28557
+ confirmLabel: labels.moveToAudienceConfirm,
28558
+ pending,
28559
+ onConfirm: onLeaveStage,
28560
+ trigger: /* @__PURE__ */ jsx(
28561
+ Button3,
28562
+ {
28563
+ type: "button",
28564
+ size: "icon-lg",
28565
+ variant: "secondary",
28566
+ disabled: pending || !canLeaveStage,
28567
+ "aria-label": labels.moveToAudience,
28568
+ children: /* @__PURE__ */ jsx(Users, {})
28569
+ }
28570
+ )
28571
+ }
28572
+ ) : /* @__PURE__ */ jsx(
28549
28573
  ControlIcon,
28550
28574
  {
28551
- label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
28575
+ label: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
28552
28576
  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, {})
28577
+ disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
28578
+ onClick: onAskStage,
28579
+ children: /* @__PURE__ */ jsx(Eye, {})
28566
28580
  }
28567
28581
  ) : null,
28568
28582
  showScreenControl ? canShareFreely ? /* @__PURE__ */ jsx(
@@ -29606,6 +29620,7 @@ function StudioRoom({
29606
29620
  try {
29607
29621
  await meeting.leave();
29608
29622
  setLeaveState("left");
29623
+ } catch {
29609
29624
  } finally {
29610
29625
  setPending(false);
29611
29626
  }
@@ -30242,7 +30257,8 @@ var LOBBY_DEVICE_LABEL_DEFAULTS = {
30242
30257
  speakerDevice: "Speaker",
30243
30258
  testSpeaker: "Test",
30244
30259
  testSpeakerError: "Could not play the speaker test",
30245
- cameraOff: "Camera is off"
30260
+ cameraOff: "Camera is off",
30261
+ joining: "Joining\u2026"
30246
30262
  };
30247
30263
  function StudioGuestLobby({
30248
30264
  reducedMotion,
@@ -30277,7 +30293,9 @@ function StudioGuestLobby({
30277
30293
  testSpeakerError: labels.testSpeakerError ?? LOBBY_DEVICE_LABEL_DEFAULTS.testSpeakerError,
30278
30294
  cameraOff: labels.cameraOff ?? LOBBY_DEVICE_LABEL_DEFAULTS.cameraOff
30279
30295
  };
30296
+ const joiningLabel = labels.joining?.trim() || LOBBY_DEVICE_LABEL_DEFAULTS.joining;
30280
30297
  function changeDevice(kind, deviceId) {
30298
+ if (joining) return;
30281
30299
  persistStudioDeviceSelection(kind, deviceId);
30282
30300
  onDeviceChange?.(kind, deviceId);
30283
30301
  if (!savedDevices) {
@@ -30285,7 +30303,7 @@ function StudioGuestLobby({
30285
30303
  }
30286
30304
  }
30287
30305
  async function onTestSpeaker() {
30288
- if (testingSpeaker) return;
30306
+ if (testingSpeaker || joining) return;
30289
30307
  const speakerId = effective.speaker || devices.speaker[0]?.deviceId || void 0;
30290
30308
  setTestingSpeaker(true);
30291
30309
  try {
@@ -30324,6 +30342,7 @@ function StudioGuestLobby({
30324
30342
  size: "icon-lg",
30325
30343
  "aria-pressed": muted,
30326
30344
  "aria-label": labels.mic,
30345
+ disabled: joining,
30327
30346
  onClick: () => onMutedChange(!muted),
30328
30347
  children: muted ? /* @__PURE__ */ jsx(MicOff, {}) : /* @__PURE__ */ jsx(Mic, {})
30329
30348
  }
@@ -30336,6 +30355,7 @@ function StudioGuestLobby({
30336
30355
  size: "icon-lg",
30337
30356
  "aria-pressed": cameraOn,
30338
30357
  "aria-label": labels.camera,
30358
+ disabled: joining,
30339
30359
  onClick: () => onCameraChange(!cameraOn),
30340
30360
  children: cameraOn ? /* @__PURE__ */ jsx(Video, {}) : /* @__PURE__ */ jsx(VideoOff, {})
30341
30361
  }
@@ -30347,6 +30367,7 @@ function StudioGuestLobby({
30347
30367
  devices,
30348
30368
  selected: effective,
30349
30369
  labels: deviceLabels,
30370
+ disabled: joining,
30350
30371
  onChange: changeDevice,
30351
30372
  speakerAction: /* @__PURE__ */ jsx(
30352
30373
  Button3,
@@ -30354,7 +30375,7 @@ function StudioGuestLobby({
30354
30375
  type: "button",
30355
30376
  size: "sm",
30356
30377
  variant: "outline",
30357
- disabled: testingSpeaker,
30378
+ disabled: testingSpeaker || joining,
30358
30379
  onClick: () => void onTestSpeaker(),
30359
30380
  children: deviceLabels.testSpeaker
30360
30381
  }
@@ -30372,6 +30393,7 @@ function StudioGuestLobby({
30372
30393
  onChange: (event) => onDisplayNameChange(event.target.value),
30373
30394
  maxLength: 80,
30374
30395
  required: true,
30396
+ disabled: joining,
30375
30397
  autoComplete: "name"
30376
30398
  }
30377
30399
  )
@@ -30385,8 +30407,18 @@ function StudioGuestLobby({
30385
30407
  size: "lg",
30386
30408
  className: "w-full",
30387
30409
  disabled: joining || showName && !displayName.trim(),
30410
+ "aria-busy": joining,
30388
30411
  onClick: onJoin,
30389
- children: waitingForHost ? labels.waitingForHost : labels.join
30412
+ children: joining ? /* @__PURE__ */ jsxs(Fragment, { children: [
30413
+ /* @__PURE__ */ jsx(
30414
+ LoaderCircle,
30415
+ {
30416
+ "aria-hidden": true,
30417
+ className: "animate-spin motion-reduce:animate-none"
30418
+ }
30419
+ ),
30420
+ joiningLabel
30421
+ ] }) : waitingForHost ? labels.waitingForHost : labels.join
30390
30422
  }
30391
30423
  )
30392
30424
  ] });
@@ -30701,6 +30733,7 @@ function studioMeetingFromRtk(raw) {
30701
30733
  const chatSend = /* @__PURE__ */ new Map();
30702
30734
  const nameOverrides = /* @__PURE__ */ new Map();
30703
30735
  let joinStageInFlight = null;
30736
+ let leaveInProgress = false;
30704
30737
  let joinedStageLocally = false;
30705
30738
  let stageGrantPending = false;
30706
30739
  let stageGrantJoinFailures = 0;
@@ -30814,6 +30847,7 @@ function studioMeetingFromRtk(raw) {
30814
30847
  for (const listener of stageGrantListeners) listener(userId);
30815
30848
  }
30816
30849
  function markStageGranted() {
30850
+ if (leaveInProgress) return;
30817
30851
  stageGrantPending = true;
30818
30852
  void ensureStageJoined();
30819
30853
  }
@@ -30861,6 +30895,7 @@ function studioMeetingFromRtk(raw) {
30861
30895
  async function runJoinStageAttempts() {
30862
30896
  let lastError;
30863
30897
  for (let attempt = 0; attempt < STAGE_JOIN_MAX_ATTEMPTS; attempt++) {
30898
+ if (leaveInProgress) return;
30864
30899
  if (currentStageStatus() === "ON_STAGE") {
30865
30900
  notifyStage();
30866
30901
  return;
@@ -30909,6 +30944,7 @@ function studioMeetingFromRtk(raw) {
30909
30944
  }
30910
30945
  }
30911
30946
  async function ensureStageJoined() {
30947
+ if (leaveInProgress) return;
30912
30948
  if (currentStageStatus() === "ON_STAGE") {
30913
30949
  clearStageGrantPending();
30914
30950
  return;
@@ -31045,10 +31081,29 @@ function studioMeetingFromRtk(raw) {
31045
31081
  await applyCurrentVideoEffects();
31046
31082
  },
31047
31083
  async leave() {
31048
- if (typeof window !== "undefined") {
31049
- window.removeEventListener("unhandledrejection", onUnhandledRejection);
31084
+ leaveInProgress = true;
31085
+ clearStageGrantPending();
31086
+ try {
31087
+ if (typeof window !== "undefined") {
31088
+ window.removeEventListener(
31089
+ "unhandledrejection",
31090
+ onUnhandledRejection
31091
+ );
31092
+ }
31093
+ let firstFailed = false;
31094
+ try {
31095
+ await meeting.leave?.();
31096
+ } catch {
31097
+ firstFailed = true;
31098
+ }
31099
+ const roomState = meeting.self?.roomState ?? null;
31100
+ if (firstFailed || roomState === "joined" || roomState === "stageLeft") {
31101
+ await meeting.leave?.();
31102
+ }
31103
+ } finally {
31104
+ clearStageGrantPending();
31105
+ leaveInProgress = false;
31050
31106
  }
31051
- await meeting.leave?.();
31052
31107
  },
31053
31108
  async setAudioEnabled(enabled) {
31054
31109
  if (enabled) await meeting.self?.enableAudio?.();
@@ -31823,6 +31878,9 @@ function LivoGuestStudio({
31823
31878
  session?.authToken ?? null,
31824
31879
  sessionGen
31825
31880
  );
31881
+ useEffect(() => {
31882
+ if (meeting) setJoining(false);
31883
+ }, [meeting]);
31826
31884
  useEffect(() => {
31827
31885
  const identity = readStudioGuestIdentity();
31828
31886
  setDisplayName(identity.displayName);
@@ -31855,6 +31913,7 @@ function LivoGuestStudio({
31855
31913
  setStatus(null);
31856
31914
  setSession(next);
31857
31915
  } catch (err) {
31916
+ setJoining(false);
31858
31917
  const code = studioErrorCode(err);
31859
31918
  if (code === "studio_not_started") {
31860
31919
  setWaitingForHost(true);
@@ -31871,7 +31930,6 @@ function LivoGuestStudio({
31871
31930
  }
31872
31931
  } finally {
31873
31932
  joinInFlight.current = false;
31874
- setJoining(false);
31875
31933
  }
31876
31934
  }, [apiUrl, displayName, guestId, lobbyLabels, token]);
31877
31935
  useEffect(() => {
@@ -32133,6 +32191,10 @@ function studioRoomLabels(t) {
32133
32191
  shareRequestTimeout: t("shareRequestTimeout"),
32134
32192
  shareRespondError: t("shareRespondError"),
32135
32193
  goOffAir: t("goOffAir"),
32194
+ moveToAudience: t("moveToAudience"),
32195
+ moveToAudienceTitle: t("moveToAudienceTitle"),
32196
+ moveToAudienceDescription: t("moveToAudienceDescription"),
32197
+ moveToAudienceConfirm: t("moveToAudienceConfirm"),
32136
32198
  goOnAir: t("goOnAir"),
32137
32199
  takeOffAir: t("takeOffAir"),
32138
32200
  bringOnAir: t("bringOnAir"),