您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
FXVNPRo Script Manager - Automatically skips YouTube ads, hides ad elements, and restores audio after skipping
// ==UserScript== // @name YouTube Ads Auto-Skipper (Safe Audio) // @version 2025.08.31 // @description FXVNPRo Script Manager - Automatically skips YouTube ads, hides ad elements, and restores audio after skipping // @author 130195 // @license MIT // @match https://www.youtube.com/* // @match https://youtube.com/* // @namespace https://greasyfork.org/en/users/1491267 // @icon https://www.youtube.com/favicon.ico // @run-at document-idle // @grant none // ==/UserScript== (() => { "use strict"; const CHECK_INTERVAL_MS = 200; const SEEK_THRESHOLD_MS = 12000; const LONG_AD_MIN_SECONDS = 25; const SEEK_END_MARGIN_S = 0.8; // ======================================== const $ = (sel, root = document) => root.querySelector(sel); const $$ = (sel, root = document) => Array.from(root.querySelectorAll(sel)); const isHidden = el => !el || el.offsetParent === null || el.hidden || el.closest?.("[hidden]"); const SKIP_BTN_SELECTOR = ".ytp-ad-skip-button, .ytp-ad-skip-button-modern"; const CLOSE_OVERLAY_SELECTOR = ".ytp-ad-overlay-close-button"; const ADS_CONTAINER_SELECTOR = ".video-ads.ytp-ad-module"; const MOVIE_PLAYER_ID = "movie_player"; const fastSeekFn = HTMLVideoElement.prototype.fastSeek || null; const audioState = new WeakMap(); // video -> { prevMuted, prevVolume } function rememberAudio(video) { if (!video) return; if (!audioState.has(video)) { audioState.set(video, { prevMuted: !!video.muted, prevVolume: typeof video.volume === "number" ? video.volume : 1 }); } else { const st = audioState.get(video); st.prevMuted = !!video.muted; if (typeof video.volume === "number") st.prevVolume = video.volume; audioState.set(video, st); } } function restoreAudio(video) { try { if (!video) return; const st = audioState.get(video); if (!st) return; if (st.prevMuted === false) { video.muted = false; if (typeof st.prevVolume === "number" && st.prevVolume > 0) { video.volume = st.prevVolume; } const ytplayer = video.closest("ytd-player, ytmusic-player"); const cnt = ytplayer && (ytplayer.polymerController || ytplayer.inst || ytplayer); const api = (cnt && (cnt.player_ || cnt.playerApi || cnt.getPlayer?.())) || null; if (api && typeof api.setMuted === "function") { api.setMuted(false); } } } catch (e) { // OKie } } function tryClickSkip() { const adsContainer = $(ADS_CONTAINER_SELECTOR); if (!adsContainer) return false; const skipBtn = $(`${SKIP_BTN_SELECTOR}`, adsContainer); if (skipBtn && !isHidden(skipBtn)) { skipBtn.click(); return true; } const closeOverlayBtn = $(`${CLOSE_OVERLAY_SELECTOR}`, adsContainer); if (closeOverlayBtn && !isHidden(closeOverlayBtn)) { closeOverlayBtn.click(); } return false; } function smartFastSeek(video) { try { if (!video) return false; const dur = Number(video.duration) || 0; if (dur <= 0) return false; const target = Math.max(0, dur - SEEK_END_MARGIN_S); if (fastSeekFn) fastSeekFn.call(video, target); else video.currentTime = target; return true; } catch { return false; } } function getMoviePlayer(video) { return (video && video.closest?.(`#${MOVIE_PLAYER_ID}`)) || document.getElementById(MOVIE_PLAYER_ID); } let adWatch = null; // { video, startedAt, interval, seekTimeout, endedOnce } function clearAdWatch() { if (!adWatch) return; try { if (adWatch.interval) clearInterval(adWatch.interval); if (adWatch.seekTimeout) clearTimeout(adWatch.seekTimeout); } catch {} adWatch = null; } function onAdStart(video) { clearAdWatch(); if (!video) return; rememberAudio(video); adWatch = { video, startedAt: performance.now(), interval: null, seekTimeout: null, endedOnce: false }; adWatch.interval = setInterval(() => { tryClickSkip(); }, CHECK_INTERVAL_MS); adWatch.seekTimeout = setTimeout(() => { const dur = Number(video.duration) || 0; const elapsed = (performance.now() - adWatch.startedAt) / 1000; if (dur >= LONG_AD_MIN_SECONDS || elapsed >= LONG_AD_MIN_SECONDS) { const stillAd = getMoviePlayer(video)?.classList.contains("ad-showing"); if (stillAd) smartFastSeek(video); } }, SEEK_THRESHOLD_MS); } function onAdEnd(video) { if (adWatch && adWatch.video === video && !adWatch.endedOnce) { adWatch.endedOnce = true; } clearAdWatch(); restoreAudio(video); } function observeAdState(video) { const moviePlayer = getMoviePlayer(video); if (!moviePlayer) return; const mo = new MutationObserver(() => { const inAd = moviePlayer.classList.contains("ad-showing"); if (inAd) onAdStart(video); else onAdEnd(video); }); mo.observe(moviePlayer, { attributes: true, attributeFilter: ["class"] }); const inAdNow = moviePlayer.classList.contains("ad-showing"); if (inAdNow) onAdStart(video); } function attachToMainVideo(video) { if (!video || video._yt_ad_handler_attached) return; video._yt_ad_handler_attached = true; const onLoadedMeta = () => rememberAudio(video); video.addEventListener("loadedmetadata", onLoadedMeta, { passive: true }); const onPlaying = () => { const mp = getMoviePlayer(video); if (mp && !mp.classList.contains("ad-showing")) restoreAudio(video); }; video.addEventListener("playing", onPlaying, { passive: true }); observeAdState(video); } const pageMO = new MutationObserver(() => { const video = $("video.video-stream.html5-main-video"); if (video) attachToMainVideo(video); }); pageMO.observe(document.documentElement, { childList: true, subtree: true }); const initVideo = $("video.video-stream.html5-main-video"); if (initVideo) attachToMainVideo(initVideo); })();