Spaces:
Running
Running
File size: 7,264 Bytes
e8a19cb 3e36429 7f2a14a 3e36429 7f2a14a e8a19cb a77f50e 3e36429 cb75fe0 c2773be 7da7877 35ee466 cb7916c 3e36429 848947d 7f2a14a c2773be 7da7877 35ee466 7da7877 35ee466 cb7916c 3e36429 d7461ed 3e36429 35ee466 3e36429 7da7877 d7461ed 3e36429 34dee1a 3e36429 34dee1a 3e36429 a9228b4 3e36429 a9228b4 3e36429 34dee1a c2773be 3e36429 c2773be 3e36429 c2773be 3e36429 a9228b4 c2773be 3e36429 c2773be 7da7877 c2773be 3e36429 6a9ed5b 3e36429 c2773be 44cae74 3e36429 e8a19cb c2773be 44cae74 3e36429 c2773be 3e36429 c2773be 3e36429 c2773be 35ee466 7da7877 d7461ed 1476e59 688a2e9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
// 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); |