如何精准捕获播放的<video>元素?
本帖最后由 Xbai 于 2026-2-25 14:05 编辑最近想用AI帮我写一个符合自己使用习惯的视频控制脚本,功能很好实现,就是这个核心功能"精准捕获播放的那个视频”一直不满意,怎么才能完美排除其他视频(比如悬停预览、首页轮播、直播之类的),只捕获播放的那个视频用于控制呢? 最近想用AI帮我写一个符合自己使用习惯的视频控制脚本,功能很好实现,就是这个核心功能"精准捕获播放的那个视频”一直不满意,怎么才能完美排除其他视频(比如悬停预览、首页轮播、直播之类的),只捕获播放的那个视频用于控制呢? 本帖最后由 cyfung1031 于 2026-2-25 14:37 编辑
这个很难
要结合大量不同领域的技术
video 本身的画面大小
video 有没有声音
video 是不是被静音
video 是不是被用户点击过
video 有没有触发 IntersectionObserver
video 的大小占了画面多大比例
游标停留在video的时间长度
video 有没有相应的控制项
video 的ancestor 有没有 absolute 广告层
然后结合所有做一个统计评分
最高分那一个 + 基本条件符合 就是你要的那一个
---
找一些現有的改腳本吧
例如 https://greasyfork.org/zh-CN/scripts/4870-maximize-video
cyfung1031 发表于 2026-2-25 14:36
这个很难
要结合大量不同领域的技术
video 本身的画面大小
好吧🫡 这是油猴脚本开发里一个很经典的难题,核心矛盾在于"播放中"这个状态太容易误判。给你系统梳理一套可靠的筛选策略:
---
## 核心思路:多条件评分,而不是单一判断
不要试图用一个条件找到"那个视频",而是给每个 `<video>` 元素打分,分最高的就是目标。
---
## 第一层:基础状态过滤(必须满足)
```javascript
function getCandidates() {
return [...document.querySelectorAll('video')].filter(v => {
if (v.readyState < 2) return false; // 还没加载到可播放
if (v.error) return false; // 出错的排除
if (!v.src && !v.currentSrc) return false; // 没有源的排除
return true;
});
}
```
---
## 第二层:排除"伪装者"的关键特征
### 排除悬停预览视频
预览视频的典型特征:
```javascript
function isPreviewVideo(v) {
// 预览通常静音、且 duration 很短或是循环小片段
if (v.muted && v.loop && v.duration < 15) return true;
// 检查 CSS 类名/父元素关键词
const keywords = ['preview', 'hover', 'thumb', 'thumbnail', 'mute'];
const context = (v.className + v.id + (v.closest('')?.className || '')).toLowerCase();
if (keywords.some(k => context.includes(k))) return true;
// 预览视频通常尺寸很小
const rect = v.getBoundingClientRect();
if (rect.width < 200 || rect.height < 120) return true;
return false;
}
```
### 排除首页轮播/广告
```javascript
function isBannerVideo(v) {
// 轮播通常:自动播放 + 静音 + 没有 controls
if (v.autoplay && v.muted && !v.controls) return true;
// 检查是否在广告/banner容器内
const adKeywords = ['ad', 'banner', 'carousel', 'swiper', 'hero'];
let el = v.parentElement;
for (let i = 0; i < 5; i++) {// 往上找5层
if (!el) break;
const ctx = (el.className + el.id).toLowerCase();
if (adKeywords.some(k => ctx.includes(k))) return true;
el = el.parentElement;
}
return false;
}
```
### 排除直播流(如果你不需要控制直播)
```javascript
function isLiveStream(v) {
// 直播的 duration 是 Infinity
if (v.duration === Infinity) return true;
// 或检查 URL 特征
if (/\.m3u8|\/live\//i.test(v.currentSrc)) return true;
return false;
}
```
---
## 第三层:打分系统(核心)
```javascript
function scoreVideo(v) {
let score = 0;
const rect = v.getBoundingClientRect();
const viewH = window.innerHeight;
const viewW = window.innerWidth;
// ✅ 正在播放(最重要)
if (!v.paused && !v.ended) score += 100;
// ✅ 有声音(用户主动看的视频通常不静音)
if (!v.muted && v.volume > 0) score += 50;
// ✅ 有进度(说明用户真的在看)
if (v.currentTime > 3) score += 30;
// ✅ 尺寸大(主视频通常占据大面积)
const area = rect.width * rect.height;
score += Math.min(area / 1000, 60);// 最多加60分
// ✅ 在视口内且居中
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const distFromCenter = Math.hypot(centerX - viewW/2, centerY - viewH/2);
score += Math.max(0, 40 - distFromCenter / 20);
// ✅ 有 controls 属性(主播放器通常有)
if (v.controls) score += 20;
// ❌ 静音扣分
if (v.muted) score -= 30;
// ❌ 在视口外扣分(用户看不到的)
if (rect.bottom < 0 || rect.top > viewH) score -= 80;
return score;
}
```
---
## 完整的捕获函数
```javascript
function getTargetVideo() {
const candidates = getCandidates()
.filter(v => !isPreviewVideo(v))
.filter(v => !isBannerVideo(v));
// 如果不需要排直播就注释掉下一行
// .filter(v => !isLiveStream(v));
if (candidates.length === 0) return null;
if (candidates.length === 1) return candidates;
// 多个候选时打分
return candidates.sort((a, b) => scoreVideo(b) - scoreVideo(a));
}
```
---
## 终极补充:用户交互信号
如果上面还不够准,加上**用户行为监听**是最可靠的方案:
```javascript
let userPickedVideo = null;
// 用户点击过的视频,直接记为目标
document.addEventListener('click', e => {
const v = e.target.closest('video') || e.composedPath().find(el => el.tagName === 'VIDEO');
if (v) userPickedVideo = v;
}, true);
function getTargetVideo() {
// 优先用用户点击过的(且还在播放的)
if (userPickedVideo && !userPickedVideo.paused) return userPickedVideo;
// 否则走评分逻辑
return getScoredVideo();
}
```
---
## 总结
| 场景 | 主要克制手段 |
|---|---|
| 悬停预览 | 尺寸小 + muted+loop+短时长 + 类名关键词 |
| 首页轮播 | autoplay+muted+无controls + 父元素关键词 |
| 直播 | duration === Infinity |
| 多视频并存 | 打分系统(有声 > 尺寸大 > 居中 > 有进度)|
| 兜底 | 监听用户点击,直接锁定目标 |
实际写脚本时,**打分 + 用户点击监听**组合基本能覆盖 99% 的情况,单靠任何一个条件都容易翻车。 这是油猴脚本开发里一个很经典的难题,核心矛盾在于"播放中"这个状态太容易误判。给你系统梳理一套可靠的筛选策略:
---
## 核心思路:多条件评分,而不是单一判断
不要试图用一个条件找到"那个视频",而是给每个 `<video>` 元素打分,分最高的就是目标。
---
## 第一层:基础状态过滤(必须满足)
```javascript
function getCandidates() {
return [...document.querySelectorAll('video')].filter(v => {
if (v.readyState < 2) return false; // 还没加载到可播放
if (v.error) return false; // 出错的排除
if (!v.src && !v.currentSrc) return false; // 没有源的排除
return true;
});
}
```
---
## 第二层:排除"伪装者"的关键特征
### 排除悬停预览视频
预览视频的典型特征:
```javascript
function isPreviewVideo(v) {
// 预览通常静音、且 duration 很短或是循环小片段
if (v.muted && v.loop && v.duration < 15) return true;
// 检查 CSS 类名/父元素关键词
const keywords = ['preview', 'hover', 'thumb', 'thumbnail', 'mute'];
const context = (v.className + v.id + (v.closest('')?.className || '')).toLowerCase();
if (keywords.some(k => context.includes(k))) return true;
// 预览视频通常尺寸很小
const rect = v.getBoundingClientRect();
if (rect.width < 200 || rect.height < 120) return true;
return false;
}
```
### 排除首页轮播/广告
```javascript
function isBannerVideo(v) {
// 轮播通常:自动播放 + 静音 + 没有 controls
if (v.autoplay && v.muted && !v.controls) return true;
// 检查是否在广告/banner容器内
const adKeywords = ['ad', 'banner', 'carousel', 'swiper', 'hero'];
let el = v.parentElement;
for (let i = 0; i < 5; i++) {// 往上找5层
if (!el) break;
const ctx = (el.className + el.id).toLowerCase();
if (adKeywords.some(k => ctx.includes(k))) return true;
el = el.parentElement;
}
return false;
}
```
### 排除直播流(如果你不需要控制直播)
```javascript
function isLiveStream(v) {
// 直播的 duration 是 Infinity
if (v.duration === Infinity) return true;
// 或检查 URL 特征
if (/\.m3u8|\/live\//i.test(v.currentSrc)) return true;
return false;
}
```
---
## 第三层:打分系统(核心)
```javascript
function scoreVideo(v) {
let score = 0;
const rect = v.getBoundingClientRect();
const viewH = window.innerHeight;
const viewW = window.innerWidth;
// ✅ 正在播放(最重要)
if (!v.paused && !v.ended) score += 100;
// ✅ 有声音(用户主动看的视频通常不静音)
if (!v.muted && v.volume > 0) score += 50;
// ✅ 有进度(说明用户真的在看)
if (v.currentTime > 3) score += 30;
// ✅ 尺寸大(主视频通常占据大面积)
const area = rect.width * rect.height;
score += Math.min(area / 1000, 60);// 最多加60分
// ✅ 在视口内且居中
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const distFromCenter = Math.hypot(centerX - viewW/2, centerY - viewH/2);
score += Math.max(0, 40 - distFromCenter / 20);
// ✅ 有 controls 属性(主播放器通常有)
if (v.controls) score += 20;
// ❌ 静音扣分
if (v.muted) score -= 30;
// ❌ 在视口外扣分(用户看不到的)
if (rect.bottom < 0 || rect.top > viewH) score -= 80;
return score;
}
```
---
## 完整的捕获函数
```javascript
function getTargetVideo() {
const candidates = getCandidates()
.filter(v => !isPreviewVideo(v))
.filter(v => !isBannerVideo(v));
// 如果不需要排直播就注释掉下一行
// .filter(v => !isLiveStream(v));
if (candidates.length === 0) return null;
if (candidates.length === 1) return candidates;
// 多个候选时打分
return candidates.sort((a, b) => scoreVideo(b) - scoreVideo(a));
}
```
---
## 终极补充:用户交互信号
如果上面还不够准,加上**用户行为监听**是最可靠的方案:
```javascript
let userPickedVideo = null;
// 用户点击过的视频,直接记为目标
document.addEventListener('click', e => {
const v = e.target.closest('video') || e.composedPath().find(el => el.tagName === 'VIDEO');
if (v) userPickedVideo = v;
}, true);
function getTargetVideo() {
// 优先用用户点击过的(且还在播放的)
if (userPickedVideo && !userPickedVideo.paused) return userPickedVideo;
// 否则走评分逻辑
return getScoredVideo();
}
```
---
## 总结
| 场景 | 主要克制手段 |
|---|---|
| 悬停预览 | 尺寸小 + muted+loop+短时长 + 类名关键词 |
| 首页轮播 | autoplay+muted+无controls + 父元素关键词 |
| 直播 | duration === Infinity |
| 多视频并存 | 打分系统(有声 > 尺寸大 > 居中 > 有进度)|
| 兜底 | 监听用户点击,直接锁定目标 |
实际写脚本时,**打分 + 用户点击监听**组合基本能覆盖 99% 的情况,单靠任何一个条件都容易翻车。
页:
[1]