@livo-tv/sdk 1.2.0 → 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 +306 -68
- 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 +307 -70
- package/dist/studio/index.js.map +1 -1
- package/dist/styles.css +6 -0
- package/package.json +2 -2
package/dist/studio/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { createContext, useContext, useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
|
-
import { Loader2, Share2, MicOff, Mic, Video, VideoOff, Settings, X, Link2, EyeOff, Eye,
|
|
4
|
+
import { Loader2, Share2, MicOff, Mic, Video, VideoOff, Settings, X, Link2, EyeOff, Eye, MonitorUp, MessageSquare, Users, Maximize, PictureInPicture2, PhoneOff, Undo2, Volume2, XIcon, Pin, MessageSquareOff, MoreVertical } from 'lucide-react';
|
|
5
5
|
import { toast } from 'sonner';
|
|
6
6
|
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
|
|
7
7
|
import { clsx } from 'clsx';
|
|
@@ -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-fit max-w-[80%]
|
|
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
|
}
|
|
@@ -2585,7 +2685,7 @@ function StudioSidePanel({
|
|
|
2585
2685
|
const panelists = participants.filter((row) => isOnStage(row.stageStatus));
|
|
2586
2686
|
const audience = participants.filter((row) => !isOnStage(row.stageStatus));
|
|
2587
2687
|
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: [
|
|
2588
|
-
/* @__PURE__ */ jsxs("div", { className: "flex
|
|
2688
|
+
/* @__PURE__ */ jsxs("div", { className: "flex", children: [
|
|
2589
2689
|
/* @__PURE__ */ jsxs(
|
|
2590
2690
|
Button,
|
|
2591
2691
|
{
|
|
@@ -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"
|
|
@@ -3239,14 +3341,24 @@ function StudioToolbar({
|
|
|
3239
3341
|
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
|
|
3240
3342
|
}
|
|
3241
3343
|
) : null,
|
|
3242
|
-
session.role === "guest" && stageEnabled
|
|
3344
|
+
session.role === "guest" && stageEnabled ? /* @__PURE__ */ jsx(
|
|
3243
3345
|
ControlIcon,
|
|
3244
3346
|
{
|
|
3245
|
-
label: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
|
|
3347
|
+
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
|
|
3246
3348
|
pressed: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
|
|
3247
|
-
disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
|
|
3248
|
-
onClick:
|
|
3249
|
-
|
|
3349
|
+
disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" || selfStageStatus === "ON_STAGE" && !canLeaveStage,
|
|
3350
|
+
onClick: () => {
|
|
3351
|
+
if (selfStageStatus === "ON_STAGE") {
|
|
3352
|
+
if (!canLeaveStage) {
|
|
3353
|
+
toast.message(labels.offAirLastVisible);
|
|
3354
|
+
return;
|
|
3355
|
+
}
|
|
3356
|
+
onLeaveStage();
|
|
3357
|
+
return;
|
|
3358
|
+
}
|
|
3359
|
+
onAskStage();
|
|
3360
|
+
},
|
|
3361
|
+
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
|
|
3250
3362
|
}
|
|
3251
3363
|
) : null,
|
|
3252
3364
|
showScreenControl ? canShareFreely ? /* @__PURE__ */ jsx(
|
|
@@ -3421,6 +3533,12 @@ function StudioRoom({
|
|
|
3421
3533
|
const seenWaitlisted = useRef(/* @__PURE__ */ new Set());
|
|
3422
3534
|
const knockPrimed = useRef(false);
|
|
3423
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);
|
|
3424
3542
|
const grantCheckTimers = useRef([]);
|
|
3425
3543
|
const screenNoticePrimed = useRef(false);
|
|
3426
3544
|
const lastSelfMedia = useRef({
|
|
@@ -3449,6 +3567,9 @@ function StudioRoom({
|
|
|
3449
3567
|
const rejected = isStudioRejected(roomState);
|
|
3450
3568
|
useBeforeUnload(active && !sessionOver);
|
|
3451
3569
|
useWakeLock(active && !sessionOver);
|
|
3570
|
+
useEffect(() => {
|
|
3571
|
+
unlockStudioSounds();
|
|
3572
|
+
}, []);
|
|
3452
3573
|
useEffect(() => {
|
|
3453
3574
|
if (onAir) {
|
|
3454
3575
|
setPublishing(false);
|
|
@@ -3674,9 +3795,57 @@ function StudioRoom({
|
|
|
3674
3795
|
const incoming = chatMessages.filter((row) => row.userId !== selfUserId);
|
|
3675
3796
|
const unread = sideTab === "chat" ? 0 : Math.max(0, incoming.length - seenIncoming);
|
|
3676
3797
|
const peopleBadge = waitlisted.length + stageRequests.length;
|
|
3798
|
+
const selfStageStatus = self?.stageStatus ?? meeting.stageStatus();
|
|
3677
3799
|
useEffect(() => {
|
|
3678
3800
|
if (sideTab === "chat") setSeenIncoming(incoming.length);
|
|
3679
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]);
|
|
3680
3849
|
useEffect(() => {
|
|
3681
3850
|
if (session.role === "moderator" && waitlisted.length > 0 && !peopleAutoOpened.current) {
|
|
3682
3851
|
peopleAutoOpened.current = true;
|
|
@@ -3693,7 +3862,10 @@ function StudioRoom({
|
|
|
3693
3862
|
if (seenWaitlisted.current.has(guest.id)) continue;
|
|
3694
3863
|
seenWaitlisted.current.add(guest.id);
|
|
3695
3864
|
toast.message(labels.knock.replace("{name}", guest.name));
|
|
3696
|
-
if (session.role === "moderator")
|
|
3865
|
+
if (session.role === "moderator") {
|
|
3866
|
+
playStudioSound("request");
|
|
3867
|
+
setSideTab("people");
|
|
3868
|
+
}
|
|
3697
3869
|
}
|
|
3698
3870
|
}, [labels.knock, session.role, waitlisted]);
|
|
3699
3871
|
useEffect(() => {
|
|
@@ -3730,6 +3902,7 @@ function StudioRoom({
|
|
|
3730
3902
|
}
|
|
3731
3903
|
if (message.type === "share-request") {
|
|
3732
3904
|
if (session.role !== "moderator") return;
|
|
3905
|
+
playStudioSound("request");
|
|
3733
3906
|
toast.message(labels.shareRequestFrom.replace("{name}", message.name), {
|
|
3734
3907
|
id: `share-request:${message.participantId}`,
|
|
3735
3908
|
action: {
|
|
@@ -4105,10 +4278,13 @@ function StudioRoom({
|
|
|
4105
4278
|
size: "icon-sm",
|
|
4106
4279
|
"aria-label": labels.share,
|
|
4107
4280
|
onClick: () => {
|
|
4108
|
-
void
|
|
4109
|
-
playerWatchUrl(session.stream.id, playerOrigin)
|
|
4110
|
-
|
|
4111
|
-
|
|
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
|
+
});
|
|
4112
4288
|
},
|
|
4113
4289
|
children: /* @__PURE__ */ jsx(Share2, {})
|
|
4114
4290
|
}
|
|
@@ -4441,6 +4617,7 @@ function StudioDevicePreflight({
|
|
|
4441
4617
|
setMicOk(Boolean(audio));
|
|
4442
4618
|
setAudioTrack(audio);
|
|
4443
4619
|
if (videoRef.current) {
|
|
4620
|
+
videoRef.current.setAttribute("webkit-playsinline", "");
|
|
4444
4621
|
videoRef.current.srcObject = video ? next : null;
|
|
4445
4622
|
}
|
|
4446
4623
|
bindEnded(next, gen);
|
|
@@ -4470,7 +4647,7 @@ function StudioDevicePreflight({
|
|
|
4470
4647
|
autoPlay: true,
|
|
4471
4648
|
muted: true,
|
|
4472
4649
|
playsInline: true,
|
|
4473
|
-
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"
|
|
4474
4651
|
}
|
|
4475
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 }) }),
|
|
4476
4653
|
/* @__PURE__ */ jsx(
|
|
@@ -4731,44 +4908,42 @@ function screenShareTrack(meeting, participantId) {
|
|
|
4731
4908
|
}
|
|
4732
4909
|
return findParticipant(meeting, participantId)?.screenShareTracks?.video ?? null;
|
|
4733
4910
|
}
|
|
4734
|
-
var
|
|
4735
|
-
function
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
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);
|
|
4743
4926
|
}
|
|
4744
4927
|
}
|
|
4745
4928
|
if (typeof document !== "undefined") {
|
|
4746
|
-
document.addEventListener("pointerdown",
|
|
4929
|
+
document.addEventListener("pointerdown", replayPendingMedia, {
|
|
4747
4930
|
passive: true
|
|
4748
4931
|
});
|
|
4749
4932
|
}
|
|
4750
4933
|
function bindTrackElement(el, track, kind) {
|
|
4751
4934
|
if (!track) {
|
|
4752
4935
|
el.srcObject = null;
|
|
4753
|
-
|
|
4936
|
+
pendingMediaPlay.delete(el);
|
|
4754
4937
|
return;
|
|
4755
4938
|
}
|
|
4756
4939
|
const current = el.srcObject;
|
|
4757
4940
|
const currentTrack = current instanceof MediaStream ? kind === "video" ? current.getVideoTracks()[0] : current.getAudioTracks()[0] : void 0;
|
|
4758
4941
|
if (currentTrack?.id === track.id) {
|
|
4942
|
+
if (el.paused) tryPlay(el);
|
|
4759
4943
|
return;
|
|
4760
4944
|
}
|
|
4761
4945
|
el.srcObject = new MediaStream([track]);
|
|
4762
|
-
|
|
4763
|
-
void el.play().then(
|
|
4764
|
-
() => {
|
|
4765
|
-
if (el instanceof HTMLAudioElement) pendingAudioPlay.delete(el);
|
|
4766
|
-
},
|
|
4767
|
-
() => {
|
|
4768
|
-
if (el instanceof HTMLAudioElement) pendingAudioPlay.add(el);
|
|
4769
|
-
}
|
|
4770
|
-
);
|
|
4771
|
-
}
|
|
4946
|
+
tryPlay(el);
|
|
4772
4947
|
}
|
|
4773
4948
|
function roomLeftState(payload, fallback) {
|
|
4774
4949
|
if (typeof payload === "string" && payload) return payload;
|
|
@@ -5255,7 +5430,15 @@ function studioMeetingFromRtk(raw) {
|
|
|
5255
5430
|
notifyStage();
|
|
5256
5431
|
return;
|
|
5257
5432
|
}
|
|
5258
|
-
|
|
5433
|
+
try {
|
|
5434
|
+
await meeting.stage?.join?.();
|
|
5435
|
+
} catch {
|
|
5436
|
+
const userId = meeting.self?.userId;
|
|
5437
|
+
if (userId) {
|
|
5438
|
+
await meeting.stage?.grantAccess?.([userId]);
|
|
5439
|
+
}
|
|
5440
|
+
await meeting.stage?.join?.();
|
|
5441
|
+
}
|
|
5259
5442
|
joinedStageLocally = true;
|
|
5260
5443
|
await republishCameraIfOn();
|
|
5261
5444
|
notifyStage();
|
|
@@ -5545,7 +5728,11 @@ function useRealtimeKitMeeting(authToken, generation = 0) {
|
|
|
5545
5728
|
const [meeting, setMeeting] = useState(null);
|
|
5546
5729
|
const [error, setError] = useState(null);
|
|
5547
5730
|
useEffect(() => {
|
|
5548
|
-
if (!authToken)
|
|
5731
|
+
if (!authToken) {
|
|
5732
|
+
setMeeting(null);
|
|
5733
|
+
setError(null);
|
|
5734
|
+
return;
|
|
5735
|
+
}
|
|
5549
5736
|
let cancelled = false;
|
|
5550
5737
|
let handle = null;
|
|
5551
5738
|
setError(null);
|
|
@@ -5720,6 +5907,21 @@ async function getStudioJoinStatus(token, apiUrl = defaultApiUrl(), fetchFn = fe
|
|
|
5720
5907
|
throw error;
|
|
5721
5908
|
}
|
|
5722
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
|
+
}
|
|
5723
5925
|
var POLL_INTERVAL_MS = 5e3;
|
|
5724
5926
|
function LivoHostStudio({
|
|
5725
5927
|
token,
|
|
@@ -5814,6 +6016,7 @@ function LivoHostStudio({
|
|
|
5814
6016
|
);
|
|
5815
6017
|
}
|
|
5816
6018
|
var POLL_INTERVAL_MS2 = 5e3;
|
|
6019
|
+
var PREVIEW_POLL_INTERVAL_MS = 2e3;
|
|
5817
6020
|
function LivoGuestStudio({
|
|
5818
6021
|
token,
|
|
5819
6022
|
apiUrl,
|
|
@@ -5825,6 +6028,7 @@ function LivoGuestStudio({
|
|
|
5825
6028
|
}) {
|
|
5826
6029
|
const reducedMotion = usePrefersReducedMotion();
|
|
5827
6030
|
const [session, setSession] = useState(null);
|
|
6031
|
+
const [status, setStatus] = useState(null);
|
|
5828
6032
|
const [error, setError] = useState(null);
|
|
5829
6033
|
const [displayName, setDisplayName] = useState("");
|
|
5830
6034
|
const [guestId, setGuestId] = useState("");
|
|
@@ -5874,6 +6078,7 @@ function LivoGuestStudio({
|
|
|
5874
6078
|
);
|
|
5875
6079
|
setWaitingForHost(false);
|
|
5876
6080
|
setRetryAfterMs(null);
|
|
6081
|
+
setStatus(null);
|
|
5877
6082
|
setSession(next);
|
|
5878
6083
|
} catch (err) {
|
|
5879
6084
|
const code = studioErrorCode(err);
|
|
@@ -5890,9 +6095,9 @@ function LivoGuestStudio({
|
|
|
5890
6095
|
setError(err instanceof Error ? err.message : lobbyLabels.joinError);
|
|
5891
6096
|
setWaitingForHost(false);
|
|
5892
6097
|
}
|
|
5893
|
-
setJoining(false);
|
|
5894
6098
|
} finally {
|
|
5895
6099
|
joinInFlight.current = false;
|
|
6100
|
+
setJoining(false);
|
|
5896
6101
|
}
|
|
5897
6102
|
}, [apiUrl, displayName, guestId, lobbyLabels, token]);
|
|
5898
6103
|
useEffect(() => {
|
|
@@ -5921,6 +6126,31 @@ function LivoGuestStudio({
|
|
|
5921
6126
|
window.clearInterval(id);
|
|
5922
6127
|
};
|
|
5923
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]);
|
|
5924
6154
|
useEffect(() => {
|
|
5925
6155
|
if (retryAfterMs == null) return;
|
|
5926
6156
|
const id = window.setTimeout(() => {
|
|
@@ -5933,7 +6163,7 @@ function LivoGuestStudio({
|
|
|
5933
6163
|
if (meetingError) {
|
|
5934
6164
|
return /* @__PURE__ */ jsx("p", { className: "text-destructive p-6", role: "alert", children: meetingError });
|
|
5935
6165
|
}
|
|
5936
|
-
if (!
|
|
6166
|
+
if (!liveSession || !meeting) {
|
|
5937
6167
|
return /* @__PURE__ */ jsx(
|
|
5938
6168
|
StudioGuestLobby,
|
|
5939
6169
|
{
|
|
@@ -5958,7 +6188,7 @@ function LivoGuestStudio({
|
|
|
5958
6188
|
return /* @__PURE__ */ jsx(
|
|
5959
6189
|
StudioRoom,
|
|
5960
6190
|
{
|
|
5961
|
-
session,
|
|
6191
|
+
session: liveSession,
|
|
5962
6192
|
meeting,
|
|
5963
6193
|
control: {
|
|
5964
6194
|
livestream: async () => "starting",
|
|
@@ -5967,7 +6197,12 @@ function LivoGuestStudio({
|
|
|
5967
6197
|
},
|
|
5968
6198
|
onRejoin: () => {
|
|
5969
6199
|
setSession(null);
|
|
6200
|
+
setStatus(null);
|
|
5970
6201
|
setSessionGen((n) => n + 1);
|
|
6202
|
+
setJoining(false);
|
|
6203
|
+
setWaitingForHost(false);
|
|
6204
|
+
setError(null);
|
|
6205
|
+
joinInFlight.current = false;
|
|
5971
6206
|
},
|
|
5972
6207
|
reducedMotion,
|
|
5973
6208
|
labels,
|
|
@@ -6109,6 +6344,8 @@ function studioRoomLabels(t) {
|
|
|
6109
6344
|
admitAllAudience: t("admitAllAudience"),
|
|
6110
6345
|
askToGoOnAir: t("askToGoOnAir"),
|
|
6111
6346
|
stageRequested: t("stageRequested"),
|
|
6347
|
+
stageApproved: t("stageApproved"),
|
|
6348
|
+
stageDenied: t("stageDenied"),
|
|
6112
6349
|
stageRequests: t("stageRequests"),
|
|
6113
6350
|
people: t("people"),
|
|
6114
6351
|
panelists: t("panelists"),
|
|
@@ -6146,6 +6383,6 @@ function studioRoomLabels(t) {
|
|
|
6146
6383
|
};
|
|
6147
6384
|
}
|
|
6148
6385
|
|
|
6149
|
-
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 };
|
|
6150
6387
|
//# sourceMappingURL=index.js.map
|
|
6151
6388
|
//# sourceMappingURL=index.js.map
|