Video Screenshot from h5player

Created 4 months ago
Updated a month ago
Press custom hotkey to take video screenshots, supports shadow DOM and cross-origin iframes
#视频截图
#多平台支持
#播放器增强
Statistics
Total Installs
130
Last 24h
+0
User Rating
-
Ad
Video screenshot tool with Shadow DOM and cross-origin ifram

Video Screenshot from h5player

Press a custom hotkey to capture the current video frame. Supports Shadow DOM and cross-origin iframes.

This is a Vibe Coding project. It extracts the powerful video screenshot feature from h5player and works with the global speed extension to fix the memory leak issue caused by h5player on Bilibili. It works well on the sites I frequently use (e.g., Bilibili, Bilibili Live, YouTube, third-party anime streaming sites, etc.). I am a complete beginner, so PRs are welcome.


1. What is this script?

This is a Tampermonkey userscript with a single job: press a hotkey on any video website and "capture" the current frame of the video.

Why it exists: many people switched to globalSpeed for playback speed to avoid the memory leak that h5player causes on Bilibili, but in doing so they lost h5player's very handy "video screenshot" feature. This script extracts the video screenshot feature from h5player and brings it back as a standalone tool.

How it differs from a normal "screen capture": a screen capture grabs the pixels on the screen, while this script reads the video frame at its original resolution — unaffected by scaling, danmaku overlays, picture-in-picture, etc. What you get is a screenshot at the video's own quality.

2. Key features

  • Hover to capture: hover over a video and that's the one that gets captured — this is the decisive factor
  • Smart selection: when the mouse isn't over any video, the script picks the "most likely intended" video using a scoring rule
  • Shadow DOM support: can reach videos hidden inside closed / open Shadow DOM
  • Cross-origin iframe support: can capture videos inside cross-origin iframes
  • Custom hotkey: S by default; re-record any key combination from the Tampermonkey menu
  • Automatic CORS handling: automatically adds crossorigin so pixels can be read, with graceful fallback on failure

3. How it works: which video gets captured?

This is the core logic of the script, and it happens in two steps.

Step 1: The hovered video is the "decisive factor"

As long as the mouse is hovering over a video, that video is captured unconditionally — no matter how large or prominent other videos on the page are.

The script tracks "which video the mouse is currently over" with a three-layer strategy:

  1. parentNode fast path: walks up the DOM parent chain and matches a regular-DOM video or a cached Shadow DOM video;
  2. composedPath forward match: matches the video element directly in the mouse event path (can reach inside a closed shadow);
  3. composedPath reverse host lookup: crosses closed shadow boundaries and finds the internal video via the host → shadowRoot WeakMap mapping.

The image below shows the script in action: [Placeholder image: a page playing a video, with the mouse pointer hovering over the center of the video frame. After pressing S, a separate preview window pops up in the top-right corner of the browser, showing the exact same frame as the video]

Step 2: When the mouse is NOT over a video, scoring picks the video

When the mouse is on an empty area of the page (or has moved off a video), the script runs the selection logic in findBestVideo():

① Visibility hard filter (fails → eliminated)

r.width > 100 && r.height > 50 // width > 100px and height > 50px
r.top < innerHeight && r.bottom > 0 // within viewport vertically
r.left < innerWidth && r.right > 0 // within viewport horizontally

Videos scrolled out of the viewport, or too small, are excluded from scoring.

② Scoring rule (highest score wins)

score = area (width × height) × playback weight (playing × 2)
  • The larger the area, the higher the score;
  • Currently playing videos (!paused && readyState > 2) get double the score;
  • A hovered video counts as a perfect score (highest priority).

③ Fallback

If no video is "visible", the script falls back to the first video that has decoded a frame (videoWidth > 0); if there is still none, it picks the first candidate.

The image below shows the script in action: [Placeholder image: a page with two videos at once — a larger one on the left that is playing, and a smaller one on the right that is paused. The mouse pointer is over the empty area in the middle. The script automatically captures the larger, playing video on the left and pops up a preview window]

Why this design?

"Where the mouse is" is the user's strongest signal of intent, so it acts as the decisive factor. When the mouse is not over a video, "large area + currently playing" best represents what the user is actually watching — the scoring logic exists to guess which video the user wants to capture when there's no hover signal.

4. Other implementation details

Shadow DOM support

The script hijacks Element.prototype.attachShadow and forces closed mode to behave like open so internal videos can be accessed, while still faking shadowRoot to return null so the site's own logic isn't broken. A WeakMap maintains a host → shadowRoot reverse mapping for O(1) lookups.

Cross-origin iframe delegation

The DOM inside a cross-origin iframe can't be accessed directly, so the script relays via postMessage between parent and child pages: when the top-level page has no video, it broadcasts a VIDEO_CAPTURE message to all iframes, and the script instance inside each iframe performs the capture on its own page.

Aggressive CORS strategy + auto recovery

  • Automatically adds crossorigin="anonymous" to every <video> so drawImage can read pixels;
  • Force-reloads already-loaded videos (except blob: URLs, to avoid breaking MSE players' internal state) so crossorigin takes effect;
  • If loading fails due to CORS, automatically removes crossorigin and retries, ensuring the video can at least play normally.

Hotkey

S by default. Re-record any combination (e.g. Ctrl+Shift+S) via the Configure hotkey item in the Tampermonkey menu.

The image below shows the hotkey configuration UI: [Placeholder image: a centered modal on the page, titled "Set Screenshot Hotkey", showing the current hotkey and the hint "Press a new key combination, or click Save", with Cancel and Save buttons at the bottom]

5. Installation

  1. Install the Tampermonkey browser extension;
  2. Open main.user.js and Tampermonkey will prompt you to install it;
  3. Open any video website and press S to take a screenshot.

6. FAQ

Pressing the hotkey does nothing?

  1. The browser may have blocked the popup — check the address bar for a "pop-up blocked" prompt and allow it;
  2. The video is cross-origin (CORS) so the script can't read the frame — press F12 to open DevTools and check the Console for errors;
  3. Make sure the page actually has a capturable <video> element (it may be hidden inside Shadow DOM).

Why show a preview window instead of downloading directly?

The preview window lets you visually confirm the captured frame is correct, and it keeps the original resolution. If the popup is blocked (especially when triggered through the iframe message chain), allow popups for the site in your browser settings.

Disclaimer

This project is for personal learning and legitimate use only. Please respect the copyright holders and the terms of service of the websites.


中文版

视频截图工具(提取自 h5player)· 按下自定义快捷键截取当前视频画面,支持 Shadow DOM 与跨域 iframe。

本项目为 Vibe Coding 产物,从 h5player 中提取了强大的视频截图功能,配合 global speed 插件使用,以解决 h5player 在 B 站上导致的内存溢出问题。目前已在常用网站上测试通过(如 B 站、B 站直播、YouTube、第三方动漫网站等)。本人为初学者,欢迎提交 PR。


一、这是什么脚本?

这是一个 Tampermonkey(油猴)用户脚本,功能只有一个:在任意视频网站按下快捷键,把当前视频的画面"截"下来

它解决的问题:很多人因为 h5player 在 B 站上的内存溢出问题,改用 globalSpeed 实现倍速,却因此失去了 h5player 中非常实用的「视频截图」能力。这个脚本把「视频截图」功能从 h5player 中独立提取出来,补上这个缺口。

它和普通"屏幕截图"的区别:普通截图截的是屏幕上的像素,本脚本直接读取视频帧的原始分辨率,不受画面缩放、弹幕遮挡、画中画等影响,得到的是视频本身清晰度的截图。

二、核心特性

  • 鼠标悬停即截取:鼠标停留在哪个视频上,就截哪个视频 —— 这是决定性依据
  • 智能评分选取:鼠标不在任何视频上时,按评分规则自动选出"最可能想看"的视频
  • 支持 Shadow DOM:能穿透 closed / open 模式的 Shadow DOM,找到内部隐藏的视频
  • 支持跨域 iframe:视频在跨域 iframe 里也能截到
  • 自定义快捷键:默认 S,可在油猴菜单里录制任意组合键
  • CORS 自动处理:自动加 crossorigin 保证能读到像素,失败时自动降级恢复

三、工作原理:脚本如何决定截哪个视频?

这是脚本最核心的逻辑,分两步。

第 1 步:鼠标停留位置是「决定性依据」

只要鼠标悬停在一个视频上,脚本就无条件优先截取它,页面上的其他视频即使更大、更显眼也不考虑。

脚本用三层策略追踪"鼠标此刻停留在哪个视频":

  1. parentNode 快速路径:沿 DOM 父链向上查找,命中常规 DOM 或已缓存的 Shadow DOM 视频;
  2. composedPath 正向直击:直接在鼠标事件路径里匹配视频元素(可命中 closed shadow 内部);
  3. composedPath 反向 host 查找:跨越 closed shadow 边界,通过 host → shadowRoot 的 WeakMap 映射找到内部视频。

如下图是脚本效果图 [占位图:一个正在播放的视频页面,鼠标指针悬停在视频画面中央,按下快捷键 S 后,浏览器右上角弹出一个独立的截图预览窗口,里面是与视频当前帧一致的画面]

第 2 步:鼠标不在视频上时,按评分选取

当鼠标停在页面空白处(或从视频上移开)时,脚本执行 findBestVideo() 的选取逻辑:

① 可见性硬性门槛(不满足直接淘汰)

r.width > 100 && r.height > 50 // 宽 > 100px 且高 > 50px
r.top < innerHeight && r.bottom > 0 // 在视口垂直范围内
r.left < innerWidth && r.right > 0 // 在视口水平范围内

滚出视口、或尺寸过小的视频,直接不参与评分。

② 评分规则(取最高分)

得分 = 面积(宽 × 高) × 播放权重(正在播放 × 2)
  • 面积越大,得分越高;
  • 正在播放的视频(!paused && readyState > 2)得分翻倍;
  • 鼠标悬停的视频直接视为满分(最高优先级)。

③ 兜底

若没有任何"可见"视频,则退回选取第一个已加载出画面的视频(videoWidth > 0);若仍没有,就取第一个候选视频。

如下图是脚本效果图 [占位图:一个页面上同时存在两个视频——左侧较大且正在播放,右侧较小且已暂停;鼠标指针停留在页面中间的空白处。脚本自动截取了左侧较大的、正在播放的那个视频的画面并弹出预览窗口]

为什么这样设计?

「鼠标放在哪里」是用户最强的意图信号,所以作为决定性依据;而当鼠标不在视频上时,"面积大 + 正在播放"最能代表用户当前正在看的内容,评分逻辑就是用来在没有悬停信号时,尽量猜中用户想截的视频。

四、其他实现细节

Shadow DOM 支持

脚本劫持 Element.prototype.attachShadow,把 closed 模式强制改为 open 以便访问内部视频,同时对外伪装 shadowRoot 仍返回 null,不破坏站点自身逻辑;再用 WeakMap 维护 host → shadowRoot 反向映射,实现 O(1) 查找。

跨域 iframe 代理

跨域 iframe 的内部 DOM 无法直接访问,脚本通过 postMessage 在父子页面间接力:顶层页面无视频时向所有 iframe 广播 VIDEO_CAPTURE 消息,iframe 内的脚本实例收到消息后在自己的页面里执行截图。

CORS 激进策略 + 自动恢复

  • 自动为每个 <video> 添加 crossorigin="anonymous",确保 drawImage 能读取像素;
  • 对已加载的视频强制重载(blob: 除外,避免破坏 MSE 流媒体播放器内部状态),保证 crossorigin 生效;
  • 若加载因 CORS 失败,自动移除 crossorigin 并重试,保证视频至少还能正常播放。

快捷键

默认 S。通过油猴菜单 Configure hotkey 可重新录制(如 Ctrl+Shift+S)。

如下图是脚本效果图 [占位图:页面上弹出居中的小窗,标题为 "Set Screenshot Hotkey",显示当前快捷键,提示 "Press a new key combination, or click Save",底部有 Cancel 与 Save 两个按钮]

五、安装

  1. 安装 Tampermonkey 浏览器扩展;
  2. 打开 main.user.js,Tampermonkey 会自动提示安装;
  3. 打开任意视频网站,按 S 即可截图。

六、常见问题

按下快捷键没反应?

  1. 浏览器可能拦截了弹窗 —— 检查地址栏是否有"弹窗被拦截"提示,放行即可;
  2. 视频跨域(CORS)导致读不到画面 —— 按 F12 打开开发者工具,查看 Console 是否有相关报错;
  3. 确认页面确实存在可截图的 <video> 元素(可能藏在 Shadow DOM 里)。

为什么用预览窗口而不是直接下载?

预览窗口能直观确认截取的画面是否正确,且保留原始分辨率。若弹窗被拦截(尤其 iframe 消息链路触发时),建议在浏览器设置中放行该站点的弹窗。

免责声明

本项目仅用于个人学习与正当用途,请遵守视频版权方与网站的相关规定。

Script Details
Applicable Sites
LicenseGPL
Permissions & access
Capabilities
Menu commandStorage