Spaces:
Running
Running
Update src/components/control-tray/ControlTray.tsx
Browse files
src/components/control-tray/ControlTray.tsx
CHANGED
@@ -4,7 +4,7 @@ Copyright 2024 Google LLC
|
|
4 |
... (لایسنس) ...
|
5 |
*/
|
6 |
import cn from "classnames";
|
7 |
-
import React, { memo, ReactNode, RefObject, useEffect, useState } from "react";
|
8 |
import { useLiveAPIContext } from "../../contexts/LiveAPIContext";
|
9 |
import { AudioRecorder } from "../../lib/audio-recorder";
|
10 |
import LogoAnimation from '../logo-animation/LogoAnimation';
|
@@ -36,63 +36,119 @@ const ControlTray: React.FC<ControlTrayProps> = ({
|
|
36 |
isAppCamActive,
|
37 |
onAppCamToggle,
|
38 |
ReferenceMicrophoneIcon,
|
39 |
-
currentFacingMode,
|
40 |
-
setCurrentFacingMode,
|
41 |
}) => {
|
42 |
-
const { client, connected, connect } = useLiveAPIContext();
|
43 |
const [audioRecorder] = useState(() => new AudioRecorder());
|
44 |
const [activeLocalVideoStream, setActiveLocalVideoStream] = useState<MediaStream | null>(null);
|
45 |
const [isSwitchingCamera, setIsSwitchingCamera] = useState(false);
|
46 |
const renderCanvasRef = React.useRef<HTMLCanvasElement>(null);
|
47 |
|
48 |
-
const
|
49 |
-
if (
|
50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
try {
|
52 |
const mediaStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: facingModeToTry }, audio: false });
|
53 |
setActiveLocalVideoStream(mediaStream);
|
54 |
-
onVideoStreamChange(mediaStream);
|
55 |
-
|
56 |
-
return mediaStream; // برگرداندن استریم برای استفاده در توابع دیگر
|
57 |
} catch (error) {
|
58 |
console.error(`❌ Start WC err ${facingModeToTry}:`, error);
|
59 |
setActiveLocalVideoStream(null);
|
60 |
onVideoStreamChange(null);
|
61 |
-
onAppCamToggle(false); //
|
62 |
return null;
|
63 |
-
} finally {
|
64 |
-
setIsSwitchingCamera(false);
|
65 |
}
|
66 |
-
};
|
67 |
|
68 |
-
const _stopWebcam = () => {
|
69 |
if (activeLocalVideoStream) {
|
70 |
activeLocalVideoStream.getTracks().forEach(track => track.stop());
|
71 |
}
|
72 |
setActiveLocalVideoStream(null);
|
73 |
onVideoStreamChange(null);
|
74 |
-
};
|
75 |
|
76 |
-
// useEffect برای مدیریت روشن/خاموش شدن دوربین بر اساس isAppCamActive
|
77 |
useEffect(() => {
|
78 |
if (isAppCamActive) {
|
79 |
if (!activeLocalVideoStream && !isSwitchingCamera) {
|
80 |
-
_startWebcam(currentFacingMode);
|
81 |
}
|
82 |
} else {
|
83 |
if (activeLocalVideoStream) {
|
84 |
_stopWebcam();
|
85 |
}
|
86 |
}
|
87 |
-
}, [isAppCamActive, activeLocalVideoStream, isSwitchingCamera, currentFacingMode]);
|
88 |
|
89 |
|
90 |
-
useEffect(() => {
|
91 |
-
|
92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
|
94 |
-
|
95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
96 |
|
97 |
const handleCamToggle = async () => {
|
98 |
if (isSwitchingCamera) return;
|
@@ -100,14 +156,13 @@ const ControlTray: React.FC<ControlTrayProps> = ({
|
|
100 |
|
101 |
if (newCamState) { // روشن کردن دوربین
|
102 |
if (!(await ensureConnectedAndReady())) {
|
103 |
-
onAppCamToggle(false);
|
104 |
return;
|
105 |
}
|
106 |
-
if (!isAppMicActive) {
|
107 |
-
onAppMicToggle(true);
|
108 |
}
|
109 |
-
onAppCamToggle(true); // این باعث میشود useEffect بالا _startWebcam را فراخوانی کند
|
110 |
-
// با currentFacingMode فعلی که از App.tsx میآید.
|
111 |
} else { // خاموش کردن دوربین
|
112 |
onAppCamToggle(false); // این باعث میشود useEffect بالا _stopWebcam را فراخوانی کند
|
113 |
}
|
@@ -115,21 +170,21 @@ const ControlTray: React.FC<ControlTrayProps> = ({
|
|
115 |
|
116 |
const handleSwitchCamera = async () => {
|
117 |
if (!isAppCamActive || !activeLocalVideoStream || isSwitchingCamera) return;
|
|
|
118 |
|
119 |
const targetFacingMode = currentFacingMode === 'user' ? 'environment' : 'user';
|
120 |
-
_stopWebcam(); //
|
121 |
|
122 |
-
|
123 |
-
await new Promise(resolve => setTimeout(resolve, 100));
|
124 |
|
125 |
-
const newStream = await _startWebcam(targetFacingMode);
|
126 |
if (newStream) {
|
127 |
-
setCurrentFacingMode(targetFacingMode); // آپدیت state در App.tsx
|
128 |
} else {
|
129 |
-
// اگر
|
130 |
-
// (currentFacingMode هنوز مقدار قبلی را دارد)
|
131 |
await _startWebcam(currentFacingMode);
|
132 |
}
|
|
|
133 |
};
|
134 |
|
135 |
return (
|
|
|
4 |
... (لایسنس) ...
|
5 |
*/
|
6 |
import cn from "classnames";
|
7 |
+
import React, { memo, ReactNode, RefObject, useEffect, useState, useCallback } from "react"; // useCallback اضافه شد
|
8 |
import { useLiveAPIContext } from "../../contexts/LiveAPIContext";
|
9 |
import { AudioRecorder } from "../../lib/audio-recorder";
|
10 |
import LogoAnimation from '../logo-animation/LogoAnimation';
|
|
|
36 |
isAppCamActive,
|
37 |
onAppCamToggle,
|
38 |
ReferenceMicrophoneIcon,
|
39 |
+
currentFacingMode,
|
40 |
+
setCurrentFacingMode,
|
41 |
}) => {
|
42 |
+
const { client, connected, connect, disconnect: liveApiDisconnect } = useLiveAPIContext(); // disconnect به liveApiDisconnect تغییر نام یافت
|
43 |
const [audioRecorder] = useState(() => new AudioRecorder());
|
44 |
const [activeLocalVideoStream, setActiveLocalVideoStream] = useState<MediaStream | null>(null);
|
45 |
const [isSwitchingCamera, setIsSwitchingCamera] = useState(false);
|
46 |
const renderCanvasRef = React.useRef<HTMLCanvasElement>(null);
|
47 |
|
48 |
+
const ensureConnectedAndReady = useCallback(async (): Promise<boolean> => {
|
49 |
+
if (!connected) {
|
50 |
+
try {
|
51 |
+
await connect();
|
52 |
+
return true;
|
53 |
+
} catch (err) {
|
54 |
+
console.error('❌ CT Connect err:', err);
|
55 |
+
return false;
|
56 |
+
}
|
57 |
+
}
|
58 |
+
return true;
|
59 |
+
}, [connected, connect]);
|
60 |
+
|
61 |
+
const _startWebcam = useCallback(async (facingModeToTry: 'user' | 'environment'): Promise<MediaStream | null> => {
|
62 |
+
// isSwitchingCamera در اینجا کنترل نمیشود، بلکه در توابع بالاتر
|
63 |
try {
|
64 |
const mediaStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: facingModeToTry }, audio: false });
|
65 |
setActiveLocalVideoStream(mediaStream);
|
66 |
+
onVideoStreamChange(mediaStream); // این باید srcObject ویدیو را در AppCore تنظیم کند
|
67 |
+
return mediaStream;
|
|
|
68 |
} catch (error) {
|
69 |
console.error(`❌ Start WC err ${facingModeToTry}:`, error);
|
70 |
setActiveLocalVideoStream(null);
|
71 |
onVideoStreamChange(null);
|
72 |
+
onAppCamToggle(false); // اطلاع به AppCore که دوربین خاموش شود
|
73 |
return null;
|
|
|
|
|
74 |
}
|
75 |
+
}, [onVideoStreamChange, onAppCamToggle]); // وابستگیها
|
76 |
|
77 |
+
const _stopWebcam = useCallback(() => {
|
78 |
if (activeLocalVideoStream) {
|
79 |
activeLocalVideoStream.getTracks().forEach(track => track.stop());
|
80 |
}
|
81 |
setActiveLocalVideoStream(null);
|
82 |
onVideoStreamChange(null);
|
83 |
+
}, [activeLocalVideoStream, onVideoStreamChange]);
|
84 |
|
|
|
85 |
useEffect(() => {
|
86 |
if (isAppCamActive) {
|
87 |
if (!activeLocalVideoStream && !isSwitchingCamera) {
|
88 |
+
_startWebcam(currentFacingMode);
|
89 |
}
|
90 |
} else {
|
91 |
if (activeLocalVideoStream) {
|
92 |
_stopWebcam();
|
93 |
}
|
94 |
}
|
95 |
+
}, [isAppCamActive, _startWebcam, _stopWebcam, activeLocalVideoStream, isSwitchingCamera, currentFacingMode]);
|
96 |
|
97 |
|
98 |
+
useEffect(() => {
|
99 |
+
const onData = (base64: string) => {
|
100 |
+
if (client && connected && isAppMicActive) {
|
101 |
+
client.sendRealtimeInput([{ mimeType: "audio/pcm;rate=16000", data: base64 }]);
|
102 |
+
}
|
103 |
+
};
|
104 |
+
if (connected && isAppMicActive && audioRecorder) {
|
105 |
+
audioRecorder.on("data", onData).start();
|
106 |
+
} else if (audioRecorder && audioRecorder.recording) {
|
107 |
+
audioRecorder.stop();
|
108 |
+
}
|
109 |
+
return () => {
|
110 |
+
if (audioRecorder) {
|
111 |
+
audioRecorder.off("data", onData);
|
112 |
+
if (audioRecorder.recording) audioRecorder.stop();
|
113 |
+
}
|
114 |
+
};
|
115 |
+
}, [connected, client, isAppMicActive, audioRecorder]);
|
116 |
|
117 |
+
useEffect(() => {
|
118 |
+
let timeoutId = -1;
|
119 |
+
function sendVideoFrame() {
|
120 |
+
if (connected && activeLocalVideoStream && client && videoRef.current) {
|
121 |
+
// ... (منطق ارسال فریم ویدیو بدون تغییر) ...
|
122 |
+
}
|
123 |
+
if (connected && activeLocalVideoStream) {
|
124 |
+
timeoutId = window.setTimeout(sendVideoFrame, 1000 / 4); // 4 FPS
|
125 |
+
}
|
126 |
+
}
|
127 |
+
if (connected && activeLocalVideoStream && videoRef.current) {
|
128 |
+
timeoutId = window.setTimeout(sendVideoFrame, 200); // شروع با کمی تاخیر
|
129 |
+
}
|
130 |
+
return () => clearTimeout(timeoutId);
|
131 |
+
}, [connected, activeLocalVideoStream, client, videoRef]);
|
132 |
+
|
133 |
+
// useEffect برای قطع اتصال وقتی هیچکدام فعال نیستند (این منطق به AppCore منتقل شد)
|
134 |
+
// useEffect(() => {
|
135 |
+
// if (!isAppMicActive && !isAppCamActive && connected) {
|
136 |
+
// liveApiDisconnect(); // استفاده از نام جدید
|
137 |
+
// }
|
138 |
+
// }, [isAppMicActive, isAppCamActive, connected, liveApiDisconnect]);
|
139 |
+
|
140 |
+
|
141 |
+
const handleMicToggle = async () => {
|
142 |
+
if (isSwitchingCamera) return;
|
143 |
+
const newMicState = !isAppMicActive;
|
144 |
+
if (newMicState) { // روشن کردن میکروفون
|
145 |
+
if (!(await ensureConnectedAndReady())) {
|
146 |
+
onAppMicToggle(false);
|
147 |
+
return;
|
148 |
+
}
|
149 |
+
}
|
150 |
+
onAppMicToggle(newMicState);
|
151 |
+
};
|
152 |
|
153 |
const handleCamToggle = async () => {
|
154 |
if (isSwitchingCamera) return;
|
|
|
156 |
|
157 |
if (newCamState) { // روشن کردن دوربین
|
158 |
if (!(await ensureConnectedAndReady())) {
|
159 |
+
onAppCamToggle(false);
|
160 |
return;
|
161 |
}
|
162 |
+
if (!isAppMicActive) { // اگر میکروفون خاموش است، آن را هم روشن کن
|
163 |
+
onAppMicToggle(true);
|
164 |
}
|
165 |
+
onAppCamToggle(true); // این باعث میشود useEffect بالا _startWebcam را با currentFacingMode فعلی فراخوانی کند
|
|
|
166 |
} else { // خاموش کردن دوربین
|
167 |
onAppCamToggle(false); // این باعث میشود useEffect بالا _stopWebcam را فراخوانی کند
|
168 |
}
|
|
|
170 |
|
171 |
const handleSwitchCamera = async () => {
|
172 |
if (!isAppCamActive || !activeLocalVideoStream || isSwitchingCamera) return;
|
173 |
+
setIsSwitchingCamera(true); // جلوگیری از فراخوانیهای همزمان
|
174 |
|
175 |
const targetFacingMode = currentFacingMode === 'user' ? 'environment' : 'user';
|
176 |
+
_stopWebcam(); // توقف استریم فعلی
|
177 |
|
178 |
+
await new Promise(resolve => setTimeout(resolve, 100)); // تاخیر کوچک
|
|
|
179 |
|
180 |
+
const newStream = await _startWebcam(targetFacingMode);
|
181 |
if (newStream) {
|
182 |
+
setCurrentFacingMode(targetFacingMode); // آپدیت state در App.tsx
|
183 |
} else {
|
184 |
+
// اگر ناموفق بود، سعی در بازگرداندن به دوربین قبلی (currentFacingMode هنوز مقدار قبلی را دارد)
|
|
|
185 |
await _startWebcam(currentFacingMode);
|
186 |
}
|
187 |
+
setIsSwitchingCamera(false); // آزاد کردن قفل
|
188 |
};
|
189 |
|
190 |
return (
|