@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.
@@ -11677,21 +11677,18 @@ function pipSpotlightFrom(tile, kind) {
11677
11677
  }
11678
11678
  function resolveStudioPipSpotlight(tiles, activeSpeakerId) {
11679
11679
  const visible = tiles.filter((tile) => isOnStage(tile.stageStatus));
11680
+ const sharing = visible.find((tile) => tile.screenShareEnabled);
11681
+ if (sharing) {
11682
+ return pipSpotlightFrom(sharing, "screen");
11683
+ }
11680
11684
  const pinned = visible.find((tile) => tile.pinned === true);
11681
11685
  if (pinned) {
11682
- return pipSpotlightFrom(
11683
- pinned,
11684
- pinned.screenShareEnabled ? "screen" : pinned.videoEnabled ? "camera" : "idle"
11685
- );
11686
+ return pipSpotlightFrom(pinned, pinned.videoEnabled ? "camera" : "idle");
11686
11687
  }
11687
11688
  const speaker = activeSpeakerId ? visible.find((tile) => tile.id === activeSpeakerId) : void 0;
11688
11689
  if (speaker) {
11689
11690
  return pipSpotlightFrom(speaker, speaker.videoEnabled ? "camera" : "idle");
11690
11691
  }
11691
- const sharing = visible.find((tile) => tile.screenShareEnabled);
11692
- if (sharing) {
11693
- return pipSpotlightFrom(sharing, "screen");
11694
- }
11695
11692
  const remoteCam = visible.find((tile) => !tile.isSelf && tile.videoEnabled);
11696
11693
  if (remoteCam) {
11697
11694
  return pipSpotlightFrom(remoteCam, "camera");
@@ -22856,6 +22853,7 @@ function StudioDeviceSelectGroup({
22856
22853
  devices,
22857
22854
  selected,
22858
22855
  labels,
22856
+ disabled: disabled2 = false,
22859
22857
  onChange,
22860
22858
  speakerAction
22861
22859
  }) {
@@ -22867,6 +22865,7 @@ function StudioDeviceSelectGroup({
22867
22865
  label: labels.cameraDevice,
22868
22866
  options: devices.video,
22869
22867
  value: selected.video,
22868
+ disabled: disabled2,
22870
22869
  onChange: (id) => onChange("video", id)
22871
22870
  }
22872
22871
  ),
@@ -22877,6 +22876,7 @@ function StudioDeviceSelectGroup({
22877
22876
  label: labels.micDevice,
22878
22877
  options: devices.audio,
22879
22878
  value: selected.audio,
22879
+ disabled: disabled2,
22880
22880
  onChange: (id) => onChange("audio", id)
22881
22881
  }
22882
22882
  ),
@@ -22887,6 +22887,7 @@ function StudioDeviceSelectGroup({
22887
22887
  label: labels.speakerDevice,
22888
22888
  options: devices.speaker,
22889
22889
  value: selected.speaker,
22890
+ disabled: disabled2,
22890
22891
  onChange: (id) => onChange("speaker", id),
22891
22892
  action: speakerAction
22892
22893
  }
@@ -22898,6 +22899,7 @@ function DeviceRow({
22898
22899
  label,
22899
22900
  options,
22900
22901
  value,
22902
+ disabled: disabled2 = false,
22901
22903
  onChange,
22902
22904
  action
22903
22905
  }) {
@@ -22916,6 +22918,7 @@ function DeviceRow({
22916
22918
  "border-input bg-background h-8 min-w-0 flex-1 rounded-md border px-2 text-base md:text-sm"
22917
22919
  ),
22918
22920
  value: display,
22921
+ disabled: disabled2,
22919
22922
  onChange: (event) => onChange(event.target.value),
22920
22923
  children: options.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option.deviceId, children: option.label }, option.deviceId))
22921
22924
  }
@@ -28566,24 +28569,35 @@ function StudioToolbar({
28566
28569
  children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsxRuntime.jsx(EyeOff, {}) : /* @__PURE__ */ jsxRuntime.jsx(Eye, {})
28567
28570
  }
28568
28571
  ) : null,
28569
- showStageGuest ? /* @__PURE__ */ jsxRuntime.jsx(
28572
+ showStageGuest ? selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsxRuntime.jsx(
28573
+ ConfirmAction,
28574
+ {
28575
+ triggerLabel: labels.moveToAudience,
28576
+ title: labels.moveToAudienceTitle,
28577
+ description: labels.moveToAudienceDescription,
28578
+ confirmLabel: labels.moveToAudienceConfirm,
28579
+ pending,
28580
+ onConfirm: onLeaveStage,
28581
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(
28582
+ Button3,
28583
+ {
28584
+ type: "button",
28585
+ size: "icon-lg",
28586
+ variant: "secondary",
28587
+ disabled: pending || !canLeaveStage,
28588
+ "aria-label": labels.moveToAudience,
28589
+ children: /* @__PURE__ */ jsxRuntime.jsx(Users, {})
28590
+ }
28591
+ )
28592
+ }
28593
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
28570
28594
  ControlIcon,
28571
28595
  {
28572
- label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
28596
+ label: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
28573
28597
  pressed: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
28574
- disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" || selfStageStatus === "ON_STAGE" && !canLeaveStage,
28575
- onClick: () => {
28576
- if (selfStageStatus === "ON_STAGE") {
28577
- if (!canLeaveStage) {
28578
- toast.message(labels.offAirLastVisible);
28579
- return;
28580
- }
28581
- onLeaveStage();
28582
- return;
28583
- }
28584
- onAskStage();
28585
- },
28586
- children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsxRuntime.jsx(EyeOff, {}) : /* @__PURE__ */ jsxRuntime.jsx(Eye, {})
28598
+ disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
28599
+ onClick: onAskStage,
28600
+ children: /* @__PURE__ */ jsxRuntime.jsx(Eye, {})
28587
28601
  }
28588
28602
  ) : null,
28589
28603
  showScreenControl ? canShareFreely ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -29627,6 +29641,7 @@ function StudioRoom({
29627
29641
  try {
29628
29642
  await meeting.leave();
29629
29643
  setLeaveState("left");
29644
+ } catch {
29630
29645
  } finally {
29631
29646
  setPending(false);
29632
29647
  }
@@ -30263,7 +30278,8 @@ var LOBBY_DEVICE_LABEL_DEFAULTS = {
30263
30278
  speakerDevice: "Speaker",
30264
30279
  testSpeaker: "Test",
30265
30280
  testSpeakerError: "Could not play the speaker test",
30266
- cameraOff: "Camera is off"
30281
+ cameraOff: "Camera is off",
30282
+ joining: "Joining\u2026"
30267
30283
  };
30268
30284
  function StudioGuestLobby({
30269
30285
  reducedMotion,
@@ -30298,7 +30314,9 @@ function StudioGuestLobby({
30298
30314
  testSpeakerError: labels.testSpeakerError ?? LOBBY_DEVICE_LABEL_DEFAULTS.testSpeakerError,
30299
30315
  cameraOff: labels.cameraOff ?? LOBBY_DEVICE_LABEL_DEFAULTS.cameraOff
30300
30316
  };
30317
+ const joiningLabel = labels.joining?.trim() || LOBBY_DEVICE_LABEL_DEFAULTS.joining;
30301
30318
  function changeDevice(kind, deviceId) {
30319
+ if (joining) return;
30302
30320
  persistStudioDeviceSelection(kind, deviceId);
30303
30321
  onDeviceChange?.(kind, deviceId);
30304
30322
  if (!savedDevices) {
@@ -30306,7 +30324,7 @@ function StudioGuestLobby({
30306
30324
  }
30307
30325
  }
30308
30326
  async function onTestSpeaker() {
30309
- if (testingSpeaker) return;
30327
+ if (testingSpeaker || joining) return;
30310
30328
  const speakerId = effective.speaker || devices.speaker[0]?.deviceId || void 0;
30311
30329
  setTestingSpeaker(true);
30312
30330
  try {
@@ -30345,6 +30363,7 @@ function StudioGuestLobby({
30345
30363
  size: "icon-lg",
30346
30364
  "aria-pressed": muted,
30347
30365
  "aria-label": labels.mic,
30366
+ disabled: joining,
30348
30367
  onClick: () => onMutedChange(!muted),
30349
30368
  children: muted ? /* @__PURE__ */ jsxRuntime.jsx(MicOff, {}) : /* @__PURE__ */ jsxRuntime.jsx(Mic, {})
30350
30369
  }
@@ -30357,6 +30376,7 @@ function StudioGuestLobby({
30357
30376
  size: "icon-lg",
30358
30377
  "aria-pressed": cameraOn,
30359
30378
  "aria-label": labels.camera,
30379
+ disabled: joining,
30360
30380
  onClick: () => onCameraChange(!cameraOn),
30361
30381
  children: cameraOn ? /* @__PURE__ */ jsxRuntime.jsx(Video, {}) : /* @__PURE__ */ jsxRuntime.jsx(VideoOff, {})
30362
30382
  }
@@ -30368,6 +30388,7 @@ function StudioGuestLobby({
30368
30388
  devices,
30369
30389
  selected: effective,
30370
30390
  labels: deviceLabels,
30391
+ disabled: joining,
30371
30392
  onChange: changeDevice,
30372
30393
  speakerAction: /* @__PURE__ */ jsxRuntime.jsx(
30373
30394
  Button3,
@@ -30375,7 +30396,7 @@ function StudioGuestLobby({
30375
30396
  type: "button",
30376
30397
  size: "sm",
30377
30398
  variant: "outline",
30378
- disabled: testingSpeaker,
30399
+ disabled: testingSpeaker || joining,
30379
30400
  onClick: () => void onTestSpeaker(),
30380
30401
  children: deviceLabels.testSpeaker
30381
30402
  }
@@ -30393,6 +30414,7 @@ function StudioGuestLobby({
30393
30414
  onChange: (event) => onDisplayNameChange(event.target.value),
30394
30415
  maxLength: 80,
30395
30416
  required: true,
30417
+ disabled: joining,
30396
30418
  autoComplete: "name"
30397
30419
  }
30398
30420
  )
@@ -30406,8 +30428,18 @@ function StudioGuestLobby({
30406
30428
  size: "lg",
30407
30429
  className: "w-full",
30408
30430
  disabled: joining || showName && !displayName.trim(),
30431
+ "aria-busy": joining,
30409
30432
  onClick: onJoin,
30410
- children: waitingForHost ? labels.waitingForHost : labels.join
30433
+ children: joining ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
30434
+ /* @__PURE__ */ jsxRuntime.jsx(
30435
+ LoaderCircle,
30436
+ {
30437
+ "aria-hidden": true,
30438
+ className: "animate-spin motion-reduce:animate-none"
30439
+ }
30440
+ ),
30441
+ joiningLabel
30442
+ ] }) : waitingForHost ? labels.waitingForHost : labels.join
30411
30443
  }
30412
30444
  )
30413
30445
  ] });
@@ -30722,6 +30754,7 @@ function studioMeetingFromRtk(raw) {
30722
30754
  const chatSend = /* @__PURE__ */ new Map();
30723
30755
  const nameOverrides = /* @__PURE__ */ new Map();
30724
30756
  let joinStageInFlight = null;
30757
+ let leaveInProgress = false;
30725
30758
  let joinedStageLocally = false;
30726
30759
  let stageGrantPending = false;
30727
30760
  let stageGrantJoinFailures = 0;
@@ -30835,6 +30868,7 @@ function studioMeetingFromRtk(raw) {
30835
30868
  for (const listener of stageGrantListeners) listener(userId);
30836
30869
  }
30837
30870
  function markStageGranted() {
30871
+ if (leaveInProgress) return;
30838
30872
  stageGrantPending = true;
30839
30873
  void ensureStageJoined();
30840
30874
  }
@@ -30882,6 +30916,7 @@ function studioMeetingFromRtk(raw) {
30882
30916
  async function runJoinStageAttempts() {
30883
30917
  let lastError;
30884
30918
  for (let attempt = 0; attempt < STAGE_JOIN_MAX_ATTEMPTS; attempt++) {
30919
+ if (leaveInProgress) return;
30885
30920
  if (currentStageStatus() === "ON_STAGE") {
30886
30921
  notifyStage();
30887
30922
  return;
@@ -30930,6 +30965,7 @@ function studioMeetingFromRtk(raw) {
30930
30965
  }
30931
30966
  }
30932
30967
  async function ensureStageJoined() {
30968
+ if (leaveInProgress) return;
30933
30969
  if (currentStageStatus() === "ON_STAGE") {
30934
30970
  clearStageGrantPending();
30935
30971
  return;
@@ -31066,10 +31102,29 @@ function studioMeetingFromRtk(raw) {
31066
31102
  await applyCurrentVideoEffects();
31067
31103
  },
31068
31104
  async leave() {
31069
- if (typeof window !== "undefined") {
31070
- window.removeEventListener("unhandledrejection", onUnhandledRejection);
31105
+ leaveInProgress = true;
31106
+ clearStageGrantPending();
31107
+ try {
31108
+ if (typeof window !== "undefined") {
31109
+ window.removeEventListener(
31110
+ "unhandledrejection",
31111
+ onUnhandledRejection
31112
+ );
31113
+ }
31114
+ let firstFailed = false;
31115
+ try {
31116
+ await meeting.leave?.();
31117
+ } catch {
31118
+ firstFailed = true;
31119
+ }
31120
+ const roomState = meeting.self?.roomState ?? null;
31121
+ if (firstFailed || roomState === "joined" || roomState === "stageLeft") {
31122
+ await meeting.leave?.();
31123
+ }
31124
+ } finally {
31125
+ clearStageGrantPending();
31126
+ leaveInProgress = false;
31071
31127
  }
31072
- await meeting.leave?.();
31073
31128
  },
31074
31129
  async setAudioEnabled(enabled) {
31075
31130
  if (enabled) await meeting.self?.enableAudio?.();
@@ -31844,6 +31899,9 @@ function LivoGuestStudio({
31844
31899
  session?.authToken ?? null,
31845
31900
  sessionGen
31846
31901
  );
31902
+ React18.useEffect(() => {
31903
+ if (meeting) setJoining(false);
31904
+ }, [meeting]);
31847
31905
  React18.useEffect(() => {
31848
31906
  const identity = readStudioGuestIdentity();
31849
31907
  setDisplayName(identity.displayName);
@@ -31876,6 +31934,7 @@ function LivoGuestStudio({
31876
31934
  setStatus(null);
31877
31935
  setSession(next);
31878
31936
  } catch (err) {
31937
+ setJoining(false);
31879
31938
  const code = studioErrorCode(err);
31880
31939
  if (code === "studio_not_started") {
31881
31940
  setWaitingForHost(true);
@@ -31892,7 +31951,6 @@ function LivoGuestStudio({
31892
31951
  }
31893
31952
  } finally {
31894
31953
  joinInFlight.current = false;
31895
- setJoining(false);
31896
31954
  }
31897
31955
  }, [apiUrl, displayName, guestId, lobbyLabels, token]);
31898
31956
  React18.useEffect(() => {
@@ -32154,6 +32212,10 @@ function studioRoomLabels(t) {
32154
32212
  shareRequestTimeout: t("shareRequestTimeout"),
32155
32213
  shareRespondError: t("shareRespondError"),
32156
32214
  goOffAir: t("goOffAir"),
32215
+ moveToAudience: t("moveToAudience"),
32216
+ moveToAudienceTitle: t("moveToAudienceTitle"),
32217
+ moveToAudienceDescription: t("moveToAudienceDescription"),
32218
+ moveToAudienceConfirm: t("moveToAudienceConfirm"),
32157
32219
  goOnAir: t("goOnAir"),
32158
32220
  takeOffAir: t("takeOffAir"),
32159
32221
  bringOnAir: t("bringOnAir"),