// ==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);
})();