@livo-tv/sdk 1.2.3 → 1.3.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.
- package/dist/studio/index.cjs +241 -80
- package/dist/studio/index.cjs.map +1 -1
- package/dist/studio/index.d.cts +7 -0
- package/dist/studio/index.d.ts +7 -0
- package/dist/studio/index.js +241 -80
- package/dist/studio/index.js.map +1 -1
- package/package.json +1 -1
package/dist/studio/index.d.cts
CHANGED
|
@@ -126,6 +126,7 @@ type StudioDevices = {
|
|
|
126
126
|
type StudioVideoKind = "camera" | "screen";
|
|
127
127
|
type StudioHostMediaKind = "audio" | "video" | "screen";
|
|
128
128
|
type StudioScreenSharePermission = "ALLOWED" | "NOT_ALLOWED" | "CAN_REQUEST";
|
|
129
|
+
type StudioConnectionState = "joined" | "waitlisted" | "connecting" | "failed";
|
|
129
130
|
type StudioBroadcast = {
|
|
130
131
|
type: "host-media";
|
|
131
132
|
kind: StudioHostMediaKind;
|
|
@@ -190,6 +191,8 @@ type StudioMeetingHandle = {
|
|
|
190
191
|
participants: () => StudioParticipantTile[];
|
|
191
192
|
waitlisted: () => StudioWaitlistedGuest[];
|
|
192
193
|
inWaitingRoom: () => boolean;
|
|
194
|
+
connectionState: () => StudioConnectionState;
|
|
195
|
+
lastMeetingError: () => string | null;
|
|
193
196
|
moderatorIds: () => string[];
|
|
194
197
|
localAudioTrack: () => MediaStreamTrack | null;
|
|
195
198
|
localVideoTrack: () => MediaStreamTrack | null;
|
|
@@ -219,6 +222,9 @@ declare function studioRoomLabels(t: (key: string, values?: Record<string, strin
|
|
|
219
222
|
preview: string;
|
|
220
223
|
waiting: string;
|
|
221
224
|
waitingHost: string;
|
|
225
|
+
connecting: string;
|
|
226
|
+
connectFailed: string;
|
|
227
|
+
retry: string;
|
|
222
228
|
admit: string;
|
|
223
229
|
deny: string;
|
|
224
230
|
denied: string;
|
|
@@ -359,6 +365,7 @@ declare function studioRoomLabels(t: (key: string, values?: Record<string, strin
|
|
|
359
365
|
unpinnedToast: string;
|
|
360
366
|
pinError: string;
|
|
361
367
|
kickTitle: string;
|
|
368
|
+
kickTitleNamed: string;
|
|
362
369
|
kickDescription: string;
|
|
363
370
|
kickConfirm: string;
|
|
364
371
|
kickError: string;
|
package/dist/studio/index.d.ts
CHANGED
|
@@ -126,6 +126,7 @@ type StudioDevices = {
|
|
|
126
126
|
type StudioVideoKind = "camera" | "screen";
|
|
127
127
|
type StudioHostMediaKind = "audio" | "video" | "screen";
|
|
128
128
|
type StudioScreenSharePermission = "ALLOWED" | "NOT_ALLOWED" | "CAN_REQUEST";
|
|
129
|
+
type StudioConnectionState = "joined" | "waitlisted" | "connecting" | "failed";
|
|
129
130
|
type StudioBroadcast = {
|
|
130
131
|
type: "host-media";
|
|
131
132
|
kind: StudioHostMediaKind;
|
|
@@ -190,6 +191,8 @@ type StudioMeetingHandle = {
|
|
|
190
191
|
participants: () => StudioParticipantTile[];
|
|
191
192
|
waitlisted: () => StudioWaitlistedGuest[];
|
|
192
193
|
inWaitingRoom: () => boolean;
|
|
194
|
+
connectionState: () => StudioConnectionState;
|
|
195
|
+
lastMeetingError: () => string | null;
|
|
193
196
|
moderatorIds: () => string[];
|
|
194
197
|
localAudioTrack: () => MediaStreamTrack | null;
|
|
195
198
|
localVideoTrack: () => MediaStreamTrack | null;
|
|
@@ -219,6 +222,9 @@ declare function studioRoomLabels(t: (key: string, values?: Record<string, strin
|
|
|
219
222
|
preview: string;
|
|
220
223
|
waiting: string;
|
|
221
224
|
waitingHost: string;
|
|
225
|
+
connecting: string;
|
|
226
|
+
connectFailed: string;
|
|
227
|
+
retry: string;
|
|
222
228
|
admit: string;
|
|
223
229
|
deny: string;
|
|
224
230
|
denied: string;
|
|
@@ -359,6 +365,7 @@ declare function studioRoomLabels(t: (key: string, values?: Record<string, strin
|
|
|
359
365
|
unpinnedToast: string;
|
|
360
366
|
pinError: string;
|
|
361
367
|
kickTitle: string;
|
|
368
|
+
kickTitleNamed: string;
|
|
362
369
|
kickDescription: string;
|
|
363
370
|
kickConfirm: string;
|
|
364
371
|
kickError: string;
|
package/dist/studio/index.js
CHANGED
|
@@ -1875,6 +1875,7 @@ function StudioGuestWaiting({
|
|
|
1875
1875
|
muted,
|
|
1876
1876
|
labels,
|
|
1877
1877
|
settingsOpen,
|
|
1878
|
+
statusLabel,
|
|
1878
1879
|
onToggleCamera,
|
|
1879
1880
|
onToggleMute,
|
|
1880
1881
|
onOpenSettings,
|
|
@@ -1906,7 +1907,7 @@ function StudioGuestWaiting({
|
|
|
1906
1907
|
reducedMotion
|
|
1907
1908
|
}
|
|
1908
1909
|
),
|
|
1909
|
-
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-center text-sm", role: "status", children: labels.waitingHost }),
|
|
1910
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-center text-sm", role: "status", children: statusLabel ?? labels.waitingHost }),
|
|
1910
1911
|
/* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs("div", { className: "flex justify-center gap-2", children: [
|
|
1911
1912
|
/* @__PURE__ */ jsx(
|
|
1912
1913
|
ControlIcon,
|
|
@@ -2679,11 +2680,15 @@ function StudioSidePanel({
|
|
|
2679
2680
|
onGrantStage,
|
|
2680
2681
|
onDenyStage,
|
|
2681
2682
|
onTakeOffAir,
|
|
2683
|
+
onKick,
|
|
2682
2684
|
guestUrl,
|
|
2683
2685
|
onShareGuestLink
|
|
2684
2686
|
}) {
|
|
2685
2687
|
const panelists = participants.filter((row) => isOnStage(row.stageStatus));
|
|
2686
2688
|
const audience = participants.filter((row) => !isOnStage(row.stageStatus));
|
|
2689
|
+
const [pendingKick, setPendingKick] = useState(
|
|
2690
|
+
null
|
|
2691
|
+
);
|
|
2687
2692
|
return /* @__PURE__ */ jsxs("aside", { className: "bg-background flex min-h-0 w-full flex-1 flex-col border-t md:w-80 md:flex-none md:border-l md:border-t-0", children: [
|
|
2688
2693
|
/* @__PURE__ */ jsxs("div", { className: "flex", children: [
|
|
2689
2694
|
/* @__PURE__ */ jsxs(
|
|
@@ -2800,23 +2805,37 @@ function StudioSidePanel({
|
|
|
2800
2805
|
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: labels.panelists }),
|
|
2801
2806
|
panelists.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: labels.noPanelists }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: panelists.map((row) => {
|
|
2802
2807
|
const userId = row.userId;
|
|
2808
|
+
const showKick = isModerator2 && !row.isSelf && Boolean(row.id);
|
|
2803
2809
|
return /* @__PURE__ */ jsxs(
|
|
2804
2810
|
"li",
|
|
2805
2811
|
{
|
|
2806
2812
|
className: "space-y-2 rounded-md border p-2",
|
|
2807
2813
|
children: [
|
|
2808
2814
|
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm", children: row.name }),
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2815
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
|
|
2816
|
+
!row.isSelf && userId ? /* @__PURE__ */ jsx(
|
|
2817
|
+
Button,
|
|
2818
|
+
{
|
|
2819
|
+
type: "button",
|
|
2820
|
+
size: "xs",
|
|
2821
|
+
variant: "outline",
|
|
2822
|
+
className: "w-full",
|
|
2823
|
+
onClick: () => onTakeOffAir(userId),
|
|
2824
|
+
children: labels.offAir
|
|
2825
|
+
}
|
|
2826
|
+
) : null,
|
|
2827
|
+
showKick ? /* @__PURE__ */ jsx(
|
|
2828
|
+
Button,
|
|
2829
|
+
{
|
|
2830
|
+
type: "button",
|
|
2831
|
+
size: "xs",
|
|
2832
|
+
variant: "ghost",
|
|
2833
|
+
className: "w-full",
|
|
2834
|
+
onClick: () => setPendingKick(row),
|
|
2835
|
+
children: labels.kick
|
|
2836
|
+
}
|
|
2837
|
+
) : null
|
|
2838
|
+
] })
|
|
2820
2839
|
]
|
|
2821
2840
|
},
|
|
2822
2841
|
row.id
|
|
@@ -2827,29 +2846,61 @@ function StudioSidePanel({
|
|
|
2827
2846
|
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: labels.audience }),
|
|
2828
2847
|
audience.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: labels.noAudience }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: audience.map((row) => {
|
|
2829
2848
|
const userId = row.userId;
|
|
2849
|
+
const showKick = isModerator2 && !row.isSelf && Boolean(row.id);
|
|
2830
2850
|
return /* @__PURE__ */ jsxs(
|
|
2831
2851
|
"li",
|
|
2832
2852
|
{
|
|
2833
2853
|
className: "space-y-2 rounded-md border p-2",
|
|
2834
2854
|
children: [
|
|
2835
2855
|
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm", children: row.name }),
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2856
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
|
|
2857
|
+
!row.isSelf && userId ? /* @__PURE__ */ jsx(
|
|
2858
|
+
Button,
|
|
2859
|
+
{
|
|
2860
|
+
type: "button",
|
|
2861
|
+
size: "xs",
|
|
2862
|
+
variant: "outline",
|
|
2863
|
+
className: "w-full",
|
|
2864
|
+
onClick: () => onGrantStage(userId),
|
|
2865
|
+
children: labels.bringOnAir
|
|
2866
|
+
}
|
|
2867
|
+
) : null,
|
|
2868
|
+
showKick ? /* @__PURE__ */ jsx(
|
|
2869
|
+
Button,
|
|
2870
|
+
{
|
|
2871
|
+
type: "button",
|
|
2872
|
+
size: "xs",
|
|
2873
|
+
variant: "ghost",
|
|
2874
|
+
className: "w-full",
|
|
2875
|
+
onClick: () => setPendingKick(row),
|
|
2876
|
+
children: labels.kick
|
|
2877
|
+
}
|
|
2878
|
+
) : null
|
|
2879
|
+
] })
|
|
2847
2880
|
]
|
|
2848
2881
|
},
|
|
2849
2882
|
row.id
|
|
2850
2883
|
);
|
|
2851
2884
|
}) })
|
|
2852
|
-
] })
|
|
2885
|
+
] }),
|
|
2886
|
+
/* @__PURE__ */ jsx(
|
|
2887
|
+
ConfirmAction,
|
|
2888
|
+
{
|
|
2889
|
+
open: pendingKick !== null,
|
|
2890
|
+
onOpenChange: (open) => {
|
|
2891
|
+
if (!open) setPendingKick(null);
|
|
2892
|
+
},
|
|
2893
|
+
triggerLabel: labels.kick,
|
|
2894
|
+
title: pendingKick ? labels.kickTitleNamed.replace("{name}", pendingKick.name) : labels.kickTitle,
|
|
2895
|
+
description: labels.kickDescription,
|
|
2896
|
+
confirmLabel: labels.kickConfirm,
|
|
2897
|
+
onConfirm: () => {
|
|
2898
|
+
const target = pendingKick;
|
|
2899
|
+
setPendingKick(null);
|
|
2900
|
+
if (target) onKick(target.id);
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
)
|
|
2853
2904
|
] })
|
|
2854
2905
|
] });
|
|
2855
2906
|
}
|
|
@@ -3490,6 +3541,7 @@ function StudioToolbar({
|
|
|
3490
3541
|
] }) });
|
|
3491
3542
|
}
|
|
3492
3543
|
var STAGE_GRANT_VERIFY_MS = 4e3;
|
|
3544
|
+
var STUDIO_CONNECT_WATCHDOG_MS = 12e3;
|
|
3493
3545
|
function StudioRoom({
|
|
3494
3546
|
session,
|
|
3495
3547
|
meeting,
|
|
@@ -3525,6 +3577,7 @@ function StudioRoom({
|
|
|
3525
3577
|
);
|
|
3526
3578
|
const [egressError, setEgressError] = useState(null);
|
|
3527
3579
|
const [shareGrant, setShareGrant] = useState("idle");
|
|
3580
|
+
const [connectTimedOut, setConnectTimedOut] = useState(false);
|
|
3528
3581
|
const [screenPermission, setScreenPermission] = useState(
|
|
3529
3582
|
() => meeting.screenSharePermission()
|
|
3530
3583
|
);
|
|
@@ -3771,6 +3824,14 @@ function StudioRoom({
|
|
|
3771
3824
|
[participants]
|
|
3772
3825
|
);
|
|
3773
3826
|
const waitlisted = useMemo(() => meeting.waitlisted(), [meeting, tick]);
|
|
3827
|
+
const connectionState = useMemo(
|
|
3828
|
+
() => meeting.connectionState(),
|
|
3829
|
+
[meeting, tick]
|
|
3830
|
+
);
|
|
3831
|
+
const lastMeetingError = useMemo(
|
|
3832
|
+
() => meeting.lastMeetingError(),
|
|
3833
|
+
[meeting, tick]
|
|
3834
|
+
);
|
|
3774
3835
|
const canManageStage = meeting.canManageStage();
|
|
3775
3836
|
const stageEnabled = meeting.stageEnabled();
|
|
3776
3837
|
const stageRequests = useMemo(
|
|
@@ -3868,6 +3929,16 @@ function StudioRoom({
|
|
|
3868
3929
|
}
|
|
3869
3930
|
}
|
|
3870
3931
|
}, [labels.knock, session.role, waitlisted]);
|
|
3932
|
+
useEffect(() => {
|
|
3933
|
+
if (session.role !== "guest" || connectionState !== "connecting") {
|
|
3934
|
+
setConnectTimedOut(false);
|
|
3935
|
+
return;
|
|
3936
|
+
}
|
|
3937
|
+
const id = window.setTimeout(() => {
|
|
3938
|
+
setConnectTimedOut(true);
|
|
3939
|
+
}, STUDIO_CONNECT_WATCHDOG_MS);
|
|
3940
|
+
return () => window.clearTimeout(id);
|
|
3941
|
+
}, [connectionState, session.role]);
|
|
3871
3942
|
useEffect(() => {
|
|
3872
3943
|
if (inWaitingRoom || screenNoticePrimed.current) return;
|
|
3873
3944
|
if (!durableScreenBlock) return;
|
|
@@ -4183,7 +4254,20 @@ function StudioRoom({
|
|
|
4183
4254
|
}
|
|
4184
4255
|
) });
|
|
4185
4256
|
}
|
|
4186
|
-
|
|
4257
|
+
const guestConnectFailed = session.role === "guest" && (connectionState === "failed" || connectionState === "connecting" && connectTimedOut);
|
|
4258
|
+
if (guestConnectFailed) {
|
|
4259
|
+
const detail = [meeting.roomState(), lastMeetingError].filter((value) => Boolean(value)).join(" \u2014 ");
|
|
4260
|
+
return /* @__PURE__ */ jsx("div", { className: "flex h-dvh flex-col items-center justify-center p-6", children: /* @__PURE__ */ jsx(
|
|
4261
|
+
EmptyState,
|
|
4262
|
+
{
|
|
4263
|
+
title: labels.connectFailed,
|
|
4264
|
+
description: detail || labels.connectFailed,
|
|
4265
|
+
actionLabel: onRejoin ? labels.retry : void 0,
|
|
4266
|
+
onAction: onRejoin
|
|
4267
|
+
}
|
|
4268
|
+
) });
|
|
4269
|
+
}
|
|
4270
|
+
if (session.role === "guest" && (connectionState === "waitlisted" || connectionState === "connecting")) {
|
|
4187
4271
|
return /* @__PURE__ */ jsx(
|
|
4188
4272
|
StudioGuestWaiting,
|
|
4189
4273
|
{
|
|
@@ -4193,6 +4277,7 @@ function StudioRoom({
|
|
|
4193
4277
|
muted,
|
|
4194
4278
|
labels,
|
|
4195
4279
|
settingsOpen,
|
|
4280
|
+
statusLabel: connectionState === "connecting" ? labels.connecting : labels.waitingHost,
|
|
4196
4281
|
onToggleCamera,
|
|
4197
4282
|
onToggleMute: () => void onToggleMute(),
|
|
4198
4283
|
onOpenSettings: () => setSettingsOpen(true),
|
|
@@ -4377,6 +4462,7 @@ function StudioRoom({
|
|
|
4377
4462
|
const tile = participants.find((row) => row.userId === userId);
|
|
4378
4463
|
if (tile) void onSetStage(tile, false);
|
|
4379
4464
|
},
|
|
4465
|
+
onKick: (id) => void onKick(id),
|
|
4380
4466
|
guestUrl: session.guestUrl,
|
|
4381
4467
|
onShareGuestLink: () => {
|
|
4382
4468
|
const url = session.guestUrl;
|
|
@@ -4718,8 +4804,8 @@ function StudioGuestLobby({
|
|
|
4718
4804
|
}
|
|
4719
4805
|
}
|
|
4720
4806
|
async function onTestSpeaker() {
|
|
4721
|
-
|
|
4722
|
-
|
|
4807
|
+
if (testingSpeaker) return;
|
|
4808
|
+
const speakerId = effective.speaker || devices.speaker[0]?.deviceId || void 0;
|
|
4723
4809
|
setTestingSpeaker(true);
|
|
4724
4810
|
try {
|
|
4725
4811
|
await playSpeakerTest(speakerId);
|
|
@@ -4787,7 +4873,7 @@ function StudioGuestLobby({
|
|
|
4787
4873
|
type: "button",
|
|
4788
4874
|
size: "sm",
|
|
4789
4875
|
variant: "outline",
|
|
4790
|
-
disabled:
|
|
4876
|
+
disabled: testingSpeaker,
|
|
4791
4877
|
onClick: () => void onTestSpeaker(),
|
|
4792
4878
|
children: deviceLabels.testSpeaker
|
|
4793
4879
|
}
|
|
@@ -5029,6 +5115,38 @@ function isStageDisabledError(error) {
|
|
|
5029
5115
|
const message = typeof row.message === "string" ? row.message : "";
|
|
5030
5116
|
return message.includes("ERR2003") || message.toLowerCase().includes("stage is disabled");
|
|
5031
5117
|
}
|
|
5118
|
+
function isStageBusyError(error) {
|
|
5119
|
+
if (!error || typeof error !== "object") return false;
|
|
5120
|
+
const row = error;
|
|
5121
|
+
if (row.name === "UnsupportedConcurrentMethodExecution") return true;
|
|
5122
|
+
if (row.code === "2006" || row.code === 2006) return true;
|
|
5123
|
+
const message = typeof row.message === "string" ? row.message : "";
|
|
5124
|
+
return message.includes("ERR2006") || message.includes("Unsupported concurrent calls") || message.includes("Action not permitted in current stage status");
|
|
5125
|
+
}
|
|
5126
|
+
function isRtkMeetingError(error) {
|
|
5127
|
+
if (!error || typeof error !== "object") return false;
|
|
5128
|
+
const row = error;
|
|
5129
|
+
const name = typeof row.name === "string" ? row.name : "";
|
|
5130
|
+
const message = typeof row.message === "string" ? row.message : "";
|
|
5131
|
+
return name === "ClientError" || name === "TransportConnectionError" || message.includes("ERR1608") || message.includes("No speaker found") || message.includes("Socket is not connected");
|
|
5132
|
+
}
|
|
5133
|
+
function meetingErrorText(error) {
|
|
5134
|
+
if (error instanceof Error && error.message.trim()) return error.message;
|
|
5135
|
+
if (error && typeof error === "object") {
|
|
5136
|
+
const message = error.message;
|
|
5137
|
+
if (typeof message === "string" && message.trim()) return message;
|
|
5138
|
+
}
|
|
5139
|
+
return "Studio failed";
|
|
5140
|
+
}
|
|
5141
|
+
async function settledDeviceList(load) {
|
|
5142
|
+
if (!load) return [];
|
|
5143
|
+
try {
|
|
5144
|
+
const [result] = await Promise.allSettled([load()]);
|
|
5145
|
+
return result?.status === "fulfilled" ? result.value : [];
|
|
5146
|
+
} catch {
|
|
5147
|
+
return [];
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5032
5150
|
function waitMs(ms) {
|
|
5033
5151
|
return new Promise((resolve) => {
|
|
5034
5152
|
setTimeout(resolve, ms);
|
|
@@ -5066,8 +5184,9 @@ function studioMeetingFromRtk(raw) {
|
|
|
5066
5184
|
const pendingPanelists = /* @__PURE__ */ new Set();
|
|
5067
5185
|
const chatSend = /* @__PURE__ */ new Map();
|
|
5068
5186
|
const nameOverrides = /* @__PURE__ */ new Map();
|
|
5069
|
-
let
|
|
5187
|
+
let joinStageInFlight = null;
|
|
5070
5188
|
let joinedStageLocally = false;
|
|
5189
|
+
let lastMeetingError = null;
|
|
5071
5190
|
const historyMessages = [];
|
|
5072
5191
|
let speakerSinkId = null;
|
|
5073
5192
|
let lastSpeakerId = typeof meeting.participants?.lastActiveSpeaker === "string" ? meeting.participants.lastActiveSpeaker : null;
|
|
@@ -5163,47 +5282,75 @@ function studioMeetingFromRtk(raw) {
|
|
|
5163
5282
|
return [...byId.values()].sort((a, b) => a.time - b.time);
|
|
5164
5283
|
}
|
|
5165
5284
|
function currentStageStatus() {
|
|
5166
|
-
return meeting.
|
|
5285
|
+
return meeting.self?.stageStatus ?? meeting.stage?.status;
|
|
5286
|
+
}
|
|
5287
|
+
function resolveConnectionState() {
|
|
5288
|
+
const self = meeting.self;
|
|
5289
|
+
const roomState = self?.roomState ?? null;
|
|
5290
|
+
if (roomState === "waitlisted") return "waitlisted";
|
|
5291
|
+
if (roomState === "joined" || self?.roomJoined === true) return "joined";
|
|
5292
|
+
if (lastMeetingError) return "failed";
|
|
5293
|
+
return "connecting";
|
|
5294
|
+
}
|
|
5295
|
+
function recordMeetingError(error) {
|
|
5296
|
+
if (!isRtkMeetingError(error)) return;
|
|
5297
|
+
lastMeetingError = meetingErrorText(error);
|
|
5298
|
+
notifyParticipants();
|
|
5299
|
+
}
|
|
5300
|
+
function onUnhandledRejection(event) {
|
|
5301
|
+
recordMeetingError(event.reason);
|
|
5302
|
+
}
|
|
5303
|
+
if (typeof window !== "undefined") {
|
|
5304
|
+
window.addEventListener("unhandledrejection", onUnhandledRejection);
|
|
5167
5305
|
}
|
|
5168
5306
|
async function republishCameraIfOn() {
|
|
5169
5307
|
if (meeting.self?.videoEnabled !== true) return;
|
|
5170
5308
|
await meeting.self.disableVideo?.();
|
|
5171
5309
|
await meeting.self.enableVideo?.();
|
|
5172
5310
|
}
|
|
5173
|
-
async function
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5311
|
+
async function runJoinStageAttempts() {
|
|
5312
|
+
let lastError;
|
|
5313
|
+
for (let attempt = 0; attempt < STAGE_JOIN_MAX_ATTEMPTS; attempt++) {
|
|
5314
|
+
if (currentStageStatus() === "ON_STAGE") {
|
|
5315
|
+
notifyStage();
|
|
5316
|
+
return;
|
|
5317
|
+
}
|
|
5318
|
+
try {
|
|
5319
|
+
await meeting.stage?.join?.();
|
|
5320
|
+
joinedStageLocally = true;
|
|
5321
|
+
await republishCameraIfOn();
|
|
5322
|
+
notifyStage();
|
|
5323
|
+
return;
|
|
5324
|
+
} catch (error) {
|
|
5325
|
+
lastError = error;
|
|
5326
|
+
if (isStageDisabledError(error)) {
|
|
5179
5327
|
notifyStage();
|
|
5180
5328
|
return;
|
|
5181
5329
|
}
|
|
5182
|
-
|
|
5183
|
-
await meeting.stage?.join?.();
|
|
5184
|
-
joinedStageLocally = true;
|
|
5185
|
-
await republishCameraIfOn();
|
|
5186
|
-
notifyStage();
|
|
5187
|
-
return;
|
|
5188
|
-
} catch (error) {
|
|
5189
|
-
if (isStageDisabledError(error)) {
|
|
5190
|
-
notifyStage();
|
|
5191
|
-
return;
|
|
5192
|
-
}
|
|
5193
|
-
console.warn("studio: stage join failed", error);
|
|
5194
|
-
if (attempt === STAGE_JOIN_MAX_ATTEMPTS - 1) {
|
|
5195
|
-
notifyStage();
|
|
5196
|
-
return;
|
|
5197
|
-
}
|
|
5330
|
+
if (isStageBusyError(error)) {
|
|
5198
5331
|
await waitMs(STAGE_JOIN_RETRY_MS);
|
|
5199
|
-
|
|
5200
|
-
notifyStage();
|
|
5201
|
-
return;
|
|
5202
|
-
}
|
|
5332
|
+
continue;
|
|
5203
5333
|
}
|
|
5334
|
+
console.warn("studio: stage join failed", error);
|
|
5335
|
+
if (attempt === STAGE_JOIN_MAX_ATTEMPTS - 1) break;
|
|
5336
|
+
await waitMs(STAGE_JOIN_RETRY_MS);
|
|
5204
5337
|
}
|
|
5338
|
+
}
|
|
5339
|
+
notifyStage();
|
|
5340
|
+
if (currentStageStatus() !== "ON_STAGE") {
|
|
5341
|
+
throw lastError instanceof Error ? lastError : new Error("stage join failed");
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5344
|
+
async function joinStageWithRetry() {
|
|
5345
|
+
if (joinStageInFlight) {
|
|
5346
|
+
await joinStageInFlight;
|
|
5347
|
+
return;
|
|
5348
|
+
}
|
|
5349
|
+
joinStageInFlight = runJoinStageAttempts();
|
|
5350
|
+
try {
|
|
5351
|
+
await joinStageInFlight;
|
|
5205
5352
|
} finally {
|
|
5206
|
-
|
|
5353
|
+
joinStageInFlight = null;
|
|
5207
5354
|
}
|
|
5208
5355
|
}
|
|
5209
5356
|
async function autoJoinStage() {
|
|
@@ -5211,18 +5358,27 @@ function studioMeetingFromRtk(raw) {
|
|
|
5211
5358
|
notifyStage();
|
|
5212
5359
|
return;
|
|
5213
5360
|
}
|
|
5214
|
-
|
|
5361
|
+
try {
|
|
5362
|
+
await joinStageWithRetry();
|
|
5363
|
+
} catch {
|
|
5364
|
+
notifyStage();
|
|
5365
|
+
}
|
|
5215
5366
|
}
|
|
5216
5367
|
async function ensureStageJoined() {
|
|
5217
5368
|
if (currentStageStatus() !== "ACCEPTED_TO_JOIN_STAGE") return;
|
|
5218
|
-
|
|
5369
|
+
try {
|
|
5370
|
+
await joinStageWithRetry();
|
|
5371
|
+
} catch {
|
|
5372
|
+
notifyStage();
|
|
5373
|
+
}
|
|
5219
5374
|
}
|
|
5220
5375
|
async function joinStageIfAllowed() {
|
|
5221
5376
|
if (meeting.self?.permissions?.stageAccess !== "ALLOWED") return;
|
|
5222
5377
|
if (currentStageStatus() === "ON_STAGE") return;
|
|
5223
5378
|
try {
|
|
5224
|
-
await
|
|
5379
|
+
await joinStageWithRetry();
|
|
5225
5380
|
} catch {
|
|
5381
|
+
notifyStage();
|
|
5226
5382
|
}
|
|
5227
5383
|
}
|
|
5228
5384
|
function queuePanelist(guest, fallbackId) {
|
|
@@ -5258,6 +5414,8 @@ function studioMeetingFromRtk(raw) {
|
|
|
5258
5414
|
listen(meeting.self, "audioUpdate", rebindMedia);
|
|
5259
5415
|
listen(meeting.self, "pinned", notifyPinned);
|
|
5260
5416
|
listen(meeting.self, "unpinned", notifyPinned);
|
|
5417
|
+
listen(meeting.self, "waitlisted", notifyParticipants);
|
|
5418
|
+
listen(meeting.self, "roomJoined", notifyParticipants);
|
|
5261
5419
|
const joined = meeting.participants?.joined;
|
|
5262
5420
|
listen(joined, "participantJoined", (payload) => {
|
|
5263
5421
|
void grantIfPending(payload);
|
|
@@ -5325,6 +5483,9 @@ function studioMeetingFromRtk(raw) {
|
|
|
5325
5483
|
await joinStageIfAllowed();
|
|
5326
5484
|
},
|
|
5327
5485
|
async leave() {
|
|
5486
|
+
if (typeof window !== "undefined") {
|
|
5487
|
+
window.removeEventListener("unhandledrejection", onUnhandledRejection);
|
|
5488
|
+
}
|
|
5328
5489
|
await meeting.leave?.();
|
|
5329
5490
|
},
|
|
5330
5491
|
async setAudioEnabled(enabled) {
|
|
@@ -5434,19 +5595,7 @@ function studioMeetingFromRtk(raw) {
|
|
|
5434
5595
|
notifyStage();
|
|
5435
5596
|
return;
|
|
5436
5597
|
}
|
|
5437
|
-
|
|
5438
|
-
if (userId) {
|
|
5439
|
-
await meeting.stage?.grantAccess?.([userId]);
|
|
5440
|
-
}
|
|
5441
|
-
try {
|
|
5442
|
-
await meeting.stage?.join?.();
|
|
5443
|
-
} catch (error) {
|
|
5444
|
-
notifyStage();
|
|
5445
|
-
throw error;
|
|
5446
|
-
}
|
|
5447
|
-
joinedStageLocally = true;
|
|
5448
|
-
await republishCameraIfOn();
|
|
5449
|
-
notifyStage();
|
|
5598
|
+
await joinStageWithRetry();
|
|
5450
5599
|
},
|
|
5451
5600
|
async leaveStage() {
|
|
5452
5601
|
await meeting.stage?.leave?.();
|
|
@@ -5454,7 +5603,7 @@ function studioMeetingFromRtk(raw) {
|
|
|
5454
5603
|
notifyStage();
|
|
5455
5604
|
},
|
|
5456
5605
|
stageStatus() {
|
|
5457
|
-
return meeting.
|
|
5606
|
+
return meeting.self?.stageStatus ?? meeting.stage?.status ?? "OFF_STAGE";
|
|
5458
5607
|
},
|
|
5459
5608
|
stageRequests() {
|
|
5460
5609
|
if (meeting.self?.permissions?.acceptStageRequests !== true) {
|
|
@@ -5566,9 +5715,13 @@ function studioMeetingFromRtk(raw) {
|
|
|
5566
5715
|
}));
|
|
5567
5716
|
},
|
|
5568
5717
|
inWaitingRoom() {
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5718
|
+
return meeting.self?.roomState === "waitlisted";
|
|
5719
|
+
},
|
|
5720
|
+
connectionState() {
|
|
5721
|
+
return resolveConnectionState();
|
|
5722
|
+
},
|
|
5723
|
+
lastMeetingError() {
|
|
5724
|
+
return lastMeetingError;
|
|
5572
5725
|
},
|
|
5573
5726
|
moderatorIds() {
|
|
5574
5727
|
const ids = [];
|
|
@@ -5688,9 +5841,9 @@ function studioMeetingFromRtk(raw) {
|
|
|
5688
5841
|
async listDevices() {
|
|
5689
5842
|
const self = meeting.self;
|
|
5690
5843
|
const [audio, video, speaker] = await Promise.all([
|
|
5691
|
-
self?.getAudioDevices?.()
|
|
5692
|
-
self?.getVideoDevices?.()
|
|
5693
|
-
self?.getSpeakerDevices?.()
|
|
5844
|
+
settledDeviceList(self?.getAudioDevices?.bind(self)),
|
|
5845
|
+
settledDeviceList(self?.getVideoDevices?.bind(self)),
|
|
5846
|
+
settledDeviceList(self?.getSpeakerDevices?.bind(self))
|
|
5694
5847
|
]);
|
|
5695
5848
|
return {
|
|
5696
5849
|
audio: asOptions(audio),
|
|
@@ -5703,6 +5856,10 @@ function studioMeetingFromRtk(raw) {
|
|
|
5703
5856
|
if (kind === "speaker") {
|
|
5704
5857
|
speakerSinkId = deviceId;
|
|
5705
5858
|
for (const el of audioEls.values()) applySink(el, speakerSinkId);
|
|
5859
|
+
const speakers = await settledDeviceList(
|
|
5860
|
+
meeting.self?.getSpeakerDevices?.bind(meeting.self)
|
|
5861
|
+
);
|
|
5862
|
+
if (speakers.length === 0) return;
|
|
5706
5863
|
}
|
|
5707
5864
|
await meeting.self?.setDevice?.({
|
|
5708
5865
|
deviceId,
|
|
@@ -6245,6 +6402,9 @@ function studioRoomLabels(t) {
|
|
|
6245
6402
|
preview: t("preview"),
|
|
6246
6403
|
waiting: t("waiting"),
|
|
6247
6404
|
waitingHost: t("waitingHost"),
|
|
6405
|
+
connecting: t("connecting"),
|
|
6406
|
+
connectFailed: t("connectFailed"),
|
|
6407
|
+
retry: t("retry"),
|
|
6248
6408
|
admit: t("admit"),
|
|
6249
6409
|
deny: t("deny"),
|
|
6250
6410
|
denied: t("denied"),
|
|
@@ -6385,6 +6545,7 @@ function studioRoomLabels(t) {
|
|
|
6385
6545
|
unpinnedToast: t("unpinnedToast"),
|
|
6386
6546
|
pinError: t("pinError"),
|
|
6387
6547
|
kickTitle: t("kickTitle"),
|
|
6548
|
+
kickTitleNamed: t("kickTitleNamed", { name: "{name}" }),
|
|
6388
6549
|
kickDescription: t("kickDescription"),
|
|
6389
6550
|
kickConfirm: t("kickConfirm"),
|
|
6390
6551
|
kickError: t("kickError"),
|