BiliBackToBeginning

打开或切换视频时,通过监听 video 元素的 loadstart 事件,精确地回到视频开头处,并修复状态控制问题

目前为 2025-04-23 提交的版本。查看 最新版本

// ==UserScript==
// @name         BiliBackToBeginning
// @namespace    https://github.com/ImQQiaoO/BiliBackToBeginning
// @version      v0.2.1
// @description  打开或切换视频时,通过监听 video 元素的 loadstart 事件,精确地回到视频开头处,并修复状态控制问题
// @author       ImQQiaoO
// @match        *://*.bilibili.com/video/*
// @match        *://*.bilibili.com/list/*
// @match        *://*.biliabili.com/watchlater/*
// @match        *://*.bilibili.com/medialist/play/*
// @match        *://*.bilibili.com/bangumi/play/*
// @exclude      *://message.bilibili.com/*
// @exclude      *://data.bilibili.com/*
// @exclude      *://cm.bilibili.com/*
// @exclude      *://link.bilibili.com/*
// @exclude      *://passport.bilibili.com/*
// @exclude      *://api.bilibili.com/*
// @exclude      *://api.*.bilibili.com/*
// @exclude      *://*.chat.bilibili.com/*
// @exclude      *://member.bilibili.com/*
// @exclude      *://www.bilibili.com/tensou/*
// @exclude      *://www.bilibili.com/correspond/*
// @exclude      *://live.bilibili.com/* // 排除所有直播页面
// @exclude      *://www.bilibili.com/blackboard/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  const STORAGE_KEY = "reset_bili_video_enabled"; // 用于 localStorage 的键名

  // --- 设置状态 ---
  function setEnabled(flag) {
    try {
      const valueToSet = flag ? "1" : "0";
      localStorage.setItem(STORAGE_KEY, valueToSet);
      console.log(
        `[B站重置进度脚本] 设置状态保存到 localStorage: ${STORAGE_KEY} = ${valueToSet}`
      );
      console.log(`[B站重置进度脚本] 设置已 ${flag ? "启用" : "禁用"}`);
    } catch (error) {
      console.error(
        "[B站重置进度脚本] 保存设置到 localStorage 时出错:",
        error
      );
    }
  }

  // --- 获取状态 ---
  function getEnabled() {
    let enabled = true; // 默认启用
    try {
      const storedValue = localStorage.getItem(STORAGE_KEY);
      console.log(
        `[B站重置进度脚本] 从 localStorage 读取状态: ${STORAGE_KEY} = ${storedValue}`
      );
      // 只有当明确存储了 "0" 时,才认为是禁用状态
      enabled = storedValue !== "0";
      // 注意:之前是 storedValue === null || storedValue === "1",现在改为 storedValue !== "0",
      // 这意味着任何非 "0" 的值(包括 null, "1", 或其他意外值)都会被视为启用,更符合“默认启用”的逻辑。
    } catch (error) {
      console.error(
        "[B站重置进度脚本] 从 localStorage 读取设置时出错:",
        error,
        "将使用默认值 (启用)."
      );
      enabled = true; // 出错时也默认启用
    }
    console.log(`[B站重置进度脚本] getEnabled() 返回: ${enabled}`);
    return enabled;
  }

  // --- 创建设置面板 ---
  function createSettingsPanel() {
    const style = `
            #biliResetPanel { /* ... 样式代码不变 ... */ }
            #biliResetPanel input[type=checkbox] { transform: scale(1.3); margin-right:8px; vertical-align: middle;}
            #biliResetPanelClose { cursor:pointer;color: #f66; float:right; font-size: 18px; line-height: 1;}
            #biliResetPanelBtn { /* ... 样式代码不变 ... */ }
        `;
    try {
      let head = document.head || document.getElementsByTagName("head")[0];
      if (head) {
        const styleEl = document.createElement("style");
        styleEl.textContent = style;
        head.appendChild(styleEl);
      } else {
        console.warn(
          "[B站重置进度脚本] 未找到 head 元素,样式可能无法应用."
        );
      }
    } catch (error) {
      console.error("[B站重置进度脚本] 添加样式时出错:", error);
    }

    const addPanelElements = () => {
      if (!document.body) {
        setTimeout(addPanelElements, 100);
        return;
      }

      try {
        // --- 创建元素 ---
        const panel = document.createElement("div");
        panel.id = "biliResetPanel";
        panel.style.display = "none"; // 初始隐藏
        panel.innerHTML = `
                    <span id="biliResetPanelClose" title="关闭设置面板">&times;</span>
                    <label>
                        <input type="checkbox" id="biliResetSwitch">
                        启用自动重置进度到0秒
                    </label>
                `;
        document.body.appendChild(panel);

        const btn = document.createElement("div");
        btn.id = "biliResetPanelBtn";
        btn.title = "打开【重置到0秒】设置";
        btn.textContent = "↩₀";
        document.body.appendChild(btn);

        // --- 获取元素引用 ---
        const switchCheckbox = document.getElementById("biliResetSwitch");
        const closeButton = document.getElementById("biliResetPanelClose");

        // --- 绑定事件 ---
        if (btn && panel) {
          btn.onclick = (e) => {
            e.stopPropagation();
            panel.style.display =
              panel.style.display === "block" ? "none" : "block";
          };
        }
        if (closeButton && panel) {
          closeButton.onclick = (e) => {
            e.stopPropagation();
            panel.style.display = "none";
          };
        }
        document.addEventListener("click", (e) => {
          if (
            panel && btn && panel.style.display === "block" &&
            !panel.contains(e.target) && !btn.contains(e.target)
          ) {
            panel.style.display = "none";
          }
        });

        // --- 初始化复选框状态并绑定 change 事件 ---
        if (switchCheckbox) {
          // **关键:在这里读取 localStorage 并设置初始状态**
          const initialState = getEnabled(); // 读取存储的状态
          console.log(
            `[B站重置进度脚本] 初始化复选框状态,读取到启用状态为: ${initialState}`
          );
          switchCheckbox.checked = initialState; // 应用到复选框

          // **关键:绑定 change 事件以更新 localStorage**
          switchCheckbox.onchange = (e) => {
            setEnabled(e.target.checked); // 保存新状态
          };
        } else {
          console.error(
            "[B站重置进度脚本] 未能找到 #biliResetSwitch 复选框元素."
          );
        }

        console.log(
          "[B站重置进度脚本] 设置面板 UI 创建完成."
        );
      } catch (error) {
        console.error("[B站重置进度脚本] 创建设置面板 UI 时出错:", error);
      }
    };

    // 确保 DOM Ready 后再执行面板创建
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', addPanelElements);
    } else {
        // DOM 已加载
        addPanelElements();
    }
  }

  // --- 核心功能:为视频元素添加重置逻辑 ---
  const processedVideoElements = new WeakSet();

  function setupVideoReset(videoElement) {
    if (!videoElement || processedVideoElements.has(videoElement)) {
      return;
    }
    processedVideoElements.add(videoElement);
    console.log("[B站重置进度脚本] 设置监听器于:", videoElement);

    const onLoadedMetadata = () => {
      // **关键:在事件触发时再次获取最新状态**
      const shouldReset = getEnabled();
      console.log(
        `[B站重置进度脚本] 'loadedmetadata' 触发. 当前启用状态: ${shouldReset}`
      );

      if (shouldReset) {
        if (videoElement.currentTime > 0) {
          console.log(
            `[B站重置进度脚本] 功能已启用且当前时间 > 0 (${videoElement.currentTime}), 准备重置.`
          );
          try {
            videoElement.currentTime = 0;
            console.log("[B站重置进度脚本] 视频进度已重置到 0 秒.");
          } catch (error) {
            console.error("[B站重置进度脚本] 重置 currentTime 时出错:", error);
          }
        } else {
           console.log("[B站重置进度脚本] 功能已启用, 但当前时间已为 0, 无需重置.");
        }
      } else {
        // **新增:明确打印禁用状态下的行为**
        console.log("[B站重置进度脚本] 功能已禁用, 跳过重置逻辑.");
      }
    };

    const onLoadStart = () => {
      console.log("[B站重置进度脚本] 'loadstart' 事件触发.");
      videoElement.removeEventListener("loadedmetadata", onLoadedMetadata);
      videoElement.addEventListener("loadedmetadata", onLoadedMetadata, {
        once: true,
      });
      console.log("[B站重置进度脚本] 已添加一次性 'loadedmetadata' 监听器.");
    };

    videoElement.addEventListener("loadstart", onLoadStart);
    console.log("[B站重置进度脚本] 已添加持续的 'loadstart' 监听器.");

    // 处理初始状态 (基本不变,依赖 onLoadedMetadata 内部的 getEnabled 判断)
    if (videoElement.readyState >= 1 && videoElement.currentSrc) {
      console.log(
        "[B站重置进度脚本] 检测到初始 video 状态可能需要检查 (readyState:", videoElement.readyState, ")"
      );
      // 直接调用一次检查,它内部会判断是否启用
      onLoadedMetadata();
    }

    videoElement.addEventListener("error", (e) => {
      console.warn("[B站重置进度脚本] Video 元素报告错误:", e);
    });
  }

  // --- 使用 MutationObserver 监听 DOM 变化  ---
  function observeDOMForVideo() {
      // ... MutationObserver 代码不变 ...
      const observer = new MutationObserver((mutationsList) => {
        for (const mutation of mutationsList) {
            if (mutation.type === "childList") {
                mutation.addedNodes.forEach((node) => {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        const videosToSetup = [];
                        if (node.tagName === "VIDEO") {
                            videosToSetup.push(node);
                        } else if (typeof node.querySelectorAll === "function") {
                            videosToSetup.push(...node.querySelectorAll("video"));
                        }
                        videosToSetup.forEach((video) => {
                            if (!processedVideoElements.has(video)) {
                                console.log("[B站重置进度脚本] MutationObserver 发现新 video.");
                                setupVideoReset(video);
                            }
                        });
                    }
                });
            }
        }
    });

    const config = { childList: true, subtree: true };

    const startObserving = () => {
        if (document.body) {
            observer.observe(document.body, config);
            console.log("[B站重置进度脚本] 已启动 MutationObserver.");

            const existingVideos = document.querySelectorAll("video");
            console.log(`[B站重置进度脚本] 初始检查发现 ${existingVideos.length} 个 video.`);
            existingVideos.forEach((video) => {
                if (!processedVideoElements.has(video)) {
                    console.log("[B站重置进度脚本] 处理初始存在的 video.");
                    setupVideoReset(video);
                }
            });
        } else {
            setTimeout(startObserving, 100);
        }
    };

    // 确保 DOM Ready 后再开始观察
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', startObserving);
    } else {
        startObserving();
    }

    return observer;
  }

  // --- 初始化 ---
  console.log("[B站重置进度脚本] 开始初始化...");
  try {
    createSettingsPanel(); // 创建设置 UI
    observeDOMForVideo(); // 开始监听视频元素
    console.log("[B站重置进度脚本] 初始化完成.");
  } catch (error) {
    console.error("[B站重置进度脚本] 初始化过程中发生严重错误:", error);
  }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址