自用video tool for pad

播放支持播放控制缩放、镜像等功能

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         自用video tool for pad
// @namespace    http://tampermonkey.net/
// @version      1
// @description  播放支持播放控制缩放、镜像等功能
// @author       i22333
// @match        *://*/*
// @grant        GM_addStyle
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // 添加自定义样式
    GM_addStyle(`
        #media-player-container {
            position: fixed;
            left: 10px;
            right: 10px;
            bottom: 10px;
            background-color: rgba(0, 0, 0, 0.8);
            z-index: 10000;
            display: none;
            border-radius: 10px;
            overflow: hidden;
            resize: both;
        }
        #player-video {
            width: 100%;
            height: 100%;
            object-fit: contain;
            transition: transform 0.3s ease;
            transform-origin: center center;
        }
        #player-video.zoomed {
            object-fit: cover;
            transform-origin: center center;
        }
        /* === 集成控制组样式 === */
        #control-group-container {
            position: absolute;
            bottom: 10px;
            left: 50%;
            transform: translateX(-50%);
            background-color: rgba(0, 0, 0, 0.5);
            color: white;
            border: none;
            border-radius: 5px;
            z-index: 10003;
            width: 420px;
            display: flex;
            flex-direction: column;
            overflow: hidden;
            font-size: 15px;
        }
        /* 进度条部分 */
        #progress-container {
            padding: 8px 15px 5px; /* 上8px 左右15px 下5px */
            width: 100%;
            box-sizing: border-box;
        }
        #progress-bar {
            position: relative;
            height: 5px;
            background-color: rgba(255, 255, 255, 0.2);
            border-radius: 0px;
            cursor: pointer;
            margin-bottom: 3px; /* 增加与时间显示的间距 */
        }
        #progress-played {
            position: absolute;
            height: 5px;
            background-color: #ff4d4d;
            border-radius: 0px;
            width: 0;
        }
        #progress-thumb {
            position: absolute;
            width: 12px;
            height: 12px;
            background-color: #ff4d4d;
            border-radius: 50%;
            top: -3px;
            left: 0;
            transform: translateX(-50%);
            cursor: pointer;
            z-index: 1;
        }
        #time-container {
            display: flex;
            justify-content: space-between;
            color: #ddd;
            font-size: 12px;
            padding: 5px 0 0px; /* 上3px 下5px */
        }
        #preview-time {
            position: absolute;
            bottom: 30px;
            background: rgba(0, 0, 0, 0.6);
            color: white;
            padding: 3px 8px;
            border-radius: 5px;
            font-size: 15px;
            display: none;
            z-index: 10005;
        }

        /* 播放按钮组 */
        #play-control-button {
            display: flex;
            justify-content: space-between;
            width: 100%;
            height: 30px;
            padding: 0px 0 5px; /* 上3px 下5px */
        }
        #play-control-button > div {
            width: 14%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: none !important;
            margin: 0 5px; /* 增加按钮间距 */
        }
        #play-control-button:active {
            transform: scale(0.95);
        }
        #control-button-group {
            position: absolute;
            top: 10px;
            left: 10px;
            background-color: rgba(0, 0, 0, 0.5);
            color: white;
            border: none;
            padding: 0;
            border-radius: 5px;
            cursor: pointer;
            font-size: 15px;
            z-index: 10003;
            display: flex;
            justify-content: space-between;
            width: 150px;
            height: 50px;
        }
        #control-button-group > div {
            width: 33%;
            height: 50px;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: none !important;
        }
        #control-button-group:active {
            transform: scale(0.95);
        }
        #speed-control-menu {
            position: absolute;
            bottom: 80px;
            left: 50%;
            transform: translateX(-50%);
            background-color: rgba(0, 0, 0, 0.5);
            border-radius: 5px;
            padding: 5px;
            display: none;
            flex-direction: column;
            z-index: 10004;
            width: 80px;
            max-height: 180px;
            overflow-y: auto;
        }
        .speed-option {
            color: white;
            padding: 5px 10px;
            cursor: pointer;
            font-size: 15px;
            display: flex;
            align-items: center;
            justify-content: center;
            height: 40px;
        }
        .controls-visible { opacity: 1; pointer-events: auto; }
        .controls-hidden { opacity: 0; pointer-events: none; }
        #detect-media-button {
            position: fixed;
            left: 20px;
            top: 70%;
            transform: translateY(-50%);
            z-index: 10001;
            background-color: #00aaff;
            color: white;
            border: none;
            padding: 10px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 15px;
            width: 40px;
            height: 40px;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s ease;
        }

        #detect-media-button:active {
            transform: translateY(-50%) scale(0.95);
        }
        /* 竖屏模式优化 */
        [aspect-ratio="9/16"] {
            transform: rotate(90deg) scale(1.75);
            transform-origin: center center;
        }
    `);

    // 创建播放器容器
    const mediaPlayer = document.createElement('div');
    mediaPlayer.id = 'media-player-container';
    document.body.appendChild(mediaPlayer);

    // 视频元素
    const playerVideo = document.createElement('video');
    playerVideo.id = 'player-video';
    playerVideo.controls = false;
    mediaPlayer.appendChild(playerVideo);

    // === 创建集成控制组 ===
    const controlGroupContainer = document.createElement('div');
    controlGroupContainer.id = 'control-group-container';
    mediaPlayer.appendChild(controlGroupContainer);

    // 进度条部分
    const progressContainer = document.createElement('div');
    progressContainer.id = 'progress-container';

    const progressBar = document.createElement('div');
    progressBar.id = 'progress-bar';

    const progressPlayed = document.createElement('div');
    progressPlayed.id = 'progress-played';

    const progressThumb = document.createElement('div');
    progressThumb.id = 'progress-thumb';

    const timeContainer = document.createElement('div');
    timeContainer.id = 'time-container';
    timeContainer.innerHTML = '<span id="current-time">00:00</span>  <span id="total-time">00:00</span>';

    const previewTime = document.createElement('div');
    previewTime.id = 'preview-time';

    progressBar.appendChild(progressPlayed);
    progressBar.appendChild(progressThumb);
    progressContainer.appendChild(progressBar);
    progressContainer.appendChild(timeContainer);
    progressContainer.appendChild(previewTime);

    controlGroupContainer.appendChild(progressContainer);

        // === 进度条功能实现 ===
    function formatTime(seconds) {
        const minutes = Math.floor(seconds / 60);
        const secs = Math.floor(seconds % 60);
        return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
    }

    function updateProgressBar() {
        if (!playerVideo.duration || playerVideo.duration === Infinity) return;

        const percent = (playerVideo.currentTime / playerVideo.duration) * 100;
        progressPlayed.style.width = `${percent}%`;
        progressThumb.style.left = `${percent}%`;

        document.getElementById('current-time').textContent = formatTime(playerVideo.currentTime);
        document.getElementById('total-time').textContent = formatTime(playerVideo.duration);
    }

    function seekToPosition(e) {
        if (!playerVideo.duration || playerVideo.duration === Infinity) return;

        const rect = progressBar.getBoundingClientRect();
        const position = (e.clientX - rect.left) / rect.width;
        const time = Math.max(0, Math.min(playerVideo.duration, playerVideo.duration * position));

        playerVideo.currentTime = time;
        updateProgressBar();
    }

    function showPreviewTime(e) {
        if (!playerVideo.duration || playerVideo.duration === Infinity) return;

        const rect = progressBar.getBoundingClientRect();
        const position = (e.clientX - rect.left) / rect.width;
        const time = Math.max(0, Math.min(playerVideo.duration, playerVideo.duration * position));

        previewTime.textContent = formatTime(time);
        previewTime.style.display = 'block';
        previewTime.style.left = `${e.clientX - rect.left + 10}px`;
    }

    function hidePreviewTime() {
        previewTime.style.display = 'none';
    }

    // 进度条事件监听
    playerVideo.addEventListener('timeupdate', updateProgressBar);
    progressBar.addEventListener('click', seekToPosition);

    let isDragging = false;
    progressThumb.addEventListener('mousedown', () => {
        isDragging = true;
        playerVideo.pause();
    });

    document.addEventListener('mousemove', (e) => {
        if (isDragging) {
            const rect = progressBar.getBoundingClientRect();
            const position = (e.clientX - rect.left) / rect.width;
            const percent = Math.max(0, Math.min(100, position * 100));

            progressPlayed.style.width = `${percent}%`;
            progressThumb.style.left = `${percent}%`;

            const time = Math.max(0, Math.min(playerVideo.duration, playerVideo.duration * position));
            document.getElementById('current-time').textContent = formatTime(time);

            previewTime.textContent = formatTime(time);
            previewTime.style.display = 'block';
            previewTime.style.left = `${e.clientX - rect.left + 10}px`;
        }
    });

    document.addEventListener('mouseup', (e) => {
        if (isDragging) {
            isDragging = false;
            const rect = progressBar.getBoundingClientRect();
            const position = (e.clientX - rect.left) / rect.width;
            playerVideo.currentTime = playerVideo.duration * position;
            playerVideo.play();
            previewTime.style.display = 'none';
        }
    });

    progressBar.addEventListener('mousemove', showPreviewTime);
    progressBar.addEventListener('mouseout', hidePreviewTime);

            // 移动端触摸事件支持
            let touchStartX = 0;
            let touchStartTime = 0;

            progressBar.addEventListener('touchstart', (e) => {
                if (e.touches.length > 0) {
                    const touch = e.touches[0];
                    touchStartX = touch.clientX;
                    touchStartTime = playerVideo.currentTime;
                    playerVideo.pause();
                    isDragging = true;
                    showPreviewTime(touch.clientX);
                    e.preventDefault();
                }
            });

            progressBar.addEventListener('touchmove', (e) => {
                if (isDragging && e.touches.length > 0) {
                    const touch = e.touches[0];
                    const rect = progressBar.getBoundingClientRect();
                    const position = (touch.clientX - rect.left) / rect.width;
                    const percent = Math.max(0, Math.min(100, position * 100));

                    progressPlayed.style.width = `${percent}%`;
                    progressThumb.style.left = `${percent}%`;

                    const time = Math.max(0, Math.min(playerVideo.duration, playerVideo.duration * position));
                    document.getElementById('current-time').textContent = formatTime(time);

                    previewTime.textContent = formatTime(time);
                    previewTime.style.display = 'block';
                    previewTime.style.left = `${touch.clientX - rect.left + 10}px`;
                    e.preventDefault();
                }
            });

            progressBar.addEventListener('touchend', (e) => {
                if (isDragging) {
                    isDragging = false;
                    if (e.changedTouches.length > 0) {
                        const touch = e.changedTouches[0];
                        const rect = progressBar.getBoundingClientRect();
                        const position = (touch.clientX - rect.left) / rect.width;
                        playerVideo.currentTime = playerVideo.duration * position;
                        playerVideo.play();
                        previewTime.style.display = 'none';
                    }
                    e.preventDefault();
                }
            });

    // 初始隐藏时间提示
    previewTime.style.display = 'none';

    // 播放按钮组
    const playControlButton = document.createElement('div');
    playControlButton.id = 'play-control-button';
    ['缩', '方', '10s', '播停', '10s', '速', '全'].forEach(text => {
        const btn = document.createElement('div');
        btn.innerText = text;
        playControlButton.appendChild(btn);
    });
    controlGroupContainer.appendChild(playControlButton);

    // 统一状态管理
    let videoState = {
        isMirrored: false,
        currentScale: 1,
        isZoomed: false,
        isSeeking: false,
        isPlaying: false,
        isDetectionVisible: true,
        screenOrientation: 'landscape',
    };

    // 状态变量
    let screenOrientation = 'landscape';
    let isFullscreen = false;
    let windowStates = [
        { width: '432px', height: '768px', desc: '默认尺寸' },
        { width: '768px', height: '432px', desc: '竖版尺寸' },
    ];
    let currentWindowState = 0;

    // 添加全屏状态管理
    let originalDimensions = {
        width: '',
        height: ''
    };

    // 统一更新变换
    function updateVideoTransform() {
        const transforms = [];
        if (videoState.screenOrientation === 'portrait') {
            transforms.push('rotate(90deg) scale(1.78)');
        }
        if (videoState.currentScale !== 1) {
            transforms.push(`scale(${videoState.currentScale})`);
        }
        if (videoState.isMirrored) {
            transforms.push('scaleX(-1)');
        }
        playerVideo.style.transform = transforms.join(' ') || 'none';
    }

    // 窗口尺寸监听
    new ResizeObserver(() => {
        if (videoState.isZoomed && !playerVideo.classList.contains('zoomed')) {
            videoState.currentScale = Math.max(
                mediaPlayer.clientWidth / playerVideo.videoWidth,
                mediaPlayer.clientHeight / playerVideo.videoHeight
            );
            updateVideoTransform();
        }
    }).observe(mediaPlayer);

    // 按钮功能绑定
    playControlButton.children[0].addEventListener('click', toggleScale);
    playControlButton.children[1].addEventListener('click', toggleOrientation);
    playControlButton.children[2].addEventListener('click', () => playerVideo.currentTime -= 10);
    playControlButton.children[3].addEventListener('click', togglePlay);
    playControlButton.children[4].addEventListener('click', () => playerVideo.currentTime += 10);
    playControlButton.children[5].addEventListener('click', toggleSpeedMenu);
    playControlButton.children[6].addEventListener('click', toggleFullscreen);

    // 缩放功能
    function toggleScale() {
        const containerWidth = mediaPlayer.clientWidth;
        const containerHeight = mediaPlayer.clientHeight;
        const videoWidth = playerVideo.videoWidth;
        const videoHeight = playerVideo.videoHeight;

        if (!videoState.isZoomed) {
            const containerRatio = containerWidth / containerHeight;
            const videoRatio = videoWidth / videoHeight;
            if (Math.abs(containerRatio - videoRatio) > 0.01) {
                playerVideo.classList.add('zoomed');
            } else {
                videoState.currentScale = Math.max(
                    containerWidth / videoWidth,
                    containerHeight / videoHeight
                );
                updateVideoTransform();
            }
        } else {
            playerVideo.classList.remove('zoomed');
            videoState.currentScale = 1;
            updateVideoTransform();
        }
        videoState.isZoomed = !videoState.isZoomed;
    }

    // 方向切换
    function toggleOrientation() {
        videoState.screenOrientation =
            videoState.screenOrientation === 'landscape' ? 'portrait' : 'landscape';
        mediaPlayer.style.aspectRatio =
            videoState.screenOrientation === 'portrait' ? '9/16' : '16/9';
        updateVideoTransform();
    }

    // 播放控制
    function togglePlay() {
        if (playerVideo.paused) {
            playerVideo.play();
            ControlsManager.scheduleHide();
        } else {
            playerVideo.pause();
            ControlsManager.cancelHide();
            ControlsManager.show();
        }
        updatePlayButton();
    }

    // 更新播放按钮文本
    function updatePlayButton() {
        playControlButton.children[3].innerText = playerVideo.paused ? '播放' : '暂停';
    }

    // 控件管理模块
    const ControlsManager = (() => {
        let hideTimer;
        const controls = [
            '#control-group-container',
            '#control-button-group'
        ];

        return {
            init() {
                mediaPlayer.addEventListener('mousemove', () => this.reset());
                mediaPlayer.addEventListener('mouseleave', () => this.scheduleHide());
                playerVideo.addEventListener('play', () => this.scheduleHide());
                playerVideo.addEventListener('pause', () => this.cancelHide());
            },

            show() {
                controls.forEach(selector => {
                    const el = document.querySelector(selector);
                    el?.classList.remove('controls-hidden');
                    el.style.pointerEvents = 'auto';
                });
            },

            hide() {
                if(playerVideo.paused) return;
                controls.forEach(selector => {
                    const el = document.querySelector(selector);
                    el?.classList.add('controls-hidden');
                    el.style.pointerEvents = 'none';
                });
            },

            scheduleHide(delay = 3000) {
                this.cancelHide();
                hideTimer = setTimeout(() => this.hide(), delay);
            },

            cancelHide() {
                clearTimeout(hideTimer);
            },

            reset() {
                this.show();
                this.scheduleHide();
            }
        };
    })();

    ControlsManager.init();

    // 速度控制菜单
    const speedOptions = [0.1, 0.25, 0.75, 1, 1.25, 1.5, 2, 2.5, 3, 4];

    const speedControlMenu = document.createElement('div');
    speedControlMenu.id = 'speed-control-menu';
    speedOptions.forEach(speed => {
        const option = document.createElement('div');
        option.className = 'speed-option';
        option.innerText = `${speed}x`;
        option.addEventListener('click', () => {
            playerVideo.playbackRate = speed;
            speedControlMenu.style.display = 'none';
        });
        speedControlMenu.appendChild(option);
    });
    mediaPlayer.appendChild(speedControlMenu);

    // 显示/隐藏速度菜单
    function toggleSpeedMenu() {
        speedControlMenu.style.display = speedControlMenu.style.display === 'flex' ? 'none' : 'flex';
        resetMenuTimeout();
    }

    // 增强全屏函数
    function toggleFullscreen() {
        if (!isFullscreen) {
            originalDimensions = {
                width: mediaPlayer.style.width,
                height: mediaPlayer.style.height,
                aspectRatio: mediaPlayer.style.aspectRatio
            };

            mediaPlayer.requestFullscreen().then(() => {
                isFullscreen = true;
                mediaPlayer.style.width = '100%';
                mediaPlayer.style.height = '100%';
                applyOrientation();
            });
        } else {
            document.exitFullscreen();
        }
    }

    // 增强全屏状态监听
    document.addEventListener('fullscreenchange', () => {
        isFullscreen = !!document.fullscreenElement;
        if (!isFullscreen) {
            mediaPlayer.style.width = originalDimensions.width;
            mediaPlayer.style.height = originalDimensions.height;
            mediaPlayer.style.aspectRatio = originalDimensions.aspectRatio;
            playerVideo.style.transform = 'none';
        }
    });

    // 更新全屏按钮状态
    function updateFullscreenButton() {
        const fullscreenBtn = playControlButton.children[6];
        fullscreenBtn.innerText = isFullscreen ? '退出' : '全';
        fullscreenBtn.title = isFullscreen ? '退出全屏 (Esc)' : '进入全屏';
    }

    // 视频状态同步
    playerVideo.addEventListener('play', () => {
        videoState.isPlaying = true;
        videoState.isDetectionVisible = false;
        detectMediaButton.style.display = 'none';
        updatePlayButton();
    });

    playerVideo.addEventListener('pause', () => {
        videoState.isPlaying = false;
        videoState.isDetectionVisible = true;
        detectMediaButton.style.display = 'block';
        updatePlayButton();
    });

    playerVideo.addEventListener('ended', () => {
        videoState.isPlaying = false;
        videoState.isDetectionVisible = true;
        detectMediaButton.style.display = 'block';
        updatePlayButton();
    });

    // 创建控制按钮组
    const controlButtonGroup = document.createElement('div');
    controlButtonGroup.id = 'control-button-group';
    ['关', '切', '镜'].forEach(text => {
        const btn = document.createElement('div');
        btn.innerText = text;
        controlButtonGroup.appendChild(btn);
    });
    mediaPlayer.appendChild(controlButtonGroup);

    // 按钮功能绑定
    controlButtonGroup.children[0].addEventListener('click', closePlayer);
    controlButtonGroup.children[1].addEventListener('click', toggleResize);
    controlButtonGroup.children[2].addEventListener('click', toggleMirror);

    // 关闭功能
    let originalVideo = null;
    function closePlayer() {
        if (document.fullscreenElement) {
            document.exitFullscreen();
        }

        if (playerVideo.src && originalVideo) {
            originalVideo.pause();
        }
        mediaPlayer.style.display = 'none';
        playerVideo.pause();
        playerVideo.src = '';
        detectMediaButton.style.display = 'block';

        document.documentElement.style.overflow = '';
    }

    // 切换窗口尺寸
    function toggleResize() {
        if (isFullscreen) {
            document.exitFullscreen().then(() => {
                currentWindowState = (currentWindowState + 1) % windowStates.length;
                applyWindowState();
            });
        } else {
            currentWindowState = (currentWindowState + 1) % windowStates.length;
            applyWindowState();
        }
    }

    // 应用窗口状态
    function applyWindowState() {
        const state = windowStates[currentWindowState];
        mediaPlayer.style.width = state.width;
        mediaPlayer.style.height = state.height;
        console.log(`切换到状态: ${state.desc}`);
    }

    // 镜像功能
    function toggleMirror() {
        videoState.isMirrored = !videoState.isMirrored;
        updateVideoTransform();
    }

    // 检测媒体功能
    const detectMediaButton = document.createElement('button');
    detectMediaButton.id = 'detect-media-button';
    detectMediaButton.innerText = '检';
    detectMediaButton.style.display = 'none';
    document.body.appendChild(detectMediaButton);

    const detectMedia = () => {
        const videos = document.querySelectorAll('video');
        let targetVideo = null;

        for (const video of videos) {
            if (video.src && (
                video.src.endsWith('.m3u8') ||
                video.querySelector('source[src$=".m3u8"]') !== null
            )) {
                targetVideo = video;
                break;
            }

            if (!video.paused && !targetVideo) {
                targetVideo = video;
            }
        }
        return targetVideo || videos[0] || null;
    };

    document.addEventListener('play', (e) => {
        if (e.target.tagName === 'VIDEO' && !e.target.isSameNode(playerVideo)) {
            videoState.isDetectionVisible = true;
            detectMediaButton.style.display = 'block';
        }
    }, true);

    detectMediaButton.addEventListener('click', () => {
        if (!videoState.isDetectionVisible) return;

        const media = detectMedia();
        if (media) {
            originalVideo = media;
            const source = media.src || media.querySelector('source')?.src;

            playerVideo.src = source;
            playerVideo.currentTime = media.currentTime;
            media.pause();
            mediaPlayer.style.display = 'block';
            ControlsManager.show();
            playerVideo.play();
            videoState.isDetectionVisible = false;
            detectMediaButton.style.display = 'none';
        }
    });

    // 视频结束自动隐藏
    playerVideo.addEventListener('ended', () => {
        playerVideo.src = '';
        detectMediaButton.style.display = 'block';
    });
})();