ChessCommanderFlet / camera_utils.py
Danisz's picture
Camera working on JS
6cbbb50
Raw History Blame Contribute Delete
6.52 kB
import logging
import flet as ft
from js_execution import set_camera_frame_callback
from flet_extensions import eval_js
import asyncio
from typing import List, Dict
logger = logging.getLogger(__name__)
class Frame:
def __init__(self, data: str, timestamp: float):
self.data = data
self.timestamp = timestamp
class CameraImage(ft.Container):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.stream_active = False
self.video_image1 = ft.Image(
src="",
width=self.width,
height=self.height,
fit=ft.ImageFit.CONTAIN,
visible=False,
)
self.video_image2 = ft.Image(
src="",
width=self.width,
height=self.height,
fit=ft.ImageFit.CONTAIN,
visible=False,
)
self.current_video_image = self.video_image1
self.video_stack = ft.Stack([self.video_image1, self.video_image2])
self.content = self.video_stack
set_camera_frame_callback(self.handle_camera_frame)
self.captured_frames: Dict[int, Frame] = {}
self.stream_start_time = None
async def start_camera_stream_async(self) -> str:
try:
result = await eval_js(self.page, """
window.cameraStreamActive = false;
window.cameraWebSocket = null;
async function setupCameraStream() {
console.log("setupCameraStream called");
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
console.log("Camera access granted");
const video = document.createElement('video');
video.srcObject = stream;
await video.play();
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth / 2;
canvas.height = video.videoHeight / 2;
const ctx = canvas.getContext('2d');
window.cameraWebSocket = new WebSocket(`ws://${window.location.host}/ws/camera`);
function sendFrame() {
if (window.cameraWebSocket && window.cameraWebSocket.readyState === WebSocket.OPEN) {
ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, canvas.width, canvas.height);
window.cameraWebSocket.send(canvas.toDataURL('image/jpeg', 0.2)); // Added quality parameter
window.cameraStreamActive && setTimeout(sendFrame, 1000 / 60);
}
}
window.cameraWebSocket.onopen = () => {
console.log("WebSocket connection opened");
window.cameraStreamActive = true;
sendFrame();
};
window.cameraWebSocket.onclose = () => {
console.log("WebSocket connection closed");
window.cameraStreamActive = false;
stream.getTracks().forEach(track => track.stop());
};
return "Camera stream setup complete";
} catch (error) {
console.error("Error accessing camera:", error);
throw error;
}
}
return await setupCameraStream();
""")
logger.info(f"start_camera_stream_async result: {result}")
self.stream_active = True
self.stream_start_time = asyncio.get_event_loop().time()
self.captured_frames.clear()
return result
except Exception as e:
logger.error(f"Error in start_camera_stream_async: {str(e)}")
return f"Error: {str(e)}"
async def stop_camera_stream_async(self) -> str:
try:
result = await eval_js(self.page, """
if (window.cameraWebSocket) {
window.cameraStreamActive = false;
window.cameraWebSocket.close();
window.cameraWebSocket = null;
}
return 'Camera stream stopped';
""")
logger.info(f"stop_camera_stream_async result: {result}")
self.stream_active = False
self.stream_start_time = None
return result
except Exception as e:
logger.error(f"Error in stop_camera_stream_async: {str(e)}")
return f"Error: {str(e)}"
async def handle_camera_frame(self, frame_data: str):
if self.stream_active:
await self.update_webcam_image(frame_data)
self.capture_timed_frames(frame_data)
def capture_timed_frames(self, frame_data: str):
current_time = asyncio.get_event_loop().time()
if self.stream_start_time is None:
self.stream_start_time = current_time
elapsed_time = current_time - self.stream_start_time
for seconds in [1, 2, 3]:
if elapsed_time >= seconds and seconds not in self.captured_frames:
self.captured_frames[seconds] = Frame(frame_data, current_time)
def get_captured_frames(self) -> List[Frame]:
return list(self.captured_frames.values())
async def update_webcam_image(self, frame_data: str) -> None:
if not frame_data:
print("Empty data!")
return
# Frame data is already in Data URI format
new_src = frame_data
self.video_image1.visible = True
self.video_image2.visible = True
# Determine which image to update
if self.current_video_image == self.video_image1:
image_to_update = self.video_image2
image_to_hide = self.video_image1
else:
image_to_update = self.video_image1
image_to_hide = self.video_image2
# Update the hidden image
image_to_update.src = new_src
image_to_update.update()
# Wait for the browser to load the new image
await asyncio.sleep(0.02) # Adjust this delay if needed
# Update opacity
image_to_update.opacity = 1
image_to_hide.opacity = 0
self.current_video_image = image_to_update
# Final update to apply opacity changes
self.video_stack.update()