11'use client' ;
22
3+ import { useEffect , useMemo , useState } from 'react' ;
34import { type VariantProps } from 'class-variance-authority' ;
45import { Track } from 'livekit-client' ;
6+ import {
7+ type AgentState ,
8+ type TrackReference ,
9+ type TrackReferenceOrPlaceholder ,
10+ useLocalParticipant ,
11+ } from '@livekit/components-react' ;
512import { MicrophoneIcon } from '@phosphor-icons/react/dist/ssr' ;
13+ import { useSession } from '@/components/app/session-provider' ;
614import { AgentControlBar } from '@/components/livekit/agent-control-bar/agent-control-bar' ;
715import { TrackDeviceSelect } from '@/components/livekit/agent-control-bar/track-device-select' ;
816import { TrackSelector } from '@/components/livekit/agent-control-bar/track-selector' ;
917import { TrackToggle } from '@/components/livekit/agent-control-bar/track-toggle' ;
1018import { Alert , AlertDescription , AlertTitle , alertVariants } from '@/components/livekit/alert' ;
1119import { 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' ;
1225import { Button , buttonVariants } from '@/components/livekit/button' ;
1326import { ChatEntry } from '@/components/livekit/chat-entry' ;
1427import {
@@ -20,21 +33,31 @@ import {
2033} from '@/components/livekit/select' ;
2134import { ShimmerText } from '@/components/livekit/shimmer-text' ;
2235import { Toggle , toggleVariants } from '@/components/livekit/toggle' ;
23- import { cn } from '@/lib/utils' ;
2436
2537type toggleVariantsType = VariantProps < typeof toggleVariants > [ 'variant' ] ;
2638type toggleVariantsSizeType = VariantProps < typeof toggleVariants > [ 'size' ] ;
2739type buttonVariantsType = VariantProps < typeof buttonVariants > [ 'variant' ] ;
2840type buttonVariantsSizeType = VariantProps < typeof buttonVariants > [ 'size' ] ;
2941type 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
3154interface 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 : ( ) => (
0 commit comments