@livo-tv/sdk 1.1.0 → 1.1.2
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/README.md +1 -1
- package/dist/index.cjs +1 -0
- package/dist/index.js +1 -0
- package/dist/studio/index.cjs +315 -129
- package/dist/studio/index.cjs.map +1 -1
- package/dist/studio/index.d.cts +1 -0
- package/dist/studio/index.d.ts +1 -0
- package/dist/studio/index.js +316 -130
- package/dist/studio/index.js.map +1 -1
- package/dist/styles.css +9 -3
- package/package.json +13 -3
package/dist/studio/index.d.cts
CHANGED
|
@@ -164,6 +164,7 @@ type StudioMeetingHandle = {
|
|
|
164
164
|
setStageAccess: (userId: string, on: boolean) => Promise<void>;
|
|
165
165
|
denyStageAccess: (userId: string) => Promise<void>;
|
|
166
166
|
requestStageAccess: () => Promise<void>;
|
|
167
|
+
joinStage: () => Promise<void>;
|
|
167
168
|
leaveStage: () => Promise<void>;
|
|
168
169
|
stageStatus: () => StudioStageStatus;
|
|
169
170
|
stageRequests: () => StudioStageRequest[];
|
package/dist/studio/index.d.ts
CHANGED
|
@@ -164,6 +164,7 @@ type StudioMeetingHandle = {
|
|
|
164
164
|
setStageAccess: (userId: string, on: boolean) => Promise<void>;
|
|
165
165
|
denyStageAccess: (userId: string) => Promise<void>;
|
|
166
166
|
requestStageAccess: () => Promise<void>;
|
|
167
|
+
joinStage: () => Promise<void>;
|
|
167
168
|
leaveStage: () => Promise<void>;
|
|
168
169
|
stageStatus: () => StudioStageStatus;
|
|
169
170
|
stageRequests: () => StudioStageRequest[];
|
package/dist/studio/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { createContext, useContext, useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
2
3
|
import { createPortal } from 'react-dom';
|
|
3
|
-
import { Loader2, MicOff, Mic, Video, VideoOff, X, EyeOff, Eye, Users, MonitorUp, MessageSquare,
|
|
4
|
+
import { Loader2, Share2, MicOff, Mic, Video, VideoOff, X, Link2, EyeOff, Eye, Users, MonitorUp, MessageSquare, Maximize, PictureInPicture2, Settings, PhoneOff, Undo2, Volume2, XIcon, Pin, MessageSquareOff, MoreVertical } from 'lucide-react';
|
|
4
5
|
import { toast } from 'sonner';
|
|
5
6
|
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
|
|
6
7
|
import { clsx } from 'clsx';
|
|
@@ -8,6 +9,8 @@ import { twMerge } from 'tailwind-merge';
|
|
|
8
9
|
import { Button as Button$1 } from '@base-ui/react/button';
|
|
9
10
|
import { cva } from 'class-variance-authority';
|
|
10
11
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
12
|
+
import { mergeProps } from '@base-ui/react/merge-props';
|
|
13
|
+
import { useRender } from '@base-ui/react/use-render';
|
|
11
14
|
import { Menu } from '@base-ui/react/menu';
|
|
12
15
|
import { Tooltip as Tooltip$1 } from '@base-ui/react/tooltip';
|
|
13
16
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
@@ -751,6 +754,58 @@ function postLivoEvent(type, payload) {
|
|
|
751
754
|
window.parent.postMessage({ source: "livo", type, payload }, "*");
|
|
752
755
|
}
|
|
753
756
|
|
|
757
|
+
// src/studio/lib/embed-urls.ts
|
|
758
|
+
function playerWatchUrl(streamId, origin = "https://player.livo.tv") {
|
|
759
|
+
return `${origin.replace(/\/$/, "")}/${streamId}`;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/studio/lib/clipboard.ts
|
|
763
|
+
async function copyText(value, clipboard = globalThis.navigator?.clipboard ?? null) {
|
|
764
|
+
if (!clipboard) return false;
|
|
765
|
+
try {
|
|
766
|
+
await clipboard.writeText(value);
|
|
767
|
+
return true;
|
|
768
|
+
} catch {
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/studio/lib/share.ts
|
|
774
|
+
function isAbort(error) {
|
|
775
|
+
return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
776
|
+
}
|
|
777
|
+
function resolveShareApi(share = typeof navigator === "undefined" ? null : typeof navigator.share === "function" ? navigator : null) {
|
|
778
|
+
return share ?? null;
|
|
779
|
+
}
|
|
780
|
+
function canNativeShare(data, share = resolveShareApi()) {
|
|
781
|
+
if (!share) return false;
|
|
782
|
+
if (typeof share.canShare !== "function") return true;
|
|
783
|
+
try {
|
|
784
|
+
return share.canShare(data);
|
|
785
|
+
} catch {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
async function shareOrCopyLink(options, deps) {
|
|
790
|
+
const data = {
|
|
791
|
+
url: options.url,
|
|
792
|
+
...options.title ? { title: options.title } : {},
|
|
793
|
+
...options.text ? { text: options.text } : {}
|
|
794
|
+
};
|
|
795
|
+
const share = resolveShareApi(deps?.share);
|
|
796
|
+
if (canNativeShare(data, share) && share) {
|
|
797
|
+
try {
|
|
798
|
+
await share.share(data);
|
|
799
|
+
return "shared";
|
|
800
|
+
} catch (error) {
|
|
801
|
+
if (isAbort(error)) return "cancelled";
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
const copy = copyText;
|
|
805
|
+
const ok = await copy(options.url);
|
|
806
|
+
return ok ? "copied" : "failed";
|
|
807
|
+
}
|
|
808
|
+
|
|
754
809
|
// src/studio/lib/status.ts
|
|
755
810
|
function canPublish(status) {
|
|
756
811
|
return status === "preview";
|
|
@@ -1424,9 +1479,124 @@ function StudioRemoteAudio({
|
|
|
1424
1479
|
}, [meeting, participantId]);
|
|
1425
1480
|
return /* @__PURE__ */ jsx("audio", { ref, autoPlay: true, className: "sr-only" });
|
|
1426
1481
|
}
|
|
1482
|
+
function BubbleGroup({ className, ...props }) {
|
|
1483
|
+
return /* @__PURE__ */ jsx(
|
|
1484
|
+
"div",
|
|
1485
|
+
{
|
|
1486
|
+
"data-slot": "bubble-group",
|
|
1487
|
+
className: cn("flex min-w-0 flex-col gap-2", className),
|
|
1488
|
+
...props
|
|
1489
|
+
}
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
var bubbleVariants = cva(
|
|
1493
|
+
"group/bubble relative flex w-fit max-w-[80%] min-w-0 flex-col gap-1 group-data-[align=end]/message:self-end data-[align=end]:self-end data-[variant=ghost]:max-w-full",
|
|
1494
|
+
{
|
|
1495
|
+
variants: {
|
|
1496
|
+
variant: {
|
|
1497
|
+
default: "*:data-[slot=bubble-content]:bg-primary *:data-[slot=bubble-content]:text-primary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-primary/80",
|
|
1498
|
+
secondary: "*:data-[slot=bubble-content]:bg-secondary *:data-[slot=bubble-content]:text-secondary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]",
|
|
1499
|
+
muted: "*:data-[slot=bubble-content]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--muted),var(--foreground)_5%)]",
|
|
1500
|
+
tinted: "*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.93_calc(c*0.4)_h)] *:data-[slot=bubble-content]:text-foreground dark:*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.3_calc(c*0.4)_h)] [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.88_calc(c*0.5)_h)] dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.35_calc(c*0.5)_h)]",
|
|
1501
|
+
outline: "*:data-[slot=bubble-content]:border-border *:data-[slot=bubble-content]:bg-background [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-input/30",
|
|
1502
|
+
ghost: "border-none *:data-[slot=bubble-content]:rounded-none *:data-[slot=bubble-content]:bg-transparent *:data-[slot=bubble-content]:p-0 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted/50",
|
|
1503
|
+
destructive: "*:data-[slot=bubble-content]:bg-destructive/10 *:data-[slot=bubble-content]:text-destructive dark:*:data-[slot=bubble-content]:bg-destructive/20 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/20 dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/30"
|
|
1504
|
+
}
|
|
1505
|
+
},
|
|
1506
|
+
defaultVariants: {
|
|
1507
|
+
variant: "default"
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
);
|
|
1511
|
+
function Bubble({
|
|
1512
|
+
variant = "default",
|
|
1513
|
+
align = "start",
|
|
1514
|
+
className,
|
|
1515
|
+
...props
|
|
1516
|
+
}) {
|
|
1517
|
+
return /* @__PURE__ */ jsx(
|
|
1518
|
+
"div",
|
|
1519
|
+
{
|
|
1520
|
+
"data-slot": "bubble",
|
|
1521
|
+
"data-variant": variant,
|
|
1522
|
+
"data-align": align,
|
|
1523
|
+
className: cn(bubbleVariants({ variant }), className),
|
|
1524
|
+
...props
|
|
1525
|
+
}
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
function BubbleContent({
|
|
1529
|
+
className,
|
|
1530
|
+
render,
|
|
1531
|
+
...props
|
|
1532
|
+
}) {
|
|
1533
|
+
return useRender({
|
|
1534
|
+
defaultTagName: "div",
|
|
1535
|
+
props: mergeProps(
|
|
1536
|
+
{
|
|
1537
|
+
className: cn(
|
|
1538
|
+
"w-fit max-w-full min-w-0 overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed wrap-break-word group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50",
|
|
1539
|
+
className
|
|
1540
|
+
)
|
|
1541
|
+
},
|
|
1542
|
+
props
|
|
1543
|
+
),
|
|
1544
|
+
render,
|
|
1545
|
+
state: {
|
|
1546
|
+
slot: "bubble-content"
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
cva(
|
|
1551
|
+
"absolute z-10 flex w-fit shrink-0 items-center justify-center gap-1 rounded-full bg-muted px-1.5 py-0.5 text-sm ring-3 ring-card has-[button]:p-0",
|
|
1552
|
+
{
|
|
1553
|
+
variants: {
|
|
1554
|
+
side: {
|
|
1555
|
+
top: "top-0 -translate-y-3/4",
|
|
1556
|
+
bottom: "bottom-0 translate-y-3/4"
|
|
1557
|
+
},
|
|
1558
|
+
align: {
|
|
1559
|
+
start: "left-3",
|
|
1560
|
+
end: "right-3"
|
|
1561
|
+
}
|
|
1562
|
+
},
|
|
1563
|
+
defaultVariants: {
|
|
1564
|
+
side: "bottom",
|
|
1565
|
+
align: "end"
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
);
|
|
1569
|
+
|
|
1570
|
+
// src/studio/lib/chat-groups.ts
|
|
1571
|
+
function groupChatMessages(messages, selfId) {
|
|
1572
|
+
const groups = [];
|
|
1573
|
+
for (const row of messages) {
|
|
1574
|
+
const mine = Boolean(selfId) && row.userId === selfId;
|
|
1575
|
+
const last = groups[groups.length - 1];
|
|
1576
|
+
if (last && last.userId === row.userId && last.mine === mine) {
|
|
1577
|
+
last.rows.push(row);
|
|
1578
|
+
continue;
|
|
1579
|
+
}
|
|
1580
|
+
groups.push({
|
|
1581
|
+
userId: row.userId,
|
|
1582
|
+
name: row.name,
|
|
1583
|
+
mine,
|
|
1584
|
+
rows: [row]
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
return groups;
|
|
1588
|
+
}
|
|
1589
|
+
function senderInitials(name) {
|
|
1590
|
+
const parts = name.trim().split(/\s+/).filter((part) => part.length > 0);
|
|
1591
|
+
if (parts.length === 0) return "?";
|
|
1592
|
+
const first = parts[0];
|
|
1593
|
+
if (parts.length === 1) return first.slice(0, 2).toUpperCase();
|
|
1594
|
+
return `${first[0]}${parts[1][0]}`.toUpperCase();
|
|
1595
|
+
}
|
|
1427
1596
|
function StudioChatPanel({
|
|
1428
1597
|
meeting,
|
|
1429
1598
|
selfId,
|
|
1599
|
+
participants,
|
|
1430
1600
|
isModerator: isModerator2,
|
|
1431
1601
|
labels
|
|
1432
1602
|
}) {
|
|
@@ -1444,6 +1614,17 @@ function StudioChatPanel({
|
|
|
1444
1614
|
);
|
|
1445
1615
|
const pinned = useMemo(() => meeting.pinnedChatMessages(), [meeting, tick]);
|
|
1446
1616
|
const limit = meeting.chatMaxTextLimit();
|
|
1617
|
+
const pictures = useMemo(() => {
|
|
1618
|
+
const map = /* @__PURE__ */ new Map();
|
|
1619
|
+
for (const row of participants) {
|
|
1620
|
+
if (row.userId) map.set(row.userId, row.picture);
|
|
1621
|
+
}
|
|
1622
|
+
return map;
|
|
1623
|
+
}, [participants]);
|
|
1624
|
+
const groups = useMemo(
|
|
1625
|
+
() => groupChatMessages(messages, selfId),
|
|
1626
|
+
[messages, selfId]
|
|
1627
|
+
);
|
|
1447
1628
|
useEffect(() => {
|
|
1448
1629
|
const list = listRef.current;
|
|
1449
1630
|
if (!list) return;
|
|
@@ -1467,6 +1648,44 @@ function StudioChatPanel({
|
|
|
1467
1648
|
);
|
|
1468
1649
|
}
|
|
1469
1650
|
}
|
|
1651
|
+
function messageActions(row, mine) {
|
|
1652
|
+
return /* @__PURE__ */ jsxs(
|
|
1653
|
+
"p",
|
|
1654
|
+
{
|
|
1655
|
+
className: mine ? "text-muted-foreground flex flex-wrap justify-end gap-2 text-xs" : "text-muted-foreground flex flex-wrap gap-2 text-xs",
|
|
1656
|
+
children: [
|
|
1657
|
+
row.isEdited ? /* @__PURE__ */ jsx("span", { children: labels.chatEdited }) : null,
|
|
1658
|
+
mine ? /* @__PURE__ */ jsx(
|
|
1659
|
+
"button",
|
|
1660
|
+
{
|
|
1661
|
+
type: "button",
|
|
1662
|
+
onClick: () => {
|
|
1663
|
+
setEditingId(row.id);
|
|
1664
|
+
setDraft(row.text);
|
|
1665
|
+
},
|
|
1666
|
+
children: labels.chatEdit
|
|
1667
|
+
}
|
|
1668
|
+
) : null,
|
|
1669
|
+
isModerator2 || mine ? /* @__PURE__ */ jsx(
|
|
1670
|
+
"button",
|
|
1671
|
+
{
|
|
1672
|
+
type: "button",
|
|
1673
|
+
onClick: () => void meeting.deleteChatMessage(row.id).catch(() => toast.error(labels.chatError)),
|
|
1674
|
+
children: labels.chatDelete
|
|
1675
|
+
}
|
|
1676
|
+
) : null,
|
|
1677
|
+
isModerator2 ? /* @__PURE__ */ jsx(
|
|
1678
|
+
"button",
|
|
1679
|
+
{
|
|
1680
|
+
type: "button",
|
|
1681
|
+
onClick: () => void meeting.pinChatMessage(row.id, row.pinned !== true).catch(() => toast.error(labels.chatError)),
|
|
1682
|
+
children: row.pinned ? labels.chatUnpin : labels.chatPin
|
|
1683
|
+
}
|
|
1684
|
+
) : null
|
|
1685
|
+
]
|
|
1686
|
+
}
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1470
1689
|
return /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col", children: [
|
|
1471
1690
|
pinned[0] ? /* @__PURE__ */ jsxs("div", { role: "status", className: "bg-muted border-b px-3 py-2 text-sm", children: [
|
|
1472
1691
|
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: labels.chatPinned }),
|
|
@@ -1478,7 +1697,7 @@ function StudioChatPanel({
|
|
|
1478
1697
|
ref: listRef,
|
|
1479
1698
|
role: "log",
|
|
1480
1699
|
"aria-live": "polite",
|
|
1481
|
-
className: "flex min-h-0 flex-1 flex-col gap-
|
|
1700
|
+
className: "flex min-h-0 flex-1 flex-col gap-3 overflow-auto p-3",
|
|
1482
1701
|
children: [
|
|
1483
1702
|
/* @__PURE__ */ jsx(
|
|
1484
1703
|
Button,
|
|
@@ -1496,48 +1715,42 @@ function StudioChatPanel({
|
|
|
1496
1715
|
children: labels.chatLoadOlder
|
|
1497
1716
|
}
|
|
1498
1717
|
),
|
|
1499
|
-
|
|
1500
|
-
const
|
|
1718
|
+
groups.map((group) => {
|
|
1719
|
+
const bubbles = group.rows.map((row) => /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [
|
|
1720
|
+
/* @__PURE__ */ jsx(
|
|
1721
|
+
Bubble,
|
|
1722
|
+
{
|
|
1723
|
+
align: group.mine ? "end" : "start",
|
|
1724
|
+
variant: group.mine ? "default" : "secondary",
|
|
1725
|
+
children: /* @__PURE__ */ jsx(BubbleContent, { children: row.text })
|
|
1726
|
+
}
|
|
1727
|
+
),
|
|
1728
|
+
messageActions(row, group.mine)
|
|
1729
|
+
] }, row.id));
|
|
1730
|
+
if (group.mine) {
|
|
1731
|
+
return /* @__PURE__ */ jsx(BubbleGroup, { className: "items-end", children: bubbles }, group.rows[0].id);
|
|
1732
|
+
}
|
|
1733
|
+
const picture = pictures.get(group.userId) ?? null;
|
|
1501
1734
|
return /* @__PURE__ */ jsxs(
|
|
1502
1735
|
"div",
|
|
1503
1736
|
{
|
|
1504
|
-
className:
|
|
1737
|
+
className: "flex max-w-full gap-2 self-start",
|
|
1505
1738
|
children: [
|
|
1506
|
-
/* @__PURE__ */ jsx(
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
"
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
},
|
|
1518
|
-
children: labels.chatEdit
|
|
1519
|
-
}
|
|
1520
|
-
) : null,
|
|
1521
|
-
isModerator2 || mine ? /* @__PURE__ */ jsx(
|
|
1522
|
-
"button",
|
|
1523
|
-
{
|
|
1524
|
-
type: "button",
|
|
1525
|
-
onClick: () => void meeting.deleteChatMessage(row.id).catch(() => toast.error(labels.chatError)),
|
|
1526
|
-
children: labels.chatDelete
|
|
1527
|
-
}
|
|
1528
|
-
) : null,
|
|
1529
|
-
isModerator2 ? /* @__PURE__ */ jsx(
|
|
1530
|
-
"button",
|
|
1531
|
-
{
|
|
1532
|
-
type: "button",
|
|
1533
|
-
onClick: () => void meeting.pinChatMessage(row.id, row.pinned !== true).catch(() => toast.error(labels.chatError)),
|
|
1534
|
-
children: row.pinned ? labels.chatUnpin : labels.chatPin
|
|
1535
|
-
}
|
|
1536
|
-
) : null
|
|
1739
|
+
picture ? /* @__PURE__ */ jsx(
|
|
1740
|
+
"img",
|
|
1741
|
+
{
|
|
1742
|
+
src: picture,
|
|
1743
|
+
alt: "",
|
|
1744
|
+
className: "mt-0.5 size-7 shrink-0 rounded-full object-cover"
|
|
1745
|
+
}
|
|
1746
|
+
) : /* @__PURE__ */ jsx("span", { className: "bg-muted text-muted-foreground mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full text-xs", children: senderInitials(group.name) }),
|
|
1747
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1748
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground mb-1 text-xs", children: group.name }),
|
|
1749
|
+
/* @__PURE__ */ jsx(BubbleGroup, { children: bubbles })
|
|
1537
1750
|
] })
|
|
1538
1751
|
]
|
|
1539
1752
|
},
|
|
1540
|
-
|
|
1753
|
+
group.rows[0].id
|
|
1541
1754
|
);
|
|
1542
1755
|
})
|
|
1543
1756
|
]
|
|
@@ -1667,7 +1880,9 @@ function StudioSidePanel({
|
|
|
1667
1880
|
onDeny,
|
|
1668
1881
|
onGrantStage,
|
|
1669
1882
|
onDenyStage,
|
|
1670
|
-
onTakeOffAir
|
|
1883
|
+
onTakeOffAir,
|
|
1884
|
+
guestUrl,
|
|
1885
|
+
onShareGuestLink
|
|
1671
1886
|
}) {
|
|
1672
1887
|
const panelists = participants.filter((row) => isOnStage(row.stageStatus));
|
|
1673
1888
|
const audience = participants.filter((row) => !isOnStage(row.stageStatus));
|
|
@@ -1724,6 +1939,19 @@ function StudioSidePanel({
|
|
|
1724
1939
|
labels
|
|
1725
1940
|
}
|
|
1726
1941
|
) : /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-4 overflow-auto p-3", children: [
|
|
1942
|
+
guestUrl ? /* @__PURE__ */ jsxs(
|
|
1943
|
+
Button,
|
|
1944
|
+
{
|
|
1945
|
+
type: "button",
|
|
1946
|
+
variant: "outline",
|
|
1947
|
+
className: "w-full",
|
|
1948
|
+
onClick: onShareGuestLink,
|
|
1949
|
+
children: [
|
|
1950
|
+
/* @__PURE__ */ jsx(Link2, {}),
|
|
1951
|
+
labels.shareLink
|
|
1952
|
+
]
|
|
1953
|
+
}
|
|
1954
|
+
) : null,
|
|
1727
1955
|
/* @__PURE__ */ jsx(
|
|
1728
1956
|
StudioWaitingRoom,
|
|
1729
1957
|
{
|
|
@@ -2275,58 +2503,6 @@ function TooltipContent({
|
|
|
2275
2503
|
}
|
|
2276
2504
|
) });
|
|
2277
2505
|
}
|
|
2278
|
-
|
|
2279
|
-
// src/studio/lib/clipboard.ts
|
|
2280
|
-
async function copyText(value, clipboard = globalThis.navigator?.clipboard ?? null) {
|
|
2281
|
-
if (!clipboard) return false;
|
|
2282
|
-
try {
|
|
2283
|
-
await clipboard.writeText(value);
|
|
2284
|
-
return true;
|
|
2285
|
-
} catch {
|
|
2286
|
-
return false;
|
|
2287
|
-
}
|
|
2288
|
-
}
|
|
2289
|
-
|
|
2290
|
-
// src/studio/lib/share.ts
|
|
2291
|
-
function isAbort(error) {
|
|
2292
|
-
return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
2293
|
-
}
|
|
2294
|
-
function resolveShareApi(share = typeof navigator === "undefined" ? null : typeof navigator.share === "function" ? navigator : null) {
|
|
2295
|
-
return share ?? null;
|
|
2296
|
-
}
|
|
2297
|
-
function canNativeShare(data, share = resolveShareApi()) {
|
|
2298
|
-
if (!share) return false;
|
|
2299
|
-
if (typeof share.canShare !== "function") return true;
|
|
2300
|
-
try {
|
|
2301
|
-
return share.canShare(data);
|
|
2302
|
-
} catch {
|
|
2303
|
-
return false;
|
|
2304
|
-
}
|
|
2305
|
-
}
|
|
2306
|
-
async function shareOrCopyLink(options, deps) {
|
|
2307
|
-
const data = {
|
|
2308
|
-
url: options.url,
|
|
2309
|
-
...options.title ? { title: options.title } : {},
|
|
2310
|
-
...options.text ? { text: options.text } : {}
|
|
2311
|
-
};
|
|
2312
|
-
const share = resolveShareApi(deps?.share);
|
|
2313
|
-
if (canNativeShare(data, share) && share) {
|
|
2314
|
-
try {
|
|
2315
|
-
await share.share(data);
|
|
2316
|
-
return "shared";
|
|
2317
|
-
} catch (error) {
|
|
2318
|
-
if (isAbort(error)) return "cancelled";
|
|
2319
|
-
}
|
|
2320
|
-
}
|
|
2321
|
-
const copy = copyText;
|
|
2322
|
-
const ok = await copy(options.url);
|
|
2323
|
-
return ok ? "copied" : "failed";
|
|
2324
|
-
}
|
|
2325
|
-
|
|
2326
|
-
// src/studio/lib/embed-urls.ts
|
|
2327
|
-
function playerWatchUrl(streamId, origin = "https://player.livo.tv") {
|
|
2328
|
-
return `${origin.replace(/\/$/, "")}/${streamId}`;
|
|
2329
|
-
}
|
|
2330
2506
|
function Dialog({ ...props }) {
|
|
2331
2507
|
return /* @__PURE__ */ jsx(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
|
|
2332
2508
|
}
|
|
@@ -2796,7 +2972,6 @@ function StudioToolbar({
|
|
|
2796
2972
|
stageEnabled,
|
|
2797
2973
|
canLeaveStage,
|
|
2798
2974
|
reducedMotion,
|
|
2799
|
-
playerOrigin,
|
|
2800
2975
|
onToggleMute,
|
|
2801
2976
|
onToggleCamera,
|
|
2802
2977
|
onToggleScreen,
|
|
@@ -2920,41 +3095,6 @@ function StudioToolbar({
|
|
|
2920
3095
|
children: /* @__PURE__ */ jsx(Users, {})
|
|
2921
3096
|
}
|
|
2922
3097
|
) : null,
|
|
2923
|
-
/* @__PURE__ */ jsx(
|
|
2924
|
-
ControlIcon,
|
|
2925
|
-
{
|
|
2926
|
-
label: labels.share,
|
|
2927
|
-
onClick: () => {
|
|
2928
|
-
void navigator.clipboard.writeText(
|
|
2929
|
-
playerWatchUrl(session.stream.id, playerOrigin)
|
|
2930
|
-
);
|
|
2931
|
-
toast.success(labels.shareCopied);
|
|
2932
|
-
},
|
|
2933
|
-
children: /* @__PURE__ */ jsx(Share2, {})
|
|
2934
|
-
}
|
|
2935
|
-
),
|
|
2936
|
-
session.role === "moderator" && session.guestUrl ? /* @__PURE__ */ jsx(
|
|
2937
|
-
ControlIcon,
|
|
2938
|
-
{
|
|
2939
|
-
label: labels.shareLink,
|
|
2940
|
-
onClick: () => {
|
|
2941
|
-
const url = session.guestUrl;
|
|
2942
|
-
if (!url) return;
|
|
2943
|
-
void shareOrCopyLink({
|
|
2944
|
-
url,
|
|
2945
|
-
title: session.stream.title,
|
|
2946
|
-
text: labels.shareGuestText.replace(
|
|
2947
|
-
"{title}",
|
|
2948
|
-
session.stream.title
|
|
2949
|
-
)
|
|
2950
|
-
}).then((result) => {
|
|
2951
|
-
if (result === "copied") toast.success(labels.copied);
|
|
2952
|
-
if (result === "failed") toast.error(labels.shareFailed);
|
|
2953
|
-
});
|
|
2954
|
-
},
|
|
2955
|
-
children: /* @__PURE__ */ jsx(Link2, {})
|
|
2956
|
-
}
|
|
2957
|
-
) : null,
|
|
2958
3098
|
/* @__PURE__ */ jsx(
|
|
2959
3099
|
ControlIcon,
|
|
2960
3100
|
{
|
|
@@ -3727,6 +3867,22 @@ function StudioRoom({
|
|
|
3727
3867
|
trigger: /* @__PURE__ */ jsx(Button, { type: "button", variant: "destructive", disabled: pending, children: labels.stop })
|
|
3728
3868
|
}
|
|
3729
3869
|
) : null,
|
|
3870
|
+
session.role === "moderator" ? /* @__PURE__ */ jsx(
|
|
3871
|
+
Button,
|
|
3872
|
+
{
|
|
3873
|
+
type: "button",
|
|
3874
|
+
variant: "outline",
|
|
3875
|
+
size: "icon-sm",
|
|
3876
|
+
"aria-label": labels.share,
|
|
3877
|
+
onClick: () => {
|
|
3878
|
+
void navigator.clipboard.writeText(
|
|
3879
|
+
playerWatchUrl(session.stream.id, playerOrigin)
|
|
3880
|
+
);
|
|
3881
|
+
toast.success(labels.shareCopied);
|
|
3882
|
+
},
|
|
3883
|
+
children: /* @__PURE__ */ jsx(Share2, {})
|
|
3884
|
+
}
|
|
3885
|
+
) : null,
|
|
3730
3886
|
embed ? null : chrome
|
|
3731
3887
|
] }),
|
|
3732
3888
|
!stageEnabled && session.role === "guest" ? /* @__PURE__ */ jsx(
|
|
@@ -3814,6 +3970,22 @@ function StudioRoom({
|
|
|
3814
3970
|
onTakeOffAir: (userId) => {
|
|
3815
3971
|
const tile = participants.find((row) => row.userId === userId);
|
|
3816
3972
|
if (tile) void onSetStage(tile, false);
|
|
3973
|
+
},
|
|
3974
|
+
guestUrl: session.guestUrl,
|
|
3975
|
+
onShareGuestLink: () => {
|
|
3976
|
+
const url = session.guestUrl;
|
|
3977
|
+
if (!url) return;
|
|
3978
|
+
void shareOrCopyLink({
|
|
3979
|
+
url,
|
|
3980
|
+
title: session.stream.title,
|
|
3981
|
+
text: labels.shareGuestText.replace(
|
|
3982
|
+
"{title}",
|
|
3983
|
+
session.stream.title
|
|
3984
|
+
)
|
|
3985
|
+
}).then((result) => {
|
|
3986
|
+
if (result === "copied") toast.success(labels.copied);
|
|
3987
|
+
if (result === "failed") toast.error(labels.shareFailed);
|
|
3988
|
+
});
|
|
3817
3989
|
}
|
|
3818
3990
|
}
|
|
3819
3991
|
) : null
|
|
@@ -3832,7 +4004,6 @@ function StudioRoom({
|
|
|
3832
4004
|
session,
|
|
3833
4005
|
meeting,
|
|
3834
4006
|
labels,
|
|
3835
|
-
playerOrigin,
|
|
3836
4007
|
muted,
|
|
3837
4008
|
cameraOn,
|
|
3838
4009
|
screenOn,
|
|
@@ -3862,9 +4033,9 @@ function StudioRoom({
|
|
|
3862
4033
|
onCancelShareRequest: () => setShareGrant("idle"),
|
|
3863
4034
|
onAskStage: () => {
|
|
3864
4035
|
if (session.role === "moderator") {
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
4036
|
+
void Promise.resolve(meeting.joinStage()).catch(
|
|
4037
|
+
() => toast.error(labels.stageError)
|
|
4038
|
+
);
|
|
3868
4039
|
return;
|
|
3869
4040
|
}
|
|
3870
4041
|
void meeting.requestStageAccess();
|
|
@@ -4612,6 +4783,21 @@ function studioMeetingFromRtk(raw) {
|
|
|
4612
4783
|
await meeting.stage?.requestAccess?.();
|
|
4613
4784
|
notifyStage();
|
|
4614
4785
|
},
|
|
4786
|
+
async joinStage() {
|
|
4787
|
+
if (currentStageStatus() === "ON_STAGE") {
|
|
4788
|
+
notifyStage();
|
|
4789
|
+
return;
|
|
4790
|
+
}
|
|
4791
|
+
if (meeting.self?.permissions?.stageAccess !== "ALLOWED") {
|
|
4792
|
+
await meeting.stage?.requestAccess?.();
|
|
4793
|
+
notifyStage();
|
|
4794
|
+
return;
|
|
4795
|
+
}
|
|
4796
|
+
await meeting.stage?.join?.();
|
|
4797
|
+
joinedStageLocally = true;
|
|
4798
|
+
await republishCameraIfOn();
|
|
4799
|
+
notifyStage();
|
|
4800
|
+
},
|
|
4615
4801
|
async leaveStage() {
|
|
4616
4802
|
await meeting.stage?.leave?.();
|
|
4617
4803
|
notifyStage();
|