@livo-tv/sdk 1.5.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 +1038 -259
- 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 +1039 -261
- 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,204 +4062,360 @@ 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
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
4078
|
+
async function onDeviceChange(kind, deviceId) {
|
|
4079
|
+
persistStudioDeviceSelection(kind, deviceId);
|
|
4080
|
+
try {
|
|
4081
|
+
await meeting.setDevice(kind, deviceId);
|
|
4082
|
+
} catch {
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
const showStageModerator = session.role === "moderator" && canManageStage;
|
|
4086
|
+
const showStageGuest = session.role === "guest" && stageEnabled;
|
|
4087
|
+
const captionsLabel = !captionsAvailable ? labels.captions : !captionsReady ? labels.captionsNotReady : captionsOn ? labels.captionsOn : labels.captionsOff;
|
|
4088
|
+
return /* @__PURE__ */ jsx("footer", { className: "flex flex-wrap items-center justify-center gap-3 border-t px-3 py-3 sm:justify-between", children: /* @__PURE__ */ jsxs(TooltipProvider, { children: [
|
|
4089
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
|
|
4090
|
+
showMediaControls ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4091
|
+
/* @__PURE__ */ jsx(
|
|
4092
|
+
MediaSplit,
|
|
4093
|
+
{
|
|
4094
|
+
label: muted ? labels.unmute : labels.mute,
|
|
4095
|
+
pressed: muted,
|
|
4096
|
+
tone: muted ? "danger" : "default",
|
|
4097
|
+
onClick: onToggleMute,
|
|
4098
|
+
pickerLabel: labels.micDevice,
|
|
4099
|
+
options: devices.audio,
|
|
4100
|
+
selected: effective.audio,
|
|
4101
|
+
onSelect: (id) => void onDeviceChange("audio", id),
|
|
4102
|
+
children: muted ? /* @__PURE__ */ jsx(MicOff, {}) : /* @__PURE__ */ jsx(Mic, {})
|
|
4103
|
+
}
|
|
4104
|
+
),
|
|
4105
|
+
/* @__PURE__ */ jsx(
|
|
4106
|
+
MediaSplit,
|
|
4107
|
+
{
|
|
4108
|
+
label: labels.camera,
|
|
4109
|
+
pressed: cameraOn,
|
|
4110
|
+
onClick: onToggleCamera,
|
|
4111
|
+
pickerLabel: labels.cameraDevice,
|
|
4112
|
+
options: devices.video,
|
|
4113
|
+
selected: effective.video,
|
|
4114
|
+
onSelect: (id) => void onDeviceChange("video", id),
|
|
4115
|
+
children: cameraOn ? /* @__PURE__ */ jsx(Video, {}) : /* @__PURE__ */ jsx(VideoOff, {})
|
|
4116
|
+
}
|
|
4117
|
+
)
|
|
4118
|
+
] }) : null,
|
|
4119
|
+
showStageModerator ? /* @__PURE__ */ jsx(
|
|
3601
4120
|
ControlIcon,
|
|
3602
4121
|
{
|
|
3603
|
-
label:
|
|
3604
|
-
pressed:
|
|
3605
|
-
|
|
3606
|
-
onClick:
|
|
3607
|
-
|
|
4122
|
+
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : labels.goOnAir,
|
|
4123
|
+
pressed: selfStageStatus !== "ON_STAGE",
|
|
4124
|
+
disabled: selfStageStatus === "ON_STAGE" && !canLeaveStage,
|
|
4125
|
+
onClick: () => {
|
|
4126
|
+
if (selfStageStatus === "ON_STAGE") {
|
|
4127
|
+
if (!canLeaveStage) {
|
|
4128
|
+
toast.message(labels.offAirLastVisible);
|
|
4129
|
+
return;
|
|
4130
|
+
}
|
|
4131
|
+
onLeaveStage();
|
|
4132
|
+
return;
|
|
4133
|
+
}
|
|
4134
|
+
onAskStage();
|
|
4135
|
+
},
|
|
4136
|
+
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
|
|
3608
4137
|
}
|
|
3609
|
-
),
|
|
3610
|
-
/* @__PURE__ */ jsx(
|
|
4138
|
+
) : null,
|
|
4139
|
+
showStageGuest ? /* @__PURE__ */ jsx(
|
|
3611
4140
|
ControlIcon,
|
|
3612
4141
|
{
|
|
3613
|
-
label: labels.
|
|
3614
|
-
pressed:
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : labels.goOnAir,
|
|
3624
|
-
pressed: selfStageStatus !== "ON_STAGE",
|
|
3625
|
-
disabled: selfStageStatus === "ON_STAGE" && !canLeaveStage,
|
|
3626
|
-
onClick: () => {
|
|
3627
|
-
if (selfStageStatus === "ON_STAGE") {
|
|
3628
|
-
if (!canLeaveStage) {
|
|
3629
|
-
toast.message(labels.offAirLastVisible);
|
|
4142
|
+
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
|
|
4143
|
+
pressed: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
|
|
4144
|
+
disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" || selfStageStatus === "ON_STAGE" && !canLeaveStage,
|
|
4145
|
+
onClick: () => {
|
|
4146
|
+
if (selfStageStatus === "ON_STAGE") {
|
|
4147
|
+
if (!canLeaveStage) {
|
|
4148
|
+
toast.message(labels.offAirLastVisible);
|
|
4149
|
+
return;
|
|
4150
|
+
}
|
|
4151
|
+
onLeaveStage();
|
|
3630
4152
|
return;
|
|
3631
4153
|
}
|
|
3632
|
-
|
|
3633
|
-
|
|
4154
|
+
onAskStage();
|
|
4155
|
+
},
|
|
4156
|
+
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
|
|
4157
|
+
}
|
|
4158
|
+
) : null,
|
|
4159
|
+
showScreenControl ? canShareFreely ? /* @__PURE__ */ jsx(
|
|
4160
|
+
ControlIcon,
|
|
4161
|
+
{
|
|
4162
|
+
label: labels.screen,
|
|
4163
|
+
pressed: screenOn,
|
|
4164
|
+
disabled: screenPending || !screenAvailable && !screenOn,
|
|
4165
|
+
pending: screenPending,
|
|
4166
|
+
onClick: onToggleScreen,
|
|
4167
|
+
children: /* @__PURE__ */ jsx(MonitorUp, {})
|
|
4168
|
+
}
|
|
4169
|
+
) : shareGrant === "requested" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4170
|
+
/* @__PURE__ */ jsx(
|
|
4171
|
+
ControlIcon,
|
|
4172
|
+
{
|
|
4173
|
+
label: labels.shareRequested,
|
|
4174
|
+
pressed: true,
|
|
4175
|
+
disabled: true,
|
|
4176
|
+
onClick: () => void 0,
|
|
4177
|
+
children: /* @__PURE__ */ jsx(MonitorUp, {})
|
|
3634
4178
|
}
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
{
|
|
3643
|
-
label: selfStageStatus === "ON_STAGE" ? labels.goOffAir : selfStageStatus === "REQUESTED_TO_JOIN_STAGE" ? labels.stageRequested : labels.askToGoOnAir,
|
|
3644
|
-
pressed: selfStageStatus === "REQUESTED_TO_JOIN_STAGE",
|
|
3645
|
-
disabled: selfStageStatus === "REQUESTED_TO_JOIN_STAGE" || selfStageStatus === "ON_STAGE" && !canLeaveStage,
|
|
3646
|
-
onClick: () => {
|
|
3647
|
-
if (selfStageStatus === "ON_STAGE") {
|
|
3648
|
-
if (!canLeaveStage) {
|
|
3649
|
-
toast.message(labels.offAirLastVisible);
|
|
3650
|
-
return;
|
|
3651
|
-
}
|
|
3652
|
-
onLeaveStage();
|
|
3653
|
-
return;
|
|
4179
|
+
),
|
|
4180
|
+
/* @__PURE__ */ jsx(
|
|
4181
|
+
ControlIcon,
|
|
4182
|
+
{
|
|
4183
|
+
label: labels.cancelShareRequest,
|
|
4184
|
+
onClick: onCancelShareRequest,
|
|
4185
|
+
children: /* @__PURE__ */ jsx(X, {})
|
|
3654
4186
|
}
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
children: selfStageStatus === "ON_STAGE" ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
|
|
3658
|
-
}
|
|
3659
|
-
) : null,
|
|
3660
|
-
showScreenControl ? canShareFreely ? /* @__PURE__ */ jsx(
|
|
3661
|
-
ControlIcon,
|
|
3662
|
-
{
|
|
3663
|
-
label: labels.screen,
|
|
3664
|
-
pressed: screenOn,
|
|
3665
|
-
disabled: screenPending || !screenAvailable && !screenOn,
|
|
3666
|
-
pending: screenPending,
|
|
3667
|
-
onClick: onToggleScreen,
|
|
3668
|
-
children: /* @__PURE__ */ jsx(MonitorUp, {})
|
|
3669
|
-
}
|
|
3670
|
-
) : shareGrant === "requested" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4187
|
+
)
|
|
4188
|
+
] }) : /* @__PURE__ */ jsx(ControlIcon, { label: labels.askToShare, onClick: onAskToShare, children: /* @__PURE__ */ jsx(MonitorUp, {}) }) : null,
|
|
3671
4189
|
/* @__PURE__ */ jsx(
|
|
3672
4190
|
ControlIcon,
|
|
3673
4191
|
{
|
|
3674
|
-
label:
|
|
3675
|
-
pressed:
|
|
3676
|
-
disabled:
|
|
3677
|
-
onClick:
|
|
3678
|
-
children: /* @__PURE__ */ jsx(
|
|
4192
|
+
label: captionsLabel,
|
|
4193
|
+
pressed: captionsOn,
|
|
4194
|
+
disabled: !captionsAvailable,
|
|
4195
|
+
onClick: onToggleCaptions,
|
|
4196
|
+
children: /* @__PURE__ */ jsx(Captions, {})
|
|
3679
4197
|
}
|
|
3680
4198
|
),
|
|
4199
|
+
/* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
4200
|
+
/* @__PURE__ */ jsx(
|
|
4201
|
+
DropdownMenuTrigger,
|
|
4202
|
+
{
|
|
4203
|
+
render: /* @__PURE__ */ jsx(
|
|
4204
|
+
Button,
|
|
4205
|
+
{
|
|
4206
|
+
type: "button",
|
|
4207
|
+
size: "icon-lg",
|
|
4208
|
+
variant: "secondary",
|
|
4209
|
+
"aria-label": labels.more
|
|
4210
|
+
}
|
|
4211
|
+
),
|
|
4212
|
+
children: /* @__PURE__ */ jsx(MoreVertical, {})
|
|
4213
|
+
}
|
|
4214
|
+
),
|
|
4215
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "center", className: "min-w-44", children: [
|
|
4216
|
+
fullscreenSupported ? /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: () => void enterFullscreen(), children: [
|
|
4217
|
+
/* @__PURE__ */ jsx(Maximize, {}),
|
|
4218
|
+
labels.fullscreen
|
|
4219
|
+
] }) : null,
|
|
4220
|
+
pipSupported ? /* @__PURE__ */ jsxs(
|
|
4221
|
+
DropdownMenuItem,
|
|
4222
|
+
{
|
|
4223
|
+
onClick: () => {
|
|
4224
|
+
if (pipOpen) onClosePip();
|
|
4225
|
+
else onOpenPip();
|
|
4226
|
+
},
|
|
4227
|
+
children: [
|
|
4228
|
+
/* @__PURE__ */ jsx(PictureInPicture2, {}),
|
|
4229
|
+
pipOpen ? labels.closePip : labels.openPip
|
|
4230
|
+
]
|
|
4231
|
+
}
|
|
4232
|
+
) : null,
|
|
4233
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: onOpenSettings, children: [
|
|
4234
|
+
/* @__PURE__ */ jsx(Settings, {}),
|
|
4235
|
+
labels.settings
|
|
4236
|
+
] })
|
|
4237
|
+
] })
|
|
4238
|
+
] })
|
|
4239
|
+
] }),
|
|
4240
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
|
|
3681
4241
|
/* @__PURE__ */ jsx(
|
|
3682
4242
|
ControlIcon,
|
|
3683
4243
|
{
|
|
3684
|
-
label: labels.
|
|
3685
|
-
|
|
3686
|
-
|
|
4244
|
+
label: unreadChat > 0 ? `${labels.chat} (${unreadChat})` : labels.chat,
|
|
4245
|
+
pressed: chatOpen,
|
|
4246
|
+
badge: unreadChat,
|
|
4247
|
+
onClick: onToggleChat,
|
|
4248
|
+
children: /* @__PURE__ */ jsx(MessageSquare, {})
|
|
3687
4249
|
}
|
|
3688
|
-
)
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
4250
|
+
),
|
|
4251
|
+
session.role === "moderator" ? /* @__PURE__ */ jsx(
|
|
4252
|
+
ControlIcon,
|
|
4253
|
+
{
|
|
4254
|
+
label: peopleBadge > 0 ? `${labels.people} (${peopleBadge})` : labels.people,
|
|
4255
|
+
pressed: peopleOpen,
|
|
4256
|
+
badge: peopleBadge,
|
|
4257
|
+
onClick: onTogglePeople,
|
|
4258
|
+
children: /* @__PURE__ */ jsx(Users, {})
|
|
4259
|
+
}
|
|
4260
|
+
) : null,
|
|
4261
|
+
/* @__PURE__ */ jsx(
|
|
4262
|
+
ControlIcon,
|
|
4263
|
+
{
|
|
4264
|
+
label: labels.settings,
|
|
4265
|
+
pressed: settingsOpen,
|
|
4266
|
+
onClick: onOpenSettings,
|
|
4267
|
+
children: /* @__PURE__ */ jsx(Settings, {})
|
|
4268
|
+
}
|
|
4269
|
+
),
|
|
4270
|
+
/* @__PURE__ */ jsx(
|
|
4271
|
+
StudioSettingsDialog,
|
|
4272
|
+
{
|
|
4273
|
+
open: settingsOpen,
|
|
4274
|
+
onOpenChange: onCloseSettings,
|
|
4275
|
+
meeting,
|
|
4276
|
+
reducedMotion,
|
|
4277
|
+
cameraOn,
|
|
4278
|
+
labels,
|
|
4279
|
+
captionPrefs,
|
|
4280
|
+
onCaptionPrefsChange,
|
|
4281
|
+
captionsAvailable,
|
|
4282
|
+
effectBackgrounds
|
|
4283
|
+
}
|
|
4284
|
+
),
|
|
4285
|
+
session.role === "guest" ? /* @__PURE__ */ jsx(
|
|
4286
|
+
ConfirmAction,
|
|
4287
|
+
{
|
|
4288
|
+
triggerLabel: labels.leave,
|
|
4289
|
+
title: labels.leaveTitle,
|
|
4290
|
+
description: labels.leaveDescription,
|
|
4291
|
+
confirmLabel: labels.leaveConfirm,
|
|
4292
|
+
pending,
|
|
4293
|
+
onConfirm: onLeave,
|
|
4294
|
+
trigger: /* @__PURE__ */ jsx(
|
|
4295
|
+
Button,
|
|
4296
|
+
{
|
|
4297
|
+
type: "button",
|
|
4298
|
+
variant: "destructive",
|
|
4299
|
+
size: "icon-lg",
|
|
4300
|
+
disabled: pending,
|
|
4301
|
+
"aria-label": labels.leave,
|
|
4302
|
+
children: /* @__PURE__ */ jsx(PhoneOff, {})
|
|
4303
|
+
}
|
|
4304
|
+
)
|
|
4305
|
+
}
|
|
4306
|
+
) : null
|
|
4307
|
+
] })
|
|
4308
|
+
] }) });
|
|
4309
|
+
}
|
|
4310
|
+
function MediaSplit({
|
|
4311
|
+
label,
|
|
4312
|
+
pressed,
|
|
4313
|
+
tone = "default",
|
|
4314
|
+
onClick,
|
|
4315
|
+
pickerLabel,
|
|
4316
|
+
options,
|
|
4317
|
+
selected,
|
|
4318
|
+
onSelect,
|
|
4319
|
+
children
|
|
4320
|
+
}) {
|
|
4321
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
|
|
3730
4322
|
/* @__PURE__ */ jsx(
|
|
3731
4323
|
ControlIcon,
|
|
3732
4324
|
{
|
|
3733
|
-
label
|
|
3734
|
-
pressed
|
|
3735
|
-
|
|
3736
|
-
|
|
4325
|
+
label,
|
|
4326
|
+
pressed,
|
|
4327
|
+
tone,
|
|
4328
|
+
onClick,
|
|
4329
|
+
children
|
|
3737
4330
|
}
|
|
3738
4331
|
),
|
|
3739
|
-
/* @__PURE__ */
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
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
|
|
4332
|
+
options.length > 0 ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
4333
|
+
/* @__PURE__ */ jsx(
|
|
4334
|
+
DropdownMenuTrigger,
|
|
4335
|
+
{
|
|
4336
|
+
render: /* @__PURE__ */ jsx(
|
|
4337
|
+
Button,
|
|
4338
|
+
{
|
|
4339
|
+
type: "button",
|
|
4340
|
+
size: "icon-xs",
|
|
4341
|
+
variant: "secondary",
|
|
4342
|
+
className: "-ml-1",
|
|
4343
|
+
"aria-label": pickerLabel
|
|
4344
|
+
}
|
|
4345
|
+
),
|
|
4346
|
+
children: /* @__PURE__ */ jsx(ChevronUp, { className: "size-3.5" })
|
|
3761
4347
|
}
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
4348
|
+
),
|
|
4349
|
+
/* @__PURE__ */ jsx(DropdownMenuContent, { align: "start", side: "top", className: "min-w-52", children: options.map((option) => /* @__PURE__ */ jsxs(
|
|
4350
|
+
DropdownMenuItem,
|
|
4351
|
+
{
|
|
4352
|
+
onClick: () => onSelect(option.deviceId),
|
|
4353
|
+
children: [
|
|
4354
|
+
option.deviceId === selected ? "\u2713 " : "",
|
|
4355
|
+
option.label
|
|
4356
|
+
]
|
|
4357
|
+
},
|
|
4358
|
+
option.deviceId
|
|
4359
|
+
)) })
|
|
4360
|
+
] }) : null
|
|
4361
|
+
] });
|
|
4362
|
+
}
|
|
4363
|
+
var POLL_MS = 2e3;
|
|
4364
|
+
var PLAYOUT_MS = 250;
|
|
4365
|
+
var STUDIO_EXTRA_LAG_MS = 200;
|
|
4366
|
+
function useStudioCaptions({
|
|
4367
|
+
enabled,
|
|
4368
|
+
baseUrl,
|
|
4369
|
+
locale,
|
|
4370
|
+
streamStatus
|
|
4371
|
+
}) {
|
|
4372
|
+
const [playout, setPlayout] = useState(
|
|
4373
|
+
EMPTY_LIVE_CAPTION_PLAYOUT
|
|
4374
|
+
);
|
|
4375
|
+
const cuesRef = useRef([]);
|
|
4376
|
+
useEffect(() => {
|
|
4377
|
+
if (!enabled) {
|
|
4378
|
+
cuesRef.current = [];
|
|
4379
|
+
setPlayout(EMPTY_LIVE_CAPTION_PLAYOUT);
|
|
4380
|
+
return;
|
|
4381
|
+
}
|
|
4382
|
+
if (!baseUrl || !isStudioActiveStatus(streamStatus)) return;
|
|
4383
|
+
let cancelled = false;
|
|
4384
|
+
const url = liveCaptionsUrl(baseUrl, locale);
|
|
4385
|
+
const poll = async () => {
|
|
4386
|
+
const loaded = await loadLiveCaptions(url);
|
|
4387
|
+
if (cancelled || !loaded.ok) return;
|
|
4388
|
+
cuesRef.current = mergeLiveCaptionCues(cuesRef.current, loaded.cues);
|
|
4389
|
+
};
|
|
4390
|
+
void poll();
|
|
4391
|
+
const timer = window.setInterval(() => void poll(), POLL_MS);
|
|
4392
|
+
return () => {
|
|
4393
|
+
cancelled = true;
|
|
4394
|
+
window.clearInterval(timer);
|
|
4395
|
+
};
|
|
4396
|
+
}, [baseUrl, enabled, locale, streamStatus]);
|
|
4397
|
+
useEffect(() => {
|
|
4398
|
+
if (!enabled || !baseUrl || !isStudioActiveStatus(streamStatus)) {
|
|
4399
|
+
setPlayout(EMPTY_LIVE_CAPTION_PLAYOUT);
|
|
4400
|
+
return;
|
|
4401
|
+
}
|
|
4402
|
+
const tick2 = () => {
|
|
4403
|
+
setPlayout(
|
|
4404
|
+
(prev) => advanceLiveCaptionPlayout(
|
|
4405
|
+
prev,
|
|
4406
|
+
cuesRef.current,
|
|
4407
|
+
Date.now(),
|
|
4408
|
+
0,
|
|
4409
|
+
void 0,
|
|
4410
|
+
STUDIO_EXTRA_LAG_MS
|
|
3783
4411
|
)
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
4412
|
+
);
|
|
4413
|
+
};
|
|
4414
|
+
tick2();
|
|
4415
|
+
const timer = window.setInterval(tick2, PLAYOUT_MS);
|
|
4416
|
+
return () => window.clearInterval(timer);
|
|
4417
|
+
}, [baseUrl, enabled, streamStatus]);
|
|
4418
|
+
return playout;
|
|
3787
4419
|
}
|
|
3788
4420
|
var STAGE_GRANT_VERIFY_MS = 4e3;
|
|
3789
4421
|
var STUDIO_CONNECT_WATCHDOG_MS = 12e3;
|
|
@@ -3802,7 +4434,10 @@ function StudioRoom({
|
|
|
3802
4434
|
initialCameraOn = true,
|
|
3803
4435
|
initialMuted = false,
|
|
3804
4436
|
initialDevices,
|
|
3805
|
-
appDownloadUrl
|
|
4437
|
+
appDownloadUrl,
|
|
4438
|
+
captionsBaseUrl,
|
|
4439
|
+
apiUrl,
|
|
4440
|
+
cdnBase
|
|
3806
4441
|
}) {
|
|
3807
4442
|
const [cameraOn, setCameraOn] = useState(initialCameraOn);
|
|
3808
4443
|
const [screenOn, setScreenOn] = useState(false);
|
|
@@ -3824,6 +4459,11 @@ function StudioRoom({
|
|
|
3824
4459
|
const [egressError, setEgressError] = useState(null);
|
|
3825
4460
|
const [shareGrant, setShareGrant] = useState("idle");
|
|
3826
4461
|
const [downloadAppOpen, setDownloadAppOpen] = useState(false);
|
|
4462
|
+
const [captionsOn, setCaptionsOn] = useState(false);
|
|
4463
|
+
const [captionPrefs, setCaptionPrefs] = useState(
|
|
4464
|
+
() => readStudioCaptionPrefs()
|
|
4465
|
+
);
|
|
4466
|
+
const captionHintShown = useRef(false);
|
|
3827
4467
|
const [connectTimedOut, setConnectTimedOut] = useState(false);
|
|
3828
4468
|
const [screenPermission, setScreenPermission] = useState(
|
|
3829
4469
|
() => meeting.screenSharePermission()
|
|
@@ -3849,6 +4489,19 @@ function StudioRoom({
|
|
|
3849
4489
|
const pip = useDocumentPictureInPicture();
|
|
3850
4490
|
const gateFromContext = useStudioWindowGate();
|
|
3851
4491
|
const { reportDuplicate } = windowGate ?? gateFromContext;
|
|
4492
|
+
const resolvedCaptionsBaseUrl = captionsBaseUrl ?? studioCaptionsBaseUrl(apiUrl, session.stream.id);
|
|
4493
|
+
const captionsAvailable = session.stream.captionsEnabled !== false;
|
|
4494
|
+
const captionsReady = isStudioActiveStatus(session.stream.status);
|
|
4495
|
+
const captionPlayout = useStudioCaptions({
|
|
4496
|
+
enabled: captionsOn && captionsAvailable,
|
|
4497
|
+
baseUrl: resolvedCaptionsBaseUrl,
|
|
4498
|
+
locale: captionPrefs.locale,
|
|
4499
|
+
streamStatus: session.stream.status
|
|
4500
|
+
});
|
|
4501
|
+
const effectBackgrounds = useMemo(
|
|
4502
|
+
() => defaultStudioEffectBackgrounds(cdnBase),
|
|
4503
|
+
[cdnBase]
|
|
4504
|
+
);
|
|
3852
4505
|
const onAir = isLiveStudioStatus(session.stream.status);
|
|
3853
4506
|
const active = isStudioActiveStatus(session.stream.status);
|
|
3854
4507
|
if (onAir && liveStartedAtRef.current === null) {
|
|
@@ -4533,6 +5186,10 @@ function StudioRoom({
|
|
|
4533
5186
|
labels,
|
|
4534
5187
|
settingsOpen,
|
|
4535
5188
|
statusLabel: connectionState === "connecting" ? labels.connecting : labels.waitingHost,
|
|
5189
|
+
captionPrefs,
|
|
5190
|
+
onCaptionPrefsChange: setCaptionPrefs,
|
|
5191
|
+
captionsAvailable,
|
|
5192
|
+
effectBackgrounds,
|
|
4536
5193
|
onToggleCamera,
|
|
4537
5194
|
onToggleMute: () => void onToggleMute(),
|
|
4538
5195
|
onOpenSettings: () => setSettingsOpen(true),
|
|
@@ -4640,27 +5297,37 @@ function StudioRoom({
|
|
|
4640
5297
|
}
|
|
4641
5298
|
) : null,
|
|
4642
5299
|
/* @__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
|
-
|
|
5300
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex min-h-0 min-w-0 flex-1 flex-col", children: [
|
|
5301
|
+
/* @__PURE__ */ jsx(
|
|
5302
|
+
StudioStage,
|
|
5303
|
+
{
|
|
5304
|
+
participants: stageParticipants,
|
|
5305
|
+
meeting,
|
|
5306
|
+
reducedMotion,
|
|
5307
|
+
canModerate: session.role === "moderator",
|
|
5308
|
+
canManageStage,
|
|
5309
|
+
onAir,
|
|
5310
|
+
labels,
|
|
5311
|
+
onKick: (id) => void onKick(id),
|
|
5312
|
+
onMute: (id) => void onHostMedia(id, "audio"),
|
|
5313
|
+
onStopCamera: (id) => void onHostMedia(id, "video"),
|
|
5314
|
+
onStopScreen: (id) => void onHostMedia(id, "screen"),
|
|
5315
|
+
onToggleStage: (tile) => void onSetStage(tile, !isOnStage(tile.stageStatus)),
|
|
5316
|
+
onTogglePin: (tile) => void onTogglePin(tile),
|
|
5317
|
+
onToggleChat: (tile) => void onToggleChat(tile),
|
|
5318
|
+
canTakeOff: (id) => canTakeOffAir(participants, id, onAir),
|
|
5319
|
+
onOpenPeople: () => setSideTab("people")
|
|
5320
|
+
}
|
|
5321
|
+
),
|
|
5322
|
+
captionsOn ? /* @__PURE__ */ jsx(
|
|
5323
|
+
CaptionOverlay,
|
|
5324
|
+
{
|
|
5325
|
+
screen: captionPlayout.screen,
|
|
5326
|
+
className: "bottom-3",
|
|
5327
|
+
textClassName: captionSizeClass(captionPrefs.size)
|
|
5328
|
+
}
|
|
5329
|
+
) : null
|
|
5330
|
+
] }),
|
|
4664
5331
|
sideTab ? /* @__PURE__ */ jsx(
|
|
4665
5332
|
StudioSidePanel,
|
|
4666
5333
|
{
|
|
@@ -4773,6 +5440,12 @@ function StudioRoom({
|
|
|
4773
5440
|
stageEnabled,
|
|
4774
5441
|
canLeaveStage: selfId != null && canTakeOffAir(participants, selfId, onAir),
|
|
4775
5442
|
reducedMotion,
|
|
5443
|
+
captionsOn,
|
|
5444
|
+
captionsAvailable,
|
|
5445
|
+
captionsReady,
|
|
5446
|
+
captionPrefs,
|
|
5447
|
+
onCaptionPrefsChange: setCaptionPrefs,
|
|
5448
|
+
effectBackgrounds,
|
|
4776
5449
|
onToggleMute: () => void onToggleMute(),
|
|
4777
5450
|
onToggleCamera,
|
|
4778
5451
|
onToggleScreen,
|
|
@@ -4800,6 +5473,16 @@ function StudioRoom({
|
|
|
4800
5473
|
onCloseSettings: setSettingsOpen,
|
|
4801
5474
|
onOpenPip,
|
|
4802
5475
|
onClosePip: pip.close,
|
|
5476
|
+
onToggleCaptions: () => {
|
|
5477
|
+
if (!captionsAvailable) return;
|
|
5478
|
+
if (!captionsOn && !captionHintShown.current) {
|
|
5479
|
+
captionHintShown.current = true;
|
|
5480
|
+
toast.message(
|
|
5481
|
+
captionsReady ? labels.captionsDelayed : labels.captionsNotReady
|
|
5482
|
+
);
|
|
5483
|
+
}
|
|
5484
|
+
setCaptionsOn((current) => !current);
|
|
5485
|
+
},
|
|
4803
5486
|
onLeave: () => void onLeave()
|
|
4804
5487
|
}
|
|
4805
5488
|
),
|
|
@@ -5194,16 +5877,16 @@ function usePrefersReducedMotion() {
|
|
|
5194
5877
|
// src/studio/lib/guest-identity.ts
|
|
5195
5878
|
var NAME_KEY = "livo-studio-display-name";
|
|
5196
5879
|
var ID_KEY = "livo-studio-guest-id";
|
|
5197
|
-
function readStudioGuestIdentity(storage =
|
|
5880
|
+
function readStudioGuestIdentity(storage = defaultStorage4()) {
|
|
5198
5881
|
const guestId = storage?.getItem(ID_KEY)?.trim() || crypto.randomUUID();
|
|
5199
5882
|
storage?.setItem(ID_KEY, guestId);
|
|
5200
5883
|
const displayName = storage?.getItem(NAME_KEY)?.trim() ?? "";
|
|
5201
5884
|
return { displayName, guestId };
|
|
5202
5885
|
}
|
|
5203
|
-
function persistStudioGuestName(displayName, storage =
|
|
5886
|
+
function persistStudioGuestName(displayName, storage = defaultStorage4()) {
|
|
5204
5887
|
storage?.setItem(NAME_KEY, displayName.trim());
|
|
5205
5888
|
}
|
|
5206
|
-
function
|
|
5889
|
+
function defaultStorage4() {
|
|
5207
5890
|
if (typeof window === "undefined") return null;
|
|
5208
5891
|
try {
|
|
5209
5892
|
return window.localStorage;
|
|
@@ -5572,10 +6255,19 @@ function studioMeetingFromRtk(raw) {
|
|
|
5572
6255
|
if (typeof window !== "undefined") {
|
|
5573
6256
|
window.addEventListener("unhandledrejection", onUnhandledRejection);
|
|
5574
6257
|
}
|
|
6258
|
+
let videoEffects = readStudioVideoEffects();
|
|
6259
|
+
async function applyCurrentVideoEffects() {
|
|
6260
|
+
await applyStudioVideoEffects({
|
|
6261
|
+
self: meeting.self,
|
|
6262
|
+
meeting,
|
|
6263
|
+
config: videoEffects
|
|
6264
|
+
});
|
|
6265
|
+
}
|
|
5575
6266
|
async function republishCameraIfOn() {
|
|
5576
6267
|
if (meeting.self?.videoEnabled !== true) return;
|
|
5577
6268
|
await meeting.self.disableVideo?.();
|
|
5578
6269
|
await meeting.self.enableVideo?.();
|
|
6270
|
+
await applyCurrentVideoEffects();
|
|
5579
6271
|
}
|
|
5580
6272
|
async function acceptAllowedStageIfOffAir() {
|
|
5581
6273
|
if (currentStageStatus() !== "OFF_STAGE") return;
|
|
@@ -5763,6 +6455,7 @@ function studioMeetingFromRtk(raw) {
|
|
|
5763
6455
|
async join() {
|
|
5764
6456
|
await meeting.join?.();
|
|
5765
6457
|
await joinStageIfAllowed();
|
|
6458
|
+
await applyCurrentVideoEffects();
|
|
5766
6459
|
},
|
|
5767
6460
|
async leave() {
|
|
5768
6461
|
if (typeof window !== "undefined") {
|
|
@@ -5775,8 +6468,10 @@ function studioMeetingFromRtk(raw) {
|
|
|
5775
6468
|
else await meeting.self?.disableAudio?.();
|
|
5776
6469
|
},
|
|
5777
6470
|
async setVideoEnabled(enabled) {
|
|
5778
|
-
if (enabled)
|
|
5779
|
-
|
|
6471
|
+
if (enabled) {
|
|
6472
|
+
await meeting.self?.enableVideo?.();
|
|
6473
|
+
await applyCurrentVideoEffects();
|
|
6474
|
+
} else await meeting.self?.disableVideo?.();
|
|
5780
6475
|
},
|
|
5781
6476
|
async setScreenShareEnabled(enabled) {
|
|
5782
6477
|
if (enabled) await meeting.self?.enableScreenShare?.();
|
|
@@ -6160,6 +6855,14 @@ function studioMeetingFromRtk(raw) {
|
|
|
6160
6855
|
}
|
|
6161
6856
|
});
|
|
6162
6857
|
},
|
|
6858
|
+
async setVideoEffects(config) {
|
|
6859
|
+
videoEffects = config;
|
|
6860
|
+
persistStudioVideoEffects(config);
|
|
6861
|
+
await applyCurrentVideoEffects();
|
|
6862
|
+
},
|
|
6863
|
+
videoEffects() {
|
|
6864
|
+
return videoEffects;
|
|
6865
|
+
},
|
|
6163
6866
|
async setDisplayName(name) {
|
|
6164
6867
|
const trimmed = name.trim().slice(0, STUDIO_DISPLAY_NAME_MAX);
|
|
6165
6868
|
if (!trimmed) return;
|
|
@@ -6391,6 +7094,21 @@ async function getPublicStreamStatus(streamId, apiUrl = defaultApiUrl(), fetchFn
|
|
|
6391
7094
|
return null;
|
|
6392
7095
|
}
|
|
6393
7096
|
}
|
|
7097
|
+
async function getPublicStreamMeta(streamId, apiUrl = defaultApiUrl(), fetchFn = fetch) {
|
|
7098
|
+
try {
|
|
7099
|
+
const body = await publicJson(
|
|
7100
|
+
`${apiUrl.replace(/\/$/, "")}/public/streams/${encodeURIComponent(streamId)}`,
|
|
7101
|
+
{},
|
|
7102
|
+
fetchFn
|
|
7103
|
+
);
|
|
7104
|
+
return parsePublicStreamMeta(body);
|
|
7105
|
+
} catch (error) {
|
|
7106
|
+
if (error instanceof StudioApiError && (error.status === 404 || error.status === 410)) {
|
|
7107
|
+
return { status: "ended", captionsEnabled: false };
|
|
7108
|
+
}
|
|
7109
|
+
return null;
|
|
7110
|
+
}
|
|
7111
|
+
}
|
|
6394
7112
|
var POLL_INTERVAL_MS = 5e3;
|
|
6395
7113
|
function LivoHostStudio({
|
|
6396
7114
|
token,
|
|
@@ -6406,6 +7124,9 @@ function LivoHostStudio({
|
|
|
6406
7124
|
const reducedMotion = usePrefersReducedMotion();
|
|
6407
7125
|
const [session, setSession] = useState(null);
|
|
6408
7126
|
const [status, setStatus] = useState(null);
|
|
7127
|
+
const [captionsEnabled, setCaptionsEnabled] = useState(
|
|
7128
|
+
void 0
|
|
7129
|
+
);
|
|
6409
7130
|
const [error, setError] = useState(null);
|
|
6410
7131
|
const { meeting, error: meetingError } = useRealtimeKitMeeting(
|
|
6411
7132
|
session?.authToken ?? null
|
|
@@ -6454,6 +7175,20 @@ function LivoHostStudio({
|
|
|
6454
7175
|
window.clearInterval(id);
|
|
6455
7176
|
};
|
|
6456
7177
|
}, [client, status]);
|
|
7178
|
+
useEffect(() => {
|
|
7179
|
+
if (!session) return;
|
|
7180
|
+
let cancelled = false;
|
|
7181
|
+
const poll = async () => {
|
|
7182
|
+
const next = await getPublicStreamMeta(session.stream.id, apiUrl);
|
|
7183
|
+
if (!cancelled && next) setCaptionsEnabled(next.captionsEnabled);
|
|
7184
|
+
};
|
|
7185
|
+
void poll();
|
|
7186
|
+
const id = window.setInterval(() => void poll(), POLL_INTERVAL_MS);
|
|
7187
|
+
return () => {
|
|
7188
|
+
cancelled = true;
|
|
7189
|
+
window.clearInterval(id);
|
|
7190
|
+
};
|
|
7191
|
+
}, [apiUrl, session]);
|
|
6457
7192
|
const liveSession = useMemo(() => {
|
|
6458
7193
|
if (!session) return null;
|
|
6459
7194
|
if (!status) return session;
|
|
@@ -6461,10 +7196,11 @@ function LivoHostStudio({
|
|
|
6461
7196
|
...session,
|
|
6462
7197
|
stream: {
|
|
6463
7198
|
...session.stream,
|
|
6464
|
-
status
|
|
7199
|
+
status,
|
|
7200
|
+
captionsEnabled: captionsEnabled ?? session.stream.captionsEnabled
|
|
6465
7201
|
}
|
|
6466
7202
|
};
|
|
6467
|
-
}, [session, status]);
|
|
7203
|
+
}, [captionsEnabled, session, status]);
|
|
6468
7204
|
if (error || meetingError) {
|
|
6469
7205
|
return /* @__PURE__ */ jsx("p", { className: "text-destructive p-6", role: "alert", children: error ?? meetingError });
|
|
6470
7206
|
}
|
|
@@ -6482,7 +7218,8 @@ function LivoHostStudio({
|
|
|
6482
7218
|
embed,
|
|
6483
7219
|
chrome,
|
|
6484
7220
|
playerOrigin,
|
|
6485
|
-
appDownloadUrl
|
|
7221
|
+
appDownloadUrl,
|
|
7222
|
+
apiUrl
|
|
6486
7223
|
}
|
|
6487
7224
|
);
|
|
6488
7225
|
}
|
|
@@ -6501,6 +7238,9 @@ function LivoGuestStudio({
|
|
|
6501
7238
|
const reducedMotion = usePrefersReducedMotion();
|
|
6502
7239
|
const [session, setSession] = useState(null);
|
|
6503
7240
|
const [status, setStatus] = useState(null);
|
|
7241
|
+
const [captionsEnabled, setCaptionsEnabled] = useState(
|
|
7242
|
+
void 0
|
|
7243
|
+
);
|
|
6504
7244
|
const [error, setError] = useState(null);
|
|
6505
7245
|
const [displayName, setDisplayName] = useState("");
|
|
6506
7246
|
const [guestId, setGuestId] = useState("");
|
|
@@ -6604,8 +7344,11 @@ function LivoGuestStudio({
|
|
|
6604
7344
|
if (current === "ended" || current === "error") return;
|
|
6605
7345
|
let cancelled = false;
|
|
6606
7346
|
const poll = async () => {
|
|
6607
|
-
const next = await
|
|
6608
|
-
if (!cancelled && next)
|
|
7347
|
+
const next = await getPublicStreamMeta(session.stream.id, apiUrl);
|
|
7348
|
+
if (!cancelled && next) {
|
|
7349
|
+
setStatus(next.status);
|
|
7350
|
+
setCaptionsEnabled(next.captionsEnabled);
|
|
7351
|
+
}
|
|
6609
7352
|
};
|
|
6610
7353
|
void poll();
|
|
6611
7354
|
const intervalMs = current === "public" ? POLL_INTERVAL_MS2 : PREVIEW_POLL_INTERVAL_MS;
|
|
@@ -6620,9 +7363,13 @@ function LivoGuestStudio({
|
|
|
6620
7363
|
if (!status) return session;
|
|
6621
7364
|
return {
|
|
6622
7365
|
...session,
|
|
6623
|
-
stream: {
|
|
7366
|
+
stream: {
|
|
7367
|
+
...session.stream,
|
|
7368
|
+
status,
|
|
7369
|
+
captionsEnabled: captionsEnabled ?? session.stream.captionsEnabled
|
|
7370
|
+
}
|
|
6624
7371
|
};
|
|
6625
|
-
}, [session, status]);
|
|
7372
|
+
}, [captionsEnabled, session, status]);
|
|
6626
7373
|
useEffect(() => {
|
|
6627
7374
|
if (retryAfterMs == null) return;
|
|
6628
7375
|
const id = window.setTimeout(() => {
|
|
@@ -6683,7 +7430,8 @@ function LivoGuestStudio({
|
|
|
6683
7430
|
initialCameraOn: cameraOn,
|
|
6684
7431
|
initialMuted: muted,
|
|
6685
7432
|
initialDevices: deviceSelection,
|
|
6686
|
-
appDownloadUrl
|
|
7433
|
+
appDownloadUrl,
|
|
7434
|
+
apiUrl
|
|
6687
7435
|
}
|
|
6688
7436
|
);
|
|
6689
7437
|
}
|
|
@@ -6859,10 +7607,40 @@ function studioRoomLabels(t) {
|
|
|
6859
7607
|
chatPin: t("chatPin"),
|
|
6860
7608
|
chatUnpin: t("chatUnpin"),
|
|
6861
7609
|
chatSave: t("chatSave"),
|
|
6862
|
-
waitingSkeleton: t("waitingSkeleton")
|
|
7610
|
+
waitingSkeleton: t("waitingSkeleton"),
|
|
7611
|
+
more: t("more"),
|
|
7612
|
+
captions: t("captions"),
|
|
7613
|
+
captionsOn: t("captionsOn"),
|
|
7614
|
+
captionsOff: t("captionsOff"),
|
|
7615
|
+
captionsNotReady: t("captionsNotReady"),
|
|
7616
|
+
captionsDelayed: t("captionsDelayed"),
|
|
7617
|
+
captionsLanguage: t("captionsLanguage"),
|
|
7618
|
+
captionsSize: t("captionsSize"),
|
|
7619
|
+
captionsSizeS: t("captionsSizeS"),
|
|
7620
|
+
captionsSizeM: t("captionsSizeM"),
|
|
7621
|
+
captionsSizeL: t("captionsSizeL"),
|
|
7622
|
+
settingsGeneral: t("settingsGeneral"),
|
|
7623
|
+
settingsAudio: t("settingsAudio"),
|
|
7624
|
+
settingsVideo: t("settingsVideo"),
|
|
7625
|
+
settingsEffects: t("settingsEffects"),
|
|
7626
|
+
settingsSubtitles: t("settingsSubtitles"),
|
|
7627
|
+
effectNone: t("effectNone"),
|
|
7628
|
+
effectBlur: t("effectBlur"),
|
|
7629
|
+
effectBlurStrong: t("effectBlurStrong"),
|
|
7630
|
+
effectBackgrounds: t("effectBackgrounds"),
|
|
7631
|
+
effectFilters: t("effectFilters"),
|
|
7632
|
+
effectFilterNone: t("effectFilterNone"),
|
|
7633
|
+
effectFilterGrayscale: t("effectFilterGrayscale"),
|
|
7634
|
+
effectFilterSepia: t("effectFilterSepia"),
|
|
7635
|
+
effectFilterWarm: t("effectFilterWarm"),
|
|
7636
|
+
effectFilterCool: t("effectFilterCool"),
|
|
7637
|
+
effectFilterBright: t("effectFilterBright"),
|
|
7638
|
+
localeEn: t("localeEn"),
|
|
7639
|
+
localeEs: t("localeEs"),
|
|
7640
|
+
localePt: t("localePt")
|
|
6863
7641
|
};
|
|
6864
7642
|
}
|
|
6865
7643
|
|
|
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 };
|
|
7644
|
+
export { LivoGuestStudio, LivoHostStudio, StudioApiError, StudioControlClient, StudioControlError, StudioGuestLobby, StudioRoom, StudioSkeleton, StudioWindowGate, StudioWindowGateContext, applyEmbedTheme, controlActionsFromClient, createStudioControlClient, decodeEmbedTheme, getPublicStreamMeta, getPublicStreamStatus, getStudioJoinStatus, joinStudioGuest, redeemStudioHost, studioErrorCode, studioRoomLabels, usePrefersReducedMotion, useRealtimeKitMeeting, useStudioWindowGate };
|
|
6867
7645
|
//# sourceMappingURL=index.js.map
|
|
6868
7646
|
//# sourceMappingURL=index.js.map
|