Przeglądaj źródła

ScreenShot 功能

yichael 5 miesięcy temu
rodzic
commit
280ddb9a50

+ 11 - 1
src/pages/Devices/Devices.js

@@ -77,17 +77,27 @@ export function DevicesLogic() {
       next.delete(ipPort);
       return next;
     });
+
+    // 通知预览停止(异步避免 render 期间 setState 警告)
+    setTimeout(() => {
+      window.dispatchEvent(new CustomEvent('stop-preview', { detail: { device: ipPort } }));
+    }, 0);
   };
 
   // 切换预览/取消预览
   const togglePreview = (ipPort) => {
     setPreviewingDevices(prev => {
       const next = new Set(prev);
-      if (next.has(ipPort)) {
+      const already = next.has(ipPort);
+      if (already) {
         next.delete(ipPort);
       } else {
         next.add(ipPort);
       }
+      // 异步通知,避免在 render 中触发别的组件 setState 警告
+      setTimeout(() => {
+        window.dispatchEvent(new CustomEvent(already ? 'stop-preview' : 'start-preview', { detail: { device: ipPort } }));
+      }, 0);
       return next;
     });
   };

+ 16 - 0
src/pages/ScreenShot/ScreenShot.css

@@ -21,4 +21,20 @@
   box-sizing: border-box;
   flex-shrink: 0;
   /* 保持 1280x2400 比例,限制在容器内 */
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  overflow: hidden;
+}
+
+.screenshot-img {
+  max-width: 100%;
+  max-height: 100%;
+  object-fit: contain;
+  display: block;
+}
+
+.screenshot-placeholder {
+  color: #666;
+  font-size: 14px;
 }

+ 121 - 2
src/pages/ScreenShot/ScreenShot.js

@@ -1,7 +1,126 @@
+import { useEffect, useRef, useState, useCallback } from 'react';
+
+// 截屏逻辑:监听设备预览事件,轮询 adb 截屏并展示
 export function ScreenShotLogic() {
-  // business logic placeholder
+  const [currentDevice, setCurrentDevice] = useState(null);
+  const [imageSrc, setImageSrc] = useState(null);
+  const stopFlag = useRef(false);
+  const waitForLoadRef = useRef(null);
+  const activeDeviceRef = useRef(null);
+  const loopIdRef = useRef(null);
+  const frameCounterRef = useRef(0);
+
+  // 截屏循环:请求截图 -> 显示 -> 等待加载 -> 再请求下一帧
+  const startLoop = useCallback(async (device) => {
+    // 避免同时多个循环
+    const loopId = Symbol('loop');
+    loopIdRef.current = loopId;
+
+    const runOnce = async () => {
+      // 检查是否应该停止循环
+      if (stopFlag.current || loopIdRef.current !== loopId || activeDeviceRef.current !== device) {
+        return;
+      }
+
+      try {
+        // 请求屏幕截图
+        if (!window.electronAPI || !window.electronAPI.captureScreenshot) {
+          console.warn('截屏 API 不可用');
+          await delay(500);
+          runOnce();
+          return;
+        }
+
+        const res = await window.electronAPI.captureScreenshot(device);
+        
+        if (!res?.success || !res.data) {
+          // 截图失败,等待后重试
+          await delay(400);
+          runOnce();
+        } else {
+          // 截图成功,更新图片并等待加载完成
+          const stamp = frameCounterRef.current++;
+          const dataUrl = `data:image/png;base64,${res.data}#${stamp}`;
+          
+          // 创建等待图片加载的 Promise
+          const waitPromise = new Promise((resolve) => {
+            waitForLoadRef.current = resolve;
+          });
+          
+          // 更新图片源
+          setImageSrc(dataUrl);
+          
+          // 等待图片加载完成(onLoad 触发)或超时(1秒),然后请求下一帧
+          await Promise.race([waitPromise, delay(1000)]);
+          
+          // 继续下一帧
+          runOnce();
+        }
+      } catch (err) {
+        console.error('截屏循环异常:', err);
+        await delay(500);
+        runOnce();
+      }
+    };
+
+    // 开始循环
+    runOnce();
+  }, []);
+
+  // 监听来自 Devices 的开始/停止预览事件
+  useEffect(() => {
+    const handleStart = (e) => {
+      const device = e.detail?.device;
+      if (!device) return;
+      
+      // 停止之前的循环(如果有)
+      stopFlag.current = true;
+      
+      // 启动新的预览循环
+      stopFlag.current = false;
+      activeDeviceRef.current = device;
+      setCurrentDevice(device);
+      setImageSrc(null); // 清空之前的图片
+      startLoop(device);
+    };
+
+    const handleStop = (e) => {
+      const device = e.detail?.device;
+      // 只有当前预览设备才停止
+      if (!device || device === activeDeviceRef.current) {
+        stopFlag.current = true;
+        activeDeviceRef.current = null;
+        loopIdRef.current = null;
+        setCurrentDevice(null);
+        setImageSrc(null);
+      }
+    };
+
+    window.addEventListener('start-preview', handleStart);
+    window.addEventListener('stop-preview', handleStop);
+
+    return () => {
+      window.removeEventListener('start-preview', handleStart);
+      window.removeEventListener('stop-preview', handleStop);
+      stopFlag.current = true;
+    };
+  }, [startLoop]);
+
+  // 提供给组件的 onLoad 回调,通知图片已加载,可以请求下一帧
+  const notifyImageLoaded = useCallback(() => {
+    if (waitForLoadRef.current) {
+      waitForLoadRef.current();
+      waitForLoadRef.current = null;
+    }
+  }, []);
 
   return {
-    // expose data or methods here
+    currentDevice,
+    imageSrc,
+    notifyImageLoaded,
   };
 }
+
+function delay(ms) {
+  return new Promise((resolve) => setTimeout(resolve, ms));
+}

+ 11 - 2
src/pages/ScreenShot/ScreenShot.jsx

@@ -2,12 +2,21 @@ import './ScreenShot.css';
 import { ScreenShotLogic } from './ScreenShot.js';
 
 function ScreenShot() {
-  const logic = ScreenShotLogic();
+  const { imageSrc, notifyImageLoaded } = ScreenShotLogic();
 
   return (
     <div className="ScreenShot-container">
       <div className="screenshot-frame">
-        {/* 竖屏 2400x1280 灰色线框区域 */}
+        {imageSrc ? (
+          <img
+            src={imageSrc}
+            alt="设备截屏"
+            onLoad={notifyImageLoaded}
+            className="screenshot-img"
+          />
+        ) : (
+          <div className="screenshot-placeholder">等待预览...</div>
+        )}
       </div>
     </div>
   );