@livo-tv/sdk 1.3.3 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/studio/index.cjs +421 -142
- package/dist/studio/index.cjs.map +1 -1
- package/dist/studio/index.d.cts +10 -3
- package/dist/studio/index.d.ts +10 -3
- package/dist/studio/index.js +421 -142
- package/dist/studio/index.js.map +1 -1
- package/dist/styles.css +23 -0
- package/package.json +2 -2
package/dist/studio/index.js
CHANGED
|
@@ -518,52 +518,38 @@ function canTakeOffAir(tiles, participantId, onAir) {
|
|
|
518
518
|
(tile) => tile.id !== participantId && isOnStage(tile.stageStatus)
|
|
519
519
|
);
|
|
520
520
|
}
|
|
521
|
+
function pipSpotlightFrom(tile, kind) {
|
|
522
|
+
return {
|
|
523
|
+
participantId: tile.id,
|
|
524
|
+
name: tile.name,
|
|
525
|
+
kind,
|
|
526
|
+
picture: tile.picture
|
|
527
|
+
};
|
|
528
|
+
}
|
|
521
529
|
function resolveStudioPipSpotlight(tiles, activeSpeakerId) {
|
|
522
530
|
const visible = tiles.filter((tile) => isOnStage(tile.stageStatus));
|
|
523
531
|
const pinned = visible.find((tile) => tile.pinned === true);
|
|
524
532
|
if (pinned) {
|
|
525
|
-
return
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
533
|
+
return pipSpotlightFrom(
|
|
534
|
+
pinned,
|
|
535
|
+
pinned.screenShareEnabled ? "screen" : pinned.videoEnabled ? "camera" : "idle"
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
const speaker = activeSpeakerId ? visible.find((tile) => tile.id === activeSpeakerId) : void 0;
|
|
539
|
+
if (speaker) {
|
|
540
|
+
return pipSpotlightFrom(speaker, speaker.videoEnabled ? "camera" : "idle");
|
|
531
541
|
}
|
|
532
542
|
const sharing = visible.find((tile) => tile.screenShareEnabled);
|
|
533
543
|
if (sharing) {
|
|
534
|
-
return
|
|
535
|
-
participantId: sharing.id,
|
|
536
|
-
name: sharing.name,
|
|
537
|
-
kind: "screen",
|
|
538
|
-
picture: sharing.picture
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
const speaker = activeSpeakerId ? visible.find((tile) => tile.id === activeSpeakerId && tile.videoEnabled) : void 0;
|
|
542
|
-
if (speaker) {
|
|
543
|
-
return {
|
|
544
|
-
participantId: speaker.id,
|
|
545
|
-
name: speaker.name,
|
|
546
|
-
kind: "camera",
|
|
547
|
-
picture: speaker.picture
|
|
548
|
-
};
|
|
544
|
+
return pipSpotlightFrom(sharing, "screen");
|
|
549
545
|
}
|
|
550
546
|
const remoteCam = visible.find((tile) => !tile.isSelf && tile.videoEnabled);
|
|
551
547
|
if (remoteCam) {
|
|
552
|
-
return
|
|
553
|
-
participantId: remoteCam.id,
|
|
554
|
-
name: remoteCam.name,
|
|
555
|
-
kind: "camera",
|
|
556
|
-
picture: remoteCam.picture
|
|
557
|
-
};
|
|
548
|
+
return pipSpotlightFrom(remoteCam, "camera");
|
|
558
549
|
}
|
|
559
550
|
const self = visible.find((tile) => tile.isSelf);
|
|
560
551
|
if (!self) return null;
|
|
561
|
-
return
|
|
562
|
-
participantId: self.id,
|
|
563
|
-
name: self.name,
|
|
564
|
-
kind: self.videoEnabled ? "camera" : "idle",
|
|
565
|
-
picture: self.picture
|
|
566
|
-
};
|
|
552
|
+
return pipSpotlightFrom(self, self.videoEnabled ? "camera" : "idle");
|
|
567
553
|
}
|
|
568
554
|
function resolveStudioPipSelfView(tiles, spotlight) {
|
|
569
555
|
const self = tiles.find((tile) => tile.isSelf);
|
|
@@ -682,6 +668,9 @@ function screenShareBlock(captureSupported, permission) {
|
|
|
682
668
|
if (permission === "NOT_ALLOWED") return "permission";
|
|
683
669
|
return null;
|
|
684
670
|
}
|
|
671
|
+
function showScreenShareControl(block) {
|
|
672
|
+
return block !== "permission";
|
|
673
|
+
}
|
|
685
674
|
|
|
686
675
|
// src/studio/lib/parse.ts
|
|
687
676
|
function isRecord(value) {
|
|
@@ -1250,15 +1239,15 @@ function AudioWaveform({
|
|
|
1250
1239
|
if (!canvas || !track || reducedMotion) return;
|
|
1251
1240
|
const AudioCtx = window.AudioContext || window.webkitAudioContext;
|
|
1252
1241
|
if (!AudioCtx) return;
|
|
1253
|
-
const
|
|
1254
|
-
const source =
|
|
1255
|
-
const analyser =
|
|
1242
|
+
const ctx3 = new AudioCtx();
|
|
1243
|
+
const source = ctx3.createMediaStreamSource(new MediaStream([track]));
|
|
1244
|
+
const analyser = ctx3.createAnalyser();
|
|
1256
1245
|
analyser.fftSize = 64;
|
|
1257
1246
|
source.connect(analyser);
|
|
1258
1247
|
const data = new Uint8Array(analyser.frequencyBinCount);
|
|
1259
1248
|
const graphic = canvas.getContext("2d");
|
|
1260
1249
|
const surface = canvas;
|
|
1261
|
-
let
|
|
1250
|
+
let frame2 = 0;
|
|
1262
1251
|
function draw() {
|
|
1263
1252
|
if (!graphic) return;
|
|
1264
1253
|
analyser.getByteFrequencyData(data);
|
|
@@ -1276,12 +1265,12 @@ function AudioWaveform({
|
|
|
1276
1265
|
barHeight
|
|
1277
1266
|
);
|
|
1278
1267
|
}
|
|
1279
|
-
|
|
1268
|
+
frame2 = requestAnimationFrame(draw);
|
|
1280
1269
|
}
|
|
1281
|
-
|
|
1270
|
+
frame2 = requestAnimationFrame(draw);
|
|
1282
1271
|
return () => {
|
|
1283
|
-
cancelAnimationFrame(
|
|
1284
|
-
void
|
|
1272
|
+
cancelAnimationFrame(frame2);
|
|
1273
|
+
void ctx3.close();
|
|
1285
1274
|
};
|
|
1286
1275
|
}, [track, reducedMotion]);
|
|
1287
1276
|
if (reducedMotion) {
|
|
@@ -1464,6 +1453,19 @@ function DialogHeader({ className, ...props }) {
|
|
|
1464
1453
|
}
|
|
1465
1454
|
);
|
|
1466
1455
|
}
|
|
1456
|
+
function DialogFooter({ className, ...props }) {
|
|
1457
|
+
return /* @__PURE__ */ jsx(
|
|
1458
|
+
"div",
|
|
1459
|
+
{
|
|
1460
|
+
"data-slot": "dialog-footer",
|
|
1461
|
+
className: cn(
|
|
1462
|
+
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
1463
|
+
className
|
|
1464
|
+
),
|
|
1465
|
+
...props
|
|
1466
|
+
}
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1467
1469
|
function DialogTitle({
|
|
1468
1470
|
className,
|
|
1469
1471
|
...props
|
|
@@ -1567,10 +1569,10 @@ async function playSpeakerTest(deviceId) {
|
|
|
1567
1569
|
if (!AudioCtx) {
|
|
1568
1570
|
throw new Error("AudioContext unavailable");
|
|
1569
1571
|
}
|
|
1570
|
-
const
|
|
1571
|
-
void
|
|
1572
|
-
const oscillator =
|
|
1573
|
-
const gain =
|
|
1572
|
+
const ctx3 = new AudioCtx();
|
|
1573
|
+
void ctx3.resume();
|
|
1574
|
+
const oscillator = ctx3.createOscillator();
|
|
1575
|
+
const gain = ctx3.createGain();
|
|
1574
1576
|
oscillator.type = "sine";
|
|
1575
1577
|
oscillator.frequency.value = TEST_HZ;
|
|
1576
1578
|
gain.gain.value = 0.18;
|
|
@@ -1580,7 +1582,7 @@ async function playSpeakerTest(deviceId) {
|
|
|
1580
1582
|
const probe = document.createElement("audio");
|
|
1581
1583
|
const canRoute = Boolean(deviceId) && typeof probe.setSinkId === "function";
|
|
1582
1584
|
if (canRoute && deviceId) {
|
|
1583
|
-
const destination =
|
|
1585
|
+
const destination = ctx3.createMediaStreamDestination();
|
|
1584
1586
|
gain.connect(destination);
|
|
1585
1587
|
audio = probe;
|
|
1586
1588
|
audio.autoplay = true;
|
|
@@ -1591,13 +1593,13 @@ async function playSpeakerTest(deviceId) {
|
|
|
1591
1593
|
await audio.play();
|
|
1592
1594
|
} catch {
|
|
1593
1595
|
gain.disconnect();
|
|
1594
|
-
gain.connect(
|
|
1596
|
+
gain.connect(ctx3.destination);
|
|
1595
1597
|
audio.srcObject = null;
|
|
1596
1598
|
audio.remove();
|
|
1597
1599
|
audio = null;
|
|
1598
1600
|
}
|
|
1599
1601
|
} else {
|
|
1600
|
-
gain.connect(
|
|
1602
|
+
gain.connect(ctx3.destination);
|
|
1601
1603
|
}
|
|
1602
1604
|
oscillator.start();
|
|
1603
1605
|
await new Promise((resolve) => {
|
|
@@ -1609,7 +1611,7 @@ async function playSpeakerTest(deviceId) {
|
|
|
1609
1611
|
audio.srcObject = null;
|
|
1610
1612
|
audio.remove();
|
|
1611
1613
|
}
|
|
1612
|
-
await
|
|
1614
|
+
await ctx3.close();
|
|
1613
1615
|
}
|
|
1614
1616
|
}
|
|
1615
1617
|
function StudioDeviceSelectGroup({
|
|
@@ -2023,6 +2025,19 @@ var badgeVariants = cva(
|
|
|
2023
2025
|
function Badge({ className, variant, ...props }) {
|
|
2024
2026
|
return /* @__PURE__ */ jsx("span", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
2025
2027
|
}
|
|
2028
|
+
|
|
2029
|
+
// src/studio/lib/studio-video-style.ts
|
|
2030
|
+
var STUDIO_VIDEO_MEDIA_STYLE = `
|
|
2031
|
+
.livo-studio-tile-video::-webkit-media-controls-start-playback-button,
|
|
2032
|
+
.livo-studio-inline-video::-webkit-media-controls-start-playback-button {
|
|
2033
|
+
display: none !important;
|
|
2034
|
+
-webkit-appearance: none;
|
|
2035
|
+
}
|
|
2036
|
+
.livo-studio-tile-video::-webkit-media-controls,
|
|
2037
|
+
.livo-studio-inline-video::-webkit-media-controls {
|
|
2038
|
+
display: none !important;
|
|
2039
|
+
}
|
|
2040
|
+
`;
|
|
2026
2041
|
function StudioPipPanel({
|
|
2027
2042
|
meeting,
|
|
2028
2043
|
labels,
|
|
@@ -2077,7 +2092,7 @@ function StudioPipPanel({
|
|
|
2077
2092
|
/* @__PURE__ */ jsx(StudioLiveBadge, { children: labels.live }),
|
|
2078
2093
|
elapsed ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground tabular-nums text-xs", children: elapsed }) : null
|
|
2079
2094
|
] }) : null,
|
|
2080
|
-
selfView ? /* @__PURE__ */ jsx(
|
|
2095
|
+
selfView && meeting.localVideoTrack() ? /* @__PURE__ */ jsx(
|
|
2081
2096
|
"div",
|
|
2082
2097
|
{
|
|
2083
2098
|
"data-testid": "pip-self-view",
|
|
@@ -2134,7 +2149,8 @@ function PipSpotlight({
|
|
|
2134
2149
|
spotlight,
|
|
2135
2150
|
cameraOffLabel
|
|
2136
2151
|
}) {
|
|
2137
|
-
|
|
2152
|
+
const track = spotlight && spotlight.kind !== "idle" ? spotlight.kind === "screen" ? meeting.participantScreenTrack(spotlight.participantId) : meeting.participantVideoTrack(spotlight.participantId) : null;
|
|
2153
|
+
if (!spotlight || spotlight.kind === "idle" || !track) {
|
|
2138
2154
|
return /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-2", children: [
|
|
2139
2155
|
spotlight?.picture ? /* @__PURE__ */ jsx(
|
|
2140
2156
|
"img",
|
|
@@ -2147,7 +2163,6 @@ function PipSpotlight({
|
|
|
2147
2163
|
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: spotlight?.name ?? cameraOffLabel })
|
|
2148
2164
|
] });
|
|
2149
2165
|
}
|
|
2150
|
-
const track = spotlight.kind === "screen" ? meeting.participantScreenTrack(spotlight.participantId) : meeting.participantVideoTrack(spotlight.participantId);
|
|
2151
2166
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2152
2167
|
/* @__PURE__ */ jsx(
|
|
2153
2168
|
PipTrackVideo,
|
|
@@ -2166,11 +2181,7 @@ function PipTrackVideo({
|
|
|
2166
2181
|
const ref = useRef(null);
|
|
2167
2182
|
useEffect(() => {
|
|
2168
2183
|
const el = ref.current;
|
|
2169
|
-
if (!el) return;
|
|
2170
|
-
if (!track) {
|
|
2171
|
-
el.srcObject = null;
|
|
2172
|
-
return;
|
|
2173
|
-
}
|
|
2184
|
+
if (!el || !track) return;
|
|
2174
2185
|
el.setAttribute("webkit-playsinline", "");
|
|
2175
2186
|
el.srcObject = new MediaStream([track]);
|
|
2176
2187
|
void el.play().catch(() => void 0);
|
|
@@ -2178,19 +2189,24 @@ function PipTrackVideo({
|
|
|
2178
2189
|
el.srcObject = null;
|
|
2179
2190
|
};
|
|
2180
2191
|
}, [track]);
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
{
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2192
|
+
if (!track) return null;
|
|
2193
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2194
|
+
/* @__PURE__ */ jsx("style", { children: STUDIO_VIDEO_MEDIA_STYLE }),
|
|
2195
|
+
/* @__PURE__ */ jsx(
|
|
2196
|
+
"video",
|
|
2197
|
+
{
|
|
2198
|
+
ref,
|
|
2199
|
+
autoPlay: true,
|
|
2200
|
+
playsInline: true,
|
|
2201
|
+
muted: true,
|
|
2202
|
+
disableRemotePlayback: true,
|
|
2203
|
+
className: cn(
|
|
2204
|
+
"livo-studio-inline-video absolute inset-0 size-full",
|
|
2205
|
+
fit === "contain" ? "object-contain" : "object-cover"
|
|
2206
|
+
)
|
|
2207
|
+
}
|
|
2208
|
+
)
|
|
2209
|
+
] });
|
|
2194
2210
|
}
|
|
2195
2211
|
function pipScreenControls({
|
|
2196
2212
|
labels,
|
|
@@ -2392,7 +2408,7 @@ function StudioChatPanel({
|
|
|
2392
2408
|
isModerator: isModerator2,
|
|
2393
2409
|
labels
|
|
2394
2410
|
}) {
|
|
2395
|
-
const [
|
|
2411
|
+
const [tick2, setTick] = useState(0);
|
|
2396
2412
|
const [draft, setDraft] = useState("");
|
|
2397
2413
|
const [editingId, setEditingId] = useState(null);
|
|
2398
2414
|
const listRef = useRef(null);
|
|
@@ -2402,9 +2418,9 @@ function StudioChatPanel({
|
|
|
2402
2418
|
);
|
|
2403
2419
|
const messages = useMemo(
|
|
2404
2420
|
() => meeting.chatMessages().filter((row) => !row.targetUserIds?.length),
|
|
2405
|
-
[meeting,
|
|
2421
|
+
[meeting, tick2]
|
|
2406
2422
|
);
|
|
2407
|
-
const pinned = useMemo(() => meeting.pinnedChatMessages(), [meeting,
|
|
2423
|
+
const pinned = useMemo(() => meeting.pinnedChatMessages(), [meeting, tick2]);
|
|
2408
2424
|
const limit = meeting.chatMaxTextLimit();
|
|
2409
2425
|
const pictures = useMemo(() => {
|
|
2410
2426
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -2904,6 +2920,171 @@ function StudioSidePanel({
|
|
|
2904
2920
|
] })
|
|
2905
2921
|
] });
|
|
2906
2922
|
}
|
|
2923
|
+
|
|
2924
|
+
// src/studio/lib/audio-levels.ts
|
|
2925
|
+
var LEVEL_SENSITIVITY = 4;
|
|
2926
|
+
var LEVEL_ATTACK = 0.55;
|
|
2927
|
+
var LEVEL_DECAY = 0.12;
|
|
2928
|
+
var SPEAKING_ON = 0.12;
|
|
2929
|
+
var SPEAKING_OFF = 0.06;
|
|
2930
|
+
var SPEAKING_HOLD_MS = 180;
|
|
2931
|
+
var tracks = /* @__PURE__ */ new Map();
|
|
2932
|
+
var ctx2 = null;
|
|
2933
|
+
var frame = 0;
|
|
2934
|
+
var tickRunning = false;
|
|
2935
|
+
function audioContextCtor2() {
|
|
2936
|
+
if (typeof window === "undefined") return null;
|
|
2937
|
+
return window.AudioContext || window.webkitAudioContext || null;
|
|
2938
|
+
}
|
|
2939
|
+
function ensureContext2() {
|
|
2940
|
+
const AudioCtx = audioContextCtor2();
|
|
2941
|
+
if (!AudioCtx) return null;
|
|
2942
|
+
if (!ctx2 || ctx2.state === "closed") {
|
|
2943
|
+
ctx2 = new AudioCtx();
|
|
2944
|
+
}
|
|
2945
|
+
return ctx2;
|
|
2946
|
+
}
|
|
2947
|
+
function rmsFromTimeDomain(data) {
|
|
2948
|
+
if (data.length === 0) return 0;
|
|
2949
|
+
let sum = 0;
|
|
2950
|
+
for (let i = 0; i < data.length; i += 1) {
|
|
2951
|
+
const centered = ((data[i] ?? 128) - 128) / 128;
|
|
2952
|
+
sum += centered * centered;
|
|
2953
|
+
}
|
|
2954
|
+
return Math.sqrt(sum / data.length);
|
|
2955
|
+
}
|
|
2956
|
+
function normalizeAudioLevel(rms) {
|
|
2957
|
+
return Math.min(1, Math.max(0, rms * LEVEL_SENSITIVITY));
|
|
2958
|
+
}
|
|
2959
|
+
function smoothAudioLevel(previous, raw) {
|
|
2960
|
+
const factor = raw > previous ? LEVEL_ATTACK : LEVEL_DECAY;
|
|
2961
|
+
return previous + (raw - previous) * factor;
|
|
2962
|
+
}
|
|
2963
|
+
function nextSpeakingGate(gate, level, now) {
|
|
2964
|
+
if (!gate.speaking) {
|
|
2965
|
+
if (level >= SPEAKING_ON) {
|
|
2966
|
+
return { speaking: true, quietSince: null };
|
|
2967
|
+
}
|
|
2968
|
+
return { speaking: false, quietSince: null };
|
|
2969
|
+
}
|
|
2970
|
+
if (level >= SPEAKING_OFF) {
|
|
2971
|
+
return { speaking: true, quietSince: null };
|
|
2972
|
+
}
|
|
2973
|
+
const quietSince = gate.quietSince ?? now;
|
|
2974
|
+
if (now - quietSince >= SPEAKING_HOLD_MS) {
|
|
2975
|
+
return { speaking: false, quietSince: null };
|
|
2976
|
+
}
|
|
2977
|
+
return { speaking: true, quietSince };
|
|
2978
|
+
}
|
|
2979
|
+
function tick() {
|
|
2980
|
+
for (const entry of tracks.values()) {
|
|
2981
|
+
entry.analyser.getByteTimeDomainData(entry.data);
|
|
2982
|
+
const raw = normalizeAudioLevel(rmsFromTimeDomain(entry.data));
|
|
2983
|
+
entry.smoothed = smoothAudioLevel(entry.smoothed, raw);
|
|
2984
|
+
for (const cb of entry.listeners) {
|
|
2985
|
+
cb(entry.smoothed);
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
if (tracks.size > 0) {
|
|
2989
|
+
frame = requestAnimationFrame(tick);
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2992
|
+
tickRunning = false;
|
|
2993
|
+
frame = 0;
|
|
2994
|
+
}
|
|
2995
|
+
function startTick() {
|
|
2996
|
+
if (tickRunning) return;
|
|
2997
|
+
tickRunning = true;
|
|
2998
|
+
frame = requestAnimationFrame(tick);
|
|
2999
|
+
}
|
|
3000
|
+
function stopTickIfIdle() {
|
|
3001
|
+
if (tracks.size > 0 || !frame) return;
|
|
3002
|
+
cancelAnimationFrame(frame);
|
|
3003
|
+
tickRunning = false;
|
|
3004
|
+
frame = 0;
|
|
3005
|
+
}
|
|
3006
|
+
function subscribeStudioAudioLevel(track, cb) {
|
|
3007
|
+
const context = ensureContext2();
|
|
3008
|
+
if (!context) return () => void 0;
|
|
3009
|
+
void context.resume();
|
|
3010
|
+
const id = track.id;
|
|
3011
|
+
let entry = tracks.get(id);
|
|
3012
|
+
if (!entry) {
|
|
3013
|
+
const source = context.createMediaStreamSource(new MediaStream([track]));
|
|
3014
|
+
const analyser = context.createAnalyser();
|
|
3015
|
+
analyser.fftSize = 512;
|
|
3016
|
+
analyser.smoothingTimeConstant = 0;
|
|
3017
|
+
source.connect(analyser);
|
|
3018
|
+
entry = {
|
|
3019
|
+
source,
|
|
3020
|
+
analyser,
|
|
3021
|
+
data: new Uint8Array(analyser.fftSize),
|
|
3022
|
+
refs: 0,
|
|
3023
|
+
smoothed: 0,
|
|
3024
|
+
listeners: /* @__PURE__ */ new Set()
|
|
3025
|
+
};
|
|
3026
|
+
tracks.set(id, entry);
|
|
3027
|
+
}
|
|
3028
|
+
entry.refs += 1;
|
|
3029
|
+
entry.listeners.add(cb);
|
|
3030
|
+
startTick();
|
|
3031
|
+
return () => {
|
|
3032
|
+
const current = tracks.get(id);
|
|
3033
|
+
if (!current) return;
|
|
3034
|
+
current.listeners.delete(cb);
|
|
3035
|
+
current.refs -= 1;
|
|
3036
|
+
if (current.refs <= 0) {
|
|
3037
|
+
current.source.disconnect();
|
|
3038
|
+
current.analyser.disconnect();
|
|
3039
|
+
tracks.delete(id);
|
|
3040
|
+
}
|
|
3041
|
+
stopTickIfIdle();
|
|
3042
|
+
};
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
// src/studio/hooks/use-speaking-glow.ts
|
|
3046
|
+
function clearSpeakingGlow(el) {
|
|
3047
|
+
el.style.removeProperty("--livo-speaking-level");
|
|
3048
|
+
el.removeAttribute("data-speaking");
|
|
3049
|
+
}
|
|
3050
|
+
function useSpeakingGlow({
|
|
3051
|
+
track,
|
|
3052
|
+
enabled,
|
|
3053
|
+
reducedMotion
|
|
3054
|
+
}) {
|
|
3055
|
+
const [el, setEl] = useState(null);
|
|
3056
|
+
const gateRef = useRef({ speaking: false, quietSince: null });
|
|
3057
|
+
const setRef = useCallback((node) => {
|
|
3058
|
+
setEl(node);
|
|
3059
|
+
}, []);
|
|
3060
|
+
useEffect(() => {
|
|
3061
|
+
if (!el || !enabled || !track) {
|
|
3062
|
+
if (el) clearSpeakingGlow(el);
|
|
3063
|
+
gateRef.current = { speaking: false, quietSince: null };
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
3066
|
+
const unsubscribe = subscribeStudioAudioLevel(track, (level) => {
|
|
3067
|
+
const node = el;
|
|
3068
|
+
gateRef.current = nextSpeakingGate(gateRef.current, level, Date.now());
|
|
3069
|
+
if (gateRef.current.speaking) {
|
|
3070
|
+
node.dataset.speaking = "true";
|
|
3071
|
+
if (!reducedMotion) {
|
|
3072
|
+
node.style.setProperty("--livo-speaking-level", String(level));
|
|
3073
|
+
} else {
|
|
3074
|
+
node.style.removeProperty("--livo-speaking-level");
|
|
3075
|
+
}
|
|
3076
|
+
return;
|
|
3077
|
+
}
|
|
3078
|
+
clearSpeakingGlow(node);
|
|
3079
|
+
});
|
|
3080
|
+
return () => {
|
|
3081
|
+
unsubscribe();
|
|
3082
|
+
clearSpeakingGlow(el);
|
|
3083
|
+
gateRef.current = { speaking: false, quietSince: null };
|
|
3084
|
+
};
|
|
3085
|
+
}, [el, enabled, reducedMotion, track]);
|
|
3086
|
+
return setRef;
|
|
3087
|
+
}
|
|
2907
3088
|
function DropdownMenu({ ...props }) {
|
|
2908
3089
|
return /* @__PURE__ */ jsx(Menu.Root, { "data-slot": "dropdown-menu", ...props });
|
|
2909
3090
|
}
|
|
@@ -3052,32 +3233,30 @@ function StudioTile({
|
|
|
3052
3233
|
const onStage = isOnStage(tile.stageStatus);
|
|
3053
3234
|
const [confirm, setConfirm] = useState(null);
|
|
3054
3235
|
const showMenu = (canModerate || canManageStage) && !tile.isSelf;
|
|
3236
|
+
const hasVideoTrack = tileHasVideoTrack(meeting, tile);
|
|
3237
|
+
const glowRef = useSpeakingGlow({
|
|
3238
|
+
track: tile.kind === "screen" || !tile.audioEnabled ? null : meeting.participantAudioTrack(tile.id),
|
|
3239
|
+
enabled: onStage,
|
|
3240
|
+
reducedMotion
|
|
3241
|
+
});
|
|
3055
3242
|
return /* @__PURE__ */ jsxs(
|
|
3056
3243
|
"article",
|
|
3057
3244
|
{
|
|
3245
|
+
ref: glowRef,
|
|
3058
3246
|
"aria-label": tile.name,
|
|
3059
3247
|
className: cn(
|
|
3060
|
-
"bg-muted relative overflow-hidden rounded-xl",
|
|
3248
|
+
"livo-studio-tile bg-muted relative overflow-hidden rounded-xl",
|
|
3061
3249
|
compact ? "aspect-video w-40 shrink-0 md:w-full" : tile.kind === "screen" ? "min-h-0 flex-1 md:min-h-48" : fill ? "size-full min-h-0 md:aspect-video md:h-auto" : "aspect-video"
|
|
3062
3250
|
),
|
|
3063
3251
|
children: [
|
|
3064
|
-
tile.kind === "idle" ? /* @__PURE__ */
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
) : /* @__PURE__ */ jsx("div", { className: "bg-background flex size-16 items-center justify-center rounded-full text-xl", children: tile.name.slice(0, 1).toUpperCase() }),
|
|
3073
|
-
/* @__PURE__ */ jsx(
|
|
3074
|
-
AudioWaveform,
|
|
3075
|
-
{
|
|
3076
|
-
track: tile.isSelf ? meeting.localAudioTrack() : null,
|
|
3077
|
-
reducedMotion
|
|
3078
|
-
}
|
|
3079
|
-
)
|
|
3080
|
-
] }) : /* @__PURE__ */ jsx(
|
|
3252
|
+
tile.kind === "idle" || !hasVideoTrack ? /* @__PURE__ */ jsx(
|
|
3253
|
+
IdlePersonTile,
|
|
3254
|
+
{
|
|
3255
|
+
tile,
|
|
3256
|
+
meeting,
|
|
3257
|
+
reducedMotion
|
|
3258
|
+
}
|
|
3259
|
+
) : /* @__PURE__ */ jsx(
|
|
3081
3260
|
StudioTileVideo,
|
|
3082
3261
|
{
|
|
3083
3262
|
meeting,
|
|
@@ -3087,6 +3266,13 @@ function StudioTile({
|
|
|
3087
3266
|
contain: tile.kind === "screen"
|
|
3088
3267
|
}
|
|
3089
3268
|
),
|
|
3269
|
+
/* @__PURE__ */ jsx(
|
|
3270
|
+
"div",
|
|
3271
|
+
{
|
|
3272
|
+
"aria-hidden": true,
|
|
3273
|
+
className: "livo-studio-tile-glow pointer-events-none absolute inset-0 z-20 rounded-xl"
|
|
3274
|
+
}
|
|
3275
|
+
),
|
|
3090
3276
|
/* @__PURE__ */ jsxs("div", { className: "absolute inset-x-0 bottom-0 z-10 flex items-end justify-between gap-2 bg-linear-to-t from-black/70 to-transparent p-2", children: [
|
|
3091
3277
|
/* @__PURE__ */ jsxs("p", { className: "min-w-0 truncate text-xs font-medium text-white", children: [
|
|
3092
3278
|
tile.name,
|
|
@@ -3169,6 +3355,37 @@ function TileStatusIcons({
|
|
|
3169
3355
|
tile.chatCanSend === false ? /* @__PURE__ */ jsx(StatusIcon, { label: labels.muteChat, children: /* @__PURE__ */ jsx(MessageSquareOff, {}) }) : null
|
|
3170
3356
|
] });
|
|
3171
3357
|
}
|
|
3358
|
+
function tileHasVideoTrack(meeting, tile) {
|
|
3359
|
+
if (tile.kind === "screen") {
|
|
3360
|
+
return meeting.participantScreenTrack(tile.id) != null;
|
|
3361
|
+
}
|
|
3362
|
+
if (tile.kind === "idle") return false;
|
|
3363
|
+
if (tile.isSelf) return meeting.localVideoTrack() != null;
|
|
3364
|
+
return meeting.participantVideoTrack(tile.id) != null;
|
|
3365
|
+
}
|
|
3366
|
+
function IdlePersonTile({
|
|
3367
|
+
tile,
|
|
3368
|
+
meeting,
|
|
3369
|
+
reducedMotion
|
|
3370
|
+
}) {
|
|
3371
|
+
return /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-3", children: [
|
|
3372
|
+
tile.picture ? /* @__PURE__ */ jsx(
|
|
3373
|
+
"img",
|
|
3374
|
+
{
|
|
3375
|
+
src: tile.picture,
|
|
3376
|
+
alt: "",
|
|
3377
|
+
className: "size-16 rounded-full object-cover"
|
|
3378
|
+
}
|
|
3379
|
+
) : /* @__PURE__ */ jsx("div", { className: "bg-background flex size-16 items-center justify-center rounded-full text-xl", children: tile.name.slice(0, 1).toUpperCase() }),
|
|
3380
|
+
/* @__PURE__ */ jsx(
|
|
3381
|
+
AudioWaveform,
|
|
3382
|
+
{
|
|
3383
|
+
track: tile.isSelf ? meeting.localAudioTrack() : null,
|
|
3384
|
+
reducedMotion
|
|
3385
|
+
}
|
|
3386
|
+
)
|
|
3387
|
+
] });
|
|
3388
|
+
}
|
|
3172
3389
|
function StatusIcon({
|
|
3173
3390
|
label,
|
|
3174
3391
|
children
|
|
@@ -3192,23 +3409,27 @@ function StudioTileVideo({
|
|
|
3192
3409
|
meeting.attachVideo(participantId, el, kind);
|
|
3193
3410
|
return () => meeting.attachVideo(participantId, null, kind);
|
|
3194
3411
|
}, [kind, meeting, participantId]);
|
|
3195
|
-
return /* @__PURE__ */
|
|
3196
|
-
"
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
"
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3412
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3413
|
+
/* @__PURE__ */ jsx("style", { children: STUDIO_VIDEO_MEDIA_STYLE }),
|
|
3414
|
+
/* @__PURE__ */ jsx(
|
|
3415
|
+
"video",
|
|
3416
|
+
{
|
|
3417
|
+
ref,
|
|
3418
|
+
autoPlay: true,
|
|
3419
|
+
playsInline: true,
|
|
3420
|
+
muted: true,
|
|
3421
|
+
disablePictureInPicture: true,
|
|
3422
|
+
disableRemotePlayback: true,
|
|
3423
|
+
controlsList: "nodownload noplaybackrate noremoteplayback",
|
|
3424
|
+
className: cn(
|
|
3425
|
+
"livo-studio-tile-video absolute inset-0 size-full",
|
|
3426
|
+
"pointer-events-none",
|
|
3427
|
+
contain ? "object-contain" : "object-cover",
|
|
3428
|
+
mirror && "-scale-x-100"
|
|
3429
|
+
)
|
|
3430
|
+
}
|
|
3431
|
+
)
|
|
3432
|
+
] });
|
|
3212
3433
|
}
|
|
3213
3434
|
function StudioStage({
|
|
3214
3435
|
participants,
|
|
@@ -3229,9 +3450,7 @@ function StudioStage({
|
|
|
3229
3450
|
onOpenPeople
|
|
3230
3451
|
}) {
|
|
3231
3452
|
const displayTiles = expandStudioTiles(participants);
|
|
3232
|
-
const stage = studioStageLayout(displayTiles
|
|
3233
|
-
activeSpeakerId: meeting.activeSpeakerId()
|
|
3234
|
-
});
|
|
3453
|
+
const stage = studioStageLayout(displayTiles);
|
|
3235
3454
|
const sharing = stage.spotlight.length > 0;
|
|
3236
3455
|
const overflowLabel = labels.stageOverflow.replace(
|
|
3237
3456
|
"{count}",
|
|
@@ -3302,6 +3521,32 @@ function StudioStage({
|
|
|
3302
3521
|
}
|
|
3303
3522
|
);
|
|
3304
3523
|
}
|
|
3524
|
+
function StudioDownloadAppDialog({
|
|
3525
|
+
open,
|
|
3526
|
+
onOpenChange,
|
|
3527
|
+
downloadUrl,
|
|
3528
|
+
labels
|
|
3529
|
+
}) {
|
|
3530
|
+
const href = downloadUrl?.trim() ?? "";
|
|
3531
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { children: [
|
|
3532
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
3533
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: labels.screenDownloadAppTitle }),
|
|
3534
|
+
/* @__PURE__ */ jsx(DialogDescription, { children: labels.screenUnsupportedDevice })
|
|
3535
|
+
] }),
|
|
3536
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
3537
|
+
/* @__PURE__ */ jsx(
|
|
3538
|
+
Button,
|
|
3539
|
+
{
|
|
3540
|
+
type: "button",
|
|
3541
|
+
variant: "outline",
|
|
3542
|
+
onClick: () => onOpenChange(false),
|
|
3543
|
+
children: labels.screenDownloadAppDismiss
|
|
3544
|
+
}
|
|
3545
|
+
),
|
|
3546
|
+
href ? /* @__PURE__ */ jsx(Button, { render: /* @__PURE__ */ jsx("a", { href, target: "_blank", rel: "noreferrer" }), children: labels.screenDownloadAppAction }) : /* @__PURE__ */ jsx(Button, { type: "button", disabled: true, children: labels.screenDownloadAppAction })
|
|
3547
|
+
] })
|
|
3548
|
+
] }) });
|
|
3549
|
+
}
|
|
3305
3550
|
function StudioToolbar({
|
|
3306
3551
|
session,
|
|
3307
3552
|
meeting,
|
|
@@ -3556,7 +3801,8 @@ function StudioRoom({
|
|
|
3556
3801
|
windowGate,
|
|
3557
3802
|
initialCameraOn = true,
|
|
3558
3803
|
initialMuted = false,
|
|
3559
|
-
initialDevices
|
|
3804
|
+
initialDevices,
|
|
3805
|
+
appDownloadUrl
|
|
3560
3806
|
}) {
|
|
3561
3807
|
const [cameraOn, setCameraOn] = useState(initialCameraOn);
|
|
3562
3808
|
const [screenOn, setScreenOn] = useState(false);
|
|
@@ -3564,7 +3810,7 @@ function StudioRoom({
|
|
|
3564
3810
|
const [screenRequestPending, setScreenRequestPending] = useState(false);
|
|
3565
3811
|
const [pending, setPending] = useState(false);
|
|
3566
3812
|
const [publishing, setPublishing] = useState(false);
|
|
3567
|
-
const [
|
|
3813
|
+
const [tick2, setTick] = useState(0);
|
|
3568
3814
|
const [leaveState, setLeaveState] = useState(null);
|
|
3569
3815
|
const [sideTab, setSideTab] = useState(null);
|
|
3570
3816
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
@@ -3577,6 +3823,7 @@ function StudioRoom({
|
|
|
3577
3823
|
);
|
|
3578
3824
|
const [egressError, setEgressError] = useState(null);
|
|
3579
3825
|
const [shareGrant, setShareGrant] = useState("idle");
|
|
3826
|
+
const [downloadAppOpen, setDownloadAppOpen] = useState(false);
|
|
3580
3827
|
const [connectTimedOut, setConnectTimedOut] = useState(false);
|
|
3581
3828
|
const [screenPermission, setScreenPermission] = useState(
|
|
3582
3829
|
() => meeting.screenSharePermission()
|
|
@@ -3618,6 +3865,11 @@ function StudioRoom({
|
|
|
3618
3865
|
const roomState = leaveState ?? meeting.roomState();
|
|
3619
3866
|
const sessionOver = isStudioSessionOver(session.stream.status, roomState);
|
|
3620
3867
|
const rejected = isStudioRejected(roomState);
|
|
3868
|
+
const connectionState = useMemo(
|
|
3869
|
+
() => meeting.connectionState(),
|
|
3870
|
+
[meeting, tick2]
|
|
3871
|
+
);
|
|
3872
|
+
const guestConnectFailed = session.role === "guest" && (connectionState === "failed" || connectionState === "connecting" && connectTimedOut);
|
|
3621
3873
|
useBeforeUnload(active && !sessionOver);
|
|
3622
3874
|
useWakeLock(active && !sessionOver);
|
|
3623
3875
|
useEffect(() => {
|
|
@@ -3781,12 +4033,13 @@ function StudioRoom({
|
|
|
3781
4033
|
void meeting.setScreenShareEnabled(false);
|
|
3782
4034
|
}, [guestOffStage, meeting]);
|
|
3783
4035
|
useEffect(() => {
|
|
3784
|
-
if (!sessionOver && !rejected) return;
|
|
4036
|
+
if (!sessionOver && !rejected && !guestConnectFailed) return;
|
|
4037
|
+
pip.close();
|
|
3785
4038
|
setShareGrant("idle");
|
|
3786
4039
|
void meeting.setAudioEnabled(false);
|
|
3787
4040
|
void meeting.setVideoEnabled(false);
|
|
3788
4041
|
void meeting.setScreenShareEnabled(false);
|
|
3789
|
-
}, [meeting, rejected, sessionOver]);
|
|
4042
|
+
}, [guestConnectFailed, meeting, pip.close, rejected, sessionOver]);
|
|
3790
4043
|
useEffect(() => {
|
|
3791
4044
|
return meeting.subscribeMedia(() => {
|
|
3792
4045
|
const self2 = meeting.participants().find((row) => row.isSelf);
|
|
@@ -3808,7 +4061,7 @@ function StudioRoom({
|
|
|
3808
4061
|
});
|
|
3809
4062
|
}, [meeting]);
|
|
3810
4063
|
const sawParticipants = useRef(false);
|
|
3811
|
-
const participants = useMemo(() => meeting.participants(), [meeting,
|
|
4064
|
+
const participants = useMemo(() => meeting.participants(), [meeting, tick2]);
|
|
3812
4065
|
useEffect(() => {
|
|
3813
4066
|
const other = duplicateSelfParticipant(participants);
|
|
3814
4067
|
if (!other) return;
|
|
@@ -3823,29 +4076,25 @@ function StudioRoom({
|
|
|
3823
4076
|
() => participants.filter((row) => isOnStage(row.stageStatus)),
|
|
3824
4077
|
[participants]
|
|
3825
4078
|
);
|
|
3826
|
-
const waitlisted = useMemo(() => meeting.waitlisted(), [meeting,
|
|
3827
|
-
const connectionState = useMemo(
|
|
3828
|
-
() => meeting.connectionState(),
|
|
3829
|
-
[meeting, tick]
|
|
3830
|
-
);
|
|
4079
|
+
const waitlisted = useMemo(() => meeting.waitlisted(), [meeting, tick2]);
|
|
3831
4080
|
const lastMeetingError = useMemo(
|
|
3832
4081
|
() => meeting.lastMeetingError(),
|
|
3833
|
-
[meeting,
|
|
4082
|
+
[meeting, tick2]
|
|
3834
4083
|
);
|
|
3835
4084
|
const canManageStage = meeting.canManageStage();
|
|
3836
4085
|
const stageEnabled = meeting.stageEnabled();
|
|
3837
4086
|
const stageRequests = useMemo(
|
|
3838
4087
|
() => canManageStage ? meeting.stageRequests() : [],
|
|
3839
|
-
[canManageStage, meeting,
|
|
4088
|
+
[canManageStage, meeting, tick2]
|
|
3840
4089
|
);
|
|
3841
|
-
const chatMessages = useMemo(() => meeting.chatMessages(), [meeting,
|
|
4090
|
+
const chatMessages = useMemo(() => meeting.chatMessages(), [meeting, tick2]);
|
|
3842
4091
|
const inWaitingRoom = meeting.inWaitingRoom();
|
|
3843
4092
|
const screenAvailable = canStartScreenShare(participants);
|
|
3844
4093
|
const durableScreenBlock = screenShareBlock(
|
|
3845
4094
|
captureSupported,
|
|
3846
4095
|
screenPermission
|
|
3847
4096
|
);
|
|
3848
|
-
const showScreenControl = durableScreenBlock
|
|
4097
|
+
const showScreenControl = showScreenShareControl(durableScreenBlock);
|
|
3849
4098
|
const canGoLive = session.role === "moderator" && canPublish(session.stream.status);
|
|
3850
4099
|
const showPublishButton = session.role === "moderator" && !onAir && session.stream.status !== "ended" && session.stream.status !== "error";
|
|
3851
4100
|
const self = participants.find((row) => row.isSelf);
|
|
@@ -3941,17 +4190,10 @@ function StudioRoom({
|
|
|
3941
4190
|
}, [connectionState, session.role]);
|
|
3942
4191
|
useEffect(() => {
|
|
3943
4192
|
if (inWaitingRoom || screenNoticePrimed.current) return;
|
|
3944
|
-
if (
|
|
4193
|
+
if (durableScreenBlock !== "permission") return;
|
|
3945
4194
|
screenNoticePrimed.current = true;
|
|
3946
|
-
toast.message(
|
|
3947
|
-
|
|
3948
|
-
);
|
|
3949
|
-
}, [
|
|
3950
|
-
durableScreenBlock,
|
|
3951
|
-
inWaitingRoom,
|
|
3952
|
-
labels.screenNotAllowed,
|
|
3953
|
-
labels.screenUnsupportedDevice
|
|
3954
|
-
]);
|
|
4195
|
+
toast.message(labels.screenNotAllowed);
|
|
4196
|
+
}, [durableScreenBlock, inWaitingRoom, labels.screenNotAllowed]);
|
|
3955
4197
|
useEffect(() => {
|
|
3956
4198
|
return meeting.subscribeBroadcast((message) => {
|
|
3957
4199
|
if (message.type === "host-media") {
|
|
@@ -4060,6 +4302,10 @@ function StudioRoom({
|
|
|
4060
4302
|
setCameraOn((current) => !current);
|
|
4061
4303
|
}, []);
|
|
4062
4304
|
const onToggleScreen = useCallback(() => {
|
|
4305
|
+
if (durableScreenBlock === "device") {
|
|
4306
|
+
setDownloadAppOpen(true);
|
|
4307
|
+
return;
|
|
4308
|
+
}
|
|
4063
4309
|
if (!canShareFreely || screenRequestPending) return;
|
|
4064
4310
|
if (!screenOn && !canStartScreenShare(meeting.participants())) {
|
|
4065
4311
|
toast.message(labels.screenBusy);
|
|
@@ -4069,12 +4315,17 @@ function StudioRoom({
|
|
|
4069
4315
|
setScreenOn((current) => !current);
|
|
4070
4316
|
}, [
|
|
4071
4317
|
canShareFreely,
|
|
4318
|
+
durableScreenBlock,
|
|
4072
4319
|
labels.screenBusy,
|
|
4073
4320
|
meeting,
|
|
4074
4321
|
screenOn,
|
|
4075
4322
|
screenRequestPending
|
|
4076
4323
|
]);
|
|
4077
4324
|
const onAskToShare = useCallback(async () => {
|
|
4325
|
+
if (durableScreenBlock === "device") {
|
|
4326
|
+
setDownloadAppOpen(true);
|
|
4327
|
+
return;
|
|
4328
|
+
}
|
|
4078
4329
|
const local = meeting.participants().find((row) => row.isSelf);
|
|
4079
4330
|
if (!local) return;
|
|
4080
4331
|
try {
|
|
@@ -4091,7 +4342,12 @@ function StudioRoom({
|
|
|
4091
4342
|
} catch {
|
|
4092
4343
|
toast.error(labels.shareRequestError);
|
|
4093
4344
|
}
|
|
4094
|
-
}, [
|
|
4345
|
+
}, [
|
|
4346
|
+
durableScreenBlock,
|
|
4347
|
+
labels.shareRequestError,
|
|
4348
|
+
labels.shareRequestSent,
|
|
4349
|
+
meeting
|
|
4350
|
+
]);
|
|
4095
4351
|
const verifyGrantTookEffect = useCallback(
|
|
4096
4352
|
(userId, retried = false) => {
|
|
4097
4353
|
const id = window.setTimeout(() => {
|
|
@@ -4254,7 +4510,6 @@ function StudioRoom({
|
|
|
4254
4510
|
}
|
|
4255
4511
|
) });
|
|
4256
4512
|
}
|
|
4257
|
-
const guestConnectFailed = session.role === "guest" && (connectionState === "failed" || connectionState === "connecting" && connectTimedOut);
|
|
4258
4513
|
if (guestConnectFailed) {
|
|
4259
4514
|
const detail = [meeting.roomState(), lastMeetingError].filter((value) => Boolean(value)).join(" \u2014 ");
|
|
4260
4515
|
return /* @__PURE__ */ jsx("div", { className: "flex h-dvh flex-col items-center justify-center p-6", children: /* @__PURE__ */ jsx(
|
|
@@ -4588,7 +4843,21 @@ function StudioRoom({
|
|
|
4588
4843
|
}
|
|
4589
4844
|
),
|
|
4590
4845
|
pip.pipWindow.document.body
|
|
4591
|
-
) : null
|
|
4846
|
+
) : null,
|
|
4847
|
+
/* @__PURE__ */ jsx(
|
|
4848
|
+
StudioDownloadAppDialog,
|
|
4849
|
+
{
|
|
4850
|
+
open: downloadAppOpen,
|
|
4851
|
+
onOpenChange: setDownloadAppOpen,
|
|
4852
|
+
downloadUrl: appDownloadUrl,
|
|
4853
|
+
labels: {
|
|
4854
|
+
screenDownloadAppTitle: labels.screenDownloadAppTitle,
|
|
4855
|
+
screenUnsupportedDevice: labels.screenUnsupportedDevice,
|
|
4856
|
+
screenDownloadAppAction: labels.screenDownloadAppAction,
|
|
4857
|
+
screenDownloadAppDismiss: labels.screenDownloadAppDismiss
|
|
4858
|
+
}
|
|
4859
|
+
}
|
|
4860
|
+
)
|
|
4592
4861
|
] });
|
|
4593
4862
|
}
|
|
4594
4863
|
function isOverconstrained(error) {
|
|
@@ -5757,6 +6026,9 @@ function studioMeetingFromRtk(raw) {
|
|
|
5757
6026
|
localVideoTrack() {
|
|
5758
6027
|
return meeting.self?.videoTrack ?? null;
|
|
5759
6028
|
},
|
|
6029
|
+
participantAudioTrack(participantId) {
|
|
6030
|
+
return participantFor(participantId)?.audioTrack ?? null;
|
|
6031
|
+
},
|
|
5760
6032
|
participantVideoTrack(participantId) {
|
|
5761
6033
|
return participantFor(participantId)?.videoTrack ?? null;
|
|
5762
6034
|
},
|
|
@@ -6128,7 +6400,8 @@ function LivoHostStudio({
|
|
|
6128
6400
|
embed = false,
|
|
6129
6401
|
chrome,
|
|
6130
6402
|
playerOrigin,
|
|
6131
|
-
theme
|
|
6403
|
+
theme,
|
|
6404
|
+
appDownloadUrl
|
|
6132
6405
|
}) {
|
|
6133
6406
|
const reducedMotion = usePrefersReducedMotion();
|
|
6134
6407
|
const [session, setSession] = useState(null);
|
|
@@ -6208,7 +6481,8 @@ function LivoHostStudio({
|
|
|
6208
6481
|
labels,
|
|
6209
6482
|
embed,
|
|
6210
6483
|
chrome,
|
|
6211
|
-
playerOrigin
|
|
6484
|
+
playerOrigin,
|
|
6485
|
+
appDownloadUrl
|
|
6212
6486
|
}
|
|
6213
6487
|
);
|
|
6214
6488
|
}
|
|
@@ -6221,7 +6495,8 @@ function LivoGuestStudio({
|
|
|
6221
6495
|
lobbyLabels,
|
|
6222
6496
|
embed = false,
|
|
6223
6497
|
playerOrigin,
|
|
6224
|
-
theme
|
|
6498
|
+
theme,
|
|
6499
|
+
appDownloadUrl
|
|
6225
6500
|
}) {
|
|
6226
6501
|
const reducedMotion = usePrefersReducedMotion();
|
|
6227
6502
|
const [session, setSession] = useState(null);
|
|
@@ -6407,7 +6682,8 @@ function LivoGuestStudio({
|
|
|
6407
6682
|
playerOrigin,
|
|
6408
6683
|
initialCameraOn: cameraOn,
|
|
6409
6684
|
initialMuted: muted,
|
|
6410
|
-
initialDevices: deviceSelection
|
|
6685
|
+
initialDevices: deviceSelection,
|
|
6686
|
+
appDownloadUrl
|
|
6411
6687
|
}
|
|
6412
6688
|
);
|
|
6413
6689
|
}
|
|
@@ -6512,6 +6788,9 @@ function studioRoomLabels(t) {
|
|
|
6512
6788
|
pipTitle: t("pipTitle"),
|
|
6513
6789
|
pipError: t("pipError"),
|
|
6514
6790
|
screenUnsupportedDevice: t("screenUnsupportedDevice"),
|
|
6791
|
+
screenDownloadAppTitle: t("screenDownloadAppTitle"),
|
|
6792
|
+
screenDownloadAppAction: t("screenDownloadAppAction"),
|
|
6793
|
+
screenDownloadAppDismiss: t("screenDownloadAppDismiss"),
|
|
6515
6794
|
screenNotAllowed: t("screenNotAllowed"),
|
|
6516
6795
|
askToShare: t("askToShare"),
|
|
6517
6796
|
shareRequested: t("shareRequested"),
|