YouTube Enhanced Player

Remembers video playback position and resumes from that point (minus 5 seconds)

// ==UserScript==
// @name         YouTube Enhanced Player
// @name:en      YouTube Enhanced Player
// @name:es      YouTube Reproductor Mejorado
// @namespace    http://tampermonkey.net/
// @version      1.6
// @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
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // ==================== Часть 1: Возобновление воспроизведения ====================
    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() {
    // 1) Находим актуальный контейнер оверлея плеера
    const overlay = document.querySelector('.html5-video-player .ytp-player-content')
                 || document.querySelector('.ytp-chrome-top')
                 || document.body;

    // 2) Делаем его position: relative (если ещё не установлен)
    if (getComputedStyle(overlay).position === 'static') {
        overlay.style.position = 'relative';
    }

    // 3) Удаляем старое уведомление (если есть)
    const old = overlay.querySelector('.timeSaveNotification');
    if (old) old.remove();

    // 4) Создаём новое уведомление
    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 5px',
        borderRadius: '5px',
        zIndex:  '9999',
        fontSize: '14px',
        transition: 'opacity 0.3s ease',
        opacity: '0',
    });
    notif.innerText = 'Время просмотра сохранено!';

    overlay.appendChild(notif);

    // 5) Запустить анимацию «появления»
    //    (нужен небольшой таймаут, чтобы браузер успел «нарисовать» с opacity=0)
    setTimeout(() => notif.style.opacity = '1', 10);

    // 6) И плавно убрать через 3 секунды
    setTimeout(() => {
        notif.style.opacity = '0';
        setTimeout(() => {
            if (notif.parentNode) notif.remove();
        }, 300);
    }, 3000);
}



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

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

    // Загружаем сохраненное время
    const savedTime = loadVideoTime(videoId);
    if (savedTime > 0) {
        // Устанавливаем время на 5 секунд раньше сохраненного
        const resumeTime = Math.max(0, savedTime - 5);
        video.currentTime = resumeTime;
    }

    // Сохраняем время каждые 5 секунд (без уведомления)
    setInterval(() => {
        if (!video.paused) {
            const videoId = getVideoId();
            if (videoId) {
                localStorage.setItem(`yt_time_${videoId}`, video.currentTime.toString());
            }
        }
    }, 5000);

    // Сохраняем время при закрытии страницы (без уведомления)
    window.addEventListener('beforeunload', () => {
        const videoId = getVideoId();
        if (videoId) {
            localStorage.setItem(`yt_time_${videoId}`, video.currentTime.toString());
        }
    });
}

    // ==================== Часть 2: Усилитель громкости ====================
    function calculateVolume(position, sliderWidth) {
        const volume = (position / sliderWidth) * 1400;
        return volume.toFixed(3);
    }

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',
        padding: '0px 0px',
        fontSize: '14px',
        background: 'rgba(0,0,0,0.8)',
        color: '#fff',
        borderRadius: '5px',
        whiteSpace: 'nowrap',
        pointerEvents: 'none',
        transition: 'opacity 0.2s ease',
        opacity: '0'
    });

    // Вставляем в контейнер кнопки
    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`;
    volumeDisplay.style.transform = 'translate(-50%, -100%)';

    // Плавно показываем
    requestAnimationFrame(() => {
        volumeDisplay.style.opacity = '1';
    });

    // Убираем через секунду
    setTimeout(() => {
        volumeDisplay.style.opacity = '0';
        setTimeout(() => volumeDisplay.remove(), 200);
    }, 1000);
}



    // ==================== Часть 3: Создание панели управления ====================
    function createControlPanel(video) {
        const videoId = getVideoId();
        if (!videoId) return;

        // Кнопка сохранения времени
        const saveButton = document.createElement('button');
        saveButton.id = 'manualSaveButton';
        saveButton.style.background = 'none';
        saveButton.style.border = 'none';
        saveButton.style.cursor = 'pointer';
        saveButton.style.marginRight = '5px';
        saveButton.innerText = '💾';
        saveButton.style.color = '#fff';
        saveButton.style.fontWeight = 'bold';
        saveButton.title = 'Сохранить текущее время просмотра';

        // В коде кнопки сохранения нужно вызвать эту функцию:
saveButton.addEventListener('click', function() {
    saveVideoTime(videoId, video.currentTime);
    showSaveNotification(); // Показываем уведомление только при нажатии
});

        // Кнопка усилителя громкости
        const volumeBoostButton = document.createElement('button');
        volumeBoostButton.id = 'volumeBoostButton';
        volumeBoostButton.style.background = 'none';
        volumeBoostButton.style.border = 'none';
        volumeBoostButton.style.cursor = 'pointer';
        volumeBoostButton.style.marginRight = '5px';
        volumeBoostButton.innerText = '🔊';
        volumeBoostButton.style.color = '#fff';
        volumeBoostButton.style.fontWeight = 'bold';
        volumeBoostButton.title = 'Усилитель громкости';

        // Ползунок громкости
        const customVolumeSlider = document.createElement('input');
        customVolumeSlider.type = 'range';
        customVolumeSlider.min = '0';
        customVolumeSlider.max = '1400';
        customVolumeSlider.step = '1';
        customVolumeSlider.style.width = '120px';
        customVolumeSlider.style.display = 'none';

        // Настройка AudioContext
        const audioContext = new (window.AudioContext || window.webkitAudioContext)();
        const gainNode = audioContext.createGain();
        gainNode.connect(audioContext.destination);

        const videoSource = audioContext.createMediaElementSource(video);
        videoSource.connect(gainNode);

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

        function resetCustomVolumeSlider() {
            customVolumeSlider.value = '100';
            const initialVolume = calculateVolume(100, customVolumeSlider.max);
            gainNode.gain.value = initialVolume / 100;
            updateVolumeDisplay(initialVolume);
        }

        function toggleCustomVolumeSlider() {
            const isSliderHidden = customVolumeSlider.style.display === 'none';
            customVolumeSlider.style.display = isSliderHidden ? 'block' : 'none';
        }

        volumeBoostButton.addEventListener('click', function() {
            toggleCustomVolumeSlider();
            resetCustomVolumeSlider();
        });

        // Вставка кнопок в один контейнер слева
        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';

            buttonContainer.appendChild(saveButton);
            buttonContainer.appendChild(volumeBoostButton);
            buttonContainer.appendChild(customVolumeSlider);
            controls.insertBefore(buttonContainer, controls.firstChild);
            buttonContainer.addEventListener('wheel', function(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'));
            });
        }

        resetCustomVolumeSlider();
    }

    // ==================== Основная инициализация ====================
    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);
})();