feat(工具箱/MSU2 USB 小屏幕控制): 添加屏幕捕获功能

This commit is contained in:
2026-03-09 14:41:20 +08:00
parent 02237e3e30
commit 9ca0619373

View File

@@ -41,6 +41,18 @@
@click="isUseNewDisplayDataConvertFunction = false" @click="isUseNewDisplayDataConvertFunction = false"
>显示数据转换算法</n-button> >显示数据转换算法</n-button>
</n-flex> </n-flex>
<n-flex>
<n-button
type="success"
:disabled="isScreenCaptureReady"
@click="handleStartScreenCapture"
>开始屏幕捕获</n-button>
<n-button
type="error"
:disabled="!isScreenCaptureReady"
@click="handleStopScreenCapture"
>停止屏幕捕获</n-button>
</n-flex>
</n-card> </n-card>
<!-- 配置参数 --> <!-- 配置参数 -->
@@ -143,6 +155,10 @@ const DISPLAY_MODES = {
label: '数字时钟', label: '数字时钟',
value: 'digitalClock', value: 'digitalClock',
}, },
screenCapture: {
label: '屏幕捕获',
value: 'screenCapture',
},
}; };
/** 分辨率配置选项 */ /** 分辨率配置选项 */
@@ -182,6 +198,9 @@ const displayMode = ref('dataText');
/** 是否使用新的显示数据转换算法 */ /** 是否使用新的显示数据转换算法 */
const isUseNewDisplayDataConvertFunction = ref(true); const isUseNewDisplayDataConvertFunction = ref(true);
/** 屏幕捕获是否已就绪 */
const isScreenCaptureReady = ref(false);
/** 渲染间隔,毫秒 */ /** 渲染间隔,毫秒 */
const renderInterval = ref(100); const renderInterval = ref(100);
@@ -247,6 +266,20 @@ let CANVAS_HEIGHT = 0; // 画布显示高度
let LCD_WIDTH = 0; // 设备实际宽度 let LCD_WIDTH = 0; // 设备实际宽度
let LCD_HEIGHT = 0; // 设备实际高度 let LCD_HEIGHT = 0; // 设备实际高度
// 屏幕捕获相关变量
/** @type {MediaStream} */
let displayStream = null;
/** @type {HTMLVideoElement} */
let displayVideo = null;
/** @type {ReturnType<typeof displayStream.getVideoTracks>} */
let displayVideoTrack = null;
/** 屏幕捕获是否已激活 */
let isScreenCaptureActive = false;
/** 处理更改显示模式 */ /** 处理更改显示模式 */
function handleChangeDisplayMode() { function handleChangeDisplayMode() {
@@ -383,6 +416,17 @@ function handleStopRender() {
} }
/** 开始屏幕捕获处理 */
async function handleStartScreenCapture() {
await startScreenCapture();
isScreenCaptureReady.value = true;
}
/** 停止屏幕捕获处理 */
function handleStopScreenCapture() {
stopScreenCapture();
}
/** 初始化 */ /** 初始化 */
function initData() { function initData() {
@@ -571,6 +615,64 @@ function audioAnalyserStop() {
} }
/** 开始屏幕捕获 */
async function startScreenCapture() {
try {
displayStream = await navigator.mediaDevices.getDisplayMedia({
video: {
displaySurface: 'monitor',
},
audio: false,
});
displayVideo = document.createElement('video');
displayVideo.srcObject = displayStream;
displayVideo.autoplay = true;
displayVideo.playsInline = true;
await displayVideo.play();
displayVideoTrack = displayStream.getVideoTracks()[0];
displayVideoTrack.onended = () => {
stopScreenCapture();
};
isScreenCaptureActive = true;
isScreenCaptureReady.value = true;
console.info('屏幕捕获已开始');
} catch (error) {
console.error('屏幕捕获失败:', error);
isScreenCaptureActive = false;
isScreenCaptureReady.value = false;
}
}
/** 停止屏幕捕获 */
function stopScreenCapture() {
if (displayVideoTrack) {
displayVideoTrack.stop();
displayVideoTrack = null;
}
if (displayStream) {
displayStream.getTracks().forEach(track => track.stop());
displayStream = null;
}
if (displayVideo) {
displayVideo.srcObject = null;
displayVideo = null;
}
isScreenCaptureActive = false;
isScreenCaptureReady.value = false;
console.info('屏幕捕获已停止');
}
/** 将 32 位整数拆分为 4 个字节 */ /** 将 32 位整数拆分为 4 个字节 */
function convertDigitToInts(di) { function convertDigitToInts(di) {
return [(di >> 24) & 0xFF, (di >> 16) & 0xFF, (di >> 8) & 0xFF, di & 0xFF]; return [(di >> 24) & 0xFF, (di >> 16) & 0xFF, (di >> 8) & 0xFF, di & 0xFF];
@@ -1059,6 +1161,9 @@ async function renderCanvas(timestamp = 0) {
case 'digitalClock': case 'digitalClock':
await renderDigitalClock(); await renderDigitalClock();
break; break;
case 'screenCapture':
await renderScreenCapture();
break;
default: default:
break; break;
} }
@@ -1330,6 +1435,51 @@ async function renderDigitalClock() {
} }
/** 渲染屏幕捕获内容 */
async function renderScreenCapture() {
if (displayVideo && isScreenCaptureActive) {
// 清空画布
canvasCtx.value.fillStyle = '#000000';
canvasCtx.value.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
} else {
// 显示提示文字
await renderCustomText('屏幕捕获未启动');
return;
}
let videoWidth = displayVideo.videoWidth;
let videoHeight = displayVideo.videoHeight;
if (videoWidth === 0 || videoHeight === 0) {
return;
}
let videoAspectRatio = videoWidth / videoHeight;
let canvasAspectRatio = CANVAS_WIDTH / CANVAS_HEIGHT;
let drawWidth, drawHeight, offsetX, offsetY;
if (videoAspectRatio > canvasAspectRatio) {
drawWidth = CANVAS_WIDTH;
drawHeight = CANVAS_WIDTH / videoAspectRatio;
offsetX = 0;
offsetY = (CANVAS_HEIGHT - drawHeight) / 2;
} else {
drawHeight = CANVAS_HEIGHT;
drawWidth = CANVAS_HEIGHT * videoAspectRatio;
offsetX = (CANVAS_WIDTH - drawWidth) / 2;
offsetY = 0;
}
canvasCtx.value.drawImage(
displayVideo,
offsetX, offsetY,
drawWidth, drawHeight
);
}
onMounted(() => { onMounted(() => {
initData(); initData();
}); });
@@ -1337,6 +1487,7 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
handleDisconnectDevice(); handleDisconnectDevice();
handleStopRender(); handleStopRender();
handleStartScreenCapture();
}); });
</script> </script>