YouTube Ads Auto-Skipper (Safe Audio)

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://gf.qytechs.cn/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);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址