H5视频调速器 手机优化版

针对手机浏览器的H5视频调速器,点击播放时立刻将视频移到网页视口中间(修复不移动问题)

// ==UserScript==
// @name         H5视频调速器 手机优化版
// @namespace    http://tampermonkey.net/
// @version      0.2.6
// @description  针对手机浏览器的H5视频调速器,点击播放时立刻将视频移到网页视口中间(修复不移动问题)
// @author       Mr.NullNull
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    var IntPbr = 1;
    var isPlaying = false;
    const IntPbrMax = 16;
    const IntPbrMin = 0.1;

    function Main() {
        if (document.querySelector("myPbrMain")) return;

        // 样式部分不变
        var myCss = document.createElement("style");
        myCss.innerHTML = `
            div#myPbrMain {
                padding: 0; margin: 0; width: auto;
                position: fixed; bottom: 28vw; right: 5vw; z-index: 2147483647;
                display: flex; flex-direction: column; align-items: flex-end;
            }
            div#myPbrMain>div.myPbrBtns { width: auto; margin-bottom: 0.8vw; display: none; }
            div.myPbrBtns { opacity: 0; }
            div.myPbrBtn {
                font: 4vw/1 '微软雅黑'; height: 4vw; padding: 2vw; margin-bottom: 0.8vw;
                border-radius: 20vw; color: #eee; background: rgba(0,0,0,0.65);
                cursor: pointer; white-space: nowrap; text-align: center; min-width: 12vw;
            }
            div#myPbrMain * { box-sizing: content-box; word-break: normal; }
            div.show { animation: shower 0.3s; opacity: 1; display: block !important; }
            div.hidden { animation: hiddener 0.3s; opacity: 0; display: none; }
            @keyframes shower { from { opacity: 0; } to { opacity: 1; } }
            @keyframes hiddener { from { opacity: 1; } to { opacity: 0; } }
        `;
        document.head.appendChild(myCss);

        // 控件HTML部分不变
        var mainDivTop = document.createElement("div");
        mainDivTop.id = "myPbrMain";
        mainDivTop.innerHTML = `
            <div class="myPbrBtns hidden">
                <div class="myPbrBtn" id="myPbrBtn_1000">x10.00</div>
                <div class="myPbrBtn" id="myPbrBtn_600">x6.00</div>
                <div class="myPbrBtn" id="myPbrBtn_300">x3.00</div>
                <div class="myPbrBtn" id="myPbrBtn_200">x2.00</div>
                <div class="myPbrBtn" id="myPbrBtn_150">x1.50</div>
                <div class="myPbrBtn" id="myPbrBtn_125">x1.25</div>
                <div class="myPbrBtn" id="myPbrBtn_100">x1.00</div>
            </div>
            <div class="myPbrBtn" id="myPbrBtn_Main">x1.XX</div>
            <div class="myPbrBtn" id="myPbrBtn_PlayPause">播放</div>
        `;
        document.body.appendChild(mainDivTop);

        var mainBtn = mainDivTop.querySelector("#myPbrBtn_Main");
        var speedPanel = mainDivTop.querySelector(".myPbrBtns");
        var playPauseBtn = mainDivTop.querySelector("#myPbrBtn_PlayPause");

        setMainBtnTxt();
        syncVideoStateToBtn();

        // 初始化同步速度
        setTimeout(() => syncAllVideosSpeed(), 500);

        // 同步按钮状态定时器
        var syncTimer = setInterval(() => {
            syncVideoStateToBtn();
            if (getTargetVideo()) clearInterval(syncTimer);
        }, 500);
        setTimeout(() => clearInterval(syncTimer), 5000);

        // 速度面板切换
        mainBtn.onclick = () => {
            speedPanel.classList.toggle("hidden");
            speedPanel.classList.toggle("show");
        };

        // 核心修复:播放/暂停按钮点击逻辑(确保先滚动再控制)
        playPauseBtn.onclick = function () {
            const targetVideo = getTargetVideo();
            if (targetVideo) {
                // 修复1:延迟50ms,确保视频加载完成后能获取到真实高度
                setTimeout(() => {
                    // 修复2:强制滚动到视频,用两种方式确保生效
                    scrollToVideoCenter(targetVideo);
                    // 修复3:滚动后立即执行播放/暂停
                    togglePlayPause(targetVideo);
                }, 50);
            }
        };

        // 速度按钮绑定(不变)
        speedPanel.querySelector("#myPbrBtn_1000").onclick = () => setVideoSpeed(10);
        speedPanel.querySelector("#myPbrBtn_600").onclick = () => setVideoSpeed(6);
        speedPanel.querySelector("#myPbrBtn_300").onclick = () => setVideoSpeed(3);
        speedPanel.querySelector("#myPbrBtn_200").onclick = () => setVideoSpeed(2);
        speedPanel.querySelector("#myPbrBtn_150").onclick = () => setVideoSpeed(1.5);
        speedPanel.querySelector("#myPbrBtn_125").onclick = () => setVideoSpeed(1.25);
        speedPanel.querySelector("#myPbrBtn_100").onclick = () => setVideoSpeed(1);

        // 获取目标视频(不变)
        function getTargetVideo() {
            const videos = document.querySelectorAll("video");
            if (videos.length === 0) return null;
            if (videos.length === 1) return videos[0];

            const viewportCenterY = window.scrollY + window.innerHeight / 2;
            let minDistance = Infinity, targetVideo = null;
            videos.forEach(video => {
                const rect = video.getBoundingClientRect();
                const videoCenterY = window.scrollY + rect.top + rect.height / 2;
                const distance = Math.abs(viewportCenterY - videoCenterY);
                if (distance < minDistance) {
                    minDistance = distance;
                    targetVideo = video;
                }
            });
            return targetVideo;
        }

        // 关键修复:滚动逻辑(用两种方式确保立刻移动到视口中间)
        function scrollToVideoCenter(video) {
            // 方式1:用getBoundingClientRect计算绝对位置(优先)
            const rect = video.getBoundingClientRect();
            // 视口中间 = 视口顶部(scrollY) + 视口高度一半 - 视频高度一半
            const targetTop = window.scrollY + (window.innerHeight / 2) - (rect.height / 2);
            
            // 强制立刻滚动(behavior: auto)
            window.scrollTo({ top: targetTop, left: 0, behavior: "auto" });

            // 方式2:备用方案,直接让视频元素调用scrollIntoView(确保生效)
            // 禁用smooth,强制立刻居中,与视口对齐
            video.scrollIntoView({
                block: "center",    // 垂直居中
                inline: "start",    // 水平靠左(手机适配)
                behavior: "auto"    // 立刻移动,无平滑
            });
        }

        // 以下函数均不变
        function syncVideoStateToBtn() {
            const targetVideo = getTargetVideo();
            if (targetVideo) {
                isPlaying = !targetVideo.paused;
                updatePlayPauseBtnText();
            }
        }

        function togglePlayPause(video) {
            video.paused ? video.play() : video.pause();
            isPlaying = !video.paused;
            updatePlayPauseBtnText();
        }

        function setVideoSpeed(speed) {
            IntPbr = Math.max(IntPbrMin, Math.min(speed, IntPbrMax));
            syncAllVideosSpeed();
            setMainBtnTxt();
            hideSpeedPanel();
        }

        function syncAllVideosSpeed() {
            document.querySelectorAll("video").forEach(v => v.playbackRate = IntPbr);
        }

        function updatePlayPauseBtnText() {
            playPauseBtn.textContent = isPlaying ? "暂停" : "播放";
        }

        function setMainBtnTxt() {
            mainBtn.innerHTML = "x" + IntPbr.toFixed(2);
        }

        function hideSpeedPanel() {
            speedPanel.classList.remove("show");
            speedPanel.classList.add("hidden");
        }

        window.addEventListener('scroll', syncVideoStateToBtn);

        document.addEventListener('play', e => {
            if (e.target.tagName === 'VIDEO') {
                e.target.playbackRate = IntPbr;
                if (e.target === getTargetVideo()) {
                    isPlaying = true;
                    updatePlayPauseBtnText();
                }
            }
        }, true);

        document.addEventListener('pause', e => {
            if (e.target.tagName === 'VIDEO' && e.target === getTargetVideo()) {
                isPlaying = false;
                updatePlayPauseBtnText();
            }
        }, true);
    }

    // 初始化脚本(不变)
    var sli = setInterval(() => {
        if (document.querySelector("video")) {
            Main();
            clearInterval(sli);
        }
    }, 1000);
    setTimeout(() => clearInterval(sli), 10000);
})();