Automatically saves progress for each video, adds playback speed control, notifications, and a settings window with customizable options. Supports two languages (Russian and English).
// ==UserScript==// @name Сохранение прогресса видео с настройками и управлением скорости воспроизведения// @name:en Saving Video Progress with Settings and Playback Speed Control// @namespace http://tampermonkey.net/// @version 2.9.1// @description Сохраняет прогресс для каждого видео автоматически, добавляет управление скоростью воспроизведения, уведомления и окно настроек с возможностью настройки. Поддержка двух языков (русский и английский).// @description:en Automatically saves progress for each video, adds playback speed control, notifications, and a settings window with customizable options. Supports two languages (Russian and English).// @author Egor Fox// @match *://*/*// @grant none// @license MIT// ==/UserScript==(function () {'use strict';const videos = document.querySelectorAll('video'); // Находим все видео на страницеif (!videos.length) return; // Если видео нет, завершаем выполнение скриптаlet currentNotification = null;let settingsWindowOpen = false; // Флаг для отслеживания открыто ли окно настроек// Загрузить настройки из localStorage или установить значения по умолчаниюlet settings = {notificationsEnabled: localStorage.getItem('notificationsEnabled') !== 'false',autoSaveInterval: parseInt(localStorage.getItem('autoSaveInterval'), 10) || 1000,maxPlaybackRate: parseFloat(localStorage.getItem('maxPlaybackRate')) || 3,hotkeysEnabled: localStorage.getItem('hotkeysEnabled') !== 'false',volumeControlEnabled: localStorage.getItem('volumeControlEnabled') !== 'false', // Добавляем настройку громкостиlanguage: localStorage.getItem('language') || 'ru' // Поддержка языка};// Тексты для уведомлений и интерфейсаconst langTexts = {ru: {settingsTitle: 'Настройки скрипта',notificationsLabel: 'Включить уведомления:',autoSaveLabel: 'Интервал авто-сохранения (мс):',maxSpeedLabel: 'Максимальная скорость (x):',volumeControlLabel: 'Включить изменение громкости при прокрутке:',hotkeysLabel: 'Включить горячие клавиши:',languageLabel: 'Выберите язык:',closeButton: 'Закрыть',progressRestored: 'Прогресс видео №{index} восстановлен!',speedChanged: 'Скорость видео изменена на {speed}x',volumeChanged: 'Громкость изменена на {volume}%',},en: {settingsTitle: 'Script Settings',notificationsLabel: 'Enable notifications:',autoSaveLabel: 'Auto-save interval (ms):',maxSpeedLabel: 'Max playback speed (x):',volumeControlLabel: 'Enable volume control on scroll:',hotkeysLabel: 'Enable hotkeys:',languageLabel: 'Choose language:',closeButton: 'Close',progressRestored: 'Video progress #{index} restored!',speedChanged: 'Playback speed changed to {speed}x',volumeChanged: 'Volume changed to {volume}%',}};// Функция для отображения уведомленийfunction showNotification(message, backgroundColor) {if (!settings.notificationsEnabled) return;if (currentNotification) {currentNotification.style.display = 'none';}const notification = document.createElement('div');notification.style.position = 'fixed';notification.style.top = '10%';notification.style.left = '50%';notification.style.transform = 'translate(-50%, -50%)';notification.style.backgroundColor = backgroundColor;notification.style.color = 'white';notification.style.padding = '10px 20px';notification.style.borderRadius = '8px';notification.style.fontSize = '16px';notification.style.zIndex = '9999';notification.innerText = message;document.body.appendChild(notification);setTimeout(() => {notification.style.display = 'none';}, 3000);currentNotification = notification;}// Функция для создания окна настроекfunction createSettingsWindow() {if (settingsWindowOpen) {closeSettingsWindow(); // Закрыть окно, если оно уже открытоreturn;}settingsWindowOpen = true; // Устанавливаем флаг, что окно открытоconst settingsOverlay = document.createElement('div');settingsOverlay.id = 'settingsOverlay';settingsOverlay.style.position = 'fixed';settingsOverlay.style.top = '0';settingsOverlay.style.left = '0';settingsOverlay.style.width = '100%';settingsOverlay.style.height = '100%';settingsOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';settingsOverlay.style.zIndex = '10000';settingsOverlay.style.display = 'flex';settingsOverlay.style.justifyContent = 'center';settingsOverlay.style.alignItems = 'center';const settingsWindow = document.createElement('div');settingsWindow.style.backgroundColor = '#333';settingsWindow.style.padding = '30px';settingsWindow.style.borderRadius = '10px';settingsWindow.style.width = '500px';settingsWindow.style.textAlign = 'left';settingsWindow.style.color = '#fff';settingsWindow.style.fontFamily = 'Arial, sans-serif';settingsWindow.style.fontSize = '18px'; // Увеличиваем шрифтconst title = document.createElement('h2');title.innerText = langTexts[settings.language].settingsTitle;title.style.textAlign = 'center';title.style.marginBottom = '30px';title.style.fontSize = '20px';settingsWindow.appendChild(title);// Настройки уведомленийconst notificationsLabel = document.createElement('label');notificationsLabel.innerText = langTexts[settings.language].notificationsLabel;const notificationsCheckbox = document.createElement('input');notificationsCheckbox.type = 'checkbox';notificationsCheckbox.checked = settings.notificationsEnabled;notificationsCheckbox.addEventListener('change', () => {settings.notificationsEnabled = notificationsCheckbox.checked;localStorage.setItem('notificationsEnabled', settings.notificationsEnabled);});settingsWindow.appendChild(notificationsLabel);settingsWindow.appendChild(notificationsCheckbox);settingsWindow.appendChild(document.createElement('br'));// Интервал авто-сохраненияconst autoSaveLabel = document.createElement('label');autoSaveLabel.innerText = langTexts[settings.language].autoSaveLabel;const autoSaveInput = document.createElement('input');autoSaveInput.type = 'number';autoSaveInput.value = settings.autoSaveInterval;autoSaveInput.addEventListener('change', () => {settings.autoSaveInterval = parseInt(autoSaveInput.value, 10);localStorage.setItem('autoSaveInterval', settings.autoSaveInterval);});settingsWindow.appendChild(autoSaveLabel);settingsWindow.appendChild(autoSaveInput);settingsWindow.appendChild(document.createElement('br'));// Максимальная скорость воспроизведенияconst maxSpeedLabel = document.createElement('label');maxSpeedLabel.innerText = langTexts[settings.language].maxSpeedLabel;const maxSpeedInput = document.createElement('input');maxSpeedInput.type = 'number';maxSpeedInput.step = '0.1';maxSpeedInput.value = settings.maxPlaybackRate;maxSpeedInput.addEventListener('change', () => {settings.maxPlaybackRate = parseFloat(maxSpeedInput.value);localStorage.setItem('maxPlaybackRate', settings.maxPlaybackRate);});settingsWindow.appendChild(maxSpeedLabel);settingsWindow.appendChild(maxSpeedInput);settingsWindow.appendChild(document.createElement('br'));// Включить изменение громкости при прокруткеconst volumeControlLabel = document.createElement('label');volumeControlLabel.innerText = langTexts[settings.language].volumeControlLabel;const volumeControlCheckbox = document.createElement('input');volumeControlCheckbox.type = 'checkbox';volumeControlCheckbox.checked = settings.volumeControlEnabled;volumeControlCheckbox.addEventListener('change', () => {settings.volumeControlEnabled = volumeControlCheckbox.checked;localStorage.setItem('volumeControlEnabled', settings.volumeControlEnabled);});settingsWindow.appendChild(volumeControlLabel);settingsWindow.appendChild(volumeControlCheckbox);settingsWindow.appendChild(document.createElement('br'));// Включить горячие клавишиconst hotkeysLabel = document.createElement('label');hotkeysLabel.innerText = langTexts[settings.language].hotkeysLabel;const hotkeysCheckbox = document.createElement('input');hotkeysCheckbox.type = 'checkbox';hotkeysCheckbox.checked = settings.hotkeysEnabled;hotkeysCheckbox.addEventListener('change', () => {settings.hotkeysEnabled = hotkeysCheckbox.checked;localStorage.setItem('hotkeysEnabled', settings.hotkeysEnabled);});settingsWindow.appendChild(hotkeysLabel);settingsWindow.appendChild(hotkeysCheckbox);settingsWindow.appendChild(document.createElement('br'));// Выбор языкаconst languageLabel = document.createElement('label');languageLabel.innerText = langTexts[settings.language].languageLabel;const languageSelect = document.createElement('select');const languages = ['ru', 'en'];languages.forEach(lang => {const option = document.createElement('option');option.value = lang;option.innerText = lang === 'ru' ? 'Русский' : 'English';languageSelect.appendChild(option);});languageSelect.value = settings.language;languageSelect.addEventListener('change', () => {settings.language = languageSelect.value;localStorage.setItem('language', settings.language);updateSettingsWindow();});settingsWindow.appendChild(languageLabel);settingsWindow.appendChild(languageSelect);settingsWindow.appendChild(document.createElement('br'));// Кнопка закрытияconst closeButton = document.createElement('button');closeButton.innerText = langTexts[settings.language].closeButton;closeButton.style.marginTop = '20px';closeButton.style.padding = '10px 20px';closeButton.style.border = 'none';closeButton.style.borderRadius = '5px';closeButton.style.backgroundColor = '#007BFF';closeButton.style.color = '#fff';closeButton.style.cursor = 'pointer';closeButton.style.fontSize = '16px';closeButton.addEventListener('click', closeSettingsWindow);settingsWindow.appendChild(closeButton);settingsOverlay.appendChild(settingsWindow);document.body.appendChild(settingsOverlay);}// Функция для обновления окна настроек при смене языкаfunction updateSettingsWindow() {const settingsOverlay = document.getElementById('settingsOverlay');if (settingsOverlay) {document.body.removeChild(settingsOverlay);}createSettingsWindow(); // Пересоздаем окно с новыми данными}// Функция для закрытия окна настроекfunction closeSettingsWindow() {const settingsOverlay = document.getElementById('settingsOverlay');if (settingsOverlay) {document.body.removeChild(settingsOverlay);settingsWindowOpen = false; // Сбрасываем флаг}}window.addEventListener('keydown', (event) => {if (event.code === 'KeyN' || event.code === 'KeyT') {createSettingsWindow();}});// Обрабатываем каждое видеоvideos.forEach((video, index) => {const videoKey = `videoProgress_${window.location.href}_${index}`;const savedTime = localStorage.getItem(videoKey);const savedVolume = localStorage.getItem(videoKey + "_volume");// Отложим восстановление прогресса на 1 секундыvideo.addEventListener('loadedmetadata', () => {setTimeout(() => {if (savedTime) {video.currentTime = parseFloat(savedTime);showNotification(langTexts[settings.language].progressRestored.replace("{index}", index + 1), 'rgba(0, 0, 0, 0.6)');}if (savedVolume !== null) {video.volume = parseFloat(savedVolume);}}, 1000); // Задержка 1 секундa});video.addEventListener('timeupdate', () => {if (!video.paused && !video.ended) {localStorage.setItem(videoKey, video.currentTime); // Сохраняем прогресс при каждом обновлении времени}});video.addEventListener('ended', () => {localStorage.removeItem(videoKey);});video.addEventListener('volumechange', () => {localStorage.setItem(videoKey + "_volume", video.volume);});window.addEventListener('beforeunload', () => {localStorage.setItem(videoKey, video.currentTime); // Обновляем прогресс перед закрытием страницы});});window.addEventListener('wheel', (event) => {if (!settings.volumeControlEnabled || !event.shiftKey) return;const focusedVideo = document.activeElement.tagName === 'VIDEO' ? document.activeElement : videos[0];if (!focusedVideo) return;const volumeChange = event.deltaY < 0 ? 0.01 : -0.01;let newVolume = Math.min(Math.max(focusedVideo.volume + volumeChange, 0), 1);// Обновляем громкость плеераfocusedVideo.volume = newVolume;// Показ уведомления при изменении громкостиshowNotification(langTexts[settings.language].volumeChanged.replace("{volume}", (newVolume * 100).toFixed(0)), 'rgba(0, 0, 0, 0.6)');});window.addEventListener('keydown', (event) => {if (!settings.hotkeysEnabled) return;const focusedVideo = document.activeElement.tagName === 'VIDEO' ? document.activeElement : videos[0];if (!focusedVideo) return;if (event.key === 'z' || event.key === 'я') {if (focusedVideo.playbackRate > 0.1) {focusedVideo.playbackRate -= 0.1;showNotification(langTexts[settings.language].speedChanged.replace("{speed}", focusedVideo.playbackRate.toFixed(1)), 'rgba(0, 0, 0, 0.6)');}} else if (event.key === 'x' || event.key === 'ч') {if (focusedVideo.playbackRate < settings.maxPlaybackRate) {focusedVideo.playbackRate += 0.1;showNotification(langTexts[settings.language].speedChanged.replace("{speed}", focusedVideo.playbackRate.toFixed(1)), 'rgba(0, 0, 0, 0.6)');}}});})();