// ==UserScript==
// @name 通用视频播放器截图工具(测试)
// @icon data:image/png;base64,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
// @version 2025.07.18
// @description 适用于所有视频播放器的截图脚本(H5、Flash、iframe等)
// @author 嘉友友
// @match *://www.youtube.com/*
// @match *://www.bilibili.com/*
// @match *://live.bilibili.com/*
// @match *://www.twitch.tv/*
// @match *://live.douyin.com/*
// @match *://live.kuaishou.com/*
// @license GPL-3.0
// @namespace https://greasyfork.org/users/1336389
// ==/UserScript==
(function() {
'use strict';
// 缓存优化
const cache = {
videoElements: null,
lastCacheTime: 0,
cacheValidityTime: 2000, // 缓存2秒有效
queryResults: new Map()
};
// 防抖函数
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 节流函数
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// 优化的页面检查(使用缓存)
function isVideoPage() {
const cacheKey = 'isVideoPage';
if (cache.queryResults.has(cacheKey)) {
return cache.queryResults.get(cacheKey);
}
const url = window.location.href.toLowerCase();
const videoKeywords = [
'youtube', 'bilibili', 'iqiyi', 'youku', 'douyin', 'tiktok',
'twitch', 'kuaishou', 'huya', 'douyu', 'acfun', 'vimeo',
'video', 'movie', 'play', 'watch', 'live', 'stream'
];
const result = videoKeywords.some(keyword => url.includes(keyword)) ||
document.querySelector('video, embed, object, iframe[src*="video"], iframe[src*="player"]');
cache.queryResults.set(cacheKey, result);
// 清理缓存,避免内存泄漏
setTimeout(() => cache.queryResults.delete(cacheKey), 5000);
return result;
}
// 优化的视频元素查找(使用缓存和优化查询)
function findVideoElements() {
const now = Date.now();
// 使用缓存
if (cache.videoElements && now - cache.lastCacheTime < cache.cacheValidityTime) {
return cache.videoElements;
}
// 优化选择器,按常用程度排序
const selectors = [
'video', // 最常用的放前面
'.html5-main-video',
'.video-stream',
'.bilibili-live-player video',
'.bpx-player-video-wrap video',
'#movie_player video',
'.player-area video',
'.xgplayer video'
];
const videos = [];
const processedElements = new Set(); // 避免重复处理
for (const selector of selectors) {
try {
const elements = document.querySelectorAll(selector);
for (const element of elements) {
if (element.tagName === 'VIDEO' && !processedElements.has(element)) {
processedElements.add(element);
// 使用更高效的尺寸检查
if (element.offsetWidth > 0 && element.offsetHeight > 0) {
videos.push(element);
}
}
}
// 如果已经找到视频,优先使用第一个匹配的选择器结果
if (videos.length > 0 && selector === 'video') break;
} catch (e) {
// 忽略选择器错误,继续下一个
continue;
}
}
// 缓存结果
cache.videoElements = videos;
cache.lastCacheTime = now;
return videos;
}
// 优化的视频有效性检查(减少DOM查询)
function isValidVideo(video) {
// 先检查最简单的属性
if (video.hidden) return false;
// 使用 offsetWidth/offsetHeight 替代 getBoundingClientRect(性能更好)
if (video.offsetWidth <= 0 || video.offsetHeight <= 0) return false;
// 最后检查 computed style(最耗性能的)
const style = video.currentStyle || window.getComputedStyle(video);
return style.display !== 'none';
}
// 优化的视频元素截图(保持源质量)
async function captureVideoElement(video) {
try {
// 提前检查,避免无用计算
if (video.readyState < 1) {
return { success: false, message: '视频尚未加载,请稍后再试' };
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { alpha: false }); // 禁用alpha通道提升性能
canvas.width = video.videoWidth || video.clientWidth;
canvas.height = video.videoHeight || video.clientHeight;
try {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
} catch (e) {
if (e.name === 'SecurityError') {
return { success: false, message: 'CORS安全限制,无法截取此视频' };
}
throw e;
}
return new Promise((resolve) => {
canvas.toBlob(function(blob) {
if (!blob) {
resolve({ success: false, message: '生成图片失败' });
return;
}
downloadImage(blob, canvas.width, canvas.height);
resolve({
success: true,
width: canvas.width,
height: canvas.height
});
}, 'image/png', 1.0); // 保持源质量 1.0
});
} catch (error) {
return { success: false, message: '截图失败: ' + error.message };
}
}
// 优化的H5视频截图
function captureH5Video() {
const videos = findVideoElements();
// 优先级排序:可见 > 已加载 > 大尺寸
const sortedVideos = videos
.filter(video => isValidVideo(video) && video.readyState >= 1)
.sort((a, b) => {
const aRect = { width: a.offsetWidth, height: a.offsetHeight };
const bRect = { width: b.offsetWidth, height: b.offsetHeight };
return (bRect.width * bRect.height) - (aRect.width * aRect.height);
});
for (let video of sortedVideos) {
// 优先选择较大尺寸的视频
if (video.offsetWidth >= 200 && video.offsetHeight >= 150) {
return captureVideoElement(video);
}
}
// 如果没有大尺寸的,选择第一个可用的
if (sortedVideos.length > 0) {
return captureVideoElement(sortedVideos[0]);
}
return null;
}
// 优化的iframe查找(减少DOM查询)
function captureIframeVideo() {
const iframes = document.querySelectorAll('iframe[src*="video"], iframe[src*="player"], iframe[src*="youtube"], iframe[src*="bilibili"], iframe[src*="vimeo"], iframe[src*="live"]');
const validIframes = [];
for (const iframe of iframes) {
if (iframe.offsetWidth > 0 && iframe.offsetHeight > 0) {
validIframes.push({
element: iframe,
area: iframe.offsetWidth * iframe.offsetHeight
});
}
}
if (validIframes.length > 0) {
// 选择面积最大的iframe
validIframes.sort((a, b) => b.area - a.area);
return captureElementArea(validIframes[0].element);
}
return null;
}
// 优化的Flash/Object查找
function captureFlashVideo() {
const objects = document.querySelectorAll('object[type*="flash"], object[data*="video"], embed[type*="flash"], embed[src*="video"]');
const validObjects = [];
for (const obj of objects) {
if (obj.offsetWidth > 0 && obj.offsetHeight > 0) {
validObjects.push(obj);
break; // 找到第一个就够了
}
}
if (validObjects.length > 0) {
return captureElementArea(validObjects[0]);
}
return null;
}
// 优化的元素区域截图(保持源质量)
function captureElementArea(element) {
try {
const width = Math.min(element.offsetWidth, 1920);
const height = Math.min(element.offsetHeight, 1080);
if (width === 0 || height === 0) {
return { success: false, message: '播放器区域无效' };
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { alpha: false });
canvas.width = width;
canvas.height = height;
// 优化绘制过程,减少绘制调用
ctx.fillStyle = '#1a1a1a';
ctx.fillRect(0, 0, width, height);
ctx.strokeStyle = '#333333';
ctx.lineWidth = 2;
ctx.strokeRect(1, 1, width - 2, height - 2);
// 合并文字绘制
ctx.textAlign = 'center';
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 28px Arial';
ctx.fillText('🎬 视频播放器区域', width / 2, height / 2 - 40);
ctx.font = '18px Arial';
ctx.fillStyle = '#ffcc00';
ctx.fillText('无法直接截取视频内容', width / 2, height / 2);
ctx.fillStyle = '#888888';
ctx.font = '16px Arial';
ctx.fillText(`尺寸: ${Math.round(element.offsetWidth)} × ${Math.round(element.offsetHeight)}`, width / 2, height / 2 + 40);
return new Promise((resolve) => {
canvas.toBlob(function(blob) {
downloadImage(blob, width, height);
resolve({ success: true, isPlaceholder: true });
}, 'image/png', 1.0); // 保持源质量 1.0
});
} catch (error) {
return { success: false, message: '截图失败: ' + error.message };
}
}
// 优化的主截图函数
async function captureVideoFrame() {
try {
// 清除缓存,强制重新查找
cache.videoElements = null;
const h5Result = captureH5Video();
if (h5Result) {
const result = await h5Result;
if (result.success) {
showMessage(`📸 截图成功!分辨率: ${result.width}×${result.height}`, 'success');
return;
} else {
showMessage(result.message, 'warning');
}
}
// 使用 requestAnimationFrame 优化后续操作
await new Promise(resolve => requestAnimationFrame(resolve));
const iframeResult = captureIframeVideo();
if (iframeResult) {
const result = await iframeResult;
if (result.success) {
const type = result.isPlaceholder ? '播放器区域截图' : '截图';
showMessage(`📸 ${type}成功!`, 'success');
return;
}
}
const flashResult = captureFlashVideo();
if (flashResult) {
const result = await flashResult;
if (result.success) {
showMessage('📸 播放器区域截图成功!', 'success');
return;
}
}
showMessage('未找到可截图的视频播放器!\n请等待视频加载完成后再试', 'error');
} catch (error) {
showMessage('截图过程出错:' + error.message, 'error');
}
}
// 优化的视频信息获取
function getVideoInfo() {
const videos = findVideoElements();
if (videos.length === 0) {
return '📺 未检测到有效的视频播放器';
}
const infoParts = [`📺 检测到 ${videos.length} 个有效视频播放器:\n`];
let validCount = 0;
videos.forEach((video) => {
const isVisible = isValidVideo(video);
const videoWidth = video.videoWidth || 0;
const videoHeight = video.videoHeight || 0;
const displayWidth = video.offsetWidth;
const displayHeight = video.offsetHeight;
if (displayWidth > 0 && displayHeight > 0) {
validCount++;
const status = video.readyState >= 1 ? '✅' : '⏳';
const visibility = isVisible ? '👁️' : '🚫';
let info = `${validCount}. ${status}${visibility} `;
if (videoWidth && videoHeight) {
info += `视频分辨率: ${videoWidth}×${videoHeight}\n`;
if (displayWidth !== videoWidth || displayHeight !== videoHeight) {
info += ` 显示尺寸: ${displayWidth}×${displayHeight}\n`;
}
} else {
info += `显示尺寸: ${displayWidth}×${displayHeight}\n`;
}
if (video.duration && !isNaN(video.duration) && video.duration !== Infinity) {
const duration = Math.round(video.duration);
const minutes = Math.floor(duration / 60);
const seconds = duration % 60;
info += ` 时长: ${minutes}:${seconds.toString().padStart(2, '0')}\n`;
}
infoParts.push(info + '\n');
}
});
// 优化iframe检查
const validIframes = document.querySelectorAll('iframe[src*="video"], iframe[src*="player"], iframe[src*="youtube"], iframe[src*="bilibili"]');
const visibleIframes = Array.from(validIframes).filter(iframe =>
iframe.offsetWidth > 0 && iframe.offsetHeight > 0
);
if (visibleIframes.length > 0) {
infoParts.push(`📱 iframe播放器: ${visibleIframes.length} 个\n`);
visibleIframes.forEach((iframe, index) => {
infoParts.push(`${index + 1}. 尺寸: ${iframe.offsetWidth}×${iframe.offsetHeight}\n`);
});
}
if (validCount === 0 && visibleIframes.length === 0) {
return '📺 未检测到有尺寸信息的播放器';
}
return infoParts.join('').trim();
}
// 优化的下载函数(使用 revokeObjectURL 的延迟清理)
function downloadImage(blob, width, height) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
const now = new Date();
const timestamp = now.getFullYear() +
String(now.getMonth() + 1).padStart(2, '0') +
String(now.getDate()).padStart(2, '0') + '_' +
String(now.getHours()).padStart(2, '0') +
String(now.getMinutes()).padStart(2, '0') +
String(now.getSeconds()).padStart(2, '0');
const domain = window.location.hostname.replace(/\./g, '_');
a.download = `${domain}_video_${timestamp}.png`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// 延迟清理,确保下载完成
setTimeout(() => URL.revokeObjectURL(url), 1000);
}
// 优化的消息显示(缓存样式)
const messageStyles = {
success: { bg: 'rgba(40, 167, 69, 0.95)', icon: '✅' },
error: { bg: 'rgba(220, 53, 69, 0.95)', icon: '❌' },
warning: { bg: 'rgba(255, 193, 7, 0.95)', icon: '⚠️' },
info: { bg: 'rgba(52, 58, 64, 0.95)', icon: 'ℹ️' }
};
function showMessage(text, type = 'info') {
const messageDiv = document.createElement('div');
const style = messageStyles[type];
messageDiv.textContent = `${style.icon} ${text}`;
messageDiv.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${style.bg};
color: white;
padding: 12px 20px;
border-radius: 8px;
font-size: 14px;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
z-index: 999999;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
max-width: 350px;
word-wrap: break-word;
white-space: pre-line;
opacity: 0;
transform: translateX(20px);
transition: all 0.3s ease;
will-change: transform, opacity;
`;
document.body.appendChild(messageDiv);
// 使用 requestAnimationFrame 优化动画
requestAnimationFrame(() => {
messageDiv.style.opacity = '1';
messageDiv.style.transform = 'translateX(0)';
});
const hideDelay = type === 'error' ? 5000 : 3500;
setTimeout(() => {
messageDiv.style.opacity = '0';
messageDiv.style.transform = 'translateX(20px)';
setTimeout(() => {
if (document.body.contains(messageDiv)) {
document.body.removeChild(messageDiv);
}
}, 300);
}, hideDelay);
}
// 优化的键盘事件监听(防抖处理)
const debouncedCapture = debounce(captureVideoFrame, 300);
const debouncedInfo = debounce(() => {
const info = getVideoInfo();
showMessage(info, 'info');
}, 300);
document.addEventListener('keydown', function(event) {
// 避免在输入框中触发
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
return;
}
if (event.altKey && event.code === 'Digit1') {
event.preventDefault();
debouncedCapture();
}
if (event.altKey && event.code === 'Digit2') {
event.preventDefault();
debouncedInfo();
}
}, { passive: false });
// 优化的初始化
function initialize() {
if (!isVideoPage()) return;
const videos = findVideoElements();
if (videos.length > 0) {
showMessage(`🎬 截图工具已就绪\nAlt+1: 截图 Alt+2: 查看信息\n检测到 ${videos.length} 个有效播放器`, 'success');
} else {
showMessage('🎬 截图工具已就绪\nAlt+1: 截图 Alt+2: 查看信息', 'info');
}
}
// 优化的页面变化监听(节流 + 防抖)
let lastUrl = location.href;
const throttledObserver = throttle(() => {
const currentUrl = location.href;
if (currentUrl !== lastUrl) {
lastUrl = currentUrl;
// 清除缓存
cache.videoElements = null;
cache.queryResults.clear();
setTimeout(() => {
if (isVideoPage()) initialize();
}, 1500);
}
}, 1000);
// 使用更精确的观察配置
const observer = new MutationObserver(throttledObserver);
observer.observe(document, {
subtree: true,
childList: true,
attributes: false, // 不观察属性变化
characterData: false // 不观察文本变化
});
// 页面卸载时清理资源
window.addEventListener('beforeunload', () => {
observer.disconnect();
cache.queryResults.clear();
cache.videoElements = null;
});
// 优化的页面加载检查
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () =>
setTimeout(initialize, 1000), { once: true });
} else {
setTimeout(initialize, 1000);
}
})();