YouTube Enhanced Player

Запоминает позицию просмотра видео и возобновляет с этого места (минус 5 секунд)

当前为 2025-05-30 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         YouTube Enhanced Player
// @name:en      YouTube Enhanced Player
// @name:es      YouTube Reproductor Mejorado
// @namespace    http://tampermonkey.net/
// @version      1.6.2
// @description  Запоминает позицию просмотра видео и возобновляет с этого места (минус 5 секунд)
// @description:en Remembers video playback position and resumes from that point (minus 5 seconds)
// @description:es Recuerda la posición de reproducción y continúa desde ese punto (menos 5 segundos)
// @author       YourName
// @match        https://www.youtube.com/*
// @grant        none
// @icon         https://img.icons8.com/?size=100&id=55200&format=png&color=000000
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    function getVideoId() {
        const urlParams = new URLSearchParams(window.location.search);
        return urlParams.get('v');
    }

    function saveVideoTime(videoId, currentTime) {
        localStorage.setItem(`yt_time_${videoId}`, currentTime.toString());
    }

    function loadVideoTime(videoId) {
        const savedTime = localStorage.getItem(`yt_time_${videoId}`);
        return savedTime ? parseFloat(savedTime) : 0;
    }

    function showSaveNotification() {
        const overlay = document.querySelector('.html5-video-player .ytp-player-content')
                     || document.querySelector('.ytp-chrome-top')
                     || document.body;

        if (getComputedStyle(overlay).position === 'static') {
            overlay.style.position = 'relative';
        }

        const old = overlay.querySelector('.timeSaveNotification');
        if (old) old.remove();

        const notif = document.createElement('div');
        notif.className = 'timeSaveNotification';
        Object.assign(notif.style, {
            position: 'absolute',
            bottom: '0px',
            right: '5px',
            background: 'rgba(0,0,0,0.7)',
            color: '#fff',
            padding: '5px 10px',
            borderRadius: '5px',
            zIndex: '9999',
            fontSize: '14px',
            opacity: '0',
            transition: 'opacity 0.5s ease',
        });
        notif.innerText = 'Время просмотра сохранено!';
        overlay.appendChild(notif);

        requestAnimationFrame(() => notif.style.opacity = '1');
        setTimeout(() => {
            notif.style.opacity = '0';
            setTimeout(() => notif.remove(), 500);
        }, 3000);
    }

    function initResumePlayback() {
        const video = document.querySelector('video');
        if (!video) return;

        const videoId = getVideoId();
        if (!videoId) return;

        const savedTime = loadVideoTime(videoId);
        if (savedTime > 0) {
            const resumeTime = Math.max(0, savedTime - 5);
            video.currentTime = resumeTime;
        }

        setInterval(() => {
            if (!video.paused) {
                const videoId = getVideoId();
                if (videoId) {
                    saveVideoTime(videoId, video.currentTime);
                }
            }
        }, 5000);

        window.addEventListener('beforeunload', () => {
            const videoId = getVideoId();
            if (videoId) {
                saveVideoTime(videoId, video.currentTime);
            }
        });
    }

    function calculateVolume(position, sliderMax) {
        const volume = (position / sliderMax) * 1400;
        return volume.toFixed();
    }

    function updateVolumeDisplay(volume) {
        const old = document.getElementById('customVolumeDisplay');
        if (old) old.remove();

        const btn = document.getElementById('volumeBoostButton');
        if (!btn) return;

        const volumeDisplay = document.createElement('div');
        volumeDisplay.id = 'customVolumeDisplay';
        volumeDisplay.innerText = `${volume}%`;

        Object.assign(volumeDisplay.style, {
            position: 'absolute',
            fontSize: '14px',
            background: 'rgba(0,0,0,0.8)',
            color: '#fff',
            borderRadius: '5px',
            whiteSpace: 'nowrap',
            padding: '2px 6px',
            pointerEvents: 'none',
            transition: 'opacity 0.3s ease, transform 0.3s ease',
            opacity: '0',
            transform: 'translate(-50%, -10px)',
        });

        const btnContainer = btn.parentElement;
        btnContainer.style.position = 'relative';
        btnContainer.appendChild(volumeDisplay);

        const btnRect = btn.getBoundingClientRect();
        const containerRect = btnContainer.getBoundingClientRect();
        const offsetX = btnRect.left - containerRect.left + btnRect.width / 2;
        const offsetY = btnRect.top - containerRect.top;

        volumeDisplay.style.left = `${offsetX}px`;
        volumeDisplay.style.top = `${offsetY}px`;

        requestAnimationFrame(() => {
            volumeDisplay.style.opacity = '1';
            volumeDisplay.style.transform = 'translate(-50%, -20px)';
        });

        setTimeout(() => {
            volumeDisplay.style.opacity = '0';
            volumeDisplay.style.transform = 'translate(-50%, -10px)';
            setTimeout(() => volumeDisplay.remove(), 300);
        }, 1000);
    }

    function createControlPanel(video) {
        const videoId = getVideoId();
        if (!videoId) return;

        const style = document.createElement('style');
        style.textContent = `
        #volumeBoostButton input[type=range] {
            -webkit-appearance: none;
            width: 100px;
            height: 4px;
            background: #ccc;
            border-radius: 2px;
            outline: none;
        }
        #volumeBoostButton input[type=range]::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: #fff;
            cursor: pointer;
            box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
        }`;
        document.head.appendChild(style);

        const saveButton = document.createElement('button');
        saveButton.id = 'manualSaveButton';
        saveButton.innerText = '💾';
        Object.assign(saveButton.style, {
            background: 'none',
            border: 'none',
            cursor: 'pointer',
            color: '#fff',
            fontWeight: 'bold',
            marginRight: '1px',
            fontSize: '18px',
            transition: 'transform 0.2s ease',
        });
        saveButton.title = 'Сохранить текущее время просмотра';

        saveButton.addEventListener('click', () => {
            saveVideoTime(videoId, video.currentTime);
            showSaveNotification();
        });

        const volumeBoostButton = document.createElement('button');
        volumeBoostButton.id = 'volumeBoostButton';
        volumeBoostButton.innerText = '🔊';
        Object.assign(volumeBoostButton.style, {
            background: 'none',
            border: 'none',
            cursor: 'pointer',
            color: '#fff',
            fontWeight: 'bold',
            marginRight: '1px',
            fontSize: '18px',
            transition: 'transform 0.2s ease',
        });
        volumeBoostButton.title = 'Усилитель громкости';

        const customVolumeSlider = document.createElement('input');
        Object.assign(customVolumeSlider, {
            type: 'range',
            min: '100',
            max: '1400',
            step: '1',
            value: '100',
        });
        Object.assign(customVolumeSlider.style, {
            display: 'none',
            opacity: '0',
            transform: 'scale(0.8)',
            transition: 'opacity 0.3s ease, transform 0.3s ease',
        });

        const audioContext = new (window.AudioContext || window.webkitAudioContext)();
        const gainNode = audioContext.createGain();
        gainNode.connect(audioContext.destination);
        const videoSource = audioContext.createMediaElementSource(video);
        videoSource.connect(gainNode);
        gainNode.gain.value = 1.0;

        customVolumeSlider.addEventListener('input', function() {
            const volume = calculateVolume(this.value, this.max);
            gainNode.gain.value = volume / 100;
            updateVolumeDisplay(volume);
        });

        function resetVolumeTo100() {
            customVolumeSlider.value = '100';
            gainNode.gain.value = 1.0;
            updateVolumeDisplay('100');
        }

        volumeBoostButton.addEventListener('mouseenter', () => {
            customVolumeSlider.style.display = 'block';
            requestAnimationFrame(() => {
                customVolumeSlider.style.opacity = '1';
                customVolumeSlider.style.transform = 'scale(1)';
            });
        });

        volumeBoostButton.addEventListener('click', (e) => {
            e.stopPropagation();
            resetVolumeTo100();
        });

        let hideTimeout;


        const sliderContainer = document.createElement('div');
        sliderContainer.style.display = 'flex';
        sliderContainer.style.alignItems = 'center';
        sliderContainer.style.position = 'relative';
        sliderContainer.style.padding = '0px';
        sliderContainer.style.marginLeft = '0px';
sliderContainer.style.borderRadius = '0px';

sliderContainer.style.cursor = 'pointer';

        sliderContainer.style.borderRadius = '1px';


        sliderContainer.addEventListener('mouseleave', () => {
            hideTimeout = setTimeout(() => {
                customVolumeSlider.style.opacity = '0';
                customVolumeSlider.style.transform = 'scale(0.8)';
                setTimeout(() => {
                    customVolumeSlider.style.display = 'none';
                }, 300);
            }, 300);
        });

        sliderContainer.addEventListener('mouseenter', () => {
            clearTimeout(hideTimeout);
        });

        const controls = document.querySelector('.ytp-chrome-controls');
        if (controls) {
            const buttonContainer = document.createElement('div');
            buttonContainer.style.display = 'flex';
            buttonContainer.style.alignItems = 'center';
            buttonContainer.style.marginRight = '10px';

            sliderContainer.appendChild(volumeBoostButton);
            sliderContainer.appendChild(customVolumeSlider);

            buttonContainer.appendChild(saveButton);
            buttonContainer.appendChild(sliderContainer);

            controls.insertBefore(buttonContainer, controls.firstChild);

            sliderContainer.addEventListener('wheel', (e) => {
                e.preventDefault();
                const step = 50;
                let val = parseInt(customVolumeSlider.value, 10);
                if (e.deltaY < 0) {
                    val = Math.min(val + step, parseInt(customVolumeSlider.max, 10));
                } else {
                    val = Math.max(val - step, parseInt(customVolumeSlider.min, 10));
                }
                customVolumeSlider.value = val;
                customVolumeSlider.dispatchEvent(new Event('input'));
            });
        }

        resetVolumeTo100();
    }

    function init() {
        initResumePlayback();
        const video = document.querySelector('video');
        if (video) createControlPanel(video);
    }

    const checkVideo = setInterval(() => {
        if (document.querySelector('video') && document.querySelector('.ytp-chrome-controls')) {
            clearInterval(checkVideo);
            init();
        }
    }, 500);
})();