Save and restore YouTube playback position across sessions (even when not signed in)
// ==UserScript==
// @name YouTube Playback Position Saver
// @namespace http://tampermonkey.net/
// @version 1.2
// @description Save and restore YouTube playback position across sessions (even when not signed in)
// @author ChatGPT
// @match https://www.youtube.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const STORAGE_KEY = 'yt-timestamps';
let lastVideoId = null;
let saveInterval = null;
function log(...args) {
console.log('[YT-PositionSaver]', ...args);
}
function getVideoId() {
const params = new URLSearchParams(window.location.search);
return params.get('v');
}
function loadTimestamps() {
try {
return JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
} catch {
return {};
}
}
function saveTimestamps(timestamps) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(timestamps));
}
function setupVideoTracking(videoId) {
const video = document.querySelector('video');
if (!video || !videoId) {
return;
}
const timestamps = loadTimestamps();
const savedTime = timestamps[videoId];
if (savedTime && savedTime < video.duration - 10) {
video.currentTime = savedTime;
}
if (saveInterval) clearInterval(saveInterval);
saveInterval = setInterval(() => {
if (!video.paused && !video.ended) {
timestamps[videoId] = Math.floor(video.currentTime);
saveTimestamps(timestamps);
}
}, 5000);
}
function waitForVideoAndSetup(videoId) {
const check = setInterval(() => {
const video = document.querySelector('video');
if (video && video.readyState >= 1) {
clearInterval(check);
setupVideoTracking(videoId);
}
}, 500);
}
function onVideoChange() {
const currentId = getVideoId();
if (!currentId || currentId === lastVideoId) return;
lastVideoId = currentId;
waitForVideoAndSetup(currentId);
}
// Detect SPA navigation using MutationObserver
const observer = new MutationObserver(() => {
onVideoChange();
});
observer.observe(document.body, { childList: true, subtree: true });
// Also run on first load
window.addEventListener('load', () => {
setTimeout(onVideoChange, 1000);
});
})();