ScreenShot.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import { useEffect, useRef, useState, useCallback } from 'react';
  2. import ScrcpyConfig from './ScrcpyConfig.js';
  3. // 截屏逻辑:监听设备预览事件,轮询 adb 截屏并展示
  4. export function ScreenShotLogic() {
  5. const [currentDevice, setCurrentDevice] = useState(null);
  6. const [imageSrc, setImageSrc] = useState(null);
  7. const stopFlag = useRef(false);
  8. const waitForLoadRef = useRef(null);
  9. const activeDeviceRef = useRef(null);
  10. const loopIdRef = useRef(null);
  11. const frameCounterRef = useRef(0);
  12. // 从配置文件读取参数
  13. const pollInterval = ScrcpyConfig['poll-interval'] || 100;
  14. const imageLoadTimeout = ScrcpyConfig['image-load-timeout'] || 500;
  15. // 截屏循环:请求截图 -> 显示 -> 等待加载 -> 再请求下一帧
  16. const startLoop = useCallback(async (device) => {
  17. // 避免同时多个循环
  18. const loopId = Symbol('loop');
  19. loopIdRef.current = loopId;
  20. const runOnce = async () => {
  21. // 检查是否应该停止循环
  22. if (stopFlag.current || loopIdRef.current !== loopId || activeDeviceRef.current !== device) {
  23. return;
  24. }
  25. try {
  26. // 请求屏幕截图
  27. if (!window.electronAPI || !window.electronAPI.captureScreenshot) {
  28. console.warn('截屏 API 不可用');
  29. await delay(pollInterval);
  30. runOnce();
  31. return;
  32. }
  33. // 请求截图(传递配置参数)
  34. const res = await window.electronAPI.captureScreenshot(device, {
  35. format: ScrcpyConfig['screencap-format'],
  36. quality: ScrcpyConfig['screencap-quality'],
  37. scale: ScrcpyConfig['screencap-scale']
  38. });
  39. if (!res?.success || !res.data) {
  40. // 截图失败,等待后重试
  41. await delay(pollInterval);
  42. runOnce();
  43. } else {
  44. // 截图成功,更新图片
  45. const stamp = frameCounterRef.current++;
  46. const format = ScrcpyConfig['screencap-format'] || 'png';
  47. const mimeType = format === 'jpeg' ? 'image/jpeg' : 'image/png';
  48. const dataUrl = `data:${mimeType};base64,${res.data}#${stamp}`;
  49. // 创建等待图片加载的 Promise
  50. const waitPromise = new Promise((resolve) => {
  51. waitForLoadRef.current = resolve;
  52. });
  53. // 更新图片源(会触发图片加载)
  54. setImageSrc(dataUrl);
  55. // 等待图片加载完成(onLoad 触发)或超时(使用配置的超时时间),然后立即请求下一帧
  56. await Promise.race([waitPromise, delay(imageLoadTimeout)]);
  57. // 继续请求下一帧截图
  58. runOnce();
  59. }
  60. } catch (err) {
  61. console.error('截屏循环异常:', err);
  62. await delay(pollInterval);
  63. runOnce();
  64. }
  65. };
  66. // 开始循环
  67. runOnce();
  68. }, []);
  69. // 监听来自 Devices 的开始/停止预览事件
  70. useEffect(() => {
  71. const handleStart = (e) => {
  72. const device = e.detail?.device;
  73. if (!device) return;
  74. // 停止之前的循环(如果有)
  75. stopFlag.current = true;
  76. // 启动新的预览循环
  77. stopFlag.current = false;
  78. activeDeviceRef.current = device;
  79. setCurrentDevice(device);
  80. setImageSrc(null); // 清空之前的图片
  81. startLoop(device);
  82. };
  83. const handleStop = (e) => {
  84. const device = e.detail?.device;
  85. // 只有当前预览设备才停止
  86. if (!device || device === activeDeviceRef.current) {
  87. stopFlag.current = true;
  88. activeDeviceRef.current = null;
  89. loopIdRef.current = null;
  90. setCurrentDevice(null);
  91. setImageSrc(null);
  92. }
  93. };
  94. window.addEventListener('start-preview', handleStart);
  95. window.addEventListener('stop-preview', handleStop);
  96. return () => {
  97. window.removeEventListener('start-preview', handleStart);
  98. window.removeEventListener('stop-preview', handleStop);
  99. stopFlag.current = true;
  100. };
  101. }, [startLoop]);
  102. // 提供给组件的 onLoad 回调,通知图片已加载,可以请求下一帧
  103. const notifyImageLoaded = useCallback(() => {
  104. if (waitForLoadRef.current) {
  105. waitForLoadRef.current();
  106. waitForLoadRef.current = null;
  107. }
  108. }, []);
  109. return {
  110. currentDevice,
  111. imageSrc,
  112. notifyImageLoaded,
  113. };
  114. }
  115. function delay(ms) {
  116. return new Promise((resolve) => setTimeout(resolve, ms));
  117. }