Skip to content

Commit b2fdb7c

Browse files
BarVisualizer
1 parent 628c94d commit b2fdb7c

File tree

8 files changed

+560
-86
lines changed

8 files changed

+560
-86
lines changed

app/ui/(landing-page)/page.tsx

Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,35 @@
11
'use client';
22

3+
import Link from 'next/link';
4+
import { useVoiceAssistant } from '@livekit/components-react';
35
import { AgentControlBar } from '@/components/livekit/agent-control-bar/agent-control-bar';
6+
import { AudioBarVisualizer } from '@/components/livekit/audio-visualizer/audio-bar-visualizer/audio-bar-visualizer';
47
import { Button } from '@/components/livekit/button';
58
import { ChatEntry } from '@/components/livekit/chat-entry';
9+
import { useMicrophone } from '../_components';
610

711
export default function Page() {
12+
const { state, audioTrack } = useVoiceAssistant();
13+
14+
useMicrophone();
15+
816
return (
917
<>
1018
<header className="grid h-96 place-content-center space-y-6 text-center">
11-
<h1 className="text-5xl">
12-
<span className="font-bold tracking-tighter">LiveKit</span>{' '}
13-
<span className="font-light tracking-tighter">UI</span>
19+
<h1 className="flex items-baseline justify-center gap-2 text-5xl">
20+
<svg
21+
height="48"
22+
viewBox="0 0 123 28"
23+
fill="none"
24+
xmlns="http://www.w3.org/2000/svg"
25+
className="text-foreground"
26+
>
27+
<path
28+
d="M4.7 0H0v27.6h17v-4H4.7V0ZM24.8 12.5h-4.5v15h4.5v-15ZM38.2 27 32.4 8H28l6 19.6h8.6l6-19.6H44l-5.8 19ZM59.8 7.6c-5.9 0-9.6 4.2-9.6 10.2 0 6 3.6 10.2 9.6 10.2 4.6 0 8-2 9.2-6.2h-4.6c-.7 1.9-2 3-4.5 3-2.8 0-4.7-2-5-5.7h14.4l.1-1.4c0-6.1-3.8-10.1-9.6-10.1Zm-5 8.4c.5-3.6 2.4-5.2 5-5.2 2.9 0 4.7 2 5 5.2h-10ZM96 0h-5.9L78.7 12.6V0H74v27.6h4.7v-14l12.6 14h6L84.1 13 96.1 0ZM104 8h-4.6v15h4.5V8ZM20.3 8h-4.6v4.5h4.6V8ZM108.5 23h-4.6v4.6h4.6V23ZM122 23h-4.5v4.6h4.6V23ZM122 12.5V8h-4.5V0H113v8h-4.6v4.5h4.6V23h4.5V12.5h4.6Z"
29+
fill="currentColor"
30+
/>
31+
</svg>
32+
<span className="font-extralight tracking-tighter">UI</span>
1433
</h1>
1534
<p className="text-lg text-pretty">
1635
A set of Open Source UI components for
@@ -19,43 +38,62 @@ export default function Page() {
1938
</p>
2039
<div className="flex justify-center gap-4">
2140
<Button variant="primary" asChild>
22-
<a href="/ui/components">View components</a>
41+
<Link href="/ui/components">View components</Link>
2342
</Button>
2443
<Button variant="ghost" asChild>
25-
<a href="https://docs.livekit.io/agents/start/frontend/">Read our docs</a>
44+
<Link href="https://docs.livekit.io/agents/start/frontend/">Read our docs</Link>
2645
</Button>
2746
</div>
2847
</header>
2948

30-
<main className="mx-auto max-w-3xl space-y-8">
31-
<div className="border-border bg-background h-96 rounded-3xl border p-8">
32-
<div className="flex h-full flex-col gap-4">
33-
<div className="flex-1 grow">
34-
<ChatEntry
35-
locale="en-US"
36-
name="User"
37-
message="Hello, how are you?"
38-
messageOrigin="local"
39-
timestamp={1761096559966}
49+
<main className="mx-auto max-w-5xl space-y-8">
50+
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
51+
<div className="border-border bg-background h-96 rounded-3xl border p-8">
52+
<div className="flex h-full flex-col gap-4">
53+
<div className="grid flex-1 grow place-content-center">
54+
<AudioBarVisualizer state={state} audioTrack={audioTrack!} />
55+
</div>
56+
<AgentControlBar
57+
className="w-full"
58+
controls={{
59+
leave: true,
60+
chat: true,
61+
camera: true,
62+
microphone: true,
63+
screenShare: true,
64+
}}
4065
/>
41-
<ChatEntry
42-
locale="en-US"
43-
name="Agent"
44-
message="I am good, how about you?"
45-
messageOrigin="remote"
46-
timestamp={1761096569216}
66+
</div>
67+
</div>
68+
<div className="border-border bg-background h-96 rounded-3xl border p-8">
69+
<div className="flex h-full flex-col gap-4">
70+
<div className="flex-1 grow">
71+
<ChatEntry
72+
locale="en-US"
73+
name="User"
74+
message="Hello, how are you?"
75+
messageOrigin="local"
76+
timestamp={1761096559966}
77+
/>
78+
<ChatEntry
79+
locale="en-US"
80+
name="Agent"
81+
message="I am good, how about you?"
82+
messageOrigin="remote"
83+
timestamp={1761096569216}
84+
/>
85+
</div>
86+
<AgentControlBar
87+
className="w-full"
88+
controls={{
89+
leave: true,
90+
chat: true,
91+
camera: true,
92+
microphone: true,
93+
screenShare: true,
94+
}}
4795
/>
4896
</div>
49-
<AgentControlBar
50-
className="w-full"
51-
controls={{
52-
leave: true,
53-
chat: true,
54-
camera: true,
55-
microphone: true,
56-
screenShare: true,
57-
}}
58-
/>
5997
</div>
6098
</div>
6199
</main>

app/ui/_components.tsx

Lines changed: 161 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
11
'use client';
22

3+
import { useEffect, useMemo, useState } from 'react';
34
import { type VariantProps } from 'class-variance-authority';
45
import { Track } from 'livekit-client';
6+
import {
7+
type AgentState,
8+
type TrackReference,
9+
type TrackReferenceOrPlaceholder,
10+
useLocalParticipant,
11+
} from '@livekit/components-react';
512
import { MicrophoneIcon } from '@phosphor-icons/react/dist/ssr';
13+
import { useSession } from '@/components/app/session-provider';
614
import { AgentControlBar } from '@/components/livekit/agent-control-bar/agent-control-bar';
715
import { TrackDeviceSelect } from '@/components/livekit/agent-control-bar/track-device-select';
816
import { TrackSelector } from '@/components/livekit/agent-control-bar/track-selector';
917
import { TrackToggle } from '@/components/livekit/agent-control-bar/track-toggle';
1018
import { Alert, AlertDescription, AlertTitle, alertVariants } from '@/components/livekit/alert';
1119
import { AlertToast } from '@/components/livekit/alert-toast';
20+
import { BarVisualizer } from '@/components/livekit/audio-visualizer/audio-bar-visualizer/_bar-visualizer';
21+
import {
22+
AudioBarVisualizer,
23+
audioBarVisualizerVariants,
24+
} from '@/components/livekit/audio-visualizer/audio-bar-visualizer/audio-bar-visualizer';
1225
import { Button, buttonVariants } from '@/components/livekit/button';
1326
import { ChatEntry } from '@/components/livekit/chat-entry';
1427
import {
@@ -20,21 +33,31 @@ import {
2033
} from '@/components/livekit/select';
2134
import { ShimmerText } from '@/components/livekit/shimmer-text';
2235
import { Toggle, toggleVariants } from '@/components/livekit/toggle';
23-
import { cn } from '@/lib/utils';
2436

2537
type toggleVariantsType = VariantProps<typeof toggleVariants>['variant'];
2638
type toggleVariantsSizeType = VariantProps<typeof toggleVariants>['size'];
2739
type buttonVariantsType = VariantProps<typeof buttonVariants>['variant'];
2840
type buttonVariantsSizeType = VariantProps<typeof buttonVariants>['size'];
2941
type alertVariantsType = VariantProps<typeof alertVariants>['variant'];
42+
type audioBarVisualizerVariantsSizeType = VariantProps<typeof audioBarVisualizerVariants>['size'];
43+
44+
export function useMicrophone() {
45+
const { startSession } = useSession();
46+
const { localParticipant } = useLocalParticipant();
47+
48+
useEffect(() => {
49+
startSession();
50+
localParticipant.setMicrophoneEnabled(true, undefined);
51+
}, [startSession, localParticipant]);
52+
}
3053

3154
interface ContainerProps {
3255
componentName: string;
3356
children: React.ReactNode;
3457
className?: string;
3558
}
3659

37-
function Container({ componentName, children, className }: ContainerProps) {
60+
function Container({ children, className }: ContainerProps) {
3861
return (
3962
<div className={className}>
4063
<div className="bg-background border-input space-y-4 rounded-3xl border p-8 drop-shadow-lg/5">
@@ -168,23 +191,143 @@ export const COMPONENTS = {
168191
</Container>
169192
),
170193

194+
// Audio visualizer
195+
AudioVisualizer: () => {
196+
const barCounts = ['0', '3', '5', '7', '9'];
197+
const sizes = ['icon', 'xs', 'sm', 'md', 'lg', 'xl'];
198+
const states = [
199+
'disconnected',
200+
'connecting',
201+
'initializing',
202+
'listening',
203+
'thinking',
204+
'speaking',
205+
] as AgentState[];
206+
207+
const { microphoneTrack, localParticipant } = useLocalParticipant();
208+
const [barCount, setBarCount] = useState<string>(barCounts[0]);
209+
const [size, setSize] = useState<audioBarVisualizerVariantsSizeType>(
210+
sizes[3] as audioBarVisualizerVariantsSizeType
211+
);
212+
const [state, setState] = useState<AgentState>(states[0]);
213+
214+
const micTrackRef = useMemo<TrackReferenceOrPlaceholder | undefined>(() => {
215+
return state === 'speaking'
216+
? ({
217+
participant: localParticipant,
218+
source: Track.Source.Microphone,
219+
publication: microphoneTrack,
220+
} as TrackReference)
221+
: undefined;
222+
}, [state, localParticipant, microphoneTrack]);
223+
224+
useMicrophone();
225+
226+
return (
227+
<Container componentName="AudioVisualizer">
228+
<div className="flex items-center gap-2">
229+
<div className="flex-1">
230+
<label className="font-mono text-xs uppercase" htmlFor="state">
231+
State
232+
</label>
233+
<Select value={state} onValueChange={(value) => setState(value as AgentState)}>
234+
<SelectTrigger id="state" className="w-full">
235+
<SelectValue placeholder="Select a state" />
236+
</SelectTrigger>
237+
<SelectContent>
238+
{states.map((state) => (
239+
<SelectItem key={state} value={state}>
240+
{state}
241+
</SelectItem>
242+
))}
243+
</SelectContent>
244+
</Select>
245+
</div>
246+
247+
<div className="flex-1">
248+
<label className="font-mono text-xs uppercase" htmlFor="size">
249+
Size
250+
</label>
251+
<Select
252+
value={size as string}
253+
onValueChange={(value) => setSize(value as audioBarVisualizerVariantsSizeType)}
254+
>
255+
<SelectTrigger id="size" className="w-full">
256+
<SelectValue placeholder="Select a size" />
257+
</SelectTrigger>
258+
<SelectContent>
259+
{sizes.map((size) => (
260+
<SelectItem key={size} value={size as string}>
261+
{size}
262+
</SelectItem>
263+
))}
264+
</SelectContent>
265+
</Select>
266+
</div>
267+
268+
<div className="flex-1">
269+
<label className="font-mono text-xs uppercase" htmlFor="barCount">
270+
Bar count
271+
</label>
272+
<Select value={barCount.toString()} onValueChange={(value) => setBarCount(value)}>
273+
<SelectTrigger id="barCount" className="w-full">
274+
<SelectValue placeholder="Select a bar count" />
275+
</SelectTrigger>
276+
<SelectContent>
277+
{barCounts.map((barCount) => (
278+
<SelectItem key={barCount} value={barCount.toString()}>
279+
{parseInt(barCount) || 'Default'}
280+
</SelectItem>
281+
))}
282+
</SelectContent>
283+
</Select>
284+
</div>
285+
</div>
286+
287+
<div className="relative flex flex-col justify-center gap-4">
288+
<AudioBarVisualizer
289+
size={size as audioBarVisualizerVariantsSizeType}
290+
state={state}
291+
audioTrack={micTrackRef!}
292+
barCount={parseInt(barCount) || undefined}
293+
className="mx-auto"
294+
/>
295+
<div className="border-border space-y-4 rounded-xl border p-4">
296+
<div className="text-center">Original BarVisualizer</div>
297+
<BarVisualizer
298+
size={size as audioBarVisualizerVariantsSizeType}
299+
state={state}
300+
audioTrack={micTrackRef!}
301+
barCount={parseInt(barCount) || undefined}
302+
className="mx-auto"
303+
/>
304+
</div>
305+
</div>
306+
</Container>
307+
);
308+
},
309+
171310
// Agent control bar
172-
AgentControlBar: () => (
173-
<Container componentName="AgentControlBar">
174-
<div className="relative flex items-center justify-center">
175-
<AgentControlBar
176-
className="w-full"
177-
controls={{
178-
leave: true,
179-
chat: true,
180-
camera: true,
181-
microphone: true,
182-
screenShare: true,
183-
}}
184-
/>
185-
</div>
186-
</Container>
187-
),
311+
AgentControlBar: () => {
312+
useMicrophone();
313+
314+
return (
315+
<Container componentName="AgentControlBar">
316+
<div className="relative flex items-center justify-center">
317+
<AgentControlBar
318+
className="w-full"
319+
controls={{
320+
leave: true,
321+
chat: true,
322+
camera: true,
323+
microphone: true,
324+
screenShare: true,
325+
}}
326+
/>
327+
</div>
328+
</Container>
329+
);
330+
},
188331

189332
// Track device select
190333
TrackDeviceSelect: () => (

app/ui/layout.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as React from 'react';
21
import { headers } from 'next/headers';
32
import Link from 'next/link';
43
import { SessionProvider } from '@/components/app/session-provider';

0 commit comments

Comments
 (0)