@livo-tv/sdk 1.2.1 → 1.2.3
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 -71
- 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 +306 -72
- 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
|
@@ -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
|
}
|
|
@@ -2182,13 +2275,13 @@ function BubbleGroup({ className, ...props }) {
|
|
|
2182
2275
|
"div",
|
|
2183
2276
|
{
|
|
2184
2277
|
"data-slot": "bubble-group",
|
|
2185
|
-
className: cn("flex min-w-
|
|
2278
|
+
className: cn("flex min-w-min flex-col gap-2", className),
|
|
2186
2279
|
...props
|
|
2187
2280
|
}
|
|
2188
2281
|
);
|
|
2189
2282
|
}
|
|
2190
2283
|
var bubbleVariants = cva(
|
|
2191
|
-
"group/bubble relative flex w-
|
|
2284
|
+
"group/bubble relative flex w-fit min-w-min max-w-full 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 whitespace-pre-wrap 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 max-w-[80%] flex-col gap-1 self-end" : "flex max-w-[80%] flex-col gap-1 self-start",
|
|
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(
|
|
@@ -4702,7 +4869,11 @@ function isRecorder(participant) {
|
|
|
4702
4869
|
function resolvedName(participant, nameOverrides) {
|
|
4703
4870
|
return nameOverrides.get(participant.id) ?? (participant.userId ? nameOverrides.get(participant.userId) : void 0) ?? participant.name?.trim() ?? "Guest";
|
|
4704
4871
|
}
|
|
4705
|
-
function
|
|
4872
|
+
function resolveTileStageStatus(participant, stageEnabled, fallback) {
|
|
4873
|
+
if (!stageEnabled) return "ON_STAGE";
|
|
4874
|
+
return participant.stageStatus ?? fallback ?? "OFF_STAGE";
|
|
4875
|
+
}
|
|
4876
|
+
function tileFrom(participant, selfId, pinnedIds, chatSend, nameOverrides, stageStatus) {
|
|
4706
4877
|
return {
|
|
4707
4878
|
id: participant.id,
|
|
4708
4879
|
userId: participant.userId ?? "",
|
|
@@ -4712,7 +4883,7 @@ function tileFrom(participant, selfId, pinnedIds, chatSend, nameOverrides) {
|
|
|
4712
4883
|
videoEnabled: participant.videoEnabled === true,
|
|
4713
4884
|
screenShareEnabled: participant.screenShareEnabled === true,
|
|
4714
4885
|
picture: participant.picture ?? null,
|
|
4715
|
-
stageStatus
|
|
4886
|
+
stageStatus,
|
|
4716
4887
|
pinned: participant.isPinned === true || pinnedIds.has(participant.id),
|
|
4717
4888
|
chatCanSend: chatSend.get(participant.id) ?? true
|
|
4718
4889
|
};
|
|
@@ -4741,44 +4912,42 @@ function screenShareTrack(meeting, participantId) {
|
|
|
4741
4912
|
}
|
|
4742
4913
|
return findParticipant(meeting, participantId)?.screenShareTracks?.video ?? null;
|
|
4743
4914
|
}
|
|
4744
|
-
var
|
|
4745
|
-
function
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4915
|
+
var pendingMediaPlay = /* @__PURE__ */ new Set();
|
|
4916
|
+
function tryPlay(el) {
|
|
4917
|
+
if (typeof el.play !== "function") return;
|
|
4918
|
+
void el.play().then(
|
|
4919
|
+
() => {
|
|
4920
|
+
pendingMediaPlay.delete(el);
|
|
4921
|
+
},
|
|
4922
|
+
() => {
|
|
4923
|
+
pendingMediaPlay.add(el);
|
|
4924
|
+
}
|
|
4925
|
+
);
|
|
4926
|
+
}
|
|
4927
|
+
function replayPendingMedia() {
|
|
4928
|
+
for (const el of pendingMediaPlay) {
|
|
4929
|
+
tryPlay(el);
|
|
4753
4930
|
}
|
|
4754
4931
|
}
|
|
4755
4932
|
if (typeof document !== "undefined") {
|
|
4756
|
-
document.addEventListener("pointerdown",
|
|
4933
|
+
document.addEventListener("pointerdown", replayPendingMedia, {
|
|
4757
4934
|
passive: true
|
|
4758
4935
|
});
|
|
4759
4936
|
}
|
|
4760
4937
|
function bindTrackElement(el, track, kind) {
|
|
4761
4938
|
if (!track) {
|
|
4762
4939
|
el.srcObject = null;
|
|
4763
|
-
|
|
4940
|
+
pendingMediaPlay.delete(el);
|
|
4764
4941
|
return;
|
|
4765
4942
|
}
|
|
4766
4943
|
const current = el.srcObject;
|
|
4767
4944
|
const currentTrack = current instanceof MediaStream ? kind === "video" ? current.getVideoTracks()[0] : current.getAudioTracks()[0] : void 0;
|
|
4768
4945
|
if (currentTrack?.id === track.id) {
|
|
4946
|
+
if (el.paused) tryPlay(el);
|
|
4769
4947
|
return;
|
|
4770
4948
|
}
|
|
4771
4949
|
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
|
-
}
|
|
4950
|
+
tryPlay(el);
|
|
4782
4951
|
}
|
|
4783
4952
|
function roomLeftState(payload, fallback) {
|
|
4784
4953
|
if (typeof payload === "string" && payload) return payload;
|
|
@@ -5256,7 +5425,7 @@ function studioMeetingFromRtk(raw) {
|
|
|
5256
5425
|
notifyStage();
|
|
5257
5426
|
},
|
|
5258
5427
|
async joinStage() {
|
|
5259
|
-
if (
|
|
5428
|
+
if (meeting.self?.stageStatus === "ON_STAGE") {
|
|
5260
5429
|
notifyStage();
|
|
5261
5430
|
return;
|
|
5262
5431
|
}
|
|
@@ -5265,14 +5434,15 @@ function studioMeetingFromRtk(raw) {
|
|
|
5265
5434
|
notifyStage();
|
|
5266
5435
|
return;
|
|
5267
5436
|
}
|
|
5437
|
+
const userId = meeting.self?.userId;
|
|
5438
|
+
if (userId) {
|
|
5439
|
+
await meeting.stage?.grantAccess?.([userId]);
|
|
5440
|
+
}
|
|
5268
5441
|
try {
|
|
5269
5442
|
await meeting.stage?.join?.();
|
|
5270
|
-
} catch {
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
await meeting.stage?.grantAccess?.([userId]);
|
|
5274
|
-
}
|
|
5275
|
-
await meeting.stage?.join?.();
|
|
5443
|
+
} catch (error) {
|
|
5444
|
+
notifyStage();
|
|
5445
|
+
throw error;
|
|
5276
5446
|
}
|
|
5277
5447
|
joinedStageLocally = true;
|
|
5278
5448
|
await republishCameraIfOn();
|
|
@@ -5280,6 +5450,7 @@ function studioMeetingFromRtk(raw) {
|
|
|
5280
5450
|
},
|
|
5281
5451
|
async leaveStage() {
|
|
5282
5452
|
await meeting.stage?.leave?.();
|
|
5453
|
+
joinedStageLocally = false;
|
|
5283
5454
|
notifyStage();
|
|
5284
5455
|
},
|
|
5285
5456
|
stageStatus() {
|
|
@@ -5361,10 +5532,27 @@ function studioMeetingFromRtk(raw) {
|
|
|
5361
5532
|
participants() {
|
|
5362
5533
|
const self = meeting.self;
|
|
5363
5534
|
const pinned = pinnedIdSet();
|
|
5364
|
-
const
|
|
5535
|
+
const stageEnabled = self?.permissions?.stageEnabled !== false;
|
|
5536
|
+
const joinedRows = asList(meeting.participants?.joined).filter((row) => row.id !== self?.id && !isRecorder(row)).map(
|
|
5537
|
+
(row) => tileFrom(
|
|
5538
|
+
row,
|
|
5539
|
+
self?.id,
|
|
5540
|
+
pinned,
|
|
5541
|
+
chatSend,
|
|
5542
|
+
nameOverrides,
|
|
5543
|
+
resolveTileStageStatus(row, stageEnabled)
|
|
5544
|
+
)
|
|
5545
|
+
);
|
|
5365
5546
|
if (self?.id && !isRecorder(self)) {
|
|
5366
5547
|
joinedRows.unshift(
|
|
5367
|
-
tileFrom(
|
|
5548
|
+
tileFrom(
|
|
5549
|
+
self,
|
|
5550
|
+
self.id,
|
|
5551
|
+
pinned,
|
|
5552
|
+
chatSend,
|
|
5553
|
+
nameOverrides,
|
|
5554
|
+
resolveTileStageStatus(self, stageEnabled, meeting.stage?.status)
|
|
5555
|
+
)
|
|
5368
5556
|
);
|
|
5369
5557
|
}
|
|
5370
5558
|
return joinedRows;
|
|
@@ -5742,6 +5930,21 @@ async function getStudioJoinStatus(token, apiUrl = defaultApiUrl(), fetchFn = fe
|
|
|
5742
5930
|
throw error;
|
|
5743
5931
|
}
|
|
5744
5932
|
}
|
|
5933
|
+
async function getPublicStreamStatus(streamId, apiUrl = defaultApiUrl(), fetchFn = fetch) {
|
|
5934
|
+
try {
|
|
5935
|
+
const body = await publicJson(
|
|
5936
|
+
`${apiUrl.replace(/\/$/, "")}/public/streams/${encodeURIComponent(streamId)}`,
|
|
5937
|
+
{},
|
|
5938
|
+
fetchFn
|
|
5939
|
+
);
|
|
5940
|
+
return parsePublicStreamStatus(body);
|
|
5941
|
+
} catch (error) {
|
|
5942
|
+
if (error instanceof StudioApiError && (error.status === 404 || error.status === 410)) {
|
|
5943
|
+
return "ended";
|
|
5944
|
+
}
|
|
5945
|
+
return null;
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5745
5948
|
var POLL_INTERVAL_MS = 5e3;
|
|
5746
5949
|
function LivoHostStudio({
|
|
5747
5950
|
token,
|
|
@@ -5836,6 +6039,7 @@ function LivoHostStudio({
|
|
|
5836
6039
|
);
|
|
5837
6040
|
}
|
|
5838
6041
|
var POLL_INTERVAL_MS2 = 5e3;
|
|
6042
|
+
var PREVIEW_POLL_INTERVAL_MS = 2e3;
|
|
5839
6043
|
function LivoGuestStudio({
|
|
5840
6044
|
token,
|
|
5841
6045
|
apiUrl,
|
|
@@ -5847,6 +6051,7 @@ function LivoGuestStudio({
|
|
|
5847
6051
|
}) {
|
|
5848
6052
|
const reducedMotion = usePrefersReducedMotion();
|
|
5849
6053
|
const [session, setSession] = useState(null);
|
|
6054
|
+
const [status, setStatus] = useState(null);
|
|
5850
6055
|
const [error, setError] = useState(null);
|
|
5851
6056
|
const [displayName, setDisplayName] = useState("");
|
|
5852
6057
|
const [guestId, setGuestId] = useState("");
|
|
@@ -5896,6 +6101,7 @@ function LivoGuestStudio({
|
|
|
5896
6101
|
);
|
|
5897
6102
|
setWaitingForHost(false);
|
|
5898
6103
|
setRetryAfterMs(null);
|
|
6104
|
+
setStatus(null);
|
|
5899
6105
|
setSession(next);
|
|
5900
6106
|
} catch (err) {
|
|
5901
6107
|
const code = studioErrorCode(err);
|
|
@@ -5943,6 +6149,31 @@ function LivoGuestStudio({
|
|
|
5943
6149
|
window.clearInterval(id);
|
|
5944
6150
|
};
|
|
5945
6151
|
}, [apiUrl, join, lobbyLabels.endedTitle, token, waitingForHost]);
|
|
6152
|
+
useEffect(() => {
|
|
6153
|
+
if (!session) return;
|
|
6154
|
+
const current = status ?? session.stream.status;
|
|
6155
|
+
if (current === "ended" || current === "error") return;
|
|
6156
|
+
let cancelled = false;
|
|
6157
|
+
const poll = async () => {
|
|
6158
|
+
const next = await getPublicStreamStatus(session.stream.id, apiUrl);
|
|
6159
|
+
if (!cancelled && next) setStatus(next);
|
|
6160
|
+
};
|
|
6161
|
+
void poll();
|
|
6162
|
+
const intervalMs = current === "public" ? POLL_INTERVAL_MS2 : PREVIEW_POLL_INTERVAL_MS;
|
|
6163
|
+
const id = window.setInterval(() => void poll(), intervalMs);
|
|
6164
|
+
return () => {
|
|
6165
|
+
cancelled = true;
|
|
6166
|
+
window.clearInterval(id);
|
|
6167
|
+
};
|
|
6168
|
+
}, [apiUrl, session, status]);
|
|
6169
|
+
const liveSession = useMemo(() => {
|
|
6170
|
+
if (!session) return null;
|
|
6171
|
+
if (!status) return session;
|
|
6172
|
+
return {
|
|
6173
|
+
...session,
|
|
6174
|
+
stream: { ...session.stream, status }
|
|
6175
|
+
};
|
|
6176
|
+
}, [session, status]);
|
|
5946
6177
|
useEffect(() => {
|
|
5947
6178
|
if (retryAfterMs == null) return;
|
|
5948
6179
|
const id = window.setTimeout(() => {
|
|
@@ -5955,7 +6186,7 @@ function LivoGuestStudio({
|
|
|
5955
6186
|
if (meetingError) {
|
|
5956
6187
|
return /* @__PURE__ */ jsx("p", { className: "text-destructive p-6", role: "alert", children: meetingError });
|
|
5957
6188
|
}
|
|
5958
|
-
if (!
|
|
6189
|
+
if (!liveSession || !meeting) {
|
|
5959
6190
|
return /* @__PURE__ */ jsx(
|
|
5960
6191
|
StudioGuestLobby,
|
|
5961
6192
|
{
|
|
@@ -5980,7 +6211,7 @@ function LivoGuestStudio({
|
|
|
5980
6211
|
return /* @__PURE__ */ jsx(
|
|
5981
6212
|
StudioRoom,
|
|
5982
6213
|
{
|
|
5983
|
-
session,
|
|
6214
|
+
session: liveSession,
|
|
5984
6215
|
meeting,
|
|
5985
6216
|
control: {
|
|
5986
6217
|
livestream: async () => "starting",
|
|
@@ -5989,6 +6220,7 @@ function LivoGuestStudio({
|
|
|
5989
6220
|
},
|
|
5990
6221
|
onRejoin: () => {
|
|
5991
6222
|
setSession(null);
|
|
6223
|
+
setStatus(null);
|
|
5992
6224
|
setSessionGen((n) => n + 1);
|
|
5993
6225
|
setJoining(false);
|
|
5994
6226
|
setWaitingForHost(false);
|
|
@@ -6135,6 +6367,8 @@ function studioRoomLabels(t) {
|
|
|
6135
6367
|
admitAllAudience: t("admitAllAudience"),
|
|
6136
6368
|
askToGoOnAir: t("askToGoOnAir"),
|
|
6137
6369
|
stageRequested: t("stageRequested"),
|
|
6370
|
+
stageApproved: t("stageApproved"),
|
|
6371
|
+
stageDenied: t("stageDenied"),
|
|
6138
6372
|
stageRequests: t("stageRequests"),
|
|
6139
6373
|
people: t("people"),
|
|
6140
6374
|
panelists: t("panelists"),
|
|
@@ -6172,6 +6406,6 @@ function studioRoomLabels(t) {
|
|
|
6172
6406
|
};
|
|
6173
6407
|
}
|
|
6174
6408
|
|
|
6175
|
-
export { LivoGuestStudio, LivoHostStudio, StudioApiError, StudioControlClient, StudioControlError, StudioGuestLobby, StudioRoom, StudioSkeleton, StudioWindowGate, StudioWindowGateContext, applyEmbedTheme, controlActionsFromClient, createStudioControlClient, decodeEmbedTheme, getStudioJoinStatus, joinStudioGuest, redeemStudioHost, studioErrorCode, studioRoomLabels, usePrefersReducedMotion, useRealtimeKitMeeting, useStudioWindowGate };
|
|
6409
|
+
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
6410
|
//# sourceMappingURL=index.js.map
|
|
6177
6411
|
//# sourceMappingURL=index.js.map
|