@@ -22,6 +22,9 @@ interface ChatMessage {
2222const InRoom : React . FC = ( ) => {
2323 const { roomName } = useParams < { roomName : string } > ( ) ;
2424 const navigate = useNavigate ( ) ;
25+ const [ permissionError , setPermissionError ] = useState < string | null > ( null ) ;
26+ const [ showPermissionModal , setShowPermissionModal ] = useState ( false ) ;
27+
2528
2629 const [ micOn , setMicOn ] = useState ( true ) ;
2730 const [ videoOn , setVideoOn ] = useState ( true ) ;
@@ -233,32 +236,50 @@ const InRoom: React.FC = () => {
233236 } , [ roomName , userName ] ) ;
234237
235238 // Initialize media stream
236- useEffect ( ( ) => {
237- const initMedia = async ( ) => {
238- try {
239- const stream = await navigator . mediaDevices . getUserMedia ( {
240- video : true ,
241- audio : true ,
242- } ) ;
243- mediaStreamRef . current = stream ;
244- if ( localVideoRef . current ) localVideoRef . current . srcObject = stream ;
245- setMicOn ( stream . getAudioTracks ( ) . some ( ( t ) => t . enabled ) ) ;
246- setVideoOn ( stream . getVideoTracks ( ) . some ( ( t ) => t . enabled ) ) ;
247- } catch ( err ) {
248- console . error ( "Error accessing camera/mic:" , err ) ;
249- alert ( "Please allow camera and microphone permissions." ) ;
250- setMicOn ( false ) ;
251- setVideoOn ( false ) ;
239+ const initMedia = async ( ) => {
240+ try {
241+ const stream = await navigator . mediaDevices . getUserMedia ( {
242+ audio : true ,
243+ video : true ,
244+ } ) ;
245+
246+ // Success
247+ setPermissionError ( null ) ;
248+ setShowPermissionModal ( false ) ;
249+
250+ // Save stream to your refs/states
251+ mediaStreamRef . current = stream ;
252+ setMicOn ( true ) ;
253+ setVideoOn ( true ) ;
254+
255+ // Attach tracks, send to peer, whatever your flow is
256+ if ( localVideoRef . current ) {
257+ localVideoRef . current . srcObject = stream ;
252258 }
253- } ;
254259
255- initMedia ( ) ;
260+ } catch ( err : any ) {
261+ console . error ( "Error accessing camera/mic:" , err ) ;
256262
257- return ( ) => {
258- mediaStreamRef . current ?. getTracks ( ) . forEach ( ( track ) => track . stop ( ) ) ;
259- } ;
263+ let msg = "Camera/Microphone access was denied. Please allow permissions." ;
264+
265+ if ( err . name === "NotAllowedError" ) {
266+ msg = "You blocked camera/mic access for this site. Please enable it from browser settings." ;
267+ } else if ( err . name === "NotFoundError" ) {
268+ msg = "No camera or microphone was found on your device." ;
269+ }
270+
271+ setPermissionError ( msg ) ;
272+ setShowPermissionModal ( true ) ;
273+
274+ setMicOn ( false ) ;
275+ setVideoOn ( false ) ;
276+ }
277+ } ;
278+ useEffect ( ( ) => {
279+ initMedia ( ) ;
260280 } , [ ] ) ;
261281
282+
262283 // When local media becomes available and a peer is present, ensure tracks are added
263284 useEffect ( ( ) => {
264285 // If we have a pc and a media stream, make sure tracks are added
@@ -452,6 +473,55 @@ const InRoom: React.FC = () => {
452473 </ div >
453474 ) ;
454475 }
476+ const permissionModal = showPermissionModal && (
477+ < div className = "fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50" >
478+ < motion . div
479+ initial = { { scale : 0.9 , opacity : 0 } }
480+ animate = { { scale : 1 , opacity : 1 } }
481+ exit = { { scale : 0.9 , opacity : 0 } }
482+ className = "bg-gray-900 text-white w-full max-w-md p-6 rounded-2xl shadow-xl border border-gray-700"
483+ >
484+ < h2 className = "text-xl font-semibold mb-3" > Permissions Required</ h2 >
485+
486+ < p className = "text-gray-300 mb-4 text-sm leading-relaxed" >
487+ { permissionError }
488+ </ p >
489+
490+ { /* Instructions when user BLOCKED permissions */ }
491+ { permissionError ?. includes ( "blocked" ) && (
492+ < div className = "text-sm text-gray-400 mb-4" >
493+ < p className = "mb-2" > To enable camera/mic permissions:</ p >
494+ < ul className = "list-disc ml-5 space-y-1" >
495+ < li > Click the lock icon in the URL bar</ li >
496+ < li > Open "Site Settings"</ li >
497+ < li > Set Camera and Microphone to "Allow"</ li >
498+ < li > Reload the page</ li >
499+ </ ul >
500+ </ div >
501+ ) }
502+
503+ < div className = "flex justify-end gap-3 mt-6" >
504+ < button
505+ onClick = { ( ) => setShowPermissionModal ( false ) }
506+ className = "px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg text-sm"
507+ >
508+ Close
509+ </ button >
510+
511+ < button
512+ onClick = { ( ) => {
513+ setShowPermissionModal ( false ) ;
514+ initMedia ( ) ; // 🔥 This re-triggers browser permission prompt
515+ } }
516+ className = "px-4 py-2 bg-indigo-600 hover:bg-indigo-500 rounded-lg text-sm font-medium"
517+ >
518+ Retry
519+ </ button >
520+ </ div >
521+ </ motion . div >
522+ </ div >
523+ ) ;
524+
455525
456526 return (
457527 < HotKeys keyMap = { keyMap } handlers = { handlers } >
@@ -477,6 +547,8 @@ const InRoom: React.FC = () => {
477547 </ header >
478548
479549 { /* Main Video Area */ }
550+ { permissionModal }
551+
480552 < div className = "flex-1 flex flex-col md:flex-row" >
481553 { /* Video Grid */ }
482554 < div className = "flex-1 grid grid-cols-1 md:grid-cols-2 gap-4 p-4" >
0 commit comments