Greasy Fork

Greasy Fork is available in English.

Bilibili Surface

单击切换进度条显示/隐藏(显示时3秒后自动隐藏),双击仅播放/暂停,保留长按倍速、左右滑动进度、左右半屏上下滑亮度/音量

当前为 2026-04-15 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Bilibili Surface
// @namespace    http://tampermonkey.net/
// @version      1.0.7
// @description  单击切换进度条显示/隐藏(显示时3秒后自动隐藏),双击仅播放/暂停,保留长按倍速、左右滑动进度、左右半屏上下滑亮度/音量
// @author       You
// @match        *://*.bilibili.com/*
// @icon         https://www.bilibili.com/favicon.ico
// @run-at       document-end
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- 参数配置 ---
    const PRESS_DELAY = 250;
    const TARGET_SPEED = 3.0;
    const DOUBLE_TAP_DELAY = 250;
    const SEEK_SENSITIVITY = 0.2;
    const CTRL_SHOW_DURATION = 5000;
    // ---------------

    let pressTimer = null;
    let clickTimer = null;
    let clickCount = 0;
    let ctrlTimer = null;

    let originalSpeed = 1.0;
    let gestureType = '';
    let isInteracting = false;
    let wasPlaying = false;

    let startX = 0, startY = 0;
    let startVal = 0;

    // --- 1. 注入核心 CSS ---
    const css = `
        /* 强制显示底部控制栏容器 */
        .gemini-seeking .bpx-player-control-wrap,
        .gemini-seeking .bilibili-player-control-wrap {
            opacity: 1 !important;
            visibility: visible !important;
            transform: none !important;
            bottom: 0 !important;
        }

        /* 强制显示底部阴影遮罩 */
        .gemini-seeking .bpx-player-control-mask,
        .gemini-seeking .bilibili-player-control-mask {
            opacity: 1 !important;
            visibility: visible !important;
        }

        /* 强制显示顶部遮罩 */
        .gemini-seeking .bpx-player-top-mask {
            opacity: 1 !important;
            visibility: visible !important;
        }
    `;
    const style = document.createElement('style');
    style.textContent = css;
    (document.head || document.documentElement).appendChild(style);

    // --- 提示框相关 ---
    function formatTime(seconds) {
        const m = Math.floor(seconds / 60);
        const s = Math.floor(seconds % 60);
        return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
    }

    function getToast(playerArea) {
        let div = playerArea.querySelector('#gemini-clean-toast');
        if (!div) {
            div = document.createElement('div');
            div.id = 'gemini-clean-toast';
            div.style.cssText = `
                position: absolute;
                top: 20%; left: 50%; transform: translateX(-50%);
                padding: 10px 22px;
                background: rgba(0,0,0,0.75);
                color: #fff;
                border-radius: 8px;
                font-size: 18px;
                font-family: "Segoe UI", sans-serif; font-weight: 500;
                z-index: 100001;
                pointer-events: none;
                display: none;
                backdrop-filter: blur(4px);
                text-align: center;
                white-space: nowrap;
                box-shadow: 0 4px 10px rgba(0,0,0,0.3);
            `;
            playerArea.appendChild(div);
        }
        return div;
    }

    function showToast(playerArea, text) {
        const div = getToast(playerArea);
        div.innerText = text;
        div.style.display = 'block';
    }

    function hideToast(playerArea) {
        const div = playerArea.querySelector('#gemini-clean-toast');
        if (div) div.style.display = 'none';
    }

    // --- 核心逻辑 ---

    function getPlayerContainer(playerArea) {
        return playerArea.closest('.bpx-player-container') ||
               playerArea.closest('#bilibili-player') ||
               playerArea;
    }

    function toggleSeekingMode(playerArea, isSeeking) {
        const container = getPlayerContainer(playerArea);
        if (isSeeking) {
            container.classList.add('gemini-seeking');
        } else {
            container.classList.remove('gemini-seeking');
        }
    }

    function showCtrl(playerArea) {
        const container = getPlayerContainer(playerArea);
        const progressEntity = container.querySelector('.bpx-player-control-entity');

        container.classList.remove('bpx-state-no-cursor');
        container.setAttribute('data-ctrl-hidden', 'false');
        if (progressEntity) {
            progressEntity.setAttribute('data-shadow-show', 'false');
        }

        // 配合强制显示类,确保视觉层面也显示
        toggleSeekingMode(playerArea, true);
    }

    function hideCtrl(playerArea) {
        const container = getPlayerContainer(playerArea);
        const progressEntity = container.querySelector('.bpx-player-control-entity');

        container.classList.add('bpx-state-no-cursor');
        container.setAttribute('data-ctrl-hidden', 'true');
        if (progressEntity) {
            progressEntity.setAttribute('data-shadow-show', 'true');
        }

        toggleSeekingMode(playerArea, false);
    }

    function hideCtrlMenus(playerArea) {
        const container = getPlayerContainer(playerArea);
        const rightMenus = container.querySelector('.bpx-player-control-bottom-right');
        if (!rightMenus) return;

        rightMenus.childNodes.forEach(menu => {
            if (menu.classList) {
                menu.classList.remove('bpx-state-show');
            }
        });
    }

    function clearCtrlTimer() {
        if (ctrlTimer) {
            clearTimeout(ctrlTimer);
            ctrlTimer = null;
        }
    }

    function showCtrlForAWhile(playerArea) {
        clearCtrlTimer();
        showCtrl(playerArea);
        ctrlTimer = setTimeout(() => {
            hideCtrl(playerArea);
            ctrlTimer = null;
        }, CTRL_SHOW_DURATION);
    }

    function hideCtrlNow(playerArea) {
        clearCtrlTimer();
        hideCtrlMenus(playerArea);
        hideCtrl(playerArea);
    }

    function toggleCtrlWithAutoHide(playerArea) {
        const container = getPlayerContainer(playerArea);
        const isCtrlHidden = container.getAttribute('data-ctrl-hidden');

        // 按代码2的逻辑:
        // 当前显示,或者已有自动隐藏计时器 → 直接隐藏
        if (isCtrlHidden === 'false' || ctrlTimer) {
            hideCtrlNow(playerArea);
        } else {
            showCtrlForAWhile(playerArea);
        }
    }

    function getCurrentBrightness(video) {
        const filter = video.style.filter;
        if (!filter || !filter.includes('brightness')) return 100;
        const match = filter.match(/brightness\((\d+)%\)/);
        return match ? parseInt(match[1], 10) : 100;
    }

    function createSafeShield(playerArea) {
        if (playerArea.querySelector('#gemini-mobile-shield')) return;

        console.log('Gemini: 单击切换显示/隐藏进度条 / 双击播放暂停 版已部署');
        const shield = document.createElement('div');
        shield.id = 'gemini-mobile-shield';
        shield.style.cssText = `
            position: absolute; top: 0; left: 0; width: 100%; height: 85%;
            z-index: 20; background: transparent;
            touch-action: none !important; user-select: none;
        `;

        playerArea.appendChild(shield);

        shield.addEventListener('pointerdown', (e) => handleDown(e, playerArea));
        shield.addEventListener('pointermove', (e) => handleMove(e, playerArea));
        shield.addEventListener('pointerup', (e) => handleUp(e, playerArea));
        shield.addEventListener('pointercancel', (e) => handleUp(e, playerArea));
        shield.addEventListener('contextmenu', (e) => { e.preventDefault(); e.stopPropagation(); });
    }

    function handleDown(e, playerArea) {
        if (!e.isPrimary || e.button === 2) return;
        const video = playerArea.querySelector('video');
        if (!video) return;

        startX = e.clientX;
        startY = e.clientY;
        gestureType = '';
        isInteracting = false;

        originalSpeed = video.playbackRate;

        pressTimer = setTimeout(() => {
            if (!isInteracting) {
                gestureType = 'speed';
                isInteracting = true;
                video.playbackRate = TARGET_SPEED;
                showToast(playerArea, `倍速 ${TARGET_SPEED}x`);
            }
        }, PRESS_DELAY);
    }

    function handleMove(e, playerArea) {
        if (!startX) return;
        const video = playerArea.querySelector('video');
        if (!video) return;

        const deltaX = e.clientX - startX;
        const deltaY = startY - e.clientY;
        const absX = Math.abs(deltaX);
        const absY = Math.abs(deltaY);

        if (!isInteracting && (absX > 15 || absY > 15)) {
            if (pressTimer) {
                clearTimeout(pressTimer);
                pressTimer = null;
            }
            isInteracting = true;

            if (absX > absY) {
                gestureType = 'seek';
                startVal = video.currentTime;
                wasPlaying = !video.paused;
                video.pause();

                clearCtrlTimer();
                showCtrl(playerArea);
            } else {
                const screenW = window.innerWidth;
                if (startX < screenW / 2) {
                    gestureType = 'brightness';
                    startVal = getCurrentBrightness(video);
                } else {
                    gestureType = 'volume';
                    startVal = video.volume;
                }
            }
        }

        if (isInteracting) {
            if (gestureType === 'seek') {
                const seekDelta = deltaX * SEEK_SENSITIVITY;
                let targetTime = startVal + seekDelta;
                if (targetTime < 0) targetTime = 0;
                if (targetTime > video.duration) targetTime = video.duration;

                video.currentTime = targetTime;
                showToast(playerArea, `${formatTime(targetTime)} / ${formatTime(video.duration)}`);
            }
            else if (gestureType === 'volume') {
                const sensitivity = window.innerHeight * 0.8;
                let newVol = startVal + (deltaY / sensitivity);
                if (newVol > 1) newVol = 1;
                else if (newVol < 0) newVol = 0;
                video.volume = newVol;
                showToast(playerArea, `音量 ${Math.round(newVol * 100)}%`);
            }
            else if (gestureType === 'brightness') {
                const sensitivity = window.innerHeight * 0.8;
                let newBrit = startVal + (deltaY / sensitivity * 100);
                if (newBrit > 200) newBrit = 200;
                else if (newBrit < 0) newBrit = 0;
                video.style.filter = `brightness(${newBrit}%)`;
                showToast(playerArea, `亮度 ${Math.round(newBrit)}%`);
            }
        }
    }

    function handleUp(e, playerArea) {
        if (pressTimer) {
            clearTimeout(pressTimer);
            pressTimer = null;
        }

        const video = playerArea.querySelector('video');
        if (!video) {
            startX = 0;
            startY = 0;
            return;
        }

        if (isInteracting) {
            if (gestureType === 'speed') {
                video.playbackRate = originalSpeed;
            }
            else if (gestureType === 'seek') {
                if (wasPlaying) video.play();
                hideCtrl(playerArea);
            }

            isInteracting = false;
            gestureType = '';
            setTimeout(() => hideToast(playerArea), 500);
        } else {
            if (Math.abs(e.clientX - startX) < 10 && Math.abs(e.clientY - startY) < 10) {
                clickCount++;
                if (clickCount === 1) {
                    clickTimer = setTimeout(() => {
                        toggleCtrlWithAutoHide(playerArea);
                        clickCount = 0;
                        clickTimer = null;
                    }, DOUBLE_TAP_DELAY);
                } else if (clickCount === 2) {
                    if (clickTimer) {
                        clearTimeout(clickTimer);
                        clickTimer = null;
                    }
                    togglePlayOnly(video, playerArea);
                    clickCount = 0;
                }
            }
        }

        startX = 0;
        startY = 0;
    }

    function togglePlayOnly(video, playerArea) {
        if (video.paused) {
            video.play();
            showToast(playerArea, '播放');
        } else {
            video.pause();
            showToast(playerArea, '暂停');
        }

        setTimeout(() => hideToast(playerArea), 500);
    }

    function init() {
        const targetArea = document.querySelector('.bpx-player-video-area') ||
                           document.querySelector('.bilibili-player-video-wrap');

        if (targetArea) {
            createSafeShield(targetArea);
        }
    }

    const observer = new MutationObserver(() => init());
    observer.observe(document.body, { childList: true, subtree: true });
    window.addEventListener('load', init);

})();