// src/components/control-tray/ControlTray.tsx /** Copyright 2024 Google LLC ... (لایسنس) ... */ import cn from "classnames"; import React, { memo, ReactNode, RefObject, useEffect, useState } from "react"; import { useLiveAPIContext } from "../../contexts/LiveAPIContext"; import { AudioRecorder } from "../../lib/audio-recorder"; import LogoAnimation from '../logo-animation/LogoAnimation'; const SvgPauseIcon = () => ; const SvgCameraIcon = () => ; const SvgStopCamIcon = () => ; const SvgSwitchCameraIcon = () => ; export type ControlTrayProps = { videoRef: RefObject; supportsVideo: boolean; onVideoStreamChange: (stream: MediaStream | null) => void; isAppMicActive: boolean; onAppMicToggle: (active: boolean) => void; isAppCamActive: boolean; onAppCamToggle: (active: boolean) => void; ReferenceMicrophoneIcon: () => JSX.Element; currentFacingMode: 'user' | 'environment'; setCurrentFacingMode: React.Dispatch>; }; const ControlTray: React.FC = ({ videoRef, onVideoStreamChange, supportsVideo, isAppMicActive, onAppMicToggle, isAppCamActive, onAppCamToggle, ReferenceMicrophoneIcon, currentFacingMode, setCurrentFacingMode, }) => { const { client, connected, connect } = useLiveAPIContext(); const [audioRecorder] = useState(() => new AudioRecorder()); const [activeLocalVideoStream, setActiveLocalVideoStream] = useState(null); const [isSwitchingCamera, setIsSwitchingCamera] = useState(false); const renderCanvasRef = React.useRef(null); useEffect(() => { if (isAppCamActive && !activeLocalVideoStream && !isSwitchingCamera) { startWebcam(currentFacingMode); // استفاده از currentFacingMode از پراپ } else if (!isAppCamActive && activeLocalVideoStream) { stopWebcam(); } }, [isAppCamActive, activeLocalVideoStream, isSwitchingCamera, currentFacingMode]); useEffect(() => { /* ... audioRecorder ... */ }, [connected, client, isAppMicActive, audioRecorder]); useEffect(() => { /* ... sendVideoFrame ... */ }, [connected, activeLocalVideoStream, client, videoRef, renderCanvasRef]); useEffect(() => { /* ... videoRef.srcObject ... */ }, [activeLocalVideoStream, videoRef]); const ensureConnectedAndReady = async (): Promise => { if (!connected) { try { await connect(); return true; } catch (err) { console.error('❌ CT Connect err:', err); return false; } } return true; }; const handleMicToggle = async () => { if (isSwitchingCamera) return; const newMicState = !isAppMicActive; if (newMicState && !(await ensureConnectedAndReady())) { onAppMicToggle(false); return; } onAppMicToggle(newMicState); }; const startWebcam = async (facingModeToTry: 'user' | 'environment') => { if (isSwitchingCamera) return; setIsSwitchingCamera(true); try { const mediaStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: facingModeToTry }, audio: false }); setActiveLocalVideoStream(mediaStream); onVideoStreamChange(mediaStream); // setCurrentFacingMode در اینجا نباید مستقیما ست شود، چون از پراپ می آید و در handleSwitchCamera و handleCamToggle مدیریت می شود } catch (error) { console.error(`❌ Start WC err ${facingModeToTry}:`, error); setActiveLocalVideoStream(null); onVideoStreamChange(null); onAppCamToggle(false); } finally { setIsSwitchingCamera(false); } }; const stopWebcam = () => { if (activeLocalVideoStream) activeLocalVideoStream.getTracks().forEach(track => track.stop()); setActiveLocalVideoStream(null); onVideoStreamChange(null); }; const handleCamToggle = async () => { if (isSwitchingCamera) return; const newCamState = !isAppCamActive; if (newCamState) { if (!(await ensureConnectedAndReady())) { onAppCamToggle(false); return; } if (!isAppMicActive) { onAppMicToggle(true); } onAppCamToggle(true); // این باعث اجرای useEffect برای startWebcam با currentFacingMode فعلی می شود } else { onAppCamToggle(false); } }; const handleSwitchCamera = async () => { if (!isAppCamActive || !activeLocalVideoStream || isSwitchingCamera) return; setIsSwitchingCamera(true); const targetFacingMode = currentFacingMode === 'user' ? 'environment' : 'user'; activeLocalVideoStream.getTracks().forEach(track => track.stop()); try { const newStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: { exact: targetFacingMode } }, audio: false }); setActiveLocalVideoStream(newStream); onVideoStreamChange(newStream); setCurrentFacingMode(targetFacingMode); // آپدیت state در App.tsx } catch (error) { console.error(`❌ Switch Cam err ${targetFacingMode}:`, error); try { const restoredStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: currentFacingMode }, audio: false }); // بازگشت به دوربین قبلی setActiveLocalVideoStream(restoredStream); onVideoStreamChange(restoredStream); // setCurrentFacingMode تغییر نمی کند } catch (restoreError) { console.error('❌ Restore Cam err:', restoreError); setActiveLocalVideoStream(null); onVideoStreamChange(null); onAppCamToggle(false); } } finally { setIsSwitchingCamera(false); } }; return ( ); }; export default memo(ControlTray);