@livo-tv/sdk 1.5.0 → 1.6.1
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 +935 -136
- package/dist/studio/index.cjs.map +1 -1
- package/dist/studio/index.d.cts +51 -2
- package/dist/studio/index.d.ts +51 -2
- package/dist/studio/index.js +936 -138
- package/dist/studio/index.js.map +1 -1
- 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';
|
|
@@ -690,7 +691,14 @@ function parseStudioStreamSummary(value) {
|
|
|
690
691
|
const status = readString(value, "status");
|
|
691
692
|
if (!id || !title || !status) return null;
|
|
692
693
|
const encoderLostAt = isRecord(value) ? typeof value.encoderLostAt === "number" ? value.encoderLostAt : value.encoderLostAt === null ? null : void 0 : void 0;
|
|
693
|
-
|
|
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
|
+
};
|
|
694
702
|
}
|
|
695
703
|
function parseStudioSession(value) {
|
|
696
704
|
if (!isRecord(value)) return null;
|
|
@@ -702,7 +710,9 @@ function parseStudioSession(value) {
|
|
|
702
710
|
const fromFull = isRecord(value.stream) ? parseStudioStreamSummary({
|
|
703
711
|
id: value.stream.id,
|
|
704
712
|
title: value.stream.title,
|
|
705
|
-
status: value.stream.status
|
|
713
|
+
status: value.stream.status,
|
|
714
|
+
captionsEnabled: value.stream.captionsEnabled,
|
|
715
|
+
captionsUrl: value.stream.captionsUrl
|
|
706
716
|
}) : null;
|
|
707
717
|
const stream = fromSummary ?? fromFull;
|
|
708
718
|
if (!stream) return null;
|
|
@@ -735,6 +745,14 @@ function parsePublicStreamStatus(value) {
|
|
|
735
745
|
const status = readString(nested, "status");
|
|
736
746
|
return status;
|
|
737
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
|
+
}
|
|
738
756
|
function studioErrorCode(error) {
|
|
739
757
|
if (!error || typeof error !== "object") return null;
|
|
740
758
|
const body = "body" in error ? error.body : null;
|
|
@@ -1614,6 +1632,211 @@ async function playSpeakerTest(deviceId) {
|
|
|
1614
1632
|
await ctx3.close();
|
|
1615
1633
|
}
|
|
1616
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
|
+
}
|
|
1617
1840
|
function StudioDeviceSelectGroup({
|
|
1618
1841
|
devices,
|
|
1619
1842
|
selected,
|
|
@@ -1692,7 +1915,11 @@ function StudioSettingsDialog({
|
|
|
1692
1915
|
meeting,
|
|
1693
1916
|
labels,
|
|
1694
1917
|
reducedMotion,
|
|
1695
|
-
cameraOn
|
|
1918
|
+
cameraOn,
|
|
1919
|
+
captionPrefs,
|
|
1920
|
+
onCaptionPrefsChange,
|
|
1921
|
+
captionsAvailable,
|
|
1922
|
+
effectBackgrounds
|
|
1696
1923
|
}) {
|
|
1697
1924
|
const devices = useMediaDevices(open ? 1 : 0, open);
|
|
1698
1925
|
const [saved, setSaved] = useState(
|
|
@@ -1704,11 +1931,16 @@ function StudioSettingsDialog({
|
|
|
1704
1931
|
const [displayName, setDisplayName] = useState("");
|
|
1705
1932
|
const [savedDisplayName, setSavedDisplayName] = useState("");
|
|
1706
1933
|
const [savingDisplayName, setSavingDisplayName] = useState(false);
|
|
1934
|
+
const [tab, setTab] = useState("general");
|
|
1935
|
+
const [effects, setEffects] = useState(
|
|
1936
|
+
() => meeting.videoEffects()
|
|
1937
|
+
);
|
|
1707
1938
|
const { effective } = resolveDeviceSelection(saved, devices);
|
|
1708
1939
|
useEffect(() => {
|
|
1709
1940
|
if (!open) return;
|
|
1710
1941
|
setSaved(readStudioDeviceSelection());
|
|
1711
|
-
|
|
1942
|
+
setEffects(meeting.videoEffects());
|
|
1943
|
+
}, [meeting, open]);
|
|
1712
1944
|
useEffect(() => {
|
|
1713
1945
|
if (!open) return;
|
|
1714
1946
|
const selfName = meeting.participants().find((row) => row.isSelf)?.name?.trim() ?? "";
|
|
@@ -1761,76 +1993,306 @@ function StudioSettingsDialog({
|
|
|
1761
1993
|
setTestingSpeaker(false);
|
|
1762
1994
|
}
|
|
1763
1995
|
}
|
|
1764
|
-
|
|
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: [
|
|
1765
2012
|
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
1766
2013
|
/* @__PURE__ */ jsx(DialogTitle, { children: labels.settingsTitle }),
|
|
1767
2014
|
/* @__PURE__ */ jsx(DialogDescription, { children: labels.settingsDescription })
|
|
1768
2015
|
] }),
|
|
1769
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
1770
|
-
/* @__PURE__ */ jsx(
|
|
1771
|
-
|
|
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: [
|
|
1772
2161
|
/* @__PURE__ */ jsx(
|
|
1773
|
-
|
|
2162
|
+
EffectTile,
|
|
1774
2163
|
{
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
onChange: (event) => setDisplayName(event.target.value)
|
|
2164
|
+
label: labels.effectNone,
|
|
2165
|
+
active: effects.background === "none",
|
|
2166
|
+
onClick: () => onApply({ ...effects, background: "none" })
|
|
1779
2167
|
}
|
|
1780
2168
|
),
|
|
1781
2169
|
/* @__PURE__ */ jsx(
|
|
1782
|
-
|
|
2170
|
+
EffectTile,
|
|
1783
2171
|
{
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
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 })
|
|
1789
2183
|
}
|
|
1790
2184
|
)
|
|
1791
2185
|
] })
|
|
1792
2186
|
] }),
|
|
1793
|
-
/* @__PURE__ */
|
|
1794
|
-
|
|
1795
|
-
{
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
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,
|
|
1802
2204
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
1803
|
-
/* @__PURE__ */ jsx("p", { className: "
|
|
1804
|
-
/* @__PURE__ */ jsx(
|
|
1805
|
-
|
|
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,
|
|
1806
2208
|
{
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
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",
|
|
1822
2239
|
{
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
variant: "outline",
|
|
1826
|
-
disabled: testingSpeaker,
|
|
1827
|
-
onClick: () => void onTestSpeaker(),
|
|
1828
|
-
children: labels.testSpeaker
|
|
2240
|
+
className: "bg-muted aspect-video w-full bg-cover bg-center",
|
|
2241
|
+
style: previewUrl ? { backgroundImage: `url(${previewUrl})` } : void 0
|
|
1829
2242
|
}
|
|
1830
|
-
)
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
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
|
+
] });
|
|
1834
2296
|
}
|
|
1835
2297
|
function SettingsCameraPreview({
|
|
1836
2298
|
track,
|
|
@@ -1881,7 +2343,11 @@ function StudioGuestWaiting({
|
|
|
1881
2343
|
onToggleCamera,
|
|
1882
2344
|
onToggleMute,
|
|
1883
2345
|
onOpenSettings,
|
|
1884
|
-
onCloseSettings
|
|
2346
|
+
onCloseSettings,
|
|
2347
|
+
captionPrefs,
|
|
2348
|
+
onCaptionPrefsChange,
|
|
2349
|
+
captionsAvailable,
|
|
2350
|
+
effectBackgrounds
|
|
1885
2351
|
}) {
|
|
1886
2352
|
const [audioTrack, setAudioTrack] = useState(null);
|
|
1887
2353
|
const [videoTrack, setVideoTrack] = useState(null);
|
|
@@ -1948,7 +2414,11 @@ function StudioGuestWaiting({
|
|
|
1948
2414
|
meeting,
|
|
1949
2415
|
labels,
|
|
1950
2416
|
reducedMotion,
|
|
1951
|
-
cameraOn
|
|
2417
|
+
cameraOn,
|
|
2418
|
+
captionPrefs,
|
|
2419
|
+
onCaptionPrefsChange,
|
|
2420
|
+
captionsAvailable,
|
|
2421
|
+
effectBackgrounds
|
|
1952
2422
|
}
|
|
1953
2423
|
)
|
|
1954
2424
|
] });
|
|
@@ -3573,6 +4043,12 @@ function StudioToolbar({
|
|
|
3573
4043
|
stageEnabled,
|
|
3574
4044
|
canLeaveStage,
|
|
3575
4045
|
reducedMotion,
|
|
4046
|
+
captionsOn,
|
|
4047
|
+
captionsAvailable,
|
|
4048
|
+
captionsReady,
|
|
4049
|
+
captionPrefs,
|
|
4050
|
+
onCaptionPrefsChange,
|
|
4051
|
+
effectBackgrounds,
|
|
3576
4052
|
onToggleMute,
|
|
3577
4053
|
onToggleCamera,
|
|
3578
4054
|
onToggleScreen,
|
|
@@ -3586,38 +4062,60 @@ function StudioToolbar({
|
|
|
3586
4062
|
onCloseSettings,
|
|
3587
4063
|
onOpenPip,
|
|
3588
4064
|
onClosePip,
|
|
4065
|
+
onToggleCaptions,
|
|
3589
4066
|
onLeave
|
|
3590
4067
|
}) {
|
|
3591
|
-
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";
|
|
3592
4072
|
async function enterFullscreen() {
|
|
3593
4073
|
try {
|
|
3594
4074
|
await document.documentElement.requestFullscreen();
|
|
3595
4075
|
} catch {
|
|
3596
4076
|
}
|
|
3597
4077
|
}
|
|
3598
|
-
|
|
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: "border-t px-3 py-3", children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
|
|
3599
4089
|
showMediaControls ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3600
4090
|
/* @__PURE__ */ jsx(
|
|
3601
|
-
|
|
4091
|
+
MediaSplit,
|
|
3602
4092
|
{
|
|
3603
4093
|
label: muted ? labels.unmute : labels.mute,
|
|
3604
4094
|
pressed: muted,
|
|
3605
4095
|
tone: muted ? "danger" : "default",
|
|
3606
4096
|
onClick: onToggleMute,
|
|
4097
|
+
pickerLabel: labels.micDevice,
|
|
4098
|
+
options: devices.audio,
|
|
4099
|
+
selected: effective.audio,
|
|
4100
|
+
onSelect: (id) => void onDeviceChange("audio", id),
|
|
3607
4101
|
children: muted ? /* @__PURE__ */ jsx(MicOff, {}) : /* @__PURE__ */ jsx(Mic, {})
|
|
3608
4102
|
}
|
|
3609
4103
|
),
|
|
3610
4104
|
/* @__PURE__ */ jsx(
|
|
3611
|
-
|
|
4105
|
+
MediaSplit,
|
|
3612
4106
|
{
|
|
3613
4107
|
label: labels.camera,
|
|
3614
4108
|
pressed: cameraOn,
|
|
3615
4109
|
onClick: onToggleCamera,
|
|
4110
|
+
pickerLabel: labels.cameraDevice,
|
|
4111
|
+
options: devices.video,
|
|
4112
|
+
selected: effective.video,
|
|
4113
|
+
onSelect: (id) => void onDeviceChange("video", id),
|
|
3616
4114
|
children: cameraOn ? /* @__PURE__ */ jsx(Video, {}) : /* @__PURE__ */ jsx(VideoOff, {})
|
|
3617
4115
|
}
|
|
3618
4116
|
)
|
|
3619
4117
|
] }) : null,
|
|
3620
|
-
|
|
4118
|
+
showStageModerator ? /* @__PURE__ */ jsx(
|
|
3621
4119
|
ControlIcon,
|
|
3622
4120
|
{
|
|
3623
4121
|
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : labels.goOnAir,
|
|
@@ -3637,7 +4135,7 @@ function StudioToolbar({
|
|
|
3637
4135
|
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
|
|
3638
4136
|
}
|
|
3639
4137
|
) : null,
|
|
3640
|
-
|
|
4138
|
+
showStageGuest ? /* @__PURE__ */ jsx(
|
|
3641
4139
|
ControlIcon,
|
|
3642
4140
|
{
|
|
3643
4141
|
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
|
|
@@ -3687,6 +4185,56 @@ function StudioToolbar({
|
|
|
3687
4185
|
}
|
|
3688
4186
|
)
|
|
3689
4187
|
] }) : /* @__PURE__ */ jsx(ControlIcon, { label: labels.askToShare, onClick: onAskToShare, children: /* @__PURE__ */ jsx(MonitorUp, {}) }) : null,
|
|
4188
|
+
/* @__PURE__ */ jsx(
|
|
4189
|
+
ControlIcon,
|
|
4190
|
+
{
|
|
4191
|
+
label: captionsLabel,
|
|
4192
|
+
pressed: captionsOn,
|
|
4193
|
+
disabled: !captionsAvailable,
|
|
4194
|
+
onClick: onToggleCaptions,
|
|
4195
|
+
children: /* @__PURE__ */ jsx(Captions, {})
|
|
4196
|
+
}
|
|
4197
|
+
),
|
|
4198
|
+
/* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
4199
|
+
/* @__PURE__ */ jsx(
|
|
4200
|
+
DropdownMenuTrigger,
|
|
4201
|
+
{
|
|
4202
|
+
render: /* @__PURE__ */ jsx(
|
|
4203
|
+
Button,
|
|
4204
|
+
{
|
|
4205
|
+
type: "button",
|
|
4206
|
+
size: "icon-lg",
|
|
4207
|
+
variant: "secondary",
|
|
4208
|
+
"aria-label": labels.more
|
|
4209
|
+
}
|
|
4210
|
+
),
|
|
4211
|
+
children: /* @__PURE__ */ jsx(MoreVertical, {})
|
|
4212
|
+
}
|
|
4213
|
+
),
|
|
4214
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "center", className: "min-w-44", children: [
|
|
4215
|
+
fullscreenSupported ? /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: () => void enterFullscreen(), children: [
|
|
4216
|
+
/* @__PURE__ */ jsx(Maximize, {}),
|
|
4217
|
+
labels.fullscreen
|
|
4218
|
+
] }) : null,
|
|
4219
|
+
pipSupported ? /* @__PURE__ */ jsxs(
|
|
4220
|
+
DropdownMenuItem,
|
|
4221
|
+
{
|
|
4222
|
+
onClick: () => {
|
|
4223
|
+
if (pipOpen) onClosePip();
|
|
4224
|
+
else onOpenPip();
|
|
4225
|
+
},
|
|
4226
|
+
children: [
|
|
4227
|
+
/* @__PURE__ */ jsx(PictureInPicture2, {}),
|
|
4228
|
+
pipOpen ? labels.closePip : labels.openPip
|
|
4229
|
+
]
|
|
4230
|
+
}
|
|
4231
|
+
) : null,
|
|
4232
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: onOpenSettings, children: [
|
|
4233
|
+
/* @__PURE__ */ jsx(Settings, {}),
|
|
4234
|
+
labels.settings
|
|
4235
|
+
] })
|
|
4236
|
+
] })
|
|
4237
|
+
] }),
|
|
3690
4238
|
/* @__PURE__ */ jsx(
|
|
3691
4239
|
ControlIcon,
|
|
3692
4240
|
{
|
|
@@ -3707,26 +4255,6 @@ function StudioToolbar({
|
|
|
3707
4255
|
children: /* @__PURE__ */ jsx(Users, {})
|
|
3708
4256
|
}
|
|
3709
4257
|
) : null,
|
|
3710
|
-
fullscreenSupported ? /* @__PURE__ */ jsx(
|
|
3711
|
-
ControlIcon,
|
|
3712
|
-
{
|
|
3713
|
-
label: labels.fullscreen,
|
|
3714
|
-
onClick: () => void enterFullscreen(),
|
|
3715
|
-
children: /* @__PURE__ */ jsx(Maximize, {})
|
|
3716
|
-
}
|
|
3717
|
-
) : null,
|
|
3718
|
-
pipSupported ? /* @__PURE__ */ jsx(
|
|
3719
|
-
ControlIcon,
|
|
3720
|
-
{
|
|
3721
|
-
label: pipOpen ? labels.closePip : labels.openPip,
|
|
3722
|
-
pressed: pipOpen,
|
|
3723
|
-
onClick: () => {
|
|
3724
|
-
if (pipOpen) onClosePip();
|
|
3725
|
-
else onOpenPip();
|
|
3726
|
-
},
|
|
3727
|
-
children: /* @__PURE__ */ jsx(PictureInPicture2, {})
|
|
3728
|
-
}
|
|
3729
|
-
) : null,
|
|
3730
4258
|
/* @__PURE__ */ jsx(
|
|
3731
4259
|
ControlIcon,
|
|
3732
4260
|
{
|
|
@@ -3744,21 +4272,11 @@ function StudioToolbar({
|
|
|
3744
4272
|
meeting,
|
|
3745
4273
|
reducedMotion,
|
|
3746
4274
|
cameraOn,
|
|
3747
|
-
labels
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
displayNameSaved: labels.displayNameSaved,
|
|
3753
|
-
displayNameError: labels.displayNameError,
|
|
3754
|
-
cameraDevice: labels.cameraDevice,
|
|
3755
|
-
micDevice: labels.micDevice,
|
|
3756
|
-
speakerDevice: labels.speakerDevice,
|
|
3757
|
-
testSpeaker: labels.testSpeaker,
|
|
3758
|
-
testSpeakerError: labels.testSpeakerError,
|
|
3759
|
-
cameraOff: labels.cameraOff,
|
|
3760
|
-
micLevel: labels.micLevel
|
|
3761
|
-
}
|
|
4275
|
+
labels,
|
|
4276
|
+
captionPrefs,
|
|
4277
|
+
onCaptionPrefsChange,
|
|
4278
|
+
captionsAvailable,
|
|
4279
|
+
effectBackgrounds
|
|
3762
4280
|
}
|
|
3763
4281
|
),
|
|
3764
4282
|
session.role === "guest" ? /* @__PURE__ */ jsx(
|
|
@@ -3783,7 +4301,141 @@ function StudioToolbar({
|
|
|
3783
4301
|
)
|
|
3784
4302
|
}
|
|
3785
4303
|
) : null
|
|
3786
|
-
] }) });
|
|
4304
|
+
] }) }) });
|
|
4305
|
+
}
|
|
4306
|
+
function MediaSplit({
|
|
4307
|
+
label,
|
|
4308
|
+
pressed,
|
|
4309
|
+
tone = "default",
|
|
4310
|
+
onClick,
|
|
4311
|
+
pickerLabel,
|
|
4312
|
+
options,
|
|
4313
|
+
selected,
|
|
4314
|
+
onSelect,
|
|
4315
|
+
children
|
|
4316
|
+
}) {
|
|
4317
|
+
if (options.length === 0) {
|
|
4318
|
+
return /* @__PURE__ */ jsx(
|
|
4319
|
+
ControlIcon,
|
|
4320
|
+
{
|
|
4321
|
+
label,
|
|
4322
|
+
pressed,
|
|
4323
|
+
tone,
|
|
4324
|
+
onClick,
|
|
4325
|
+
children
|
|
4326
|
+
}
|
|
4327
|
+
);
|
|
4328
|
+
}
|
|
4329
|
+
const iconVariant = tone === "danger" ? "destructive" : pressed ? "default" : "secondary";
|
|
4330
|
+
return /* @__PURE__ */ jsxs("div", { className: "bg-secondary inline-flex h-10 items-center rounded-full p-0.5", children: [
|
|
4331
|
+
/* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
4332
|
+
/* @__PURE__ */ jsx(
|
|
4333
|
+
DropdownMenuTrigger,
|
|
4334
|
+
{
|
|
4335
|
+
render: /* @__PURE__ */ jsx(
|
|
4336
|
+
Button,
|
|
4337
|
+
{
|
|
4338
|
+
type: "button",
|
|
4339
|
+
size: "icon-xs",
|
|
4340
|
+
variant: "ghost",
|
|
4341
|
+
className: "text-muted-foreground hover:text-foreground h-9 w-8 rounded-full hover:bg-transparent",
|
|
4342
|
+
"aria-label": pickerLabel
|
|
4343
|
+
}
|
|
4344
|
+
),
|
|
4345
|
+
children: /* @__PURE__ */ jsx(ChevronUp, { className: "size-3.5" })
|
|
4346
|
+
}
|
|
4347
|
+
),
|
|
4348
|
+
/* @__PURE__ */ jsx(DropdownMenuContent, { align: "start", side: "top", className: "min-w-52", children: options.map((option) => /* @__PURE__ */ jsxs(
|
|
4349
|
+
DropdownMenuItem,
|
|
4350
|
+
{
|
|
4351
|
+
onClick: () => onSelect(option.deviceId),
|
|
4352
|
+
children: [
|
|
4353
|
+
option.deviceId === selected ? "\u2713 " : "",
|
|
4354
|
+
option.label
|
|
4355
|
+
]
|
|
4356
|
+
},
|
|
4357
|
+
option.deviceId
|
|
4358
|
+
)) })
|
|
4359
|
+
] }),
|
|
4360
|
+
/* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
4361
|
+
/* @__PURE__ */ jsx(
|
|
4362
|
+
TooltipTrigger,
|
|
4363
|
+
{
|
|
4364
|
+
render: /* @__PURE__ */ jsx(
|
|
4365
|
+
Button,
|
|
4366
|
+
{
|
|
4367
|
+
type: "button",
|
|
4368
|
+
size: "icon-lg",
|
|
4369
|
+
variant: iconVariant,
|
|
4370
|
+
className: iconVariant === "secondary" ? "rounded-full bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_10%)] hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_16%)]" : "rounded-full",
|
|
4371
|
+
"aria-label": label,
|
|
4372
|
+
"aria-pressed": pressed,
|
|
4373
|
+
onClick
|
|
4374
|
+
}
|
|
4375
|
+
),
|
|
4376
|
+
children
|
|
4377
|
+
}
|
|
4378
|
+
),
|
|
4379
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: label })
|
|
4380
|
+
] })
|
|
4381
|
+
] });
|
|
4382
|
+
}
|
|
4383
|
+
var POLL_MS = 2e3;
|
|
4384
|
+
var PLAYOUT_MS = 250;
|
|
4385
|
+
var STUDIO_EXTRA_LAG_MS = 200;
|
|
4386
|
+
function useStudioCaptions({
|
|
4387
|
+
enabled,
|
|
4388
|
+
baseUrl,
|
|
4389
|
+
locale,
|
|
4390
|
+
streamStatus
|
|
4391
|
+
}) {
|
|
4392
|
+
const [playout, setPlayout] = useState(
|
|
4393
|
+
EMPTY_LIVE_CAPTION_PLAYOUT
|
|
4394
|
+
);
|
|
4395
|
+
const cuesRef = useRef([]);
|
|
4396
|
+
useEffect(() => {
|
|
4397
|
+
if (!enabled) {
|
|
4398
|
+
cuesRef.current = [];
|
|
4399
|
+
setPlayout(EMPTY_LIVE_CAPTION_PLAYOUT);
|
|
4400
|
+
return;
|
|
4401
|
+
}
|
|
4402
|
+
if (!baseUrl || !isStudioActiveStatus(streamStatus)) return;
|
|
4403
|
+
let cancelled = false;
|
|
4404
|
+
const url = liveCaptionsUrl(baseUrl, locale);
|
|
4405
|
+
const poll = async () => {
|
|
4406
|
+
const loaded = await loadLiveCaptions(url);
|
|
4407
|
+
if (cancelled || !loaded.ok) return;
|
|
4408
|
+
cuesRef.current = mergeLiveCaptionCues(cuesRef.current, loaded.cues);
|
|
4409
|
+
};
|
|
4410
|
+
void poll();
|
|
4411
|
+
const timer = window.setInterval(() => void poll(), POLL_MS);
|
|
4412
|
+
return () => {
|
|
4413
|
+
cancelled = true;
|
|
4414
|
+
window.clearInterval(timer);
|
|
4415
|
+
};
|
|
4416
|
+
}, [baseUrl, enabled, locale, streamStatus]);
|
|
4417
|
+
useEffect(() => {
|
|
4418
|
+
if (!enabled || !baseUrl || !isStudioActiveStatus(streamStatus)) {
|
|
4419
|
+
setPlayout(EMPTY_LIVE_CAPTION_PLAYOUT);
|
|
4420
|
+
return;
|
|
4421
|
+
}
|
|
4422
|
+
const tick2 = () => {
|
|
4423
|
+
setPlayout(
|
|
4424
|
+
(prev) => advanceLiveCaptionPlayout(
|
|
4425
|
+
prev,
|
|
4426
|
+
cuesRef.current,
|
|
4427
|
+
Date.now(),
|
|
4428
|
+
0,
|
|
4429
|
+
void 0,
|
|
4430
|
+
STUDIO_EXTRA_LAG_MS
|
|
4431
|
+
)
|
|
4432
|
+
);
|
|
4433
|
+
};
|
|
4434
|
+
tick2();
|
|
4435
|
+
const timer = window.setInterval(tick2, PLAYOUT_MS);
|
|
4436
|
+
return () => window.clearInterval(timer);
|
|
4437
|
+
}, [baseUrl, enabled, streamStatus]);
|
|
4438
|
+
return playout;
|
|
3787
4439
|
}
|
|
3788
4440
|
var STAGE_GRANT_VERIFY_MS = 4e3;
|
|
3789
4441
|
var STUDIO_CONNECT_WATCHDOG_MS = 12e3;
|
|
@@ -3802,7 +4454,10 @@ function StudioRoom({
|
|
|
3802
4454
|
initialCameraOn = true,
|
|
3803
4455
|
initialMuted = false,
|
|
3804
4456
|
initialDevices,
|
|
3805
|
-
appDownloadUrl
|
|
4457
|
+
appDownloadUrl,
|
|
4458
|
+
captionsBaseUrl,
|
|
4459
|
+
apiUrl,
|
|
4460
|
+
cdnBase
|
|
3806
4461
|
}) {
|
|
3807
4462
|
const [cameraOn, setCameraOn] = useState(initialCameraOn);
|
|
3808
4463
|
const [screenOn, setScreenOn] = useState(false);
|
|
@@ -3824,6 +4479,11 @@ function StudioRoom({
|
|
|
3824
4479
|
const [egressError, setEgressError] = useState(null);
|
|
3825
4480
|
const [shareGrant, setShareGrant] = useState("idle");
|
|
3826
4481
|
const [downloadAppOpen, setDownloadAppOpen] = useState(false);
|
|
4482
|
+
const [captionsOn, setCaptionsOn] = useState(false);
|
|
4483
|
+
const [captionPrefs, setCaptionPrefs] = useState(
|
|
4484
|
+
() => readStudioCaptionPrefs()
|
|
4485
|
+
);
|
|
4486
|
+
const captionHintShown = useRef(false);
|
|
3827
4487
|
const [connectTimedOut, setConnectTimedOut] = useState(false);
|
|
3828
4488
|
const [screenPermission, setScreenPermission] = useState(
|
|
3829
4489
|
() => meeting.screenSharePermission()
|
|
@@ -3849,6 +4509,19 @@ function StudioRoom({
|
|
|
3849
4509
|
const pip = useDocumentPictureInPicture();
|
|
3850
4510
|
const gateFromContext = useStudioWindowGate();
|
|
3851
4511
|
const { reportDuplicate } = windowGate ?? gateFromContext;
|
|
4512
|
+
const resolvedCaptionsBaseUrl = captionsBaseUrl ?? studioCaptionsBaseUrl(apiUrl, session.stream.id);
|
|
4513
|
+
const captionsAvailable = session.stream.captionsEnabled !== false;
|
|
4514
|
+
const captionsReady = isStudioActiveStatus(session.stream.status);
|
|
4515
|
+
const captionPlayout = useStudioCaptions({
|
|
4516
|
+
enabled: captionsOn && captionsAvailable,
|
|
4517
|
+
baseUrl: resolvedCaptionsBaseUrl,
|
|
4518
|
+
locale: captionPrefs.locale,
|
|
4519
|
+
streamStatus: session.stream.status
|
|
4520
|
+
});
|
|
4521
|
+
const effectBackgrounds = useMemo(
|
|
4522
|
+
() => defaultStudioEffectBackgrounds(cdnBase),
|
|
4523
|
+
[cdnBase]
|
|
4524
|
+
);
|
|
3852
4525
|
const onAir = isLiveStudioStatus(session.stream.status);
|
|
3853
4526
|
const active = isStudioActiveStatus(session.stream.status);
|
|
3854
4527
|
if (onAir && liveStartedAtRef.current === null) {
|
|
@@ -4533,6 +5206,10 @@ function StudioRoom({
|
|
|
4533
5206
|
labels,
|
|
4534
5207
|
settingsOpen,
|
|
4535
5208
|
statusLabel: connectionState === "connecting" ? labels.connecting : labels.waitingHost,
|
|
5209
|
+
captionPrefs,
|
|
5210
|
+
onCaptionPrefsChange: setCaptionPrefs,
|
|
5211
|
+
captionsAvailable,
|
|
5212
|
+
effectBackgrounds,
|
|
4536
5213
|
onToggleCamera,
|
|
4537
5214
|
onToggleMute: () => void onToggleMute(),
|
|
4538
5215
|
onOpenSettings: () => setSettingsOpen(true),
|
|
@@ -4640,27 +5317,37 @@ function StudioRoom({
|
|
|
4640
5317
|
}
|
|
4641
5318
|
) : null,
|
|
4642
5319
|
/* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col md:flex-row", children: [
|
|
4643
|
-
/* @__PURE__ */
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
5320
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex min-h-0 min-w-0 flex-1 flex-col", children: [
|
|
5321
|
+
/* @__PURE__ */ jsx(
|
|
5322
|
+
StudioStage,
|
|
5323
|
+
{
|
|
5324
|
+
participants: stageParticipants,
|
|
5325
|
+
meeting,
|
|
5326
|
+
reducedMotion,
|
|
5327
|
+
canModerate: session.role === "moderator",
|
|
5328
|
+
canManageStage,
|
|
5329
|
+
onAir,
|
|
5330
|
+
labels,
|
|
5331
|
+
onKick: (id) => void onKick(id),
|
|
5332
|
+
onMute: (id) => void onHostMedia(id, "audio"),
|
|
5333
|
+
onStopCamera: (id) => void onHostMedia(id, "video"),
|
|
5334
|
+
onStopScreen: (id) => void onHostMedia(id, "screen"),
|
|
5335
|
+
onToggleStage: (tile) => void onSetStage(tile, !isOnStage(tile.stageStatus)),
|
|
5336
|
+
onTogglePin: (tile) => void onTogglePin(tile),
|
|
5337
|
+
onToggleChat: (tile) => void onToggleChat(tile),
|
|
5338
|
+
canTakeOff: (id) => canTakeOffAir(participants, id, onAir),
|
|
5339
|
+
onOpenPeople: () => setSideTab("people")
|
|
5340
|
+
}
|
|
5341
|
+
),
|
|
5342
|
+
captionsOn ? /* @__PURE__ */ jsx(
|
|
5343
|
+
CaptionOverlay,
|
|
5344
|
+
{
|
|
5345
|
+
screen: captionPlayout.screen,
|
|
5346
|
+
className: "bottom-3",
|
|
5347
|
+
textClassName: captionSizeClass(captionPrefs.size)
|
|
5348
|
+
}
|
|
5349
|
+
) : null
|
|
5350
|
+
] }),
|
|
4664
5351
|
sideTab ? /* @__PURE__ */ jsx(
|
|
4665
5352
|
StudioSidePanel,
|
|
4666
5353
|
{
|
|
@@ -4773,6 +5460,12 @@ function StudioRoom({
|
|
|
4773
5460
|
stageEnabled,
|
|
4774
5461
|
canLeaveStage: selfId != null && canTakeOffAir(participants, selfId, onAir),
|
|
4775
5462
|
reducedMotion,
|
|
5463
|
+
captionsOn,
|
|
5464
|
+
captionsAvailable,
|
|
5465
|
+
captionsReady,
|
|
5466
|
+
captionPrefs,
|
|
5467
|
+
onCaptionPrefsChange: setCaptionPrefs,
|
|
5468
|
+
effectBackgrounds,
|
|
4776
5469
|
onToggleMute: () => void onToggleMute(),
|
|
4777
5470
|
onToggleCamera,
|
|
4778
5471
|
onToggleScreen,
|
|
@@ -4800,6 +5493,16 @@ function StudioRoom({
|
|
|
4800
5493
|
onCloseSettings: setSettingsOpen,
|
|
4801
5494
|
onOpenPip,
|
|
4802
5495
|
onClosePip: pip.close,
|
|
5496
|
+
onToggleCaptions: () => {
|
|
5497
|
+
if (!captionsAvailable) return;
|
|
5498
|
+
if (!captionsOn && !captionHintShown.current) {
|
|
5499
|
+
captionHintShown.current = true;
|
|
5500
|
+
toast.message(
|
|
5501
|
+
captionsReady ? labels.captionsDelayed : labels.captionsNotReady
|
|
5502
|
+
);
|
|
5503
|
+
}
|
|
5504
|
+
setCaptionsOn((current) => !current);
|
|
5505
|
+
},
|
|
4803
5506
|
onLeave: () => void onLeave()
|
|
4804
5507
|
}
|
|
4805
5508
|
),
|
|
@@ -5194,16 +5897,16 @@ function usePrefersReducedMotion() {
|
|
|
5194
5897
|
// src/studio/lib/guest-identity.ts
|
|
5195
5898
|
var NAME_KEY = "livo-studio-display-name";
|
|
5196
5899
|
var ID_KEY = "livo-studio-guest-id";
|
|
5197
|
-
function readStudioGuestIdentity(storage =
|
|
5900
|
+
function readStudioGuestIdentity(storage = defaultStorage4()) {
|
|
5198
5901
|
const guestId = storage?.getItem(ID_KEY)?.trim() || crypto.randomUUID();
|
|
5199
5902
|
storage?.setItem(ID_KEY, guestId);
|
|
5200
5903
|
const displayName = storage?.getItem(NAME_KEY)?.trim() ?? "";
|
|
5201
5904
|
return { displayName, guestId };
|
|
5202
5905
|
}
|
|
5203
|
-
function persistStudioGuestName(displayName, storage =
|
|
5906
|
+
function persistStudioGuestName(displayName, storage = defaultStorage4()) {
|
|
5204
5907
|
storage?.setItem(NAME_KEY, displayName.trim());
|
|
5205
5908
|
}
|
|
5206
|
-
function
|
|
5909
|
+
function defaultStorage4() {
|
|
5207
5910
|
if (typeof window === "undefined") return null;
|
|
5208
5911
|
try {
|
|
5209
5912
|
return window.localStorage;
|
|
@@ -5572,10 +6275,19 @@ function studioMeetingFromRtk(raw) {
|
|
|
5572
6275
|
if (typeof window !== "undefined") {
|
|
5573
6276
|
window.addEventListener("unhandledrejection", onUnhandledRejection);
|
|
5574
6277
|
}
|
|
6278
|
+
let videoEffects = readStudioVideoEffects();
|
|
6279
|
+
async function applyCurrentVideoEffects() {
|
|
6280
|
+
await applyStudioVideoEffects({
|
|
6281
|
+
self: meeting.self,
|
|
6282
|
+
meeting,
|
|
6283
|
+
config: videoEffects
|
|
6284
|
+
});
|
|
6285
|
+
}
|
|
5575
6286
|
async function republishCameraIfOn() {
|
|
5576
6287
|
if (meeting.self?.videoEnabled !== true) return;
|
|
5577
6288
|
await meeting.self.disableVideo?.();
|
|
5578
6289
|
await meeting.self.enableVideo?.();
|
|
6290
|
+
await applyCurrentVideoEffects();
|
|
5579
6291
|
}
|
|
5580
6292
|
async function acceptAllowedStageIfOffAir() {
|
|
5581
6293
|
if (currentStageStatus() !== "OFF_STAGE") return;
|
|
@@ -5763,6 +6475,7 @@ function studioMeetingFromRtk(raw) {
|
|
|
5763
6475
|
async join() {
|
|
5764
6476
|
await meeting.join?.();
|
|
5765
6477
|
await joinStageIfAllowed();
|
|
6478
|
+
await applyCurrentVideoEffects();
|
|
5766
6479
|
},
|
|
5767
6480
|
async leave() {
|
|
5768
6481
|
if (typeof window !== "undefined") {
|
|
@@ -5775,8 +6488,10 @@ function studioMeetingFromRtk(raw) {
|
|
|
5775
6488
|
else await meeting.self?.disableAudio?.();
|
|
5776
6489
|
},
|
|
5777
6490
|
async setVideoEnabled(enabled) {
|
|
5778
|
-
if (enabled)
|
|
5779
|
-
|
|
6491
|
+
if (enabled) {
|
|
6492
|
+
await meeting.self?.enableVideo?.();
|
|
6493
|
+
await applyCurrentVideoEffects();
|
|
6494
|
+
} else await meeting.self?.disableVideo?.();
|
|
5780
6495
|
},
|
|
5781
6496
|
async setScreenShareEnabled(enabled) {
|
|
5782
6497
|
if (enabled) await meeting.self?.enableScreenShare?.();
|
|
@@ -6160,6 +6875,14 @@ function studioMeetingFromRtk(raw) {
|
|
|
6160
6875
|
}
|
|
6161
6876
|
});
|
|
6162
6877
|
},
|
|
6878
|
+
async setVideoEffects(config) {
|
|
6879
|
+
videoEffects = config;
|
|
6880
|
+
persistStudioVideoEffects(config);
|
|
6881
|
+
await applyCurrentVideoEffects();
|
|
6882
|
+
},
|
|
6883
|
+
videoEffects() {
|
|
6884
|
+
return videoEffects;
|
|
6885
|
+
},
|
|
6163
6886
|
async setDisplayName(name) {
|
|
6164
6887
|
const trimmed = name.trim().slice(0, STUDIO_DISPLAY_NAME_MAX);
|
|
6165
6888
|
if (!trimmed) return;
|
|
@@ -6391,6 +7114,21 @@ async function getPublicStreamStatus(streamId, apiUrl = defaultApiUrl(), fetchFn
|
|
|
6391
7114
|
return null;
|
|
6392
7115
|
}
|
|
6393
7116
|
}
|
|
7117
|
+
async function getPublicStreamMeta(streamId, apiUrl = defaultApiUrl(), fetchFn = fetch) {
|
|
7118
|
+
try {
|
|
7119
|
+
const body = await publicJson(
|
|
7120
|
+
`${apiUrl.replace(/\/$/, "")}/public/streams/${encodeURIComponent(streamId)}`,
|
|
7121
|
+
{},
|
|
7122
|
+
fetchFn
|
|
7123
|
+
);
|
|
7124
|
+
return parsePublicStreamMeta(body);
|
|
7125
|
+
} catch (error) {
|
|
7126
|
+
if (error instanceof StudioApiError && (error.status === 404 || error.status === 410)) {
|
|
7127
|
+
return { status: "ended", captionsEnabled: false };
|
|
7128
|
+
}
|
|
7129
|
+
return null;
|
|
7130
|
+
}
|
|
7131
|
+
}
|
|
6394
7132
|
var POLL_INTERVAL_MS = 5e3;
|
|
6395
7133
|
function LivoHostStudio({
|
|
6396
7134
|
token,
|
|
@@ -6406,6 +7144,9 @@ function LivoHostStudio({
|
|
|
6406
7144
|
const reducedMotion = usePrefersReducedMotion();
|
|
6407
7145
|
const [session, setSession] = useState(null);
|
|
6408
7146
|
const [status, setStatus] = useState(null);
|
|
7147
|
+
const [captionsEnabled, setCaptionsEnabled] = useState(
|
|
7148
|
+
void 0
|
|
7149
|
+
);
|
|
6409
7150
|
const [error, setError] = useState(null);
|
|
6410
7151
|
const { meeting, error: meetingError } = useRealtimeKitMeeting(
|
|
6411
7152
|
session?.authToken ?? null
|
|
@@ -6454,6 +7195,20 @@ function LivoHostStudio({
|
|
|
6454
7195
|
window.clearInterval(id);
|
|
6455
7196
|
};
|
|
6456
7197
|
}, [client, status]);
|
|
7198
|
+
useEffect(() => {
|
|
7199
|
+
if (!session) return;
|
|
7200
|
+
let cancelled = false;
|
|
7201
|
+
const poll = async () => {
|
|
7202
|
+
const next = await getPublicStreamMeta(session.stream.id, apiUrl);
|
|
7203
|
+
if (!cancelled && next) setCaptionsEnabled(next.captionsEnabled);
|
|
7204
|
+
};
|
|
7205
|
+
void poll();
|
|
7206
|
+
const id = window.setInterval(() => void poll(), POLL_INTERVAL_MS);
|
|
7207
|
+
return () => {
|
|
7208
|
+
cancelled = true;
|
|
7209
|
+
window.clearInterval(id);
|
|
7210
|
+
};
|
|
7211
|
+
}, [apiUrl, session]);
|
|
6457
7212
|
const liveSession = useMemo(() => {
|
|
6458
7213
|
if (!session) return null;
|
|
6459
7214
|
if (!status) return session;
|
|
@@ -6461,10 +7216,11 @@ function LivoHostStudio({
|
|
|
6461
7216
|
...session,
|
|
6462
7217
|
stream: {
|
|
6463
7218
|
...session.stream,
|
|
6464
|
-
status
|
|
7219
|
+
status,
|
|
7220
|
+
captionsEnabled: captionsEnabled ?? session.stream.captionsEnabled
|
|
6465
7221
|
}
|
|
6466
7222
|
};
|
|
6467
|
-
}, [session, status]);
|
|
7223
|
+
}, [captionsEnabled, session, status]);
|
|
6468
7224
|
if (error || meetingError) {
|
|
6469
7225
|
return /* @__PURE__ */ jsx("p", { className: "text-destructive p-6", role: "alert", children: error ?? meetingError });
|
|
6470
7226
|
}
|
|
@@ -6482,7 +7238,8 @@ function LivoHostStudio({
|
|
|
6482
7238
|
embed,
|
|
6483
7239
|
chrome,
|
|
6484
7240
|
playerOrigin,
|
|
6485
|
-
appDownloadUrl
|
|
7241
|
+
appDownloadUrl,
|
|
7242
|
+
apiUrl
|
|
6486
7243
|
}
|
|
6487
7244
|
);
|
|
6488
7245
|
}
|
|
@@ -6501,6 +7258,9 @@ function LivoGuestStudio({
|
|
|
6501
7258
|
const reducedMotion = usePrefersReducedMotion();
|
|
6502
7259
|
const [session, setSession] = useState(null);
|
|
6503
7260
|
const [status, setStatus] = useState(null);
|
|
7261
|
+
const [captionsEnabled, setCaptionsEnabled] = useState(
|
|
7262
|
+
void 0
|
|
7263
|
+
);
|
|
6504
7264
|
const [error, setError] = useState(null);
|
|
6505
7265
|
const [displayName, setDisplayName] = useState("");
|
|
6506
7266
|
const [guestId, setGuestId] = useState("");
|
|
@@ -6604,8 +7364,11 @@ function LivoGuestStudio({
|
|
|
6604
7364
|
if (current === "ended" || current === "error") return;
|
|
6605
7365
|
let cancelled = false;
|
|
6606
7366
|
const poll = async () => {
|
|
6607
|
-
const next = await
|
|
6608
|
-
if (!cancelled && next)
|
|
7367
|
+
const next = await getPublicStreamMeta(session.stream.id, apiUrl);
|
|
7368
|
+
if (!cancelled && next) {
|
|
7369
|
+
setStatus(next.status);
|
|
7370
|
+
setCaptionsEnabled(next.captionsEnabled);
|
|
7371
|
+
}
|
|
6609
7372
|
};
|
|
6610
7373
|
void poll();
|
|
6611
7374
|
const intervalMs = current === "public" ? POLL_INTERVAL_MS2 : PREVIEW_POLL_INTERVAL_MS;
|
|
@@ -6620,9 +7383,13 @@ function LivoGuestStudio({
|
|
|
6620
7383
|
if (!status) return session;
|
|
6621
7384
|
return {
|
|
6622
7385
|
...session,
|
|
6623
|
-
stream: {
|
|
7386
|
+
stream: {
|
|
7387
|
+
...session.stream,
|
|
7388
|
+
status,
|
|
7389
|
+
captionsEnabled: captionsEnabled ?? session.stream.captionsEnabled
|
|
7390
|
+
}
|
|
6624
7391
|
};
|
|
6625
|
-
}, [session, status]);
|
|
7392
|
+
}, [captionsEnabled, session, status]);
|
|
6626
7393
|
useEffect(() => {
|
|
6627
7394
|
if (retryAfterMs == null) return;
|
|
6628
7395
|
const id = window.setTimeout(() => {
|
|
@@ -6683,7 +7450,8 @@ function LivoGuestStudio({
|
|
|
6683
7450
|
initialCameraOn: cameraOn,
|
|
6684
7451
|
initialMuted: muted,
|
|
6685
7452
|
initialDevices: deviceSelection,
|
|
6686
|
-
appDownloadUrl
|
|
7453
|
+
appDownloadUrl,
|
|
7454
|
+
apiUrl
|
|
6687
7455
|
}
|
|
6688
7456
|
);
|
|
6689
7457
|
}
|
|
@@ -6859,10 +7627,40 @@ function studioRoomLabels(t) {
|
|
|
6859
7627
|
chatPin: t("chatPin"),
|
|
6860
7628
|
chatUnpin: t("chatUnpin"),
|
|
6861
7629
|
chatSave: t("chatSave"),
|
|
6862
|
-
waitingSkeleton: t("waitingSkeleton")
|
|
7630
|
+
waitingSkeleton: t("waitingSkeleton"),
|
|
7631
|
+
more: t("more"),
|
|
7632
|
+
captions: t("captions"),
|
|
7633
|
+
captionsOn: t("captionsOn"),
|
|
7634
|
+
captionsOff: t("captionsOff"),
|
|
7635
|
+
captionsNotReady: t("captionsNotReady"),
|
|
7636
|
+
captionsDelayed: t("captionsDelayed"),
|
|
7637
|
+
captionsLanguage: t("captionsLanguage"),
|
|
7638
|
+
captionsSize: t("captionsSize"),
|
|
7639
|
+
captionsSizeS: t("captionsSizeS"),
|
|
7640
|
+
captionsSizeM: t("captionsSizeM"),
|
|
7641
|
+
captionsSizeL: t("captionsSizeL"),
|
|
7642
|
+
settingsGeneral: t("settingsGeneral"),
|
|
7643
|
+
settingsAudio: t("settingsAudio"),
|
|
7644
|
+
settingsVideo: t("settingsVideo"),
|
|
7645
|
+
settingsEffects: t("settingsEffects"),
|
|
7646
|
+
settingsSubtitles: t("settingsSubtitles"),
|
|
7647
|
+
effectNone: t("effectNone"),
|
|
7648
|
+
effectBlur: t("effectBlur"),
|
|
7649
|
+
effectBlurStrong: t("effectBlurStrong"),
|
|
7650
|
+
effectBackgrounds: t("effectBackgrounds"),
|
|
7651
|
+
effectFilters: t("effectFilters"),
|
|
7652
|
+
effectFilterNone: t("effectFilterNone"),
|
|
7653
|
+
effectFilterGrayscale: t("effectFilterGrayscale"),
|
|
7654
|
+
effectFilterSepia: t("effectFilterSepia"),
|
|
7655
|
+
effectFilterWarm: t("effectFilterWarm"),
|
|
7656
|
+
effectFilterCool: t("effectFilterCool"),
|
|
7657
|
+
effectFilterBright: t("effectFilterBright"),
|
|
7658
|
+
localeEn: t("localeEn"),
|
|
7659
|
+
localeEs: t("localeEs"),
|
|
7660
|
+
localePt: t("localePt")
|
|
6863
7661
|
};
|
|
6864
7662
|
}
|
|
6865
7663
|
|
|
6866
|
-
export { LivoGuestStudio, LivoHostStudio, StudioApiError, StudioControlClient, StudioControlError, StudioGuestLobby, StudioRoom, StudioSkeleton, StudioWindowGate, StudioWindowGateContext, applyEmbedTheme, controlActionsFromClient, createStudioControlClient, decodeEmbedTheme, getPublicStreamStatus, getStudioJoinStatus, joinStudioGuest, redeemStudioHost, studioErrorCode, studioRoomLabels, usePrefersReducedMotion, useRealtimeKitMeeting, useStudioWindowGate };
|
|
7664
|
+
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 };
|
|
6867
7665
|
//# sourceMappingURL=index.js.map
|
|
6868
7666
|
//# sourceMappingURL=index.js.map
|