no message

This commit is contained in:
DESKTOP-RQ919RC\Pc
2025-12-10 17:20:56 +08:00
parent c7cd36ff05
commit 899d0d04fa
2 changed files with 177 additions and 53 deletions

View File

@@ -1661,73 +1661,35 @@
// 启动渲染循环html-to-image 截图 // 启动渲染循环html-to-image 截图
// 目标帧率 120 FPS不再使用 setTimeout 限流,而是全力渲染 // 目标帧率 120 FPS不再使用 setTimeout 限流,而是全力渲染
const targetFPS = 120; const targetFPS = 120;
let isRendering = false; // 防止堆叠
// FPS 计算变量
let frameCount = 0;
let lastFpsTime = Date.now();
async function renderLoop() { async function renderLoop() {
if (!isMVRecording) return; if (!isMVRecording) return;
console.log('renderLoop');
// 计算并打印 FPS
frameCount++;
const now = Date.now();
if (now - lastFpsTime >= 1000) {
console.log(`Recording FPS: ${frameCount}`);
frameCount = 0;
lastFpsTime = now;
}
// 标记开始处理 // 标记开始处理
const beginTime = Date.now(); const beginTime = Date.now();
try { try {
// 如果上一帧还没处理完,直接跳过这一帧,防止 UI 线程卡死 const source = document.querySelector('.main-container');
if (isRendering) { if (source) {
requestAnimationFrame(renderLoop); // 使用高性能的手动绘制函数替代 html-to-image
return; // 这能将帧率从 5 FPS 提升到 60+ FPS
} drawCanvasFrame();
}
isRendering = true;
const source = document.querySelector('.main-container');
if (source) {
// 恢复高清录制,限制最大 dpr 为 2 以平衡性能
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const canvas = await htmlToImage.toCanvas(source, {
backgroundColor: null,
pixelRatio: dpr,
skipAutoScale: true,
cacheBust: false,
fontEmbedCSS: '', // 禁用字体嵌入,防止崩溃
filter: (node) => {
// 过滤无效图片防止崩溃
if (node.tagName === 'IMG' && (!node.src || node.src === window.location.href)) return false;
return true;
},
style: {
transform: 'translateZ(0)'
}
});
// 将捕捉到的画面绘制到录制画布上
mvCtx.clearRect(0, 0, mvCanvas.width, mvCanvas.height);
mvCtx.drawImage(canvas, 0, 0, mvCanvas.width, mvCanvas.height);
}
} catch (e) { } catch (e) {
console.error('Frame capture error:', e); console.error('Frame capture error:', e);
} finally {
isRendering = false;
} }
// 不再使用 setTimeout 进行延时,避免 JS 定时器精度问题导致的抖动
// 直接请求下一帧,让浏览器决定最佳时机(通常是 60Hz如果设备支持高刷则更高
// 这样可以消除人为引入的卡顿
if (isMVRecording) { if (isMVRecording) {
mvRafId = requestAnimationFrame(renderLoop); mvRafId = requestAnimationFrame(renderLoop);
} }
} }
mvRafId = requestAnimationFrame(renderLoop); mvRafId = requestAnimationFrame(renderLoop);
mvStream = mvCanvas.captureStream(targetFPS); mvStream = mvCanvas.captureStream(targetFPS);
// 添加音频轨道 // 添加音频轨道

162
未命名 (4).html Normal file
View File

@@ -0,0 +1,162 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Chrome 原生 API 屏幕录制</title>
<style>
.control-panel { margin: 20px 0; }
button { padding: 8px 16px; margin-right: 10px; cursor: pointer; }
#preview { border: 1px solid #ccc; width: 800px; height: 450px; margin: 10px 0; }
.status { color: #666; margin: 10px 0; }
</style>
</head>
<body>
<div class="control-panel">
<button id="startBtn">开始录制</button>
<button id="pauseBtn" disabled>暂停录制</button>
<button id="resumeBtn" disabled>继续录制</button>
<button id="stopBtn" disabled>停止录制</button>
</div>
<div class="status" id="status">状态:未开始</div>
<video id="preview" autoplay muted></video>
<script>
// 核心变量
let mediaStream = null; // 屏幕捕获流
let mediaRecorder = null; // 录制器实例
let recordedChunks = []; // 录制的视频数据块
let isPaused = false; // 录制暂停状态
// DOM 元素
const startBtn = document.getElementById('startBtn');
const pauseBtn = document.getElementById('pauseBtn');
const resumeBtn = document.getElementById('resumeBtn');
const stopBtn = document.getElementById('stopBtn');
const preview = document.getElementById('preview');
const status = document.getElementById('status');
// 1. 开始录制:捕获屏幕流 + 初始化录制器
startBtn.addEventListener('click', async () => {
try {
// 步骤1请求屏幕捕获权限用户选择录制范围全屏/窗口/标签页)
mediaStream = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: 'always', // 显示鼠标光标可选never/hidden
frameRate: { ideal: 30, max: 60 } // 录制帧率理想30帧最大60帧
},
audio: true // 同时捕获系统音频需浏览器支持Chrome需开启实验性功能
});
// 步骤2绑定流到预览窗口
preview.srcObject = mediaStream;
// 步骤3初始化 MediaRecorder指定录制格式为webmChrome 默认支持)
const options = {
mimeType: 'video/webm; codecs=vp9', // 推荐vp9编码体积小、画质好
// 可选:设置码率(根据需求调整,越高画质越好但体积越大)
// videoBitsPerSecond: 2500000 // 2.5Mbps
};
mediaRecorder = new MediaRecorder(mediaStream, options);
// 步骤4监听录制数据每块数据存入数组
mediaRecorder.ondataavailable = (e) => {
if (e.data.size > 0) {
recordedChunks.push(e.data);
}
};
// 步骤5监听录制结束自动下载视频
mediaRecorder.onstop = () => {
// 合并数据块为 Blob 视频文件
const blob = new Blob(recordedChunks, { type: 'video/webm' });
// 生成视频下载链接
const videoUrl = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = videoUrl;
a.download = `录制视频_${new Date().getTime()}.webm`;
a.click();
// 释放资源
URL.revokeObjectURL(videoUrl);
recordedChunks = [];
// 重置状态
resetRecordingState();
status.textContent = '状态:录制已停止,视频已下载';
};
// 步骤6启动录制
mediaRecorder.start(1000); // 每1秒生成一个数据块可选
status.textContent = '状态:正在录制...';
// 更新按钮状态
startBtn.disabled = true;
pauseBtn.disabled = false;
stopBtn.disabled = false;
// 监听流结束(用户手动关闭捕获窗口)
mediaStream.getTracks().forEach(track => {
track.addEventListener('ended', () => {
if (mediaRecorder.state !== 'inactive') {
mediaRecorder.stop();
}
status.textContent = '状态:用户终止了屏幕捕获';
resetRecordingState();
});
});
} catch (error) {
console.error('录制启动失败:', error);
status.textContent = `状态:启动失败 - ${error.message}`;
resetRecordingState();
}
});
// 2. 暂停录制
pauseBtn.addEventListener('click', () => {
if (mediaRecorder.state === 'recording') {
mediaRecorder.pause();
isPaused = true;
status.textContent = '状态:录制已暂停';
pauseBtn.disabled = true;
resumeBtn.disabled = false;
}
});
// 3. 继续录制
resumeBtn.addEventListener('click', () => {
if (mediaRecorder.state === 'paused') {
mediaRecorder.resume();
isPaused = false;
status.textContent = '状态:正在录制...';
pauseBtn.disabled = false;
resumeBtn.disabled = true;
}
});
// 4. 停止录制
stopBtn.addEventListener('click', () => {
if (mediaRecorder.state !== 'inactive') {
// 停止录制器
mediaRecorder.stop();
// 停止所有流轨道(释放屏幕捕获)
mediaStream.getTracks().forEach(track => track.stop());
status.textContent = '状态:正在处理视频...';
}
});
// 辅助函数:重置录制状态
function resetRecordingState() {
mediaStream = null;
mediaRecorder = null;
isPaused = false;
preview.srcObject = null;
// 重置按钮
startBtn.disabled = false;
pauseBtn.disabled = true;
resumeBtn.disabled = true;
stopBtn.disabled = true;
}
</script>
</body>
</html>