YouTube Quick Speed & Volume Interface

Add a quick speed and volume interface to YouTube's middle-bottom area without interfering with existing controls.

Version au 17/12/2024. Voir la dernière version.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         YouTube Quick Speed & Volume Interface
// @name:zh-TW   YouTube 快速倍速與音量控制介面
// @name:zh-CN   YouTube 快速倍速与音量控制界面
// @namespace    https://twitter.com/CobleeH
// @version      1.16
// @description  Add a quick speed and volume interface to YouTube's middle-bottom area without interfering with existing controls.
// @description:zh-TW  在YouTube的中下部區域添加一個快速速度和音量界面,而不干擾現有控件。
// @description:zh-CN  在YouTube的中下部区域添加一个快速速度和音量界面,而不干扰现有控件。
// @author       CobleeH
// @match        https://www.youtube.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const speeds = [0.5, 1, 1.5, 2, 3];
    const volumes = [0, 0.15, 0.35, 0.65, 1];

    // 創建整體容器
    function createControlContainer() {
        const container = document.createElement('div');
        container.classList.add('ytp-control-container');
        container.style.display = 'flex';
        container.style.flexDirection = 'column';
        container.style.alignItems = 'center';
        container.style.position = 'absolute';
        container.style.right = '15px';
        container.style.bottom = '55px';
        container.style.zIndex = '9999';
        container.style.background = 'rgba(0, 0, 0, 0.7)';
        container.style.borderRadius = '5px';
        container.style.padding = '3px 6px'; // 縮小內邊距
        container.style.color = '#fff';
        container.style.fontSize = '12px'; // 字體縮小
        container.style.lineHeight = '1.2';

        const volumeOptions = createVolumeOptions();
        const speedOptions = createSpeedOptions();

        container.appendChild(volumeOptions);
        container.appendChild(speedOptions);
        return container;
    }

    // 創建音量控件
    function createVolumeOptions() {
        const volumeContainer = document.createElement('div');
        volumeContainer.classList.add('ytp-volume-options');
        volumeContainer.style.display = 'flex';
        volumeContainer.style.alignItems = 'center';
        volumeContainer.style.marginBottom = '4px'; // 縮小上下間距

        const label = document.createElement('span');
        label.innerText = 'Vol';
        label.style.marginRight = '6px';
        volumeContainer.appendChild(label);

        volumes.forEach(volume => {
            const option = document.createElement('div');
            option.innerText = (volume * 100) + '%';
            option.style.cursor = 'pointer';
            option.style.margin = '0 3px'; // 縮小選項間距
            option.style.padding = '2px 4px'; // 縮小內邊距

            option.addEventListener('click', () => {
                const video = document.querySelector('video');
                if (video) {
                    video.volume = volume;
                    highlightOption(option, '.ytp-volume-options div');
                }
            });

            volumeContainer.appendChild(option);
        });

        return volumeContainer;
    }

    // 創建速度控件
    function createSpeedOptions() {
        const speedContainer = document.createElement('div');
        speedContainer.classList.add('ytp-speed-options');
        speedContainer.style.display = 'flex';
        speedContainer.style.alignItems = 'center';

        const label = document.createElement('span');
        label.innerText = 'Spd';
        label.style.marginRight = '6px';
        speedContainer.appendChild(label);

        speeds.forEach(speed => {
            const option = document.createElement('div');
            option.innerText = speed + 'x';
            option.style.cursor = 'pointer';
            option.style.margin = '0 3px'; // 縮小選項間距
            option.style.padding = '2px 4px'; // 縮小內邊距

            option.addEventListener('click', () => {
                const video = document.querySelector('video');
                if (video) {
                    video.playbackRate = speed;
                    highlightOption(option, '.ytp-speed-options div');
                }
            });

            speedContainer.appendChild(option);
        });

        return speedContainer;
    }

    // 高亮所選選項
    function highlightOption(selectedOption, selector) {
        const options = document.querySelectorAll(selector);
        options.forEach(option => {
            option.style.color = '#fff';
            option.style.fontWeight = 'normal';
        });
        selectedOption.style.color = '#ff0';
        selectedOption.style.fontWeight = 'bold';
    }

    // 插入控件到播放器
    function insertControls() {
        const chromeBottom = document.querySelector('.ytp-chrome-bottom');
        if (!chromeBottom || document.querySelector('.ytp-control-container')) return;

        const controlContainer = createControlContainer();
        chromeBottom.appendChild(controlContainer);

        setInitialHighlight();
    }

    // 初始化高亮選項
    function setInitialHighlight() {
        const video = document.querySelector('video');
        if (!video) return;

        const currentSpeed = video.playbackRate || 1;
        const speedOptions = document.querySelectorAll('.ytp-speed-options div');
        speedOptions.forEach(option => {
            if (option.innerText === currentSpeed + 'x') {
                highlightOption(option, '.ytp-speed-options div');
            }
        });

        const currentVolume = video.volume || 1;
        const volumeOptions = document.querySelectorAll('.ytp-volume-options div');
        volumeOptions.forEach(option => {
            if (option.innerText === (currentVolume * 100) + '%') {
                highlightOption(option, '.ytp-volume-options div');
            }
        });
    }

    const observer = new MutationObserver(() => {
        insertControls();
    });

    observer.observe(document.body, { childList: true, subtree: true });
    window.addEventListener('load', insertControls);
})();