![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/lavocat.ca/public_html/src/components/Chat/ |
import React, { useRef, useEffect, useState } from 'react';
interface VideoCallProps {
roomName: string;
onLeave: () => void;
localStream: MediaStream | null;
remoteStream: MediaStream | null;
onEndCall: () => void;
callInProgress: boolean;
}
const VideoCall: React.FC<VideoCallProps> = ({
roomName,
onLeave,
localStream,
remoteStream,
onEndCall,
callInProgress,
}) => {
const localVideoRef = useRef<HTMLVideoElement>(null);
const remoteVideoRef = useRef<HTMLVideoElement>(null);
const [isLocalMuted, setIsLocalMuted] = useState(false);
const [isLocalVideoOff, setIsLocalVideoOff] = useState(false);
const [connectionStatus, setConnectionStatus] = useState<'connecting' | 'connected' | 'failed'>('connecting');
// ✅ ADD CALL TIMER
const [callDuration, setCallDuration] = useState(0);
const [callStartTime] = useState<number>(Date.now());
// ✅ Update call timer every second
useEffect(() => {
const timer = setInterval(() => {
setCallDuration(Math.floor((Date.now() - callStartTime) / 1000));
}, 1000);
return () => clearInterval(timer);
}, [callStartTime]);
// ✅ Format call duration (mm:ss)
const formatCallDuration = (seconds: number): string => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
};
useEffect(() => {
if (localVideoRef.current && localStream) {
localVideoRef.current.srcObject = localStream;
}
}, [localStream]);
useEffect(() => {
if (remoteVideoRef.current && remoteStream) {
remoteVideoRef.current.srcObject = remoteStream;
setConnectionStatus('connected');
} else if (callInProgress) {
setConnectionStatus('connecting');
}
}, [remoteStream, callInProgress]);
const toggleMute = () => {
if (localStream) {
const audioTracks = localStream.getAudioTracks();
audioTracks.forEach(track => {
track.enabled = isLocalMuted;
});
setIsLocalMuted(!isLocalMuted);
}
};
const toggleVideo = () => {
if (localStream) {
const videoTracks = localStream.getVideoTracks();
videoTracks.forEach(track => {
track.enabled = isLocalVideoOff;
});
setIsLocalVideoOff(!isLocalVideoOff);
}
};
const handleEndCall = () => {
// Clean up streams before ending call
if (localStream) {
localStream.getTracks().forEach(track => track.stop());
}
onEndCall();
};
return (
<div className="fixed inset-0 bg-gray-900 flex flex-col z-50">
{/* Header with Call Timer and Status */}
<div className="bg-gray-800 text-white p-4 flex items-center justify-between">
<div>
<h2 className="text-lg font-semibold flex items-center gap-2">
🎥 {roomName}
</h2>
<div className="flex items-center gap-4">
{/* ✅ CONNECTION STATUS */}
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${
connectionStatus === 'connected' ? 'bg-green-500 animate-pulse' :
connectionStatus === 'connecting' ? 'bg-yellow-500 animate-pulse' :
'bg-red-500'
}`}></div>
<p className="text-sm text-gray-300">
{connectionStatus === 'connecting' && 'Connecting...'}
{connectionStatus === 'connected' && 'Live'}
{connectionStatus === 'failed' && 'Connection failed'}
</p>
</div>
{/* ✅ CALL TIMER */}
<div className="flex items-center gap-2 bg-gray-700 px-3 py-1 rounded-lg">
<svg className="w-4 h-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clipRule="evenodd" />
</svg>
<span className="text-sm font-mono text-white">
{formatCallDuration(callDuration)}
</span>
</div>
{/* ✅ DEV MODE INDICATOR */}
{localStream && localStream.getVideoTracks().some(track =>
track.label?.includes('canvas') || track.kind === 'video' && !track.label
) && (
<span className="text-xs bg-blue-600 px-2 py-1 rounded-full animate-pulse">
🎭 DEV MODE
</span>
)}
</div>
</div>
{/* ✅ IMPROVED END CALL BUTTON */}
<button
onClick={handleEndCall}
className="bg-red-600 hover:bg-red-700 text-white px-6 py-2 rounded-lg transition-all duration-200 flex items-center gap-2 hover:scale-105 transform"
>
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M3 3a1 1 0 000 2v8a2 2 0 002 2h2.586l-1.293 1.293a1 1 0 101.414 1.414L10 15.414l2.293 2.293a1 1 0 001.414-1.414L12.414 15H15a2 2 0 002-2V5a1 1 0 100-2H3zm11.707 4.707a1 1 0 00-1.414-1.414L10 9.586 6.707 6.293a1 1 0 00-1.414 1.414L8.586 11l-3.293 3.293a1 1 0 001.414 1.414L10 12.414l3.293 3.293a1 1 0 001.414-1.414L11.414 11l3.293-3.293z" clipRule="evenodd" />
</svg>
End Call
</button>
</div>
{/* Video Area */}
<div className="flex-1 relative bg-black">
{/* Remote Video */}
{remoteStream ? (
<video
ref={remoteVideoRef}
autoPlay
playsInline
className="w-full h-full object-contain"
/>
) : (
<div className="w-full h-full flex items-center justify-center text-white">
<div className="text-center">
{connectionStatus === 'connecting' ? (
<>
<div className="w-16 h-16 border-4 border-t-transparent border-blue-500 rounded-full animate-spin mx-auto mb-4"></div>
<p className="text-xl mb-2">🔄 Connecting to call...</p>
<p className="text-sm text-gray-400">Waiting for the other person to join</p>
<div className="mt-4 text-xs text-gray-500">
Call duration: {formatCallDuration(callDuration)}
</div>
</>
) : (
<>
<div className="w-20 h-20 bg-gray-700 rounded-full flex items-center justify-center mb-4 mx-auto">
<svg className="w-10 h-10 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clipRule="evenodd" />
</svg>
</div>
<p className="text-lg">📹 No video from other participant</p>
<p className="text-sm text-gray-400 mt-2">Audio connection may still be active</p>
</>
)}
</div>
</div>
)}
{/* ✅ ENHANCED LOCAL VIDEO WITH BETTER STYLING */}
{localStream && (
<div className="absolute top-4 right-4 w-48 h-36 bg-gray-800 rounded-lg overflow-hidden border-2 border-gray-600 shadow-2xl">
{isLocalVideoOff ? (
<div className="w-full h-full flex items-center justify-center bg-gray-700">
<svg className="w-8 h-8 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A2 2 0 0017 14V6a2 2 0 00-2-2h-5.586L3.707 2.293z" clipRule="evenodd" />
</svg>
</div>
) : (
<video
ref={localVideoRef}
autoPlay
playsInline
muted
className="w-full h-full object-cover"
/>
)}
<div className="absolute bottom-2 left-2 text-xs text-white bg-black bg-opacity-75 px-2 py-1 rounded">
📹 You
</div>
{/* ✅ MUTE INDICATOR ON LOCAL VIDEO */}
{isLocalMuted && (
<div className="absolute top-2 right-2 bg-red-600 rounded-full p-1">
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM12.293 7.293a1 1 0 011.414 0L15 8.586l1.293-1.293a1 1 0 111.414 1.414L16.414 10l1.293 1.293a1 1 0 01-1.414 1.414L15 11.414l-1.293 1.293a1 1 0 01-1.414-1.414L13.586 10l-1.293-1.293a1 1 0 010-1.414z" clipRule="evenodd" />
</svg>
</div>
)}
</div>
)}
{/* ✅ CONNECTION QUALITY INDICATOR */}
<div className="absolute top-4 left-4 bg-black bg-opacity-50 rounded-lg p-2 text-white text-sm">
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${
connectionStatus === 'connected' ? 'bg-green-500' :
connectionStatus === 'connecting' ? 'bg-yellow-500' :
'bg-red-500'
}`}></div>
<span>
{connectionStatus === 'connected' ? '🟢 HD Quality' :
connectionStatus === 'connecting' ? '🟡 Connecting...' :
'🔴 Poor Connection'}
</span>
</div>
</div>
</div>
{/* ✅ ENHANCED CONTROLS WITH BETTER STYLING */}
<div className="bg-gray-800 p-6 flex items-center justify-center space-x-6">
<button
onClick={toggleMute}
className={`w-14 h-14 rounded-full flex items-center justify-center transition-all duration-200 transform hover:scale-110 ${
isLocalMuted
? 'bg-red-600 hover:bg-red-700 ring-2 ring-red-400'
: 'bg-gray-600 hover:bg-gray-700'
}`}
title={isLocalMuted ? 'Unmute' : 'Mute'}
>
{isLocalMuted ? (
<svg className="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM12.293 7.293a1 1 0 011.414 0L15 8.586l1.293-1.293a1 1 0 111.414 1.414L16.414 10l1.293 1.293a1 1 0 01-1.414 1.414L15 11.414l-1.293 1.293a1 1 0 01-1.414-1.414L13.586 10l-1.293-1.293a1 1 0 010-1.414z" clipRule="evenodd" />
</svg>
) : (
<svg className="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM14.657 2.929a1 1 0 011.414 0A9.972 9.972 0 0119 10a9.972 9.972 0 01-2.929 7.071 1 1 0 01-1.414-1.414A7.971 7.971 0 0017 10c0-2.21-.894-4.208-2.343-5.657a1 1 0 010-1.414zm-2.829 2.828a1 1 0 011.415 0A5.983 5.983 0 0115 10a5.984 5.984 0 01-1.757 4.243 1 1 0 01-1.415-1.415A3.984 3.984 0 0013 10a3.983 3.983 0 00-1.172-2.828 1 1 0 010-1.415z" clipRule="evenodd" />
</svg>
)}
</button>
<button
onClick={toggleVideo}
className={`w-14 h-14 rounded-full flex items-center justify-center transition-all duration-200 transform hover:scale-110 ${
isLocalVideoOff
? 'bg-red-600 hover:bg-red-700 ring-2 ring-red-400'
: 'bg-gray-600 hover:bg-gray-700'
}`}
title={isLocalVideoOff ? 'Turn on camera' : 'Turn off camera'}
>
{isLocalVideoOff ? (
<svg className="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A2 2 0 0017 14V6a2 2 0 00-2-2h-5.586L3.707 2.293z" clipRule="evenodd" />
</svg>
) : (
<svg className="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M2 6a2 2 0 012-2h6l2 2h6a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6zM14 8a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
)}
</button>
<button
onClick={handleEndCall}
className="w-16 h-16 rounded-full bg-red-600 hover:bg-red-700 flex items-center justify-center transition-all duration-200 transform hover:scale-110 ring-2 ring-red-400"
title="End call"
>
<svg className="w-8 h-8 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M3 3a1 1 0 000 2v8a2 2 0 002 2h2.586l-1.293 1.293a1 1 0 101.414 1.414L10 15.414l2.293 2.293a1 1 0 001.414-1.414L12.414 15H15a2 2 0 002-2V5a1 1 0 100-2H3zm11.707 4.707a1 1 0 00-1.414-1.414L10 9.586 6.707 6.293a1 1 0 00-1.414 1.414L8.586 11l-3.293 3.293a1 1 0 001.414 1.414L10 12.414l3.293 3.293a1 1 0 001.414-1.414L11.414 11l3.293-3.293z" clipRule="evenodd" />
</svg>
</button>
</div>
</div>
);
};
export default VideoCall;