BiliBackToBeginning (Improved)

打开视频时通过监听事件更精确地回到视频开头处

目前為 2025-04-23 提交的版本,檢視 最新版本

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

    // 创建设置面板
    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; /* 防止意外选中文本 */
            }
        `;
        // Ensure head exists before appending style
        let head = document.head;
        if (!head) {
            head = document.createElement('head');
            document.documentElement.insertBefore(head, document.body); // Basic fallback
        }
        const styleEl = document.createElement("style");
        styleEl.textContent = style;
        head.appendChild(styleEl);


        // Ensure body exists before appending panel/button
        const addPanelElements = () => {
            if (!document.body) {
                 setTimeout(addPanelElements, 50); // Wait if body not ready
                 return;
            }

            // Panel content
            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);

            // Show/hide button
            const btn = document.createElement("div");
            btn.id = "biliResetPanelBtn";
            btn.title = "打开【重置到0秒】设置";
            btn.textContent = "↩₀"; // Use a more symbolic icon
            document.body.appendChild(btn);

            const switchCheckbox = document.getElementById('biliResetSwitch');
            const closeButton = document.getElementById("biliResetPanelClose");

            btn.onclick = (e) => {
                e.stopPropagation(); // Prevent event bubbling
                panel.style.display = panel.style.display === "block" ? "none" : "block";
            }
            closeButton.onclick = (e) => {
                 e.stopPropagation();
                 panel.style.display = "none";
            }
            // Close panel when clicking outside
            document.addEventListener('click', (e) => {
                // Check if panel and button exist and panel is visible
                 if (panel && btn && panel.style.display === 'block' && !panel.contains(e.target) && !btn.contains(e.target)) {
                     panel.style.display = 'none';
                 }
            });

            if (switchCheckbox) { // Check if element exists before accessing properties
                switchCheckbox.checked = getEnabled();
                switchCheckbox.onchange = (e) => {
                    setEnabled(e.target.checked);
                };
            } else {
                 console.error("[B站重置进度脚本] Checkbox 'biliResetSwitch' not found.");
            }
        };
        addPanelElements(); // Call function to add elements

    }


    // Core function: add reset logic to video elements
    function setupVideoReset(videoElement) {
        if (!videoElement) return; // Guard clause

        // Define event handler function
        const onLoadedMetadata = () => {
            if (getEnabled()) {
                // Check if current time is not 0 to avoid unnecessary operations
                if (videoElement.currentTime > 0) {
                    console.log(`[B站重置进度脚本] Detected video metadata loaded (Current time: ${videoElement.currentTime}), preparing to reset to 0s.`);
                    videoElement.currentTime = 0;
                    console.log("[B站重置进度脚本] Video progress reset to 0s.");
                    // Note: No need to manually play() or pause(), the Bilibili player will handle playback state
                } else {
                    // console.log("[B站重置进度脚本] Video metadata loaded, current time is already 0, no reset needed.");
                }
            }
        };

        // Add a one-time 'loadedmetadata' event listener
        videoElement.addEventListener('loadedmetadata', onLoadedMetadata, { once: true });
        console.log("[B站重置进度脚本] Added 'loadedmetadata' listener to video element.");

        // (Optional) Add error handling or listener removal logic just in case
        videoElement.addEventListener('error', () => {
            console.warn("[B站重置进度脚本] Video loading error, removing listener.");
            // Attempt to remove the listener if it hasn't fired yet
            try {
                 videoElement.removeEventListener('loadedmetadata', onLoadedMetadata);
            } catch(e) {/* ignore */}
        }, { once: true });
    }

    // Use MutationObserver to listen for DOM changes and catch dynamically loaded <video> elements
    function observeDOMForVideo() {
        const observer = new MutationObserver((mutationsList) => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach(node => {
                        // Ensure it's an element node
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            let videoElement = null;
                            // Check if the added node itself is a video
                            if (node.tagName === 'VIDEO') {
                                console.log("[B站重置进度脚本] Detected <video> element added directly to DOM.");
                                videoElement = node;
                            }
                            // Check if the added node contains a video element (more common)
                            else if (typeof node.querySelector === 'function') { // Check if querySelector exists
                                videoElement = node.querySelector('video');
                                if (videoElement) {
                                    console.log("[B站重置进度脚本] Detected <video> element added within a child node.");
                                }
                            }

                            if (videoElement) {
                                setupVideoReset(videoElement);
                                // Consider disconnecting observer if only one main video expected, for performance.
                                // observer.disconnect(); 
                            }
                        }
                    });
                }
            }
        });

        // Configuration for the observer: watch for child node additions/removals in the entire subtree
        const config = { childList: true, subtree: true };

        // Start observing the target node (document.body is usually sufficient)
        // Ensure body exists before observing
        const startObserving = () => {
             if (document.body) {
                  observer.observe(document.body, config);
                  console.log("[B站重置进度脚本] Started listening for DOM changes to find <video> elements.");

                  // Also, check if a video element already exists on page load (for non-dynamic loading)
                  const existingVideo = document.querySelector("video");
                  if (existingVideo) {
                      console.log("[B站重置进度脚本] Detected existing <video> element on page load.");
                      setupVideoReset(existingVideo);
                  } else {
                      // console.log("[B站重置进度脚本] No <video> element found on page load, waiting for DOM changes...");
                  }
             } else {
                 setTimeout(startObserving, 50); // Wait if body not ready
             }
        };
        startObserving();


        // Return the observer instance in case you need to disconnect it later
        return observer;
    }

    // Initialization
    try {
        createSettingsPanel(); // Create settings UI
        observeDOMForVideo(); // Start listening for video elements
    } catch (error) {
        console.error("[B站重置进度脚本] Error during initialization:", error);
    }


})();

QingJ © 2025

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