Ezmary commited on
Commit
34dee1a
·
verified ·
1 Parent(s): fc82e2c

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, // دریافت از AppCore
40
- setCurrentFacingMode, // دریافت از AppCore
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 _startWebcam = async (facingModeToTry: 'user' | 'environment') => {
49
- if (isSwitchingCamera) return null; // برگرداندن null اگر در حال تعویض دوربین هستیم
50
- setIsSwitchingCamera(true);
 
 
 
 
 
 
 
 
 
 
 
 
51
  try {
52
  const mediaStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: facingModeToTry }, audio: false });
53
  setActiveLocalVideoStream(mediaStream);
54
- onVideoStreamChange(mediaStream);
55
- // setCurrentFacingMode در اینجا ست نمی‌شود، چون توسط handleSwitchCamera یا handleCamToggle مدیریت می‌شود
56
- return mediaStream; // برگرداندن استریم برای استفاده در توابع دیگر
57
  } catch (error) {
58
  console.error(`❌ Start WC err ${facingModeToTry}:`, error);
59
  setActiveLocalVideoStream(null);
60
  onVideoStreamChange(null);
61
- onAppCamToggle(false); // خاموش کردن دوربین در App.tsx اگر خطا رخ داد
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); // استفاده از currentFacingMode از state والد
81
  }
82
  } else {
83
  if (activeLocalVideoStream) {
84
  _stopWebcam();
85
  }
86
  }
87
- }, [isAppCamActive, activeLocalVideoStream, isSwitchingCamera, currentFacingMode]);
88
 
89
 
90
- useEffect(() => { /* ... audioRecorder ... */ }, [connected, client, isAppMicActive, audioRecorder]);
91
- useEffect(() => { /* ... sendVideoFrame ... */ }, [connected, activeLocalVideoStream, client, videoRef, renderCanvasRef]);
92
- useEffect(() => { /* ... videoRef.srcObject ... */ }, [activeLocalVideoStream, videoRef]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
- const ensureConnectedAndReady = async (): Promise<boolean> => { if (!connected) { try { await connect(); return true; } catch (err) { console.error('❌ CT Connect err:', err); return false; } } return true; };
95
- const handleMicToggle = async () => { if (isSwitchingCamera) return; const newMicState = !isAppMicActive; if (newMicState && !(await ensureConnectedAndReady())) { onAppMicToggle(false); return; } onAppMicToggle(newMicState); };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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); // آپدیت state در App.tsx
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 (