您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
打开或切换视频时,通过监听 video 元素的 loadstart 事件,精确地回到视频开头处
当前为
// ==UserScript== // @name BiliBackToBeginning (v0.2.0 - 修复切换) // @namespace https://github.com/ImQQiaoO/BiliBackToBeginning // @version v0.2.0 // @description 打开或切换视频时,通过监听 video 元素的 loadstart 事件,精确地回到视频开头处 // @author ImQQiaoO // @match *://*.bilibili.com/video/* // @match *://*.bilibili.com/list/* // @match *://*.bilibili.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"; // --- 设置和获取 localStorage (保持不变) --- const STORAGE_KEY = "reset_bili_video_enabled"; function setEnabled(flag) { localStorage.setItem(STORAGE_KEY, flag ? "1" : "0"); console.log(`[B站重置进度脚本] 设置已 ${flag ? "启用" : "禁用"}`); } function getEnabled() { const storedValue = localStorage.getItem(STORAGE_KEY); return storedValue !== "0"; } // --- 创建设置面板 (保持不变) --- function createSettingsPanel() { // ... (面板创建和事件处理代码不变,此处省略) ... // (请确保你使用的是之前我们完善过的面板代码,包括检查 head/body 是否存在) const style = ` #biliResetPanel { position: fixed; bottom: 30px; right: 30px; z-index: 99999; background: #fff; color: #333; border: 1px solid #bbb; border-radius: 8px; box-shadow: 0 6px 16px rgba(0,0,0,.1); padding: 18px 26px 18px 18px; font-size: 16px; display: none; } #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 { position: fixed; bottom: 30px; right: 30px; z-index: 99998; background: #ffe2a0; color: #333; border: 1px solid #bbb; border-radius: 50%; width: 42px; height: 42px; text-align:center; line-height: 42px; font-size: 24px; cursor: pointer; box-shadow: 0 3px 12px rgba(0,0,0,.08); user-select: none; /* 防止意外选中文本 */ } `; // 确保 head 存在 let head = document.head; if (!head) { // 降级处理:如果 head 不存在,尝试创建或等待 head = document.getElementsByTagName("head")[0]; if (!head) { head = document.createElement("head"); document.documentElement.insertBefore(head, document.body); // 尝试插入 } } const styleEl = document.createElement("style"); styleEl.textContent = style; if (head) { head.appendChild(styleEl); } else { // 如果 head 实在没有,作为最后手段延迟添加 document.addEventListener("DOMContentLoaded", () => document.head.appendChild(styleEl) ); console.warn( "[B站重置进度脚本] Head element not found immediately, delaying style injection." ); } // 确保 body 存在再添加面板/按钮 const addPanelElements = () => { if (!document.body) { console.log( "[B站重置进度脚本] Body not ready, delaying panel creation..." ); setTimeout(addPanelElements, 100); // 稍后重试 return; } // 面板内容 const panel = document.createElement("div"); panel.id = "biliResetPanel"; panel.innerHTML = ` <span id="biliResetPanelClose" title="关闭设置面板">×</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); // 获取元素(在添加到 DOM 后) const switchCheckbox = document.getElementById("biliResetSwitch"); if (switchCheckbox) { switchCheckbox.checked = getEnabled(); switchCheckbox.onchange = (e) => { setEnabled(e.target.checked); }; } else { console.error("[B站重置进度脚本] Checkbox 'biliResetSwitch' not found after creation."); } const closeButton = document.getElementById("biliResetPanelClose"); // 绑定事件 if (btn) { btn.onclick = (e) => { e.stopPropagation(); panel.style.display = panel.style.display === "block" ? "none" : "block"; }; } if (closeButton) { 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"; } }); if (switchCheckbox) { switchCheckbox.checked = getEnabled(); switchCheckbox.onchange = (e) => { setEnabled(e.target.checked); }; } else { console.error( "[B站重置进度脚本] Checkbox 'biliResetSwitch' not found after creation." ); } console.log( "[B站重置进度脚本] Settings panel created and events attached." ); }; addPanelElements(); } // --- 核心功能:为视频元素添加重置逻辑 (修改版) --- // 使用 WeakSet 来跟踪已经处理过的 video 元素,防止重复添加监听器, // 并且当 video 元素被 GC 时自动移除引用,避免内存泄漏。 const processedVideoElements = new WeakSet(); function setupVideoReset(videoElement) { // 如果元素无效或已处理过,则直接返回 if (!videoElement || processedVideoElements.has(videoElement)) { return; } console.log( "[B站重置进度脚本] 开始为 video 元素设置重置监听器:", videoElement ); processedVideoElements.add(videoElement); // 标记为已处理 // 定义在元数据加载后执行的重置操作 const onLoadedMetadata = () => { // 每次触发时都重新检查功能是否启用 if (getEnabled()) { if (videoElement.currentTime > 0) { console.log( `[B站重置进度脚本] 'loadedmetadata' 触发 (当前时间: ${videoElement.currentTime}), 准备重置到 0 秒.` ); videoElement.currentTime = 0; console.log("[B站重置进度脚本] 视频进度已重置到 0 秒."); } else { // console.log("[B站重置进度脚本] 'loadedmetadata' 触发, 但当前时间已为 0, 无需重置."); } } else { // console.log("[B站重置进度脚本] 'loadedmetadata' 触发, 但功能已禁用."); } }; // 定义在每次视频开始加载时执行的操作 const onLoadStart = () => { console.log( "[B站重置进度脚本] 'loadstart' 事件触发, 为本次加载添加一次性的 'loadedmetadata' 监听器." ); // 移除上一次可能遗留的监听器 (以防万一) videoElement.removeEventListener("loadedmetadata", onLoadedMetadata); // 添加一次性的 'loadedmetadata' 监听器 videoElement.addEventListener("loadedmetadata", onLoadedMetadata, { once: true, }); }; // 为 video 元素 **持续地** 监听 'loadstart' 事件 videoElement.addEventListener("loadstart", onLoadStart); console.log( "[B站重置进度脚本] 已为 video 元素添加持续的 'loadstart' 监听器." ); // --- 处理初始状态 --- // 有时候脚本运行时,视频可能已经开始加载甚至元数据已加载完毕 // readyState: 0=HAVE_NOTHING, 1=HAVE_METADATA, 2=HAVE_CURRENT_DATA, ... // 如果 readyState >= 1,说明至少元数据有了,或者正在加载。 // 并且 currentSrc 有值,说明确实有一个有效的视频源。 if (videoElement.readyState >= 1 && videoElement.currentSrc) { console.log( "[B站重置进度脚本] 检测到脚本运行时 video 已有数据或在加载中 (readyState:", videoElement.readyState, "), 尝试立即触发一次重置检查." ); // 直接调用一次 onLoadedMetadata,检查当前是否需要重置 // 注意:这里不再手动调用 onLoadStart,因为如果 loadstart 事件还没触发,我们直接处理;如果已经触发了,onLoadStart 里的逻辑会处理。 // 我们关心的是元数据加载完的那个时间点。 onLoadedMetadata(); // 如果此时元数据已经加载完毕,但 'loadedmetadata' 事件可能已经错过了, // 再次添加监听器以防万一 (如果上面 onLoadedMetadata 没重置,这里可以确保下次 loadstart 能触发)。 // 实际上 onLoadStart 会处理这个,这里的冗余检查可以简化。 // 简化:信任 loadstart 会在未来触发(如果视频源变了),或者信任上面的 onLoadedMetadata() 已经处理了当前状态。 } else if (videoElement.currentSrc) { // 如果有 src 但 readyState 是 0,说明即将开始加载 console.log( "[B站重置进度脚本] 检测到脚本运行时 video 有 src 但未加载 (readyState: 0), 等待 'loadstart'..." ); // 这种情况 'loadstart' 事件会正常触发,无需额外操作。 } // (可选) 错误处理 videoElement.addEventListener("error", (e) => { console.warn("[B站重置进度脚本] Video 元素报告错误:", e); // 可以在这里移除监听器,但 'loadstart' 可能在尝试重新加载时仍需要 // videoElement.removeEventListener('loadstart', onLoadStart); }); } // --- 使用 MutationObserver 监听 DOM 变化 (逻辑微调) --- function observeDOMForVideo() { const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === "childList") { mutation.addedNodes.forEach((node) => { // 检查添加的节点本身或其子孙节点是否包含 <video> if (node.nodeType === Node.ELEMENT_NODE) { const videosToSetup = []; if (node.tagName === "VIDEO") { videosToSetup.push(node); } else if (typeof node.querySelectorAll === "function") { // 使用 querySelectorAll 查找所有后代 video 元素 videosToSetup.push(...node.querySelectorAll("video")); } videosToSetup.forEach((video) => { // 对每个找到的 video 元素,如果之前未处理过,则设置监听器 if (!processedVideoElements.has(video)) { console.log( "[B站重置进度脚本] MutationObserver 发现新的 video 元素." ); setupVideoReset(video); } }); } }); } // 注意:我们不关心 removedNodes,因为 WeakSet 会自动处理元素移除的情况 } }); const config = { childList: true, subtree: true }; // 确保 body 存在后再开始观察 const startObserving = () => { if (document.body) { observer.observe(document.body, config); console.log( "[B站重置进度脚本] 已启动 DOM 变化监听 (MutationObserver)." ); // --- 初始检查 --- // 检查页面加载时已经存在的 video 元素 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 { console.log( "[B站重置进度脚本] Body not ready, delaying MutationObserver start..." ); setTimeout(startObserving, 100); // 稍后重试 } }; startObserving(); // 返回 observer 实例(虽然当前代码没用到,但保留是好习惯) return observer; } // --- 初始化 --- try { createSettingsPanel(); // 创建设置 UI observeDOMForVideo(); // 开始监听视频元素 console.log("[B站重置进度脚本] 初始化完成."); } catch (error) { console.error("[B站重置进度脚本] 初始化过程中发生错误:", error); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址