Ezmary's picture
Update src/components/control-tray/ControlTray.tsx
3e36429 verified
raw
history blame
7.26 kB
// src/components/control-tray/ControlTray.tsx
// ... (ایمپورت‌های اولیه و SVGهای دیگر مثل قبل) ...
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';
// ... (SVG Icons: SvgPauseIcon, SvgCameraIcon, SvgStopCamIcon, SvgSwitchCameraIcon بدون تغییر) ...
export type ControlTrayProps = {
videoRef: RefObject<HTMLVideoElement>;
supportsVideo: boolean;
onVideoStreamChange: (stream: MediaStream | null) => void;
isAppMicActive: boolean;
onAppMicToggle: (active: boolean) => void;
isAppCamActive: boolean;
onAppCamToggle: (active: boolean) => void;
ReferenceMicrophoneIcon: () => JSX.Element;
initialFacingMode: 'user' | 'environment'; // *** NEW PROP ***
onFacingModeChange: (newMode: 'user' | 'environment') => void; // *** NEW PROP ***
};
const ControlTray: React.FC<ControlTrayProps> = ({
videoRef,
onVideoStreamChange,
supportsVideo,
isAppMicActive,
onAppMicToggle,
isAppCamActive,
onAppCamToggle,
ReferenceMicrophoneIcon,
initialFacingMode, // *** NEW PROP ***
onFacingModeChange, // *** NEW PROP ***
}) => {
const { client, connected, connect } = useLiveAPIContext();
const [audioRecorder] = useState(() => new AudioRecorder());
const [activeLocalVideoStream, setActiveLocalVideoStream] = useState<MediaStream | null>(null);
// *** currentFacingMode حالا از initialFacingMode مقدار اولیه می‌گیرد ***
const [currentFacingMode, setCurrentFacingMode] = useState<'user' | 'environment'>(initialFacingMode);
const [isSwitchingCamera, setIsSwitchingCamera] = useState(false);
const renderCanvasRef = React.useRef<HTMLCanvasElement>(null);
// وقتی initialFacingMode از بیرون تغییر می‌کند، state داخلی را هم آپدیت کن
useEffect(() => {
setCurrentFacingMode(initialFacingMode);
}, [initialFacingMode]);
// ... (useEffect های audioRecorder, sendVideoFrame, videoRef.srcObject بدون تغییر) ...
useEffect(() => {
if (isAppCamActive && !activeLocalVideoStream && !isSwitchingCamera) {
// هنگام شروع وب‌کم، از currentFacingMode داخلی استفاده کن
startWebcam(currentFacingMode);
} else if (!isAppCamActive && activeLocalVideoStream) {
stopWebcam();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isAppCamActive, activeLocalVideoStream, isSwitchingCamera]); // currentFacingMode را از وابستگی‌ها حذف می‌کنیم تا startWebcam فقط یکبار با مقدار اولیه اجرا شود و بعدا توسط handleSwitchCamera مدیریت شود.
const startWebcam = async (facingModeToTry: 'user' | 'environment' = currentFacingMode) => {
if (isSwitchingCamera) return;
setIsSwitchingCamera(true);
try {
const mediaStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: facingModeToTry }, audio: false });
setActiveLocalVideoStream(mediaStream);
onVideoStreamChange(mediaStream);
setCurrentFacingMode(facingModeToTry); // آپدیت state داخلی
onFacingModeChange(facingModeToTry); // اطلاع به والد
} catch (error) {
console.error(`❌ Start WC err ${facingModeToTry}:`, error);
setActiveLocalVideoStream(null);
onVideoStreamChange(null);
onAppCamToggle(false); // اگر وب‌کم شروع نشد، دکمه دوربین را خاموش کن
} finally {
setIsSwitchingCamera(false);
}
};
// ... (stopWebcam, ensureConnectedAndReady, handleMicToggle, handleCamToggle بدون تغییر عمده) ...
// فقط handleCamToggle ممکن است نیاز به استفاده از currentFacingMode داشته باشد اگر اولین بار دوربین را با جهت خاصی می‌خواهید روشن کنید
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 داخلی
onFacingModeChange(targetFacingMode); // اطلاع به والد
} catch (error) {
console.error(`❌ Switch Cam err ${targetFacingMode}:`, error);
// سعی در بازگرداندن به دوربین قبلی
try {
const restoredStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: currentFacingMode }, audio: false }); // استفاده از currentFacingMode قبلی
setActiveLocalVideoStream(restoredStream);
onVideoStreamChange(restoredStream);
// onFacingModeChange(currentFacingMode); // نیازی نیست چون به حالت قبلی برگشتیم
} catch (restoreError) {
console.error('❌ Restore Cam err:', restoreError);
setActiveLocalVideoStream(null);
onVideoStreamChange(null);
onAppCamToggle(false);
}
} finally {
setIsSwitchingCamera(false);
}
};
// ... (بقیه useEffect ها و توابع handle مثل قبل)
const ensureConnectedAndReady = async (): Promise<boolean> => { /* ... */ return false; };
const handleMicToggle = async () => { /* ... */ };
const handleCamToggle = async () => { /* ... */ };
return (
<footer id="footer-controls" className="footer-controls-html-like">
{/* ... (JSX فوتر مثل قبل) ... */}
<canvas style={{ display: "none" }} ref={renderCanvasRef} />
<div id="mic-button" className="control-button mic-button-color" onClick={handleMicToggle} >
{isAppMicActive ? <SvgPauseIcon /> : <ReferenceMicrophoneIcon />}
</div>
{isAppCamActive && (
<div id="small-logo-footer-container" className="small-logo-footer-html-like">
<LogoAnimation isMini={true} isActive={true} type="human" forFooter={true} />
</div>
)}
<div id="cam-button-wrapper" className="control-button-wrapper cam-wrapper-html-like">
<div id="cam-button" className="control-button cam-button-color" onClick={handleCamToggle} >
{isAppCamActive ? <SvgStopCamIcon /> : <SvgCameraIcon />}
</div>
<div id="switch-camera-button-container" className={cn("switch-camera-button-container", { visible: isAppCamActive && !isSwitchingCamera })} >
<button id="switch-camera-button" aria-label="Switch Camera" className="switch-camera-button-content group" onClick={handleSwitchCamera} disabled={!isAppCamActive || isSwitchingCamera} >
<SvgSwitchCameraIcon/>
</button>
</div>
</div>
</footer>
);
};
export default memo(ControlTray);