Video Control with Reload

为指定视频添加可移动的进度条、音量控制器和重新加载按钮

Versión del día 29/6/2024. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Video Control with Reload
// @namespace    http://tampermonkey.net/
// @version      0.9
// @description  为指定视频添加可移动的进度条、音量控制器和重新加载按钮
// @match        https://app.kosmi.io/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    let videoElement = null;
    let controller = null;
    let isDragging = false; // 是否正在拖动控制器
    let initialX = 0, initialY = 0; // 初始点击位置
    let lastX = 0, lastY = 0; // 上一次位置

    // 创建控制器
    function createController() {
        controller = document.createElement('div');
        controller.id = 'video-controller';
        controller.style.cssText = `
            position: fixed;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            background-color: rgba(0, 0, 0, 0.7);
            color: white;
            padding: 10px;
            border-radius: 5px;
            z-index: 9999;
            cursor: move;
            user-select: none;
            width: 300px;
            transition: width 0.3s, height 0.3s;
            display: none; /* 初始隐藏 */
        `;
        controller.innerHTML = `
            <div id="progress-container" style="width: 100%; height: 10px; background-color: #444; position: relative; cursor: pointer;">
                <div id="progress-indicator" style="width: 0%; height: 100%; background-color: #fff; position: absolute;"></div>
            </div>
            <div id="time-display" style="text-align: center; margin-top: 5px;">0:00 / 0:00</div>
            <div id="volume-container" style="display: flex; align-items: center; margin-top: 10px;">
                <span id="volume-icon" style="margin-right: 10px;">🔊</span>
                <input type="range" id="volume-slider" min="0" max="1" step="0.1" value="1" style="flex-grow: 1;">
            </div>
            <button id="reload-button" style="width: 100%; margin-top: 10px; padding: 5px; background-color: #4CAF50; border: none; color: white; cursor: pointer;">重新加载视频控制</button>
        `;
        document.body.appendChild(controller);
        return controller;
    }

    // 创建显示/隐藏按钮
    function createToggleButton() {
        const button = document.createElement('button');
        button.textContent = '🎥'; // 修改按钮图标
        button.style.cssText = `
            position: fixed;
            left: 10px;
            top: 50%;
            transform: translateY(-50%);
            padding: 5px;
            font-size: 20px;
            background-color: rgba(0, 0, 0, 0.5);
            color: white;
            border: none;
            border-radius: 50%;
            cursor: move;
            z-index: 9999;
        `;
        document.body.appendChild(button);

        // 使按钮可拖动
        let pos1 = 0, pos2 = 0;

        button.addEventListener('touchstart', touchStart, false);

        function touchStart(e) {
            isDragging = true;
            initialX = e.touches[0].clientX;
            initialY = e.touches[0].clientY;
            lastX = initialX;
            lastY = initialY;
            document.addEventListener('touchmove', touchMove, false);
            document.addEventListener('touchend', touchEnd, false);
        }

        function touchMove(e) {
            if (!isDragging) return;
            e.preventDefault();
            let currentX = e.touches[0].clientX;
            let currentY = e.touches[0].clientY;

            pos1 = lastX - currentX;
            pos2 = lastY - currentY;
            lastX = currentX;
            lastY = currentY;

            button.style.top = (button.offsetTop - pos2) + "px";
            button.style.left = (button.offsetLeft - pos1) + "px";
        }

        function touchEnd() {
            isDragging = false;
            document.removeEventListener('touchmove', touchMove, false);
            document.removeEventListener('touchend', touchEnd, false);
        }

        button.addEventListener('click', function() {
            if (controller.style.display === 'none') {
                controller.style.display = 'block';
            } else {
                controller.style.display = 'none';
            }
        });
    }

    // 使控制器可拖动
    function makeDraggable(element) {
        let pos1 = 0, pos2 = 0;

        element.addEventListener('mousedown', dragMouseDown, false);

        function dragMouseDown(e) {
            if (e.target.id === 'progress-container' || e.target.id === 'progress-indicator' || e.target.id === 'volume-slider' || e.target.id === 'reload-button') return;
            e.preventDefault();
            isDragging = true;

            initialX = e.clientX;
            initialY = e.clientY;
            lastX = initialX;
            lastY = initialY;
            document.addEventListener('mouseup', closeDragElement, false);
            document.addEventListener('mousemove', throttle(elementDrag, 16), false); // 节流控制拖动频率
        }

        function elementDrag(e) {
            if (!isDragging) return;
            e.preventDefault();
            pos1 = initialX - e.clientX;
            pos2 = initialY - e.clientY;
            initialX = e.clientX;
            initialY = e.clientY;

            requestAnimationFrame(() => {
                element.style.top = (element.offsetTop - pos2) + "px";
                element.style.left = (element.offsetLeft - pos1) + "px";
                element.style.bottom = 'auto';
            });
        }

        function closeDragElement() {
            document.removeEventListener('mouseup', closeDragElement, false);
            document.removeEventListener('mousemove', throttle(elementDrag, 16), false);
            isDragging = false;
        }
    }

    // 节流函数,用于限制函数调用频率
    function throttle(func, limit) {
        let lastFunc;
        let lastRan;
        return function() {
            const context = this;
            const args = arguments;
            if (!lastRan) {
                func.apply(context, args);
                lastRan = Date.now();
            } else {
                clearTimeout(lastFunc);
                lastFunc = setTimeout(function() {
                    if ((Date.now() - lastRan) >= limit) {
                        func.apply(context, args);
                        lastRan = Date.now();
                    }
                }, limit - (Date.now() - lastRan));
            }
        };
    }

    // 格式化时间
    function formatTime(seconds) {
        const minutes = Math.floor(seconds / 60);
        seconds = Math.floor(seconds % 60);
        return `${minutes}:${seconds.toString().padStart(2, '0')}`;
    }

    // 主函数
    function main() {
        // 先移除已存在的控制器元素和按钮
        const existingController = document.getElementById('video-controller');
        const existingButton = document.getElementById('toggle-button');
        if (existingController) {
            existingController.remove();
        }
        if (existingButton) {
            existingButton.remove();
        }

        videoElement = document.querySelector('video[src^="blob:https://app.kosmi.io/"]');
        if (!videoElement) {
            console.log('未找到指定视频');
            return;
        }

        controller = createController();
        makeDraggable(controller);
        createToggleButton();

        const progressContainer = document.getElementById('progress-container');
        const progressIndicator = document.getElementById('progress-indicator');
        const timeDisplay = document.getElementById('time-display');
        const volumeSlider = document.getElementById('volume-slider');
        const volumeIcon = document.getElementById('volume-icon');
        const reloadButton = document.getElementById('reload-button');

        // 更新进度
        function updateProgress() {
            const progress = (videoElement.currentTime / videoElement.duration) * 100;
            progressIndicator.style.width = `${progress}%`;
            const current = formatTime(videoElement.currentTime);
            const total = formatTime(videoElement.duration);
            timeDisplay.textContent = `${current} / ${total}`;
        }

        // 点击进度条更新视频位置
        progressContainer.addEventListener('click', function(e) {
            if (isDragging) return;
            const rect = progressContainer.getBoundingClientRect();
            const pos = (e.clientX - rect.left) / rect.width;
            videoElement.currentTime = pos * videoElement.duration;
        });

        // 更新音量
        volumeSlider.addEventListener('input', function() {
            videoElement.volume = this.value;
            updateVolumeIcon(this.value);
        });

        // 更新音量图标
        function updateVolumeIcon(volume) {
            if (volume > 0.5) {
                volumeIcon.textContent = '🔊';
            } else if (volume > 0) {
                volumeIcon.textContent = '🔉';
            } else {
                volumeIcon.textContent = '🔇';
            }
        }

        // 初始化音量
        volumeSlider.value = videoElement.volume;
        updateVolumeIcon(videoElement.volume);

        // 重新加载按钮事件
        reloadButton.addEventListener('click', function() {
            // 移除旧的事件监听器
            videoElement.removeEventListener('timeupdate', updateProgress);
            videoElement.removeEventListener('loadedmetadata', updateProgress);

            // 重新执行主函数
            main();

            // 重新绑定事件监听器
            videoElement.addEventListener('timeupdate', updateProgress);
            videoElement.addEventListener('loadedmetadata', updateProgress);
        });

        videoElement.addEventListener('timeupdate', updateProgress);
        videoElement.addEventListener('loadedmetadata', updateProgress);
    }

    // 等待视频加载完成
    function waitForVideo() {
        const video = document.querySelector('video[src^="blob:https://app.kosmi.io/"]');
        if (video) {
            main();
        } else {
            setTimeout(waitForVideo, 1000);
        }
    }

    waitForVideo();
})();