您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Сохраняет прогресс для каждого видео автоматически, добавляет управление скоростью воспроизведения, уведомления и окно настроек с возможностью настройки. Поддержка двух языков (русский и английский).
当前为
// ==UserScript== // @name Сохранение прогресса видео с настройками и управлением скорости воспроизведения // @name:en Video Progress Saver with Settings and playbackrate // @namespace http://tampermonkey.net/ // @version 2.8 // @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'; 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 notificationsContainer = document.createElement('div'); notificationsContainer.style.marginBottom = '20px'; const notificationsLabel = document.createElement('label'); notificationsLabel.innerText = langTexts[settings.language].notificationsLabel; notificationsLabel.style.display = 'block'; notificationsLabel.style.marginBottom = '10px'; notificationsLabel.style.fontSize = '16px'; notificationsContainer.appendChild(notificationsLabel); const notificationsCheckbox = document.createElement('input'); notificationsCheckbox.type = 'checkbox'; notificationsCheckbox.checked = settings.notificationsEnabled; notificationsCheckbox.style.marginRight = '10px'; notificationsCheckbox.addEventListener('change', () => { settings.notificationsEnabled = notificationsCheckbox.checked; localStorage.setItem('notificationsEnabled', settings.notificationsEnabled); }); notificationsContainer.appendChild(notificationsCheckbox); settingsWindow.appendChild(notificationsContainer); // Интервал авто-сохранения const intervalContainer = document.createElement('div'); intervalContainer.style.marginBottom = '20px'; const intervalLabel = document.createElement('label'); intervalLabel.innerText = langTexts[settings.language].autoSaveLabel; intervalLabel.style.display = 'block'; intervalLabel.style.marginBottom = '10px'; intervalLabel.style.fontSize = '16px'; intervalContainer.appendChild(intervalLabel); const intervalInput = document.createElement('input'); intervalInput.type = 'number'; intervalInput.value = settings.autoSaveInterval; intervalInput.style.width = '100%'; intervalInput.style.padding = '8px'; intervalInput.style.borderRadius = '5px'; intervalInput.style.fontSize = '16px'; intervalInput.addEventListener('input', () => { settings.autoSaveInterval = parseInt(intervalInput.value, 10); localStorage.setItem('autoSaveInterval', settings.autoSaveInterval); }); intervalContainer.appendChild(intervalInput); settingsWindow.appendChild(intervalContainer); // Максимальная скорость воспроизведения const maxSpeedContainer = document.createElement('div'); maxSpeedContainer.style.marginBottom = '20px'; const maxSpeedLabel = document.createElement('label'); maxSpeedLabel.innerText = langTexts[settings.language].maxSpeedLabel; maxSpeedLabel.style.display = 'block'; maxSpeedLabel.style.marginBottom = '10px'; maxSpeedLabel.style.fontSize = '16px'; maxSpeedContainer.appendChild(maxSpeedLabel); const maxSpeedInput = document.createElement('input'); maxSpeedInput.type = 'number'; maxSpeedInput.step = '0.1'; maxSpeedInput.value = settings.maxPlaybackRate; maxSpeedInput.style.width = '100%'; maxSpeedInput.style.padding = '8px'; maxSpeedInput.style.borderRadius = '5px'; maxSpeedInput.style.fontSize = '16px'; maxSpeedInput.addEventListener('input', () => { settings.maxPlaybackRate = parseFloat(maxSpeedInput.value); localStorage.setItem('maxPlaybackRate', settings.maxPlaybackRate); }); maxSpeedContainer.appendChild(maxSpeedInput); settingsWindow.appendChild(maxSpeedContainer); // Включение/выключение громкости const volumeControlContainer = document.createElement('div'); volumeControlContainer.style.marginBottom = '20px'; const volumeControlLabel = document.createElement('label'); volumeControlLabel.innerText = langTexts[settings.language].volumeControlLabel; volumeControlLabel.style.display = 'block'; volumeControlLabel.style.marginBottom = '10px'; volumeControlLabel.style.fontSize = '16px'; volumeControlContainer.appendChild(volumeControlLabel); const volumeControlCheckbox = document.createElement('input'); volumeControlCheckbox.type = 'checkbox'; volumeControlCheckbox.checked = settings.volumeControlEnabled; volumeControlCheckbox.style.marginRight = '10px'; volumeControlCheckbox.addEventListener('change', () => { settings.volumeControlEnabled = volumeControlCheckbox.checked; localStorage.setItem('volumeControlEnabled', settings.volumeControlEnabled); }); volumeControlContainer.appendChild(volumeControlCheckbox); settingsWindow.appendChild(volumeControlContainer); // Включение/выключение горячих клавиш const hotkeysContainer = document.createElement('div'); hotkeysContainer.style.marginBottom = '20px'; const hotkeysLabel = document.createElement('label'); hotkeysLabel.innerText = langTexts[settings.language].hotkeysLabel; hotkeysLabel.style.display = 'block'; hotkeysLabel.style.marginBottom = '10px'; hotkeysLabel.style.fontSize = '16px'; hotkeysContainer.appendChild(hotkeysLabel); const hotkeysCheckbox = document.createElement('input'); hotkeysCheckbox.type = 'checkbox'; hotkeysCheckbox.checked = settings.hotkeysEnabled; hotkeysCheckbox.style.marginRight = '10px'; hotkeysCheckbox.addEventListener('change', () => { settings.hotkeysEnabled = hotkeysCheckbox.checked; localStorage.setItem('hotkeysEnabled', settings.hotkeysEnabled); }); hotkeysContainer.appendChild(hotkeysCheckbox); settingsWindow.appendChild(hotkeysContainer); // Выбор языка const languageContainer = document.createElement('div'); languageContainer.style.marginBottom = '20px'; const languageLabel = document.createElement('label'); languageLabel.innerText = langTexts[settings.language].languageLabel; languageLabel.style.display = 'block'; languageLabel.style.marginBottom = '10px'; languageLabel.style.fontSize = '16px'; languageContainer.appendChild(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'; if (lang === settings.language) option.selected = true; languageSelect.appendChild(option); }); languageSelect.addEventListener('change', () => { settings.language = languageSelect.value; localStorage.setItem('language', settings.language); updateSettingsWindow(); // Обновить окно с новым языком // Перезагрузка страницы при смене языка location.reload(); // Перезагружает страницу }); languageContainer.appendChild(languageSelect); settingsWindow.appendChild(languageContainer); // Кнопка закрытия 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') { createSettingsWindow(); } }); // Обрабатываем каждое видео videos.forEach((video, index) => { const videoKey = `videoProgress_${window.location.href}_${index}`; const savedTime = localStorage.getItem(videoKey); const savedVolume = localStorage.getItem('videoVolume'); 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); } let saveProgress; const saveInterval = setInterval(() => { // Сохраняем прогресс только если видео воспроизводится if (!video.paused && !video.ended) { saveProgress(); } }, settings.autoSaveInterval); video.addEventListener('ended', () => { clearInterval(saveInterval); localStorage.removeItem(videoKey); }); video.addEventListener('volumechange', () => { localStorage.setItem('videoVolume', video.volume); }); window.addEventListener('beforeunload', () => { clearInterval(saveInterval); }); saveProgress = () => { 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)'); } } }); })();