Greasy Fork is available in English.

PrimeVideoControlsEnhancer

Enhance Amazon Prime Video by adding volume control via mouse wheel and Picture-in-Picture mode.

// ==UserScript==
// @name         PrimeVideoControlsEnhancer
// @name:ja      PrimeVideoControlsEnhancer
// @namespace    http://tampermonkey.net/
// @version      0.2.1
// @description  Enhance Amazon Prime Video by adding volume control via mouse wheel and Picture-in-Picture mode.
// @description:ja マウスホイールによる音量調節とピクチャー・イン・ピクチャー・モードを追加し、Amazonプライム・ビデオを強化。
// @author       You
// @match        https://www.amazon.co.jp/gp/video/detail/*
// @match        https://www.amazon.com/gp/video/detail/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const DEBUG_MODE = false;

    function debugLog(message) {
        if (DEBUG_MODE) {
            console.log(message);
        }
    }

    function createVolumeDisplay() {
        let volumeDisplay = document.getElementById('volume-display');

        if (!volumeDisplay) {
            volumeDisplay = document.createElement('div');
            volumeDisplay.id = 'volume-display';
            Object.assign(volumeDisplay.style, {
                position: 'fixed', bottom: '10px', right: '10px', fontSize: '20px', background: 'rgba(0, 0, 0, 0.7)',
                color: 'white', padding: '10px', borderRadius: '5px', zIndex: 10000, display: 'none', fontWeight: 'bold',
                textShadow: '2px 2px 4px #000000', boxShadow: '0 0 10px rgba(0, 0, 0, 0.5)', transition: 'opacity 0.3s'
            });
            document.body.appendChild(volumeDisplay);
            debugLog('Volume display initialized');
        }

        return volumeDisplay;
    }

    function updateVolumeDisplay(volumeDisplay, volume) {
        volumeDisplay.style.display = 'block';
        volumeDisplay.style.opacity = '1';
        volumeDisplay.textContent = 'Volume: ' + Math.round(volume * 100);
        debugLog('Volume display updated');

        if (volume <= 0.25) {
            volumeDisplay.style.color = '#0000FF';
        } else if (volume <= 0.5) {
            volumeDisplay.style.color = '#008000';
        } else if (volume <= 0.75) {
            volumeDisplay.style.color = '#FFFF00';
        } else {
            volumeDisplay.style.color = '#FF0000';
        }
    }

    function handleWheelEvent(volumeDisplay) {
        let volumeTimeoutId = 0;
        return function(e) {
            let video = document.querySelector('#dv-web-player video');
            debugLog('Video player found: ' + video);
            if (video) {
                let newVolume = video.volume + (e.deltaY < 0 ? 0.1 : -0.1);
                debugLog('New volume calculated: ' + newVolume);
                video.volume = Math.max(0, Math.min(1, newVolume));
                updateVolumeDisplay(volumeDisplay, video.volume);

                if (volumeTimeoutId) {
                    clearTimeout(volumeTimeoutId);
                }
                // Set new timer to hide volume display
                volumeTimeoutId = setTimeout(function() {
                    debugLog('Fading out volume display');
                    volumeDisplay.style.display = 'none';
                    volumeDisplay.style.opacity = '0';
                }, 3000);

                e.preventDefault();
            }
        };
    }

    function createPiPButton() {
        let pipButton = document.getElementById('pip-button');

        if (!pipButton) {
            pipButton = document.createElement('button');
            pipButton.id = 'pip-button';
            Object.assign(pipButton.style, {
                position: 'fixed', top: '20%', left: '50%', zIndex: '10000', padding: '10px', borderRadius: '5px',
                color: '#ffffff', border: 'none', cursor: 'pointer', display: 'none', backgroundImage: 'url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNHB4IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzAwMDAwMCI+CiAgICA8cGF0aCBkPSJNMTkgMTFoLTh2Nmg4di02em00IDhWNC45OEMyMyAzLjg4IDIyLjEgMyAyMSAzSDNjLTEuMSAwLTIgLjg4LTIgMS45OFYxOWMwIDEuMS45IDIgMiAyaDE4YzEuMSAwIDItLjkgMi0yem0tMiAuMDJIM1Y0Ljk3aDE4djE0LjA1eiIvPgogICAgPHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgyNHYyNEgwVjB6Ii8+Cjwvc3ZnPgo=)',
                backgroundSize: '20px', backgroundRepeat: 'no-repeat', backgroundPosition: 'center center', backgroundColor: 'rgba(223, 222, 219, 0.55)',
                transition: 'opacity 0.3s', width: '30px', height: '25px'
            });
            document.body.appendChild(pipButton);

            pipButton.addEventListener('click', function() {
                debugLog('PiP button clicked...');

                let video = document.querySelector('#dv-web-player video');
                if (video !== document.pictureInPictureElement) {
                    video.requestPictureInPicture();
                } else {
                    document.exitPictureInPicture();
                }
            });

            debugLog('PiP button created.');
        }

        return pipButton;
    }

    function handleMouseoverEvent(pipButton) {
        let pipTimeoutId = 0;
        return function(e) {
            pipButton.style.display = 'block';
            pipButton.style.opacity = '1';

            if (pipTimeoutId) {
                clearTimeout(pipTimeoutId);
            }
            pipTimeoutId = setTimeout(() => {
                debugLog('Fading out PiP button');
                pipButton.style.display = 'none';
                pipButton.style.opacity = '0';
            }, 1500);
        };
    }

    const volumeDisplay = createVolumeDisplay();
    const pipButton = createPiPButton();

    const observer = new MutationObserver(function(mutations, observer) {
        for(let mutation of mutations) {
            debugLog(mutation);
        }

        const video = document.querySelector('#dv-web-player');
        if (video) {
            debugLog("'#dv-web-player' has been loaded in the DOM.");
            video.addEventListener('wheel', handleWheelEvent(volumeDisplay), {passive: false});
            video.addEventListener('mouseover', handleMouseoverEvent(pipButton));
            observer.disconnect();
            debugLog('Disconnected the observer since the video element has been found.');
        }
    });

    debugLog('Starting to observe the whole document for addition of the video element.');
    observer.observe(document, {childList: true, subtree: true});
})();