import React, { useState, useEffect } from 'react'; function FocusPage({ videoManager, sessionResult, setSessionResult, isActive, displayVideoRef }) { const [currentFrame, setCurrentFrame] = useState(30); const [timelineEvents, setTimelineEvents] = useState([]); const videoRef = displayVideoRef; // 辅助函数:格式化时间 const formatDuration = (seconds) => { // 如果是 0,直接显示 0s (或者你可以保留原来的 0m 0s) if (seconds === 0) return "0s"; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}m ${secs}s`; }; useEffect(() => { if (!videoManager) return; // 设置回调函数来更新时间轴 const originalOnStatusUpdate = videoManager.callbacks.onStatusUpdate; videoManager.callbacks.onStatusUpdate = (isFocused) => { setTimelineEvents(prev => { const newEvents = [...prev, { isFocused, timestamp: Date.now() }]; if (newEvents.length > 60) newEvents.shift(); return newEvents; }); // 调用原始回调(如果有) if (originalOnStatusUpdate) originalOnStatusUpdate(isFocused); }; // 清理函数:不再自动停止session,只清理回调 return () => { if (videoManager) { videoManager.callbacks.onStatusUpdate = originalOnStatusUpdate; } }; }, [videoManager]); const handleStart = async () => { try { if (videoManager) { setSessionResult(null); // 开始时清除结果层 setTimelineEvents([]); console.log('🎬 Initializing camera...'); await videoManager.initCamera(videoRef.current); console.log('✅ Camera initialized'); console.log('🚀 Starting streaming...'); await videoManager.startStreaming(); console.log('✅ Streaming started successfully'); } } catch (err) { console.error('❌ Start error:', err); let errorMessage = "Failed to start: "; if (err.name === 'NotAllowedError') { errorMessage += "Camera permission denied. Please allow camera access."; } else if (err.name === 'NotFoundError') { errorMessage += "No camera found. Please connect a camera."; } else if (err.name === 'NotReadableError') { errorMessage += "Camera is already in use by another application."; } else if (err.message && err.message.includes('HTTPS')) { errorMessage += "Camera requires HTTPS. Please use a secure connection."; } else { errorMessage += err.message || "Unknown error occurred."; } alert(errorMessage + "\n\nCheck browser console for details."); } }; const handleStop = () => { if (videoManager) { videoManager.stopStreaming(); } }; const handlePiP = async () => { try { const sourceVideoEl = videoRef.current; if (!sourceVideoEl) { alert('Video not ready. Please click Start first.'); return; } if (document.pictureInPictureElement) { await document.exitPictureInPicture(); return; } sourceVideoEl.disablePictureInPicture = false; if (typeof sourceVideoEl.webkitSetPresentationMode === 'function') { sourceVideoEl.play().catch(() => {}); sourceVideoEl.webkitSetPresentationMode('picture-in-picture'); return; } if (!document.pictureInPictureEnabled || typeof sourceVideoEl.requestPictureInPicture !== 'function') { alert('Picture-in-Picture is not supported in this browser.'); return; } const pipPromise = sourceVideoEl.requestPictureInPicture(); sourceVideoEl.play().catch(() => {}); await pipPromise; } catch (err) { console.error('PiP error:', err); alert('Failed to enter Picture-in-Picture.'); } }; // 浮窗功能 const handleFloatingWindow = () => { handlePiP(); }; // ========================================== // 新增功能:预览按钮的处理函数 // ========================================== const handlePreview = () => { // 强制设置一个 0 分 0 秒的假数据,触发 overlay 显示 setSessionResult({ duration_seconds: 0, focus_score: 0 }); }; const handleCloseOverlay = () => { setSessionResult(null); }; // ========================================== const handleFrameChange = (val) => { setCurrentFrame(val); if (videoManager) { videoManager.setFrameRate(val); } }; const pageStyle = isActive ? undefined : { position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', opacity: 0, pointerEvents: 'none' }; return (
{/* 1. Camera / Display Area */}
{/* 2. Timeline Area */}
Timeline
{timelineEvents.map((event, index) => (
))}
{/* 3. Control Buttons */}
{/* 修改:把 Models 按钮暂时改成 Preview 按钮,或者加在它后面 */}
{/* 4. Frame Control */}
handleFrameChange(e.target.value)} /> handleFrameChange(e.target.value)} />
); } export default FocusPage;