This commit is contained in:
Халимов Рустам
2026-03-11 01:25:19 +03:00
parent 3ab9cdbb2a
commit a0ccdb052f
+29 -33
View File
@@ -1535,35 +1535,43 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
</div> </div>
</> </>
)} )}
{/* === Video layout (connected, has video) === */} {/* === Main Content Area === */}
{showVideoArea ? ( <div className="flex-1 flex flex-col relative overflow-hidden">
<div {/*
ref={videoContainerRef} CRITICAL: Single stable remote video element.
className="relative bg-black w-full" We keep it always mounted so WebRTC srcObject is never lost
style={{ aspectRatio: '16 / 9' }} during transitions between voice and video modes.
> */}
<div className="absolute inset-0 bg-black flex items-center justify-center">
{/* Unified Remote video — always rendered if area is shown to keep stream alive & prevent ref-swapping issues */}
<video <video
ref={remoteVideoRef} ref={remoteVideoRef}
autoPlay autoPlay
playsInline playsInline
className={`absolute inset-0 w-full h-full object-contain bg-black transition-opacity duration-300 ${hasRemoteVideo ? 'opacity-100' : 'opacity-0'}`} className={`
bg-black transition-all duration-500
${showVideoArea
? 'w-full h-full object-contain'
: 'w-0 h-0 opacity-0 absolute pointer-events-none'
}
`}
onContextMenu={(e) => { e.preventDefault(); setShowVolumeSlider(true); }} onContextMenu={(e) => { e.preventDefault(); setShowVolumeSlider(true); }}
/> />
{/* Placeholder shown when video track is muted or not yet arrived */} {showVideoArea ? (
<div
ref={videoContainerRef}
className="absolute inset-0 flex items-center justify-center bg-black"
>
{/* Placeholder shown when video track is not yet arrived or muted */}
{!hasRemoteVideo && ( {!hasRemoteVideo && (
<div className="absolute inset-0 flex flex-col items-center justify-center bg-zinc-900/50 backdrop-blur-sm z-10"> <div className="absolute inset-0 flex flex-col items-center justify-center bg-zinc-900/50 backdrop-blur-sm z-10">
<VideoOff size={48} className="text-zinc-500 mb-2" /> <VideoOff size={48} className="text-zinc-500 mb-2" />
<span className="text-sm text-zinc-500">{displayName}</span> <span className="text-sm text-zinc-500">{displayName}</span>
</div> </div>
)} )}
</div>
{/* Local video PIP (bottom-right) */} {/* Local video PIP (bottom-right) */}
{hasLocalVideo && ( {hasLocalVideo && (
<div className="absolute bottom-3 right-3 w-48 rounded-xl overflow-hidden border-2 border-white/20 shadow-lg bg-black z-10" <div className="absolute bottom-3 right-3 w-48 rounded-xl overflow-hidden border-2 border-white/20 shadow-lg bg-black z-20"
style={{ aspectRatio: '16 / 9' }} style={{ aspectRatio: '16 / 9' }}
> >
<video <video
@@ -1577,16 +1585,15 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
)} )}
{/* Duration badge */} {/* Duration badge */}
<div className="absolute top-4 left-1/2 -translate-x-1/2 px-4 py-1 rounded-full bg-black/50 backdrop-blur-sm z-10"> <div className="absolute top-4 left-1/2 -translate-x-1/2 px-4 py-1 rounded-full bg-black/50 backdrop-blur-sm z-20">
<span className="text-sm text-white font-mono">{formatDuration(duration)}</span> <span className="text-sm text-white font-mono">{formatDuration(duration)}</span>
</div> </div>
{/* Top-right buttons: minimize + fullscreen */} {/* Top-right buttons */}
<div className="absolute top-4 right-4 flex items-center gap-2 z-10"> <div className="absolute top-4 right-4 flex items-center gap-2 z-20">
<button <button
onClick={() => setIsMinimized(true)} onClick={() => setIsMinimized(true)}
className="w-8 h-8 rounded-full bg-black/50 backdrop-blur-sm flex items-center justify-center text-white/70 hover:text-white transition-colors" className="w-8 h-8 rounded-full bg-black/50 backdrop-blur-sm flex items-center justify-center text-white/70 hover:text-white transition-colors"
title={t('minimize')}
> >
<Minimize2 size={16} /> <Minimize2 size={16} />
</button> </button>
@@ -1600,7 +1607,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
</div> </div>
) : ( ) : (
/* === Voice / calling / incoming layout === */ /* === Voice / calling / incoming layout === */
<div className="px-8 py-12 flex flex-col items-center relative z-10"> <div className="flex-1 px-8 py-12 flex flex-col items-center justify-center relative z-10">
{/* Minimize button (top-right) */} {/* Minimize button (top-right) */}
{callState === 'connected' && ( {callState === 'connected' && (
<button <button
@@ -1611,17 +1618,8 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
<Minimize2 size={16} /> <Minimize2 size={16} />
</button> </button>
)} )}
{/* Avatar with pulse — right-click for volume */}
<div <div className="relative mb-8 mt-4">
className="relative mb-8 mt-4"
onContextMenu={(e) => {
if (callState === 'connected') {
e.preventDefault();
setShowVolumeSlider(true);
}
}}
title={callState === 'connected' ? t('rightClickVolume') : undefined}
>
{(callState === 'calling' || callState === 'connected') && ( {(callState === 'calling' || callState === 'connected') && (
<> <>
<div className="absolute inset-0 rounded-full bg-vortex-500/30 animate-call-wave" /> <div className="absolute inset-0 rounded-full bg-vortex-500/30 animate-call-wave" />
@@ -1655,7 +1653,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
{/* Local video preview (during calling with camera) */} {/* Local video preview (during calling with camera) */}
{hasLocalVideo && callState === 'calling' && ( {hasLocalVideo && callState === 'calling' && (
<div className="mb-6 rounded-2xl overflow-hidden bg-zinc-900 w-48"> <div className="mb-6 rounded-2xl overflow-hidden bg-zinc-900 w-48 shadow-2xl border border-white/10">
<video <video
ref={localVideoRef} ref={localVideoRef}
autoPlay autoPlay
@@ -1665,11 +1663,9 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
/> />
</div> </div>
)} )}
{/* Hidden remote video element */}
<video ref={remoteVideoRef} autoPlay playsInline className="hidden" />
</div> </div>
)} )}
</div>
{/* === Controls === */} {/* === Controls === */}
<div className="px-8 pb-10 flex items-center justify-center gap-4 relative z-10 flex-wrap"> <div className="px-8 pb-10 flex items-center justify-center gap-4 relative z-10 flex-wrap">