上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

如何精准捕获播放的<video>元素?

[复制链接]
  • TA的每日心情
    奋斗
    2026-2-26 23:31
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    1

    主题

    2

    回帖

    6

    积分

    助理工程师

    积分
    6
    发表于 2026-2-25 13:45:55 | 显示全部楼层 | 阅读模式
    悬赏20油猫币未解决

    本帖最后由 Xbai 于 2026-2-25 14:05 编辑

    最近想用AI帮我写一个符合自己使用习惯的视频控制脚本,功能很好实现,就是这个核心功能"精准捕获播放的那个视频”一直不满意,怎么才能完美排除其他视频(比如悬停预览、首页轮播、直播之类的),只捕获播放的那个视频用于控制呢?

  • TA的每日心情
    奋斗
    2026-2-26 23:31
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    1

    主题

    2

    回帖

    6

    积分

    助理工程师

    积分
    6
    发表于 2026-2-25 13:49:07 | 显示全部楼层
    最近想用AI帮我写一个符合自己使用习惯的视频控制脚本,功能很好实现,就是这个核心功能"精准捕获播放的那个视频”一直不满意,怎么才能完美排除其他视频(比如悬停预览、首页轮播、直播之类的),只捕获播放的那个视频用于控制呢?
    回复

    使用道具 举报

  • TA的每日心情
    擦汗
    2026-1-10 12:23
  • 签到天数: 1 天

    [LV.1]初来乍到

    8

    主题

    20

    回帖

    44

    积分

    初级工程师

    积分
    44
    发表于 2026-2-25 14:36:25 | 显示全部楼层
    本帖最后由 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
    回复

    使用道具 举报

  • TA的每日心情
    奋斗
    2026-2-26 23:31
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    1

    主题

    2

    回帖

    6

    积分

    助理工程师

    积分
    6
    发表于 2026-3-1 19:15:41 | 显示全部楼层

    cyfung1031 发表于 2026-2-25 14:36

    这个很难
    要结合大量不同领域的技术
    video 本身的画面大小

    好吧🫡

    回复

    使用道具 举报

    该用户从未签到

    1

    主题

    3

    回帖

    6

    积分

    助理工程师

    积分
    6
    发表于 2026-3-1 21:30:29 | 显示全部楼层
    这是油猴脚本开发里一个很经典的难题,核心矛盾在于"播放中"这个状态太容易误判。给你系统梳理一套可靠的筛选策略:

    ---

    ## 核心思路:多条件评分,而不是单一判断

    不要试图用一个条件找到"那个视频",而是给每个 `<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('[class]')?.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[0];

      // 多个候选时打分
      return candidates.sort((a, b) => scoreVideo(b) - scoreVideo(a))[0];
    }
    ```

    ---

    ## 终极补充:用户交互信号

    如果上面还不够准,加上**用户行为监听**是最可靠的方案:

    ```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

    主题

    3

    回帖

    6

    积分

    助理工程师

    积分
    6
    发表于 2026-3-1 21:30:54 | 显示全部楼层

    这是油猴脚本开发里一个很经典的难题,核心矛盾在于"播放中"这个状态太容易误判。给你系统梳理一套可靠的筛选策略:


    核心思路:多条件评分,而不是单一判断

    不要试图用一个条件找到"那个视频",而是给每个 <video> 元素打分,分最高的就是目标。


    第一层:基础状态过滤(必须满足)

    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;
      });
    }

    第二层:排除"伪装者"的关键特征

    排除悬停预览视频

    预览视频的典型特征:

    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('[class]')?.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;
    }

    排除首页轮播/广告

    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;
    }

    排除直播流(如果你不需要控制直播)

    function isLiveStream(v) {
      // 直播的 duration 是 Infinity
      if (v.duration === Infinity) return true;
      // 或检查 URL 特征
      if (/\.m3u8|\/live\//i.test(v.currentSrc)) return true;
      return false;
    }

    第三层:打分系统(核心)

    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;
    }

    完整的捕获函数

    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[0];
    
      // 多个候选时打分
      return candidates.sort((a, b) => scoreVideo(b) - scoreVideo(a))[0];
    }

    终极补充:用户交互信号

    如果上面还不够准,加上用户行为监听是最可靠的方案:

    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% 的情况,单靠任何一个条件都容易翻车。

    回复

    使用道具 举报

    发表回复

    本版积分规则