@@ -26,6 +26,8 @@ const CustomizeExample: NextPage = () => {
2626 const [ connect , setConnect ] = useState ( false ) ;
2727 const [ isConnected , setIsConnected ] = useState ( false ) ;
2828 const [ error , setError ] = useState < string > ( ) ;
29+ const [ hasAudio , setHasAudio ] = useState ( false ) ;
30+ const [ hasVideo , setHasVideo ] = useState ( false ) ;
2931
3032 // Get room parameters once on mount
3133 const [ roomParams ] = useState ( ( ) => {
@@ -37,6 +39,30 @@ const CustomizeExample: NextPage = () => {
3739 } ;
3840 } ) ;
3941
42+ // Check available devices on mount
43+ useEffect ( ( ) => {
44+ async function checkDevices ( ) {
45+ try {
46+ const devices = await navigator . mediaDevices . enumerateDevices ( ) ;
47+ setHasAudio ( devices . some ( device => device . kind === 'audioinput' ) ) ;
48+ setHasVideo ( devices . some ( device => device . kind === 'videoinput' ) ) ;
49+ } catch ( e ) {
50+ console . warn ( 'Unable to check media devices:' , e ) ;
51+ setHasAudio ( false ) ;
52+ setHasVideo ( false ) ;
53+ }
54+ }
55+
56+ // Only check devices if on client side and API is available
57+ if ( typeof window !== 'undefined' && navigator . mediaDevices ) {
58+ checkDevices ( ) ;
59+
60+ // Listen for device changes
61+ navigator . mediaDevices . addEventListener ( 'devicechange' , checkDevices ) ;
62+ return ( ) => navigator . mediaDevices . removeEventListener ( 'devicechange' , checkDevices ) ;
63+ }
64+ } , [ ] ) ;
65+
4066 // Fetch token only when connect button is clicked
4167 const fetchToken = useCallback ( async ( ) => {
4268 try {
@@ -75,6 +101,11 @@ const CustomizeExample: NextPage = () => {
75101 } , [ ] ) ;
76102
77103 const handleError = useCallback ( ( err : Error ) => {
104+ // Don't treat missing devices as a fatal error
105+ if ( err . name === 'NotFoundError' || err . name === 'NotAllowedError' ) {
106+ console . warn ( 'Media device error:' , err ) ;
107+ return ;
108+ }
78109 console . error ( 'LiveKit error:' , err ) ;
79110 setError ( err . message ) ;
80111 handleDisconnect ( ) ;
@@ -87,8 +118,14 @@ const CustomizeExample: NextPage = () => {
87118 Welcome to < a href = "https://livekit.io" > LiveKit</ a >
88119 </ h1 >
89120
121+ { ! hasAudio && ! hasVideo && (
122+ < div style = { { backgroundColor : '#fff3cd' , color : '#856404' , padding : '1rem' , borderRadius : '4px' , margin : '1rem 0' } } >
123+ No camera or microphone detected. You can still join but won't be able to share audio or video.
124+ </ div >
125+ ) }
126+
90127 { error && (
91- < div style = { { color : 'red ' , margin : '1rem 0' } } >
128+ < div style = { { backgroundColor : '#f8d7da' , color : '#721c24' , padding : '1rem' , borderRadius : '4px ', margin : '1rem 0' } } >
92129 Error: { error }
93130 </ div >
94131 ) }
@@ -111,8 +148,16 @@ const CustomizeExample: NextPage = () => {
111148 onConnected = { ( ) => setIsConnected ( true ) }
112149 onDisconnected = { handleDisconnect }
113150 onError = { handleError }
114- audio = { true }
115- video = { true }
151+ // Only enable audio/video if devices are available
152+ audio = { hasAudio }
153+ video = { hasVideo }
154+ options = { {
155+ audioCaptureDefaults : {
156+ echoCancellation : true ,
157+ noiseSuppression : true ,
158+ autoGainControl : true ,
159+ } ,
160+ } }
116161 >
117162 < RoomAudioRenderer />
118163 { isConnected && < Stage /> }
0 commit comments