// ==UserScript== // @name Æther Player — 全网VIP视频免费解析 · 毛玻璃美学全网首发 · 免费破解去广告 // @namespace https://github.com/shiyi312/-ther-Player-VIP- // @version 5.15.3 // @description 全网VIP视频免费解析:爱奇艺、腾讯、优酷、B站等主流平台一键播放!✨毛玻璃UI ✨彻底静音 ✨记忆上次选择 ✨完全免费 ✨无广告 ✨真4K超清 ✨开源维护 【仅限学习交流】 // @author 辻弌20 github:shiyi312 // @icon https://raw.githubusercontent.com/shiyi312/-ther-Player-VIP-/main/tubiao.ico // @match *://*/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @run-at document-end // @license MIT // ==/UserScript== /* ── 致谢 ── * 温和静音方案改自 88lin/video_vip(GPL-3.0,茉灵智库);感谢开源社区! */ (function () { 'use strict'; /** * @typedef {{ name: string, url?: string }} Api * @typedef {{ success: number, fail: number }} SourceStat */ // ─── GM API 兜底(无 GM 环境时降级,避免直接抛错) ───────────── const GMAddStyle = (typeof GM_addStyle === 'function') ? GM_addStyle : (css) => { const s = document.createElement('style'); s.textContent = css; document.head.appendChild(s); }; const GMGetValue = (typeof GM_getValue === 'function') ? GM_getValue : (key, dflt) => { try { const raw = localStorage.getItem('aether_shim:' + key); return raw === null ? dflt : JSON.parse(raw); } catch (e) { return dflt; } }; const GMSetValue = (typeof GM_setValue === 'function') ? GM_setValue : (key, val) => { try { localStorage.setItem('aether_shim:' + key, JSON.stringify(val)); } catch (e) {} }; const GMRegisterMenuCommand = (typeof GM_registerMenuCommand === 'function') ? GM_registerMenuCommand : () => {}; // ─── 配置常量 ──────────────────────────────────────────────── const CONFIG = { BTN_SIZE: 56, CLAMP_MARGIN: 8, PANEL_FADE_MS: 300, SWITCH_SRC_GUARD_MS: 3000, LOAD_TIMEOUT_MS: 10000, PROTECTION_INTERVAL_MS: 500, MEDIA_KILL_BOOT_DELAY_MS: 100, MEDIA_KILL_INTERVAL_MS: 1000, AD_CLEAN_INTERVAL_MS: 800, URL_WATCH_INTERVAL_MS: 600, URL_CHANGE_DEBOUNCE_MS: 300, STAT_SAVE_DEBOUNCE_MS: 1000, AUTO_PLAY_DELAY_MS: 1200, PLAYER_WAIT_POLL_MS: 200, PLAYER_WAIT_TIMEOUT_MS: 5000, }; // ─── 解析源(第三方解析接口) ──────────────────────────────── const APIS = [ { name: '虾米', url: 'https://jx.xmflv.cc/?url=' }, { name: '七七云', url: 'https://jx.77flv.cc/?url=' }, { name: 'HLS', url: 'https://jx.hls.one/?url=' }, { name: 'playm3u8', url: 'https://www.playm3u8.cn/jiexi.php?url=' }, { name: 'CK', url: 'https://www.ckplayer.vip/jiexi/?url=' }, { name: '冰豆', url: 'https://bd.jx.cn/?url=' }, { name: '789', url: 'https://jiexi.789jiexi.icu:4433/?url=' }, { name: 'M3U8', url: 'https://jx.m3u8.tv/jiexi/?url=' }, { name: '8090', url: 'https://www.8090g.cn/?url=' }, { name: '爱豆', url: 'https://jx.aidouer.net/?url=' }, { name: '芒果TV', url: 'https://video.isyour.love/player/getplayer?url=' }, { name: 'TXNQ', url: 'https://bfq.txnp.cn/player?url=' }, { name: '七哥', url: 'https://jx.202617.xyz/tv.php?url=' }, { name: 'fongmi', url: 'https://json.fongmi.cc/web?url=' }, { name: '极速', url: 'https://jx.2s0.cn/player/?url=' }, { name: '花旗', url: 'https://www.huaqi.live/?url=' }, { name: 'Player-JY', url: 'https://jx.playerjy.com/?url=' }, { name: 'Yparse', url: 'https://jx.yparse.com/index.php?url=' }, { name: '默认B', url: 'https://super.playr.top/?url=' }, ]; // ─── 视频站判定 ──────────────────────────────────────────── const VIDEO_HOST_RE = /v\.qq\.com|iqiyi\.com|youku\.com|bilibili\.com|mgtv\.com|sohu\.com|le\.com|pptv\.com|1905\.com|acfun\.cn/; // ─── 站点广告/蒙层选择器(定点清理,避免误伤整站布局) ────────── const HIDE_AD_SELECTORS = { 'v.qq.com': ['.mod_vip_popup', '#mask_layer', '.panel-tip-pay', '.vip-cover', '.pay-tips', '.mod_automask', '.pay-dialog', '.vip-info'], 'iqiyi.com': ['.iqp-player-vipmask', '.iqp-player-paymask', '.iqp-player-loginmask', '#playerPopup', '.vip-cover', '.pay-pop', '.qiyi-dialog'], 'iq.com': ['.iqp-player-vipmask', '.iqp-player-paymask', '.iqp-player-loginmask', '#playerPopup', '.vip-cover', '.pay-pop', '.qiyi-dialog'], 'youku.com': ['.advertise-layer', '.youku-advertise-layer', '#youku-advertise', '#player-advertise', '.preloading-layer', '.preplay-layer', '.kui-dashboard', '.kui-layer'], 'mgtv.com': ['.adFixedContain', '.ad-banner', '.player-tip', '.pay-mask', '.mustsee-mask'], 'bilibili.com': ['.bpx-player-video-wrap .bpx-player-video-mask', '.bpx-player-video-wrap .bpx-player-video-pay', '.bpx-player-video-wrap .bpx-player-video-cover'], }; const PLAYER_SELECTORS = [ '#mod_player', '#player-container', '#player', '.container-player', '#bilibili-player', '#mgtv-player-wrap', '#outlayer', '#ykPlayer', '#sohuplayer', '#le_player', '#pptv_playpage_box', '#flashContent', '#ACPlayer', '#video-player', '#xigua-player', '.video-area', '.player-container', '.artplayer-app', '.mod_player', '#tenvideo_player', '.iqp-player-videolayer', '.m-video-player-wrap', '.h5-detail-player', // 补充(参考 88lin 站点容器表,提升各站定位精度) '#areaLeftContainer', '#flashbox', '.player-root', '#le_playbox', '.td-playbox', '#playerWrap', '#vodPlayer', ]; // ─── 状态 ────────────────────────────────────────────────── const State = { activeSource: null, currentApi: null, panelOpen: false, playing: false, overlayEl: null, iframeEl: null, failedSources: new Set(), sourceStats: new Map(), toastTimer: null, autoSwitchEnabled: true, isAutoSwitch: false, lastSwitchTime: 0, loadTimer: null, adCleaner: null, protectionInterval: null, urlWatchInterval: null, lastUrl: '', _mediaKillerStarted: false, _mediaBlocked: false, _originalPlay: null, _mediaObserver: null, _mediaInterval: null, _suppressorHandler: null, _suppressorInterval: null, _shadowScanEnabled: false, _shadowScanInterval: null, _switching: false, manualPicked: false, lastSourceName: null, panelHideTimer: null, lastResolveUrl: '', loadingSource: null, lastSuccessSource: null, statsTimer: null, _btnGuard: null, _btnEnsureTimer: null, _dragging: false, }; // ─── 工具 ────────────────────────────────────────────────── const Utils = { _hiddenAds: new WeakSet(), _statsTimer: null, isVideoPage() { return VIDEO_HOST_RE.test(location.hostname); }, getTargetUrl() { return location.href; }, buildResolveUrl(api) { return api.url + encodeURIComponent(this.getTargetUrl()); }, debounce(fn, wait) { let t = null; return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), wait); }; }, loadStats() { try { const data = GMGetValue('aether_stats', '{}'); const parsed = JSON.parse(data); for (const [k, v] of Object.entries(parsed)) { State.sourceStats.set(k, v); } } catch (e) {} }, saveStatsNow() { const obj = {}; for (const [k, v] of State.sourceStats.entries()) { obj[k] = v; } GMSetValue('aether_stats', JSON.stringify(obj)); }, scheduleStatsSave() { clearTimeout(this._statsTimer); this._statsTimer = setTimeout(() => { this._statsTimer = null; this.saveStatsNow(); }, CONFIG.STAT_SAVE_DEBOUNCE_MS); }, flushStats() { if (this._statsTimer) { clearTimeout(this._statsTimer); this._statsTimer = null; this.saveStatsNow(); } }, recordSuccess(name) { if (!name) return; const stat = State.sourceStats.get(name) || { success: 0, fail: 0 }; stat.success++; State.sourceStats.set(name, stat); this.scheduleStatsSave(); State.failedSources.clear(); }, recordFail(name) { if (!name) return; const stat = State.sourceStats.get(name) || { success: 0, fail: 0 }; stat.fail++; State.sourceStats.set(name, stat); this.scheduleStatsSave(); }, getScore(name) { if (!name) return 0; const stat = State.sourceStats.get(name); if (!stat) return 0; const total = stat.success + stat.fail; return total === 0 ? 0 : stat.success / total; }, sortApis(apis) { // 按历史成功率降序(同分保持原顺序),让更可靠的源排前面 return apis.slice().sort((a, b) => { const sa = this.getScore(a.name); const sb = this.getScore(b.name); if (sb !== sa) return sb - sa; return apis.indexOf(a) - apis.indexOf(b); }); }, findPlayerContainer() { // 通道 1:站点固定选择器(常规结构) for (const sel of PLAYER_SELECTORS) { const el = document.querySelector(sel); if (el && el.offsetHeight > 100) return el; } // 通道 2(语义兜底,全站通用,站点改版也覆盖): // 从页面实际存在的 video 向上溯,取面积最大的容器(播放器宿主) const media = document.querySelector('video'); if (media) { let best = null; let bestArea = 0; let n = media; while (n && n !== document.body) { const r = n.getBoundingClientRect(); const a = r.width * r.height; if (a > bestArea) { bestArea = a; best = n; } n = n.parentElement; } if (best && bestArea > 10000) return best; } // 通道 3(语义兜底):顶层无 video 时(跨域 iframe 播放器),取"最大且可见"的 // 疑似播放器 iframe 的宿主作为播放器区域(腾讯新版等) const iframes = Array.from(document.querySelectorAll('iframe')).filter(f => f.offsetWidth > 300 && f.offsetHeight > 200 && f !== State.iframeEl && !(f.closest && f.closest('.aether-overlay-embedded')) ); if (iframes.length) { iframes.sort((a, b) => (b.offsetWidth * b.offsetHeight) - (a.offsetWidth * a.offsetHeight)); const biggest = iframes[0]; const host = (biggest.closest && ( biggest.closest('[id*="player" i]') || biggest.closest('[class*="player" i]') || biggest.closest('[id*="video" i]') || biggest.closest('[class*="video" i]') )) || biggest.parentElement || biggest; return host; } return null; }, getVideoRect() { const container = this.findPlayerContainer(); if (!container) return null; const rect = container.getBoundingClientRect(); return { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom }; }, // 我们用 overlay 自身的几何兜底(一定存在):容器定位异常时仍保证 // iframe 处置/相交判定有可用基准(腾讯新版播放器区域即 overlay 覆盖区域) _overlayRect() { if (!State.overlayEl || !State.overlayEl.getBoundingClientRect) return null; const r = State.overlayEl.getBoundingClientRect(); if (r.width > 0 && r.height > 0) { return { left: r.left, top: r.top, right: r.right, bottom: r.bottom }; } return null; }, waitForPlayerContainer(callback) { if (this.findPlayerContainer()) { callback(); return; } let waited = 0; const timer = setInterval(() => { waited += CONFIG.PLAYER_WAIT_POLL_MS; if (this.findPlayerContainer()) { clearInterval(timer); callback(); } else if (waited >= CONFIG.PLAYER_WAIT_TIMEOUT_MS) { clearInterval(timer); callback(); } }, CONFIG.PLAYER_WAIT_POLL_MS); }, _hideElement(el) { if (this._hiddenAds.has(el)) return; this._hiddenAds.add(el); el.style.setProperty('display', 'none', 'important'); el.style.setProperty('opacity', '0', 'important'); el.style.setProperty('pointer-events', 'none', 'important'); }, hideAds() { const host = location.hostname; for (const [key, sels] of Object.entries(HIDE_AD_SELECTORS)) { if (host.includes(key)) { sels.forEach(sel => { document.querySelectorAll(sel).forEach(el => this._hideElement(el)); }); break; } } // 仅在播放器容器内做通用蒙层清理(容器已被解析层覆盖),避免误伤整站布局 const container = this.findPlayerContainer(); if (container) { container.querySelectorAll('[class*="vip"],[class*="pay"],[class*="mask"],[class*="popup"]').forEach(el => this._hideElement(el)); } }, // ─── 静音核心(温和版:保留元素,只暂停+清源+静音) ───── // 方案来源:改写自 88lin/video_vip 的 util.stopMedia / mutePageMedia / // blockNativeMediaPlayback / AdSoundSuppressor —— 不删除媒体元素, // 避免官网播放器因元素被移除而陷入异常重试循环(行为与原版 88lin 对齐) _stopMedia(media) { if (!media) return; try { media.pause(); media.autoplay = false; media.loop = false; media.muted = true; media.defaultMuted = true; media.volume = 0; media.playbackRate = 1; media.removeAttribute('autoplay'); media.removeAttribute('src'); media.srcObject = null; media.querySelectorAll('source').forEach((node) => node.remove()); if (media.currentSrc || media.srcObject || media.querySelector('source')) { media.load(); } } catch (e) {} }, _removeNativeMedia(root) { if (!root || !root.querySelectorAll) return; root.querySelectorAll('video, audio').forEach((media) => { try { media.pause(); media.removeAttribute('autoplay'); media.removeAttribute('src'); media.srcObject = null; media.load(); } catch (e) {} }); }, _blockNativeMediaPlayback() { if (State._mediaBlocked || !window.HTMLMediaElement) return; State._mediaBlocked = true; const rawPlay = HTMLMediaElement.prototype.play; State._originalPlay = rawPlay; HTMLMediaElement.prototype.play = function () { Utils._stopMedia(this); return Promise.resolve(); }; document.addEventListener('play', (event) => { if (!State._mediaBlocked) return; if (event.target instanceof HTMLMediaElement) { Utils._stopMedia(event.target); } }, true); document.addEventListener('playing', (event) => { if (!State._mediaBlocked) return; if (event.target instanceof HTMLMediaElement) { Utils._stopMedia(event.target); } }, true); document.addEventListener('volumechange', (event) => { if (!State._mediaBlocked) return; if (event.target instanceof HTMLMediaElement) { Utils._stopMedia(event.target); } }, true); try { HTMLMediaElement.prototype.play.toString = () => rawPlay.toString(); } catch (e) {} }, // 第二道防线(移植自 88lin 的 startAdSoundSuppressor): // 只静音不暂停、不删 src,避免触发官网播放器异常处理 _startAdSoundSuppressor() { if (State._suppressorHandler) return; const silence = (m) => { try { if (!m.muted) m.muted = true; if (m.volume !== 0) m.volume = 0; } catch (e) {} }; const handler = (ev) => { const m = ev.target; if (!m || !m.tagName || !/^(video|audio)$/i.test(m.tagName)) return; silence(m); }; State._suppressorHandler = handler; ['play', 'playing', 'volumechange'].forEach((name) => { document.addEventListener(name, handler, true); }); State._suppressorInterval = setInterval(() => { document.querySelectorAll('video,audio').forEach(silence); document.querySelectorAll('iframe').forEach((f) => { try { const doc = f.contentDocument; if (doc) doc.querySelectorAll('video,audio').forEach(silence); } catch (e) {} }); }, 250); }, _stopAdSoundSuppressor() { if (!State._suppressorHandler) return; ['play', 'playing', 'volumechange'].forEach((name) => { document.removeEventListener(name, State._suppressorHandler, true); }); State._suppressorHandler = null; if (State._suppressorInterval) { clearInterval(State._suppressorInterval); State._suppressorInterval = null; } }, // 官网播放器 iframe 处置(全站通用):跨域 iframe 内的视频无法用 DOM 静音, // 只能置换内容。策略:同源 iframe → 温和静音内部媒体(无闪断); // 跨域 iframe → src 置空 about:blank(保留元素防官网 JS 报错,视频消失即声音消失)。 // 判定(双通道,防止藏匿后 rect=0 漏网——腾讯根因): // A. 播放器容器内的 iframe → 无条件处置(即使被 display:none 藏匿、rect=0); // B. 容器外 iframe → 仅当与播放器区域矩形相交才处置(不误伤登录/支付等弹窗)。 _neutralizeForeignIframes(scope) { const isIframe = scope && scope.tagName === 'IFRAME'; const list = isIframe ? [scope] : Array.from((scope || document).querySelectorAll('iframe')); const vr = State.playing ? (this.getVideoRect() || this._overlayRect()) : null; const pc = State.playing ? this.findPlayerContainer() : null; list.forEach((f) => { if (f === State.iframeEl) return; if (f.closest && f.closest('.aether-overlay-embedded')) return; // 通道 A:播放器容器内(含深层子树)无条件处置 const inContainer = !!(pc && (f === pc || pc.contains(f))); // 通道 B:容器外按视觉位置相交判定 let overlaps = false; if (!inContainer && vr && !isIframe) { const fr = f.getBoundingClientRect(); if (fr.width && fr.height && (fr.left < vr.right && fr.right > vr.left && fr.top < vr.bottom && fr.bottom > vr.top)) { overlaps = true; } } if (!inContainer && !overlaps) return; try { const doc = f.contentDocument; if (doc) { this._removeNativeMedia(doc); return; } } catch (e) {} if (f.src && f.src !== 'about:blank') { try { f.src = 'about:blank'; } catch (e) {} } }); }, // Shadow DOM 递归收集:遍历 open shadowRoot 内的媒体并温和静音, // 返回是否发现媒体(供自适应探测使用)。仅对真正使用 shadow 播放器的站点有成本。 _collectShadowMedia(root) { let hasMedia = false; (root.querySelectorAll('*') || []).forEach((el) => { try { if (el.shadowRoot) { if (this._collectShadowMedia(el.shadowRoot)) hasMedia = true; } } catch (e) {} }); const medias = root.querySelectorAll('video, audio'); if (medias && medias.length) { hasMedia = true; medias.forEach((m) => this._stopMedia(m)); } return hasMedia; }, // 自适应探测(零开销设计):接管后 2s / 5s 各探测一次; // 发现 shadow 媒体 → 启用 1s 低频扫描;未发现 → 永不启用 _detectShadowMedia() { try { if (this._collectShadowMedia(document) && !State._shadowScanEnabled) { State._shadowScanEnabled = true; State._shadowScanInterval = setInterval(() => { this._collectShadowMedia(document); }, 1000); } } catch (e) {} }, startMediaKiller() { if (State._mediaKillerStarted) { this._removeNativeMedia(document); return; } State._mediaKillerStarted = true; this._removeNativeMedia(document); this._startAdSoundSuppressor(); setTimeout(() => { this._blockNativeMediaPlayback(); if (State._mediaInterval) clearInterval(State._mediaInterval); // 主处理交给 MutationObserver;轮询降频至 1s 仅作兜底 State._mediaInterval = setInterval(() => { this._removeNativeMedia(document); document.querySelectorAll('iframe').forEach(f => { try { const doc = f.contentDocument; if (doc) this._removeNativeMedia(doc); } catch (e) {} }); }, CONFIG.MEDIA_KILL_INTERVAL_MS); const target = document.documentElement || document.body; if (target && !State._mediaObserver) { State._mediaObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'attributes' && mutation.target instanceof Element) { if (mutation.target.matches('video, audio')) { Utils._stopMedia(mutation.target); return; } // 官网播放器 iframe 的 src/autoplay 变更(腾讯等)→ 立即处置 if (mutation.target.matches('iframe')) { Utils._neutralizeForeignIframes(mutation.target); return; } } mutation.addedNodes.forEach((node) => { if (!(node instanceof Element)) return; if (node.matches && node.matches('video, audio')) { Utils._stopMedia(node); return; } if (node.matches && node.matches('iframe')) { // 新增的官网播放器 iframe:立即处置(跨域置空/同源静音) Utils._neutralizeForeignIframes(node); return; } Utils._removeNativeMedia(node); Utils._neutralizeForeignIframes(node); }); }); }); State._mediaObserver.observe(target, { childList: true, subtree: true, attributes: true, attributeFilter: ['src', 'autoplay'] }); } }, CONFIG.MEDIA_KILL_BOOT_DELAY_MS); // Shadow DOM 自适应探测(2s / 5s 各一次;发现才启用低频扫描) setTimeout(() => Utils._detectShadowMedia(), 2000); setTimeout(() => Utils._detectShadowMedia(), 5000); }, stopMediaKiller() { if (State._mediaInterval) { clearInterval(State._mediaInterval); State._mediaInterval = null; } if (State._mediaObserver) { try { State._mediaObserver.disconnect(); } catch (e) {} State._mediaObserver = null; } if (State._originalPlay && window.HTMLMediaElement) { try { HTMLMediaElement.prototype.play = State._originalPlay; } catch (e) {} State._mediaBlocked = false; State._originalPlay = null; } this._stopAdSoundSuppressor(); if (State._shadowScanInterval) { clearInterval(State._shadowScanInterval); State._shadowScanInterval = null; } State._shadowScanEnabled = false; State._mediaKillerStarted = false; }, // 播放级清理:仅影响当前播放会话,不影响 URL 监听 / 广告清理等页面级循环 clearPlaybackTimers() { if (State.loadTimer) { clearTimeout(State.loadTimer); State.loadTimer = null; } if (State.protectionInterval) { clearInterval(State.protectionInterval); State.protectionInterval = null; } if (State.toastTimer) { clearTimeout(State.toastTimer); State.toastTimer = null; } this.stopMediaKiller(); }, // 页面级清理:仅在页面重载/脚本停止时调用 clearAllTimers() { this.clearPlaybackTimers(); if (State.adCleaner) { clearInterval(State.adCleaner); State.adCleaner = null; } if (State.urlWatchInterval) { clearInterval(State.urlWatchInterval); State.urlWatchInterval = null; } if (State._btnGuard) { clearInterval(State._btnGuard); State._btnGuard = null; } if (State._btnEnsureTimer) { clearInterval(State._btnEnsureTimer); State._btnEnsureTimer = null; } this.flushStats(); }, }; // ─── UI 系统 ────────────────────────────────────────────────── const UI = { container: null, btn: null, panel: null, toast: null, list: null, autoToggle: null, refreshBtn: null, viewTitle: null, currentApi: null, init() { this.injectStyles(); this.createElements(); this.bindEvents(); this.loadPosition(); Utils.loadStats(); State.autoSwitchEnabled = GMGetValue('aether_auto_switch', true); this.updateToggleUI(); State.lastSourceName = GMGetValue('aether_last_source', null); State.adCleaner = setInterval(() => Utils.hideAds(), CONFIG.AD_CLEAN_INTERVAL_MS); this.startUrlWatcher(); if (GMGetValue('aether_panel_open', false)) { this.openPanel(); } // 悬浮球防丢失:站点 SPA 重渲染清空/替换 body 时自动重新挂载 State._btnGuard = setInterval(() => { const root = this.container; if (root && document.body && root.parentNode !== document.body) { document.body.appendChild(root); } else if (!root || !document.body) { clearInterval(State._btnGuard); State._btnGuard = null; } }, 2000); // 悬浮球持续避让:任何适配站点的播放器区域都不允许停留(滚动/换集/窗口变化后兜底) State._btnEnsureTimer = setInterval(() => this.ensureBtnVisible(), 2000); }, startUrlWatcher() { State.lastUrl = location.href; const debouncedChange = Utils.debounce(() => { if (!State._switching) this.onUrlChanged(); }, CONFIG.URL_CHANGE_DEBOUNCE_MS); State.urlWatchInterval = setInterval(() => { const currentUrl = location.href; if (currentUrl !== State.lastUrl) { State.lastUrl = currentUrl; debouncedChange(); } }, CONFIG.URL_WATCH_INTERVAL_MS); const originalPushState = history.pushState; const originalReplaceState = history.replaceState; const self = this; history.pushState = function (...args) { originalPushState.apply(this, args); debouncedChange(); }; history.replaceState = function (...args) { originalReplaceState.apply(this, args); debouncedChange(); }; window.addEventListener('popstate', () => { debouncedChange(); }); }, onUrlChanged() { const isVideo = Utils.isVideoPage(); if (State.playing && this.currentApi && State.iframeEl && isVideo) { const newUrl = Utils.buildResolveUrl(this.currentApi); if (State.iframeEl.src === newUrl) { return; } State.lastResolveUrl = newUrl; State.iframeEl.src = newUrl; Utils.startMediaKiller(); this.toast('已切换集数', 1500); } }, injectStyles() { GMAddStyle(` :root { --aeth-violet: #1a0b2e; --aeth-purple: #2d1b69; --aeth-lavender: #4a2c8a; --aeth-lilac: #c084fc; --aeth-cyan: #67e8f9; --aeth-white: #f1f5f9; --aeth-glass: rgba(26, 11, 46, 0.78); --aeth-radius: 16px; --aeth-shadow: 0 20px 60px rgba(0, 0, 0, 0.6); --aeth-font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } #aether-root { position: fixed; z-index: 2147483647; font-family: var(--aeth-font); font-weight: 400; letter-spacing: 0.01em; user-select: none; pointer-events: none; } #aether-root * { pointer-events: auto; box-sizing: border-box; } #aether-btn { width: 56px; height: 56px; border-radius: 50%; background: linear-gradient(135deg, var(--aeth-purple), var(--aeth-lavender)); box-shadow: 0 8px 28px rgba(74,44,138,0.5), 0 0 0 2px rgba(192,132,252,0.2); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: transform 0.3s cubic-bezier(0.34,1.56,0.64,1), box-shadow 0.3s ease; color: #fff; font-size: 18px; font-weight: 700; letter-spacing: 0.02em; position: relative; pointer-events: auto; } #aether-btn:hover { transform: scale(1.08); box-shadow: 0 12px 36px rgba(74,44,138,0.7), 0 0 0 4px rgba(192,132,252,0.4); } #aether-btn:active { transform: scale(0.94); } #aether-btn .glow { position: absolute; inset: -4px; border-radius: 50%; background: conic-gradient(from 0deg, var(--aeth-lavender), var(--aeth-cyan), var(--aeth-lavender)); opacity: 0; transition: opacity 0.4s; z-index: -1; filter: blur(8px); animation: aether-spin 6s linear infinite; } #aether-btn:hover .glow { opacity: 0.6; } @keyframes aether-spin { to { transform: rotate(360deg); } } #aether-panel { position: absolute; top: 64px; left: 0; width: 340px; max-height: 70vh; background: var(--aeth-glass); backdrop-filter: blur(24px) saturate(1.6); -webkit-backdrop-filter: blur(24px) saturate(1.6); border: 1px solid rgba(192,132,252,0.2); border-radius: var(--aeth-radius); box-shadow: var(--aeth-shadow); padding: 18px 16px 16px; display: none; flex-direction: column; gap: 12px; color: var(--aeth-white); opacity: 0; transform: translateY(-8px) scale(0.96); transition: opacity 0.25s ease, transform 0.25s ease; pointer-events: auto; overflow: hidden; } #aether-panel.open { display: flex; opacity: 1; transform: translateY(0) scale(1); } .aether-header { display: flex; justify-content: space-between; align-items: center; font-size: 14px; font-weight: 500; color: var(--aeth-lilac); padding-bottom: 8px; border-bottom: 1px solid rgba(192,132,252,0.15); } .aether-header .close { cursor: pointer; font-size: 20px; line-height: 1; opacity: 0.7; transition: opacity 0.2s; background: none; border: none; color: inherit; } .aether-header .close:hover { opacity: 1; } .aether-control-row { display: flex; align-items: center; justify-content: space-between; padding: 6px 10px; background: rgba(255,255,255,0.04); border-radius: 10px; font-size: 13px; color: var(--aeth-white); gap: 12px; } .aether-control-row .left { display: flex; align-items: center; gap: 8px; } .aether-control-row .left label { display: flex; align-items: center; gap: 8px; cursor: pointer; } .aether-control-actions { display: flex; gap: 6px; flex-shrink: 0; } .aether-control-row input[type="checkbox"] { width: 40px; height: 22px; border-radius: 11px; appearance: none; background: rgba(255,255,255,0.2); border: 1px solid rgba(255,255,255,0.1); transition: background-color 0.25s ease, border-color 0.25s ease; position: relative; cursor: pointer; flex-shrink: 0; } .aether-control-row input[type="checkbox"]:checked { background: var(--aeth-lavender); border-color: var(--aeth-lilac); } .aether-control-row input[type="checkbox"]::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; background: #fff; border-radius: 50%; transition: transform 0.25s ease; box-shadow: 0 2px 4px rgba(0,0,0,0.2); } .aether-control-row input[type="checkbox"]:checked::after { transform: translateX(18px); } .aether-refresh-btn { background: rgba(255,255,255,0.08); border: 1px solid rgba(192,132,252,0.2); border-radius: 8px; padding: 4px 12px; font-size: 13px; color: var(--aeth-white); cursor: pointer; transition: background-color 0.15s ease, border-color 0.15s ease, transform 0.15s ease; display: flex; align-items: center; gap: 4px; font-family: inherit; white-space: nowrap; flex-shrink: 0; } .aether-refresh-btn:hover { background: rgba(192,132,252,0.2); border-color: var(--aeth-lilac); transform: scale(1.03); } .aether-refresh-btn:active { transform: scale(0.95); } .aether-refresh-btn .icon { font-size: 16px; line-height: 1; } .aether-list { display: flex; flex-direction: column; gap: 6px; overflow-y: auto; max-height: 40vh; padding-right: 4px; } .aether-list::-webkit-scrollbar { width: 4px; } .aether-list::-webkit-scrollbar-thumb { background: var(--aeth-lilac); border-radius: 4px; } .aether-item { background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.08); border-radius: 10px; padding: 10px 14px; font-size: 13px; font-weight: 500; color: var(--aeth-white); cursor: pointer; transition: background-color 0.15s ease, border-color 0.15s ease, transform 0.15s ease; display: flex; justify-content: space-between; align-items: center; } .aether-item:hover { background: rgba(192,132,252,0.2); border-color: var(--aeth-lilac); transform: translateX(4px); } .aether-item.active { background: linear-gradient(135deg, var(--aeth-purple), var(--aeth-lavender)); border-color: var(--aeth-lilac); box-shadow: 0 4px 12px rgba(74,44,138,0.4); } .aether-item .badge { font-size: 10px; opacity: 0.6; color: var(--aeth-cyan); } .aether-overlay-embedded { position: fixed !important; background: #000; overflow: hidden; z-index: 2147483646 !important; pointer-events: auto; box-shadow: none; } .aether-overlay-embedded iframe { width: 100%; height: 100%; border: none; display: block; pointer-events: auto; } #aether-toast { position: fixed; top: 24px; left: 50%; transform: translateX(-50%) translateY(-20px); background: rgba(26,11,46,0.88); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); color: var(--aeth-lilac); font-size: 14px; font-weight: 500; padding: 10px 24px; border-radius: 30px; border: 1px solid rgba(192,132,252,0.3); box-shadow: 0 10px 30px rgba(0,0,0,0.5); pointer-events: none; opacity: 0; z-index: 2147483647; transition: opacity 0.3s cubic-bezier(0.34,1.56,0.64,1), transform 0.3s cubic-bezier(0.34,1.56,0.64,1); } #aether-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); } #aether-btn:focus-visible, .aether-refresh-btn:focus-visible, .aether-header .close:focus-visible { outline: 2px solid var(--aeth-cyan); outline-offset: 2px; } /* 低端设备(核数≤4):关闭毛玻璃模糊,改为不透明背景,降低 GPU 开销 */ #aether-root.aether-lite #aether-panel { backdrop-filter: none; -webkit-backdrop-filter: none; background: rgba(26,11,46,0.92); } /* 系统"减少动态效果":禁用动画与过渡 */ @media (prefers-reduced-motion: reduce) { #aether-btn, #aether-btn .glow, #aether-panel, .aether-item, .aether-refresh-btn, .aether-header .close, #aether-toast, .aether-control-row input[type="checkbox"], .aether-control-row input[type="checkbox"]::after { transition: none !important; animation: none !important; } } @media (max-width:600px) { #aether-panel { width: calc(100vw - 32px); left: 16px; top: 72px; max-height: 60vh; } .aether-control-row { flex-wrap: wrap; gap: 6px; } .aether-refresh-btn { padding: 2px 10px; font-size: 12px; } } `); }, createElements() { const root = document.createElement('div'); root.id = 'aether-root'; if (navigator.hardwareConcurrency && navigator.hardwareConcurrency <= 4) { root.classList.add('aether-lite'); } document.body.appendChild(root); const btn = document.createElement('div'); btn.id = 'aether-btn'; btn.setAttribute('role', 'button'); btn.setAttribute('tabindex', '0'); btn.setAttribute('aria-label', '打开 Æther Player 解析面板'); btn.title = 'Æther Player'; btn.innerHTML = `Æ
`; root.appendChild(btn); this.btn = btn; const panel = document.createElement('div'); panel.id = 'aether-panel'; panel.setAttribute('role', 'dialog'); panel.setAttribute('aria-label', '解析源面板'); panel.innerHTML = `
解析源
`; root.appendChild(panel); this.panel = panel; this.list = document.getElementById('aether-list'); this.autoToggle = document.getElementById('aether-auto-toggle'); this.refreshBtn = document.getElementById('aether-refresh-btn'); this.viewTitle = document.getElementById('view-title'); const toast = document.createElement('div'); toast.id = 'aether-toast'; document.body.appendChild(toast); this.toast = toast; this.container = root; }, bindEvents() { const self = this; const btn = this.btn; let dragActive = false; let dragMoved = false; let startX = 0, startY = 0; let origLeft = 0, origTop = 0; let moveHandler = null, upHandler = null; let dragVideoRect = null; const btnSize = CONFIG.BTN_SIZE; this.refreshBtn.addEventListener('click', (e) => { e.stopPropagation(); this.refreshPlayer(); }); const onDragMove = (e) => { if (!dragActive) return; const dx = e.clientX - startX; const dy = e.clientY - startY; if (Math.abs(dx) > 2 || Math.abs(dy) > 2) { dragMoved = true; } let nx = origLeft + dx; let ny = origTop + dy; const videoRect = dragVideoRect; if (videoRect) { const cx = nx + btnSize / 2; const cy = ny + btnSize / 2; if (cx > videoRect.left && cx < videoRect.right && cy > videoRect.top && cy < videoRect.bottom) { nx = videoRect.right + 10; ny = Math.max(CONFIG.CLAMP_MARGIN, Math.min(window.innerHeight - btnSize - CONFIG.CLAMP_MARGIN, ny)); } } nx = Math.max(CONFIG.CLAMP_MARGIN, Math.min(window.innerWidth - btnSize - CONFIG.CLAMP_MARGIN, nx)); ny = Math.max(CONFIG.CLAMP_MARGIN, Math.min(window.innerHeight - btnSize - CONFIG.CLAMP_MARGIN, ny)); self.container.style.left = nx + 'px'; self.container.style.top = ny + 'px'; }; const onDragUp = (e) => { if (!dragActive) return; dragActive = false; State._dragging = false; if (moveHandler) { document.removeEventListener('mousemove', moveHandler); moveHandler = null; } if (upHandler) { document.removeEventListener('mouseup', upHandler); upHandler = null; } if (self.overlay) { self.overlay.style.pointerEvents = 'auto'; if (self.iframe) self.iframe.style.pointerEvents = 'auto'; } if (!dragMoved) { self.handleBtnClick(); } else { const rect = self.container.getBoundingClientRect(); GMSetValue('aether_pos', { left: rect.left, top: rect.top }); } dragMoved = false; window.removeEventListener('blur', onBlur); }; const onBlur = () => { if (dragActive) { onDragUp(null); } }; btn.addEventListener('mousedown', (e) => { if (e.button !== 0) return; if (moveHandler) { document.removeEventListener('mousemove', moveHandler); } if (upHandler) { document.removeEventListener('mouseup', upHandler); } const rect = self.container.getBoundingClientRect(); dragActive = true; State._dragging = true; dragMoved = false; startX = e.clientX; startY = e.clientY; origLeft = rect.left; origTop = rect.top; dragVideoRect = Utils.getVideoRect(); if (self.overlay) { self.overlay.style.pointerEvents = 'none'; if (self.iframe) self.iframe.style.pointerEvents = 'none'; } moveHandler = onDragMove; upHandler = onDragUp; document.addEventListener('mousemove', moveHandler); document.addEventListener('mouseup', upHandler); window.addEventListener('blur', onBlur); e.preventDefault(); }); btn.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.handleBtnClick(); } }); document.getElementById('aether-panel-close').addEventListener('click', () => this.closePanel()); this.autoToggle.addEventListener('change', (e) => { State.autoSwitchEnabled = e.target.checked; GMSetValue('aether_auto_switch', State.autoSwitchEnabled); this.updateToggleUI(); this.toast(State.autoSwitchEnabled ? '自动解析已开启' : '自动解析已关闭', 1500); }); this.list.addEventListener('click', (e) => { const item = e.target.closest('.aether-item'); if (!item) return; const name = item.dataset.name; if (name) { const api = APIS.find(a => a.name === name); if (api) this.playSource(api, true); } }); const onGlobalKeydown = (e) => { if (e.key === 'Escape' && State.panelOpen) { this.closePanel(); } }; window.addEventListener('keydown', onGlobalKeydown, true); document.addEventListener('keydown', onGlobalKeydown, true); GMRegisterMenuCommand('Æther Player — 切换面板', () => { if (this.panel.classList.contains('open')) this.closePanel(); else this.openPanel(); }); }, refreshPlayer() { if (State.iframeEl && State.iframeEl.src) { const oldSrc = State.iframeEl.src; State.iframeEl.src = ''; setTimeout(() => { State.iframeEl.src = oldSrc; Utils.startMediaKiller(); this.toast('刷新播放器', 1000); }, 50); } else { this.toast('没有正在播放的视频', 1500); } }, // 悬浮球持续避让(通用,适配所有站点):球心落在播放器区域内时, // 依次尝试右侧 → 左侧 → 下方 → 上方 弹开,保证悬浮球任何情况下可见可点 ensureBtnVisible() { if (State._dragging) return; const vr = Utils.getVideoRect(); if (!vr) return; const r = this.container.getBoundingClientRect(); const cx = r.left + r.width / 2; const cy = r.top + r.height / 2; if (!(cx > vr.left && cx < vr.right && cy > vr.top && cy < vr.bottom)) return; const btnSize = CONFIG.BTN_SIZE; const m = CONFIG.CLAMP_MARGIN; const W = window.innerWidth, H = window.innerHeight; const candidates = []; if (vr.right + btnSize + 10 + m <= W) candidates.push([vr.right + 10, Math.max(m, Math.min(H - btnSize - m, cy - btnSize / 2))]); if (vr.left - btnSize - 10 - m >= m) candidates.push([vr.left - btnSize - 10, Math.max(m, Math.min(H - btnSize - m, cy - btnSize / 2))]); if (vr.bottom + btnSize + 10 + m <= H) candidates.push([Math.max(m, Math.min(W - btnSize - m, cx - btnSize / 2)), vr.bottom + 10]); if (vr.top - btnSize - 10 - m >= m) candidates.push([Math.max(m, Math.min(W - btnSize - m, cx - btnSize / 2)), vr.top - btnSize - 10]); candidates.push([m, m]); // 终极兜底:左上角 const [nx, ny] = candidates[0]; this.container.style.left = nx + 'px'; this.container.style.top = ny + 'px'; }, handleBtnClick() { if (this.panel.classList.contains('open')) { this.closePanel(); } else { this.openPanel(); } }, loadPosition() { const pos = GMGetValue('aether_pos', { left: 20, top: 120 }) || {}; const left = typeof pos.left === 'number' ? pos.left : 20; const top = typeof pos.top === 'number' ? pos.top : 120; this.container.style.left = Math.max(CONFIG.CLAMP_MARGIN, Math.min(window.innerWidth - CONFIG.BTN_SIZE - CONFIG.CLAMP_MARGIN, left)) + 'px'; this.container.style.top = Math.max(CONFIG.CLAMP_MARGIN, Math.min(window.innerHeight - CONFIG.BTN_SIZE - CONFIG.CLAMP_MARGIN, top)) + 'px'; // 加载位置立即避让视频区域(若已落入播放器区域则弹出) this.ensureBtnVisible(); }, updateToggleUI() { if (this.autoToggle) { this.autoToggle.checked = State.autoSwitchEnabled; } }, openPanel() { clearTimeout(State.panelHideTimer); State.panelHideTimer = null; this.renderSources(); this.panel.classList.add('open'); this.panel.style.display = 'flex'; State.panelOpen = true; GMSetValue('aether_panel_open', true); if (this.viewTitle) { this.viewTitle.textContent = State.activeSource ? `解析源 · ${State.activeSource}` : '解析源'; } }, closePanel() { this.panel.classList.remove('open'); clearTimeout(State.panelHideTimer); State.panelHideTimer = setTimeout(() => { this.panel.style.display = 'none'; }, CONFIG.PANEL_FADE_MS); State.panelOpen = false; GMSetValue('aether_panel_open', false); }, renderSources() { const sorted = Utils.sortApis(APIS); const frag = document.createDocumentFragment(); sorted.forEach(api => { const stat = State.sourceStats.get(api.name) || { success: 0, fail: 0 }; const score = Math.round(Utils.getScore(api.name) * 100); let badge; if (State.loadingSource === api.name) { badge = '⏳'; } else if (State.failedSources.has(api.name)) { badge = '✗'; } else if (State.lastSuccessSource === api.name) { badge = '✓'; } else { badge = score + '%'; } const div = document.createElement('div'); div.className = 'aether-item' + (api.name === State.activeSource ? ' active' : ''); div.dataset.name = api.name; div.title = `${api.name}\n${api.url}\n成功率 ${score}% · 成功 ${stat.success} 次 / 失败 ${stat.fail} 次`; div.innerHTML = `${api.name}${badge}`; frag.appendChild(div); }); this.list.appendChild(frag); }, // ─── 核心播放 ────────────────────────────────────────── _createOverlay(url) { const overlay = document.createElement('div'); overlay.className = 'aether-overlay-embedded'; overlay.innerHTML = ` `; return overlay; }, _armProtection(overlay, container) { // 位置同步:overlay 挂在 body 上(z-index 最高),实时跟随官网播放器区域, // 官网播放器无论重建在哪个容器/层级都不可能盖住我们 const sync = () => { const c = Utils.findPlayerContainer(); if (!c) return; const r = c.getBoundingClientRect(); const h = r.height > 50 ? r.height : Math.round(r.width * 9 / 16); overlay.style.left = r.left + 'px'; overlay.style.top = r.top + 'px'; overlay.style.width = r.width + 'px'; overlay.style.height = h + 'px'; }; sync(); window.addEventListener('resize', sync); window.addEventListener('scroll', sync, true); let rounds = 0; State.protectionInterval = setInterval(() => { if (!State.playing) { clearInterval(State.protectionInterval); State.protectionInterval = null; window.removeEventListener('resize', sync); window.removeEventListener('scroll', sync, true); return; } sync(); rounds++; // ① 先处置官网播放器 iframe(容器内无条件:跨域置空/同源静音)—— // 必须在藏匿之前/以容器判定执行,防止 display:none 后 rect=0 跳过(腾讯根因) Utils._removeNativeMedia(document); Utils._neutralizeForeignIframes(document); // ② 再"藏匿"官网播放器:保留元素(避免官网 JS 因节点被移除而反复重建/报错), // 用 display:none 压住视觉,配合温和静音彻底不出声 if (container && container.parentNode) { Array.from(container.children).forEach(child => { if (child !== overlay && (!child.classList || !child.classList.contains('aether-overlay-embedded'))) { child.style.setProperty('display', 'none', 'important'); } }); } // ③ 硬兜底(低频,每 5s):温和静音后仍在播放的官网媒体 → 物理移除 // (最初版本的同款手段;官网 JS 重建后由上述①②继续接管) if (rounds % 10 === 1) { document.querySelectorAll('video,audio').forEach(m => { try { if (!m.paused && m.currentSrc && m.parentNode) { m.parentNode.removeChild(m); } } catch (e) {} }); } if (overlay.style.zIndex !== '2147483646') { overlay.style.setProperty('z-index', '2147483646', 'important'); } }, CONFIG.PROTECTION_INTERVAL_MS); }, _armLoadTimeout() { State.loadTimer = setTimeout(() => { State.loadTimer = null; if (!State.playing) return; State.loadingSource = null; this.toast('加载超时,自动切换...', 2000); this.autoSwitch(); }, CONFIG.LOAD_TIMEOUT_MS); }, playSource(api, manual = false) { Utils.clearPlaybackTimers(); document.querySelectorAll('.aether-overlay-embedded').forEach(el => el.remove()); if (State.playing) { this.closePlayerImmediate(); } Utils.startMediaKiller(); GMSetValue('aether_last_source', api.name); State.lastSourceName = api.name; const url = Utils.buildResolveUrl(api); State.lastResolveUrl = url; State.activeSource = api.name; this.currentApi = api; State.loadingSource = api.name; // 仅手动选择时才清空失败记录(自动切换需按会话去重,避免 A↔B 死循环) if (manual) State.failedSources.clear(); this.closePanel(); State.manualPicked = manual || State.manualPicked; const container = Utils.findPlayerContainer(); if (!container) { State.loadingSource = null; this.toast('未找到播放器区域,请刷新页面', 3000); return; } Utils._removeNativeMedia(container); const containerStyle = window.getComputedStyle(container); if (containerStyle.position === 'static') { container.style.position = 'relative'; } const overlay = this._createOverlay(url); const iframeEl = overlay.querySelector('iframe'); iframeEl.addEventListener('load', () => { if (State.activeSource) { State.lastSuccessSource = State.activeSource; State.loadingSource = null; Utils.recordSuccess(State.activeSource); } if (State.loadTimer) { clearTimeout(State.loadTimer); State.loadTimer = null; } Utils.startMediaKiller(); this.toast('播放加载成功', 1500); }); iframeEl.addEventListener('error', () => { State.loadingSource = null; this.toast('加载失败,自动切换', 2000); overlay.remove(); State.playing = false; this.autoSwitch(); }); // body 级挂载:z-index 最高 + 位置跟随官网播放器区域(_armProtection 同步), // 官网播放器层级再高也无法遮挡我们 document.body.appendChild(overlay); State.overlayEl = overlay; State.iframeEl = iframeEl; State.playing = true; this._armProtection(overlay, container); this._armLoadTimeout(); Utils.hideAds(); this.toast(`正在加载 ${api.name}...`, 3000); }, closePlayerImmediate() { document.querySelectorAll('.aether-overlay-embedded').forEach(el => el.remove()); State.overlayEl = null; State.iframeEl = null; State.playing = false; State.activeSource = null; State.currentApi = null; State.lastResolveUrl = ''; State.loadingSource = null; if (State.loadTimer) { clearTimeout(State.loadTimer); State.loadTimer = null; } if (State.protectionInterval) { clearInterval(State.protectionInterval); State.protectionInterval = null; } Utils.stopMediaKiller(); }, autoSwitch() { if (!State.autoSwitchEnabled) { this.toast('自动解析已关闭,请手动选择源', 2000); return; } if (State.isAutoSwitch) return; if (State.manualPicked) { this.toast('您已手动选择解析源,自动切换已停用', 2000); return; } const now = Date.now(); if (now - State.lastSwitchTime < CONFIG.SWITCH_SRC_GUARD_MS) return; State.lastSwitchTime = now; State.isAutoSwitch = true; if (State.activeSource) { State.failedSources.add(State.activeSource); Utils.recordFail(State.activeSource); } const sorted = Utils.sortApis(APIS); for (const api of sorted) { if (!State.failedSources.has(api.name)) { this.playSource(api, false); State.isAutoSwitch = false; this.toast(`自动切换至 ${api.name}`, 2000); return; } } this.toast('所有源均失败,请刷新重试', 3000); State.isAutoSwitch = false; }, toast(msg, duration = 2000) { if (duration === 0) duration = 9999; this.toast.textContent = msg; this.toast.classList.add('show'); clearTimeout(State.toastTimer); State.toastTimer = setTimeout(() => { this.toast.classList.remove('show'); }, duration); }, }; // ─── 启动 ────────────────────────────────────────────────── function bootstrap() { try { if (!Utils.isVideoPage()) { console.log('[Æther Player] 当前不是受支持的视频站,悬浮球按设计不显示。支持:腾讯/爱奇艺/优酷/B站/芒果/搜狐/乐视/PPTV/1905/AcFun'); return; } UI.init(); console.log('[Æther Player] v5.15.10 初始化完成,悬浮球已挂载'); setTimeout(() => { const lastSourceName = State.lastSourceName; let targetApi = lastSourceName ? APIS.find(a => a.name === lastSourceName) : null; if (!targetApi && State.autoSwitchEnabled) { targetApi = APIS[0]; } if (!targetApi) { UI.toast('自动解析已关闭,点击Æ按钮手动选择', 3000); return; } // 等待播放器容器出现(慢速/懒加载页面不再误报"未找到播放器区域") Utils.waitForPlayerContainer(() => { if (Utils.isVideoPage()) { UI.playSource(targetApi); } }); }, CONFIG.AUTO_PLAY_DELAY_MS); } catch (err) { console.warn('[Æther Player] 初始化异常:', err); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', bootstrap); } else { bootstrap(); } })();