// ==UserScript== // @name 多功能整合:多功能按钮 + 视频增强 // @namespace https://github.com/merged-allinone // @version 4.0.0 // @description 回顶/到底/刷新/DeepSeek按钮(均可独立开关),视频控制(全屏/3倍速/快进/重播/手势,总开关控制),视频进度记忆 // @author Assistant // @match *://*/* // @run-at document-start // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // ==================== 黑名单配置 ==================== // 这些域名的视频增强功能将被完全禁用 const VIDEO_BLACKLIST = ['manhuabika.com']; // ==================== 全局常量 ==================== // 按钮与提示相关 ID const IDS = { // 视频按钮 FULLSCREEN: 've-fullscreen', SPEED: 've-speed', FORWARD: 've-forward', RESTART: 've-restart', // 多功能按钮 SCROLL_TOP: 'multi-top', SCROLL_BOTTOM: 'multi-bottom', REFRESH: 'multi-refresh', DEEPSEEK: 'multi-deepseek', // 其他 TOAST: 'allinone-toast', STYLE: 'allinone-styles' }; const CLASSES = { VISIBLE: 'visible', SPEED_ACTIVE: 'speed-active', SWIPE_HINT: 've-swipe-hint', VIDEO_MODE: 'video-mode' }; // ==================== 配置项 ==================== const CONFIG = { // ---------- 视频增强总开关 ---------- ENABLE_VIDEO_ENHANCEMENT: true, // ---------- 多功能按钮独立开关 ---------- ENABLE_SCROLL_TOP_BTN: true, ENABLE_SCROLL_BOTTOM_BTN: true, ENABLE_REFRESH_BTN: true, ENABLE_DEEPSEEK_BTN: true, // ---------- DeepSeek 链接 ---------- DEEPSEEK_URL: 'https://chat.deepseek.com/', // ---------- UI 样式 ---------- BUTTON_SIZE: 33, BUTTON_RIGHT: 6, BUTTON_GAP: 22, START_TOP: 62, // ---------- 滚动阈值 ---------- SCROLL_THRESHOLD: 200, BOTTOM_THRESHOLD: 50, // ---------- 视频参数 ---------- SEEK_STEP: 20, DOUBLE_TAP_DELAY: 300, SWIPE_THRESHOLD: 40, SWIPE_HORIZONTAL_TOLERANCE: 70, // ---------- 手势开关 ---------- ENABLE_DOUBLE_TAP: true, ENABLE_SWIPE_FULLSCREEN: true, SHOW_SWIPE_HINT: true, // ---------- 视频进度缓存 ---------- CACHE_PREFIX: 'video_progress_', AUTO_SAVE_INTERVAL: 5000, CACHE_EXPIRE_DAYS: 7, RESTORE_MIN_REMAINING: 30 }; // ==================== 工具函数 ==================== const throttle = (fn, wait) => { let timeout, lastRun = 0; return function(...args) { const now = Date.now(); const later = () => { lastRun = Date.now(); timeout = null; fn.apply(this, args); }; if (now - lastRun >= wait) later(); else if (!timeout) timeout = setTimeout(later, wait - (now - lastRun)); }; }; const debounce = (fn, wait) => { let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => fn.apply(this, args), wait); }; }; const addStyleOnce = (css, id) => { if (document.getElementById(id)) return; const style = document.createElement('style'); style.id = id; style.textContent = css; (document.head || document.documentElement).appendChild(style); }; // 全屏 API 封装 const fullscreenAPI = { request: (el) => { const methods = ['requestFullscreen','webkitRequestFullscreen','mozRequestFullScreen','msRequestFullscreen']; for (const m of methods) if (el[m]) { el[m](); return true; } return false; }, exit: () => { const methods = ['exitFullscreen','webkitExitFullscreen','mozCancelFullScreen','msExitFullscreen']; for (const m of methods) if (document[m]) { document[m](); return true; } return false; }, isFullscreen: () => !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement) }; // Toast 提示 let toastElement = null; const showToast = (msg, duration = 2000) => { if (!toastElement) { if (!document.body) return; toastElement = document.createElement('div'); toastElement.id = IDS.TOAST; toastElement.style.cssText = ` position:fixed; bottom:20vh; left:50%; transform:translateX(-50%); background:rgba(60,60,60,0.8); backdrop-filter:blur(4px); -webkit-backdrop-filter:blur(4px); color:#fff; padding:10px 20px; border-radius:30px; font-size:14px; z-index:2147483647; opacity:0; transition:opacity 0.2s; pointer-events:none; white-space:nowrap; box-shadow:0 2px 8px rgba(0,0,0,0.15); border:1px solid rgba(255,255,255,0.2); `; document.body.appendChild(toastElement); } toastElement.textContent = msg; toastElement.style.opacity = '1'; clearTimeout(toastElement._hideTimer); toastElement._hideTimer = setTimeout(() => toastElement.style.opacity = '0', duration); }; // localStorage 安全检测 const storageAvailable = (() => { try { const test = '__storage_test__'; localStorage.setItem(test, test); localStorage.removeItem(test); return true; } catch (e) { return false; } })(); // ==================== 视频评分选择器 ==================== const getActiveVideo = () => { const videos = Array.from(document.querySelectorAll('video')).filter(v => v.videoWidth > 0 && v.videoHeight > 0); if (!videos.length) return null; const scored = videos.map(v => { let score = 0; if (!v.paused && v.readyState >= 2) score += 5; if (v.src || v.currentSrc) score += 3; const rect = v.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) score += 2; return { video: v, score }; }); scored.sort((a, b) => b.score - a.score); return scored[0].video; }; // ==================== 视频进度缓存管理器 ==================== class ProgressManager { static getCacheKey(video) { const src = video.src || video.currentSrc || location.href; try { return CONFIG.CACHE_PREFIX + btoa(encodeURIComponent(src)).replace(/[^a-zA-Z0-9]/g, ''); } catch { return CONFIG.CACHE_PREFIX + src.replace(/\W/g, ''); } } static save(video) { if (!storageAvailable || !video || !isFinite(video.duration)) return; try { localStorage.setItem(ProgressManager.getCacheKey(video), JSON.stringify({ currentTime: video.currentTime, duration: video.duration, playbackRate: video.playbackRate, timestamp: Date.now() })); } catch (e) {} } static restore(video, onRestore) { if (!storageAvailable || !video) return; try { const saved = localStorage.getItem(ProgressManager.getCacheKey(video)); if (!saved) return; const p = JSON.parse(saved); if (Date.now() - p.timestamp > CONFIG.CACHE_EXPIRE_DAYS * 86400000) { localStorage.removeItem(ProgressManager.getCacheKey(video)); return; } const apply = () => { if (video.readyState >= 1 && isFinite(video.duration)) { const t = Math.min(p.currentTime, video.duration - CONFIG.RESTORE_MIN_REMAINING); if (t > 0) video.currentTime = t; video.playbackRate = p.playbackRate || 1.0; if (onRestore) onRestore(video.playbackRate); return true; } return false; }; if (!apply()) { const interval = setInterval(() => { if (apply() || !video.isConnected) clearInterval(interval); }, 100); setTimeout(() => clearInterval(interval), 5000); } } catch (e) {} } static clear(video) { if (!storageAvailable || !video) return; try { localStorage.removeItem(ProgressManager.getCacheKey(video)); } catch (e) {} } } // ==================== 手势管理器 ==================== class GestureHandler { constructor(videoEnabledFn, getVideoFn) { this.videoEnabled = videoEnabledFn; // 函数,返回是否启用视频增强 this.getActiveVideo = getVideoFn; this.doubleTap = { lastTap: 0 }; this.swipeState = { startY: 0, startX: 0, hintTimer: null, targetVideo: null, isTracking: false }; this.init(); } init() { if (!CONFIG.ENABLE_DOUBLE_TAP && !CONFIG.ENABLE_SWIPE_FULLSCREEN) return; // 双击播放/暂停 if (CONFIG.ENABLE_DOUBLE_TAP) { document.addEventListener('touchstart', (e) => { const target = e.target; if (target.tagName !== 'VIDEO' || !target.videoWidth || !target.videoHeight) return; const now = Date.now(); if (now - this.doubleTap.lastTap < CONFIG.DOUBLE_TAP_DELAY) { e.preventDefault(); target.paused ? target.play() : target.pause(); } this.doubleTap.lastTap = now; }, { passive: false }); } // 上下滑动切换全屏 if (CONFIG.ENABLE_SWIPE_FULLSCREEN) { this._bindSwipe(); } } _bindSwipe() { const state = this.swipeState; const isValidVideo = (el) => el && el.tagName === 'VIDEO' && el.videoWidth > 0 && el.videoHeight > 0; const canSwipe = () => this.videoEnabled() && this.getActiveVideo(); const clearHint = () => { if (state.targetVideo) state.targetVideo.classList.remove(CLASSES.SWIPE_HINT); if (state.hintTimer) { clearTimeout(state.hintTimer); state.hintTimer = null; } }; const reset = () => { clearHint(); state.startY = state.startX = 0; state.targetVideo = null; state.isTracking = false; }; document.addEventListener('touchstart', (e) => { const touch = e.touches[0]; if (!touch) return; const isFS = fullscreenAPI.isFullscreen(); if (isFS) { const target = e.target; if (!isValidVideo(target)) return; e.preventDefault(); state.startY = touch.clientY; state.startX = touch.clientX; state.targetVideo = target; state.isTracking = true; return; } if (!canSwipe()) return; state.startY = touch.clientY; state.startX = touch.clientX; state.targetVideo = this.getActiveVideo(); state.isTracking = true; }, { passive: false }); document.addEventListener('touchmove', (e) => { if (!state.isTracking) return; const touch = e.touches[0]; if (!touch) { reset(); return; } const deltaY = state.startY - touch.clientY; const deltaX = Math.abs(state.startX - touch.clientX); const isFS = fullscreenAPI.isFullscreen(); const showHint = () => { if (CONFIG.SHOW_SWIPE_HINT && state.targetVideo) { state.targetVideo.classList.add(CLASSES.SWIPE_HINT); if (state.hintTimer) clearTimeout(state.hintTimer); state.hintTimer = setTimeout(() => { if (state.targetVideo) state.targetVideo.classList.remove(CLASSES.SWIPE_HINT); state.hintTimer = null; }, 300); } }; if (!isFS) { if (!canSwipe()) { reset(); return; } if (deltaX > CONFIG.SWIPE_HORIZONTAL_TOLERANCE) { reset(); return; } if (deltaY > CONFIG.SWIPE_THRESHOLD) { e.preventDefault(); showHint(); } } else { e.preventDefault(); if (!state.targetVideo || !isValidVideo(state.targetVideo)) { reset(); return; } if (deltaX > CONFIG.SWIPE_HORIZONTAL_TOLERANCE) { reset(); return; } if (deltaY < -CONFIG.SWIPE_THRESHOLD) { showHint(); } } }, { passive: false }); document.addEventListener('touchend', (e) => { if (!state.isTracking) return; const touch = e.changedTouches[0]; if (!touch) { reset(); return; } const deltaY = state.startY - touch.clientY; const deltaX = Math.abs(state.startX - touch.clientX); const isFS = fullscreenAPI.isFullscreen(); if (!isFS) { if (!canSwipe()) { reset(); return; } if (deltaX <= CONFIG.SWIPE_HORIZONTAL_TOLERANCE && deltaY > CONFIG.SWIPE_THRESHOLD) { const video = this.getActiveVideo(); if (video) { const success = fullscreenAPI.request(video); if (!success) showToast('全屏被浏览器拒绝,请点击全屏按钮'); } } } else { if (deltaX <= CONFIG.SWIPE_HORIZONTAL_TOLERANCE && deltaY < -CONFIG.SWIPE_THRESHOLD) { fullscreenAPI.exit(); } } reset(); }); document.addEventListener('touchcancel', reset); } } // ==================== 主控制器 ==================== class AllInOne { constructor() { this.currentVideo = null; this.isSpeed3x = false; this.videoHandlers = new WeakMap(); this.videoEnabled = CONFIG.ENABLE_VIDEO_ENHANCEMENT && !this._isHostBlocked(); // 注入样式 → 创建按钮 → 绑定事件 → 初始化手势 → 启动DOM监听 this._injectStyles(); this._createButtons(); this._bindEvents(); if (this.videoEnabled) { this.gestureHandler = new GestureHandler( () => this.videoEnabled, () => getActiveVideo() ); } this._startObservers(); this._updateUI(); // 初始化时恢复视频进度(如果页面已有视频) const active = getActiveVideo(); if (active && this.videoEnabled) this._attachVideo(active); } // --- 黑名单检测 --- _isHostBlocked() { const host = location.hostname.toLowerCase(); const url = location.href.toLowerCase(); return VIDEO_BLACKLIST.some(rule => host.includes(rule.trim().toLowerCase()) || url.includes(rule.trim().toLowerCase()) ); } // --- 样式注入 --- _injectStyles() { const { BUTTON_SIZE, BUTTON_RIGHT, BUTTON_GAP, START_TOP, SEEK_STEP } = CONFIG; const id = IDS.STYLE; let css = ''; // 基本按钮样式 const allIds = [IDS.FULLSCREEN, IDS.SPEED, IDS.FORWARD, IDS.RESTART, IDS.SCROLL_TOP, IDS.SCROLL_BOTTOM, IDS.REFRESH, IDS.DEEPSEEK]; css += `${allIds.map(id => `#${id}`).join(',')} { position: fixed; right: ${BUTTON_RIGHT}px; width: ${BUTTON_SIZE}px; height: ${BUTTON_SIZE}px; border-radius: 50%; background: rgba(60,60,60,0.5); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); box-shadow: 0 2px 8px rgba(0,0,0,0.15); display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 2147483647; transition: opacity 0.25s, transform 0.2s, background 0.2s; border: 1px solid rgba(255,255,255,0.2); color: white; font-weight: 600; font-family: Arial, sans-serif; }`; // 按钮垂直位置 const vIndex = [IDS.FULLSCREEN, IDS.SPEED, IDS.FORWARD, IDS.RESTART]; vIndex.forEach((id, i) => css += `#${id} { top: calc(${START_TOP}vh + ${i * (BUTTON_SIZE + BUTTON_GAP)}px); }`); const mIndex = [IDS.SCROLL_TOP, IDS.SCROLL_BOTTOM, IDS.REFRESH, IDS.DEEPSEEK]; mIndex.forEach((id, i) => css += `#${id} { top: calc(${START_TOP}vh + ${i * (BUTTON_SIZE + BUTTON_GAP)}px); }`); // 可见性控制 css += ` #${IDS.SCROLL_TOP}, #${IDS.SCROLL_BOTTOM} { opacity:0; transform:scale(0.8); pointer-events:none; } #${IDS.SCROLL_TOP}.${CLASSES.VISIBLE}, #${IDS.SCROLL_BOTTOM}.${CLASSES.VISIBLE} { opacity:1; transform:scale(1); pointer-events:auto; } #${IDS.FULLSCREEN}, #${IDS.SPEED}, #${IDS.FORWARD}, #${IDS.RESTART} { opacity:0; transform:scale(0.8); pointer-events:none; } .${CLASSES.VIDEO_MODE} #${IDS.FULLSCREEN}, .${CLASSES.VIDEO_MODE} #${IDS.SPEED}, .${CLASSES.VIDEO_MODE} #${IDS.FORWARD}, .${CLASSES.VIDEO_MODE} #${IDS.RESTART} { opacity:1; transform:scale(1); pointer-events:auto; } .${CLASSES.VIDEO_MODE} #${IDS.SCROLL_TOP}, .${CLASSES.VIDEO_MODE} #${IDS.SCROLL_BOTTOM}, .${CLASSES.VIDEO_MODE} #${IDS.REFRESH}, .${CLASSES.VIDEO_MODE} #${IDS.DEEPSEEK} { opacity:0; transform:scale(0.8); pointer-events:none; } /* 图标内容 */ #${IDS.FULLSCREEN}::before { content:'⛶'; font-size:22px; } #${IDS.SPEED}::before { content:'3×'; } #${IDS.SPEED}.${CLASSES.SPEED_ACTIVE} { background:rgba(0,150,200,0.7) !important; } #${IDS.FORWARD}::before { content:'↷${SEEK_STEP}s'; font-size:12px; } #${IDS.RESTART}::before { content:'↺'; font-size:20px; } #${IDS.SCROLL_TOP}::before { content:''; width:10px; height:10px; border-left:2.2px solid #fff; border-bottom:2.2px solid #fff; transform:rotate(135deg); margin-top:4px; } #${IDS.SCROLL_BOTTOM}::before { content:''; width:10px; height:10px; border-left:2.2px solid #fff; border-bottom:2.2px solid #fff; transform:rotate(-45deg); margin-top:-4px; } #${IDS.REFRESH}::before { content:'↻'; font-size:26px; line-height:1; } #${IDS.DEEPSEEK} { font-size:16px; } #${IDS.DEEPSEEK}::before { content:'DS'; line-height:1; text-shadow:0 1px 2px rgba(0,0,0,0.2); } /* 点击反馈 */ ${allIds.map(id => `#${id}:active`).join(',')} { background:rgba(80,80,80,0.6); transform:scale(0.9); } @media (prefers-color-scheme: dark) { ${allIds.map(id => `#${id}`).join(',')} { background:rgba(220,220,220,0.2); border-color:rgba(255,255,255,0.1); } #${IDS.SPEED}.${CLASSES.SPEED_ACTIVE} { background:rgba(0,150,200,0.5) !important; } } /* 手势提示动画 */ .${CLASSES.SWIPE_HINT} { position:relative; } .${CLASSES.SWIPE_HINT}::after { content:''; position:absolute; top:0; left:0; right:0; bottom:0; background:rgba(0,150,200,0.3); pointer-events:none; z-index:999; animation:veHintFade 0.2s ease; } @keyframes veHintFade { from { opacity:0; } to { opacity:1; } } `; addStyleOnce(css, id); } // --- 按钮创建 --- _createButtons() { // 视频按钮(受总开关控制) if (CONFIG.ENABLE_VIDEO_ENHANCEMENT) { this._makeBtn(IDS.FULLSCREEN, '全屏切换', () => this._onVideoAction('FULLSCREEN')); this._makeBtn(IDS.SPEED, '三倍速', () => this._onVideoAction('SPEED')); this._makeBtn(IDS.FORWARD, `快进${CONFIG.SEEK_STEP}秒`, () => this._onVideoAction('FORWARD')); this._makeBtn(IDS.RESTART, '重播', () => this._onVideoAction('RESTART')); } // 多功能按钮(各自独立开关) if (CONFIG.ENABLE_SCROLL_TOP_BTN) { this._makeBtn(IDS.SCROLL_TOP, '回到顶部', () => window.scrollTo({top: 0, behavior: 'smooth'})); } if (CONFIG.ENABLE_SCROLL_BOTTOM_BTN) { this._makeBtn(IDS.SCROLL_BOTTOM, '去到底部', () => window.scrollTo({top: document.documentElement.scrollHeight, behavior: 'smooth'})); } if (CONFIG.ENABLE_REFRESH_BTN) { this._makeBtn(IDS.REFRESH, '刷新页面', () => location.reload()); } if (CONFIG.ENABLE_DEEPSEEK_BTN) { this._makeBtn(IDS.DEEPSEEK, '打开 DeepSeek', () => location.href = CONFIG.DEEPSEEK_URL); } } _makeBtn(id, title, clickHandler) { const btn = document.createElement('div'); btn.id = id; btn.title = title; btn.addEventListener('click', (e) => { e.stopPropagation(); clickHandler(); }); document.body.appendChild(btn); return btn; } // --- 视频操作分发 --- _onVideoAction(action) { if (!this.videoEnabled) { alert('视频增强已禁用'); return; } const video = this.currentVideo || getActiveVideo(); if (!video) { alert('未检测到视频'); return; } switch (action) { case 'FULLSCREEN': if (fullscreenAPI.isFullscreen()) fullscreenAPI.exit(); else { const ok = fullscreenAPI.request(video); if (!ok) showToast('全屏被浏览器拒绝'); } break; case 'SPEED': this.isSpeed3x = !this.isSpeed3x; video.playbackRate = this.isSpeed3x ? 3.0 : 1.0; document.getElementById(IDS.SPEED)?.classList.toggle(CLASSES.SPEED_ACTIVE, this.isSpeed3x); break; case 'FORWARD': video.currentTime = Math.min(video.currentTime + CONFIG.SEEK_STEP, video.duration); ProgressManager.save(video); break; case 'RESTART': video.currentTime = 0; video.play(); ProgressManager.save(video); break; } } // --- 进度恢复回调 --- _onProgressRestore(playbackRate) { this.isSpeed3x = (playbackRate === 3.0); document.getElementById(IDS.SPEED)?.classList.toggle(CLASSES.SPEED_ACTIVE, this.isSpeed3x); } // --- 视频绑定/解绑 --- _attachVideo(video) { if (!video || video === this.currentVideo) return; this._detachVideo(); this.currentVideo = video; this.isSpeed3x = (video.playbackRate === 3.0); document.getElementById(IDS.SPEED)?.classList.toggle(CLASSES.SPEED_ACTIVE, this.isSpeed3x); ProgressManager.restore(video, (rate) => this._onProgressRestore(rate)); const throttledSave = throttle(() => ProgressManager.save(video), CONFIG.AUTO_SAVE_INTERVAL); const onPause = () => ProgressManager.save(video); const onEnded = () => ProgressManager.clear(video); video.addEventListener('timeupdate', throttledSave); video.addEventListener('pause', onPause); video.addEventListener('ended', onEnded); this.videoHandlers.set(video, { throttledSave, onPause, onEnded }); } _detachVideo() { if (!this.currentVideo) return; const video = this.currentVideo; const handlers = this.videoHandlers.get(video); if (handlers) { video.removeEventListener('timeupdate', handlers.throttledSave); video.removeEventListener('pause', handlers.onPause); video.removeEventListener('ended', handlers.onEnded); this.videoHandlers.delete(video); } ProgressManager.save(video); this.currentVideo = null; } // --- UI 更新(防抖) --- _updateUI = debounce(() => { const video = this.videoEnabled ? getActiveVideo() : null; const hasVideo = video && video.readyState >= 1 && isFinite(video.duration) && video.duration > 0; document.body.classList.toggle(CLASSES.VIDEO_MODE, hasVideo); // 按钮显隐 if (CONFIG.ENABLE_SCROLL_TOP_BTN) { const btn = document.getElementById(IDS.SCROLL_TOP); if (btn) { const y = window.scrollY || document.documentElement.scrollTop || 0; btn.classList.toggle(CLASSES.VISIBLE, y > CONFIG.SCROLL_THRESHOLD); } } if (CONFIG.ENABLE_SCROLL_BOTTOM_BTN) { const btn = document.getElementById(IDS.SCROLL_BOTTOM); if (btn) { const y = window.scrollY || document.documentElement.scrollTop || 0; const ch = document.documentElement.clientHeight; const sh = document.documentElement.scrollHeight; const atBottom = (y + ch) >= (sh - CONFIG.BOTTOM_THRESHOLD); btn.classList.toggle(CLASSES.VISIBLE, !atBottom && sh > ch); } } // 视频切换 if (hasVideo && video !== this.currentVideo) { this._attachVideo(video); } else if (!hasVideo && this.currentVideo) { this._detachVideo(); } // 若当前视频已从DOM移除,强制解绑 if (this.currentVideo && !this.currentVideo.isConnected) { this._detachVideo(); this._updateUI(); } }, 100); // --- 事件监听 --- _bindEvents() { window.addEventListener('scroll', this._updateUI, { passive: true }); document.addEventListener('play', this._updateUI, true); document.addEventListener('pause', this._updateUI, true); document.addEventListener('loadedmetadata', this._updateUI, true); document.addEventListener('fullscreenchange', this._updateUI); document.addEventListener('webkitfullscreenchange', this._updateUI); window.addEventListener('beforeunload', () => { if (this.currentVideo) ProgressManager.save(this.currentVideo); }); // SPA 路由变化感知 if (!window._allInOneRoutePatched) { window._allInOneRoutePatched = true; const patch = (type) => { const orig = history[type]; return function() { const ret = orig.apply(this, arguments); window.dispatchEvent(new Event('urlchange')); return ret; }; }; history.pushState = patch('pushState'); history.replaceState = patch('replaceState'); window.addEventListener('popstate', () => window.dispatchEvent(new Event('urlchange'))); window.addEventListener('urlchange', () => this._updateUI()); window.addEventListener('hashchange', () => this._updateUI()); } } // --- DOM 观察器 --- _startObservers() { new MutationObserver(() => this._updateUI()).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['src'] }); // 额外检测视频移除 new MutationObserver(() => { if (this.currentVideo && !this.currentVideo.isConnected) { this._detachVideo(); this._updateUI(); } }).observe(document.body, { childList: true, subtree: true }); } } // ==================== 启动 ==================== if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => new AllInOne()); } else { new AllInOne(); } })();