(feat): add deaf & mute button
(feat): add ping graph (via recharts) (feat): remove old expand button
This commit is contained in:
parent
246163486c
commit
d01df7c02c
6 changed files with 212 additions and 53 deletions
5
bun.lock
5
bun.lock
|
|
@ -111,6 +111,7 @@
|
||||||
"lucide-react": "^0.564.0",
|
"lucide-react": "^0.564.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
|
"recharts": "^3.8.1",
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -1389,7 +1390,7 @@
|
||||||
|
|
||||||
"recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="],
|
"recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="],
|
||||||
|
|
||||||
"recharts": ["recharts@3.8.0", "", { "dependencies": { "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ=="],
|
"recharts": ["recharts@3.8.1", "", { "dependencies": { "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg=="],
|
||||||
|
|
||||||
"redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="],
|
"redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="],
|
||||||
|
|
||||||
|
|
@ -1667,6 +1668,8 @@
|
||||||
|
|
||||||
"@tensamin/ui/lucide-react": ["lucide-react@1.8.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw=="],
|
"@tensamin/ui/lucide-react": ["lucide-react@1.8.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw=="],
|
||||||
|
|
||||||
|
"@tensamin/ui/recharts": ["recharts@3.8.0", "", { "dependencies": { "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ=="],
|
||||||
|
|
||||||
"@tensamin/ui/shadcn": ["shadcn@3.8.5", "", { "dependencies": { "@antfu/ni": "^25.0.0", "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-jPRx44e+eyeV7xwY3BLJXcfrks00+M0h5BGB9l6DdcBW4BpAj4x3lVmVy0TXPEs2iHEisxejr62sZAAw6B1EVA=="],
|
"@tensamin/ui/shadcn": ["shadcn@3.8.5", "", { "dependencies": { "@antfu/ni": "^25.0.0", "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-jPRx44e+eyeV7xwY3BLJXcfrks00+M0h5BGB9l6DdcBW4BpAj4x3lVmVy0TXPEs2iHEisxejr62sZAAw6B1EVA=="],
|
||||||
|
|
||||||
"@tensamin/ui/sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
|
"@tensamin/ui/sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
"lucide-react": "^0.564.0",
|
"lucide-react": "^0.564.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
|
"recharts": "^3.8.1",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
packages/call/src/components/buttons/deaf.tsx
Normal file
17
packages/call/src/components/buttons/deaf.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Button } from "@tensamin/ui";
|
||||||
|
import { useCall } from "../../context";
|
||||||
|
import { HeadphoneOff, Headphones } from "lucide-react";
|
||||||
|
|
||||||
|
export default function DeafButton({ className }: { className?: string }) {
|
||||||
|
const { toggleDeaf, deaf } = useCall();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant={deaf ? "destructive" : "default"}
|
||||||
|
onClick={toggleDeaf}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{deaf ? <HeadphoneOff /> : <Headphones />}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
17
packages/call/src/components/buttons/mute.tsx
Normal file
17
packages/call/src/components/buttons/mute.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Button } from "@tensamin/ui";
|
||||||
|
import { useCall } from "../../context";
|
||||||
|
import { Mic, MicOff } from "lucide-react";
|
||||||
|
|
||||||
|
export default function MuteButton({ className }: { className?: string }) {
|
||||||
|
const { room } = useCall();
|
||||||
|
const micEnabled = room.localParticipant.isMicrophoneEnabled;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant={micEnabled ? "default" : "destructive"}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{micEnabled ? <Mic /> : <MicOff />}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
import { Expand, Lock, LockOpen } from "lucide-react";
|
import { Lock, LockOpen } from "lucide-react";
|
||||||
import { useCall } from "../context";
|
import { useCall } from "../context";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
|
|
@ -15,52 +12,51 @@ import {
|
||||||
} from "@tensamin/ui";
|
} from "@tensamin/ui";
|
||||||
import { LeaveIcon } from "@livekit/components-react";
|
import { LeaveIcon } from "@livekit/components-react";
|
||||||
import ScreenshareButton from "./buttons/screenshare";
|
import ScreenshareButton from "./buttons/screenshare";
|
||||||
|
import MuteButton from "./buttons/mute";
|
||||||
|
import DeafButton from "./buttons/deaf";
|
||||||
|
import { Room, Track } from "livekit-client";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { ResponsiveContainer, AreaChart, Area } from "recharts";
|
||||||
|
|
||||||
export default function SidebarBox() {
|
export default function SidebarBox() {
|
||||||
const { state, room, disconnect, openCallPage, callId } = useCall();
|
const { state, disconnect } = useCall();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
return state === "closed" ? null : (
|
return state === "closed" ? null : (
|
||||||
<Card className="p-1.5 gap-1" hidden={isMobile}>
|
<Card className="p-1.5 gap-2" hidden={isMobile}>
|
||||||
<CardHeader className="p-0!">
|
<CardHeader className="p-0! pb-2! border-b-2">
|
||||||
<ConnectionBar
|
<ConnectionBar />
|
||||||
state={state}
|
|
||||||
encrypted={
|
|
||||||
room.localParticipant.isE2EEEnabled &&
|
|
||||||
room.localParticipant.isEncrypted
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="p-0! flex justify-end gap-1">
|
<CardContent className="p-0! flex flex-col gap-1">
|
||||||
<Button className="w-9 h-9" onClick={() => openCallPage(callId || "")}>
|
<div className="flex justify-start gap-1">
|
||||||
<Expand />
|
<MuteButton className="w-9 h-9" />
|
||||||
</Button>
|
<DeafButton className="w-9 h-9" />
|
||||||
<ScreenshareButton className="w-9 h-9" />
|
<ScreenshareButton className="w-9 h-9" />
|
||||||
<Button
|
<Button
|
||||||
size="lg"
|
size="lg"
|
||||||
className="w-9 h-9"
|
className="w-9 h-9"
|
||||||
onClick={() => disconnect()}
|
onClick={() => disconnect()}
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
>
|
>
|
||||||
<LeaveIcon />
|
<LeaveIcon />
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ConnectionBar({
|
function ConnectionBar() {
|
||||||
state,
|
const { state, room, openCallPage, callId } = useCall();
|
||||||
encrypted,
|
const encrypted =
|
||||||
}: {
|
room.localParticipant.isE2EEEnabled && room.localParticipant.isEncrypted;
|
||||||
state: "closed" | "closing" | "connecting" | "open" | "encrypting";
|
|
||||||
encrypted: boolean;
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<Tooltip>
|
||||||
<PopoverTrigger
|
<TooltipTrigger
|
||||||
render={
|
render={
|
||||||
<Button
|
<Button
|
||||||
|
onClick={() => openCallPage(callId || "")}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant={
|
variant={
|
||||||
state === "open" && encrypted ? "subtleDefault" : "destructive"
|
state === "open" && encrypted ? "subtleDefault" : "destructive"
|
||||||
|
|
@ -73,24 +69,133 @@ function ConnectionBar({
|
||||||
{state === "closed" && "Closed"}
|
{state === "closed" && "Closed"}
|
||||||
{state === "closing" && "Closing"}
|
{state === "closing" && "Closing"}
|
||||||
|
|
||||||
<Tooltip>
|
<TinyPingGraph />
|
||||||
<TooltipTrigger
|
|
||||||
render={
|
{encrypted ? (
|
||||||
encrypted ? (
|
<Lock color="var(--primary-foreground-alt)" />
|
||||||
<Lock color="var(--primary-foreground-alt)" />
|
) : (
|
||||||
) : (
|
<LockOpen color="var(--destructive)" />
|
||||||
<LockOpen color="var(--destructive)" />
|
)}
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<TooltipContent>
|
|
||||||
{encrypted ? "Encrypted" : "Unencrypted"}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<PopoverContent side="top">Test stuff</PopoverContent>
|
<TooltipContent>Click to open call page</TooltipContent>
|
||||||
</Popover>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function TinyPingGraph() {
|
||||||
|
const { room } = useCall();
|
||||||
|
|
||||||
|
const [mapData, setMapData] = useState<Map<number, number>>(
|
||||||
|
() => new Map([[Date.now(), 0]]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = Array.from(mapData, ([time, ping]) => ({ time, ping }));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(async () => {
|
||||||
|
const now = Date.now();
|
||||||
|
const ping = await getPing(room);
|
||||||
|
const cutoff = now - 10_000;
|
||||||
|
|
||||||
|
setMapData((prev) => {
|
||||||
|
const next = new Map(prev);
|
||||||
|
|
||||||
|
next.set(now, ping || 0);
|
||||||
|
|
||||||
|
for (const time of next.keys()) {
|
||||||
|
if (time < cutoff) next.delete(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, 2_000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [room]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger
|
||||||
|
render={
|
||||||
|
<div
|
||||||
|
className="w-full h-6"
|
||||||
|
style={{
|
||||||
|
WebkitMaskImage:
|
||||||
|
"linear-gradient(to right, transparent 0%, var(--primary) 15%, var(--primary) 85%, transparent 100%)",
|
||||||
|
maskImage:
|
||||||
|
"linear-gradient(to right, transparent 0%, var(--primary) 15%, var(--primary) 85%, transparent 100%)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
<AreaChart
|
||||||
|
data={data}
|
||||||
|
margin={{ top: 2, right: 0, bottom: 2, left: 0 }}
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="pingGradient" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop
|
||||||
|
offset="0%"
|
||||||
|
stopColor="currentColor"
|
||||||
|
stopOpacity={0.35}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset="70%"
|
||||||
|
stopColor="currentColor"
|
||||||
|
stopOpacity={0.08}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset="100%"
|
||||||
|
stopColor="currentColor"
|
||||||
|
stopOpacity={0}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<Area
|
||||||
|
type="monotone"
|
||||||
|
dataKey="ping"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth={2}
|
||||||
|
fill="url(#pingGradient)"
|
||||||
|
fillOpacity={1}
|
||||||
|
dot={false}
|
||||||
|
activeDot={false}
|
||||||
|
isAnimationActive={false}
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TooltipContent>{data.at(-1)?.ping} ms</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPing(room: Room): Promise<number | undefined> {
|
||||||
|
const report = await room.localParticipant
|
||||||
|
.getTrackPublication(Track.Source.Microphone)
|
||||||
|
?.track?.getRTCStatsReport();
|
||||||
|
|
||||||
|
if (!report) return;
|
||||||
|
|
||||||
|
let bestRtt: number | undefined;
|
||||||
|
|
||||||
|
report.forEach((stat) => {
|
||||||
|
if (
|
||||||
|
stat.type === "candidate-pair" &&
|
||||||
|
stat.state === "succeeded" &&
|
||||||
|
stat.currentRoundTripTime != null
|
||||||
|
) {
|
||||||
|
bestRtt = stat.currentRoundTripTime * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stat.type === "remote-inbound-rtp" && stat.roundTripTime != null) {
|
||||||
|
bestRtt = stat.roundTripTime * 1000;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Math.round(bestRtt || 0);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,17 @@ export default function Provider(props: { children: React.ReactNode }) {
|
||||||
typeof ttp.call_data.response
|
typeof ttp.call_data.response
|
||||||
> | null>(null);
|
> | null>(null);
|
||||||
|
|
||||||
|
const [deaf, setDeaf] = useState(false);
|
||||||
|
|
||||||
|
const toggleDeaf = () => {
|
||||||
|
setDeaf((wasDeaf) => {
|
||||||
|
room.remoteParticipants.forEach((participant) => {
|
||||||
|
participant.setVolume(wasDeaf ? 100 : 0);
|
||||||
|
});
|
||||||
|
return !wasDeaf;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const openCallPage = (callId: string) =>
|
const openCallPage = (callId: string) =>
|
||||||
navigate({ to: "/call", search: { id: callId } });
|
navigate({ to: "/call", search: { id: callId } });
|
||||||
|
|
||||||
|
|
@ -166,6 +177,9 @@ export default function Provider(props: { children: React.ReactNode }) {
|
||||||
callId,
|
callId,
|
||||||
openCallPage,
|
openCallPage,
|
||||||
|
|
||||||
|
deaf,
|
||||||
|
toggleDeaf,
|
||||||
|
|
||||||
disconnect,
|
disconnect,
|
||||||
joinCall,
|
joinCall,
|
||||||
currentCallData,
|
currentCallData,
|
||||||
|
|
@ -252,6 +266,8 @@ type contextType = {
|
||||||
room: Room;
|
room: Room;
|
||||||
openCallPage: (callId: string) => Promise<void>;
|
openCallPage: (callId: string) => Promise<void>;
|
||||||
callId: string | null;
|
callId: string | null;
|
||||||
|
deaf: boolean;
|
||||||
|
toggleDeaf: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useCall(): contextType {
|
export function useCall(): contextType {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue