BiliBackToBeginning

打开视频时默认回到视频开头处

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

// ==UserScript==
// @name         BiliBackToBeginning
// @namespace    https://github.com/ImQQiaoO/BiliBackToBeginning
// @version      v0.1.1
// @description  打开视频时默认回到视频开头处
// @author       ImQQiaoO
// @match        *://*.bilibili.com/*
// @exclude      *://message.bilibili.com/pages/nav/header_sync
// @exclude      *://message.bilibili.com/pages/nav/index_new_pc_sync
// @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/p/html/*
// @exclude      *://live.bilibili.com/live-room-play-game-together
// @exclude      *://www.bilibili.com/blackboard/comment-detail.html*
// @exclude      *://www.bilibili.com/blackboard/newplayer.html*
// @license      MIT
// @grant        none // Explicitly state no special GM functions needed if true
// ==/UserScript==

// ==UserScript==
// @name         BiliBackToBeginning (Improved)
// @namespace    https://github.com/ImQQiaoO/BiliBackToBeginning
// @version      v0.2.0
// @description  打开视频时通过监听事件更精确地回到视频开头处
// @author       ImQQiaoO & AI Assistant
// @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() {
        // 默认启用,如果localStorage没有记录的话
        const storedValue = localStorage.getItem(STORAGE_KEY);
        return storedValue === null || storedValue === "1";
    }

    // 创建设置面板 (代码与原版一致,此处省略以保持简洁,请将原代码的 createSettingsPanel 函数复制到这里)
    function createSettingsPanel() {
        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; /* 防止意外选中文本 */
            }
        `;
        const styleEl = document.createElement("style");
        styleEl.textContent = style;
        document.head.appendChild(styleEl);

        // 面板内容
        const panel = document.createElement("div");
        panel.id = "biliResetPanel";
        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');

        btn.onclick = (e) => {
            e.stopPropagation(); // 阻止事件冒泡
            panel.style.display = panel.style.display === "block" ? "none" : "block";
        }
        document.getElementById("biliResetPanelClose").onclick = (e) => {
             e.stopPropagation();
             panel.style.display = "none";
        }
        // 点击页面其他地方关闭面板
        document.addEventListener('click', (e) => {
            if (!panel.contains(e.target) && !btn.contains(e.target)) {
                panel.style.display = 'none';
            }
        });

        switchCheckbox.checked = getEnabled();
        switchCheckbox.onchange = (e) => {
            setEnabled(e.target.checked);
            // 如果用户禁用了,可能需要一些即时反馈或处理,但目前逻辑是视频加载时检查
        };
    }


    // 核心功能:为视频元素添加重置逻辑
    function setupVideoReset(videoElement) {
        // 定义事件处理函数
        const onLoadedMetadata = () => {
            if (getEnabled()) {
                 // 检查当前时间是否不为0,避免B站本身就从0开始时进行不必要的操作
                if (videoElement.currentTime > 0) {
                    console.log(`[B站重置进度脚本] 检测到视频元数据加载完毕 (当前时间: ${videoElement.currentTime}),准备重置到 0 秒。`);
                    videoElement.currentTime = 0;
                    console.log("[B站重置进度脚本] 已将视频进度重置到 0 秒。");
                    // 注意:不需要手动 play() 或 pause(),B站播放器会自行处理播放状态
                } else {
                    // console.log("[B站重置进度脚本] 视频元数据加载完毕,当前时间已为 0,无需重置。");
                }
            }
        };

        // 添加一次性的 'loadedmetadata' 事件监听器
        // 这个事件在视频的元数据(时长、尺寸等)加载后触发,是设置初始状态的好时机
        videoElement.addEventListener('loadedmetadata', onLoadedMetadata, { once: true });
        console.log("[B站重置进度脚本] 已为视频元素添加 'loadedmetadata' 监听器。");

        // (可选) 添加错误处理或移除监听器的逻辑,以防万一
        videoElement.addEventListener('error', () => {
            console.warn("[B站重置进度脚本] 视频加载出错,移除监听器。");
            videoElement.removeEventListener('loadedmetadata', onLoadedMetadata);
        }, { once: true });
    }

    // 使用 MutationObserver 监听 DOM 变化,以捕获动态加载的 <video> 元素
    function observeDOMForVideo() {
        const observer = new MutationObserver((mutationsList) => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach(node => {
                        // 确保是元素节点
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            // 检查添加的节点本身是否是 video
                            if (node.tagName === 'VIDEO') {
                                console.log("[B站重置进度脚本] 检测到 <video> 元素直接添加到DOM。");
                                setupVideoReset(node);
                            } 
                            // 检查添加的节点是否包含 video 元素 (更常见)
                            else if (node.querySelector) {
                                const videoElement = node.querySelector('video');
                                if (videoElement) {
                                    console.log("[B站重置进度脚本] 检测到 <video> 元素在子节点中添加。");
                                    setupVideoReset(videoElement);
                                    // B站页面通常只有一个主视频,找到后可以考虑停止观察以提高性能
                                    // 但考虑到可能有特殊页面或组件,暂时保持观察
                                    // observer.disconnect(); 
                                }
                            }
                        }
                    });
                }
            }
        });

        // 配置观察选项:观察子节点的添加或删除,并递归观察所有后代节点
        const config = { childList: true, subtree: true };

        // 指定目标节点开始观察
        observer.observe(document.body, config);
        console.log("[B站重置进度脚本] 开始监听 DOM 变化以查找 <video> 元素。");

        // 同时,检查页面加载时是否已存在 video 元素 (处理非动态加载的情况)
        const existingVideo = document.querySelector("video");
        if (existingVideo) {
            console.log("[B站重置进度脚本] 页面加载时检测到已存在的 <video> 元素。");
            // 需要稍微延迟一下,确保脚本的其他部分或B站自身逻辑已准备好
            // 但 'loadedmetadata' 事件本身就处理了时机问题,所以直接设置监听器
            setupVideoReset(existingVideo);
        } else {
            // console.log("[B站重置进度脚本] 页面加载时未发现 <video> 元素,等待 DOM 变化...");
        }

        // 返回 observer 实例,以便将来可能需要停止观察
        return observer;
    }

    // 初始化
    createSettingsPanel(); // 创建设置 UI
    observeDOMForVideo(); // 开始监听视频元素

})();

QingJ © 2025

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