您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Запоминает позицию просмотра видео и громкость, возобновляет с этого места (минус 5 секунд)
- // ==UserScript==
- // @name YouTube Enhanced Player
- // @name:en YouTube Enhanced Player
- // @name:es YouTube Reproductor Mejorado
- // @namespace http://tampermonkey.net/
- // @version 1.6.3.1
- // @description Запоминает позицию просмотра видео и громкость, возобновляет с этого места (минус 5 секунд)
- // @description:en Remembers video playback position and volume, resumes from that point (minus 5 seconds)
- // @description:es Recuerda la posición y volumen de reproducción, continúa desde ese punto (menos 5 segundos)
- // @author LegonYY
- // @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 saveVolumeLevel(volume) {
- localStorage.setItem('yt_volume_global', volume.toString());
- }
- function loadVolumeLevel() {
- const savedVolume = localStorage.getItem('yt_volume_global');
- return savedVolume ? parseFloat(savedVolume) : 100;
- }
- 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);
- video.addEventListener('loadedmetadata', () => {
- if (savedTime > 0 && video.duration > savedTime - 5) {
- const resumeTime = Math.max(0, savedTime - 5);
- video.currentTime = resumeTime;
- }
- });
- setInterval(() => {
- if (!video.paused && !video.seeking) {
- 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 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 = 'Сохранить текущее время просмотра';
- 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',
- });
- 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);
- const initialVolume = loadVolumeLevel();
- gainNode.gain.value = initialVolume / 100;
- customVolumeSlider.value = initialVolume.toString();
- updateVolumeDisplay(initialVolume.toString());
- customVolumeSlider.addEventListener('input', function () {
- const volume = calculateVolume(this.value, this.max);
- gainNode.gain.value = volume / 100;
- updateVolumeDisplay(volume);
- saveVolumeLevel(volume);
- });
- function resetVolumeTo100() {
- customVolumeSlider.value = '100';
- gainNode.gain.value = 1.0;
- updateVolumeDisplay('100');
- saveVolumeLevel(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.cursor = 'pointer';
- 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'));
- });
- }
- saveButton.addEventListener('click', () => {
- const videoId = getVideoId();
- if (videoId) {
- saveVideoTime(videoId, video.currentTime);
- showSaveNotification();
- }
- });
- }
- function init() {
- initResumePlayback();
- const video = document.querySelector('video');
- if (video) {
- createControlPanel(video);
- createSpeedControl();
- }
- }
- const checkVideo = setInterval(() => {
- if (document.querySelector('video') && document.querySelector('.ytp-chrome-controls')) {
- clearInterval(checkVideo);
- init();
- }
- }, 500);
- function createSpeedControl() {
- const style = document.createElement("style");
- style.textContent = `
- .ytp-speed-button {
- color: white;
- background: transparent;
- border: none;
- font-size: 14px;
- cursor: pointer;
- position: relative;
- align-self: center;
- margin-left: auto;
- margin-right: auto;
- transition: transform 0.2s ease;
- }
- .ytp-speed-menu {
- position: absolute;
- bottom: 30px;
- left: 0;
- background: #303031;
- color: white;
- border-radius: 5px;
- display: none;
- z-index: 9999;
- }
- .ytp-speed-option {
- padding: 5px 10px;
- cursor: pointer;
- font-size: 14px;
- text-align: center;
- }
- .ytp-speed-option:hover,
- .ytp-speed-option.active {
- background: Dodgerblue;
- color: #fff;
- }
- `;
- document.head.appendChild(style);
- const speeds = [0.5, 0.75, 1.0, 1.15, 1.25, 1.5, 2.0];
- let currentSpeed = parseFloat(localStorage.getItem('yt_speed') || 1.0);
- const controls = document.querySelector(".ytp-right-controls");
- if (!controls) return;
- const button = document.createElement("button");
- button.className = "ytp-speed-button";
- button.textContent = `${currentSpeed}×`;
- Object.assign(button.style, {
- color: '#fff',
- background: 'transparent',
- border: 'none',
- fontSize: '14px',
- cursor: 'pointer',
- position: 'relative',
- alignSelf: 'center',
- marginLeft: 'auto',
- marginRight: 'auto',
- transition: 'transform 0.2s ease',
- });
- const menu = document.createElement("div");
- menu.className = "ytp-speed-menu";
- speeds.forEach(speed => {
- const item = document.createElement("div");
- item.className = "ytp-speed-option";
- item.textContent = `${speed}×`;
- item.dataset.speed = speed;
- if (speed === currentSpeed) item.classList.add("active");
- menu.appendChild(item);
- });
- button.appendChild(menu);
- controls.prepend(button);
- button.addEventListener("click", () => {
- menu.style.display = menu.style.display === "block" ? "none" : "block";
- });
- menu.addEventListener("click", (e) => {
- if (e.target.classList.contains("ytp-speed-option")) {
- const newSpeed = parseFloat(e.target.dataset.speed);
- document.querySelector("video").playbackRate = newSpeed;
- currentSpeed = newSpeed;
- localStorage.setItem('yt_speed', newSpeed);
- button.firstChild.textContent = `${newSpeed}×`;
- menu.querySelectorAll(".ytp-speed-option").forEach(opt => opt.classList.remove("active"));
- e.target.classList.add("active");
- menu.style.display = "none";
- }
- });
- const video = document.querySelector("video");
- if (video) video.playbackRate = currentSpeed;
- }
- })();