网页视频倍速播放器·视频全页面播放适配

将视频播放窗口适配为全页面显示

// ==UserScript==
// @name         网页视频倍速播放器·视频全页面播放适配
// @namespace    https://toolsdar.cn/
// @version      0.2
// @description  将视频播放窗口适配为全页面显示
// @author       Your name
// @match        *://*/*
// @grant        none
// @run-at       document-start
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    class VideoFullpage {
        constructor() {
            this.handleGlobalKey = this.handleGlobalKey.bind(this);
            document.addEventListener('keydown', this.handleGlobalKey);
            this.originalStates = new Map();
        }

        handleGlobalKey(e) {
            if (e.code === 'KeyH' && !e.ctrlKey && !e.altKey && !e.metaKey) {
                e.preventDefault();
                const video = document.querySelector('video');
                if (!video) return;

                if (video.classList.contains('video-fullpage')) {
                    this.toggleFullpage(video);
                } else {
                    if (!this.initialized) {
                        this.createButton();
                        this.initialized = true;
                    }
                    this.toggleFullpage(video);
                }
            }
        }

        createButton() {
            const button = document.createElement('button');
            this.button = button;
            button.innerHTML = '全页面';
            button.style.cssText = `
                position: fixed;
                right: 20px;
                top: 20px;
                z-index: 999999;
                background: rgba(0, 0, 0, 0.6);
                color: white;
                border: none;
                border-radius: 4px;
                padding: 8px 16px;
                cursor: pointer;
                font-size: 14px;
                transition: background-color 0.3s;
                display: none;
            `;

            const style = document.createElement('style');
            style.textContent = `
                .active {
                    background: rgba(0, 0, 0, 0.9) !important;
                }
            `;
            document.head.appendChild(style);

            document.body.appendChild(button);

            button.addEventListener('click', () => {
                const video = document.querySelector('video');
                if (video) {
                    this.toggleFullpage(video);
                    button.classList.toggle('active');
                }
            });
        }

        toggleFullpage(video) {
            if (video.classList.contains('video-fullpage')) {
                this.restoreOriginalState(video);
                if (this.button) {
                    this.button.classList.remove('active');
                    this.button.style.display = 'none';
                }
            } else {
                this.saveOriginalState(video);
                this.enterFullpage(video);
                if (this.button) {
                    this.button.classList.add('active');
                    this.button.style.display = 'block';
                }
            }
        }

        saveOriginalState(video) {
            const originalState = {
                style: video.style.cssText,
                parentNode: video.parentNode,
                nextSibling: video.nextSibling,
                scrollTop: window.scrollY,
                scrollLeft: window.scrollX,
                bodyOverflow: document.body.style.overflow,
                bodyPosition: document.body.style.position,
                videoPosition: {
                    width: video.offsetWidth,
                    height: video.offsetHeight,
                    rect: video.getBoundingClientRect()
                }
            };
            this.originalStates.set(video, originalState);
        }

        restoreOriginalState(video) {
            const state = this.originalStates.get(video);
            if (!state) return;

            if (this.videoEvents) {
                video.removeEventListener('click', this.videoEvents.click);
                document.removeEventListener('keydown', this.videoEvents.keydown);
                this.videoEvents = null;
            }

            const container = document.querySelector('.video-fullpage-container');
            if (container) {
                if (state.parentNode) {
                    if (state.nextSibling) {
                        state.parentNode.insertBefore(video, state.nextSibling);
                    } else {
                        state.parentNode.appendChild(video);
                    }
                }
                container.remove();
            }

            video.classList.remove('video-fullpage');
            video.style.cssText = state.style || '';

            document.body.style.overflow = state.bodyOverflow || '';
            document.body.style.position = state.bodyPosition || '';

            requestAnimationFrame(() => {
                window.scrollTo({
                    left: state.scrollLeft || 0,
                    top: state.scrollTop || 0,
                    behavior: 'instant'
                });
            });

            window.removeEventListener('resize', this.resizeHandler);

            this.originalStates.delete(video);

            const hint = document.querySelector('.video-seek-hint');
            if (hint) {
                hint.remove();
            }

            video.style.cssText = state.style || '';
            video.style.width = '';
            video.style.height = '';
            video.style.maxWidth = '100%';
            video.style.maxHeight = '100%';

            video.dispatchEvent(new CustomEvent('exitfullpage'));

            video.offsetHeight;

            if (this.progressCleanup) {
                this.progressCleanup();
                this.progressCleanup = null;
            }
        }

        enterFullpage(video) {
            const container = document.createElement('div');
            container.className = 'video-fullpage-container';
            container.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                width: 100vw;
                height: 100vh;
                background: rgba(0, 0, 0, 0.9);
                z-index: 2147483646;
                display: flex;
                justify-content: center;
                align-items: center;
            `;

            video.classList.add('video-fullpage');

            const updateVideoSize = () => {
                const windowWidth = window.innerWidth;
                const windowHeight = window.innerHeight;
                const videoRatio = video.videoWidth / video.videoHeight;
                const windowRatio = windowWidth / windowHeight;

                if (windowRatio > videoRatio) {
                    video.style.cssText = `
                        height: ${windowHeight}px !important;
                        width: ${windowHeight * videoRatio}px !important;
                        position: relative !important;
                        z-index: 2147483647 !important;
                        background: transparent !important;
                        margin: 0 !important;
                        padding: 0 !important;
                        cursor: pointer !important;
                    `;
                } else {
                    video.style.cssText = `
                        width: ${windowWidth}px !important;
                        height: ${windowWidth / videoRatio}px !important;
                        position: relative !important;
                        z-index: 2147483647 !important;
                        background: transparent !important;
                        margin: 0 !important;
                        padding: 0 !important;
                        cursor: pointer !important;
                    `;
                }
            };

            this.resizeHandler = updateVideoSize;
            window.addEventListener('resize', this.resizeHandler);

            const handleClick = (e) => {
                e.preventDefault();
                e.stopPropagation();
                if (video.paused) {
                    video.play();
                } else {
                    video.pause();
                }
            };

            const handleKeydown = (e) => {
                if (!video.classList.contains('video-fullpage')) return;

                switch (e.code) {
                    case 'Space':
                        e.preventDefault();
                        if (video.paused) {
                            video.play();
                        } else {
                            video.pause();
                        }
                        break;
                    case 'ArrowLeft':
                        e.preventDefault();
                        video.currentTime = Math.max(0, video.currentTime - 3);
                        this.showSeekHint(video, '⏪ -3s');
                        break;
                    case 'ArrowRight':
                        e.preventDefault();
                        video.currentTime = Math.min(video.duration, video.currentTime + 3);
                        this.showSeekHint(video, '⏩ +3s');
                        break;
                    case 'ArrowUp':
                        e.preventDefault();
                        video.volume = Math.min(1, video.volume + 0.1);
                        this.showSeekHint(video, `🔊 ${Math.round(video.volume * 100)}%`);
                        break;
                    case 'ArrowDown':
                        e.preventDefault();
                        video.volume = Math.max(0, video.volume - 0.1);
                        this.showSeekHint(video, `🔉 ${Math.round(video.volume * 100)}%`);
                        break;
                    case 'KeyC':
                        e.preventDefault();
                        video.playbackRate = Math.min(16, video.playbackRate + 0.1);
                        this.showSeekHint(video, `⏩ ${video.playbackRate.toFixed(1)}x`);
                        break;
                    case 'KeyX':
                        e.preventDefault();
                        video.playbackRate = Math.max(0.1, video.playbackRate - 0.1);
                        this.showSeekHint(video, `⏪ ${video.playbackRate.toFixed(1)}x`);
                        break;
                    case 'KeyZ':
                        e.preventDefault();
                        video.playbackRate = 1.0;
                        this.showSeekHint(video, '⏮ 1.0x');
                        break;
                }
            };

            video.addEventListener('click', handleClick);
            document.addEventListener('keydown', handleKeydown);

            this.videoEvents = {
                click: handleClick,
                keydown: handleKeydown
            };

            container.appendChild(video);
            document.body.appendChild(container);
            document.body.style.overflow = 'hidden';

            if (video.readyState >= 1) {
                updateVideoSize();
            } else {
                video.addEventListener('loadedmetadata', updateVideoSize);
            }

            const progressContainer = document.createElement('div');
            progressContainer.className = 'video-progress-container';
            progressContainer.style.cssText = `
                position: absolute;
                bottom: 0;
                left: 0;
                right: 0;
                height: 40px;
                background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
                opacity: 0;
                transition: opacity 0.3s;
                display: flex;
                align-items: center;
                padding: 0 20px;
                z-index: 2147483647;
            `;

            const progress = document.createElement('div');
            progress.className = 'video-progress';
            progress.style.cssText = `
                position: relative;
                width: 100%;
                height: 4px;
                background: rgba(255, 255, 255, 0.3);
                border-radius: 2px;
                cursor: pointer;
            `;

            const progressFill = document.createElement('div');
            progressFill.className = 'video-progress-fill';
            progressFill.style.cssText = `
                position: absolute;
                left: 0;
                top: 0;
                height: 100%;
                background: #ff0000;
                border-radius: 2px;
            `;

            const timeDisplay = document.createElement('div');
            timeDisplay.className = 'video-time-display';
            timeDisplay.style.cssText = `
                color: white;
                margin-left: 10px;
                font-size: 14px;
                min-width: 100px;
                text-align: right;
            `;

            progress.appendChild(progressFill);
            progressContainer.appendChild(progress);
            progressContainer.appendChild(timeDisplay);

            const updateProgress = () => {
                const percent = (video.currentTime / video.duration) * 100;
                progressFill.style.width = `${percent}%`;
                timeDisplay.textContent = `${formatTime(video.currentTime)} / ${formatTime(video.duration)}`;
            };

            const formatTime = (seconds) => {
                const h = Math.floor(seconds / 3600);
                const m = Math.floor((seconds % 3600) / 60);
                const s = Math.floor(seconds % 60);
                if (h > 0) {
                    return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
                }
                return `${m}:${s.toString().padStart(2, '0')}`;
            };

            let isDragging = false;
            const handleProgressClick = (e) => {
                const rect = progress.getBoundingClientRect();
                const percent = (e.clientX - rect.left) / rect.width;
                video.currentTime = video.duration * percent;
                updateProgress();
            };

            progress.addEventListener('mousedown', (e) => {
                isDragging = true;
                handleProgressClick(e);
            });

            document.addEventListener('mousemove', (e) => {
                if (isDragging) {
                    handleProgressClick(e);
                }
            });

            document.addEventListener('mouseup', () => {
                isDragging = false;
            });

            let hideTimeout;
            container.addEventListener('mousemove', () => {
                progressContainer.style.opacity = '1';
                clearTimeout(hideTimeout);
                hideTimeout = setTimeout(() => {
                    if (!isDragging) {
                        progressContainer.style.opacity = '0';
                    }
                }, 2000);
            });

            container.addEventListener('mouseleave', () => {
                if (!isDragging) {
                    progressContainer.style.opacity = '0';
                }
            });

            video.addEventListener('timeupdate', updateProgress);

            container.appendChild(progressContainer);

            const cleanup = () => {
                video.removeEventListener('timeupdate', updateProgress);
                clearTimeout(hideTimeout);
            };
            this.progressCleanup = cleanup;
        }

        showSeekHint(video, text) {
            const existingHint = document.querySelector('.video-seek-hint');
            if (existingHint) {
                existingHint.remove();
            }

            const hint = document.createElement('div');
            hint.className = 'video-seek-hint';
            hint.textContent = text;
            hint.style.cssText = `
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background: rgba(0, 0, 0, 0.8);
                color: white;
                padding: 10px 20px;
                border-radius: 4px;
                font-size: 16px;
                pointer-events: none;
                z-index: 2147483648;
                animation: fadeOut 0.5s ease-out 0.5s forwards;
            `;

            const style = document.createElement('style');
            style.textContent = `
                @keyframes fadeOut {
                    from { opacity: 1; }
                    to { opacity: 0; }
                }
            `;
            document.head.appendChild(style);

            const container = document.querySelector('.video-fullpage-container');
            if (container) {
                container.appendChild(hint);
                setTimeout(() => hint.remove(), 1000);
            }
        }
    }

    new VideoFullpage();
})();

QingJ © 2025

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