@livo-tv/sdk 1.2.1 → 1.2.2
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 +271 -59
- package/dist/studio/index.cjs.map +1 -1
- package/dist/studio/index.d.cts +4 -1
- package/dist/studio/index.d.ts +4 -1
- package/dist/studio/index.js +271 -60
- package/dist/studio/index.js.map +1 -1
- package/dist/styles.css +6 -0
- package/package.json +2 -2
package/dist/studio/index.d.cts
CHANGED
|
@@ -341,6 +341,8 @@ declare function studioRoomLabels(t: (key: string, values?: Record<string, strin
|
|
|
341
341
|
admitAllAudience: string;
|
|
342
342
|
askToGoOnAir: string;
|
|
343
343
|
stageRequested: string;
|
|
344
|
+
stageApproved: string;
|
|
345
|
+
stageDenied: string;
|
|
344
346
|
stageRequests: string;
|
|
345
347
|
people: string;
|
|
346
348
|
panelists: string;
|
|
@@ -515,5 +517,6 @@ declare function getStudioJoinStatus(token: string, apiUrl?: string, fetchFn?: t
|
|
|
515
517
|
ready: boolean;
|
|
516
518
|
streamStatus: string | null;
|
|
517
519
|
} | "ended">;
|
|
520
|
+
declare function getPublicStreamStatus(streamId: string, apiUrl?: string, fetchFn?: typeof fetch): Promise<string | null>;
|
|
518
521
|
|
|
519
|
-
export { LivoGuestStudio, LivoHostStudio, StudioApiError, type StudioControlActions, StudioControlClient, type StudioControlClientOptions, StudioControlError, StudioGuestLobby, type StudioLobbyLabels, type StudioMeetingHandle, type StudioRole, StudioRoom, type StudioRoomLabels, type StudioSession, StudioSkeleton, StudioWindowGate, StudioWindowGateContext, controlActionsFromClient, createStudioControlClient, getStudioJoinStatus, joinStudioGuest, redeemStudioHost, studioErrorCode, studioRoomLabels, usePrefersReducedMotion, useRealtimeKitMeeting, useStudioWindowGate };
|
|
522
|
+
export { LivoGuestStudio, LivoHostStudio, StudioApiError, type StudioControlActions, StudioControlClient, type StudioControlClientOptions, StudioControlError, StudioGuestLobby, type StudioLobbyLabels, type StudioMeetingHandle, type StudioRole, StudioRoom, type StudioRoomLabels, type StudioSession, StudioSkeleton, StudioWindowGate, StudioWindowGateContext, controlActionsFromClient, createStudioControlClient, getPublicStreamStatus, getStudioJoinStatus, joinStudioGuest, redeemStudioHost, studioErrorCode, studioRoomLabels, usePrefersReducedMotion, useRealtimeKitMeeting, useStudioWindowGate };
|
package/dist/studio/index.d.ts
CHANGED
|
@@ -341,6 +341,8 @@ declare function studioRoomLabels(t: (key: string, values?: Record<string, strin
|
|
|
341
341
|
admitAllAudience: string;
|
|
342
342
|
askToGoOnAir: string;
|
|
343
343
|
stageRequested: string;
|
|
344
|
+
stageApproved: string;
|
|
345
|
+
stageDenied: string;
|
|
344
346
|
stageRequests: string;
|
|
345
347
|
people: string;
|
|
346
348
|
panelists: string;
|
|
@@ -515,5 +517,6 @@ declare function getStudioJoinStatus(token: string, apiUrl?: string, fetchFn?: t
|
|
|
515
517
|
ready: boolean;
|
|
516
518
|
streamStatus: string | null;
|
|
517
519
|
} | "ended">;
|
|
520
|
+
declare function getPublicStreamStatus(streamId: string, apiUrl?: string, fetchFn?: typeof fetch): Promise<string | null>;
|
|
518
521
|
|
|
519
|
-
export { LivoGuestStudio, LivoHostStudio, StudioApiError, type StudioControlActions, StudioControlClient, type StudioControlClientOptions, StudioControlError, StudioGuestLobby, type StudioLobbyLabels, type StudioMeetingHandle, type StudioRole, StudioRoom, type StudioRoomLabels, type StudioSession, StudioSkeleton, StudioWindowGate, StudioWindowGateContext, controlActionsFromClient, createStudioControlClient, getStudioJoinStatus, joinStudioGuest, redeemStudioHost, studioErrorCode, studioRoomLabels, usePrefersReducedMotion, useRealtimeKitMeeting, useStudioWindowGate };
|
|
522
|
+
export { LivoGuestStudio, LivoHostStudio, StudioApiError, type StudioControlActions, StudioControlClient, type StudioControlClientOptions, StudioControlError, StudioGuestLobby, type StudioLobbyLabels, type StudioMeetingHandle, type StudioRole, StudioRoom, type StudioRoomLabels, type StudioSession, StudioSkeleton, StudioWindowGate, StudioWindowGateContext, controlActionsFromClient, createStudioControlClient, getPublicStreamStatus, getStudioJoinStatus, joinStudioGuest, redeemStudioHost, studioErrorCode, studioRoomLabels, usePrefersReducedMotion, useRealtimeKitMeeting, useStudioWindowGate };
|
package/dist/studio/index.js
CHANGED
|
@@ -740,6 +740,12 @@ function parseStudioJoinStatus(value) {
|
|
|
740
740
|
if (value.streamStatus !== null && streamStatus === null) return null;
|
|
741
741
|
return { ready: value.ready, streamStatus };
|
|
742
742
|
}
|
|
743
|
+
function parsePublicStreamStatus(value) {
|
|
744
|
+
if (!isRecord(value)) return null;
|
|
745
|
+
const nested = isRecord(value.stream) ? value.stream : value;
|
|
746
|
+
const status = readString(nested, "status");
|
|
747
|
+
return status;
|
|
748
|
+
}
|
|
743
749
|
function studioErrorCode(error) {
|
|
744
750
|
if (!error || typeof error !== "object") return null;
|
|
745
751
|
const body = "body" in error ? error.body : null;
|
|
@@ -747,6 +753,90 @@ function studioErrorCode(error) {
|
|
|
747
753
|
return body.error;
|
|
748
754
|
}
|
|
749
755
|
|
|
756
|
+
// src/studio/lib/studio-sounds.ts
|
|
757
|
+
var CHAT_DEBOUNCE_MS = 400;
|
|
758
|
+
var CHAT_HZ = 880;
|
|
759
|
+
var CHAT_MS = 80;
|
|
760
|
+
var REQUEST_LOW_HZ = 660;
|
|
761
|
+
var REQUEST_HIGH_HZ = 880;
|
|
762
|
+
var REQUEST_NOTE_MS = 80;
|
|
763
|
+
var GAIN = 0.1;
|
|
764
|
+
var ctx = null;
|
|
765
|
+
var lastChatAt = 0;
|
|
766
|
+
function audioContextCtor() {
|
|
767
|
+
if (typeof window === "undefined") return null;
|
|
768
|
+
return window.AudioContext || window.webkitAudioContext || null;
|
|
769
|
+
}
|
|
770
|
+
function prefersReducedMotion() {
|
|
771
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
775
|
+
}
|
|
776
|
+
function ensureContext() {
|
|
777
|
+
const AudioCtx = audioContextCtor();
|
|
778
|
+
if (!AudioCtx) return null;
|
|
779
|
+
if (!ctx || ctx.state === "closed") {
|
|
780
|
+
ctx = new AudioCtx();
|
|
781
|
+
}
|
|
782
|
+
return ctx;
|
|
783
|
+
}
|
|
784
|
+
function unlockStudioSounds() {
|
|
785
|
+
const context = ensureContext();
|
|
786
|
+
if (!context) return;
|
|
787
|
+
void context.resume();
|
|
788
|
+
}
|
|
789
|
+
function playTone(context, frequency, startAt, durationSec) {
|
|
790
|
+
const oscillator = context.createOscillator();
|
|
791
|
+
const gain = context.createGain();
|
|
792
|
+
oscillator.type = "sine";
|
|
793
|
+
oscillator.frequency.value = frequency;
|
|
794
|
+
gain.gain.setValueAtTime(GAIN, startAt);
|
|
795
|
+
gain.gain.exponentialRampToValueAtTime(1e-3, startAt + durationSec);
|
|
796
|
+
oscillator.connect(gain);
|
|
797
|
+
gain.connect(context.destination);
|
|
798
|
+
oscillator.start(startAt);
|
|
799
|
+
oscillator.stop(startAt + durationSec);
|
|
800
|
+
}
|
|
801
|
+
function playNow(context, kind) {
|
|
802
|
+
const now = context.currentTime;
|
|
803
|
+
if (kind === "chat") {
|
|
804
|
+
playTone(context, CHAT_HZ, now, CHAT_MS / 1e3);
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
playTone(context, REQUEST_LOW_HZ, now, REQUEST_NOTE_MS / 1e3);
|
|
808
|
+
playTone(
|
|
809
|
+
context,
|
|
810
|
+
REQUEST_HIGH_HZ,
|
|
811
|
+
now + REQUEST_NOTE_MS / 1e3,
|
|
812
|
+
REQUEST_NOTE_MS / 1e3
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
function playStudioSound(kind) {
|
|
816
|
+
if (prefersReducedMotion()) return;
|
|
817
|
+
if (kind === "chat") {
|
|
818
|
+
const at = Date.now();
|
|
819
|
+
if (at - lastChatAt < CHAT_DEBOUNCE_MS) return;
|
|
820
|
+
lastChatAt = at;
|
|
821
|
+
}
|
|
822
|
+
const context = ensureContext();
|
|
823
|
+
if (!context) return;
|
|
824
|
+
const run = () => {
|
|
825
|
+
if (context.state !== "running") return;
|
|
826
|
+
playNow(context, kind);
|
|
827
|
+
};
|
|
828
|
+
if (context.state === "running") {
|
|
829
|
+
run();
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
void context.resume().then(run, () => void 0);
|
|
833
|
+
}
|
|
834
|
+
if (typeof document !== "undefined") {
|
|
835
|
+
document.addEventListener("pointerdown", unlockStudioSounds, {
|
|
836
|
+
passive: true
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
|
|
750
840
|
// src/studio/lib/bridge.ts
|
|
751
841
|
function postLivoEvent(type, payload) {
|
|
752
842
|
if (typeof window === "undefined") return;
|
|
@@ -1160,9 +1250,9 @@ function AudioWaveform({
|
|
|
1160
1250
|
if (!canvas || !track || reducedMotion) return;
|
|
1161
1251
|
const AudioCtx = window.AudioContext || window.webkitAudioContext;
|
|
1162
1252
|
if (!AudioCtx) return;
|
|
1163
|
-
const
|
|
1164
|
-
const source =
|
|
1165
|
-
const analyser =
|
|
1253
|
+
const ctx2 = new AudioCtx();
|
|
1254
|
+
const source = ctx2.createMediaStreamSource(new MediaStream([track]));
|
|
1255
|
+
const analyser = ctx2.createAnalyser();
|
|
1166
1256
|
analyser.fftSize = 64;
|
|
1167
1257
|
source.connect(analyser);
|
|
1168
1258
|
const data = new Uint8Array(analyser.frequencyBinCount);
|
|
@@ -1191,7 +1281,7 @@ function AudioWaveform({
|
|
|
1191
1281
|
frame = requestAnimationFrame(draw);
|
|
1192
1282
|
return () => {
|
|
1193
1283
|
cancelAnimationFrame(frame);
|
|
1194
|
-
void
|
|
1284
|
+
void ctx2.close();
|
|
1195
1285
|
};
|
|
1196
1286
|
}, [track, reducedMotion]);
|
|
1197
1287
|
if (reducedMotion) {
|
|
@@ -1477,10 +1567,10 @@ async function playSpeakerTest(deviceId) {
|
|
|
1477
1567
|
if (!AudioCtx) {
|
|
1478
1568
|
throw new Error("AudioContext unavailable");
|
|
1479
1569
|
}
|
|
1480
|
-
const
|
|
1481
|
-
void
|
|
1482
|
-
const oscillator =
|
|
1483
|
-
const gain =
|
|
1570
|
+
const ctx2 = new AudioCtx();
|
|
1571
|
+
void ctx2.resume();
|
|
1572
|
+
const oscillator = ctx2.createOscillator();
|
|
1573
|
+
const gain = ctx2.createGain();
|
|
1484
1574
|
oscillator.type = "sine";
|
|
1485
1575
|
oscillator.frequency.value = TEST_HZ;
|
|
1486
1576
|
gain.gain.value = 0.18;
|
|
@@ -1490,7 +1580,7 @@ async function playSpeakerTest(deviceId) {
|
|
|
1490
1580
|
const probe = document.createElement("audio");
|
|
1491
1581
|
const canRoute = Boolean(deviceId) && typeof probe.setSinkId === "function";
|
|
1492
1582
|
if (canRoute && deviceId) {
|
|
1493
|
-
const destination =
|
|
1583
|
+
const destination = ctx2.createMediaStreamDestination();
|
|
1494
1584
|
gain.connect(destination);
|
|
1495
1585
|
audio = probe;
|
|
1496
1586
|
audio.autoplay = true;
|
|
@@ -1501,13 +1591,13 @@ async function playSpeakerTest(deviceId) {
|
|
|
1501
1591
|
await audio.play();
|
|
1502
1592
|
} catch {
|
|
1503
1593
|
gain.disconnect();
|
|
1504
|
-
gain.connect(
|
|
1594
|
+
gain.connect(ctx2.destination);
|
|
1505
1595
|
audio.srcObject = null;
|
|
1506
1596
|
audio.remove();
|
|
1507
1597
|
audio = null;
|
|
1508
1598
|
}
|
|
1509
1599
|
} else {
|
|
1510
|
-
gain.connect(
|
|
1600
|
+
gain.connect(ctx2.destination);
|
|
1511
1601
|
}
|
|
1512
1602
|
oscillator.start();
|
|
1513
1603
|
await new Promise((resolve) => {
|
|
@@ -1519,7 +1609,7 @@ async function playSpeakerTest(deviceId) {
|
|
|
1519
1609
|
audio.srcObject = null;
|
|
1520
1610
|
audio.remove();
|
|
1521
1611
|
}
|
|
1522
|
-
await
|
|
1612
|
+
await ctx2.close();
|
|
1523
1613
|
}
|
|
1524
1614
|
}
|
|
1525
1615
|
function StudioDeviceSelectGroup({
|
|
@@ -1754,6 +1844,7 @@ function SettingsCameraPreview({
|
|
|
1754
1844
|
el.srcObject = null;
|
|
1755
1845
|
return;
|
|
1756
1846
|
}
|
|
1847
|
+
el.setAttribute("webkit-playsinline", "");
|
|
1757
1848
|
el.srcObject = new MediaStream([track]);
|
|
1758
1849
|
void el.play().catch(() => void 0);
|
|
1759
1850
|
return () => {
|
|
@@ -1773,7 +1864,7 @@ function SettingsCameraPreview({
|
|
|
1773
1864
|
autoPlay: true,
|
|
1774
1865
|
muted: true,
|
|
1775
1866
|
playsInline: true,
|
|
1776
|
-
className: "absolute inset-0 size-full -scale-x-100 object-cover"
|
|
1867
|
+
className: "livo-studio-inline-video absolute inset-0 size-full -scale-x-100 object-cover"
|
|
1777
1868
|
}
|
|
1778
1869
|
) });
|
|
1779
1870
|
}
|
|
@@ -1872,6 +1963,7 @@ function WaitingCameraPreview({
|
|
|
1872
1963
|
el.srcObject = null;
|
|
1873
1964
|
return;
|
|
1874
1965
|
}
|
|
1966
|
+
el.setAttribute("webkit-playsinline", "");
|
|
1875
1967
|
el.srcObject = new MediaStream([track]);
|
|
1876
1968
|
void el.play().catch(() => void 0);
|
|
1877
1969
|
return () => {
|
|
@@ -1891,7 +1983,7 @@ function WaitingCameraPreview({
|
|
|
1891
1983
|
autoPlay: true,
|
|
1892
1984
|
muted: true,
|
|
1893
1985
|
playsInline: true,
|
|
1894
|
-
className: "absolute inset-0 size-full -scale-x-100 object-cover"
|
|
1986
|
+
className: "livo-studio-inline-video absolute inset-0 size-full -scale-x-100 object-cover"
|
|
1895
1987
|
}
|
|
1896
1988
|
) });
|
|
1897
1989
|
}
|
|
@@ -2078,6 +2170,7 @@ function PipTrackVideo({
|
|
|
2078
2170
|
el.srcObject = null;
|
|
2079
2171
|
return;
|
|
2080
2172
|
}
|
|
2173
|
+
el.setAttribute("webkit-playsinline", "");
|
|
2081
2174
|
el.srcObject = new MediaStream([track]);
|
|
2082
2175
|
void el.play().catch(() => void 0);
|
|
2083
2176
|
return () => {
|
|
@@ -2092,7 +2185,7 @@ function PipTrackVideo({
|
|
|
2092
2185
|
playsInline: true,
|
|
2093
2186
|
muted: true,
|
|
2094
2187
|
className: cn(
|
|
2095
|
-
"absolute inset-0 size-full",
|
|
2188
|
+
"livo-studio-inline-video absolute inset-0 size-full",
|
|
2096
2189
|
fit === "contain" ? "object-contain" : "object-cover"
|
|
2097
2190
|
)
|
|
2098
2191
|
}
|
|
@@ -2188,7 +2281,7 @@ function BubbleGroup({ className, ...props }) {
|
|
|
2188
2281
|
);
|
|
2189
2282
|
}
|
|
2190
2283
|
var bubbleVariants = cva(
|
|
2191
|
-
"group/bubble relative flex w-
|
|
2284
|
+
"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",
|
|
2192
2285
|
{
|
|
2193
2286
|
variants: {
|
|
2194
2287
|
variant: {
|
|
@@ -2233,7 +2326,7 @@ function BubbleContent({
|
|
|
2233
2326
|
props: mergeProps(
|
|
2234
2327
|
{
|
|
2235
2328
|
className: cn(
|
|
2236
|
-
"w-
|
|
2329
|
+
"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",
|
|
2237
2330
|
className
|
|
2238
2331
|
)
|
|
2239
2332
|
},
|
|
@@ -2414,17 +2507,24 @@ function StudioChatPanel({
|
|
|
2414
2507
|
}
|
|
2415
2508
|
),
|
|
2416
2509
|
groups.map((group) => {
|
|
2417
|
-
const bubbles = group.rows.map((row) => /* @__PURE__ */ jsxs(
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2510
|
+
const bubbles = group.rows.map((row) => /* @__PURE__ */ jsxs(
|
|
2511
|
+
"div",
|
|
2512
|
+
{
|
|
2513
|
+
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",
|
|
2514
|
+
children: [
|
|
2515
|
+
/* @__PURE__ */ jsx(
|
|
2516
|
+
Bubble,
|
|
2517
|
+
{
|
|
2518
|
+
align: group.mine ? "end" : "start",
|
|
2519
|
+
variant: group.mine ? "default" : "secondary",
|
|
2520
|
+
children: /* @__PURE__ */ jsx(BubbleContent, { children: row.text })
|
|
2521
|
+
}
|
|
2522
|
+
),
|
|
2523
|
+
messageActions(row, group.mine)
|
|
2524
|
+
]
|
|
2525
|
+
},
|
|
2526
|
+
row.id
|
|
2527
|
+
));
|
|
2428
2528
|
if (group.mine) {
|
|
2429
2529
|
return /* @__PURE__ */ jsx(BubbleGroup, { className: "items-end", children: bubbles }, group.rows[0].id);
|
|
2430
2530
|
}
|
|
@@ -3036,7 +3136,9 @@ function StudioTileVideo({
|
|
|
3036
3136
|
}) {
|
|
3037
3137
|
const ref = useRef(null);
|
|
3038
3138
|
useEffect(() => {
|
|
3039
|
-
|
|
3139
|
+
const el = ref.current;
|
|
3140
|
+
el?.setAttribute("webkit-playsinline", "");
|
|
3141
|
+
meeting.attachVideo(participantId, el, kind);
|
|
3040
3142
|
return () => meeting.attachVideo(participantId, null, kind);
|
|
3041
3143
|
}, [kind, meeting, participantId]);
|
|
3042
3144
|
return /* @__PURE__ */ jsx(
|
|
@@ -3049,7 +3151,7 @@ function StudioTileVideo({
|
|
|
3049
3151
|
disablePictureInPicture: true,
|
|
3050
3152
|
controlsList: "nodownload noplaybackrate noremoteplayback",
|
|
3051
3153
|
className: cn(
|
|
3052
|
-
"absolute inset-0 size-full",
|
|
3154
|
+
"livo-studio-tile-video absolute inset-0 size-full",
|
|
3053
3155
|
"pointer-events-none",
|
|
3054
3156
|
contain ? "object-contain" : "object-cover",
|
|
3055
3157
|
mirror && "-scale-x-100"
|
|
@@ -3431,6 +3533,12 @@ function StudioRoom({
|
|
|
3431
3533
|
const seenWaitlisted = useRef(/* @__PURE__ */ new Set());
|
|
3432
3534
|
const knockPrimed = useRef(false);
|
|
3433
3535
|
const peopleAutoOpened = useRef(false);
|
|
3536
|
+
const seenChatIds = useRef(/* @__PURE__ */ new Set());
|
|
3537
|
+
const chatSoundPrimed = useRef(false);
|
|
3538
|
+
const seenStageRequests = useRef(/* @__PURE__ */ new Set());
|
|
3539
|
+
const stageRequestPrimed = useRef(false);
|
|
3540
|
+
const awaitingStageDecision = useRef(false);
|
|
3541
|
+
const prevStageStatus = useRef(null);
|
|
3434
3542
|
const grantCheckTimers = useRef([]);
|
|
3435
3543
|
const screenNoticePrimed = useRef(false);
|
|
3436
3544
|
const lastSelfMedia = useRef({
|
|
@@ -3459,6 +3567,9 @@ function StudioRoom({
|
|
|
3459
3567
|
const rejected = isStudioRejected(roomState);
|
|
3460
3568
|
useBeforeUnload(active && !sessionOver);
|
|
3461
3569
|
useWakeLock(active && !sessionOver);
|
|
3570
|
+
useEffect(() => {
|
|
3571
|
+
unlockStudioSounds();
|
|
3572
|
+
}, []);
|
|
3462
3573
|
useEffect(() => {
|
|
3463
3574
|
if (onAir) {
|
|
3464
3575
|
setPublishing(false);
|
|
@@ -3684,9 +3795,57 @@ function StudioRoom({
|
|
|
3684
3795
|
const incoming = chatMessages.filter((row) => row.userId !== selfUserId);
|
|
3685
3796
|
const unread = sideTab === "chat" ? 0 : Math.max(0, incoming.length - seenIncoming);
|
|
3686
3797
|
const peopleBadge = waitlisted.length + stageRequests.length;
|
|
3798
|
+
const selfStageStatus = self?.stageStatus ?? meeting.stageStatus();
|
|
3687
3799
|
useEffect(() => {
|
|
3688
3800
|
if (sideTab === "chat") setSeenIncoming(incoming.length);
|
|
3689
3801
|
}, [incoming.length, sideTab]);
|
|
3802
|
+
useEffect(() => {
|
|
3803
|
+
if (!chatSoundPrimed.current) {
|
|
3804
|
+
for (const row of incoming) seenChatIds.current.add(row.id);
|
|
3805
|
+
chatSoundPrimed.current = true;
|
|
3806
|
+
return;
|
|
3807
|
+
}
|
|
3808
|
+
for (const row of incoming) {
|
|
3809
|
+
if (seenChatIds.current.has(row.id)) continue;
|
|
3810
|
+
seenChatIds.current.add(row.id);
|
|
3811
|
+
playStudioSound("chat");
|
|
3812
|
+
}
|
|
3813
|
+
}, [incoming]);
|
|
3814
|
+
useEffect(() => {
|
|
3815
|
+
if (session.role !== "moderator") return;
|
|
3816
|
+
if (!stageRequestPrimed.current) {
|
|
3817
|
+
for (const row of stageRequests) {
|
|
3818
|
+
seenStageRequests.current.add(row.userId);
|
|
3819
|
+
}
|
|
3820
|
+
stageRequestPrimed.current = true;
|
|
3821
|
+
return;
|
|
3822
|
+
}
|
|
3823
|
+
for (const row of stageRequests) {
|
|
3824
|
+
if (seenStageRequests.current.has(row.userId)) continue;
|
|
3825
|
+
seenStageRequests.current.add(row.userId);
|
|
3826
|
+
playStudioSound("request");
|
|
3827
|
+
}
|
|
3828
|
+
}, [session.role, stageRequests]);
|
|
3829
|
+
useEffect(() => {
|
|
3830
|
+
const next = selfStageStatus;
|
|
3831
|
+
const prev = prevStageStatus.current;
|
|
3832
|
+
prevStageStatus.current = next;
|
|
3833
|
+
if (session.role !== "guest") return;
|
|
3834
|
+
if (next === "REQUESTED_TO_JOIN_STAGE") {
|
|
3835
|
+
awaitingStageDecision.current = true;
|
|
3836
|
+
return;
|
|
3837
|
+
}
|
|
3838
|
+
if (!awaitingStageDecision.current) return;
|
|
3839
|
+
if (next === "ACCEPTED_TO_JOIN_STAGE" || next === "ON_STAGE") {
|
|
3840
|
+
awaitingStageDecision.current = false;
|
|
3841
|
+
toast.success(labels.stageApproved);
|
|
3842
|
+
return;
|
|
3843
|
+
}
|
|
3844
|
+
if (next === "OFF_STAGE" && prev === "REQUESTED_TO_JOIN_STAGE") {
|
|
3845
|
+
awaitingStageDecision.current = false;
|
|
3846
|
+
toast.message(labels.stageDenied);
|
|
3847
|
+
}
|
|
3848
|
+
}, [labels.stageApproved, labels.stageDenied, selfStageStatus, session.role]);
|
|
3690
3849
|
useEffect(() => {
|
|
3691
3850
|
if (session.role === "moderator" && waitlisted.length > 0 && !peopleAutoOpened.current) {
|
|
3692
3851
|
peopleAutoOpened.current = true;
|
|
@@ -3703,7 +3862,10 @@ function StudioRoom({
|
|
|
3703
3862
|
if (seenWaitlisted.current.has(guest.id)) continue;
|
|
3704
3863
|
seenWaitlisted.current.add(guest.id);
|
|
3705
3864
|
toast.message(labels.knock.replace("{name}", guest.name));
|
|
3706
|
-
if (session.role === "moderator")
|
|
3865
|
+
if (session.role === "moderator") {
|
|
3866
|
+
playStudioSound("request");
|
|
3867
|
+
setSideTab("people");
|
|
3868
|
+
}
|
|
3707
3869
|
}
|
|
3708
3870
|
}, [labels.knock, session.role, waitlisted]);
|
|
3709
3871
|
useEffect(() => {
|
|
@@ -3740,6 +3902,7 @@ function StudioRoom({
|
|
|
3740
3902
|
}
|
|
3741
3903
|
if (message.type === "share-request") {
|
|
3742
3904
|
if (session.role !== "moderator") return;
|
|
3905
|
+
playStudioSound("request");
|
|
3743
3906
|
toast.message(labels.shareRequestFrom.replace("{name}", message.name), {
|
|
3744
3907
|
id: `share-request:${message.participantId}`,
|
|
3745
3908
|
action: {
|
|
@@ -4115,10 +4278,13 @@ function StudioRoom({
|
|
|
4115
4278
|
size: "icon-sm",
|
|
4116
4279
|
"aria-label": labels.share,
|
|
4117
4280
|
onClick: () => {
|
|
4118
|
-
void
|
|
4119
|
-
playerWatchUrl(session.stream.id, playerOrigin)
|
|
4120
|
-
|
|
4121
|
-
|
|
4281
|
+
void shareOrCopyLink({
|
|
4282
|
+
url: playerWatchUrl(session.stream.id, playerOrigin),
|
|
4283
|
+
title: session.stream.title
|
|
4284
|
+
}).then((result) => {
|
|
4285
|
+
if (result === "copied") toast.success(labels.shareCopied);
|
|
4286
|
+
if (result === "failed") toast.error(labels.shareFailed);
|
|
4287
|
+
});
|
|
4122
4288
|
},
|
|
4123
4289
|
children: /* @__PURE__ */ jsx(Share2, {})
|
|
4124
4290
|
}
|
|
@@ -4451,6 +4617,7 @@ function StudioDevicePreflight({
|
|
|
4451
4617
|
setMicOk(Boolean(audio));
|
|
4452
4618
|
setAudioTrack(audio);
|
|
4453
4619
|
if (videoRef.current) {
|
|
4620
|
+
videoRef.current.setAttribute("webkit-playsinline", "");
|
|
4454
4621
|
videoRef.current.srcObject = video ? next : null;
|
|
4455
4622
|
}
|
|
4456
4623
|
bindEnded(next, gen);
|
|
@@ -4480,7 +4647,7 @@ function StudioDevicePreflight({
|
|
|
4480
4647
|
autoPlay: true,
|
|
4481
4648
|
muted: true,
|
|
4482
4649
|
playsInline: true,
|
|
4483
|
-
className: "bg-muted aspect-video w-full rounded-xl object-cover"
|
|
4650
|
+
className: "livo-studio-inline-video bg-muted aspect-video w-full rounded-xl object-cover"
|
|
4484
4651
|
}
|
|
4485
4652
|
) : /* @__PURE__ */ jsx("div", { className: "bg-muted flex aspect-video w-full items-center justify-center rounded-xl", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: cameraOffLabel }) }),
|
|
4486
4653
|
/* @__PURE__ */ jsx(
|
|
@@ -4741,44 +4908,42 @@ function screenShareTrack(meeting, participantId) {
|
|
|
4741
4908
|
}
|
|
4742
4909
|
return findParticipant(meeting, participantId)?.screenShareTracks?.video ?? null;
|
|
4743
4910
|
}
|
|
4744
|
-
var
|
|
4745
|
-
function
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4911
|
+
var pendingMediaPlay = /* @__PURE__ */ new Set();
|
|
4912
|
+
function tryPlay(el) {
|
|
4913
|
+
if (typeof el.play !== "function") return;
|
|
4914
|
+
void el.play().then(
|
|
4915
|
+
() => {
|
|
4916
|
+
pendingMediaPlay.delete(el);
|
|
4917
|
+
},
|
|
4918
|
+
() => {
|
|
4919
|
+
pendingMediaPlay.add(el);
|
|
4920
|
+
}
|
|
4921
|
+
);
|
|
4922
|
+
}
|
|
4923
|
+
function replayPendingMedia() {
|
|
4924
|
+
for (const el of pendingMediaPlay) {
|
|
4925
|
+
tryPlay(el);
|
|
4753
4926
|
}
|
|
4754
4927
|
}
|
|
4755
4928
|
if (typeof document !== "undefined") {
|
|
4756
|
-
document.addEventListener("pointerdown",
|
|
4929
|
+
document.addEventListener("pointerdown", replayPendingMedia, {
|
|
4757
4930
|
passive: true
|
|
4758
4931
|
});
|
|
4759
4932
|
}
|
|
4760
4933
|
function bindTrackElement(el, track, kind) {
|
|
4761
4934
|
if (!track) {
|
|
4762
4935
|
el.srcObject = null;
|
|
4763
|
-
|
|
4936
|
+
pendingMediaPlay.delete(el);
|
|
4764
4937
|
return;
|
|
4765
4938
|
}
|
|
4766
4939
|
const current = el.srcObject;
|
|
4767
4940
|
const currentTrack = current instanceof MediaStream ? kind === "video" ? current.getVideoTracks()[0] : current.getAudioTracks()[0] : void 0;
|
|
4768
4941
|
if (currentTrack?.id === track.id) {
|
|
4942
|
+
if (el.paused) tryPlay(el);
|
|
4769
4943
|
return;
|
|
4770
4944
|
}
|
|
4771
4945
|
el.srcObject = new MediaStream([track]);
|
|
4772
|
-
|
|
4773
|
-
void el.play().then(
|
|
4774
|
-
() => {
|
|
4775
|
-
if (el instanceof HTMLAudioElement) pendingAudioPlay.delete(el);
|
|
4776
|
-
},
|
|
4777
|
-
() => {
|
|
4778
|
-
if (el instanceof HTMLAudioElement) pendingAudioPlay.add(el);
|
|
4779
|
-
}
|
|
4780
|
-
);
|
|
4781
|
-
}
|
|
4946
|
+
tryPlay(el);
|
|
4782
4947
|
}
|
|
4783
4948
|
function roomLeftState(payload, fallback) {
|
|
4784
4949
|
if (typeof payload === "string" && payload) return payload;
|
|
@@ -5742,6 +5907,21 @@ async function getStudioJoinStatus(token, apiUrl = defaultApiUrl(), fetchFn = fe
|
|
|
5742
5907
|
throw error;
|
|
5743
5908
|
}
|
|
5744
5909
|
}
|
|
5910
|
+
async function getPublicStreamStatus(streamId, apiUrl = defaultApiUrl(), fetchFn = fetch) {
|
|
5911
|
+
try {
|
|
5912
|
+
const body = await publicJson(
|
|
5913
|
+
`${apiUrl.replace(/\/$/, "")}/public/streams/${encodeURIComponent(streamId)}`,
|
|
5914
|
+
{},
|
|
5915
|
+
fetchFn
|
|
5916
|
+
);
|
|
5917
|
+
return parsePublicStreamStatus(body);
|
|
5918
|
+
} catch (error) {
|
|
5919
|
+
if (error instanceof StudioApiError && (error.status === 404 || error.status === 410)) {
|
|
5920
|
+
return "ended";
|
|
5921
|
+
}
|
|
5922
|
+
return null;
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5745
5925
|
var POLL_INTERVAL_MS = 5e3;
|
|
5746
5926
|
function LivoHostStudio({
|
|
5747
5927
|
token,
|
|
@@ -5836,6 +6016,7 @@ function LivoHostStudio({
|
|
|
5836
6016
|
);
|
|
5837
6017
|
}
|
|
5838
6018
|
var POLL_INTERVAL_MS2 = 5e3;
|
|
6019
|
+
var PREVIEW_POLL_INTERVAL_MS = 2e3;
|
|
5839
6020
|
function LivoGuestStudio({
|
|
5840
6021
|
token,
|
|
5841
6022
|
apiUrl,
|
|
@@ -5847,6 +6028,7 @@ function LivoGuestStudio({
|
|
|
5847
6028
|
}) {
|
|
5848
6029
|
const reducedMotion = usePrefersReducedMotion();
|
|
5849
6030
|
const [session, setSession] = useState(null);
|
|
6031
|
+
const [status, setStatus] = useState(null);
|
|
5850
6032
|
const [error, setError] = useState(null);
|
|
5851
6033
|
const [displayName, setDisplayName] = useState("");
|
|
5852
6034
|
const [guestId, setGuestId] = useState("");
|
|
@@ -5896,6 +6078,7 @@ function LivoGuestStudio({
|
|
|
5896
6078
|
);
|
|
5897
6079
|
setWaitingForHost(false);
|
|
5898
6080
|
setRetryAfterMs(null);
|
|
6081
|
+
setStatus(null);
|
|
5899
6082
|
setSession(next);
|
|
5900
6083
|
} catch (err) {
|
|
5901
6084
|
const code = studioErrorCode(err);
|
|
@@ -5943,6 +6126,31 @@ function LivoGuestStudio({
|
|
|
5943
6126
|
window.clearInterval(id);
|
|
5944
6127
|
};
|
|
5945
6128
|
}, [apiUrl, join, lobbyLabels.endedTitle, token, waitingForHost]);
|
|
6129
|
+
useEffect(() => {
|
|
6130
|
+
if (!session) return;
|
|
6131
|
+
const current = status ?? session.stream.status;
|
|
6132
|
+
if (current === "ended" || current === "error") return;
|
|
6133
|
+
let cancelled = false;
|
|
6134
|
+
const poll = async () => {
|
|
6135
|
+
const next = await getPublicStreamStatus(session.stream.id, apiUrl);
|
|
6136
|
+
if (!cancelled && next) setStatus(next);
|
|
6137
|
+
};
|
|
6138
|
+
void poll();
|
|
6139
|
+
const intervalMs = current === "public" ? POLL_INTERVAL_MS2 : PREVIEW_POLL_INTERVAL_MS;
|
|
6140
|
+
const id = window.setInterval(() => void poll(), intervalMs);
|
|
6141
|
+
return () => {
|
|
6142
|
+
cancelled = true;
|
|
6143
|
+
window.clearInterval(id);
|
|
6144
|
+
};
|
|
6145
|
+
}, [apiUrl, session, status]);
|
|
6146
|
+
const liveSession = useMemo(() => {
|
|
6147
|
+
if (!session) return null;
|
|
6148
|
+
if (!status) return session;
|
|
6149
|
+
return {
|
|
6150
|
+
...session,
|
|
6151
|
+
stream: { ...session.stream, status }
|
|
6152
|
+
};
|
|
6153
|
+
}, [session, status]);
|
|
5946
6154
|
useEffect(() => {
|
|
5947
6155
|
if (retryAfterMs == null) return;
|
|
5948
6156
|
const id = window.setTimeout(() => {
|
|
@@ -5955,7 +6163,7 @@ function LivoGuestStudio({
|
|
|
5955
6163
|
if (meetingError) {
|
|
5956
6164
|
return /* @__PURE__ */ jsx("p", { className: "text-destructive p-6", role: "alert", children: meetingError });
|
|
5957
6165
|
}
|
|
5958
|
-
if (!
|
|
6166
|
+
if (!liveSession || !meeting) {
|
|
5959
6167
|
return /* @__PURE__ */ jsx(
|
|
5960
6168
|
StudioGuestLobby,
|
|
5961
6169
|
{
|
|
@@ -5980,7 +6188,7 @@ function LivoGuestStudio({
|
|
|
5980
6188
|
return /* @__PURE__ */ jsx(
|
|
5981
6189
|
StudioRoom,
|
|
5982
6190
|
{
|
|
5983
|
-
session,
|
|
6191
|
+
session: liveSession,
|
|
5984
6192
|
meeting,
|
|
5985
6193
|
control: {
|
|
5986
6194
|
livestream: async () => "starting",
|
|
@@ -5989,6 +6197,7 @@ function LivoGuestStudio({
|
|
|
5989
6197
|
},
|
|
5990
6198
|
onRejoin: () => {
|
|
5991
6199
|
setSession(null);
|
|
6200
|
+
setStatus(null);
|
|
5992
6201
|
setSessionGen((n) => n + 1);
|
|
5993
6202
|
setJoining(false);
|
|
5994
6203
|
setWaitingForHost(false);
|
|
@@ -6135,6 +6344,8 @@ function studioRoomLabels(t) {
|
|
|
6135
6344
|
admitAllAudience: t("admitAllAudience"),
|
|
6136
6345
|
askToGoOnAir: t("askToGoOnAir"),
|
|
6137
6346
|
stageRequested: t("stageRequested"),
|
|
6347
|
+
stageApproved: t("stageApproved"),
|
|
6348
|
+
stageDenied: t("stageDenied"),
|
|
6138
6349
|
stageRequests: t("stageRequests"),
|
|
6139
6350
|
people: t("people"),
|
|
6140
6351
|
panelists: t("panelists"),
|
|
@@ -6172,6 +6383,6 @@ function studioRoomLabels(t) {
|
|
|
6172
6383
|
};
|
|
6173
6384
|
}
|
|
6174
6385
|
|
|
6175
|
-
export { LivoGuestStudio, LivoHostStudio, StudioApiError, StudioControlClient, StudioControlError, StudioGuestLobby, StudioRoom, StudioSkeleton, StudioWindowGate, StudioWindowGateContext, applyEmbedTheme, controlActionsFromClient, createStudioControlClient, decodeEmbedTheme, getStudioJoinStatus, joinStudioGuest, redeemStudioHost, studioErrorCode, studioRoomLabels, usePrefersReducedMotion, useRealtimeKitMeeting, useStudioWindowGate };
|
|
6386
|
+
export { LivoGuestStudio, LivoHostStudio, StudioApiError, StudioControlClient, StudioControlError, StudioGuestLobby, StudioRoom, StudioSkeleton, StudioWindowGate, StudioWindowGateContext, applyEmbedTheme, controlActionsFromClient, createStudioControlClient, decodeEmbedTheme, getPublicStreamStatus, getStudioJoinStatus, joinStudioGuest, redeemStudioHost, studioErrorCode, studioRoomLabels, usePrefersReducedMotion, useRealtimeKitMeeting, useStudioWindowGate };
|
|
6176
6387
|
//# sourceMappingURL=index.js.map
|
|
6177
6388
|
//# sourceMappingURL=index.js.map
|