您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Hides comments and recommendations until you watch a configurable percentage of the video without skipping; automatically pauses when the player goes out of view, when switching tabs or clicking outside, and tries to resume when you return.
当前为
// ==UserScript== // @name YouTube Focus Enhancer // @namespace http://tampermonkey.net/ // @version 1.2.1 // @description Hides comments and recommendations until you watch a configurable percentage of the video without skipping; automatically pauses when the player goes out of view, when switching tabs or clicking outside, and tries to resume when you return. // @author Choudhary // @match https://www.youtube.com/* // @grant none // @run-at document-idle // ==/UserScript== (() => { 'use strict'; /*** CONFIGURATION ***/ const config = { requiredPercentToUnlockComments: 50, // Prozent (Standard 50). Setzen Sie 100 falls gewünscht. seekToleranceSeconds: 2, // wenn vorwärts gesprungen > diese Sek., gilt als "geskippt" intersectionVisibilityThreshold: 0.5, // wieviel vom Player sichtbar sein muss, sonst Pause (0..1) smoothRevealMs: 400, // Dauer der Fade-In Animation beim Freischalten (ms) pauseAtEndPlaylistTolerance: 0.5, // Sekunden vor Ende, wo wir bei Playlists anhalten können initRetryIntervalMs: 800, // interval um video wrapper zu finden verbose: false // true -> console.debugs }; /*** UTILITY ***/ const INSTANCE_ID = Math.random().toString(36).slice(2, 9); const log = (...args) => { if (config.verbose) console.debug(`[YT-SMART ${INSTANCE_ID}]`, ...args); }; const sleep = ms => new Promise(r => setTimeout(r, ms)); /*** ADD STYLES (once per page) ***/ function ensureCSS() { if (document.head.querySelector('style[data-yt-smart]')) return; const css = ` ytd-comments#comments, ytd-item-section-renderer#related, ytd-watch-next-secondary-results-renderer { transition: opacity ${config.smoothRevealMs}ms ease, max-height ${config.smoothRevealMs}ms ease; } .tm-hide-comments { opacity: 0 !important; max-height: 0 !important; overflow: hidden !important; pointer-events: none !important; } .tm-hide-related { opacity: 0 !important; max-height: 0 !important; overflow: hidden !important; pointer-events: none !important; } .tm-show-comments { opacity: 1 !important; max-height: 2000px !important; pointer-events: auto !important; }`; const s = document.createElement('style'); s.setAttribute('data-yt-smart', INSTANCE_ID); s.textContent = css; document.head.appendChild(s); log('CSS injected'); } /*** MAIN CLASS - ENCAPSULATES PER-PAGE INSTANCE ***/ class YTGuard { constructor() { this.playerVideo = null; this.videoWrapper = null; this.currentWatchId = null; this.watchingState = null; this.intersectionObserver = null; this.mutationObserver = null; this.bound = {}; // bound handlers for removal this.lastUrl = location.href; this.urlPoller = null; this.initialized = false; this.destroyed = false; log('YTGuard ctor'); this.setup(); } setup() { ensureCSS(); if (window.__ytSmartInstance && !window.__ytSmartInstance.destroyed) { log('Vorherige Instanz vorhanden — bereinigen'); window.__ytSmartInstance.destroy(); } window.__ytSmartInstance = this; this.bound.visibility = this.onVisibilityChange.bind(this); this.bound.focus = this.onWindowFocus.bind(this); this.bound.blur = this.onWindowBlur.bind(this); this.bound.pageshow = this.onPageShow.bind(this); this.bound.mutation = this.onBodyMutations.bind(this); this.bound.popstate = this.onHistoryChange.bind(this); this.bound.pointer = this.onMouseMove.bind(this); this.bound.click = this.onDocumentClick.bind(this); document.addEventListener('visibilitychange', this.bound.visibility, true); window.addEventListener('focus', this.bound.focus, true); window.addEventListener('blur', this.bound.blur, true); window.addEventListener('pageshow', this.bound.pageshow); window.addEventListener('popstate', this.bound.popstate); document.addEventListener('mousemove', this.bound.pointer, { passive: true }); document.addEventListener('click', this.bound.click, true); this.patchHistory(); this.mutationObserver = new MutationObserver(this.bound.mutation); try { this.mutationObserver.observe(document.documentElement || document.body, { childList: true, subtree: true }); } catch (e) { /* ignore */ } this.urlPoller = setInterval(() => { if (location.href !== this.lastUrl) { log('URL change detected by poller', this.lastUrl, '->', location.href); this.lastUrl = location.href; this.onUrlChange(); } }, config.initRetryIntervalMs); this.tryAttachLoop(); } patchHistory() { try { const push = history.pushState; const replace = history.replaceState; history.pushState = function () { const r = push.apply(this, arguments); window.dispatchEvent(new Event('yt-smart-history')); return r; }; history.replaceState = function () { const r = replace.apply(this, arguments); window.dispatchEvent(new Event('yt-smart-history')); return r; }; window.addEventListener('yt-smart-history', () => { log('history API change -> onUrlChange'); this.onUrlChange(); }); } catch (e) { log('history patch failed', e); } } async tryAttachLoop() { for (let i = 0; !this.initialized && i < 50 && !this.destroyed; i++) { try { const v = this.findVideoElement(); if (v) { log('Video gefunden in tryAttachLoop'); this.initForVideo(v); break; } } catch (e) { /* ignore */ } await sleep(config.initRetryIntervalMs); } } findVideoElement() { const v = document.querySelector('video.html5-main-video, video.video-stream, #movie_player video'); return v || null; } onBodyMutations(muts) { if (this.destroyed) return; const v = this.findVideoElement(); if (v && (!this.playerVideo || this.playerVideo !== v)) { log('MutationObserver detected Video change/appearance'); this.initForVideo(v); } this.tryAttachHideClasses(); } onHistoryChange() { if (this.destroyed) return; this.onUrlChange(); } onUrlChange() { log('onUrlChange called'); this.cleanupVideoListeners(); this.playerVideo = null; this.videoWrapper = null; this.initialized = false; this.currentWatchId = null; this.watchingState = null; this.tryAttachLoop(); this.tryAttachHideClasses(); } onPageShow() { log('pageshow'); this.tryAttachLoop(); } onWindowFocus() { log('window focus'); this.tryAttachLoop(); if (this.playerVideo) this.resumeIfAllowed('window:focus'); } onWindowBlur() { log('window blur'); if (this.playerVideo) this.pauseByScript('window:blur'); } onVisibilityChange() { if (document.hidden) { log('document hidden'); if (this.playerVideo) this.pauseByScript('visibility:hidden'); } else { log('document visible'); if (this.playerVideo) this.resumeIfAllowed('visibility:visible'); } } tryAttachHideClasses() { const comments = document.querySelector('ytd-comments#comments'); if (comments && !comments.classList.contains('tm-show-comments') && !comments.classList.contains('tm-hide-comments')) { comments.classList.add('tm-hide-comments'); log('Kommentare initial versteckt'); } const related = document.querySelector('ytd-watch-next-secondary-results-renderer, ytd-item-section-renderer#related'); if (related && !related.classList.contains('tm-hide-related')) { related.classList.add('tm-hide-related'); log('Seitenleiste initial versteckt'); } } initForVideo(videoEl) { if (this.destroyed) return; if (!videoEl) return; if (this.playerVideo === videoEl && this.initialized) { log('initForVideo: gleiche Video-Element bereits angebunden'); return; } this.cleanupVideoListeners(); this.playerVideo = videoEl; this.videoWrapper = document.querySelector('.html5-video-player') || document.querySelector('#movie_player') || this.playerVideo.closest('.html5-video-player') || null; this.currentWatchId = this.getVideoIdFromUrl() || `${Date.now()}`; this.resetWatchingState(); this.bound.timeupdate = this.onTimeUpdate.bind(this); this.bound.seeking = this.onSeeking.bind(this); this.bound.seeked = this.onSeeked.bind(this); this.bound.pause = this.onPauseEvent.bind(this); this.bound.play = this.onPlayEvent.bind(this); this.bound.ended = this.onEndedEvent.bind(this); this.playerVideo.addEventListener('timeupdate', this.bound.timeupdate); this.playerVideo.addEventListener('seeking', this.bound.seeking); this.playerVideo.addEventListener('seeked', this.bound.seeked); this.playerVideo.addEventListener('pause', this.bound.pause); this.playerVideo.addEventListener('play', this.bound.play); this.playerVideo.addEventListener('ended', this.bound.ended); this.setupIntersectionObserver(); this.initialized = true; log('initForVideo completed — instance:', INSTANCE_ID, 'videoId:', this.currentWatchId); this.tryAttachHideClasses(); } resetWatchingState() { this.watchingState = { organic: true, watchedOrganicSeconds: 0, lastTimeSeen: this.playerVideo ? this.playerVideo.currentTime : 0, lastReportedCurrentTime: this.playerVideo ? this.playerVideo.currentTime : 0, lastSeekedFrom: null, unlocked: false, autoPausedByScript: false, userPaused: false }; } getVideoIdFromUrl() { const m = location.search.match(/[?&]v=([^&]+)/); return m ? decodeURIComponent(m[1]) : null; } onTimeUpdate() { if (!this.playerVideo || this.destroyed) return; const t = this.playerVideo.currentTime; const d = this.playerVideo.duration || 0; const last = this.watchingState.lastReportedCurrentTime || t; if (t >= last && !this.playerVideo.seeking) { const delta = t - last; if (this.watchingState.organic && delta > 0 && delta < 10) { this.watchingState.watchedOrganicSeconds += delta; } this.watchingState.lastReportedCurrentTime = t; this.watchingState.lastTimeSeen = t; } else { this.watchingState.lastReportedCurrentTime = t; this.watchingState.lastTimeSeen = t; } this.tryUnlockCommentsIfEligible(d); this.tryPauseAtEndIfPlaylist(d, t); } onSeeking() { if (!this.playerVideo) return; this.watchingState.lastSeekedFrom = this.watchingState.lastReportedCurrentTime || this.playerVideo.currentTime; log('seeking from', this.watchingState.lastSeekedFrom); } onSeeked() { if (!this.playerVideo) return; const from = this.watchingState.lastSeekedFrom != null ? this.watchingState.lastSeekedFrom : this.watchingState.lastReportedCurrentTime; const to = this.playerVideo.currentTime; const delta = to - from; log('seeked', { from, to, delta }); if (delta > config.seekToleranceSeconds) { this.watchingState.organic = false; this.watchingState.watchedOrganicSeconds = 0; log('Detected forward skip -> organic=false; watched reset'); } else { log('Seek small/backwards -> allowed'); } this.watchingState.lastReportedCurrentTime = to; this.watchingState.lastSeekedFrom = null; } tryUnlockCommentsIfEligible(duration) { if (!this.playerVideo || this.watchingState.unlocked) return; if (!duration || !isFinite(duration) || duration <= 0) return; const requiredSeconds = (config.requiredPercentToUnlockComments / 100) * duration; log('watchedOrganicSeconds', this.watchingState.watchedOrganicSeconds, 'requiredSeconds', requiredSeconds); if (this.watchingState.watchedOrganicSeconds >= requiredSeconds && this.watchingState.organic) { this.unlockCommentsSmooth(); this.watchingState.unlocked = true; } } unlockCommentsSmooth() { const comments = document.querySelector('ytd-comments#comments'); if (!comments) { log('Kommentare DOM nicht gefunden zum Freischalten.'); return; } comments.classList.remove('tm-hide-comments'); comments.classList.add('tm-show-comments'); log('Kommentare freigeschaltet (smooth)'); } isPlaylistActive() { try { if (location.search && location.search.includes('list=')) return true; if (document.querySelector('ytd-playlist-panel-renderer')) return true; } catch (e) {} return false; } tryPauseAtEndIfPlaylist(duration, currentTime) { if (!this.isPlaylistActive()) return; if (config.requiredPercentToUnlockComments < 100) return; if (!this.playerVideo || !duration || !isFinite(duration)) return; if (duration - currentTime <= config.pauseAtEndPlaylistTolerance) { try { this.playerVideo.pause(); this.watchingState.autoPausedByScript = true; this.watchingState.watchedOrganicSeconds = duration; this.watchingState.unlocked = true; this.unlockCommentsSmooth(); log('Playlist detected: am Ende - Video pausiert und Kommentare freigegeben.'); } catch (e) { log('Fehler beim Pausieren am Ende der Playlist', e); } } } onPauseEvent() { if (this.watchingState.autoPausedByScript) { log('pause event: von Skript ausgelöst'); } else { this.watchingState.userPaused = true; log('pause event: vom Benutzer'); } } onPlayEvent() { this.watchingState.userPaused = false; this.watchingState.autoPausedByScript = false; log('play event'); } onEndedEvent() { const d = this.playerVideo.duration || 0; this.watchingState.watchedOrganicSeconds = d; this.tryUnlockCommentsIfEligible(d); log('ended event'); } pauseByScript(reason) { if (!this.playerVideo) return; try { if (!this.playerVideo.paused) { this.playerVideo.pause(); this.watchingState.autoPausedByScript = true; log('Video pausiert (script). Grund:', reason); } } catch (e) { log('Fehler beim Pausieren durch Script', e); } } async resumeIfAllowed(reason) { if (!this.playerVideo) return; if (!this.watchingState.autoPausedByScript) { log('resumeIfAllowed: skipping, not auto-paused by script. Grund:', reason); return; } if (this.watchingState.userPaused) { log('resumeIfAllowed: skipping, Benutzer pausiert aktiv. Grund:', reason); return; } try { await this.playerVideo.play(); this.watchingState.autoPausedByScript = false; log('Video automatisch fortgesetzt. Grund:', reason); } catch (e) { log('Fehler beim automatischen Fortsetzen (Autoplay-Policy?):', e); } } isClickInsidePlayer(evt) { const player = document.querySelector('.html5-video-player, #movie_player'); if (!player) return false; return player.contains(evt.target); } onDocumentClick(evt) { const inside = this.isClickInsidePlayer(evt); if (!inside && this.playerVideo && !this.playerVideo.paused) { this.pauseByScript('click:outside-player'); } } onMouseMove(evt) { if (!this.playerVideo) return; const player = document.querySelector('.html5-video-player, #movie_player'); if (!player) return; const rect = player.getBoundingClientRect(); const inside = evt.clientX >= rect.left && evt.clientX <= rect.right && evt.clientY >= rect.top && evt.clientY <= rect.bottom; if (inside) { this.resumeIfAllowed('pointer:enter-player'); } } setupIntersectionObserver() { try { if (this.intersectionObserver) { this.intersectionObserver.disconnect(); this.intersectionObserver = null; } const videoWrapper = this.videoWrapper || document.querySelector('.html5-video-player') || document.querySelector('#movie_player'); if (!videoWrapper) { log('IntersectionObserver: Video-Wrapper noch nicht gefunden.'); return; } this.intersectionObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { const ratio = entry.intersectionRatio; log('Intersection ratio', ratio); if (ratio < config.intersectionVisibilityThreshold) { this.pauseByScript('intersection:out-of-view'); } else { this.resumeIfAllowed('intersection:back-in-view'); } }); }, { threshold: [0, config.intersectionVisibilityThreshold, 1.0] }); this.intersectionObserver.observe(videoWrapper); log('IntersectionObserver eingerichtet'); } catch (e) { log('IntersectionObserver Fehler', e); } } cleanupVideoListeners() { try { if (!this.playerVideo) return; const evs = ['timeupdate', 'seeking', 'seeked', 'pause', 'play', 'ended']; evs.forEach(ev => { const h = this.bound[ev]; if (h) { this.playerVideo.removeEventListener(ev, h); } }); } catch (e) { /* ignore */ } try { if (this.intersectionObserver) { this.intersectionObserver.disconnect(); this.intersectionObserver = null; } } catch (e) {} } destroy() { if (this.destroyed) return; this.destroyed = true; log('destroying instance'); try { if (this.mutationObserver) this.mutationObserver.disconnect(); if (this.intersectionObserver) this.intersectionObserver.disconnect(); if (this.urlPoller) clearInterval(this.urlPoller); document.removeEventListener('visibilitychange', this.bound.visibility, true); window.removeEventListener('focus', this.bound.focus, true); window.removeEventListener('blur', this.bound.blur, true); window.removeEventListener('pageshow', this.bound.pageshow); window.removeEventListener('popstate', this.bound.popstate); document.removeEventListener('mousemove', this.bound.pointer, { passive: true }); document.removeEventListener('click', this.bound.click, true); this.cleanupVideoListeners(); } catch (e) { /* ignore */ } if (window.__ytSmartInstance === this) window.__ytSmartInstance = null; } } // end class /*** STARTUP ***/ try { if (!window.__ytSmartInstance || window.__ytSmartInstance.destroyed) { new YTGuard(); log('YTGuard started — instance id', INSTANCE_ID); } else { window.__ytSmartInstance.destroy(); new YTGuard(); log('Replaced existing instance with new instance', INSTANCE_ID); } } catch (e) { console.error('YT-SMART init error', e); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址