Created
July 5, 2026 14:38
-
-
Save anphetamina/2fc2bdc13f1c44235bdad37273c19960 to your computer and use it in GitHub Desktop.
YouTube Playback Error Auto-Refresh script for tampermonkey (Firefox)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name YouTube Playback Error Auto-Refresh | |
| // @namespace https://local.userscripts/youtube-error-refresh | |
| // @version 3.0.0 | |
| // @description When YouTube's "An error occurred, please try again later" playback overlay appears, clears the per-tab sessionStorage (mimicking a fresh tab) and reloads the SAME tab, until the video is playable. Language-independent. Does NOT autoplay. No new tabs. | |
| // @author Antonio Santoro | |
| // @match https://www.youtube.com/* | |
| // @match https://m.youtube.com/* | |
| // @run-at document-start | |
| // @grant none | |
| // @noframes | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| // ------------------------- Config (tweak freely) ------------------------- | |
| const MAX_ATTEMPTS = 6; // Stop after this many resets for the same video (prevents infinite loops). | |
| const CONFIRM_MS = 1200; // Error must stay visible this long before acting (ignores a flicker). | |
| const RELOAD_DELAY_MS = 300; // Small pause before reloading. | |
| const POLL_MS = 1000; // How often we check while watching. | |
| const STABLE_MS = 10000; // Go idle / reset counter after this much error-free, loaded time. | |
| const MAX_WATCH_MS = 90000; // Absolute safety cutoff for polling after (re)start. | |
| const CLEAR_SESSION = true; // The key trick: wipe sessionStorage before reloading (emulates a fresh tab). | |
| const VERBOSE = true; // Set false to silence routine logs. | |
| // ------------------------------------------------------------------------- | |
| // The transient playback error overlay — same class in every language. | |
| // The "video unavailable / private" screen is a DIFFERENT element, not matched here. | |
| const ERROR_SELECTOR = '.ytp-error'; | |
| const log = (...a) => { if (VERBOSE) console.log('[YT auto-refresh]', ...a); }; | |
| let confirmTimer = null; | |
| let reloading = false; | |
| let pollId = null; | |
| let watchStart = 0; | |
| let cleanSince = 0; | |
| function videoKey() { | |
| try { | |
| const u = new URL(location.href); | |
| return u.searchParams.get('v') || location.pathname; | |
| } catch (e) { | |
| return location.pathname; | |
| } | |
| } | |
| // Counter lives in localStorage so wiping sessionStorage doesn't erase it. | |
| const key = () => 'ytErrRefresh:' + videoKey(); | |
| const getCount = () => { try { return parseInt(localStorage.getItem(key()) || '0', 10) || 0; } catch (e) { return 0; } }; | |
| const setCount = (n) => { try { localStorage.setItem(key(), String(n)); } catch (e) {} }; | |
| const clearCount = () => { try { localStorage.removeItem(key()); } catch (e) {} }; | |
| function isVisible(el) { | |
| if (!el) return false; | |
| const cs = getComputedStyle(el); | |
| if (cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0') return false; | |
| return el.getClientRects().length > 0; | |
| } | |
| const errorShowing = () => isVisible(document.querySelector(ERROR_SELECTOR)); | |
| function playerLooksLoaded() { | |
| const v = document.querySelector('video.html5-main-video') || document.querySelector('video'); | |
| return !!v && (v.readyState >= 1 || v.duration > 0); | |
| } | |
| function startPolling() { | |
| watchStart = Date.now(); | |
| cleanSince = 0; | |
| if (pollId == null) pollId = setInterval(check, POLL_MS); | |
| } | |
| function stopPolling(reason) { | |
| if (pollId != null) { clearInterval(pollId); pollId = null; log('idle —', reason); } | |
| } | |
| function cancelPending() { | |
| if (confirmTimer) { clearTimeout(confirmTimer); confirmTimer = null; } | |
| } | |
| function scheduleReload() { | |
| if (reloading || confirmTimer) return; | |
| log('error overlay detected — confirming for ' + CONFIRM_MS + 'ms...'); | |
| confirmTimer = setTimeout(() => { | |
| confirmTimer = null; | |
| if (!errorShowing()) { log('overlay cleared on its own; not reloading'); return; } | |
| const count = getCount(); | |
| if (count >= MAX_ATTEMPTS) { | |
| console.warn('[YT auto-refresh] Giving up after ' + count + ' resets for this video.'); | |
| stopPolling('max attempts reached'); | |
| return; | |
| } | |
| setCount(count + 1); | |
| reloading = true; | |
| if (CLEAR_SESSION) { | |
| try { sessionStorage.clear(); log('sessionStorage cleared (fresh-tab emulation)'); } | |
| catch (e) { log('could not clear sessionStorage:', e); } | |
| } | |
| log('reloading (attempt ' + (count + 1) + '/' + MAX_ATTEMPTS + | |
| ') — enable "Persist Logs" to keep this visible across the reload'); | |
| setTimeout(() => location.reload(), RELOAD_DELAY_MS); | |
| }, CONFIRM_MS); | |
| } | |
| function check() { | |
| if (reloading) return; | |
| if (errorShowing()) { | |
| cleanSince = 0; | |
| watchStart = Date.now(); | |
| scheduleReload(); | |
| return; | |
| } | |
| cancelPending(); | |
| if (playerLooksLoaded()) { | |
| if (!cleanSince) cleanSince = Date.now(); | |
| if (Date.now() - cleanSince > STABLE_MS) { clearCount(); stopPolling('video stable'); } | |
| } else { | |
| cleanSince = 0; | |
| if (Date.now() - watchStart > MAX_WATCH_MS) stopPolling('watch window elapsed'); | |
| } | |
| } | |
| log('active on', location.href); | |
| startPolling(); | |
| document.addEventListener('DOMContentLoaded', check); | |
| window.addEventListener('load', check); | |
| window.addEventListener('yt-navigate-finish', () => { reloading = false; startPolling(); check(); }); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment