You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何确保Chrome扩展脚本每次访问YouTube播放页时都运行?

问题:确保Chrome扩展仅在YouTube播放页稳定运行

我正在编写一款Chrome扩展,希望在访问YouTube播放页(youtube.com/watch?)时触发运行。参考10年前的相关问题,解决方案效果不稳定。

当前manifest.json配置导致content.js在所有YouTube页面运行,而非仅播放页:

"content_scripts": [
    {
      "js": ["scripts/content.js"],
      "matches": ["https://www.youtube.com/*"]
    }
]

content.js使用事件监听器,但并非每次加载播放页都运行,有时需刷新才生效。脚本功能是读取视频当前时间、时长和播放速度,计算剩余观看时间并显示在视频信息面板中:

document.addEventListener("yt-navigate-start", removeRemainingTime);
document.addEventListener("yt-navigate-finish", setupRemainingTimeDisplay);

let intervalId;

function setupRemainingTimeDisplay() {
  const video = document.querySelector("video");
  const timeWrapper = document.querySelector(".ytp-time-wrapper");

  if (timeWrapper && video) {
    // Create or find the existing span for remaining time
    let remainingTime = document.querySelector(".remaining-time");
    if (!remainingTime) {
      remainingTime = document.createElement("span");
      remainingTime.className = "remaining-time";
      remainingTime.style.color = "#ddd";
      timeWrapper.appendChild(remainingTime);
    }

    // Update remaining time every second
    intervalId = setInterval(() => {
      updateRemainingTime(video, remainingTime);
    }, 1000);
  }
}

function updateRemainingTime(video, remainingTime) {
  const { duration, playbackRate, currentTime } = video;

  if (!duration || isNaN(playbackRate) || isNaN(currentTime)) {
    remainingTime.textContent = " • Invalid time";
    return;
  }

  const timeLeft = calculateRemainingTime(currentTime, playbackRate, duration);
  remainingTime.textContent = ` • ${formatTimestamp(timeLeft)} left`;
}

function calculateRemainingTime(currentTime, playbackRate, duration) {
  return Math.max((duration - currentTime) / playbackRate, 0);
}

function formatTimestamp(seconds) {
  const totalSeconds = Math.floor(seconds);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const secs = totalSeconds % 60;

  if (hours > 0) {
    return `${hours}:${String(minutes).padStart(2, "0")}:${String(
      secs
    ).padStart(2, "0")}`;
  } else {
    return `${minutes}:${String(secs).padStart(2, "0")}`;
  }
}

function removeRemainingTime() {
  const remainingTimeElements = document.querySelectorAll(".remaining-time");
  remainingTimeElements.forEach((element) => element.remove());
  clearInterval(intervalId);
}

请问如何确保content.js每次访问YouTube播放页时都能运行?


解决方案

1. 精准匹配YouTube播放页URL

修改manifest.json的匹配规则,只针对播放页加载脚本,同时指定脚本执行时机:

"content_scripts": [
    {
      "js": ["scripts/content.js"],
      "matches": ["https://www.youtube.com/watch*"],
      "run_at": "document_idle"
    }
]
  • watch*确保匹配所有带watch路径的URL(包括带视频ID参数的?v=xxx)
  • run_at: "document_idle"保证DOM结构加载完成后再执行脚本,避免初始阶段找不到目标元素

2. 适配YouTube的SPA路由机制

YouTube是单页应用,页面切换不会刷新整个页面,仅依赖yt-navigate事件稳定性不足,需结合URL变化监听:
在content.js开头添加URL校验逻辑:

// 页面初始加载时检查并初始化
checkAndSetup();

// 监听浏览器前进后退的URL变化
window.addEventListener('popstate', checkAndSetup);
// 监听YouTube内部路由切换完成事件
document.addEventListener('yt-navigate-finish', checkAndSetup);

function checkAndSetup() {
  // 验证当前是否为播放页
  if (window.location.pathname === '/watch' || window.location.href.includes('watch?')) {
    removeRemainingTime(); // 先清理之前的实例
    setupRemainingTimeDisplay();
  } else {
    removeRemainingTime(); // 非播放页时清理元素和定时器
  }
}

3. 优化元素查找的稳定性

YouTube的DOM元素可能动态加载,直接使用querySelector可能找不到目标元素,改用MutationObserver监听元素出现:
修改setupRemainingTimeDisplay函数:

function setupRemainingTimeDisplay() {
  // 监听目标元素的动态生成
  const observer = new MutationObserver((mutations) => {
    const timeWrapper = document.querySelector(".ytp-time-wrapper");
    const video = document.querySelector("video");
    if (timeWrapper && video) {
      observer.disconnect(); // 找到元素后停止监听
      initRemainingTime(video, timeWrapper);
    }
  });

  observer.observe(document.body, {
    childList: true,
    subtree: true
  });
}

function initRemainingTime(video, timeWrapper) {
  let remainingTime = document.querySelector(".remaining-time");
  if (!remainingTime) {
    remainingTime = document.createElement("span");
    remainingTime.className = "remaining-time";
    remainingTime.style.color = "#ddd";
    timeWrapper.appendChild(remainingTime);
  }

  updateRemainingTime(video, remainingTime); // 立即更新一次
  intervalId = setInterval(() => {
    updateRemainingTime(video, remainingTime);
  }, 1000);
}

4. 增强清理逻辑的健壮性

确保定时器和DOM元素在页面切换时完全清理,避免内存泄漏:

function removeRemainingTime() {
  const remainingTimeElements = document.querySelectorAll(".remaining-time");
  remainingTimeElements.forEach((element) => element.remove());
  if (intervalId) {
    clearInterval(intervalId);
    intervalId = null; // 重置定时器ID,避免重复清理报错
  }
}

内容的提问来源于stack exchange,提问作者Natalie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 00:12:05