@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.cjs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
var react = require('react');
|
|
5
5
|
var reactDom = require('react-dom');
|
|
6
6
|
var lucideReact = require('lucide-react');
|
|
7
|
+
var video = require('@livo-tv/blocks/video');
|
|
7
8
|
var sonner = require('sonner');
|
|
8
9
|
var AlertDialogPrimitive = require('@radix-ui/react-alert-dialog');
|
|
9
10
|
var clsx = require('clsx');
|
|
@@ -542,52 +543,38 @@ function canTakeOffAir(tiles, participantId, onAir) {
|
|
|
542
543
|
(tile) => tile.id !== participantId && isOnStage(tile.stageStatus)
|
|
543
544
|
);
|
|
544
545
|
}
|
|
546
|
+
function pipSpotlightFrom(tile, kind) {
|
|
547
|
+
return {
|
|
548
|
+
participantId: tile.id,
|
|
549
|
+
name: tile.name,
|
|
550
|
+
kind,
|
|
551
|
+
picture: tile.picture
|
|
552
|
+
};
|
|
553
|
+
}
|
|
545
554
|
function resolveStudioPipSpotlight(tiles, activeSpeakerId) {
|
|
546
555
|
const visible = tiles.filter((tile) => isOnStage(tile.stageStatus));
|
|
547
556
|
const pinned = visible.find((tile) => tile.pinned === true);
|
|
548
557
|
if (pinned) {
|
|
549
|
-
return
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
558
|
+
return pipSpotlightFrom(
|
|
559
|
+
pinned,
|
|
560
|
+
pinned.screenShareEnabled ? "screen" : pinned.videoEnabled ? "camera" : "idle"
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
const speaker = activeSpeakerId ? visible.find((tile) => tile.id === activeSpeakerId) : void 0;
|
|
564
|
+
if (speaker) {
|
|
565
|
+
return pipSpotlightFrom(speaker, speaker.videoEnabled ? "camera" : "idle");
|
|
555
566
|
}
|
|
556
567
|
const sharing = visible.find((tile) => tile.screenShareEnabled);
|
|
557
568
|
if (sharing) {
|
|
558
|
-
return
|
|
559
|
-
participantId: sharing.id,
|
|
560
|
-
name: sharing.name,
|
|
561
|
-
kind: "screen",
|
|
562
|
-
picture: sharing.picture
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
const speaker = activeSpeakerId ? visible.find((tile) => tile.id === activeSpeakerId && tile.videoEnabled) : void 0;
|
|
566
|
-
if (speaker) {
|
|
567
|
-
return {
|
|
568
|
-
participantId: speaker.id,
|
|
569
|
-
name: speaker.name,
|
|
570
|
-
kind: "camera",
|
|
571
|
-
picture: speaker.picture
|
|
572
|
-
};
|
|
569
|
+
return pipSpotlightFrom(sharing, "screen");
|
|
573
570
|
}
|
|
574
571
|
const remoteCam = visible.find((tile) => !tile.isSelf && tile.videoEnabled);
|
|
575
572
|
if (remoteCam) {
|
|
576
|
-
return
|
|
577
|
-
participantId: remoteCam.id,
|
|
578
|
-
name: remoteCam.name,
|
|
579
|
-
kind: "camera",
|
|
580
|
-
picture: remoteCam.picture
|
|
581
|
-
};
|
|
573
|
+
return pipSpotlightFrom(remoteCam, "camera");
|
|
582
574
|
}
|
|
583
575
|
const self = visible.find((tile) => tile.isSelf);
|
|
584
576
|
if (!self) return null;
|
|
585
|
-
return
|
|
586
|
-
participantId: self.id,
|
|
587
|
-
name: self.name,
|
|
588
|
-
kind: self.videoEnabled ? "camera" : "idle",
|
|
589
|
-
picture: self.picture
|
|
590
|
-
};
|
|
577
|
+
return pipSpotlightFrom(self, self.videoEnabled ? "camera" : "idle");
|
|
591
578
|
}
|
|
592
579
|
function resolveStudioPipSelfView(tiles, spotlight) {
|
|
593
580
|
const self = tiles.find((tile) => tile.isSelf);
|
|
@@ -706,6 +693,9 @@ function screenShareBlock(captureSupported, permission) {
|
|
|
706
693
|
if (permission === "NOT_ALLOWED") return "permission";
|
|
707
694
|
return null;
|
|
708
695
|
}
|
|
696
|
+
function showScreenShareControl(block) {
|
|
697
|
+
return block !== "permission";
|
|
698
|
+
}
|
|
709
699
|
|
|
710
700
|
// src/studio/lib/parse.ts
|
|
711
701
|
function isRecord(value) {
|
|
@@ -725,7 +715,14 @@ function parseStudioStreamSummary(value) {
|
|
|
725
715
|
const status = readString(value, "status");
|
|
726
716
|
if (!id || !title || !status) return null;
|
|
727
717
|
const encoderLostAt = isRecord(value) ? typeof value.encoderLostAt === "number" ? value.encoderLostAt : value.encoderLostAt === null ? null : void 0 : void 0;
|
|
728
|
-
|
|
718
|
+
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;
|
|
719
|
+
return {
|
|
720
|
+
id,
|
|
721
|
+
title,
|
|
722
|
+
status,
|
|
723
|
+
...encoderLostAt !== void 0 ? { encoderLostAt } : {},
|
|
724
|
+
...captionsEnabled !== void 0 ? { captionsEnabled } : {}
|
|
725
|
+
};
|
|
729
726
|
}
|
|
730
727
|
function parseStudioSession(value) {
|
|
731
728
|
if (!isRecord(value)) return null;
|
|
@@ -737,7 +734,9 @@ function parseStudioSession(value) {
|
|
|
737
734
|
const fromFull = isRecord(value.stream) ? parseStudioStreamSummary({
|
|
738
735
|
id: value.stream.id,
|
|
739
736
|
title: value.stream.title,
|
|
740
|
-
status: value.stream.status
|
|
737
|
+
status: value.stream.status,
|
|
738
|
+
captionsEnabled: value.stream.captionsEnabled,
|
|
739
|
+
captionsUrl: value.stream.captionsUrl
|
|
741
740
|
}) : null;
|
|
742
741
|
const stream = fromSummary ?? fromFull;
|
|
743
742
|
if (!stream) return null;
|
|
@@ -770,6 +769,14 @@ function parsePublicStreamStatus(value) {
|
|
|
770
769
|
const status = readString(nested, "status");
|
|
771
770
|
return status;
|
|
772
771
|
}
|
|
772
|
+
function parsePublicStreamMeta(value) {
|
|
773
|
+
if (!isRecord(value)) return null;
|
|
774
|
+
const nested = isRecord(value.stream) ? value.stream : value;
|
|
775
|
+
const status = readString(nested, "status");
|
|
776
|
+
if (!status) return null;
|
|
777
|
+
const captionsEnabled = typeof nested.captionsEnabled === "boolean" ? nested.captionsEnabled : typeof nested.captionsUrl === "string" && nested.captionsUrl.length > 0;
|
|
778
|
+
return { status, captionsEnabled };
|
|
779
|
+
}
|
|
773
780
|
function studioErrorCode(error) {
|
|
774
781
|
if (!error || typeof error !== "object") return null;
|
|
775
782
|
const body = "body" in error ? error.body : null;
|
|
@@ -1488,6 +1495,19 @@ function DialogHeader({ className, ...props }) {
|
|
|
1488
1495
|
}
|
|
1489
1496
|
);
|
|
1490
1497
|
}
|
|
1498
|
+
function DialogFooter({ className, ...props }) {
|
|
1499
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1500
|
+
"div",
|
|
1501
|
+
{
|
|
1502
|
+
"data-slot": "dialog-footer",
|
|
1503
|
+
className: cn(
|
|
1504
|
+
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
1505
|
+
className
|
|
1506
|
+
),
|
|
1507
|
+
...props
|
|
1508
|
+
}
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1491
1511
|
function DialogTitle({
|
|
1492
1512
|
className,
|
|
1493
1513
|
...props
|
|
@@ -1636,6 +1656,211 @@ async function playSpeakerTest(deviceId) {
|
|
|
1636
1656
|
await ctx3.close();
|
|
1637
1657
|
}
|
|
1638
1658
|
}
|
|
1659
|
+
|
|
1660
|
+
// src/studio/lib/caption-prefs.ts
|
|
1661
|
+
var STUDIO_CAPTION_LOCALES = ["en", "es", "pt"];
|
|
1662
|
+
var DEFAULT_STUDIO_CAPTION_PREFS = {
|
|
1663
|
+
locale: "en",
|
|
1664
|
+
size: "m"
|
|
1665
|
+
};
|
|
1666
|
+
function isStudioCaptionLocale(value) {
|
|
1667
|
+
return STUDIO_CAPTION_LOCALES.includes(value);
|
|
1668
|
+
}
|
|
1669
|
+
var STORAGE_KEY = "livo-studio-captions";
|
|
1670
|
+
function defaultStorage2() {
|
|
1671
|
+
if (typeof window === "undefined") return null;
|
|
1672
|
+
try {
|
|
1673
|
+
return window.localStorage;
|
|
1674
|
+
} catch {
|
|
1675
|
+
return null;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
function isCaptionSize(value) {
|
|
1679
|
+
return value === "s" || value === "m" || value === "l";
|
|
1680
|
+
}
|
|
1681
|
+
function captionSizeClass(size) {
|
|
1682
|
+
switch (size) {
|
|
1683
|
+
case "s":
|
|
1684
|
+
return "text-[length:clamp(10px,2.1cqh,0.75rem)]";
|
|
1685
|
+
case "m":
|
|
1686
|
+
return "text-[length:clamp(10px,2.6cqh,0.875rem)]";
|
|
1687
|
+
case "l":
|
|
1688
|
+
return "text-[length:clamp(12px,3.2cqh,1.05rem)]";
|
|
1689
|
+
default: {
|
|
1690
|
+
const _exhaustive = size;
|
|
1691
|
+
return _exhaustive;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
function readStudioCaptionPrefs(storage = defaultStorage2(), fallbackLocale = "en") {
|
|
1696
|
+
const raw = storage?.getItem(STORAGE_KEY);
|
|
1697
|
+
if (!raw) {
|
|
1698
|
+
return { ...DEFAULT_STUDIO_CAPTION_PREFS, locale: fallbackLocale };
|
|
1699
|
+
}
|
|
1700
|
+
try {
|
|
1701
|
+
const parsed = JSON.parse(raw);
|
|
1702
|
+
const locale = typeof parsed.locale === "string" && isStudioCaptionLocale(parsed.locale) ? parsed.locale : fallbackLocale;
|
|
1703
|
+
const size = typeof parsed.size === "string" && isCaptionSize(parsed.size) ? parsed.size : "m";
|
|
1704
|
+
return { locale, size };
|
|
1705
|
+
} catch {
|
|
1706
|
+
return { ...DEFAULT_STUDIO_CAPTION_PREFS, locale: fallbackLocale };
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
function persistStudioCaptionPrefs(prefs, storage = defaultStorage2()) {
|
|
1710
|
+
storage?.setItem(STORAGE_KEY, JSON.stringify(prefs));
|
|
1711
|
+
}
|
|
1712
|
+
function studioCaptionsBaseUrl(apiUrl, streamId) {
|
|
1713
|
+
if (!apiUrl) return void 0;
|
|
1714
|
+
return `${apiUrl.replace(/\/$/, "")}/public/streams/${encodeURIComponent(streamId)}/captions`;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// src/studio/lib/video-effects.ts
|
|
1718
|
+
var DEFAULT_STUDIO_VIDEO_EFFECTS = {
|
|
1719
|
+
background: "none",
|
|
1720
|
+
blurStrength: 50,
|
|
1721
|
+
filter: "none"
|
|
1722
|
+
};
|
|
1723
|
+
var FILTER_CSS = {
|
|
1724
|
+
none: "",
|
|
1725
|
+
grayscale: "grayscale(1)",
|
|
1726
|
+
sepia: "sepia(0.85)",
|
|
1727
|
+
warm: "sepia(0.35) saturate(1.2) hue-rotate(-10deg)",
|
|
1728
|
+
cool: "saturate(0.9) hue-rotate(15deg) brightness(1.05)",
|
|
1729
|
+
bright: "brightness(1.15) contrast(1.08)"
|
|
1730
|
+
};
|
|
1731
|
+
function defaultStudioEffectBackgrounds(cdnBase = "https://hls.livo.tv") {
|
|
1732
|
+
const root = cdnBase.replace(/\/$/, "");
|
|
1733
|
+
return [
|
|
1734
|
+
{
|
|
1735
|
+
id: "office",
|
|
1736
|
+
url: `${root}/env/backgrounds/office.jpg`,
|
|
1737
|
+
label: "Office"
|
|
1738
|
+
},
|
|
1739
|
+
{
|
|
1740
|
+
id: "lounge",
|
|
1741
|
+
url: `${root}/env/backgrounds/lounge.jpg`,
|
|
1742
|
+
label: "Lounge"
|
|
1743
|
+
},
|
|
1744
|
+
{
|
|
1745
|
+
id: "studio",
|
|
1746
|
+
url: `${root}/env/backgrounds/studio.jpg`,
|
|
1747
|
+
label: "Studio"
|
|
1748
|
+
},
|
|
1749
|
+
{ id: "sky", url: `${root}/env/backgrounds/sky.jpg`, label: "Sky" },
|
|
1750
|
+
{
|
|
1751
|
+
id: "forest",
|
|
1752
|
+
url: `${root}/env/backgrounds/forest.jpg`,
|
|
1753
|
+
label: "Forest"
|
|
1754
|
+
},
|
|
1755
|
+
{
|
|
1756
|
+
id: "abstract",
|
|
1757
|
+
url: `${root}/env/backgrounds/abstract.jpg`,
|
|
1758
|
+
label: "Abstract"
|
|
1759
|
+
}
|
|
1760
|
+
];
|
|
1761
|
+
}
|
|
1762
|
+
function createFilterVideoMiddleware(filter) {
|
|
1763
|
+
const css = FILTER_CSS[filter];
|
|
1764
|
+
if (!css) return null;
|
|
1765
|
+
return async () => {
|
|
1766
|
+
const scratch = document.createElement("canvas");
|
|
1767
|
+
return async (canvas, ctx3) => {
|
|
1768
|
+
if (scratch.width !== canvas.width || scratch.height !== canvas.height) {
|
|
1769
|
+
scratch.width = canvas.width;
|
|
1770
|
+
scratch.height = canvas.height;
|
|
1771
|
+
}
|
|
1772
|
+
const copy = scratch.getContext("2d");
|
|
1773
|
+
if (!copy) return;
|
|
1774
|
+
copy.clearRect(0, 0, scratch.width, scratch.height);
|
|
1775
|
+
copy.drawImage(canvas, 0, 0);
|
|
1776
|
+
ctx3.save();
|
|
1777
|
+
ctx3.filter = css;
|
|
1778
|
+
ctx3.drawImage(scratch, 0, 0);
|
|
1779
|
+
ctx3.restore();
|
|
1780
|
+
};
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
async function loadBackgroundTransformer(meeting) {
|
|
1784
|
+
const mod = await import('@cloudflare/realtimekit-virtual-background');
|
|
1785
|
+
const Init = mod.default ?? mod.RealtimeKitVideoBackgroundTransformer ?? null;
|
|
1786
|
+
if (!Init) return null;
|
|
1787
|
+
return Init.init({ meeting });
|
|
1788
|
+
}
|
|
1789
|
+
async function applyStudioVideoEffects(options) {
|
|
1790
|
+
const self = options.self;
|
|
1791
|
+
if (!self?.removeAllVideoMiddlewares || !self.addVideoMiddleware) return;
|
|
1792
|
+
await self.removeAllVideoMiddlewares();
|
|
1793
|
+
const needsSegmentation = options.config.background !== "none";
|
|
1794
|
+
if (needsSegmentation) {
|
|
1795
|
+
await self.setVideoMiddlewareGlobalConfig?.({
|
|
1796
|
+
disablePerFrameCanvasRendering: true
|
|
1797
|
+
});
|
|
1798
|
+
try {
|
|
1799
|
+
const transformer = await loadBackgroundTransformer(options.meeting);
|
|
1800
|
+
if (transformer && options.config.background === "blur") {
|
|
1801
|
+
const strength = Math.min(
|
|
1802
|
+
100,
|
|
1803
|
+
Math.max(0, options.config.blurStrength)
|
|
1804
|
+
);
|
|
1805
|
+
await self.addVideoMiddleware(
|
|
1806
|
+
await transformer.createBackgroundBlurVideoMiddleware(strength)
|
|
1807
|
+
);
|
|
1808
|
+
} else if (transformer && options.config.background === "image" && options.config.imageUrl) {
|
|
1809
|
+
await self.addVideoMiddleware(
|
|
1810
|
+
await transformer.createStaticBackgroundVideoMiddleware(
|
|
1811
|
+
options.config.imageUrl
|
|
1812
|
+
)
|
|
1813
|
+
);
|
|
1814
|
+
}
|
|
1815
|
+
} catch {
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
const filter = createFilterVideoMiddleware(options.config.filter);
|
|
1819
|
+
if (filter) await self.addVideoMiddleware(filter);
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
// src/studio/lib/effects-prefs.ts
|
|
1823
|
+
var STORAGE_KEY2 = "livo-studio-video-effects";
|
|
1824
|
+
function defaultStorage3() {
|
|
1825
|
+
if (typeof window === "undefined") return null;
|
|
1826
|
+
try {
|
|
1827
|
+
return window.localStorage;
|
|
1828
|
+
} catch {
|
|
1829
|
+
return null;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
var BACKGROUNDS = ["none", "blur", "image"];
|
|
1833
|
+
var FILTERS = [
|
|
1834
|
+
"none",
|
|
1835
|
+
"grayscale",
|
|
1836
|
+
"sepia",
|
|
1837
|
+
"warm",
|
|
1838
|
+
"cool",
|
|
1839
|
+
"bright"
|
|
1840
|
+
];
|
|
1841
|
+
function isBackground(value) {
|
|
1842
|
+
return BACKGROUNDS.includes(value);
|
|
1843
|
+
}
|
|
1844
|
+
function isFilter(value) {
|
|
1845
|
+
return FILTERS.includes(value);
|
|
1846
|
+
}
|
|
1847
|
+
function readStudioVideoEffects(storage = defaultStorage3()) {
|
|
1848
|
+
const raw = storage?.getItem(STORAGE_KEY2);
|
|
1849
|
+
if (!raw) return { ...DEFAULT_STUDIO_VIDEO_EFFECTS };
|
|
1850
|
+
try {
|
|
1851
|
+
const parsed = JSON.parse(raw);
|
|
1852
|
+
const background = typeof parsed.background === "string" && isBackground(parsed.background) ? parsed.background : "none";
|
|
1853
|
+
const filter = typeof parsed.filter === "string" && isFilter(parsed.filter) ? parsed.filter : "none";
|
|
1854
|
+
const blurStrength = typeof parsed.blurStrength === "number" && Number.isFinite(parsed.blurStrength) ? Math.min(100, Math.max(0, parsed.blurStrength)) : 50;
|
|
1855
|
+
const imageUrl = typeof parsed.imageUrl === "string" && parsed.imageUrl ? parsed.imageUrl : void 0;
|
|
1856
|
+
return { background, blurStrength, imageUrl, filter };
|
|
1857
|
+
} catch {
|
|
1858
|
+
return { ...DEFAULT_STUDIO_VIDEO_EFFECTS };
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
function persistStudioVideoEffects(config, storage = defaultStorage3()) {
|
|
1862
|
+
storage?.setItem(STORAGE_KEY2, JSON.stringify(config));
|
|
1863
|
+
}
|
|
1639
1864
|
function StudioDeviceSelectGroup({
|
|
1640
1865
|
devices,
|
|
1641
1866
|
selected,
|
|
@@ -1714,7 +1939,11 @@ function StudioSettingsDialog({
|
|
|
1714
1939
|
meeting,
|
|
1715
1940
|
labels,
|
|
1716
1941
|
reducedMotion,
|
|
1717
|
-
cameraOn
|
|
1942
|
+
cameraOn,
|
|
1943
|
+
captionPrefs,
|
|
1944
|
+
onCaptionPrefsChange,
|
|
1945
|
+
captionsAvailable,
|
|
1946
|
+
effectBackgrounds
|
|
1718
1947
|
}) {
|
|
1719
1948
|
const devices = useMediaDevices(open ? 1 : 0, open);
|
|
1720
1949
|
const [saved, setSaved] = react.useState(
|
|
@@ -1726,11 +1955,16 @@ function StudioSettingsDialog({
|
|
|
1726
1955
|
const [displayName, setDisplayName] = react.useState("");
|
|
1727
1956
|
const [savedDisplayName, setSavedDisplayName] = react.useState("");
|
|
1728
1957
|
const [savingDisplayName, setSavingDisplayName] = react.useState(false);
|
|
1958
|
+
const [tab, setTab] = react.useState("general");
|
|
1959
|
+
const [effects, setEffects] = react.useState(
|
|
1960
|
+
() => meeting.videoEffects()
|
|
1961
|
+
);
|
|
1729
1962
|
const { effective } = resolveDeviceSelection(saved, devices);
|
|
1730
1963
|
react.useEffect(() => {
|
|
1731
1964
|
if (!open) return;
|
|
1732
1965
|
setSaved(readStudioDeviceSelection());
|
|
1733
|
-
|
|
1966
|
+
setEffects(meeting.videoEffects());
|
|
1967
|
+
}, [meeting, open]);
|
|
1734
1968
|
react.useEffect(() => {
|
|
1735
1969
|
if (!open) return;
|
|
1736
1970
|
const selfName = meeting.participants().find((row) => row.isSelf)?.name?.trim() ?? "";
|
|
@@ -1783,76 +2017,306 @@ function StudioSettingsDialog({
|
|
|
1783
2017
|
setTestingSpeaker(false);
|
|
1784
2018
|
}
|
|
1785
2019
|
}
|
|
1786
|
-
|
|
2020
|
+
async function applyEffects(next) {
|
|
2021
|
+
setEffects(next);
|
|
2022
|
+
persistStudioVideoEffects(next);
|
|
2023
|
+
try {
|
|
2024
|
+
await meeting.setVideoEffects(next);
|
|
2025
|
+
} catch {
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
const tabs = [
|
|
2029
|
+
{ id: "general", label: labels.settingsGeneral },
|
|
2030
|
+
{ id: "audio", label: labels.settingsAudio },
|
|
2031
|
+
{ id: "video", label: labels.settingsVideo },
|
|
2032
|
+
{ id: "effects", label: labels.settingsEffects },
|
|
2033
|
+
{ id: "subtitles", label: labels.settingsSubtitles }
|
|
2034
|
+
];
|
|
2035
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(DialogContent, { className: "sm:max-w-2xl", children: [
|
|
1787
2036
|
/* @__PURE__ */ jsxRuntime.jsxs(DialogHeader, { children: [
|
|
1788
2037
|
/* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: labels.settingsTitle }),
|
|
1789
2038
|
/* @__PURE__ */ jsxRuntime.jsx(DialogDescription, { children: labels.settingsDescription })
|
|
1790
2039
|
] }),
|
|
1791
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "
|
|
1792
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1793
|
-
|
|
2040
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 sm:flex-row", children: [
|
|
2041
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2042
|
+
"div",
|
|
2043
|
+
{
|
|
2044
|
+
role: "tablist",
|
|
2045
|
+
"aria-label": labels.settings,
|
|
2046
|
+
className: "flex shrink-0 flex-row gap-1 overflow-x-auto sm:w-40 sm:flex-col",
|
|
2047
|
+
children: tabs.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2048
|
+
"button",
|
|
2049
|
+
{
|
|
2050
|
+
type: "button",
|
|
2051
|
+
role: "tab",
|
|
2052
|
+
"aria-selected": tab === item.id,
|
|
2053
|
+
className: cn(
|
|
2054
|
+
"rounded-md px-3 py-1.5 text-left text-sm",
|
|
2055
|
+
tab === item.id ? "bg-muted font-medium" : "text-muted-foreground hover:bg-muted/60"
|
|
2056
|
+
),
|
|
2057
|
+
onClick: () => setTab(item.id),
|
|
2058
|
+
children: item.label
|
|
2059
|
+
},
|
|
2060
|
+
item.id
|
|
2061
|
+
))
|
|
2062
|
+
}
|
|
2063
|
+
),
|
|
2064
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 space-y-4", children: [
|
|
2065
|
+
tab === "general" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2066
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "studio-display-name", children: labels.guestName }),
|
|
2067
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
2068
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2069
|
+
Input,
|
|
2070
|
+
{
|
|
2071
|
+
id: "studio-display-name",
|
|
2072
|
+
value: displayName,
|
|
2073
|
+
maxLength: STUDIO_DISPLAY_NAME_MAX,
|
|
2074
|
+
onChange: (event) => setDisplayName(event.target.value)
|
|
2075
|
+
}
|
|
2076
|
+
),
|
|
2077
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2078
|
+
Button,
|
|
2079
|
+
{
|
|
2080
|
+
type: "button",
|
|
2081
|
+
variant: "outline",
|
|
2082
|
+
disabled: displayNameSaveDisabled,
|
|
2083
|
+
onClick: () => void onSaveDisplayName(),
|
|
2084
|
+
children: labels.displayNameSave
|
|
2085
|
+
}
|
|
2086
|
+
)
|
|
2087
|
+
] })
|
|
2088
|
+
] }) : null,
|
|
2089
|
+
tab === "audio" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2090
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2091
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "sr-only", children: labels.micLevel }),
|
|
2092
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2093
|
+
AudioWaveform,
|
|
2094
|
+
{
|
|
2095
|
+
track: audioTrack,
|
|
2096
|
+
reducedMotion,
|
|
2097
|
+
className: "w-full"
|
|
2098
|
+
}
|
|
2099
|
+
)
|
|
2100
|
+
] }),
|
|
2101
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2102
|
+
StudioDeviceSelectGroup,
|
|
2103
|
+
{
|
|
2104
|
+
devices: { ...devices, video: [] },
|
|
2105
|
+
selected: effective,
|
|
2106
|
+
labels,
|
|
2107
|
+
onChange: (kind, id) => void onChange(kind, id),
|
|
2108
|
+
speakerAction: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2109
|
+
Button,
|
|
2110
|
+
{
|
|
2111
|
+
type: "button",
|
|
2112
|
+
size: "sm",
|
|
2113
|
+
variant: "outline",
|
|
2114
|
+
disabled: testingSpeaker,
|
|
2115
|
+
onClick: () => void onTestSpeaker(),
|
|
2116
|
+
children: labels.testSpeaker
|
|
2117
|
+
}
|
|
2118
|
+
)
|
|
2119
|
+
}
|
|
2120
|
+
)
|
|
2121
|
+
] }) : null,
|
|
2122
|
+
tab === "video" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2123
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2124
|
+
SettingsCameraPreview,
|
|
2125
|
+
{
|
|
2126
|
+
track: videoTrack,
|
|
2127
|
+
open,
|
|
2128
|
+
cameraOn,
|
|
2129
|
+
cameraOffLabel: labels.cameraOff
|
|
2130
|
+
}
|
|
2131
|
+
),
|
|
2132
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2133
|
+
StudioDeviceSelectGroup,
|
|
2134
|
+
{
|
|
2135
|
+
devices: { ...devices, audio: [], speaker: [] },
|
|
2136
|
+
selected: effective,
|
|
2137
|
+
labels,
|
|
2138
|
+
onChange: (kind, id) => void onChange(kind, id)
|
|
2139
|
+
}
|
|
2140
|
+
)
|
|
2141
|
+
] }) : null,
|
|
2142
|
+
tab === "effects" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2143
|
+
EffectsPanel,
|
|
2144
|
+
{
|
|
2145
|
+
labels,
|
|
2146
|
+
effects,
|
|
2147
|
+
backgrounds: effectBackgrounds,
|
|
2148
|
+
onApply: (next) => void applyEffects(next)
|
|
2149
|
+
}
|
|
2150
|
+
) : null,
|
|
2151
|
+
tab === "subtitles" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2152
|
+
SubtitlesPanel,
|
|
2153
|
+
{
|
|
2154
|
+
labels,
|
|
2155
|
+
prefs: captionPrefs,
|
|
2156
|
+
available: captionsAvailable,
|
|
2157
|
+
onChange: (next) => {
|
|
2158
|
+
persistStudioCaptionPrefs(next);
|
|
2159
|
+
onCaptionPrefsChange(next);
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
) : null
|
|
2163
|
+
] })
|
|
2164
|
+
] })
|
|
2165
|
+
] }) });
|
|
2166
|
+
}
|
|
2167
|
+
function EffectsPanel({
|
|
2168
|
+
labels,
|
|
2169
|
+
effects,
|
|
2170
|
+
backgrounds,
|
|
2171
|
+
onApply
|
|
2172
|
+
}) {
|
|
2173
|
+
const filters = [
|
|
2174
|
+
{ id: "none", label: labels.effectFilterNone },
|
|
2175
|
+
{ id: "grayscale", label: labels.effectFilterGrayscale },
|
|
2176
|
+
{ id: "sepia", label: labels.effectFilterSepia },
|
|
2177
|
+
{ id: "warm", label: labels.effectFilterWarm },
|
|
2178
|
+
{ id: "cool", label: labels.effectFilterCool },
|
|
2179
|
+
{ id: "bright", label: labels.effectFilterBright }
|
|
2180
|
+
];
|
|
2181
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
2182
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2183
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-sm font-medium", children: labels.settingsEffects }),
|
|
2184
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
|
|
1794
2185
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1795
|
-
|
|
2186
|
+
EffectTile,
|
|
1796
2187
|
{
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
onChange: (event) => setDisplayName(event.target.value)
|
|
2188
|
+
label: labels.effectNone,
|
|
2189
|
+
active: effects.background === "none",
|
|
2190
|
+
onClick: () => onApply({ ...effects, background: "none" })
|
|
1801
2191
|
}
|
|
1802
2192
|
),
|
|
1803
2193
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1804
|
-
|
|
2194
|
+
EffectTile,
|
|
1805
2195
|
{
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
2196
|
+
label: labels.effectBlur,
|
|
2197
|
+
active: effects.background === "blur" && effects.blurStrength <= 50,
|
|
2198
|
+
onClick: () => onApply({ ...effects, background: "blur", blurStrength: 40 })
|
|
2199
|
+
}
|
|
2200
|
+
),
|
|
2201
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2202
|
+
EffectTile,
|
|
2203
|
+
{
|
|
2204
|
+
label: labels.effectBlurStrong,
|
|
2205
|
+
active: effects.background === "blur" && effects.blurStrength > 50,
|
|
2206
|
+
onClick: () => onApply({ ...effects, background: "blur", blurStrength: 80 })
|
|
1811
2207
|
}
|
|
1812
2208
|
)
|
|
1813
2209
|
] })
|
|
1814
2210
|
] }),
|
|
1815
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1816
|
-
|
|
1817
|
-
{
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
2211
|
+
backgrounds.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2212
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-sm font-medium", children: labels.effectBackgrounds }),
|
|
2213
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-3 gap-2", children: backgrounds.map((bg) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2214
|
+
EffectTile,
|
|
2215
|
+
{
|
|
2216
|
+
label: bg.label,
|
|
2217
|
+
active: effects.background === "image" && effects.imageUrl === bg.url,
|
|
2218
|
+
previewUrl: bg.url,
|
|
2219
|
+
onClick: () => onApply({
|
|
2220
|
+
...effects,
|
|
2221
|
+
background: "image",
|
|
2222
|
+
imageUrl: bg.url
|
|
2223
|
+
})
|
|
2224
|
+
},
|
|
2225
|
+
bg.id
|
|
2226
|
+
)) })
|
|
2227
|
+
] }) : null,
|
|
1824
2228
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1825
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "
|
|
1826
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1827
|
-
|
|
2229
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-sm font-medium", children: labels.effectFilters }),
|
|
2230
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: filters.map((filter) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2231
|
+
Button,
|
|
1828
2232
|
{
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
2233
|
+
type: "button",
|
|
2234
|
+
size: "sm",
|
|
2235
|
+
variant: effects.filter === filter.id ? "default" : "outline",
|
|
2236
|
+
onClick: () => onApply({ ...effects, filter: filter.id }),
|
|
2237
|
+
children: filter.label
|
|
2238
|
+
},
|
|
2239
|
+
filter.id
|
|
2240
|
+
)) })
|
|
2241
|
+
] })
|
|
2242
|
+
] });
|
|
2243
|
+
}
|
|
2244
|
+
function EffectTile({
|
|
2245
|
+
label,
|
|
2246
|
+
active,
|
|
2247
|
+
previewUrl,
|
|
2248
|
+
onClick
|
|
2249
|
+
}) {
|
|
2250
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2251
|
+
"button",
|
|
2252
|
+
{
|
|
2253
|
+
type: "button",
|
|
2254
|
+
"aria-pressed": active,
|
|
2255
|
+
onClick,
|
|
2256
|
+
className: cn(
|
|
2257
|
+
"overflow-hidden rounded-lg border text-left text-xs",
|
|
2258
|
+
active ? "ring-primary ring-2" : "border-border"
|
|
2259
|
+
),
|
|
2260
|
+
children: [
|
|
2261
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2262
|
+
"div",
|
|
1844
2263
|
{
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
variant: "outline",
|
|
1848
|
-
disabled: testingSpeaker,
|
|
1849
|
-
onClick: () => void onTestSpeaker(),
|
|
1850
|
-
children: labels.testSpeaker
|
|
2264
|
+
className: "bg-muted aspect-video w-full bg-cover bg-center",
|
|
2265
|
+
style: previewUrl ? { backgroundImage: `url(${previewUrl})` } : void 0
|
|
1851
2266
|
}
|
|
1852
|
-
)
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
2267
|
+
),
|
|
2268
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate px-2 py-1", children: label })
|
|
2269
|
+
]
|
|
2270
|
+
}
|
|
2271
|
+
);
|
|
2272
|
+
}
|
|
2273
|
+
function SubtitlesPanel({
|
|
2274
|
+
labels,
|
|
2275
|
+
prefs,
|
|
2276
|
+
available,
|
|
2277
|
+
onChange
|
|
2278
|
+
}) {
|
|
2279
|
+
const localeLabel = {
|
|
2280
|
+
en: labels.localeEn,
|
|
2281
|
+
es: labels.localeEs,
|
|
2282
|
+
pt: labels.localePt
|
|
2283
|
+
};
|
|
2284
|
+
const sizes = [
|
|
2285
|
+
{ id: "s", label: labels.captionsSizeS },
|
|
2286
|
+
{ id: "m", label: labels.captionsSizeM },
|
|
2287
|
+
{ id: "l", label: labels.captionsSizeL }
|
|
2288
|
+
];
|
|
2289
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
2290
|
+
available ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-sm", children: labels.captionsDelayed }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-sm", children: labels.captionsNotReady }),
|
|
2291
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2292
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: labels.captionsLanguage }),
|
|
2293
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: STUDIO_CAPTION_LOCALES.map((locale) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2294
|
+
Button,
|
|
2295
|
+
{
|
|
2296
|
+
type: "button",
|
|
2297
|
+
size: "sm",
|
|
2298
|
+
variant: prefs.locale === locale ? "default" : "outline",
|
|
2299
|
+
onClick: () => onChange({ ...prefs, locale }),
|
|
2300
|
+
children: localeLabel[locale]
|
|
2301
|
+
},
|
|
2302
|
+
locale
|
|
2303
|
+
)) })
|
|
2304
|
+
] }),
|
|
2305
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2306
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: labels.captionsSize }),
|
|
2307
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: sizes.map((size) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2308
|
+
Button,
|
|
2309
|
+
{
|
|
2310
|
+
type: "button",
|
|
2311
|
+
size: "sm",
|
|
2312
|
+
variant: prefs.size === size.id ? "default" : "outline",
|
|
2313
|
+
onClick: () => onChange({ ...prefs, size: size.id }),
|
|
2314
|
+
children: size.label
|
|
2315
|
+
},
|
|
2316
|
+
size.id
|
|
2317
|
+
)) })
|
|
2318
|
+
] })
|
|
2319
|
+
] });
|
|
1856
2320
|
}
|
|
1857
2321
|
function SettingsCameraPreview({
|
|
1858
2322
|
track,
|
|
@@ -1903,7 +2367,11 @@ function StudioGuestWaiting({
|
|
|
1903
2367
|
onToggleCamera,
|
|
1904
2368
|
onToggleMute,
|
|
1905
2369
|
onOpenSettings,
|
|
1906
|
-
onCloseSettings
|
|
2370
|
+
onCloseSettings,
|
|
2371
|
+
captionPrefs,
|
|
2372
|
+
onCaptionPrefsChange,
|
|
2373
|
+
captionsAvailable,
|
|
2374
|
+
effectBackgrounds
|
|
1907
2375
|
}) {
|
|
1908
2376
|
const [audioTrack, setAudioTrack] = react.useState(null);
|
|
1909
2377
|
const [videoTrack, setVideoTrack] = react.useState(null);
|
|
@@ -1970,7 +2438,11 @@ function StudioGuestWaiting({
|
|
|
1970
2438
|
meeting,
|
|
1971
2439
|
labels,
|
|
1972
2440
|
reducedMotion,
|
|
1973
|
-
cameraOn
|
|
2441
|
+
cameraOn,
|
|
2442
|
+
captionPrefs,
|
|
2443
|
+
onCaptionPrefsChange,
|
|
2444
|
+
captionsAvailable,
|
|
2445
|
+
effectBackgrounds
|
|
1974
2446
|
}
|
|
1975
2447
|
)
|
|
1976
2448
|
] });
|
|
@@ -2047,6 +2519,19 @@ var badgeVariants = classVarianceAuthority.cva(
|
|
|
2047
2519
|
function Badge({ className, variant, ...props }) {
|
|
2048
2520
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
2049
2521
|
}
|
|
2522
|
+
|
|
2523
|
+
// src/studio/lib/studio-video-style.ts
|
|
2524
|
+
var STUDIO_VIDEO_MEDIA_STYLE = `
|
|
2525
|
+
.livo-studio-tile-video::-webkit-media-controls-start-playback-button,
|
|
2526
|
+
.livo-studio-inline-video::-webkit-media-controls-start-playback-button {
|
|
2527
|
+
display: none !important;
|
|
2528
|
+
-webkit-appearance: none;
|
|
2529
|
+
}
|
|
2530
|
+
.livo-studio-tile-video::-webkit-media-controls,
|
|
2531
|
+
.livo-studio-inline-video::-webkit-media-controls {
|
|
2532
|
+
display: none !important;
|
|
2533
|
+
}
|
|
2534
|
+
`;
|
|
2050
2535
|
function StudioPipPanel({
|
|
2051
2536
|
meeting,
|
|
2052
2537
|
labels,
|
|
@@ -2101,7 +2586,7 @@ function StudioPipPanel({
|
|
|
2101
2586
|
/* @__PURE__ */ jsxRuntime.jsx(StudioLiveBadge, { children: labels.live }),
|
|
2102
2587
|
elapsed ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground tabular-nums text-xs", children: elapsed }) : null
|
|
2103
2588
|
] }) : null,
|
|
2104
|
-
selfView ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2589
|
+
selfView && meeting.localVideoTrack() ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2105
2590
|
"div",
|
|
2106
2591
|
{
|
|
2107
2592
|
"data-testid": "pip-self-view",
|
|
@@ -2158,7 +2643,8 @@ function PipSpotlight({
|
|
|
2158
2643
|
spotlight,
|
|
2159
2644
|
cameraOffLabel
|
|
2160
2645
|
}) {
|
|
2161
|
-
|
|
2646
|
+
const track = spotlight && spotlight.kind !== "idle" ? spotlight.kind === "screen" ? meeting.participantScreenTrack(spotlight.participantId) : meeting.participantVideoTrack(spotlight.participantId) : null;
|
|
2647
|
+
if (!spotlight || spotlight.kind === "idle" || !track) {
|
|
2162
2648
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-2", children: [
|
|
2163
2649
|
spotlight?.picture ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2164
2650
|
"img",
|
|
@@ -2171,7 +2657,6 @@ function PipSpotlight({
|
|
|
2171
2657
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-xs", children: spotlight?.name ?? cameraOffLabel })
|
|
2172
2658
|
] });
|
|
2173
2659
|
}
|
|
2174
|
-
const track = spotlight.kind === "screen" ? meeting.participantScreenTrack(spotlight.participantId) : meeting.participantVideoTrack(spotlight.participantId);
|
|
2175
2660
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2176
2661
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2177
2662
|
PipTrackVideo,
|
|
@@ -2190,11 +2675,7 @@ function PipTrackVideo({
|
|
|
2190
2675
|
const ref = react.useRef(null);
|
|
2191
2676
|
react.useEffect(() => {
|
|
2192
2677
|
const el = ref.current;
|
|
2193
|
-
if (!el) return;
|
|
2194
|
-
if (!track) {
|
|
2195
|
-
el.srcObject = null;
|
|
2196
|
-
return;
|
|
2197
|
-
}
|
|
2678
|
+
if (!el || !track) return;
|
|
2198
2679
|
el.setAttribute("webkit-playsinline", "");
|
|
2199
2680
|
el.srcObject = new MediaStream([track]);
|
|
2200
2681
|
void el.play().catch(() => void 0);
|
|
@@ -2202,19 +2683,24 @@ function PipTrackVideo({
|
|
|
2202
2683
|
el.srcObject = null;
|
|
2203
2684
|
};
|
|
2204
2685
|
}, [track]);
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
{
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2686
|
+
if (!track) return null;
|
|
2687
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2688
|
+
/* @__PURE__ */ jsxRuntime.jsx("style", { children: STUDIO_VIDEO_MEDIA_STYLE }),
|
|
2689
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2690
|
+
"video",
|
|
2691
|
+
{
|
|
2692
|
+
ref,
|
|
2693
|
+
autoPlay: true,
|
|
2694
|
+
playsInline: true,
|
|
2695
|
+
muted: true,
|
|
2696
|
+
disableRemotePlayback: true,
|
|
2697
|
+
className: cn(
|
|
2698
|
+
"livo-studio-inline-video absolute inset-0 size-full",
|
|
2699
|
+
fit === "contain" ? "object-contain" : "object-cover"
|
|
2700
|
+
)
|
|
2701
|
+
}
|
|
2702
|
+
)
|
|
2703
|
+
] });
|
|
2218
2704
|
}
|
|
2219
2705
|
function pipScreenControls({
|
|
2220
2706
|
labels,
|
|
@@ -3241,6 +3727,7 @@ function StudioTile({
|
|
|
3241
3727
|
const onStage = isOnStage(tile.stageStatus);
|
|
3242
3728
|
const [confirm, setConfirm] = react.useState(null);
|
|
3243
3729
|
const showMenu = (canModerate || canManageStage) && !tile.isSelf;
|
|
3730
|
+
const hasVideoTrack = tileHasVideoTrack(meeting, tile);
|
|
3244
3731
|
const glowRef = useSpeakingGlow({
|
|
3245
3732
|
track: tile.kind === "screen" || !tile.audioEnabled ? null : meeting.participantAudioTrack(tile.id),
|
|
3246
3733
|
enabled: onStage,
|
|
@@ -3256,23 +3743,14 @@ function StudioTile({
|
|
|
3256
3743
|
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"
|
|
3257
3744
|
),
|
|
3258
3745
|
children: [
|
|
3259
|
-
tile.kind === "idle" ? /* @__PURE__ */ jsxRuntime.
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-background flex size-16 items-center justify-center rounded-full text-xl", children: tile.name.slice(0, 1).toUpperCase() }),
|
|
3268
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3269
|
-
AudioWaveform,
|
|
3270
|
-
{
|
|
3271
|
-
track: tile.isSelf ? meeting.localAudioTrack() : null,
|
|
3272
|
-
reducedMotion
|
|
3273
|
-
}
|
|
3274
|
-
)
|
|
3275
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
3746
|
+
tile.kind === "idle" || !hasVideoTrack ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3747
|
+
IdlePersonTile,
|
|
3748
|
+
{
|
|
3749
|
+
tile,
|
|
3750
|
+
meeting,
|
|
3751
|
+
reducedMotion
|
|
3752
|
+
}
|
|
3753
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
3276
3754
|
StudioTileVideo,
|
|
3277
3755
|
{
|
|
3278
3756
|
meeting,
|
|
@@ -3371,6 +3849,37 @@ function TileStatusIcons({
|
|
|
3371
3849
|
tile.chatCanSend === false ? /* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { label: labels.muteChat, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquareOff, {}) }) : null
|
|
3372
3850
|
] });
|
|
3373
3851
|
}
|
|
3852
|
+
function tileHasVideoTrack(meeting, tile) {
|
|
3853
|
+
if (tile.kind === "screen") {
|
|
3854
|
+
return meeting.participantScreenTrack(tile.id) != null;
|
|
3855
|
+
}
|
|
3856
|
+
if (tile.kind === "idle") return false;
|
|
3857
|
+
if (tile.isSelf) return meeting.localVideoTrack() != null;
|
|
3858
|
+
return meeting.participantVideoTrack(tile.id) != null;
|
|
3859
|
+
}
|
|
3860
|
+
function IdlePersonTile({
|
|
3861
|
+
tile,
|
|
3862
|
+
meeting,
|
|
3863
|
+
reducedMotion
|
|
3864
|
+
}) {
|
|
3865
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-3", children: [
|
|
3866
|
+
tile.picture ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3867
|
+
"img",
|
|
3868
|
+
{
|
|
3869
|
+
src: tile.picture,
|
|
3870
|
+
alt: "",
|
|
3871
|
+
className: "size-16 rounded-full object-cover"
|
|
3872
|
+
}
|
|
3873
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-background flex size-16 items-center justify-center rounded-full text-xl", children: tile.name.slice(0, 1).toUpperCase() }),
|
|
3874
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3875
|
+
AudioWaveform,
|
|
3876
|
+
{
|
|
3877
|
+
track: tile.isSelf ? meeting.localAudioTrack() : null,
|
|
3878
|
+
reducedMotion
|
|
3879
|
+
}
|
|
3880
|
+
)
|
|
3881
|
+
] });
|
|
3882
|
+
}
|
|
3374
3883
|
function StatusIcon({
|
|
3375
3884
|
label,
|
|
3376
3885
|
children
|
|
@@ -3394,23 +3903,27 @@ function StudioTileVideo({
|
|
|
3394
3903
|
meeting.attachVideo(participantId, el, kind);
|
|
3395
3904
|
return () => meeting.attachVideo(participantId, null, kind);
|
|
3396
3905
|
}, [kind, meeting, participantId]);
|
|
3397
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
3398
|
-
"
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
"
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3906
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3907
|
+
/* @__PURE__ */ jsxRuntime.jsx("style", { children: STUDIO_VIDEO_MEDIA_STYLE }),
|
|
3908
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3909
|
+
"video",
|
|
3910
|
+
{
|
|
3911
|
+
ref,
|
|
3912
|
+
autoPlay: true,
|
|
3913
|
+
playsInline: true,
|
|
3914
|
+
muted: true,
|
|
3915
|
+
disablePictureInPicture: true,
|
|
3916
|
+
disableRemotePlayback: true,
|
|
3917
|
+
controlsList: "nodownload noplaybackrate noremoteplayback",
|
|
3918
|
+
className: cn(
|
|
3919
|
+
"livo-studio-tile-video absolute inset-0 size-full",
|
|
3920
|
+
"pointer-events-none",
|
|
3921
|
+
contain ? "object-contain" : "object-cover",
|
|
3922
|
+
mirror && "-scale-x-100"
|
|
3923
|
+
)
|
|
3924
|
+
}
|
|
3925
|
+
)
|
|
3926
|
+
] });
|
|
3414
3927
|
}
|
|
3415
3928
|
function StudioStage({
|
|
3416
3929
|
participants,
|
|
@@ -3502,6 +4015,32 @@ function StudioStage({
|
|
|
3502
4015
|
}
|
|
3503
4016
|
);
|
|
3504
4017
|
}
|
|
4018
|
+
function StudioDownloadAppDialog({
|
|
4019
|
+
open,
|
|
4020
|
+
onOpenChange,
|
|
4021
|
+
downloadUrl,
|
|
4022
|
+
labels
|
|
4023
|
+
}) {
|
|
4024
|
+
const href = downloadUrl?.trim() ?? "";
|
|
4025
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(DialogContent, { children: [
|
|
4026
|
+
/* @__PURE__ */ jsxRuntime.jsxs(DialogHeader, { children: [
|
|
4027
|
+
/* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: labels.screenDownloadAppTitle }),
|
|
4028
|
+
/* @__PURE__ */ jsxRuntime.jsx(DialogDescription, { children: labels.screenUnsupportedDevice })
|
|
4029
|
+
] }),
|
|
4030
|
+
/* @__PURE__ */ jsxRuntime.jsxs(DialogFooter, { children: [
|
|
4031
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4032
|
+
Button,
|
|
4033
|
+
{
|
|
4034
|
+
type: "button",
|
|
4035
|
+
variant: "outline",
|
|
4036
|
+
onClick: () => onOpenChange(false),
|
|
4037
|
+
children: labels.screenDownloadAppDismiss
|
|
4038
|
+
}
|
|
4039
|
+
),
|
|
4040
|
+
href ? /* @__PURE__ */ jsxRuntime.jsx(Button, { render: /* @__PURE__ */ jsxRuntime.jsx("a", { href, target: "_blank", rel: "noreferrer" }), children: labels.screenDownloadAppAction }) : /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", disabled: true, children: labels.screenDownloadAppAction })
|
|
4041
|
+
] })
|
|
4042
|
+
] }) });
|
|
4043
|
+
}
|
|
3505
4044
|
function StudioToolbar({
|
|
3506
4045
|
session,
|
|
3507
4046
|
meeting,
|
|
@@ -3528,6 +4067,12 @@ function StudioToolbar({
|
|
|
3528
4067
|
stageEnabled,
|
|
3529
4068
|
canLeaveStage,
|
|
3530
4069
|
reducedMotion,
|
|
4070
|
+
captionsOn,
|
|
4071
|
+
captionsAvailable,
|
|
4072
|
+
captionsReady,
|
|
4073
|
+
captionPrefs,
|
|
4074
|
+
onCaptionPrefsChange,
|
|
4075
|
+
effectBackgrounds,
|
|
3531
4076
|
onToggleMute,
|
|
3532
4077
|
onToggleCamera,
|
|
3533
4078
|
onToggleScreen,
|
|
@@ -3541,204 +4086,360 @@ function StudioToolbar({
|
|
|
3541
4086
|
onCloseSettings,
|
|
3542
4087
|
onOpenPip,
|
|
3543
4088
|
onClosePip,
|
|
4089
|
+
onToggleCaptions,
|
|
3544
4090
|
onLeave
|
|
3545
4091
|
}) {
|
|
3546
|
-
const
|
|
4092
|
+
const devices = useMediaDevices(1, true);
|
|
4093
|
+
const saved = readStudioDeviceSelection();
|
|
4094
|
+
const { effective } = resolveDeviceSelection(saved, devices);
|
|
4095
|
+
const fullscreenSupported = typeof document !== "undefined" && document.fullscreenEnabled === true && typeof document.documentElement.requestFullscreen === "function";
|
|
3547
4096
|
async function enterFullscreen() {
|
|
3548
4097
|
try {
|
|
3549
4098
|
await document.documentElement.requestFullscreen();
|
|
3550
4099
|
} catch {
|
|
3551
4100
|
}
|
|
3552
4101
|
}
|
|
3553
|
-
|
|
3554
|
-
|
|
4102
|
+
async function onDeviceChange(kind, deviceId) {
|
|
4103
|
+
persistStudioDeviceSelection(kind, deviceId);
|
|
4104
|
+
try {
|
|
4105
|
+
await meeting.setDevice(kind, deviceId);
|
|
4106
|
+
} catch {
|
|
4107
|
+
}
|
|
4108
|
+
}
|
|
4109
|
+
const showStageModerator = session.role === "moderator" && canManageStage;
|
|
4110
|
+
const showStageGuest = session.role === "guest" && stageEnabled;
|
|
4111
|
+
const captionsLabel = !captionsAvailable ? labels.captions : !captionsReady ? labels.captionsNotReady : captionsOn ? labels.captionsOn : labels.captionsOff;
|
|
4112
|
+
return /* @__PURE__ */ jsxRuntime.jsx("footer", { className: "flex flex-wrap items-center justify-center gap-3 border-t px-3 py-3 sm:justify-between", children: /* @__PURE__ */ jsxRuntime.jsxs(TooltipProvider, { children: [
|
|
4113
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
|
|
4114
|
+
showMediaControls ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4115
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4116
|
+
MediaSplit,
|
|
4117
|
+
{
|
|
4118
|
+
label: muted ? labels.unmute : labels.mute,
|
|
4119
|
+
pressed: muted,
|
|
4120
|
+
tone: muted ? "danger" : "default",
|
|
4121
|
+
onClick: onToggleMute,
|
|
4122
|
+
pickerLabel: labels.micDevice,
|
|
4123
|
+
options: devices.audio,
|
|
4124
|
+
selected: effective.audio,
|
|
4125
|
+
onSelect: (id) => void onDeviceChange("audio", id),
|
|
4126
|
+
children: muted ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MicOff, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, {})
|
|
4127
|
+
}
|
|
4128
|
+
),
|
|
4129
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4130
|
+
MediaSplit,
|
|
4131
|
+
{
|
|
4132
|
+
label: labels.camera,
|
|
4133
|
+
pressed: cameraOn,
|
|
4134
|
+
onClick: onToggleCamera,
|
|
4135
|
+
pickerLabel: labels.cameraDevice,
|
|
4136
|
+
options: devices.video,
|
|
4137
|
+
selected: effective.video,
|
|
4138
|
+
onSelect: (id) => void onDeviceChange("video", id),
|
|
4139
|
+
children: cameraOn ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.VideoOff, {})
|
|
4140
|
+
}
|
|
4141
|
+
)
|
|
4142
|
+
] }) : null,
|
|
4143
|
+
showStageModerator ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4144
|
+
ControlIcon,
|
|
4145
|
+
{
|
|
4146
|
+
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : labels.goOnAir,
|
|
4147
|
+
pressed: selfStageStatus !== "ON_STAGE",
|
|
4148
|
+
disabled: selfStageStatus === "ON_STAGE" && !canLeaveStage,
|
|
4149
|
+
onClick: () => {
|
|
4150
|
+
if (selfStageStatus === "ON_STAGE") {
|
|
4151
|
+
if (!canLeaveStage) {
|
|
4152
|
+
sonner.toast.message(labels.offAirLastVisible);
|
|
4153
|
+
return;
|
|
4154
|
+
}
|
|
4155
|
+
onLeaveStage();
|
|
4156
|
+
return;
|
|
4157
|
+
}
|
|
4158
|
+
onAskStage();
|
|
4159
|
+
},
|
|
4160
|
+
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeOff, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, {})
|
|
4161
|
+
}
|
|
4162
|
+
) : null,
|
|
4163
|
+
showStageGuest ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4164
|
+
ControlIcon,
|
|
4165
|
+
{
|
|
4166
|
+
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
|
|
4167
|
+
pressed: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
|
|
4168
|
+
disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" || selfStageStatus === "ON_STAGE" && !canLeaveStage,
|
|
4169
|
+
onClick: () => {
|
|
4170
|
+
if (selfStageStatus === "ON_STAGE") {
|
|
4171
|
+
if (!canLeaveStage) {
|
|
4172
|
+
sonner.toast.message(labels.offAirLastVisible);
|
|
4173
|
+
return;
|
|
4174
|
+
}
|
|
4175
|
+
onLeaveStage();
|
|
4176
|
+
return;
|
|
4177
|
+
}
|
|
4178
|
+
onAskStage();
|
|
4179
|
+
},
|
|
4180
|
+
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeOff, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, {})
|
|
4181
|
+
}
|
|
4182
|
+
) : null,
|
|
4183
|
+
showScreenControl ? canShareFreely ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4184
|
+
ControlIcon,
|
|
4185
|
+
{
|
|
4186
|
+
label: labels.screen,
|
|
4187
|
+
pressed: screenOn,
|
|
4188
|
+
disabled: screenPending || !screenAvailable && !screenOn,
|
|
4189
|
+
pending: screenPending,
|
|
4190
|
+
onClick: onToggleScreen,
|
|
4191
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MonitorUp, {})
|
|
4192
|
+
}
|
|
4193
|
+
) : shareGrant === "requested" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4194
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4195
|
+
ControlIcon,
|
|
4196
|
+
{
|
|
4197
|
+
label: labels.shareRequested,
|
|
4198
|
+
pressed: true,
|
|
4199
|
+
disabled: true,
|
|
4200
|
+
onClick: () => void 0,
|
|
4201
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MonitorUp, {})
|
|
4202
|
+
}
|
|
4203
|
+
),
|
|
4204
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4205
|
+
ControlIcon,
|
|
4206
|
+
{
|
|
4207
|
+
label: labels.cancelShareRequest,
|
|
4208
|
+
onClick: onCancelShareRequest,
|
|
4209
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, {})
|
|
4210
|
+
}
|
|
4211
|
+
)
|
|
4212
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(ControlIcon, { label: labels.askToShare, onClick: onAskToShare, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MonitorUp, {}) }) : null,
|
|
4213
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4214
|
+
ControlIcon,
|
|
4215
|
+
{
|
|
4216
|
+
label: captionsLabel,
|
|
4217
|
+
pressed: captionsOn,
|
|
4218
|
+
disabled: !captionsAvailable,
|
|
4219
|
+
onClick: onToggleCaptions,
|
|
4220
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Captions, {})
|
|
4221
|
+
}
|
|
4222
|
+
),
|
|
4223
|
+
/* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, { children: [
|
|
4224
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4225
|
+
DropdownMenuTrigger,
|
|
4226
|
+
{
|
|
4227
|
+
render: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4228
|
+
Button,
|
|
4229
|
+
{
|
|
4230
|
+
type: "button",
|
|
4231
|
+
size: "icon-lg",
|
|
4232
|
+
variant: "secondary",
|
|
4233
|
+
"aria-label": labels.more
|
|
4234
|
+
}
|
|
4235
|
+
),
|
|
4236
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreVertical, {})
|
|
4237
|
+
}
|
|
4238
|
+
),
|
|
4239
|
+
/* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuContent, { align: "center", className: "min-w-44", children: [
|
|
4240
|
+
fullscreenSupported ? /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuItem, { onClick: () => void enterFullscreen(), children: [
|
|
4241
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize, {}),
|
|
4242
|
+
labels.fullscreen
|
|
4243
|
+
] }) : null,
|
|
4244
|
+
pipSupported ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4245
|
+
DropdownMenuItem,
|
|
4246
|
+
{
|
|
4247
|
+
onClick: () => {
|
|
4248
|
+
if (pipOpen) onClosePip();
|
|
4249
|
+
else onOpenPip();
|
|
4250
|
+
},
|
|
4251
|
+
children: [
|
|
4252
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.PictureInPicture2, {}),
|
|
4253
|
+
pipOpen ? labels.closePip : labels.openPip
|
|
4254
|
+
]
|
|
4255
|
+
}
|
|
4256
|
+
) : null,
|
|
4257
|
+
/* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuItem, { onClick: onOpenSettings, children: [
|
|
4258
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Settings, {}),
|
|
4259
|
+
labels.settings
|
|
4260
|
+
] })
|
|
4261
|
+
] })
|
|
4262
|
+
] })
|
|
4263
|
+
] }),
|
|
4264
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
|
|
3555
4265
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3556
4266
|
ControlIcon,
|
|
3557
4267
|
{
|
|
3558
|
-
label:
|
|
3559
|
-
pressed:
|
|
3560
|
-
|
|
3561
|
-
onClick:
|
|
3562
|
-
children:
|
|
4268
|
+
label: unreadChat > 0 ? `${labels.chat} (${unreadChat})` : labels.chat,
|
|
4269
|
+
pressed: chatOpen,
|
|
4270
|
+
badge: unreadChat,
|
|
4271
|
+
onClick: onToggleChat,
|
|
4272
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, {})
|
|
3563
4273
|
}
|
|
3564
4274
|
),
|
|
3565
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4275
|
+
session.role === "moderator" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3566
4276
|
ControlIcon,
|
|
3567
4277
|
{
|
|
3568
|
-
label: labels.
|
|
3569
|
-
pressed:
|
|
3570
|
-
|
|
3571
|
-
|
|
4278
|
+
label: peopleBadge > 0 ? `${labels.people} (${peopleBadge})` : labels.people,
|
|
4279
|
+
pressed: peopleOpen,
|
|
4280
|
+
badge: peopleBadge,
|
|
4281
|
+
onClick: onTogglePeople,
|
|
4282
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Users, {})
|
|
3572
4283
|
}
|
|
3573
|
-
)
|
|
3574
|
-
] }) : null,
|
|
3575
|
-
session.role === "moderator" && canManageStage ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3576
|
-
ControlIcon,
|
|
3577
|
-
{
|
|
3578
|
-
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : labels.goOnAir,
|
|
3579
|
-
pressed: selfStageStatus !== "ON_STAGE",
|
|
3580
|
-
disabled: selfStageStatus === "ON_STAGE" && !canLeaveStage,
|
|
3581
|
-
onClick: () => {
|
|
3582
|
-
if (selfStageStatus === "ON_STAGE") {
|
|
3583
|
-
if (!canLeaveStage) {
|
|
3584
|
-
sonner.toast.message(labels.offAirLastVisible);
|
|
3585
|
-
return;
|
|
3586
|
-
}
|
|
3587
|
-
onLeaveStage();
|
|
3588
|
-
return;
|
|
3589
|
-
}
|
|
3590
|
-
onAskStage();
|
|
3591
|
-
},
|
|
3592
|
-
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeOff, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, {})
|
|
3593
|
-
}
|
|
3594
|
-
) : null,
|
|
3595
|
-
session.role === "guest" && stageEnabled ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3596
|
-
ControlIcon,
|
|
3597
|
-
{
|
|
3598
|
-
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
|
|
3599
|
-
pressed: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
|
|
3600
|
-
disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" || selfStageStatus === "ON_STAGE" && !canLeaveStage,
|
|
3601
|
-
onClick: () => {
|
|
3602
|
-
if (selfStageStatus === "ON_STAGE") {
|
|
3603
|
-
if (!canLeaveStage) {
|
|
3604
|
-
sonner.toast.message(labels.offAirLastVisible);
|
|
3605
|
-
return;
|
|
3606
|
-
}
|
|
3607
|
-
onLeaveStage();
|
|
3608
|
-
return;
|
|
3609
|
-
}
|
|
3610
|
-
onAskStage();
|
|
3611
|
-
},
|
|
3612
|
-
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeOff, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, {})
|
|
3613
|
-
}
|
|
3614
|
-
) : null,
|
|
3615
|
-
showScreenControl ? canShareFreely ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3616
|
-
ControlIcon,
|
|
3617
|
-
{
|
|
3618
|
-
label: labels.screen,
|
|
3619
|
-
pressed: screenOn,
|
|
3620
|
-
disabled: screenPending || !screenAvailable && !screenOn,
|
|
3621
|
-
pending: screenPending,
|
|
3622
|
-
onClick: onToggleScreen,
|
|
3623
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MonitorUp, {})
|
|
3624
|
-
}
|
|
3625
|
-
) : shareGrant === "requested" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4284
|
+
) : null,
|
|
3626
4285
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3627
4286
|
ControlIcon,
|
|
3628
4287
|
{
|
|
3629
|
-
label: labels.
|
|
3630
|
-
pressed:
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MonitorUp, {})
|
|
4288
|
+
label: labels.settings,
|
|
4289
|
+
pressed: settingsOpen,
|
|
4290
|
+
onClick: onOpenSettings,
|
|
4291
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Settings, {})
|
|
3634
4292
|
}
|
|
3635
4293
|
),
|
|
3636
4294
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3637
|
-
|
|
4295
|
+
StudioSettingsDialog,
|
|
3638
4296
|
{
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
4297
|
+
open: settingsOpen,
|
|
4298
|
+
onOpenChange: onCloseSettings,
|
|
4299
|
+
meeting,
|
|
4300
|
+
reducedMotion,
|
|
4301
|
+
cameraOn,
|
|
4302
|
+
labels,
|
|
4303
|
+
captionPrefs,
|
|
4304
|
+
onCaptionPrefsChange,
|
|
4305
|
+
captionsAvailable,
|
|
4306
|
+
effectBackgrounds
|
|
3642
4307
|
}
|
|
3643
|
-
)
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
},
|
|
3682
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PictureInPicture2, {})
|
|
3683
|
-
}
|
|
3684
|
-
) : null,
|
|
4308
|
+
),
|
|
4309
|
+
session.role === "guest" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4310
|
+
ConfirmAction,
|
|
4311
|
+
{
|
|
4312
|
+
triggerLabel: labels.leave,
|
|
4313
|
+
title: labels.leaveTitle,
|
|
4314
|
+
description: labels.leaveDescription,
|
|
4315
|
+
confirmLabel: labels.leaveConfirm,
|
|
4316
|
+
pending,
|
|
4317
|
+
onConfirm: onLeave,
|
|
4318
|
+
trigger: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4319
|
+
Button,
|
|
4320
|
+
{
|
|
4321
|
+
type: "button",
|
|
4322
|
+
variant: "destructive",
|
|
4323
|
+
size: "icon-lg",
|
|
4324
|
+
disabled: pending,
|
|
4325
|
+
"aria-label": labels.leave,
|
|
4326
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PhoneOff, {})
|
|
4327
|
+
}
|
|
4328
|
+
)
|
|
4329
|
+
}
|
|
4330
|
+
) : null
|
|
4331
|
+
] })
|
|
4332
|
+
] }) });
|
|
4333
|
+
}
|
|
4334
|
+
function MediaSplit({
|
|
4335
|
+
label,
|
|
4336
|
+
pressed,
|
|
4337
|
+
tone = "default",
|
|
4338
|
+
onClick,
|
|
4339
|
+
pickerLabel,
|
|
4340
|
+
options,
|
|
4341
|
+
selected,
|
|
4342
|
+
onSelect,
|
|
4343
|
+
children
|
|
4344
|
+
}) {
|
|
4345
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center", children: [
|
|
3685
4346
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3686
4347
|
ControlIcon,
|
|
3687
4348
|
{
|
|
3688
|
-
label
|
|
3689
|
-
pressed
|
|
3690
|
-
|
|
3691
|
-
|
|
4349
|
+
label,
|
|
4350
|
+
pressed,
|
|
4351
|
+
tone,
|
|
4352
|
+
onClick,
|
|
4353
|
+
children
|
|
3692
4354
|
}
|
|
3693
4355
|
),
|
|
3694
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
cameraDevice: labels.cameraDevice,
|
|
3710
|
-
micDevice: labels.micDevice,
|
|
3711
|
-
speakerDevice: labels.speakerDevice,
|
|
3712
|
-
testSpeaker: labels.testSpeaker,
|
|
3713
|
-
testSpeakerError: labels.testSpeakerError,
|
|
3714
|
-
cameraOff: labels.cameraOff,
|
|
3715
|
-
micLevel: labels.micLevel
|
|
4356
|
+
options.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, { children: [
|
|
4357
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4358
|
+
DropdownMenuTrigger,
|
|
4359
|
+
{
|
|
4360
|
+
render: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4361
|
+
Button,
|
|
4362
|
+
{
|
|
4363
|
+
type: "button",
|
|
4364
|
+
size: "icon-xs",
|
|
4365
|
+
variant: "secondary",
|
|
4366
|
+
className: "-ml-1",
|
|
4367
|
+
"aria-label": pickerLabel
|
|
4368
|
+
}
|
|
4369
|
+
),
|
|
4370
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "size-3.5" })
|
|
3716
4371
|
}
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
4372
|
+
),
|
|
4373
|
+
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuContent, { align: "start", side: "top", className: "min-w-52", children: options.map((option) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4374
|
+
DropdownMenuItem,
|
|
4375
|
+
{
|
|
4376
|
+
onClick: () => onSelect(option.deviceId),
|
|
4377
|
+
children: [
|
|
4378
|
+
option.deviceId === selected ? "\u2713 " : "",
|
|
4379
|
+
option.label
|
|
4380
|
+
]
|
|
4381
|
+
},
|
|
4382
|
+
option.deviceId
|
|
4383
|
+
)) })
|
|
4384
|
+
] }) : null
|
|
4385
|
+
] });
|
|
4386
|
+
}
|
|
4387
|
+
var POLL_MS = 2e3;
|
|
4388
|
+
var PLAYOUT_MS = 250;
|
|
4389
|
+
var STUDIO_EXTRA_LAG_MS = 200;
|
|
4390
|
+
function useStudioCaptions({
|
|
4391
|
+
enabled,
|
|
4392
|
+
baseUrl,
|
|
4393
|
+
locale,
|
|
4394
|
+
streamStatus
|
|
4395
|
+
}) {
|
|
4396
|
+
const [playout, setPlayout] = react.useState(
|
|
4397
|
+
video.EMPTY_LIVE_CAPTION_PLAYOUT
|
|
4398
|
+
);
|
|
4399
|
+
const cuesRef = react.useRef([]);
|
|
4400
|
+
react.useEffect(() => {
|
|
4401
|
+
if (!enabled) {
|
|
4402
|
+
cuesRef.current = [];
|
|
4403
|
+
setPlayout(video.EMPTY_LIVE_CAPTION_PLAYOUT);
|
|
4404
|
+
return;
|
|
4405
|
+
}
|
|
4406
|
+
if (!baseUrl || !isStudioActiveStatus(streamStatus)) return;
|
|
4407
|
+
let cancelled = false;
|
|
4408
|
+
const url = video.liveCaptionsUrl(baseUrl, locale);
|
|
4409
|
+
const poll = async () => {
|
|
4410
|
+
const loaded = await video.loadLiveCaptions(url);
|
|
4411
|
+
if (cancelled || !loaded.ok) return;
|
|
4412
|
+
cuesRef.current = video.mergeLiveCaptionCues(cuesRef.current, loaded.cues);
|
|
4413
|
+
};
|
|
4414
|
+
void poll();
|
|
4415
|
+
const timer = window.setInterval(() => void poll(), POLL_MS);
|
|
4416
|
+
return () => {
|
|
4417
|
+
cancelled = true;
|
|
4418
|
+
window.clearInterval(timer);
|
|
4419
|
+
};
|
|
4420
|
+
}, [baseUrl, enabled, locale, streamStatus]);
|
|
4421
|
+
react.useEffect(() => {
|
|
4422
|
+
if (!enabled || !baseUrl || !isStudioActiveStatus(streamStatus)) {
|
|
4423
|
+
setPlayout(video.EMPTY_LIVE_CAPTION_PLAYOUT);
|
|
4424
|
+
return;
|
|
4425
|
+
}
|
|
4426
|
+
const tick2 = () => {
|
|
4427
|
+
setPlayout(
|
|
4428
|
+
(prev) => video.advanceLiveCaptionPlayout(
|
|
4429
|
+
prev,
|
|
4430
|
+
cuesRef.current,
|
|
4431
|
+
Date.now(),
|
|
4432
|
+
0,
|
|
4433
|
+
void 0,
|
|
4434
|
+
STUDIO_EXTRA_LAG_MS
|
|
3738
4435
|
)
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
4436
|
+
);
|
|
4437
|
+
};
|
|
4438
|
+
tick2();
|
|
4439
|
+
const timer = window.setInterval(tick2, PLAYOUT_MS);
|
|
4440
|
+
return () => window.clearInterval(timer);
|
|
4441
|
+
}, [baseUrl, enabled, streamStatus]);
|
|
4442
|
+
return playout;
|
|
3742
4443
|
}
|
|
3743
4444
|
var STAGE_GRANT_VERIFY_MS = 4e3;
|
|
3744
4445
|
var STUDIO_CONNECT_WATCHDOG_MS = 12e3;
|
|
@@ -3756,7 +4457,11 @@ function StudioRoom({
|
|
|
3756
4457
|
windowGate,
|
|
3757
4458
|
initialCameraOn = true,
|
|
3758
4459
|
initialMuted = false,
|
|
3759
|
-
initialDevices
|
|
4460
|
+
initialDevices,
|
|
4461
|
+
appDownloadUrl,
|
|
4462
|
+
captionsBaseUrl,
|
|
4463
|
+
apiUrl,
|
|
4464
|
+
cdnBase
|
|
3760
4465
|
}) {
|
|
3761
4466
|
const [cameraOn, setCameraOn] = react.useState(initialCameraOn);
|
|
3762
4467
|
const [screenOn, setScreenOn] = react.useState(false);
|
|
@@ -3777,6 +4482,12 @@ function StudioRoom({
|
|
|
3777
4482
|
);
|
|
3778
4483
|
const [egressError, setEgressError] = react.useState(null);
|
|
3779
4484
|
const [shareGrant, setShareGrant] = react.useState("idle");
|
|
4485
|
+
const [downloadAppOpen, setDownloadAppOpen] = react.useState(false);
|
|
4486
|
+
const [captionsOn, setCaptionsOn] = react.useState(false);
|
|
4487
|
+
const [captionPrefs, setCaptionPrefs] = react.useState(
|
|
4488
|
+
() => readStudioCaptionPrefs()
|
|
4489
|
+
);
|
|
4490
|
+
const captionHintShown = react.useRef(false);
|
|
3780
4491
|
const [connectTimedOut, setConnectTimedOut] = react.useState(false);
|
|
3781
4492
|
const [screenPermission, setScreenPermission] = react.useState(
|
|
3782
4493
|
() => meeting.screenSharePermission()
|
|
@@ -3802,6 +4513,19 @@ function StudioRoom({
|
|
|
3802
4513
|
const pip = useDocumentPictureInPicture();
|
|
3803
4514
|
const gateFromContext = useStudioWindowGate();
|
|
3804
4515
|
const { reportDuplicate } = windowGate ?? gateFromContext;
|
|
4516
|
+
const resolvedCaptionsBaseUrl = captionsBaseUrl ?? studioCaptionsBaseUrl(apiUrl, session.stream.id);
|
|
4517
|
+
const captionsAvailable = session.stream.captionsEnabled !== false;
|
|
4518
|
+
const captionsReady = isStudioActiveStatus(session.stream.status);
|
|
4519
|
+
const captionPlayout = useStudioCaptions({
|
|
4520
|
+
enabled: captionsOn && captionsAvailable,
|
|
4521
|
+
baseUrl: resolvedCaptionsBaseUrl,
|
|
4522
|
+
locale: captionPrefs.locale,
|
|
4523
|
+
streamStatus: session.stream.status
|
|
4524
|
+
});
|
|
4525
|
+
const effectBackgrounds = react.useMemo(
|
|
4526
|
+
() => defaultStudioEffectBackgrounds(cdnBase),
|
|
4527
|
+
[cdnBase]
|
|
4528
|
+
);
|
|
3805
4529
|
const onAir = isLiveStudioStatus(session.stream.status);
|
|
3806
4530
|
const active = isStudioActiveStatus(session.stream.status);
|
|
3807
4531
|
if (onAir && liveStartedAtRef.current === null) {
|
|
@@ -3818,6 +4542,11 @@ function StudioRoom({
|
|
|
3818
4542
|
const roomState = leaveState ?? meeting.roomState();
|
|
3819
4543
|
const sessionOver = isStudioSessionOver(session.stream.status, roomState);
|
|
3820
4544
|
const rejected = isStudioRejected(roomState);
|
|
4545
|
+
const connectionState = react.useMemo(
|
|
4546
|
+
() => meeting.connectionState(),
|
|
4547
|
+
[meeting, tick2]
|
|
4548
|
+
);
|
|
4549
|
+
const guestConnectFailed = session.role === "guest" && (connectionState === "failed" || connectionState === "connecting" && connectTimedOut);
|
|
3821
4550
|
useBeforeUnload(active && !sessionOver);
|
|
3822
4551
|
useWakeLock(active && !sessionOver);
|
|
3823
4552
|
react.useEffect(() => {
|
|
@@ -3981,12 +4710,13 @@ function StudioRoom({
|
|
|
3981
4710
|
void meeting.setScreenShareEnabled(false);
|
|
3982
4711
|
}, [guestOffStage, meeting]);
|
|
3983
4712
|
react.useEffect(() => {
|
|
3984
|
-
if (!sessionOver && !rejected) return;
|
|
4713
|
+
if (!sessionOver && !rejected && !guestConnectFailed) return;
|
|
4714
|
+
pip.close();
|
|
3985
4715
|
setShareGrant("idle");
|
|
3986
4716
|
void meeting.setAudioEnabled(false);
|
|
3987
4717
|
void meeting.setVideoEnabled(false);
|
|
3988
4718
|
void meeting.setScreenShareEnabled(false);
|
|
3989
|
-
}, [meeting, rejected, sessionOver]);
|
|
4719
|
+
}, [guestConnectFailed, meeting, pip.close, rejected, sessionOver]);
|
|
3990
4720
|
react.useEffect(() => {
|
|
3991
4721
|
return meeting.subscribeMedia(() => {
|
|
3992
4722
|
const self2 = meeting.participants().find((row) => row.isSelf);
|
|
@@ -4024,10 +4754,6 @@ function StudioRoom({
|
|
|
4024
4754
|
[participants]
|
|
4025
4755
|
);
|
|
4026
4756
|
const waitlisted = react.useMemo(() => meeting.waitlisted(), [meeting, tick2]);
|
|
4027
|
-
const connectionState = react.useMemo(
|
|
4028
|
-
() => meeting.connectionState(),
|
|
4029
|
-
[meeting, tick2]
|
|
4030
|
-
);
|
|
4031
4757
|
const lastMeetingError = react.useMemo(
|
|
4032
4758
|
() => meeting.lastMeetingError(),
|
|
4033
4759
|
[meeting, tick2]
|
|
@@ -4045,7 +4771,7 @@ function StudioRoom({
|
|
|
4045
4771
|
captureSupported,
|
|
4046
4772
|
screenPermission
|
|
4047
4773
|
);
|
|
4048
|
-
const showScreenControl = durableScreenBlock
|
|
4774
|
+
const showScreenControl = showScreenShareControl(durableScreenBlock);
|
|
4049
4775
|
const canGoLive = session.role === "moderator" && canPublish(session.stream.status);
|
|
4050
4776
|
const showPublishButton = session.role === "moderator" && !onAir && session.stream.status !== "ended" && session.stream.status !== "error";
|
|
4051
4777
|
const self = participants.find((row) => row.isSelf);
|
|
@@ -4141,17 +4867,10 @@ function StudioRoom({
|
|
|
4141
4867
|
}, [connectionState, session.role]);
|
|
4142
4868
|
react.useEffect(() => {
|
|
4143
4869
|
if (inWaitingRoom || screenNoticePrimed.current) return;
|
|
4144
|
-
if (
|
|
4870
|
+
if (durableScreenBlock !== "permission") return;
|
|
4145
4871
|
screenNoticePrimed.current = true;
|
|
4146
|
-
sonner.toast.message(
|
|
4147
|
-
|
|
4148
|
-
);
|
|
4149
|
-
}, [
|
|
4150
|
-
durableScreenBlock,
|
|
4151
|
-
inWaitingRoom,
|
|
4152
|
-
labels.screenNotAllowed,
|
|
4153
|
-
labels.screenUnsupportedDevice
|
|
4154
|
-
]);
|
|
4872
|
+
sonner.toast.message(labels.screenNotAllowed);
|
|
4873
|
+
}, [durableScreenBlock, inWaitingRoom, labels.screenNotAllowed]);
|
|
4155
4874
|
react.useEffect(() => {
|
|
4156
4875
|
return meeting.subscribeBroadcast((message) => {
|
|
4157
4876
|
if (message.type === "host-media") {
|
|
@@ -4260,6 +4979,10 @@ function StudioRoom({
|
|
|
4260
4979
|
setCameraOn((current) => !current);
|
|
4261
4980
|
}, []);
|
|
4262
4981
|
const onToggleScreen = react.useCallback(() => {
|
|
4982
|
+
if (durableScreenBlock === "device") {
|
|
4983
|
+
setDownloadAppOpen(true);
|
|
4984
|
+
return;
|
|
4985
|
+
}
|
|
4263
4986
|
if (!canShareFreely || screenRequestPending) return;
|
|
4264
4987
|
if (!screenOn && !canStartScreenShare(meeting.participants())) {
|
|
4265
4988
|
sonner.toast.message(labels.screenBusy);
|
|
@@ -4269,12 +4992,17 @@ function StudioRoom({
|
|
|
4269
4992
|
setScreenOn((current) => !current);
|
|
4270
4993
|
}, [
|
|
4271
4994
|
canShareFreely,
|
|
4995
|
+
durableScreenBlock,
|
|
4272
4996
|
labels.screenBusy,
|
|
4273
4997
|
meeting,
|
|
4274
4998
|
screenOn,
|
|
4275
4999
|
screenRequestPending
|
|
4276
5000
|
]);
|
|
4277
5001
|
const onAskToShare = react.useCallback(async () => {
|
|
5002
|
+
if (durableScreenBlock === "device") {
|
|
5003
|
+
setDownloadAppOpen(true);
|
|
5004
|
+
return;
|
|
5005
|
+
}
|
|
4278
5006
|
const local = meeting.participants().find((row) => row.isSelf);
|
|
4279
5007
|
if (!local) return;
|
|
4280
5008
|
try {
|
|
@@ -4291,7 +5019,12 @@ function StudioRoom({
|
|
|
4291
5019
|
} catch {
|
|
4292
5020
|
sonner.toast.error(labels.shareRequestError);
|
|
4293
5021
|
}
|
|
4294
|
-
}, [
|
|
5022
|
+
}, [
|
|
5023
|
+
durableScreenBlock,
|
|
5024
|
+
labels.shareRequestError,
|
|
5025
|
+
labels.shareRequestSent,
|
|
5026
|
+
meeting
|
|
5027
|
+
]);
|
|
4295
5028
|
const verifyGrantTookEffect = react.useCallback(
|
|
4296
5029
|
(userId, retried = false) => {
|
|
4297
5030
|
const id = window.setTimeout(() => {
|
|
@@ -4454,7 +5187,6 @@ function StudioRoom({
|
|
|
4454
5187
|
}
|
|
4455
5188
|
) });
|
|
4456
5189
|
}
|
|
4457
|
-
const guestConnectFailed = session.role === "guest" && (connectionState === "failed" || connectionState === "connecting" && connectTimedOut);
|
|
4458
5190
|
if (guestConnectFailed) {
|
|
4459
5191
|
const detail = [meeting.roomState(), lastMeetingError].filter((value) => Boolean(value)).join(" \u2014 ");
|
|
4460
5192
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-dvh flex-col items-center justify-center p-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4478,6 +5210,10 @@ function StudioRoom({
|
|
|
4478
5210
|
labels,
|
|
4479
5211
|
settingsOpen,
|
|
4480
5212
|
statusLabel: connectionState === "connecting" ? labels.connecting : labels.waitingHost,
|
|
5213
|
+
captionPrefs,
|
|
5214
|
+
onCaptionPrefsChange: setCaptionPrefs,
|
|
5215
|
+
captionsAvailable,
|
|
5216
|
+
effectBackgrounds,
|
|
4481
5217
|
onToggleCamera,
|
|
4482
5218
|
onToggleMute: () => void onToggleMute(),
|
|
4483
5219
|
onOpenSettings: () => setSettingsOpen(true),
|
|
@@ -4585,27 +5321,37 @@ function StudioRoom({
|
|
|
4585
5321
|
}
|
|
4586
5322
|
) : null,
|
|
4587
5323
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 flex-col md:flex-row", children: [
|
|
4588
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
5324
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex min-h-0 min-w-0 flex-1 flex-col", children: [
|
|
5325
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5326
|
+
StudioStage,
|
|
5327
|
+
{
|
|
5328
|
+
participants: stageParticipants,
|
|
5329
|
+
meeting,
|
|
5330
|
+
reducedMotion,
|
|
5331
|
+
canModerate: session.role === "moderator",
|
|
5332
|
+
canManageStage,
|
|
5333
|
+
onAir,
|
|
5334
|
+
labels,
|
|
5335
|
+
onKick: (id) => void onKick(id),
|
|
5336
|
+
onMute: (id) => void onHostMedia(id, "audio"),
|
|
5337
|
+
onStopCamera: (id) => void onHostMedia(id, "video"),
|
|
5338
|
+
onStopScreen: (id) => void onHostMedia(id, "screen"),
|
|
5339
|
+
onToggleStage: (tile) => void onSetStage(tile, !isOnStage(tile.stageStatus)),
|
|
5340
|
+
onTogglePin: (tile) => void onTogglePin(tile),
|
|
5341
|
+
onToggleChat: (tile) => void onToggleChat(tile),
|
|
5342
|
+
canTakeOff: (id) => canTakeOffAir(participants, id, onAir),
|
|
5343
|
+
onOpenPeople: () => setSideTab("people")
|
|
5344
|
+
}
|
|
5345
|
+
),
|
|
5346
|
+
captionsOn ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
5347
|
+
video.CaptionOverlay,
|
|
5348
|
+
{
|
|
5349
|
+
screen: captionPlayout.screen,
|
|
5350
|
+
className: "bottom-3",
|
|
5351
|
+
textClassName: captionSizeClass(captionPrefs.size)
|
|
5352
|
+
}
|
|
5353
|
+
) : null
|
|
5354
|
+
] }),
|
|
4609
5355
|
sideTab ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4610
5356
|
StudioSidePanel,
|
|
4611
5357
|
{
|
|
@@ -4718,6 +5464,12 @@ function StudioRoom({
|
|
|
4718
5464
|
stageEnabled,
|
|
4719
5465
|
canLeaveStage: selfId != null && canTakeOffAir(participants, selfId, onAir),
|
|
4720
5466
|
reducedMotion,
|
|
5467
|
+
captionsOn,
|
|
5468
|
+
captionsAvailable,
|
|
5469
|
+
captionsReady,
|
|
5470
|
+
captionPrefs,
|
|
5471
|
+
onCaptionPrefsChange: setCaptionPrefs,
|
|
5472
|
+
effectBackgrounds,
|
|
4721
5473
|
onToggleMute: () => void onToggleMute(),
|
|
4722
5474
|
onToggleCamera,
|
|
4723
5475
|
onToggleScreen,
|
|
@@ -4745,6 +5497,16 @@ function StudioRoom({
|
|
|
4745
5497
|
onCloseSettings: setSettingsOpen,
|
|
4746
5498
|
onOpenPip,
|
|
4747
5499
|
onClosePip: pip.close,
|
|
5500
|
+
onToggleCaptions: () => {
|
|
5501
|
+
if (!captionsAvailable) return;
|
|
5502
|
+
if (!captionsOn && !captionHintShown.current) {
|
|
5503
|
+
captionHintShown.current = true;
|
|
5504
|
+
sonner.toast.message(
|
|
5505
|
+
captionsReady ? labels.captionsDelayed : labels.captionsNotReady
|
|
5506
|
+
);
|
|
5507
|
+
}
|
|
5508
|
+
setCaptionsOn((current) => !current);
|
|
5509
|
+
},
|
|
4748
5510
|
onLeave: () => void onLeave()
|
|
4749
5511
|
}
|
|
4750
5512
|
),
|
|
@@ -4788,7 +5550,21 @@ function StudioRoom({
|
|
|
4788
5550
|
}
|
|
4789
5551
|
),
|
|
4790
5552
|
pip.pipWindow.document.body
|
|
4791
|
-
) : null
|
|
5553
|
+
) : null,
|
|
5554
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5555
|
+
StudioDownloadAppDialog,
|
|
5556
|
+
{
|
|
5557
|
+
open: downloadAppOpen,
|
|
5558
|
+
onOpenChange: setDownloadAppOpen,
|
|
5559
|
+
downloadUrl: appDownloadUrl,
|
|
5560
|
+
labels: {
|
|
5561
|
+
screenDownloadAppTitle: labels.screenDownloadAppTitle,
|
|
5562
|
+
screenUnsupportedDevice: labels.screenUnsupportedDevice,
|
|
5563
|
+
screenDownloadAppAction: labels.screenDownloadAppAction,
|
|
5564
|
+
screenDownloadAppDismiss: labels.screenDownloadAppDismiss
|
|
5565
|
+
}
|
|
5566
|
+
}
|
|
5567
|
+
)
|
|
4792
5568
|
] });
|
|
4793
5569
|
}
|
|
4794
5570
|
function isOverconstrained(error) {
|
|
@@ -5125,16 +5901,16 @@ function usePrefersReducedMotion() {
|
|
|
5125
5901
|
// src/studio/lib/guest-identity.ts
|
|
5126
5902
|
var NAME_KEY = "livo-studio-display-name";
|
|
5127
5903
|
var ID_KEY = "livo-studio-guest-id";
|
|
5128
|
-
function readStudioGuestIdentity(storage =
|
|
5904
|
+
function readStudioGuestIdentity(storage = defaultStorage4()) {
|
|
5129
5905
|
const guestId = storage?.getItem(ID_KEY)?.trim() || crypto.randomUUID();
|
|
5130
5906
|
storage?.setItem(ID_KEY, guestId);
|
|
5131
5907
|
const displayName = storage?.getItem(NAME_KEY)?.trim() ?? "";
|
|
5132
5908
|
return { displayName, guestId };
|
|
5133
5909
|
}
|
|
5134
|
-
function persistStudioGuestName(displayName, storage =
|
|
5910
|
+
function persistStudioGuestName(displayName, storage = defaultStorage4()) {
|
|
5135
5911
|
storage?.setItem(NAME_KEY, displayName.trim());
|
|
5136
5912
|
}
|
|
5137
|
-
function
|
|
5913
|
+
function defaultStorage4() {
|
|
5138
5914
|
if (typeof window === "undefined") return null;
|
|
5139
5915
|
try {
|
|
5140
5916
|
return window.localStorage;
|
|
@@ -5503,10 +6279,19 @@ function studioMeetingFromRtk(raw) {
|
|
|
5503
6279
|
if (typeof window !== "undefined") {
|
|
5504
6280
|
window.addEventListener("unhandledrejection", onUnhandledRejection);
|
|
5505
6281
|
}
|
|
6282
|
+
let videoEffects = readStudioVideoEffects();
|
|
6283
|
+
async function applyCurrentVideoEffects() {
|
|
6284
|
+
await applyStudioVideoEffects({
|
|
6285
|
+
self: meeting.self,
|
|
6286
|
+
meeting,
|
|
6287
|
+
config: videoEffects
|
|
6288
|
+
});
|
|
6289
|
+
}
|
|
5506
6290
|
async function republishCameraIfOn() {
|
|
5507
6291
|
if (meeting.self?.videoEnabled !== true) return;
|
|
5508
6292
|
await meeting.self.disableVideo?.();
|
|
5509
6293
|
await meeting.self.enableVideo?.();
|
|
6294
|
+
await applyCurrentVideoEffects();
|
|
5510
6295
|
}
|
|
5511
6296
|
async function acceptAllowedStageIfOffAir() {
|
|
5512
6297
|
if (currentStageStatus() !== "OFF_STAGE") return;
|
|
@@ -5694,6 +6479,7 @@ function studioMeetingFromRtk(raw) {
|
|
|
5694
6479
|
async join() {
|
|
5695
6480
|
await meeting.join?.();
|
|
5696
6481
|
await joinStageIfAllowed();
|
|
6482
|
+
await applyCurrentVideoEffects();
|
|
5697
6483
|
},
|
|
5698
6484
|
async leave() {
|
|
5699
6485
|
if (typeof window !== "undefined") {
|
|
@@ -5706,8 +6492,10 @@ function studioMeetingFromRtk(raw) {
|
|
|
5706
6492
|
else await meeting.self?.disableAudio?.();
|
|
5707
6493
|
},
|
|
5708
6494
|
async setVideoEnabled(enabled) {
|
|
5709
|
-
if (enabled)
|
|
5710
|
-
|
|
6495
|
+
if (enabled) {
|
|
6496
|
+
await meeting.self?.enableVideo?.();
|
|
6497
|
+
await applyCurrentVideoEffects();
|
|
6498
|
+
} else await meeting.self?.disableVideo?.();
|
|
5711
6499
|
},
|
|
5712
6500
|
async setScreenShareEnabled(enabled) {
|
|
5713
6501
|
if (enabled) await meeting.self?.enableScreenShare?.();
|
|
@@ -6091,6 +6879,14 @@ function studioMeetingFromRtk(raw) {
|
|
|
6091
6879
|
}
|
|
6092
6880
|
});
|
|
6093
6881
|
},
|
|
6882
|
+
async setVideoEffects(config) {
|
|
6883
|
+
videoEffects = config;
|
|
6884
|
+
persistStudioVideoEffects(config);
|
|
6885
|
+
await applyCurrentVideoEffects();
|
|
6886
|
+
},
|
|
6887
|
+
videoEffects() {
|
|
6888
|
+
return videoEffects;
|
|
6889
|
+
},
|
|
6094
6890
|
async setDisplayName(name) {
|
|
6095
6891
|
const trimmed = name.trim().slice(0, STUDIO_DISPLAY_NAME_MAX);
|
|
6096
6892
|
if (!trimmed) return;
|
|
@@ -6322,6 +7118,21 @@ async function getPublicStreamStatus(streamId, apiUrl = defaultApiUrl(), fetchFn
|
|
|
6322
7118
|
return null;
|
|
6323
7119
|
}
|
|
6324
7120
|
}
|
|
7121
|
+
async function getPublicStreamMeta(streamId, apiUrl = defaultApiUrl(), fetchFn = fetch) {
|
|
7122
|
+
try {
|
|
7123
|
+
const body = await publicJson(
|
|
7124
|
+
`${apiUrl.replace(/\/$/, "")}/public/streams/${encodeURIComponent(streamId)}`,
|
|
7125
|
+
{},
|
|
7126
|
+
fetchFn
|
|
7127
|
+
);
|
|
7128
|
+
return parsePublicStreamMeta(body);
|
|
7129
|
+
} catch (error) {
|
|
7130
|
+
if (error instanceof StudioApiError && (error.status === 404 || error.status === 410)) {
|
|
7131
|
+
return { status: "ended", captionsEnabled: false };
|
|
7132
|
+
}
|
|
7133
|
+
return null;
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
6325
7136
|
var POLL_INTERVAL_MS = 5e3;
|
|
6326
7137
|
function LivoHostStudio({
|
|
6327
7138
|
token,
|
|
@@ -6331,11 +7142,15 @@ function LivoHostStudio({
|
|
|
6331
7142
|
embed = false,
|
|
6332
7143
|
chrome,
|
|
6333
7144
|
playerOrigin,
|
|
6334
|
-
theme
|
|
7145
|
+
theme,
|
|
7146
|
+
appDownloadUrl
|
|
6335
7147
|
}) {
|
|
6336
7148
|
const reducedMotion = usePrefersReducedMotion();
|
|
6337
7149
|
const [session, setSession] = react.useState(null);
|
|
6338
7150
|
const [status, setStatus] = react.useState(null);
|
|
7151
|
+
const [captionsEnabled, setCaptionsEnabled] = react.useState(
|
|
7152
|
+
void 0
|
|
7153
|
+
);
|
|
6339
7154
|
const [error, setError] = react.useState(null);
|
|
6340
7155
|
const { meeting, error: meetingError } = useRealtimeKitMeeting(
|
|
6341
7156
|
session?.authToken ?? null
|
|
@@ -6384,6 +7199,20 @@ function LivoHostStudio({
|
|
|
6384
7199
|
window.clearInterval(id);
|
|
6385
7200
|
};
|
|
6386
7201
|
}, [client, status]);
|
|
7202
|
+
react.useEffect(() => {
|
|
7203
|
+
if (!session) return;
|
|
7204
|
+
let cancelled = false;
|
|
7205
|
+
const poll = async () => {
|
|
7206
|
+
const next = await getPublicStreamMeta(session.stream.id, apiUrl);
|
|
7207
|
+
if (!cancelled && next) setCaptionsEnabled(next.captionsEnabled);
|
|
7208
|
+
};
|
|
7209
|
+
void poll();
|
|
7210
|
+
const id = window.setInterval(() => void poll(), POLL_INTERVAL_MS);
|
|
7211
|
+
return () => {
|
|
7212
|
+
cancelled = true;
|
|
7213
|
+
window.clearInterval(id);
|
|
7214
|
+
};
|
|
7215
|
+
}, [apiUrl, session]);
|
|
6387
7216
|
const liveSession = react.useMemo(() => {
|
|
6388
7217
|
if (!session) return null;
|
|
6389
7218
|
if (!status) return session;
|
|
@@ -6391,10 +7220,11 @@ function LivoHostStudio({
|
|
|
6391
7220
|
...session,
|
|
6392
7221
|
stream: {
|
|
6393
7222
|
...session.stream,
|
|
6394
|
-
status
|
|
7223
|
+
status,
|
|
7224
|
+
captionsEnabled: captionsEnabled ?? session.stream.captionsEnabled
|
|
6395
7225
|
}
|
|
6396
7226
|
};
|
|
6397
|
-
}, [session, status]);
|
|
7227
|
+
}, [captionsEnabled, session, status]);
|
|
6398
7228
|
if (error || meetingError) {
|
|
6399
7229
|
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-destructive p-6", role: "alert", children: error ?? meetingError });
|
|
6400
7230
|
}
|
|
@@ -6411,7 +7241,9 @@ function LivoHostStudio({
|
|
|
6411
7241
|
labels,
|
|
6412
7242
|
embed,
|
|
6413
7243
|
chrome,
|
|
6414
|
-
playerOrigin
|
|
7244
|
+
playerOrigin,
|
|
7245
|
+
appDownloadUrl,
|
|
7246
|
+
apiUrl
|
|
6415
7247
|
}
|
|
6416
7248
|
);
|
|
6417
7249
|
}
|
|
@@ -6424,11 +7256,15 @@ function LivoGuestStudio({
|
|
|
6424
7256
|
lobbyLabels,
|
|
6425
7257
|
embed = false,
|
|
6426
7258
|
playerOrigin,
|
|
6427
|
-
theme
|
|
7259
|
+
theme,
|
|
7260
|
+
appDownloadUrl
|
|
6428
7261
|
}) {
|
|
6429
7262
|
const reducedMotion = usePrefersReducedMotion();
|
|
6430
7263
|
const [session, setSession] = react.useState(null);
|
|
6431
7264
|
const [status, setStatus] = react.useState(null);
|
|
7265
|
+
const [captionsEnabled, setCaptionsEnabled] = react.useState(
|
|
7266
|
+
void 0
|
|
7267
|
+
);
|
|
6432
7268
|
const [error, setError] = react.useState(null);
|
|
6433
7269
|
const [displayName, setDisplayName] = react.useState("");
|
|
6434
7270
|
const [guestId, setGuestId] = react.useState("");
|
|
@@ -6532,8 +7368,11 @@ function LivoGuestStudio({
|
|
|
6532
7368
|
if (current === "ended" || current === "error") return;
|
|
6533
7369
|
let cancelled = false;
|
|
6534
7370
|
const poll = async () => {
|
|
6535
|
-
const next = await
|
|
6536
|
-
if (!cancelled && next)
|
|
7371
|
+
const next = await getPublicStreamMeta(session.stream.id, apiUrl);
|
|
7372
|
+
if (!cancelled && next) {
|
|
7373
|
+
setStatus(next.status);
|
|
7374
|
+
setCaptionsEnabled(next.captionsEnabled);
|
|
7375
|
+
}
|
|
6537
7376
|
};
|
|
6538
7377
|
void poll();
|
|
6539
7378
|
const intervalMs = current === "public" ? POLL_INTERVAL_MS2 : PREVIEW_POLL_INTERVAL_MS;
|
|
@@ -6548,9 +7387,13 @@ function LivoGuestStudio({
|
|
|
6548
7387
|
if (!status) return session;
|
|
6549
7388
|
return {
|
|
6550
7389
|
...session,
|
|
6551
|
-
stream: {
|
|
7390
|
+
stream: {
|
|
7391
|
+
...session.stream,
|
|
7392
|
+
status,
|
|
7393
|
+
captionsEnabled: captionsEnabled ?? session.stream.captionsEnabled
|
|
7394
|
+
}
|
|
6552
7395
|
};
|
|
6553
|
-
}, [session, status]);
|
|
7396
|
+
}, [captionsEnabled, session, status]);
|
|
6554
7397
|
react.useEffect(() => {
|
|
6555
7398
|
if (retryAfterMs == null) return;
|
|
6556
7399
|
const id = window.setTimeout(() => {
|
|
@@ -6610,7 +7453,9 @@ function LivoGuestStudio({
|
|
|
6610
7453
|
playerOrigin,
|
|
6611
7454
|
initialCameraOn: cameraOn,
|
|
6612
7455
|
initialMuted: muted,
|
|
6613
|
-
initialDevices: deviceSelection
|
|
7456
|
+
initialDevices: deviceSelection,
|
|
7457
|
+
appDownloadUrl,
|
|
7458
|
+
apiUrl
|
|
6614
7459
|
}
|
|
6615
7460
|
);
|
|
6616
7461
|
}
|
|
@@ -6715,6 +7560,9 @@ function studioRoomLabels(t) {
|
|
|
6715
7560
|
pipTitle: t("pipTitle"),
|
|
6716
7561
|
pipError: t("pipError"),
|
|
6717
7562
|
screenUnsupportedDevice: t("screenUnsupportedDevice"),
|
|
7563
|
+
screenDownloadAppTitle: t("screenDownloadAppTitle"),
|
|
7564
|
+
screenDownloadAppAction: t("screenDownloadAppAction"),
|
|
7565
|
+
screenDownloadAppDismiss: t("screenDownloadAppDismiss"),
|
|
6718
7566
|
screenNotAllowed: t("screenNotAllowed"),
|
|
6719
7567
|
askToShare: t("askToShare"),
|
|
6720
7568
|
shareRequested: t("shareRequested"),
|
|
@@ -6783,7 +7631,37 @@ function studioRoomLabels(t) {
|
|
|
6783
7631
|
chatPin: t("chatPin"),
|
|
6784
7632
|
chatUnpin: t("chatUnpin"),
|
|
6785
7633
|
chatSave: t("chatSave"),
|
|
6786
|
-
waitingSkeleton: t("waitingSkeleton")
|
|
7634
|
+
waitingSkeleton: t("waitingSkeleton"),
|
|
7635
|
+
more: t("more"),
|
|
7636
|
+
captions: t("captions"),
|
|
7637
|
+
captionsOn: t("captionsOn"),
|
|
7638
|
+
captionsOff: t("captionsOff"),
|
|
7639
|
+
captionsNotReady: t("captionsNotReady"),
|
|
7640
|
+
captionsDelayed: t("captionsDelayed"),
|
|
7641
|
+
captionsLanguage: t("captionsLanguage"),
|
|
7642
|
+
captionsSize: t("captionsSize"),
|
|
7643
|
+
captionsSizeS: t("captionsSizeS"),
|
|
7644
|
+
captionsSizeM: t("captionsSizeM"),
|
|
7645
|
+
captionsSizeL: t("captionsSizeL"),
|
|
7646
|
+
settingsGeneral: t("settingsGeneral"),
|
|
7647
|
+
settingsAudio: t("settingsAudio"),
|
|
7648
|
+
settingsVideo: t("settingsVideo"),
|
|
7649
|
+
settingsEffects: t("settingsEffects"),
|
|
7650
|
+
settingsSubtitles: t("settingsSubtitles"),
|
|
7651
|
+
effectNone: t("effectNone"),
|
|
7652
|
+
effectBlur: t("effectBlur"),
|
|
7653
|
+
effectBlurStrong: t("effectBlurStrong"),
|
|
7654
|
+
effectBackgrounds: t("effectBackgrounds"),
|
|
7655
|
+
effectFilters: t("effectFilters"),
|
|
7656
|
+
effectFilterNone: t("effectFilterNone"),
|
|
7657
|
+
effectFilterGrayscale: t("effectFilterGrayscale"),
|
|
7658
|
+
effectFilterSepia: t("effectFilterSepia"),
|
|
7659
|
+
effectFilterWarm: t("effectFilterWarm"),
|
|
7660
|
+
effectFilterCool: t("effectFilterCool"),
|
|
7661
|
+
effectFilterBright: t("effectFilterBright"),
|
|
7662
|
+
localeEn: t("localeEn"),
|
|
7663
|
+
localeEs: t("localeEs"),
|
|
7664
|
+
localePt: t("localePt")
|
|
6787
7665
|
};
|
|
6788
7666
|
}
|
|
6789
7667
|
|
|
@@ -6801,6 +7679,7 @@ exports.applyEmbedTheme = applyEmbedTheme;
|
|
|
6801
7679
|
exports.controlActionsFromClient = controlActionsFromClient;
|
|
6802
7680
|
exports.createStudioControlClient = createStudioControlClient;
|
|
6803
7681
|
exports.decodeEmbedTheme = decodeEmbedTheme;
|
|
7682
|
+
exports.getPublicStreamMeta = getPublicStreamMeta;
|
|
6804
7683
|
exports.getPublicStreamStatus = getPublicStreamStatus;
|
|
6805
7684
|
exports.getStudioJoinStatus = getStudioJoinStatus;
|
|
6806
7685
|
exports.joinStudioGuest = joinStudioGuest;
|