YouTube Quick Speed & Volume Interface

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

Bạn sẽ cần cài đặt một tiện ích mở rộng như Tampermonkey hoặc Violentmonkey để cài đặt kịch bản này.

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

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

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

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         YouTube Quick Speed & Volume Interface
// @name:zh-TW   YouTube 快速倍速與音量控制介面
// @name:zh-CN   YouTube 快速倍速与音量控制界面
// @namespace    https://twitter.com/CobleeH
// @version      1.17
// @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 = '62px';
        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);
})();