// ==UserScript== // @name YouTube Auto-Resume // @icon https://www.youtube.com/img/favicon_48.png // @author ElectroKnight22 // @namespace electroknight22_youtube_auto_resume_namespace // @version 1.2.1 // @match *://*.youtube.com/* // @match *://www.youtube-nocookie.com/* // @grant GM.getValue // @grant GM.setValue // @grant GM.deleteValue // @grant GM.listValues // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_listValues // @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. // @downloadURL none // ==/UserScript== /*jshint esversion: 11 */ (function () { "use strict"; let moviePlayer; let videoElement; let videoId; let useCompatibilityMode = false; // script will save playback time for 3 days by default, edit this value to change. This will be overwritten if the script is updated. const daysToRemember = 3; // Greasemonkey API Compatibility Layer const GMCustomGetValue = useCompatibilityMode ? GM_getValue : GM.getValue; const GMCustomSetValue = useCompatibilityMode ? GM_setValue : GM.setValue; const GMCustomDeleteValue = useCompatibilityMode ? GM_deleteValue : GM.deleteValue; const GMCustomListValues = useCompatibilityMode ? GM_listValues : GM.listValues; async function resumePlayback() { try { const playbackStatus = await GMCustomGetValue(videoId); if (!playbackStatus) return; const lastPlaybackTime = playbackStatus.timestamp; if (!isNaN(lastPlaybackTime) && lastPlaybackTime !== 0) { moviePlayer?.seekTo(lastPlaybackTime); } } catch (error) { throw ("Failed to resume playback due to this error. Error: " + error); } } async function handleVideoLoad(event) { moviePlayer = event.detail; videoElement = event.target?.querySelector('video'); videoId = event.detail?.getVideoData().video_id; const isLive = moviePlayer.getVideoData().isLive; let resumed = false; videoElement?.addEventListener('timeupdate', async () => { if (moviePlayer.getPlayerState() === 5) return if (!resumed && !isLive) { resumed = true; await resumePlayback(); } updatePlaybackStatus(); }, true); } function updatePlaybackStatus() { if (!videoId) return; try { const currentPlaybackTime = videoElement.currentTime; if (currentPlaybackTime && currentPlaybackTime !== 0) { const currentPlaybackStatus = { timestamp: currentPlaybackTime, lastUpdated: Date.now() }; GMCustomSetValue(videoId, currentPlaybackStatus); console.log(`[YouTube Auto-Resume] Updated playback status for video ${videoId}: ${currentPlaybackTime}`); } } catch (error) { throw ("Failed to update playback status due to this error. Error: " + error); } } async function cleanUpStoredPlaybackStatuses() { const keys = await GMCustomListValues(); for (const key of keys) { const storedStatus = await GMCustomGetValue(key); if (storedStatus && Date.now() - storedStatus.lastUpdated > daysToRemember * 24 * 60 * 60 * 1000) { await GMCustomDeleteValue(key); } } } 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 cleanUpStoredPlaybackStatuses(); window.addEventListener('yt-player-updated', async (event) => { await handleVideoLoad(event); }, true); window.addEventListener('yt-autonav-pause-player-ended', () => { if (videoId) GMCustomDeleteValue(videoId); }, true); } catch (error) { console.error(`Error when initializing script: ${error}. Aborting script.`); } } initialize(); })();