您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
This script automatically tracks and restores your YouTube playback position. This user script remembers where you left off in any video—allowing you to seamlessly continue watching even after navigating away or reloading the page. It saves your progress for each video for up to 3 days (configurable) and automatically cleans up outdated entries, ensuring a smooth and uninterrupted viewing experience every time.
当前为
// ==UserScript== // @name YouTube Auto-Resume // @icon https://www.youtube.com/img/favicon_48.png // @author ElectroKnight22 // @namespace electroknight22_youtube_auto_resume_namespace // @version 1.0.0 // @match *://*.youtube.com/* // @match *://www.youtube-nocookie.com/* // @grant GM.getValue // @grant GM.setValue // @grant GM_getValue // @grant GM_setValue // @license MIT // @description This script automatically tracks and restores your YouTube playback position. This user script remembers where you left off in any video—allowing you to seamlessly continue watching even after navigating away or reloading the page. It saves your progress for each video for up to 3 days (configurable) and automatically cleans up outdated entries, ensuring a smooth and uninterrupted viewing experience every time. // ==/UserScript== /*jshint esversion: 11 */ (function () { "use strict"; let playbackStatuses = {}; let videoId; let useCompatibilityMode = false; // script will save playback time for 3 days by default, edit this value to change. const daysToRemember = 3; // Greasemonkey API Compatibility Layer const GMCustomGetValue = useCompatibilityMode ? GM_getValue : GM.getValue; const GMCustomSetValue = useCompatibilityMode ? GM_setValue : GM.setValue; function updateVideoStatus(event) { try { videoId = event.detail?.getVideoData().video_id; const videoElement = event.target?.querySelector('video'); const lastPlaybackTime = playbackStatuses[videoId]?.timestamp; if (!isNaN(lastPlaybackTime)) event.detail?.seekTo(lastPlaybackTime); if (videoElement) videoElement.addEventListener('timeupdate', () => { updatePlaybackStatus(videoElement); }, true); } catch (error) { throw ("Failed to update video status due to this error. Error: " + error); } } function updatePlaybackStatus(videoElement) { if (!videoId) return; try { const currentPlaybackTime = videoElement.currentTime; if (currentPlaybackTime && currentPlaybackTime !== 0) { const currentPlaybackStatus = { timestamp: currentPlaybackTime, lastUpdated: Date.now() }; playbackStatuses[videoId] = currentPlaybackStatus; GMCustomSetValue('playbackStatuses', playbackStatuses); } } catch (error) { throw ("Failed to update playback status due to this error. Error: " + error); } } function cleanUpStoredPlaybackStatuses() { let hasUpdated = false; for (const [key, value] of Object.entries(playbackStatuses)) { if (Date.now() - value.lastUpdated > daysToRemember * 24 * 60 * 60 * 1000) { delete playbackStatuses[key]; hasUpdated = true; } } if (hasUpdated) GMCustomSetValue('playbackStatuses', playbackStatuses); } async function loadStoredPlaybackStatuses() { try { playbackStatuses = await GMCustomGetValue('playbackStatuses', {}); } catch (error) { throw `Error loading stored playback statuses: ${error}. Aborting script.`; } } function hasGreasyMonkeyAPI() { if (typeof GM !== 'undefined') return true; if (typeof GM_info !== 'undefined') { useCompatibilityMode = true; console.warn("Running in compatibility mode."); return true; } return false; } async function initialize() { try { if (!hasGreasyMonkeyAPI()) throw "Did not detect valid Grease Monkey API"; await loadStoredPlaybackStatuses(); window.addEventListener('yt-player-updated', (event) => { cleanUpStoredPlaybackStatuses(); updateVideoStatus(event); }, true); } catch (error) { console.error(`Error when initializing script: ${error}. Aborting script.`); } } initialize(); })();