@livo-tv/sdk 1.4.0 → 1.6.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 +1250 -371
- package/dist/studio/index.cjs.map +1 -1
- package/dist/studio/index.d.cts +59 -4
- package/dist/studio/index.d.ts +59 -4
- package/dist/studio/index.js +1251 -373
- package/dist/studio/index.js.map +1 -1
- package/dist/styles.css +5 -0
- package/package.json +3 -2
package/dist/studio/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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, MonitorUp,
|
|
4
|
+
import { Loader2, Share2, MicOff, Mic, Video, VideoOff, Settings, X, Link2, EyeOff, Eye, MonitorUp, Captions, MoreVertical, Maximize, PictureInPicture2, MessageSquare, Users, PhoneOff, Undo2, Volume2, ChevronUp, XIcon, Pin, MessageSquareOff } from 'lucide-react';
|
|
5
|
+
import { CaptionOverlay, EMPTY_LIVE_CAPTION_PLAYOUT, liveCaptionsUrl, loadLiveCaptions, mergeLiveCaptionCues, advanceLiveCaptionPlayout } from '@livo-tv/blocks/video';
|
|
5
6
|
import { toast } from 'sonner';
|
|
6
7
|
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
|
|
7
8
|
import { clsx } from 'clsx';
|
|
@@ -518,52 +519,38 @@ function canTakeOffAir(tiles, participantId, onAir) {
|
|
|
518
519
|
(tile) => tile.id !== participantId && isOnStage(tile.stageStatus)
|
|
519
520
|
);
|
|
520
521
|
}
|
|
522
|
+
function pipSpotlightFrom(tile, kind) {
|
|
523
|
+
return {
|
|
524
|
+
participantId: tile.id,
|
|
525
|
+
name: tile.name,
|
|
526
|
+
kind,
|
|
527
|
+
picture: tile.picture
|
|
528
|
+
};
|
|
529
|
+
}
|
|
521
530
|
function resolveStudioPipSpotlight(tiles, activeSpeakerId) {
|
|
522
531
|
const visible = tiles.filter((tile) => isOnStage(tile.stageStatus));
|
|
523
532
|
const pinned = visible.find((tile) => tile.pinned === true);
|
|
524
533
|
if (pinned) {
|
|
525
|
-
return
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
534
|
+
return pipSpotlightFrom(
|
|
535
|
+
pinned,
|
|
536
|
+
pinned.screenShareEnabled ? "screen" : pinned.videoEnabled ? "camera" : "idle"
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
const speaker = activeSpeakerId ? visible.find((tile) => tile.id === activeSpeakerId) : void 0;
|
|
540
|
+
if (speaker) {
|
|
541
|
+
return pipSpotlightFrom(speaker, speaker.videoEnabled ? "camera" : "idle");
|
|
531
542
|
}
|
|
532
543
|
const sharing = visible.find((tile) => tile.screenShareEnabled);
|
|
533
544
|
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
|
-
};
|
|
545
|
+
return pipSpotlightFrom(sharing, "screen");
|
|
549
546
|
}
|
|
550
547
|
const remoteCam = visible.find((tile) => !tile.isSelf && tile.videoEnabled);
|
|
551
548
|
if (remoteCam) {
|
|
552
|
-
return
|
|
553
|
-
participantId: remoteCam.id,
|
|
554
|
-
name: remoteCam.name,
|
|
555
|
-
kind: "camera",
|
|
556
|
-
picture: remoteCam.picture
|
|
557
|
-
};
|
|
549
|
+
return pipSpotlightFrom(remoteCam, "camera");
|
|
558
550
|
}
|
|
559
551
|
const self = visible.find((tile) => tile.isSelf);
|
|
560
552
|
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
|
-
};
|
|
553
|
+
return pipSpotlightFrom(self, self.videoEnabled ? "camera" : "idle");
|
|
567
554
|
}
|
|
568
555
|
function resolveStudioPipSelfView(tiles, spotlight) {
|
|
569
556
|
const self = tiles.find((tile) => tile.isSelf);
|
|
@@ -682,6 +669,9 @@ function screenShareBlock(captureSupported, permission) {
|
|
|
682
669
|
if (permission === "NOT_ALLOWED") return "permission";
|
|
683
670
|
return null;
|
|
684
671
|
}
|
|
672
|
+
function showScreenShareControl(block) {
|
|
673
|
+
return block !== "permission";
|
|
674
|
+
}
|
|
685
675
|
|
|
686
676
|
// src/studio/lib/parse.ts
|
|
687
677
|
function isRecord(value) {
|
|
@@ -701,7 +691,14 @@ function parseStudioStreamSummary(value) {
|
|
|
701
691
|
const status = readString(value, "status");
|
|
702
692
|
if (!id || !title || !status) return null;
|
|
703
693
|
const encoderLostAt = isRecord(value) ? typeof value.encoderLostAt === "number" ? value.encoderLostAt : value.encoderLostAt === null ? null : void 0 : void 0;
|
|
704
|
-
|
|
694
|
+
const captionsEnabled = isRecord(value) ? typeof value.captionsEnabled === "boolean" ? value.captionsEnabled : typeof value.captionsUrl === "string" && value.captionsUrl.length > 0 ? true : value.captionsUrl === null ? false : void 0 : void 0;
|
|
695
|
+
return {
|
|
696
|
+
id,
|
|
697
|
+
title,
|
|
698
|
+
status,
|
|
699
|
+
...encoderLostAt !== void 0 ? { encoderLostAt } : {},
|
|
700
|
+
...captionsEnabled !== void 0 ? { captionsEnabled } : {}
|
|
701
|
+
};
|
|
705
702
|
}
|
|
706
703
|
function parseStudioSession(value) {
|
|
707
704
|
if (!isRecord(value)) return null;
|
|
@@ -713,7 +710,9 @@ function parseStudioSession(value) {
|
|
|
713
710
|
const fromFull = isRecord(value.stream) ? parseStudioStreamSummary({
|
|
714
711
|
id: value.stream.id,
|
|
715
712
|
title: value.stream.title,
|
|
716
|
-
status: value.stream.status
|
|
713
|
+
status: value.stream.status,
|
|
714
|
+
captionsEnabled: value.stream.captionsEnabled,
|
|
715
|
+
captionsUrl: value.stream.captionsUrl
|
|
717
716
|
}) : null;
|
|
718
717
|
const stream = fromSummary ?? fromFull;
|
|
719
718
|
if (!stream) return null;
|
|
@@ -746,6 +745,14 @@ function parsePublicStreamStatus(value) {
|
|
|
746
745
|
const status = readString(nested, "status");
|
|
747
746
|
return status;
|
|
748
747
|
}
|
|
748
|
+
function parsePublicStreamMeta(value) {
|
|
749
|
+
if (!isRecord(value)) return null;
|
|
750
|
+
const nested = isRecord(value.stream) ? value.stream : value;
|
|
751
|
+
const status = readString(nested, "status");
|
|
752
|
+
if (!status) return null;
|
|
753
|
+
const captionsEnabled = typeof nested.captionsEnabled === "boolean" ? nested.captionsEnabled : typeof nested.captionsUrl === "string" && nested.captionsUrl.length > 0;
|
|
754
|
+
return { status, captionsEnabled };
|
|
755
|
+
}
|
|
749
756
|
function studioErrorCode(error) {
|
|
750
757
|
if (!error || typeof error !== "object") return null;
|
|
751
758
|
const body = "body" in error ? error.body : null;
|
|
@@ -1464,6 +1471,19 @@ function DialogHeader({ className, ...props }) {
|
|
|
1464
1471
|
}
|
|
1465
1472
|
);
|
|
1466
1473
|
}
|
|
1474
|
+
function DialogFooter({ className, ...props }) {
|
|
1475
|
+
return /* @__PURE__ */ jsx(
|
|
1476
|
+
"div",
|
|
1477
|
+
{
|
|
1478
|
+
"data-slot": "dialog-footer",
|
|
1479
|
+
className: cn(
|
|
1480
|
+
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
1481
|
+
className
|
|
1482
|
+
),
|
|
1483
|
+
...props
|
|
1484
|
+
}
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1467
1487
|
function DialogTitle({
|
|
1468
1488
|
className,
|
|
1469
1489
|
...props
|
|
@@ -1612,6 +1632,211 @@ async function playSpeakerTest(deviceId) {
|
|
|
1612
1632
|
await ctx3.close();
|
|
1613
1633
|
}
|
|
1614
1634
|
}
|
|
1635
|
+
|
|
1636
|
+
// src/studio/lib/caption-prefs.ts
|
|
1637
|
+
var STUDIO_CAPTION_LOCALES = ["en", "es", "pt"];
|
|
1638
|
+
var DEFAULT_STUDIO_CAPTION_PREFS = {
|
|
1639
|
+
locale: "en",
|
|
1640
|
+
size: "m"
|
|
1641
|
+
};
|
|
1642
|
+
function isStudioCaptionLocale(value) {
|
|
1643
|
+
return STUDIO_CAPTION_LOCALES.includes(value);
|
|
1644
|
+
}
|
|
1645
|
+
var STORAGE_KEY = "livo-studio-captions";
|
|
1646
|
+
function defaultStorage2() {
|
|
1647
|
+
if (typeof window === "undefined") return null;
|
|
1648
|
+
try {
|
|
1649
|
+
return window.localStorage;
|
|
1650
|
+
} catch {
|
|
1651
|
+
return null;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
function isCaptionSize(value) {
|
|
1655
|
+
return value === "s" || value === "m" || value === "l";
|
|
1656
|
+
}
|
|
1657
|
+
function captionSizeClass(size) {
|
|
1658
|
+
switch (size) {
|
|
1659
|
+
case "s":
|
|
1660
|
+
return "text-[length:clamp(10px,2.1cqh,0.75rem)]";
|
|
1661
|
+
case "m":
|
|
1662
|
+
return "text-[length:clamp(10px,2.6cqh,0.875rem)]";
|
|
1663
|
+
case "l":
|
|
1664
|
+
return "text-[length:clamp(12px,3.2cqh,1.05rem)]";
|
|
1665
|
+
default: {
|
|
1666
|
+
const _exhaustive = size;
|
|
1667
|
+
return _exhaustive;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
function readStudioCaptionPrefs(storage = defaultStorage2(), fallbackLocale = "en") {
|
|
1672
|
+
const raw = storage?.getItem(STORAGE_KEY);
|
|
1673
|
+
if (!raw) {
|
|
1674
|
+
return { ...DEFAULT_STUDIO_CAPTION_PREFS, locale: fallbackLocale };
|
|
1675
|
+
}
|
|
1676
|
+
try {
|
|
1677
|
+
const parsed = JSON.parse(raw);
|
|
1678
|
+
const locale = typeof parsed.locale === "string" && isStudioCaptionLocale(parsed.locale) ? parsed.locale : fallbackLocale;
|
|
1679
|
+
const size = typeof parsed.size === "string" && isCaptionSize(parsed.size) ? parsed.size : "m";
|
|
1680
|
+
return { locale, size };
|
|
1681
|
+
} catch {
|
|
1682
|
+
return { ...DEFAULT_STUDIO_CAPTION_PREFS, locale: fallbackLocale };
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
function persistStudioCaptionPrefs(prefs, storage = defaultStorage2()) {
|
|
1686
|
+
storage?.setItem(STORAGE_KEY, JSON.stringify(prefs));
|
|
1687
|
+
}
|
|
1688
|
+
function studioCaptionsBaseUrl(apiUrl, streamId) {
|
|
1689
|
+
if (!apiUrl) return void 0;
|
|
1690
|
+
return `${apiUrl.replace(/\/$/, "")}/public/streams/${encodeURIComponent(streamId)}/captions`;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// src/studio/lib/video-effects.ts
|
|
1694
|
+
var DEFAULT_STUDIO_VIDEO_EFFECTS = {
|
|
1695
|
+
background: "none",
|
|
1696
|
+
blurStrength: 50,
|
|
1697
|
+
filter: "none"
|
|
1698
|
+
};
|
|
1699
|
+
var FILTER_CSS = {
|
|
1700
|
+
none: "",
|
|
1701
|
+
grayscale: "grayscale(1)",
|
|
1702
|
+
sepia: "sepia(0.85)",
|
|
1703
|
+
warm: "sepia(0.35) saturate(1.2) hue-rotate(-10deg)",
|
|
1704
|
+
cool: "saturate(0.9) hue-rotate(15deg) brightness(1.05)",
|
|
1705
|
+
bright: "brightness(1.15) contrast(1.08)"
|
|
1706
|
+
};
|
|
1707
|
+
function defaultStudioEffectBackgrounds(cdnBase = "https://hls.livo.tv") {
|
|
1708
|
+
const root = cdnBase.replace(/\/$/, "");
|
|
1709
|
+
return [
|
|
1710
|
+
{
|
|
1711
|
+
id: "office",
|
|
1712
|
+
url: `${root}/env/backgrounds/office.jpg`,
|
|
1713
|
+
label: "Office"
|
|
1714
|
+
},
|
|
1715
|
+
{
|
|
1716
|
+
id: "lounge",
|
|
1717
|
+
url: `${root}/env/backgrounds/lounge.jpg`,
|
|
1718
|
+
label: "Lounge"
|
|
1719
|
+
},
|
|
1720
|
+
{
|
|
1721
|
+
id: "studio",
|
|
1722
|
+
url: `${root}/env/backgrounds/studio.jpg`,
|
|
1723
|
+
label: "Studio"
|
|
1724
|
+
},
|
|
1725
|
+
{ id: "sky", url: `${root}/env/backgrounds/sky.jpg`, label: "Sky" },
|
|
1726
|
+
{
|
|
1727
|
+
id: "forest",
|
|
1728
|
+
url: `${root}/env/backgrounds/forest.jpg`,
|
|
1729
|
+
label: "Forest"
|
|
1730
|
+
},
|
|
1731
|
+
{
|
|
1732
|
+
id: "abstract",
|
|
1733
|
+
url: `${root}/env/backgrounds/abstract.jpg`,
|
|
1734
|
+
label: "Abstract"
|
|
1735
|
+
}
|
|
1736
|
+
];
|
|
1737
|
+
}
|
|
1738
|
+
function createFilterVideoMiddleware(filter) {
|
|
1739
|
+
const css = FILTER_CSS[filter];
|
|
1740
|
+
if (!css) return null;
|
|
1741
|
+
return async () => {
|
|
1742
|
+
const scratch = document.createElement("canvas");
|
|
1743
|
+
return async (canvas, ctx3) => {
|
|
1744
|
+
if (scratch.width !== canvas.width || scratch.height !== canvas.height) {
|
|
1745
|
+
scratch.width = canvas.width;
|
|
1746
|
+
scratch.height = canvas.height;
|
|
1747
|
+
}
|
|
1748
|
+
const copy = scratch.getContext("2d");
|
|
1749
|
+
if (!copy) return;
|
|
1750
|
+
copy.clearRect(0, 0, scratch.width, scratch.height);
|
|
1751
|
+
copy.drawImage(canvas, 0, 0);
|
|
1752
|
+
ctx3.save();
|
|
1753
|
+
ctx3.filter = css;
|
|
1754
|
+
ctx3.drawImage(scratch, 0, 0);
|
|
1755
|
+
ctx3.restore();
|
|
1756
|
+
};
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
async function loadBackgroundTransformer(meeting) {
|
|
1760
|
+
const mod = await import('@cloudflare/realtimekit-virtual-background');
|
|
1761
|
+
const Init = mod.default ?? mod.RealtimeKitVideoBackgroundTransformer ?? null;
|
|
1762
|
+
if (!Init) return null;
|
|
1763
|
+
return Init.init({ meeting });
|
|
1764
|
+
}
|
|
1765
|
+
async function applyStudioVideoEffects(options) {
|
|
1766
|
+
const self = options.self;
|
|
1767
|
+
if (!self?.removeAllVideoMiddlewares || !self.addVideoMiddleware) return;
|
|
1768
|
+
await self.removeAllVideoMiddlewares();
|
|
1769
|
+
const needsSegmentation = options.config.background !== "none";
|
|
1770
|
+
if (needsSegmentation) {
|
|
1771
|
+
await self.setVideoMiddlewareGlobalConfig?.({
|
|
1772
|
+
disablePerFrameCanvasRendering: true
|
|
1773
|
+
});
|
|
1774
|
+
try {
|
|
1775
|
+
const transformer = await loadBackgroundTransformer(options.meeting);
|
|
1776
|
+
if (transformer && options.config.background === "blur") {
|
|
1777
|
+
const strength = Math.min(
|
|
1778
|
+
100,
|
|
1779
|
+
Math.max(0, options.config.blurStrength)
|
|
1780
|
+
);
|
|
1781
|
+
await self.addVideoMiddleware(
|
|
1782
|
+
await transformer.createBackgroundBlurVideoMiddleware(strength)
|
|
1783
|
+
);
|
|
1784
|
+
} else if (transformer && options.config.background === "image" && options.config.imageUrl) {
|
|
1785
|
+
await self.addVideoMiddleware(
|
|
1786
|
+
await transformer.createStaticBackgroundVideoMiddleware(
|
|
1787
|
+
options.config.imageUrl
|
|
1788
|
+
)
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
} catch {
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
const filter = createFilterVideoMiddleware(options.config.filter);
|
|
1795
|
+
if (filter) await self.addVideoMiddleware(filter);
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
// src/studio/lib/effects-prefs.ts
|
|
1799
|
+
var STORAGE_KEY2 = "livo-studio-video-effects";
|
|
1800
|
+
function defaultStorage3() {
|
|
1801
|
+
if (typeof window === "undefined") return null;
|
|
1802
|
+
try {
|
|
1803
|
+
return window.localStorage;
|
|
1804
|
+
} catch {
|
|
1805
|
+
return null;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
var BACKGROUNDS = ["none", "blur", "image"];
|
|
1809
|
+
var FILTERS = [
|
|
1810
|
+
"none",
|
|
1811
|
+
"grayscale",
|
|
1812
|
+
"sepia",
|
|
1813
|
+
"warm",
|
|
1814
|
+
"cool",
|
|
1815
|
+
"bright"
|
|
1816
|
+
];
|
|
1817
|
+
function isBackground(value) {
|
|
1818
|
+
return BACKGROUNDS.includes(value);
|
|
1819
|
+
}
|
|
1820
|
+
function isFilter(value) {
|
|
1821
|
+
return FILTERS.includes(value);
|
|
1822
|
+
}
|
|
1823
|
+
function readStudioVideoEffects(storage = defaultStorage3()) {
|
|
1824
|
+
const raw = storage?.getItem(STORAGE_KEY2);
|
|
1825
|
+
if (!raw) return { ...DEFAULT_STUDIO_VIDEO_EFFECTS };
|
|
1826
|
+
try {
|
|
1827
|
+
const parsed = JSON.parse(raw);
|
|
1828
|
+
const background = typeof parsed.background === "string" && isBackground(parsed.background) ? parsed.background : "none";
|
|
1829
|
+
const filter = typeof parsed.filter === "string" && isFilter(parsed.filter) ? parsed.filter : "none";
|
|
1830
|
+
const blurStrength = typeof parsed.blurStrength === "number" && Number.isFinite(parsed.blurStrength) ? Math.min(100, Math.max(0, parsed.blurStrength)) : 50;
|
|
1831
|
+
const imageUrl = typeof parsed.imageUrl === "string" && parsed.imageUrl ? parsed.imageUrl : void 0;
|
|
1832
|
+
return { background, blurStrength, imageUrl, filter };
|
|
1833
|
+
} catch {
|
|
1834
|
+
return { ...DEFAULT_STUDIO_VIDEO_EFFECTS };
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
function persistStudioVideoEffects(config, storage = defaultStorage3()) {
|
|
1838
|
+
storage?.setItem(STORAGE_KEY2, JSON.stringify(config));
|
|
1839
|
+
}
|
|
1615
1840
|
function StudioDeviceSelectGroup({
|
|
1616
1841
|
devices,
|
|
1617
1842
|
selected,
|
|
@@ -1690,7 +1915,11 @@ function StudioSettingsDialog({
|
|
|
1690
1915
|
meeting,
|
|
1691
1916
|
labels,
|
|
1692
1917
|
reducedMotion,
|
|
1693
|
-
cameraOn
|
|
1918
|
+
cameraOn,
|
|
1919
|
+
captionPrefs,
|
|
1920
|
+
onCaptionPrefsChange,
|
|
1921
|
+
captionsAvailable,
|
|
1922
|
+
effectBackgrounds
|
|
1694
1923
|
}) {
|
|
1695
1924
|
const devices = useMediaDevices(open ? 1 : 0, open);
|
|
1696
1925
|
const [saved, setSaved] = useState(
|
|
@@ -1702,11 +1931,16 @@ function StudioSettingsDialog({
|
|
|
1702
1931
|
const [displayName, setDisplayName] = useState("");
|
|
1703
1932
|
const [savedDisplayName, setSavedDisplayName] = useState("");
|
|
1704
1933
|
const [savingDisplayName, setSavingDisplayName] = useState(false);
|
|
1934
|
+
const [tab, setTab] = useState("general");
|
|
1935
|
+
const [effects, setEffects] = useState(
|
|
1936
|
+
() => meeting.videoEffects()
|
|
1937
|
+
);
|
|
1705
1938
|
const { effective } = resolveDeviceSelection(saved, devices);
|
|
1706
1939
|
useEffect(() => {
|
|
1707
1940
|
if (!open) return;
|
|
1708
1941
|
setSaved(readStudioDeviceSelection());
|
|
1709
|
-
|
|
1942
|
+
setEffects(meeting.videoEffects());
|
|
1943
|
+
}, [meeting, open]);
|
|
1710
1944
|
useEffect(() => {
|
|
1711
1945
|
if (!open) return;
|
|
1712
1946
|
const selfName = meeting.participants().find((row) => row.isSelf)?.name?.trim() ?? "";
|
|
@@ -1759,76 +1993,306 @@ function StudioSettingsDialog({
|
|
|
1759
1993
|
setTestingSpeaker(false);
|
|
1760
1994
|
}
|
|
1761
1995
|
}
|
|
1762
|
-
|
|
1996
|
+
async function applyEffects(next) {
|
|
1997
|
+
setEffects(next);
|
|
1998
|
+
persistStudioVideoEffects(next);
|
|
1999
|
+
try {
|
|
2000
|
+
await meeting.setVideoEffects(next);
|
|
2001
|
+
} catch {
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
const tabs = [
|
|
2005
|
+
{ id: "general", label: labels.settingsGeneral },
|
|
2006
|
+
{ id: "audio", label: labels.settingsAudio },
|
|
2007
|
+
{ id: "video", label: labels.settingsVideo },
|
|
2008
|
+
{ id: "effects", label: labels.settingsEffects },
|
|
2009
|
+
{ id: "subtitles", label: labels.settingsSubtitles }
|
|
2010
|
+
];
|
|
2011
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-2xl", children: [
|
|
1763
2012
|
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
1764
2013
|
/* @__PURE__ */ jsx(DialogTitle, { children: labels.settingsTitle }),
|
|
1765
2014
|
/* @__PURE__ */ jsx(DialogDescription, { children: labels.settingsDescription })
|
|
1766
2015
|
] }),
|
|
1767
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
1768
|
-
/* @__PURE__ */ jsx(
|
|
1769
|
-
|
|
2016
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 sm:flex-row", children: [
|
|
2017
|
+
/* @__PURE__ */ jsx(
|
|
2018
|
+
"div",
|
|
2019
|
+
{
|
|
2020
|
+
role: "tablist",
|
|
2021
|
+
"aria-label": labels.settings,
|
|
2022
|
+
className: "flex shrink-0 flex-row gap-1 overflow-x-auto sm:w-40 sm:flex-col",
|
|
2023
|
+
children: tabs.map((item) => /* @__PURE__ */ jsx(
|
|
2024
|
+
"button",
|
|
2025
|
+
{
|
|
2026
|
+
type: "button",
|
|
2027
|
+
role: "tab",
|
|
2028
|
+
"aria-selected": tab === item.id,
|
|
2029
|
+
className: cn(
|
|
2030
|
+
"rounded-md px-3 py-1.5 text-left text-sm",
|
|
2031
|
+
tab === item.id ? "bg-muted font-medium" : "text-muted-foreground hover:bg-muted/60"
|
|
2032
|
+
),
|
|
2033
|
+
onClick: () => setTab(item.id),
|
|
2034
|
+
children: item.label
|
|
2035
|
+
},
|
|
2036
|
+
item.id
|
|
2037
|
+
))
|
|
2038
|
+
}
|
|
2039
|
+
),
|
|
2040
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-4", children: [
|
|
2041
|
+
tab === "general" ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2042
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "studio-display-name", children: labels.guestName }),
|
|
2043
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
2044
|
+
/* @__PURE__ */ jsx(
|
|
2045
|
+
Input,
|
|
2046
|
+
{
|
|
2047
|
+
id: "studio-display-name",
|
|
2048
|
+
value: displayName,
|
|
2049
|
+
maxLength: STUDIO_DISPLAY_NAME_MAX,
|
|
2050
|
+
onChange: (event) => setDisplayName(event.target.value)
|
|
2051
|
+
}
|
|
2052
|
+
),
|
|
2053
|
+
/* @__PURE__ */ jsx(
|
|
2054
|
+
Button,
|
|
2055
|
+
{
|
|
2056
|
+
type: "button",
|
|
2057
|
+
variant: "outline",
|
|
2058
|
+
disabled: displayNameSaveDisabled,
|
|
2059
|
+
onClick: () => void onSaveDisplayName(),
|
|
2060
|
+
children: labels.displayNameSave
|
|
2061
|
+
}
|
|
2062
|
+
)
|
|
2063
|
+
] })
|
|
2064
|
+
] }) : null,
|
|
2065
|
+
tab === "audio" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2066
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2067
|
+
/* @__PURE__ */ jsx("p", { className: "sr-only", children: labels.micLevel }),
|
|
2068
|
+
/* @__PURE__ */ jsx(
|
|
2069
|
+
AudioWaveform,
|
|
2070
|
+
{
|
|
2071
|
+
track: audioTrack,
|
|
2072
|
+
reducedMotion,
|
|
2073
|
+
className: "w-full"
|
|
2074
|
+
}
|
|
2075
|
+
)
|
|
2076
|
+
] }),
|
|
2077
|
+
/* @__PURE__ */ jsx(
|
|
2078
|
+
StudioDeviceSelectGroup,
|
|
2079
|
+
{
|
|
2080
|
+
devices: { ...devices, video: [] },
|
|
2081
|
+
selected: effective,
|
|
2082
|
+
labels,
|
|
2083
|
+
onChange: (kind, id) => void onChange(kind, id),
|
|
2084
|
+
speakerAction: /* @__PURE__ */ jsx(
|
|
2085
|
+
Button,
|
|
2086
|
+
{
|
|
2087
|
+
type: "button",
|
|
2088
|
+
size: "sm",
|
|
2089
|
+
variant: "outline",
|
|
2090
|
+
disabled: testingSpeaker,
|
|
2091
|
+
onClick: () => void onTestSpeaker(),
|
|
2092
|
+
children: labels.testSpeaker
|
|
2093
|
+
}
|
|
2094
|
+
)
|
|
2095
|
+
}
|
|
2096
|
+
)
|
|
2097
|
+
] }) : null,
|
|
2098
|
+
tab === "video" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2099
|
+
/* @__PURE__ */ jsx(
|
|
2100
|
+
SettingsCameraPreview,
|
|
2101
|
+
{
|
|
2102
|
+
track: videoTrack,
|
|
2103
|
+
open,
|
|
2104
|
+
cameraOn,
|
|
2105
|
+
cameraOffLabel: labels.cameraOff
|
|
2106
|
+
}
|
|
2107
|
+
),
|
|
2108
|
+
/* @__PURE__ */ jsx(
|
|
2109
|
+
StudioDeviceSelectGroup,
|
|
2110
|
+
{
|
|
2111
|
+
devices: { ...devices, audio: [], speaker: [] },
|
|
2112
|
+
selected: effective,
|
|
2113
|
+
labels,
|
|
2114
|
+
onChange: (kind, id) => void onChange(kind, id)
|
|
2115
|
+
}
|
|
2116
|
+
)
|
|
2117
|
+
] }) : null,
|
|
2118
|
+
tab === "effects" ? /* @__PURE__ */ jsx(
|
|
2119
|
+
EffectsPanel,
|
|
2120
|
+
{
|
|
2121
|
+
labels,
|
|
2122
|
+
effects,
|
|
2123
|
+
backgrounds: effectBackgrounds,
|
|
2124
|
+
onApply: (next) => void applyEffects(next)
|
|
2125
|
+
}
|
|
2126
|
+
) : null,
|
|
2127
|
+
tab === "subtitles" ? /* @__PURE__ */ jsx(
|
|
2128
|
+
SubtitlesPanel,
|
|
2129
|
+
{
|
|
2130
|
+
labels,
|
|
2131
|
+
prefs: captionPrefs,
|
|
2132
|
+
available: captionsAvailable,
|
|
2133
|
+
onChange: (next) => {
|
|
2134
|
+
persistStudioCaptionPrefs(next);
|
|
2135
|
+
onCaptionPrefsChange(next);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
) : null
|
|
2139
|
+
] })
|
|
2140
|
+
] })
|
|
2141
|
+
] }) });
|
|
2142
|
+
}
|
|
2143
|
+
function EffectsPanel({
|
|
2144
|
+
labels,
|
|
2145
|
+
effects,
|
|
2146
|
+
backgrounds,
|
|
2147
|
+
onApply
|
|
2148
|
+
}) {
|
|
2149
|
+
const filters = [
|
|
2150
|
+
{ id: "none", label: labels.effectFilterNone },
|
|
2151
|
+
{ id: "grayscale", label: labels.effectFilterGrayscale },
|
|
2152
|
+
{ id: "sepia", label: labels.effectFilterSepia },
|
|
2153
|
+
{ id: "warm", label: labels.effectFilterWarm },
|
|
2154
|
+
{ id: "cool", label: labels.effectFilterCool },
|
|
2155
|
+
{ id: "bright", label: labels.effectFilterBright }
|
|
2156
|
+
];
|
|
2157
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
2158
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2159
|
+
/* @__PURE__ */ jsx("p", { className: "mb-2 text-sm font-medium", children: labels.settingsEffects }),
|
|
2160
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
|
|
1770
2161
|
/* @__PURE__ */ jsx(
|
|
1771
|
-
|
|
2162
|
+
EffectTile,
|
|
1772
2163
|
{
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
onChange: (event) => setDisplayName(event.target.value)
|
|
2164
|
+
label: labels.effectNone,
|
|
2165
|
+
active: effects.background === "none",
|
|
2166
|
+
onClick: () => onApply({ ...effects, background: "none" })
|
|
1777
2167
|
}
|
|
1778
2168
|
),
|
|
1779
2169
|
/* @__PURE__ */ jsx(
|
|
1780
|
-
|
|
2170
|
+
EffectTile,
|
|
1781
2171
|
{
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
2172
|
+
label: labels.effectBlur,
|
|
2173
|
+
active: effects.background === "blur" && effects.blurStrength <= 50,
|
|
2174
|
+
onClick: () => onApply({ ...effects, background: "blur", blurStrength: 40 })
|
|
2175
|
+
}
|
|
2176
|
+
),
|
|
2177
|
+
/* @__PURE__ */ jsx(
|
|
2178
|
+
EffectTile,
|
|
2179
|
+
{
|
|
2180
|
+
label: labels.effectBlurStrong,
|
|
2181
|
+
active: effects.background === "blur" && effects.blurStrength > 50,
|
|
2182
|
+
onClick: () => onApply({ ...effects, background: "blur", blurStrength: 80 })
|
|
1787
2183
|
}
|
|
1788
2184
|
)
|
|
1789
2185
|
] })
|
|
1790
2186
|
] }),
|
|
1791
|
-
/* @__PURE__ */
|
|
1792
|
-
|
|
1793
|
-
{
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
2187
|
+
backgrounds.length > 0 ? /* @__PURE__ */ jsxs("div", { children: [
|
|
2188
|
+
/* @__PURE__ */ jsx("p", { className: "mb-2 text-sm font-medium", children: labels.effectBackgrounds }),
|
|
2189
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2", children: backgrounds.map((bg) => /* @__PURE__ */ jsx(
|
|
2190
|
+
EffectTile,
|
|
2191
|
+
{
|
|
2192
|
+
label: bg.label,
|
|
2193
|
+
active: effects.background === "image" && effects.imageUrl === bg.url,
|
|
2194
|
+
previewUrl: bg.url,
|
|
2195
|
+
onClick: () => onApply({
|
|
2196
|
+
...effects,
|
|
2197
|
+
background: "image",
|
|
2198
|
+
imageUrl: bg.url
|
|
2199
|
+
})
|
|
2200
|
+
},
|
|
2201
|
+
bg.id
|
|
2202
|
+
)) })
|
|
2203
|
+
] }) : null,
|
|
1800
2204
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
1801
|
-
/* @__PURE__ */ jsx("p", { className: "
|
|
1802
|
-
/* @__PURE__ */ jsx(
|
|
1803
|
-
|
|
2205
|
+
/* @__PURE__ */ jsx("p", { className: "mb-2 text-sm font-medium", children: labels.effectFilters }),
|
|
2206
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: filters.map((filter) => /* @__PURE__ */ jsx(
|
|
2207
|
+
Button,
|
|
1804
2208
|
{
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2209
|
+
type: "button",
|
|
2210
|
+
size: "sm",
|
|
2211
|
+
variant: effects.filter === filter.id ? "default" : "outline",
|
|
2212
|
+
onClick: () => onApply({ ...effects, filter: filter.id }),
|
|
2213
|
+
children: filter.label
|
|
2214
|
+
},
|
|
2215
|
+
filter.id
|
|
2216
|
+
)) })
|
|
2217
|
+
] })
|
|
2218
|
+
] });
|
|
2219
|
+
}
|
|
2220
|
+
function EffectTile({
|
|
2221
|
+
label,
|
|
2222
|
+
active,
|
|
2223
|
+
previewUrl,
|
|
2224
|
+
onClick
|
|
2225
|
+
}) {
|
|
2226
|
+
return /* @__PURE__ */ jsxs(
|
|
2227
|
+
"button",
|
|
2228
|
+
{
|
|
2229
|
+
type: "button",
|
|
2230
|
+
"aria-pressed": active,
|
|
2231
|
+
onClick,
|
|
2232
|
+
className: cn(
|
|
2233
|
+
"overflow-hidden rounded-lg border text-left text-xs",
|
|
2234
|
+
active ? "ring-primary ring-2" : "border-border"
|
|
2235
|
+
),
|
|
2236
|
+
children: [
|
|
2237
|
+
/* @__PURE__ */ jsx(
|
|
2238
|
+
"div",
|
|
1820
2239
|
{
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
variant: "outline",
|
|
1824
|
-
disabled: testingSpeaker,
|
|
1825
|
-
onClick: () => void onTestSpeaker(),
|
|
1826
|
-
children: labels.testSpeaker
|
|
2240
|
+
className: "bg-muted aspect-video w-full bg-cover bg-center",
|
|
2241
|
+
style: previewUrl ? { backgroundImage: `url(${previewUrl})` } : void 0
|
|
1827
2242
|
}
|
|
1828
|
-
)
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
2243
|
+
),
|
|
2244
|
+
/* @__PURE__ */ jsx("p", { className: "truncate px-2 py-1", children: label })
|
|
2245
|
+
]
|
|
2246
|
+
}
|
|
2247
|
+
);
|
|
2248
|
+
}
|
|
2249
|
+
function SubtitlesPanel({
|
|
2250
|
+
labels,
|
|
2251
|
+
prefs,
|
|
2252
|
+
available,
|
|
2253
|
+
onChange
|
|
2254
|
+
}) {
|
|
2255
|
+
const localeLabel = {
|
|
2256
|
+
en: labels.localeEn,
|
|
2257
|
+
es: labels.localeEs,
|
|
2258
|
+
pt: labels.localePt
|
|
2259
|
+
};
|
|
2260
|
+
const sizes = [
|
|
2261
|
+
{ id: "s", label: labels.captionsSizeS },
|
|
2262
|
+
{ id: "m", label: labels.captionsSizeM },
|
|
2263
|
+
{ id: "l", label: labels.captionsSizeL }
|
|
2264
|
+
];
|
|
2265
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
2266
|
+
available ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: labels.captionsDelayed }) : /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: labels.captionsNotReady }),
|
|
2267
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2268
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: labels.captionsLanguage }),
|
|
2269
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: STUDIO_CAPTION_LOCALES.map((locale) => /* @__PURE__ */ jsx(
|
|
2270
|
+
Button,
|
|
2271
|
+
{
|
|
2272
|
+
type: "button",
|
|
2273
|
+
size: "sm",
|
|
2274
|
+
variant: prefs.locale === locale ? "default" : "outline",
|
|
2275
|
+
onClick: () => onChange({ ...prefs, locale }),
|
|
2276
|
+
children: localeLabel[locale]
|
|
2277
|
+
},
|
|
2278
|
+
locale
|
|
2279
|
+
)) })
|
|
2280
|
+
] }),
|
|
2281
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2282
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: labels.captionsSize }),
|
|
2283
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: sizes.map((size) => /* @__PURE__ */ jsx(
|
|
2284
|
+
Button,
|
|
2285
|
+
{
|
|
2286
|
+
type: "button",
|
|
2287
|
+
size: "sm",
|
|
2288
|
+
variant: prefs.size === size.id ? "default" : "outline",
|
|
2289
|
+
onClick: () => onChange({ ...prefs, size: size.id }),
|
|
2290
|
+
children: size.label
|
|
2291
|
+
},
|
|
2292
|
+
size.id
|
|
2293
|
+
)) })
|
|
2294
|
+
] })
|
|
2295
|
+
] });
|
|
1832
2296
|
}
|
|
1833
2297
|
function SettingsCameraPreview({
|
|
1834
2298
|
track,
|
|
@@ -1879,7 +2343,11 @@ function StudioGuestWaiting({
|
|
|
1879
2343
|
onToggleCamera,
|
|
1880
2344
|
onToggleMute,
|
|
1881
2345
|
onOpenSettings,
|
|
1882
|
-
onCloseSettings
|
|
2346
|
+
onCloseSettings,
|
|
2347
|
+
captionPrefs,
|
|
2348
|
+
onCaptionPrefsChange,
|
|
2349
|
+
captionsAvailable,
|
|
2350
|
+
effectBackgrounds
|
|
1883
2351
|
}) {
|
|
1884
2352
|
const [audioTrack, setAudioTrack] = useState(null);
|
|
1885
2353
|
const [videoTrack, setVideoTrack] = useState(null);
|
|
@@ -1946,7 +2414,11 @@ function StudioGuestWaiting({
|
|
|
1946
2414
|
meeting,
|
|
1947
2415
|
labels,
|
|
1948
2416
|
reducedMotion,
|
|
1949
|
-
cameraOn
|
|
2417
|
+
cameraOn,
|
|
2418
|
+
captionPrefs,
|
|
2419
|
+
onCaptionPrefsChange,
|
|
2420
|
+
captionsAvailable,
|
|
2421
|
+
effectBackgrounds
|
|
1950
2422
|
}
|
|
1951
2423
|
)
|
|
1952
2424
|
] });
|
|
@@ -2023,6 +2495,19 @@ var badgeVariants = cva(
|
|
|
2023
2495
|
function Badge({ className, variant, ...props }) {
|
|
2024
2496
|
return /* @__PURE__ */ jsx("span", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
2025
2497
|
}
|
|
2498
|
+
|
|
2499
|
+
// src/studio/lib/studio-video-style.ts
|
|
2500
|
+
var STUDIO_VIDEO_MEDIA_STYLE = `
|
|
2501
|
+
.livo-studio-tile-video::-webkit-media-controls-start-playback-button,
|
|
2502
|
+
.livo-studio-inline-video::-webkit-media-controls-start-playback-button {
|
|
2503
|
+
display: none !important;
|
|
2504
|
+
-webkit-appearance: none;
|
|
2505
|
+
}
|
|
2506
|
+
.livo-studio-tile-video::-webkit-media-controls,
|
|
2507
|
+
.livo-studio-inline-video::-webkit-media-controls {
|
|
2508
|
+
display: none !important;
|
|
2509
|
+
}
|
|
2510
|
+
`;
|
|
2026
2511
|
function StudioPipPanel({
|
|
2027
2512
|
meeting,
|
|
2028
2513
|
labels,
|
|
@@ -2077,7 +2562,7 @@ function StudioPipPanel({
|
|
|
2077
2562
|
/* @__PURE__ */ jsx(StudioLiveBadge, { children: labels.live }),
|
|
2078
2563
|
elapsed ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground tabular-nums text-xs", children: elapsed }) : null
|
|
2079
2564
|
] }) : null,
|
|
2080
|
-
selfView ? /* @__PURE__ */ jsx(
|
|
2565
|
+
selfView && meeting.localVideoTrack() ? /* @__PURE__ */ jsx(
|
|
2081
2566
|
"div",
|
|
2082
2567
|
{
|
|
2083
2568
|
"data-testid": "pip-self-view",
|
|
@@ -2134,7 +2619,8 @@ function PipSpotlight({
|
|
|
2134
2619
|
spotlight,
|
|
2135
2620
|
cameraOffLabel
|
|
2136
2621
|
}) {
|
|
2137
|
-
|
|
2622
|
+
const track = spotlight && spotlight.kind !== "idle" ? spotlight.kind === "screen" ? meeting.participantScreenTrack(spotlight.participantId) : meeting.participantVideoTrack(spotlight.participantId) : null;
|
|
2623
|
+
if (!spotlight || spotlight.kind === "idle" || !track) {
|
|
2138
2624
|
return /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-2", children: [
|
|
2139
2625
|
spotlight?.picture ? /* @__PURE__ */ jsx(
|
|
2140
2626
|
"img",
|
|
@@ -2147,7 +2633,6 @@ function PipSpotlight({
|
|
|
2147
2633
|
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: spotlight?.name ?? cameraOffLabel })
|
|
2148
2634
|
] });
|
|
2149
2635
|
}
|
|
2150
|
-
const track = spotlight.kind === "screen" ? meeting.participantScreenTrack(spotlight.participantId) : meeting.participantVideoTrack(spotlight.participantId);
|
|
2151
2636
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2152
2637
|
/* @__PURE__ */ jsx(
|
|
2153
2638
|
PipTrackVideo,
|
|
@@ -2166,11 +2651,7 @@ function PipTrackVideo({
|
|
|
2166
2651
|
const ref = useRef(null);
|
|
2167
2652
|
useEffect(() => {
|
|
2168
2653
|
const el = ref.current;
|
|
2169
|
-
if (!el) return;
|
|
2170
|
-
if (!track) {
|
|
2171
|
-
el.srcObject = null;
|
|
2172
|
-
return;
|
|
2173
|
-
}
|
|
2654
|
+
if (!el || !track) return;
|
|
2174
2655
|
el.setAttribute("webkit-playsinline", "");
|
|
2175
2656
|
el.srcObject = new MediaStream([track]);
|
|
2176
2657
|
void el.play().catch(() => void 0);
|
|
@@ -2178,19 +2659,24 @@ function PipTrackVideo({
|
|
|
2178
2659
|
el.srcObject = null;
|
|
2179
2660
|
};
|
|
2180
2661
|
}, [track]);
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
{
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2662
|
+
if (!track) return null;
|
|
2663
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2664
|
+
/* @__PURE__ */ jsx("style", { children: STUDIO_VIDEO_MEDIA_STYLE }),
|
|
2665
|
+
/* @__PURE__ */ jsx(
|
|
2666
|
+
"video",
|
|
2667
|
+
{
|
|
2668
|
+
ref,
|
|
2669
|
+
autoPlay: true,
|
|
2670
|
+
playsInline: true,
|
|
2671
|
+
muted: true,
|
|
2672
|
+
disableRemotePlayback: true,
|
|
2673
|
+
className: cn(
|
|
2674
|
+
"livo-studio-inline-video absolute inset-0 size-full",
|
|
2675
|
+
fit === "contain" ? "object-contain" : "object-cover"
|
|
2676
|
+
)
|
|
2677
|
+
}
|
|
2678
|
+
)
|
|
2679
|
+
] });
|
|
2194
2680
|
}
|
|
2195
2681
|
function pipScreenControls({
|
|
2196
2682
|
labels,
|
|
@@ -3217,6 +3703,7 @@ function StudioTile({
|
|
|
3217
3703
|
const onStage = isOnStage(tile.stageStatus);
|
|
3218
3704
|
const [confirm, setConfirm] = useState(null);
|
|
3219
3705
|
const showMenu = (canModerate || canManageStage) && !tile.isSelf;
|
|
3706
|
+
const hasVideoTrack = tileHasVideoTrack(meeting, tile);
|
|
3220
3707
|
const glowRef = useSpeakingGlow({
|
|
3221
3708
|
track: tile.kind === "screen" || !tile.audioEnabled ? null : meeting.participantAudioTrack(tile.id),
|
|
3222
3709
|
enabled: onStage,
|
|
@@ -3232,23 +3719,14 @@ function StudioTile({
|
|
|
3232
3719
|
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"
|
|
3233
3720
|
),
|
|
3234
3721
|
children: [
|
|
3235
|
-
tile.kind === "idle" ? /* @__PURE__ */
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
) : /* @__PURE__ */ jsx("div", { className: "bg-background flex size-16 items-center justify-center rounded-full text-xl", children: tile.name.slice(0, 1).toUpperCase() }),
|
|
3244
|
-
/* @__PURE__ */ jsx(
|
|
3245
|
-
AudioWaveform,
|
|
3246
|
-
{
|
|
3247
|
-
track: tile.isSelf ? meeting.localAudioTrack() : null,
|
|
3248
|
-
reducedMotion
|
|
3249
|
-
}
|
|
3250
|
-
)
|
|
3251
|
-
] }) : /* @__PURE__ */ jsx(
|
|
3722
|
+
tile.kind === "idle" || !hasVideoTrack ? /* @__PURE__ */ jsx(
|
|
3723
|
+
IdlePersonTile,
|
|
3724
|
+
{
|
|
3725
|
+
tile,
|
|
3726
|
+
meeting,
|
|
3727
|
+
reducedMotion
|
|
3728
|
+
}
|
|
3729
|
+
) : /* @__PURE__ */ jsx(
|
|
3252
3730
|
StudioTileVideo,
|
|
3253
3731
|
{
|
|
3254
3732
|
meeting,
|
|
@@ -3347,6 +3825,37 @@ function TileStatusIcons({
|
|
|
3347
3825
|
tile.chatCanSend === false ? /* @__PURE__ */ jsx(StatusIcon, { label: labels.muteChat, children: /* @__PURE__ */ jsx(MessageSquareOff, {}) }) : null
|
|
3348
3826
|
] });
|
|
3349
3827
|
}
|
|
3828
|
+
function tileHasVideoTrack(meeting, tile) {
|
|
3829
|
+
if (tile.kind === "screen") {
|
|
3830
|
+
return meeting.participantScreenTrack(tile.id) != null;
|
|
3831
|
+
}
|
|
3832
|
+
if (tile.kind === "idle") return false;
|
|
3833
|
+
if (tile.isSelf) return meeting.localVideoTrack() != null;
|
|
3834
|
+
return meeting.participantVideoTrack(tile.id) != null;
|
|
3835
|
+
}
|
|
3836
|
+
function IdlePersonTile({
|
|
3837
|
+
tile,
|
|
3838
|
+
meeting,
|
|
3839
|
+
reducedMotion
|
|
3840
|
+
}) {
|
|
3841
|
+
return /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-3", children: [
|
|
3842
|
+
tile.picture ? /* @__PURE__ */ jsx(
|
|
3843
|
+
"img",
|
|
3844
|
+
{
|
|
3845
|
+
src: tile.picture,
|
|
3846
|
+
alt: "",
|
|
3847
|
+
className: "size-16 rounded-full object-cover"
|
|
3848
|
+
}
|
|
3849
|
+
) : /* @__PURE__ */ jsx("div", { className: "bg-background flex size-16 items-center justify-center rounded-full text-xl", children: tile.name.slice(0, 1).toUpperCase() }),
|
|
3850
|
+
/* @__PURE__ */ jsx(
|
|
3851
|
+
AudioWaveform,
|
|
3852
|
+
{
|
|
3853
|
+
track: tile.isSelf ? meeting.localAudioTrack() : null,
|
|
3854
|
+
reducedMotion
|
|
3855
|
+
}
|
|
3856
|
+
)
|
|
3857
|
+
] });
|
|
3858
|
+
}
|
|
3350
3859
|
function StatusIcon({
|
|
3351
3860
|
label,
|
|
3352
3861
|
children
|
|
@@ -3370,23 +3879,27 @@ function StudioTileVideo({
|
|
|
3370
3879
|
meeting.attachVideo(participantId, el, kind);
|
|
3371
3880
|
return () => meeting.attachVideo(participantId, null, kind);
|
|
3372
3881
|
}, [kind, meeting, participantId]);
|
|
3373
|
-
return /* @__PURE__ */
|
|
3374
|
-
"
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
"
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3882
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3883
|
+
/* @__PURE__ */ jsx("style", { children: STUDIO_VIDEO_MEDIA_STYLE }),
|
|
3884
|
+
/* @__PURE__ */ jsx(
|
|
3885
|
+
"video",
|
|
3886
|
+
{
|
|
3887
|
+
ref,
|
|
3888
|
+
autoPlay: true,
|
|
3889
|
+
playsInline: true,
|
|
3890
|
+
muted: true,
|
|
3891
|
+
disablePictureInPicture: true,
|
|
3892
|
+
disableRemotePlayback: true,
|
|
3893
|
+
controlsList: "nodownload noplaybackrate noremoteplayback",
|
|
3894
|
+
className: cn(
|
|
3895
|
+
"livo-studio-tile-video absolute inset-0 size-full",
|
|
3896
|
+
"pointer-events-none",
|
|
3897
|
+
contain ? "object-contain" : "object-cover",
|
|
3898
|
+
mirror && "-scale-x-100"
|
|
3899
|
+
)
|
|
3900
|
+
}
|
|
3901
|
+
)
|
|
3902
|
+
] });
|
|
3390
3903
|
}
|
|
3391
3904
|
function StudioStage({
|
|
3392
3905
|
participants,
|
|
@@ -3478,6 +3991,32 @@ function StudioStage({
|
|
|
3478
3991
|
}
|
|
3479
3992
|
);
|
|
3480
3993
|
}
|
|
3994
|
+
function StudioDownloadAppDialog({
|
|
3995
|
+
open,
|
|
3996
|
+
onOpenChange,
|
|
3997
|
+
downloadUrl,
|
|
3998
|
+
labels
|
|
3999
|
+
}) {
|
|
4000
|
+
const href = downloadUrl?.trim() ?? "";
|
|
4001
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { children: [
|
|
4002
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
4003
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: labels.screenDownloadAppTitle }),
|
|
4004
|
+
/* @__PURE__ */ jsx(DialogDescription, { children: labels.screenUnsupportedDevice })
|
|
4005
|
+
] }),
|
|
4006
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
4007
|
+
/* @__PURE__ */ jsx(
|
|
4008
|
+
Button,
|
|
4009
|
+
{
|
|
4010
|
+
type: "button",
|
|
4011
|
+
variant: "outline",
|
|
4012
|
+
onClick: () => onOpenChange(false),
|
|
4013
|
+
children: labels.screenDownloadAppDismiss
|
|
4014
|
+
}
|
|
4015
|
+
),
|
|
4016
|
+
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 })
|
|
4017
|
+
] })
|
|
4018
|
+
] }) });
|
|
4019
|
+
}
|
|
3481
4020
|
function StudioToolbar({
|
|
3482
4021
|
session,
|
|
3483
4022
|
meeting,
|
|
@@ -3504,6 +4043,12 @@ function StudioToolbar({
|
|
|
3504
4043
|
stageEnabled,
|
|
3505
4044
|
canLeaveStage,
|
|
3506
4045
|
reducedMotion,
|
|
4046
|
+
captionsOn,
|
|
4047
|
+
captionsAvailable,
|
|
4048
|
+
captionsReady,
|
|
4049
|
+
captionPrefs,
|
|
4050
|
+
onCaptionPrefsChange,
|
|
4051
|
+
effectBackgrounds,
|
|
3507
4052
|
onToggleMute,
|
|
3508
4053
|
onToggleCamera,
|
|
3509
4054
|
onToggleScreen,
|
|
@@ -3517,204 +4062,360 @@ function StudioToolbar({
|
|
|
3517
4062
|
onCloseSettings,
|
|
3518
4063
|
onOpenPip,
|
|
3519
4064
|
onClosePip,
|
|
4065
|
+
onToggleCaptions,
|
|
3520
4066
|
onLeave
|
|
3521
4067
|
}) {
|
|
3522
|
-
const
|
|
4068
|
+
const devices = useMediaDevices(1, true);
|
|
4069
|
+
const saved = readStudioDeviceSelection();
|
|
4070
|
+
const { effective } = resolveDeviceSelection(saved, devices);
|
|
4071
|
+
const fullscreenSupported = typeof document !== "undefined" && document.fullscreenEnabled === true && typeof document.documentElement.requestFullscreen === "function";
|
|
3523
4072
|
async function enterFullscreen() {
|
|
3524
4073
|
try {
|
|
3525
4074
|
await document.documentElement.requestFullscreen();
|
|
3526
4075
|
} catch {
|
|
3527
4076
|
}
|
|
3528
4077
|
}
|
|
3529
|
-
|
|
3530
|
-
|
|
4078
|
+
async function onDeviceChange(kind, deviceId) {
|
|
4079
|
+
persistStudioDeviceSelection(kind, deviceId);
|
|
4080
|
+
try {
|
|
4081
|
+
await meeting.setDevice(kind, deviceId);
|
|
4082
|
+
} catch {
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
const showStageModerator = session.role === "moderator" && canManageStage;
|
|
4086
|
+
const showStageGuest = session.role === "guest" && stageEnabled;
|
|
4087
|
+
const captionsLabel = !captionsAvailable ? labels.captions : !captionsReady ? labels.captionsNotReady : captionsOn ? labels.captionsOn : labels.captionsOff;
|
|
4088
|
+
return /* @__PURE__ */ jsx("footer", { className: "flex flex-wrap items-center justify-center gap-3 border-t px-3 py-3 sm:justify-between", children: /* @__PURE__ */ jsxs(TooltipProvider, { children: [
|
|
4089
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
|
|
4090
|
+
showMediaControls ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4091
|
+
/* @__PURE__ */ jsx(
|
|
4092
|
+
MediaSplit,
|
|
4093
|
+
{
|
|
4094
|
+
label: muted ? labels.unmute : labels.mute,
|
|
4095
|
+
pressed: muted,
|
|
4096
|
+
tone: muted ? "danger" : "default",
|
|
4097
|
+
onClick: onToggleMute,
|
|
4098
|
+
pickerLabel: labels.micDevice,
|
|
4099
|
+
options: devices.audio,
|
|
4100
|
+
selected: effective.audio,
|
|
4101
|
+
onSelect: (id) => void onDeviceChange("audio", id),
|
|
4102
|
+
children: muted ? /* @__PURE__ */ jsx(MicOff, {}) : /* @__PURE__ */ jsx(Mic, {})
|
|
4103
|
+
}
|
|
4104
|
+
),
|
|
4105
|
+
/* @__PURE__ */ jsx(
|
|
4106
|
+
MediaSplit,
|
|
4107
|
+
{
|
|
4108
|
+
label: labels.camera,
|
|
4109
|
+
pressed: cameraOn,
|
|
4110
|
+
onClick: onToggleCamera,
|
|
4111
|
+
pickerLabel: labels.cameraDevice,
|
|
4112
|
+
options: devices.video,
|
|
4113
|
+
selected: effective.video,
|
|
4114
|
+
onSelect: (id) => void onDeviceChange("video", id),
|
|
4115
|
+
children: cameraOn ? /* @__PURE__ */ jsx(Video, {}) : /* @__PURE__ */ jsx(VideoOff, {})
|
|
4116
|
+
}
|
|
4117
|
+
)
|
|
4118
|
+
] }) : null,
|
|
4119
|
+
showStageModerator ? /* @__PURE__ */ jsx(
|
|
4120
|
+
ControlIcon,
|
|
4121
|
+
{
|
|
4122
|
+
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : labels.goOnAir,
|
|
4123
|
+
pressed: selfStageStatus !== "ON_STAGE",
|
|
4124
|
+
disabled: selfStageStatus === "ON_STAGE" && !canLeaveStage,
|
|
4125
|
+
onClick: () => {
|
|
4126
|
+
if (selfStageStatus === "ON_STAGE") {
|
|
4127
|
+
if (!canLeaveStage) {
|
|
4128
|
+
toast.message(labels.offAirLastVisible);
|
|
4129
|
+
return;
|
|
4130
|
+
}
|
|
4131
|
+
onLeaveStage();
|
|
4132
|
+
return;
|
|
4133
|
+
}
|
|
4134
|
+
onAskStage();
|
|
4135
|
+
},
|
|
4136
|
+
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
|
|
4137
|
+
}
|
|
4138
|
+
) : null,
|
|
4139
|
+
showStageGuest ? /* @__PURE__ */ jsx(
|
|
4140
|
+
ControlIcon,
|
|
4141
|
+
{
|
|
4142
|
+
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
|
|
4143
|
+
pressed: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
|
|
4144
|
+
disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" || selfStageStatus === "ON_STAGE" && !canLeaveStage,
|
|
4145
|
+
onClick: () => {
|
|
4146
|
+
if (selfStageStatus === "ON_STAGE") {
|
|
4147
|
+
if (!canLeaveStage) {
|
|
4148
|
+
toast.message(labels.offAirLastVisible);
|
|
4149
|
+
return;
|
|
4150
|
+
}
|
|
4151
|
+
onLeaveStage();
|
|
4152
|
+
return;
|
|
4153
|
+
}
|
|
4154
|
+
onAskStage();
|
|
4155
|
+
},
|
|
4156
|
+
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
|
|
4157
|
+
}
|
|
4158
|
+
) : null,
|
|
4159
|
+
showScreenControl ? canShareFreely ? /* @__PURE__ */ jsx(
|
|
4160
|
+
ControlIcon,
|
|
4161
|
+
{
|
|
4162
|
+
label: labels.screen,
|
|
4163
|
+
pressed: screenOn,
|
|
4164
|
+
disabled: screenPending || !screenAvailable && !screenOn,
|
|
4165
|
+
pending: screenPending,
|
|
4166
|
+
onClick: onToggleScreen,
|
|
4167
|
+
children: /* @__PURE__ */ jsx(MonitorUp, {})
|
|
4168
|
+
}
|
|
4169
|
+
) : shareGrant === "requested" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4170
|
+
/* @__PURE__ */ jsx(
|
|
4171
|
+
ControlIcon,
|
|
4172
|
+
{
|
|
4173
|
+
label: labels.shareRequested,
|
|
4174
|
+
pressed: true,
|
|
4175
|
+
disabled: true,
|
|
4176
|
+
onClick: () => void 0,
|
|
4177
|
+
children: /* @__PURE__ */ jsx(MonitorUp, {})
|
|
4178
|
+
}
|
|
4179
|
+
),
|
|
4180
|
+
/* @__PURE__ */ jsx(
|
|
4181
|
+
ControlIcon,
|
|
4182
|
+
{
|
|
4183
|
+
label: labels.cancelShareRequest,
|
|
4184
|
+
onClick: onCancelShareRequest,
|
|
4185
|
+
children: /* @__PURE__ */ jsx(X, {})
|
|
4186
|
+
}
|
|
4187
|
+
)
|
|
4188
|
+
] }) : /* @__PURE__ */ jsx(ControlIcon, { label: labels.askToShare, onClick: onAskToShare, children: /* @__PURE__ */ jsx(MonitorUp, {}) }) : null,
|
|
4189
|
+
/* @__PURE__ */ jsx(
|
|
4190
|
+
ControlIcon,
|
|
4191
|
+
{
|
|
4192
|
+
label: captionsLabel,
|
|
4193
|
+
pressed: captionsOn,
|
|
4194
|
+
disabled: !captionsAvailable,
|
|
4195
|
+
onClick: onToggleCaptions,
|
|
4196
|
+
children: /* @__PURE__ */ jsx(Captions, {})
|
|
4197
|
+
}
|
|
4198
|
+
),
|
|
4199
|
+
/* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
4200
|
+
/* @__PURE__ */ jsx(
|
|
4201
|
+
DropdownMenuTrigger,
|
|
4202
|
+
{
|
|
4203
|
+
render: /* @__PURE__ */ jsx(
|
|
4204
|
+
Button,
|
|
4205
|
+
{
|
|
4206
|
+
type: "button",
|
|
4207
|
+
size: "icon-lg",
|
|
4208
|
+
variant: "secondary",
|
|
4209
|
+
"aria-label": labels.more
|
|
4210
|
+
}
|
|
4211
|
+
),
|
|
4212
|
+
children: /* @__PURE__ */ jsx(MoreVertical, {})
|
|
4213
|
+
}
|
|
4214
|
+
),
|
|
4215
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "center", className: "min-w-44", children: [
|
|
4216
|
+
fullscreenSupported ? /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: () => void enterFullscreen(), children: [
|
|
4217
|
+
/* @__PURE__ */ jsx(Maximize, {}),
|
|
4218
|
+
labels.fullscreen
|
|
4219
|
+
] }) : null,
|
|
4220
|
+
pipSupported ? /* @__PURE__ */ jsxs(
|
|
4221
|
+
DropdownMenuItem,
|
|
4222
|
+
{
|
|
4223
|
+
onClick: () => {
|
|
4224
|
+
if (pipOpen) onClosePip();
|
|
4225
|
+
else onOpenPip();
|
|
4226
|
+
},
|
|
4227
|
+
children: [
|
|
4228
|
+
/* @__PURE__ */ jsx(PictureInPicture2, {}),
|
|
4229
|
+
pipOpen ? labels.closePip : labels.openPip
|
|
4230
|
+
]
|
|
4231
|
+
}
|
|
4232
|
+
) : null,
|
|
4233
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: onOpenSettings, children: [
|
|
4234
|
+
/* @__PURE__ */ jsx(Settings, {}),
|
|
4235
|
+
labels.settings
|
|
4236
|
+
] })
|
|
4237
|
+
] })
|
|
4238
|
+
] })
|
|
4239
|
+
] }),
|
|
4240
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
|
|
3531
4241
|
/* @__PURE__ */ jsx(
|
|
3532
4242
|
ControlIcon,
|
|
3533
4243
|
{
|
|
3534
|
-
label:
|
|
3535
|
-
pressed:
|
|
3536
|
-
|
|
3537
|
-
onClick:
|
|
3538
|
-
children:
|
|
4244
|
+
label: unreadChat > 0 ? `${labels.chat} (${unreadChat})` : labels.chat,
|
|
4245
|
+
pressed: chatOpen,
|
|
4246
|
+
badge: unreadChat,
|
|
4247
|
+
onClick: onToggleChat,
|
|
4248
|
+
children: /* @__PURE__ */ jsx(MessageSquare, {})
|
|
3539
4249
|
}
|
|
3540
4250
|
),
|
|
3541
|
-
/* @__PURE__ */ jsx(
|
|
4251
|
+
session.role === "moderator" ? /* @__PURE__ */ jsx(
|
|
3542
4252
|
ControlIcon,
|
|
3543
4253
|
{
|
|
3544
|
-
label: labels.
|
|
3545
|
-
pressed:
|
|
3546
|
-
|
|
3547
|
-
|
|
4254
|
+
label: peopleBadge > 0 ? `${labels.people} (${peopleBadge})` : labels.people,
|
|
4255
|
+
pressed: peopleOpen,
|
|
4256
|
+
badge: peopleBadge,
|
|
4257
|
+
onClick: onTogglePeople,
|
|
4258
|
+
children: /* @__PURE__ */ jsx(Users, {})
|
|
3548
4259
|
}
|
|
3549
|
-
)
|
|
3550
|
-
] }) : null,
|
|
3551
|
-
session.role === "moderator" && canManageStage ? /* @__PURE__ */ jsx(
|
|
3552
|
-
ControlIcon,
|
|
3553
|
-
{
|
|
3554
|
-
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : labels.goOnAir,
|
|
3555
|
-
pressed: selfStageStatus !== "ON_STAGE",
|
|
3556
|
-
disabled: selfStageStatus === "ON_STAGE" && !canLeaveStage,
|
|
3557
|
-
onClick: () => {
|
|
3558
|
-
if (selfStageStatus === "ON_STAGE") {
|
|
3559
|
-
if (!canLeaveStage) {
|
|
3560
|
-
toast.message(labels.offAirLastVisible);
|
|
3561
|
-
return;
|
|
3562
|
-
}
|
|
3563
|
-
onLeaveStage();
|
|
3564
|
-
return;
|
|
3565
|
-
}
|
|
3566
|
-
onAskStage();
|
|
3567
|
-
},
|
|
3568
|
-
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
|
|
3569
|
-
}
|
|
3570
|
-
) : null,
|
|
3571
|
-
session.role === "guest" && stageEnabled ? /* @__PURE__ */ jsx(
|
|
3572
|
-
ControlIcon,
|
|
3573
|
-
{
|
|
3574
|
-
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
|
|
3575
|
-
pressed: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
|
|
3576
|
-
disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" || selfStageStatus === "ON_STAGE" && !canLeaveStage,
|
|
3577
|
-
onClick: () => {
|
|
3578
|
-
if (selfStageStatus === "ON_STAGE") {
|
|
3579
|
-
if (!canLeaveStage) {
|
|
3580
|
-
toast.message(labels.offAirLastVisible);
|
|
3581
|
-
return;
|
|
3582
|
-
}
|
|
3583
|
-
onLeaveStage();
|
|
3584
|
-
return;
|
|
3585
|
-
}
|
|
3586
|
-
onAskStage();
|
|
3587
|
-
},
|
|
3588
|
-
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
|
|
3589
|
-
}
|
|
3590
|
-
) : null,
|
|
3591
|
-
showScreenControl ? canShareFreely ? /* @__PURE__ */ jsx(
|
|
3592
|
-
ControlIcon,
|
|
3593
|
-
{
|
|
3594
|
-
label: labels.screen,
|
|
3595
|
-
pressed: screenOn,
|
|
3596
|
-
disabled: screenPending || !screenAvailable && !screenOn,
|
|
3597
|
-
pending: screenPending,
|
|
3598
|
-
onClick: onToggleScreen,
|
|
3599
|
-
children: /* @__PURE__ */ jsx(MonitorUp, {})
|
|
3600
|
-
}
|
|
3601
|
-
) : shareGrant === "requested" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4260
|
+
) : null,
|
|
3602
4261
|
/* @__PURE__ */ jsx(
|
|
3603
4262
|
ControlIcon,
|
|
3604
4263
|
{
|
|
3605
|
-
label: labels.
|
|
3606
|
-
pressed:
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
children: /* @__PURE__ */ jsx(MonitorUp, {})
|
|
4264
|
+
label: labels.settings,
|
|
4265
|
+
pressed: settingsOpen,
|
|
4266
|
+
onClick: onOpenSettings,
|
|
4267
|
+
children: /* @__PURE__ */ jsx(Settings, {})
|
|
3610
4268
|
}
|
|
3611
4269
|
),
|
|
3612
4270
|
/* @__PURE__ */ jsx(
|
|
3613
|
-
|
|
4271
|
+
StudioSettingsDialog,
|
|
3614
4272
|
{
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
4273
|
+
open: settingsOpen,
|
|
4274
|
+
onOpenChange: onCloseSettings,
|
|
4275
|
+
meeting,
|
|
4276
|
+
reducedMotion,
|
|
4277
|
+
cameraOn,
|
|
4278
|
+
labels,
|
|
4279
|
+
captionPrefs,
|
|
4280
|
+
onCaptionPrefsChange,
|
|
4281
|
+
captionsAvailable,
|
|
4282
|
+
effectBackgrounds
|
|
3618
4283
|
}
|
|
3619
|
-
)
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
},
|
|
3658
|
-
children: /* @__PURE__ */ jsx(PictureInPicture2, {})
|
|
3659
|
-
}
|
|
3660
|
-
) : null,
|
|
4284
|
+
),
|
|
4285
|
+
session.role === "guest" ? /* @__PURE__ */ jsx(
|
|
4286
|
+
ConfirmAction,
|
|
4287
|
+
{
|
|
4288
|
+
triggerLabel: labels.leave,
|
|
4289
|
+
title: labels.leaveTitle,
|
|
4290
|
+
description: labels.leaveDescription,
|
|
4291
|
+
confirmLabel: labels.leaveConfirm,
|
|
4292
|
+
pending,
|
|
4293
|
+
onConfirm: onLeave,
|
|
4294
|
+
trigger: /* @__PURE__ */ jsx(
|
|
4295
|
+
Button,
|
|
4296
|
+
{
|
|
4297
|
+
type: "button",
|
|
4298
|
+
variant: "destructive",
|
|
4299
|
+
size: "icon-lg",
|
|
4300
|
+
disabled: pending,
|
|
4301
|
+
"aria-label": labels.leave,
|
|
4302
|
+
children: /* @__PURE__ */ jsx(PhoneOff, {})
|
|
4303
|
+
}
|
|
4304
|
+
)
|
|
4305
|
+
}
|
|
4306
|
+
) : null
|
|
4307
|
+
] })
|
|
4308
|
+
] }) });
|
|
4309
|
+
}
|
|
4310
|
+
function MediaSplit({
|
|
4311
|
+
label,
|
|
4312
|
+
pressed,
|
|
4313
|
+
tone = "default",
|
|
4314
|
+
onClick,
|
|
4315
|
+
pickerLabel,
|
|
4316
|
+
options,
|
|
4317
|
+
selected,
|
|
4318
|
+
onSelect,
|
|
4319
|
+
children
|
|
4320
|
+
}) {
|
|
4321
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
|
|
3661
4322
|
/* @__PURE__ */ jsx(
|
|
3662
4323
|
ControlIcon,
|
|
3663
4324
|
{
|
|
3664
|
-
label
|
|
3665
|
-
pressed
|
|
3666
|
-
|
|
3667
|
-
|
|
4325
|
+
label,
|
|
4326
|
+
pressed,
|
|
4327
|
+
tone,
|
|
4328
|
+
onClick,
|
|
4329
|
+
children
|
|
3668
4330
|
}
|
|
3669
4331
|
),
|
|
3670
|
-
/* @__PURE__ */
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
cameraDevice: labels.cameraDevice,
|
|
3686
|
-
micDevice: labels.micDevice,
|
|
3687
|
-
speakerDevice: labels.speakerDevice,
|
|
3688
|
-
testSpeaker: labels.testSpeaker,
|
|
3689
|
-
testSpeakerError: labels.testSpeakerError,
|
|
3690
|
-
cameraOff: labels.cameraOff,
|
|
3691
|
-
micLevel: labels.micLevel
|
|
4332
|
+
options.length > 0 ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
4333
|
+
/* @__PURE__ */ jsx(
|
|
4334
|
+
DropdownMenuTrigger,
|
|
4335
|
+
{
|
|
4336
|
+
render: /* @__PURE__ */ jsx(
|
|
4337
|
+
Button,
|
|
4338
|
+
{
|
|
4339
|
+
type: "button",
|
|
4340
|
+
size: "icon-xs",
|
|
4341
|
+
variant: "secondary",
|
|
4342
|
+
className: "-ml-1",
|
|
4343
|
+
"aria-label": pickerLabel
|
|
4344
|
+
}
|
|
4345
|
+
),
|
|
4346
|
+
children: /* @__PURE__ */ jsx(ChevronUp, { className: "size-3.5" })
|
|
3692
4347
|
}
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
4348
|
+
),
|
|
4349
|
+
/* @__PURE__ */ jsx(DropdownMenuContent, { align: "start", side: "top", className: "min-w-52", children: options.map((option) => /* @__PURE__ */ jsxs(
|
|
4350
|
+
DropdownMenuItem,
|
|
4351
|
+
{
|
|
4352
|
+
onClick: () => onSelect(option.deviceId),
|
|
4353
|
+
children: [
|
|
4354
|
+
option.deviceId === selected ? "\u2713 " : "",
|
|
4355
|
+
option.label
|
|
4356
|
+
]
|
|
4357
|
+
},
|
|
4358
|
+
option.deviceId
|
|
4359
|
+
)) })
|
|
4360
|
+
] }) : null
|
|
4361
|
+
] });
|
|
4362
|
+
}
|
|
4363
|
+
var POLL_MS = 2e3;
|
|
4364
|
+
var PLAYOUT_MS = 250;
|
|
4365
|
+
var STUDIO_EXTRA_LAG_MS = 200;
|
|
4366
|
+
function useStudioCaptions({
|
|
4367
|
+
enabled,
|
|
4368
|
+
baseUrl,
|
|
4369
|
+
locale,
|
|
4370
|
+
streamStatus
|
|
4371
|
+
}) {
|
|
4372
|
+
const [playout, setPlayout] = useState(
|
|
4373
|
+
EMPTY_LIVE_CAPTION_PLAYOUT
|
|
4374
|
+
);
|
|
4375
|
+
const cuesRef = useRef([]);
|
|
4376
|
+
useEffect(() => {
|
|
4377
|
+
if (!enabled) {
|
|
4378
|
+
cuesRef.current = [];
|
|
4379
|
+
setPlayout(EMPTY_LIVE_CAPTION_PLAYOUT);
|
|
4380
|
+
return;
|
|
4381
|
+
}
|
|
4382
|
+
if (!baseUrl || !isStudioActiveStatus(streamStatus)) return;
|
|
4383
|
+
let cancelled = false;
|
|
4384
|
+
const url = liveCaptionsUrl(baseUrl, locale);
|
|
4385
|
+
const poll = async () => {
|
|
4386
|
+
const loaded = await loadLiveCaptions(url);
|
|
4387
|
+
if (cancelled || !loaded.ok) return;
|
|
4388
|
+
cuesRef.current = mergeLiveCaptionCues(cuesRef.current, loaded.cues);
|
|
4389
|
+
};
|
|
4390
|
+
void poll();
|
|
4391
|
+
const timer = window.setInterval(() => void poll(), POLL_MS);
|
|
4392
|
+
return () => {
|
|
4393
|
+
cancelled = true;
|
|
4394
|
+
window.clearInterval(timer);
|
|
4395
|
+
};
|
|
4396
|
+
}, [baseUrl, enabled, locale, streamStatus]);
|
|
4397
|
+
useEffect(() => {
|
|
4398
|
+
if (!enabled || !baseUrl || !isStudioActiveStatus(streamStatus)) {
|
|
4399
|
+
setPlayout(EMPTY_LIVE_CAPTION_PLAYOUT);
|
|
4400
|
+
return;
|
|
4401
|
+
}
|
|
4402
|
+
const tick2 = () => {
|
|
4403
|
+
setPlayout(
|
|
4404
|
+
(prev) => advanceLiveCaptionPlayout(
|
|
4405
|
+
prev,
|
|
4406
|
+
cuesRef.current,
|
|
4407
|
+
Date.now(),
|
|
4408
|
+
0,
|
|
4409
|
+
void 0,
|
|
4410
|
+
STUDIO_EXTRA_LAG_MS
|
|
3714
4411
|
)
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
4412
|
+
);
|
|
4413
|
+
};
|
|
4414
|
+
tick2();
|
|
4415
|
+
const timer = window.setInterval(tick2, PLAYOUT_MS);
|
|
4416
|
+
return () => window.clearInterval(timer);
|
|
4417
|
+
}, [baseUrl, enabled, streamStatus]);
|
|
4418
|
+
return playout;
|
|
3718
4419
|
}
|
|
3719
4420
|
var STAGE_GRANT_VERIFY_MS = 4e3;
|
|
3720
4421
|
var STUDIO_CONNECT_WATCHDOG_MS = 12e3;
|
|
@@ -3732,7 +4433,11 @@ function StudioRoom({
|
|
|
3732
4433
|
windowGate,
|
|
3733
4434
|
initialCameraOn = true,
|
|
3734
4435
|
initialMuted = false,
|
|
3735
|
-
initialDevices
|
|
4436
|
+
initialDevices,
|
|
4437
|
+
appDownloadUrl,
|
|
4438
|
+
captionsBaseUrl,
|
|
4439
|
+
apiUrl,
|
|
4440
|
+
cdnBase
|
|
3736
4441
|
}) {
|
|
3737
4442
|
const [cameraOn, setCameraOn] = useState(initialCameraOn);
|
|
3738
4443
|
const [screenOn, setScreenOn] = useState(false);
|
|
@@ -3753,6 +4458,12 @@ function StudioRoom({
|
|
|
3753
4458
|
);
|
|
3754
4459
|
const [egressError, setEgressError] = useState(null);
|
|
3755
4460
|
const [shareGrant, setShareGrant] = useState("idle");
|
|
4461
|
+
const [downloadAppOpen, setDownloadAppOpen] = useState(false);
|
|
4462
|
+
const [captionsOn, setCaptionsOn] = useState(false);
|
|
4463
|
+
const [captionPrefs, setCaptionPrefs] = useState(
|
|
4464
|
+
() => readStudioCaptionPrefs()
|
|
4465
|
+
);
|
|
4466
|
+
const captionHintShown = useRef(false);
|
|
3756
4467
|
const [connectTimedOut, setConnectTimedOut] = useState(false);
|
|
3757
4468
|
const [screenPermission, setScreenPermission] = useState(
|
|
3758
4469
|
() => meeting.screenSharePermission()
|
|
@@ -3778,6 +4489,19 @@ function StudioRoom({
|
|
|
3778
4489
|
const pip = useDocumentPictureInPicture();
|
|
3779
4490
|
const gateFromContext = useStudioWindowGate();
|
|
3780
4491
|
const { reportDuplicate } = windowGate ?? gateFromContext;
|
|
4492
|
+
const resolvedCaptionsBaseUrl = captionsBaseUrl ?? studioCaptionsBaseUrl(apiUrl, session.stream.id);
|
|
4493
|
+
const captionsAvailable = session.stream.captionsEnabled !== false;
|
|
4494
|
+
const captionsReady = isStudioActiveStatus(session.stream.status);
|
|
4495
|
+
const captionPlayout = useStudioCaptions({
|
|
4496
|
+
enabled: captionsOn && captionsAvailable,
|
|
4497
|
+
baseUrl: resolvedCaptionsBaseUrl,
|
|
4498
|
+
locale: captionPrefs.locale,
|
|
4499
|
+
streamStatus: session.stream.status
|
|
4500
|
+
});
|
|
4501
|
+
const effectBackgrounds = useMemo(
|
|
4502
|
+
() => defaultStudioEffectBackgrounds(cdnBase),
|
|
4503
|
+
[cdnBase]
|
|
4504
|
+
);
|
|
3781
4505
|
const onAir = isLiveStudioStatus(session.stream.status);
|
|
3782
4506
|
const active = isStudioActiveStatus(session.stream.status);
|
|
3783
4507
|
if (onAir && liveStartedAtRef.current === null) {
|
|
@@ -3794,6 +4518,11 @@ function StudioRoom({
|
|
|
3794
4518
|
const roomState = leaveState ?? meeting.roomState();
|
|
3795
4519
|
const sessionOver = isStudioSessionOver(session.stream.status, roomState);
|
|
3796
4520
|
const rejected = isStudioRejected(roomState);
|
|
4521
|
+
const connectionState = useMemo(
|
|
4522
|
+
() => meeting.connectionState(),
|
|
4523
|
+
[meeting, tick2]
|
|
4524
|
+
);
|
|
4525
|
+
const guestConnectFailed = session.role === "guest" && (connectionState === "failed" || connectionState === "connecting" && connectTimedOut);
|
|
3797
4526
|
useBeforeUnload(active && !sessionOver);
|
|
3798
4527
|
useWakeLock(active && !sessionOver);
|
|
3799
4528
|
useEffect(() => {
|
|
@@ -3957,12 +4686,13 @@ function StudioRoom({
|
|
|
3957
4686
|
void meeting.setScreenShareEnabled(false);
|
|
3958
4687
|
}, [guestOffStage, meeting]);
|
|
3959
4688
|
useEffect(() => {
|
|
3960
|
-
if (!sessionOver && !rejected) return;
|
|
4689
|
+
if (!sessionOver && !rejected && !guestConnectFailed) return;
|
|
4690
|
+
pip.close();
|
|
3961
4691
|
setShareGrant("idle");
|
|
3962
4692
|
void meeting.setAudioEnabled(false);
|
|
3963
4693
|
void meeting.setVideoEnabled(false);
|
|
3964
4694
|
void meeting.setScreenShareEnabled(false);
|
|
3965
|
-
}, [meeting, rejected, sessionOver]);
|
|
4695
|
+
}, [guestConnectFailed, meeting, pip.close, rejected, sessionOver]);
|
|
3966
4696
|
useEffect(() => {
|
|
3967
4697
|
return meeting.subscribeMedia(() => {
|
|
3968
4698
|
const self2 = meeting.participants().find((row) => row.isSelf);
|
|
@@ -4000,10 +4730,6 @@ function StudioRoom({
|
|
|
4000
4730
|
[participants]
|
|
4001
4731
|
);
|
|
4002
4732
|
const waitlisted = useMemo(() => meeting.waitlisted(), [meeting, tick2]);
|
|
4003
|
-
const connectionState = useMemo(
|
|
4004
|
-
() => meeting.connectionState(),
|
|
4005
|
-
[meeting, tick2]
|
|
4006
|
-
);
|
|
4007
4733
|
const lastMeetingError = useMemo(
|
|
4008
4734
|
() => meeting.lastMeetingError(),
|
|
4009
4735
|
[meeting, tick2]
|
|
@@ -4021,7 +4747,7 @@ function StudioRoom({
|
|
|
4021
4747
|
captureSupported,
|
|
4022
4748
|
screenPermission
|
|
4023
4749
|
);
|
|
4024
|
-
const showScreenControl = durableScreenBlock
|
|
4750
|
+
const showScreenControl = showScreenShareControl(durableScreenBlock);
|
|
4025
4751
|
const canGoLive = session.role === "moderator" && canPublish(session.stream.status);
|
|
4026
4752
|
const showPublishButton = session.role === "moderator" && !onAir && session.stream.status !== "ended" && session.stream.status !== "error";
|
|
4027
4753
|
const self = participants.find((row) => row.isSelf);
|
|
@@ -4117,17 +4843,10 @@ function StudioRoom({
|
|
|
4117
4843
|
}, [connectionState, session.role]);
|
|
4118
4844
|
useEffect(() => {
|
|
4119
4845
|
if (inWaitingRoom || screenNoticePrimed.current) return;
|
|
4120
|
-
if (
|
|
4846
|
+
if (durableScreenBlock !== "permission") return;
|
|
4121
4847
|
screenNoticePrimed.current = true;
|
|
4122
|
-
toast.message(
|
|
4123
|
-
|
|
4124
|
-
);
|
|
4125
|
-
}, [
|
|
4126
|
-
durableScreenBlock,
|
|
4127
|
-
inWaitingRoom,
|
|
4128
|
-
labels.screenNotAllowed,
|
|
4129
|
-
labels.screenUnsupportedDevice
|
|
4130
|
-
]);
|
|
4848
|
+
toast.message(labels.screenNotAllowed);
|
|
4849
|
+
}, [durableScreenBlock, inWaitingRoom, labels.screenNotAllowed]);
|
|
4131
4850
|
useEffect(() => {
|
|
4132
4851
|
return meeting.subscribeBroadcast((message) => {
|
|
4133
4852
|
if (message.type === "host-media") {
|
|
@@ -4236,6 +4955,10 @@ function StudioRoom({
|
|
|
4236
4955
|
setCameraOn((current) => !current);
|
|
4237
4956
|
}, []);
|
|
4238
4957
|
const onToggleScreen = useCallback(() => {
|
|
4958
|
+
if (durableScreenBlock === "device") {
|
|
4959
|
+
setDownloadAppOpen(true);
|
|
4960
|
+
return;
|
|
4961
|
+
}
|
|
4239
4962
|
if (!canShareFreely || screenRequestPending) return;
|
|
4240
4963
|
if (!screenOn && !canStartScreenShare(meeting.participants())) {
|
|
4241
4964
|
toast.message(labels.screenBusy);
|
|
@@ -4245,12 +4968,17 @@ function StudioRoom({
|
|
|
4245
4968
|
setScreenOn((current) => !current);
|
|
4246
4969
|
}, [
|
|
4247
4970
|
canShareFreely,
|
|
4971
|
+
durableScreenBlock,
|
|
4248
4972
|
labels.screenBusy,
|
|
4249
4973
|
meeting,
|
|
4250
4974
|
screenOn,
|
|
4251
4975
|
screenRequestPending
|
|
4252
4976
|
]);
|
|
4253
4977
|
const onAskToShare = useCallback(async () => {
|
|
4978
|
+
if (durableScreenBlock === "device") {
|
|
4979
|
+
setDownloadAppOpen(true);
|
|
4980
|
+
return;
|
|
4981
|
+
}
|
|
4254
4982
|
const local = meeting.participants().find((row) => row.isSelf);
|
|
4255
4983
|
if (!local) return;
|
|
4256
4984
|
try {
|
|
@@ -4267,7 +4995,12 @@ function StudioRoom({
|
|
|
4267
4995
|
} catch {
|
|
4268
4996
|
toast.error(labels.shareRequestError);
|
|
4269
4997
|
}
|
|
4270
|
-
}, [
|
|
4998
|
+
}, [
|
|
4999
|
+
durableScreenBlock,
|
|
5000
|
+
labels.shareRequestError,
|
|
5001
|
+
labels.shareRequestSent,
|
|
5002
|
+
meeting
|
|
5003
|
+
]);
|
|
4271
5004
|
const verifyGrantTookEffect = useCallback(
|
|
4272
5005
|
(userId, retried = false) => {
|
|
4273
5006
|
const id = window.setTimeout(() => {
|
|
@@ -4430,7 +5163,6 @@ function StudioRoom({
|
|
|
4430
5163
|
}
|
|
4431
5164
|
) });
|
|
4432
5165
|
}
|
|
4433
|
-
const guestConnectFailed = session.role === "guest" && (connectionState === "failed" || connectionState === "connecting" && connectTimedOut);
|
|
4434
5166
|
if (guestConnectFailed) {
|
|
4435
5167
|
const detail = [meeting.roomState(), lastMeetingError].filter((value) => Boolean(value)).join(" \u2014 ");
|
|
4436
5168
|
return /* @__PURE__ */ jsx("div", { className: "flex h-dvh flex-col items-center justify-center p-6", children: /* @__PURE__ */ jsx(
|
|
@@ -4454,6 +5186,10 @@ function StudioRoom({
|
|
|
4454
5186
|
labels,
|
|
4455
5187
|
settingsOpen,
|
|
4456
5188
|
statusLabel: connectionState === "connecting" ? labels.connecting : labels.waitingHost,
|
|
5189
|
+
captionPrefs,
|
|
5190
|
+
onCaptionPrefsChange: setCaptionPrefs,
|
|
5191
|
+
captionsAvailable,
|
|
5192
|
+
effectBackgrounds,
|
|
4457
5193
|
onToggleCamera,
|
|
4458
5194
|
onToggleMute: () => void onToggleMute(),
|
|
4459
5195
|
onOpenSettings: () => setSettingsOpen(true),
|
|
@@ -4561,27 +5297,37 @@ function StudioRoom({
|
|
|
4561
5297
|
}
|
|
4562
5298
|
) : null,
|
|
4563
5299
|
/* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col md:flex-row", children: [
|
|
4564
|
-
/* @__PURE__ */
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
5300
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex min-h-0 min-w-0 flex-1 flex-col", children: [
|
|
5301
|
+
/* @__PURE__ */ jsx(
|
|
5302
|
+
StudioStage,
|
|
5303
|
+
{
|
|
5304
|
+
participants: stageParticipants,
|
|
5305
|
+
meeting,
|
|
5306
|
+
reducedMotion,
|
|
5307
|
+
canModerate: session.role === "moderator",
|
|
5308
|
+
canManageStage,
|
|
5309
|
+
onAir,
|
|
5310
|
+
labels,
|
|
5311
|
+
onKick: (id) => void onKick(id),
|
|
5312
|
+
onMute: (id) => void onHostMedia(id, "audio"),
|
|
5313
|
+
onStopCamera: (id) => void onHostMedia(id, "video"),
|
|
5314
|
+
onStopScreen: (id) => void onHostMedia(id, "screen"),
|
|
5315
|
+
onToggleStage: (tile) => void onSetStage(tile, !isOnStage(tile.stageStatus)),
|
|
5316
|
+
onTogglePin: (tile) => void onTogglePin(tile),
|
|
5317
|
+
onToggleChat: (tile) => void onToggleChat(tile),
|
|
5318
|
+
canTakeOff: (id) => canTakeOffAir(participants, id, onAir),
|
|
5319
|
+
onOpenPeople: () => setSideTab("people")
|
|
5320
|
+
}
|
|
5321
|
+
),
|
|
5322
|
+
captionsOn ? /* @__PURE__ */ jsx(
|
|
5323
|
+
CaptionOverlay,
|
|
5324
|
+
{
|
|
5325
|
+
screen: captionPlayout.screen,
|
|
5326
|
+
className: "bottom-3",
|
|
5327
|
+
textClassName: captionSizeClass(captionPrefs.size)
|
|
5328
|
+
}
|
|
5329
|
+
) : null
|
|
5330
|
+
] }),
|
|
4585
5331
|
sideTab ? /* @__PURE__ */ jsx(
|
|
4586
5332
|
StudioSidePanel,
|
|
4587
5333
|
{
|
|
@@ -4694,6 +5440,12 @@ function StudioRoom({
|
|
|
4694
5440
|
stageEnabled,
|
|
4695
5441
|
canLeaveStage: selfId != null && canTakeOffAir(participants, selfId, onAir),
|
|
4696
5442
|
reducedMotion,
|
|
5443
|
+
captionsOn,
|
|
5444
|
+
captionsAvailable,
|
|
5445
|
+
captionsReady,
|
|
5446
|
+
captionPrefs,
|
|
5447
|
+
onCaptionPrefsChange: setCaptionPrefs,
|
|
5448
|
+
effectBackgrounds,
|
|
4697
5449
|
onToggleMute: () => void onToggleMute(),
|
|
4698
5450
|
onToggleCamera,
|
|
4699
5451
|
onToggleScreen,
|
|
@@ -4721,6 +5473,16 @@ function StudioRoom({
|
|
|
4721
5473
|
onCloseSettings: setSettingsOpen,
|
|
4722
5474
|
onOpenPip,
|
|
4723
5475
|
onClosePip: pip.close,
|
|
5476
|
+
onToggleCaptions: () => {
|
|
5477
|
+
if (!captionsAvailable) return;
|
|
5478
|
+
if (!captionsOn && !captionHintShown.current) {
|
|
5479
|
+
captionHintShown.current = true;
|
|
5480
|
+
toast.message(
|
|
5481
|
+
captionsReady ? labels.captionsDelayed : labels.captionsNotReady
|
|
5482
|
+
);
|
|
5483
|
+
}
|
|
5484
|
+
setCaptionsOn((current) => !current);
|
|
5485
|
+
},
|
|
4724
5486
|
onLeave: () => void onLeave()
|
|
4725
5487
|
}
|
|
4726
5488
|
),
|
|
@@ -4764,7 +5526,21 @@ function StudioRoom({
|
|
|
4764
5526
|
}
|
|
4765
5527
|
),
|
|
4766
5528
|
pip.pipWindow.document.body
|
|
4767
|
-
) : null
|
|
5529
|
+
) : null,
|
|
5530
|
+
/* @__PURE__ */ jsx(
|
|
5531
|
+
StudioDownloadAppDialog,
|
|
5532
|
+
{
|
|
5533
|
+
open: downloadAppOpen,
|
|
5534
|
+
onOpenChange: setDownloadAppOpen,
|
|
5535
|
+
downloadUrl: appDownloadUrl,
|
|
5536
|
+
labels: {
|
|
5537
|
+
screenDownloadAppTitle: labels.screenDownloadAppTitle,
|
|
5538
|
+
screenUnsupportedDevice: labels.screenUnsupportedDevice,
|
|
5539
|
+
screenDownloadAppAction: labels.screenDownloadAppAction,
|
|
5540
|
+
screenDownloadAppDismiss: labels.screenDownloadAppDismiss
|
|
5541
|
+
}
|
|
5542
|
+
}
|
|
5543
|
+
)
|
|
4768
5544
|
] });
|
|
4769
5545
|
}
|
|
4770
5546
|
function isOverconstrained(error) {
|
|
@@ -5101,16 +5877,16 @@ function usePrefersReducedMotion() {
|
|
|
5101
5877
|
// src/studio/lib/guest-identity.ts
|
|
5102
5878
|
var NAME_KEY = "livo-studio-display-name";
|
|
5103
5879
|
var ID_KEY = "livo-studio-guest-id";
|
|
5104
|
-
function readStudioGuestIdentity(storage =
|
|
5880
|
+
function readStudioGuestIdentity(storage = defaultStorage4()) {
|
|
5105
5881
|
const guestId = storage?.getItem(ID_KEY)?.trim() || crypto.randomUUID();
|
|
5106
5882
|
storage?.setItem(ID_KEY, guestId);
|
|
5107
5883
|
const displayName = storage?.getItem(NAME_KEY)?.trim() ?? "";
|
|
5108
5884
|
return { displayName, guestId };
|
|
5109
5885
|
}
|
|
5110
|
-
function persistStudioGuestName(displayName, storage =
|
|
5886
|
+
function persistStudioGuestName(displayName, storage = defaultStorage4()) {
|
|
5111
5887
|
storage?.setItem(NAME_KEY, displayName.trim());
|
|
5112
5888
|
}
|
|
5113
|
-
function
|
|
5889
|
+
function defaultStorage4() {
|
|
5114
5890
|
if (typeof window === "undefined") return null;
|
|
5115
5891
|
try {
|
|
5116
5892
|
return window.localStorage;
|
|
@@ -5479,10 +6255,19 @@ function studioMeetingFromRtk(raw) {
|
|
|
5479
6255
|
if (typeof window !== "undefined") {
|
|
5480
6256
|
window.addEventListener("unhandledrejection", onUnhandledRejection);
|
|
5481
6257
|
}
|
|
6258
|
+
let videoEffects = readStudioVideoEffects();
|
|
6259
|
+
async function applyCurrentVideoEffects() {
|
|
6260
|
+
await applyStudioVideoEffects({
|
|
6261
|
+
self: meeting.self,
|
|
6262
|
+
meeting,
|
|
6263
|
+
config: videoEffects
|
|
6264
|
+
});
|
|
6265
|
+
}
|
|
5482
6266
|
async function republishCameraIfOn() {
|
|
5483
6267
|
if (meeting.self?.videoEnabled !== true) return;
|
|
5484
6268
|
await meeting.self.disableVideo?.();
|
|
5485
6269
|
await meeting.self.enableVideo?.();
|
|
6270
|
+
await applyCurrentVideoEffects();
|
|
5486
6271
|
}
|
|
5487
6272
|
async function acceptAllowedStageIfOffAir() {
|
|
5488
6273
|
if (currentStageStatus() !== "OFF_STAGE") return;
|
|
@@ -5670,6 +6455,7 @@ function studioMeetingFromRtk(raw) {
|
|
|
5670
6455
|
async join() {
|
|
5671
6456
|
await meeting.join?.();
|
|
5672
6457
|
await joinStageIfAllowed();
|
|
6458
|
+
await applyCurrentVideoEffects();
|
|
5673
6459
|
},
|
|
5674
6460
|
async leave() {
|
|
5675
6461
|
if (typeof window !== "undefined") {
|
|
@@ -5682,8 +6468,10 @@ function studioMeetingFromRtk(raw) {
|
|
|
5682
6468
|
else await meeting.self?.disableAudio?.();
|
|
5683
6469
|
},
|
|
5684
6470
|
async setVideoEnabled(enabled) {
|
|
5685
|
-
if (enabled)
|
|
5686
|
-
|
|
6471
|
+
if (enabled) {
|
|
6472
|
+
await meeting.self?.enableVideo?.();
|
|
6473
|
+
await applyCurrentVideoEffects();
|
|
6474
|
+
} else await meeting.self?.disableVideo?.();
|
|
5687
6475
|
},
|
|
5688
6476
|
async setScreenShareEnabled(enabled) {
|
|
5689
6477
|
if (enabled) await meeting.self?.enableScreenShare?.();
|
|
@@ -6067,6 +6855,14 @@ function studioMeetingFromRtk(raw) {
|
|
|
6067
6855
|
}
|
|
6068
6856
|
});
|
|
6069
6857
|
},
|
|
6858
|
+
async setVideoEffects(config) {
|
|
6859
|
+
videoEffects = config;
|
|
6860
|
+
persistStudioVideoEffects(config);
|
|
6861
|
+
await applyCurrentVideoEffects();
|
|
6862
|
+
},
|
|
6863
|
+
videoEffects() {
|
|
6864
|
+
return videoEffects;
|
|
6865
|
+
},
|
|
6070
6866
|
async setDisplayName(name) {
|
|
6071
6867
|
const trimmed = name.trim().slice(0, STUDIO_DISPLAY_NAME_MAX);
|
|
6072
6868
|
if (!trimmed) return;
|
|
@@ -6298,6 +7094,21 @@ async function getPublicStreamStatus(streamId, apiUrl = defaultApiUrl(), fetchFn
|
|
|
6298
7094
|
return null;
|
|
6299
7095
|
}
|
|
6300
7096
|
}
|
|
7097
|
+
async function getPublicStreamMeta(streamId, apiUrl = defaultApiUrl(), fetchFn = fetch) {
|
|
7098
|
+
try {
|
|
7099
|
+
const body = await publicJson(
|
|
7100
|
+
`${apiUrl.replace(/\/$/, "")}/public/streams/${encodeURIComponent(streamId)}`,
|
|
7101
|
+
{},
|
|
7102
|
+
fetchFn
|
|
7103
|
+
);
|
|
7104
|
+
return parsePublicStreamMeta(body);
|
|
7105
|
+
} catch (error) {
|
|
7106
|
+
if (error instanceof StudioApiError && (error.status === 404 || error.status === 410)) {
|
|
7107
|
+
return { status: "ended", captionsEnabled: false };
|
|
7108
|
+
}
|
|
7109
|
+
return null;
|
|
7110
|
+
}
|
|
7111
|
+
}
|
|
6301
7112
|
var POLL_INTERVAL_MS = 5e3;
|
|
6302
7113
|
function LivoHostStudio({
|
|
6303
7114
|
token,
|
|
@@ -6307,11 +7118,15 @@ function LivoHostStudio({
|
|
|
6307
7118
|
embed = false,
|
|
6308
7119
|
chrome,
|
|
6309
7120
|
playerOrigin,
|
|
6310
|
-
theme
|
|
7121
|
+
theme,
|
|
7122
|
+
appDownloadUrl
|
|
6311
7123
|
}) {
|
|
6312
7124
|
const reducedMotion = usePrefersReducedMotion();
|
|
6313
7125
|
const [session, setSession] = useState(null);
|
|
6314
7126
|
const [status, setStatus] = useState(null);
|
|
7127
|
+
const [captionsEnabled, setCaptionsEnabled] = useState(
|
|
7128
|
+
void 0
|
|
7129
|
+
);
|
|
6315
7130
|
const [error, setError] = useState(null);
|
|
6316
7131
|
const { meeting, error: meetingError } = useRealtimeKitMeeting(
|
|
6317
7132
|
session?.authToken ?? null
|
|
@@ -6360,6 +7175,20 @@ function LivoHostStudio({
|
|
|
6360
7175
|
window.clearInterval(id);
|
|
6361
7176
|
};
|
|
6362
7177
|
}, [client, status]);
|
|
7178
|
+
useEffect(() => {
|
|
7179
|
+
if (!session) return;
|
|
7180
|
+
let cancelled = false;
|
|
7181
|
+
const poll = async () => {
|
|
7182
|
+
const next = await getPublicStreamMeta(session.stream.id, apiUrl);
|
|
7183
|
+
if (!cancelled && next) setCaptionsEnabled(next.captionsEnabled);
|
|
7184
|
+
};
|
|
7185
|
+
void poll();
|
|
7186
|
+
const id = window.setInterval(() => void poll(), POLL_INTERVAL_MS);
|
|
7187
|
+
return () => {
|
|
7188
|
+
cancelled = true;
|
|
7189
|
+
window.clearInterval(id);
|
|
7190
|
+
};
|
|
7191
|
+
}, [apiUrl, session]);
|
|
6363
7192
|
const liveSession = useMemo(() => {
|
|
6364
7193
|
if (!session) return null;
|
|
6365
7194
|
if (!status) return session;
|
|
@@ -6367,10 +7196,11 @@ function LivoHostStudio({
|
|
|
6367
7196
|
...session,
|
|
6368
7197
|
stream: {
|
|
6369
7198
|
...session.stream,
|
|
6370
|
-
status
|
|
7199
|
+
status,
|
|
7200
|
+
captionsEnabled: captionsEnabled ?? session.stream.captionsEnabled
|
|
6371
7201
|
}
|
|
6372
7202
|
};
|
|
6373
|
-
}, [session, status]);
|
|
7203
|
+
}, [captionsEnabled, session, status]);
|
|
6374
7204
|
if (error || meetingError) {
|
|
6375
7205
|
return /* @__PURE__ */ jsx("p", { className: "text-destructive p-6", role: "alert", children: error ?? meetingError });
|
|
6376
7206
|
}
|
|
@@ -6387,7 +7217,9 @@ function LivoHostStudio({
|
|
|
6387
7217
|
labels,
|
|
6388
7218
|
embed,
|
|
6389
7219
|
chrome,
|
|
6390
|
-
playerOrigin
|
|
7220
|
+
playerOrigin,
|
|
7221
|
+
appDownloadUrl,
|
|
7222
|
+
apiUrl
|
|
6391
7223
|
}
|
|
6392
7224
|
);
|
|
6393
7225
|
}
|
|
@@ -6400,11 +7232,15 @@ function LivoGuestStudio({
|
|
|
6400
7232
|
lobbyLabels,
|
|
6401
7233
|
embed = false,
|
|
6402
7234
|
playerOrigin,
|
|
6403
|
-
theme
|
|
7235
|
+
theme,
|
|
7236
|
+
appDownloadUrl
|
|
6404
7237
|
}) {
|
|
6405
7238
|
const reducedMotion = usePrefersReducedMotion();
|
|
6406
7239
|
const [session, setSession] = useState(null);
|
|
6407
7240
|
const [status, setStatus] = useState(null);
|
|
7241
|
+
const [captionsEnabled, setCaptionsEnabled] = useState(
|
|
7242
|
+
void 0
|
|
7243
|
+
);
|
|
6408
7244
|
const [error, setError] = useState(null);
|
|
6409
7245
|
const [displayName, setDisplayName] = useState("");
|
|
6410
7246
|
const [guestId, setGuestId] = useState("");
|
|
@@ -6508,8 +7344,11 @@ function LivoGuestStudio({
|
|
|
6508
7344
|
if (current === "ended" || current === "error") return;
|
|
6509
7345
|
let cancelled = false;
|
|
6510
7346
|
const poll = async () => {
|
|
6511
|
-
const next = await
|
|
6512
|
-
if (!cancelled && next)
|
|
7347
|
+
const next = await getPublicStreamMeta(session.stream.id, apiUrl);
|
|
7348
|
+
if (!cancelled && next) {
|
|
7349
|
+
setStatus(next.status);
|
|
7350
|
+
setCaptionsEnabled(next.captionsEnabled);
|
|
7351
|
+
}
|
|
6513
7352
|
};
|
|
6514
7353
|
void poll();
|
|
6515
7354
|
const intervalMs = current === "public" ? POLL_INTERVAL_MS2 : PREVIEW_POLL_INTERVAL_MS;
|
|
@@ -6524,9 +7363,13 @@ function LivoGuestStudio({
|
|
|
6524
7363
|
if (!status) return session;
|
|
6525
7364
|
return {
|
|
6526
7365
|
...session,
|
|
6527
|
-
stream: {
|
|
7366
|
+
stream: {
|
|
7367
|
+
...session.stream,
|
|
7368
|
+
status,
|
|
7369
|
+
captionsEnabled: captionsEnabled ?? session.stream.captionsEnabled
|
|
7370
|
+
}
|
|
6528
7371
|
};
|
|
6529
|
-
}, [session, status]);
|
|
7372
|
+
}, [captionsEnabled, session, status]);
|
|
6530
7373
|
useEffect(() => {
|
|
6531
7374
|
if (retryAfterMs == null) return;
|
|
6532
7375
|
const id = window.setTimeout(() => {
|
|
@@ -6586,7 +7429,9 @@ function LivoGuestStudio({
|
|
|
6586
7429
|
playerOrigin,
|
|
6587
7430
|
initialCameraOn: cameraOn,
|
|
6588
7431
|
initialMuted: muted,
|
|
6589
|
-
initialDevices: deviceSelection
|
|
7432
|
+
initialDevices: deviceSelection,
|
|
7433
|
+
appDownloadUrl,
|
|
7434
|
+
apiUrl
|
|
6590
7435
|
}
|
|
6591
7436
|
);
|
|
6592
7437
|
}
|
|
@@ -6691,6 +7536,9 @@ function studioRoomLabels(t) {
|
|
|
6691
7536
|
pipTitle: t("pipTitle"),
|
|
6692
7537
|
pipError: t("pipError"),
|
|
6693
7538
|
screenUnsupportedDevice: t("screenUnsupportedDevice"),
|
|
7539
|
+
screenDownloadAppTitle: t("screenDownloadAppTitle"),
|
|
7540
|
+
screenDownloadAppAction: t("screenDownloadAppAction"),
|
|
7541
|
+
screenDownloadAppDismiss: t("screenDownloadAppDismiss"),
|
|
6694
7542
|
screenNotAllowed: t("screenNotAllowed"),
|
|
6695
7543
|
askToShare: t("askToShare"),
|
|
6696
7544
|
shareRequested: t("shareRequested"),
|
|
@@ -6759,10 +7607,40 @@ function studioRoomLabels(t) {
|
|
|
6759
7607
|
chatPin: t("chatPin"),
|
|
6760
7608
|
chatUnpin: t("chatUnpin"),
|
|
6761
7609
|
chatSave: t("chatSave"),
|
|
6762
|
-
waitingSkeleton: t("waitingSkeleton")
|
|
7610
|
+
waitingSkeleton: t("waitingSkeleton"),
|
|
7611
|
+
more: t("more"),
|
|
7612
|
+
captions: t("captions"),
|
|
7613
|
+
captionsOn: t("captionsOn"),
|
|
7614
|
+
captionsOff: t("captionsOff"),
|
|
7615
|
+
captionsNotReady: t("captionsNotReady"),
|
|
7616
|
+
captionsDelayed: t("captionsDelayed"),
|
|
7617
|
+
captionsLanguage: t("captionsLanguage"),
|
|
7618
|
+
captionsSize: t("captionsSize"),
|
|
7619
|
+
captionsSizeS: t("captionsSizeS"),
|
|
7620
|
+
captionsSizeM: t("captionsSizeM"),
|
|
7621
|
+
captionsSizeL: t("captionsSizeL"),
|
|
7622
|
+
settingsGeneral: t("settingsGeneral"),
|
|
7623
|
+
settingsAudio: t("settingsAudio"),
|
|
7624
|
+
settingsVideo: t("settingsVideo"),
|
|
7625
|
+
settingsEffects: t("settingsEffects"),
|
|
7626
|
+
settingsSubtitles: t("settingsSubtitles"),
|
|
7627
|
+
effectNone: t("effectNone"),
|
|
7628
|
+
effectBlur: t("effectBlur"),
|
|
7629
|
+
effectBlurStrong: t("effectBlurStrong"),
|
|
7630
|
+
effectBackgrounds: t("effectBackgrounds"),
|
|
7631
|
+
effectFilters: t("effectFilters"),
|
|
7632
|
+
effectFilterNone: t("effectFilterNone"),
|
|
7633
|
+
effectFilterGrayscale: t("effectFilterGrayscale"),
|
|
7634
|
+
effectFilterSepia: t("effectFilterSepia"),
|
|
7635
|
+
effectFilterWarm: t("effectFilterWarm"),
|
|
7636
|
+
effectFilterCool: t("effectFilterCool"),
|
|
7637
|
+
effectFilterBright: t("effectFilterBright"),
|
|
7638
|
+
localeEn: t("localeEn"),
|
|
7639
|
+
localeEs: t("localeEs"),
|
|
7640
|
+
localePt: t("localePt")
|
|
6763
7641
|
};
|
|
6764
7642
|
}
|
|
6765
7643
|
|
|
6766
|
-
export { LivoGuestStudio, LivoHostStudio, StudioApiError, StudioControlClient, StudioControlError, StudioGuestLobby, StudioRoom, StudioSkeleton, StudioWindowGate, StudioWindowGateContext, applyEmbedTheme, controlActionsFromClient, createStudioControlClient, decodeEmbedTheme, getPublicStreamStatus, getStudioJoinStatus, joinStudioGuest, redeemStudioHost, studioErrorCode, studioRoomLabels, usePrefersReducedMotion, useRealtimeKitMeeting, useStudioWindowGate };
|
|
7644
|
+
export { LivoGuestStudio, LivoHostStudio, StudioApiError, StudioControlClient, StudioControlError, StudioGuestLobby, StudioRoom, StudioSkeleton, StudioWindowGate, StudioWindowGateContext, applyEmbedTheme, controlActionsFromClient, createStudioControlClient, decodeEmbedTheme, getPublicStreamMeta, getPublicStreamStatus, getStudioJoinStatus, joinStudioGuest, redeemStudioHost, studioErrorCode, studioRoomLabels, usePrefersReducedMotion, useRealtimeKitMeeting, useStudioWindowGate };
|
|
6767
7645
|
//# sourceMappingURL=index.js.map
|
|
6768
7646
|
//# sourceMappingURL=index.js.map
|