// ==UserScript== // @name iYF Enhanced // @name:zh-CN 爱壹帆增强 // @namespace http://tampermonkey.net/ // @version 1.1 // @description 解锁网页端 4K 画质 + 网页全屏 + 画中画 + 去广告 // @author XHXIAIEIN // @match *://*.aiyifan.tv/* // @match *://*.iyf.tv/* // @match *://*.yfsp.tv/* // @match *://*.yifan.tv/* // @match *://*.wyav.tv/* // @icon https://www.google.com/s2/favicons?sz=32&domain=iyf.tv // @license MIT // @grant GM_addStyle // @grant GM_registerMenuCommand // @run-at document-start // @downloadURL https://update.greasyfork.icu/scripts/572733/iYF%20Enhanced.user.js // @updateURL https://update.greasyfork.icu/scripts/572733/iYF%20Enhanced.meta.js // ==/UserScript== (function () { 'use strict'; // ═════════════════════════════════════════════ // §1 4K 画质解锁 // ═════════════════════════════════════════════ // // 绕过 Angular 质量限制,通过 filter hook 显示 4K 选项, // 点击时拦截 Angular 事件,从 ngContext 取 HLS URL 后 // 直接用 HLS.js 加载 4K 流(3840x1608) const pageScript = document.createElement('script'); pageScript.textContent = `(function(){ var _origFilter = Array.prototype.filter; // ── filter hook: 让 4K 选项显示在质量选择器 UI 中 ── Array.prototype.filter = function(fn, thisArg) { if (this.length > 0 && this[0] && typeof this[0].bitrate === 'number' && 'path' in this[0]) { var has4K = false; for (var j = 0; j < this.length; j++) { if (this[j].bitrate > 1080 && this[j].path) { has4K = true; break; } } if (has4K) { try { if (!fn.call(thisArg, { bitrate: 2160, path: '/t' }, 0, [{ bitrate: 2160, path: '/t' }])) { return _origFilter.call(this, function(item) { return !!item.path; }); } } catch(e) {} } } return _origFilter.call(this, fn, thisArg); }; // ── 移除 "(客户端)" 标签 ── var cleanObserver = new MutationObserver(function() { document.querySelectorAll('.bitrate-text').forEach(function(el) { if (el.textContent.indexOf('客户端') > -1) el.remove(); }); }); if (document.body) cleanObserver.observe(document.body, { childList: true, subtree: true }); else document.addEventListener('DOMContentLoaded', function() { cleanObserver.observe(document.body, { childList: true, subtree: true }); }); // ── 4K 点击劫持:直接用 HLS.js 加载 ── function getVisibleVideo() { var videos = document.querySelectorAll('vg-player video'); for (var i = 0; i < videos.length; i++) { if (videos[i].style.display !== 'none' && videos[i].offsetWidth > 100) return videos[i]; } return null; } function getBitrateData() { var qs = document.querySelector('vg-quality-selector'); if (!qs || !qs.__ngContext__) return null; var ctx = qs.__ngContext__; for (var i = 0; i < Math.min(ctx.length, 60); i++) { var item = ctx[i]; if (item && typeof item === 'object' && item.bitrates && Array.isArray(item.bitrates)) { return item; } } return null; } function findOldHls() { var vgPlayer = document.querySelector('vg-player'); if (!vgPlayer || !vgPlayer.__ngContext__) return null; var ctx = vgPlayer.__ngContext__; for (var i = 0; i < ctx.length; i++) { if (ctx[i] && ctx[i].hls && typeof ctx[i].hls.destroy === 'function') { return ctx[i].hls; } } return null; } function switchToQuality(bitrate) { var comp = getBitrateData(); if (!comp) return false; var target = null; for (var i = 0; i < comp.bitrates.length; i++) { if (comp.bitrates[i].bitrate === bitrate) { target = comp.bitrates[i]; break; } } if (!target || !target.path || !target.path.result) return false; if (typeof Hls === 'undefined') return false; var video = getVisibleVideo(); if (!video) return false; var currentTime = video.currentTime; var wasPlaying = !video.paused; var url = target.path.result; var oldHls = window.__iyf_hls || findOldHls(); if (oldHls) { try { oldHls.destroy(); } catch(e) {} } window.__iyf_hls = null; requestAnimationFrame(function() { var hls = new Hls(); hls.loadSource(url); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, function() { video.currentTime = currentTime; if (wasPlaying) video.play().catch(function(){}); var label = document.querySelector('.vg-quality-selector-label'); if (label) label.textContent = ' ' + (bitrate > 1440 ? '4K' : bitrate + 'P') + ' '; document.querySelectorAll('vg-quality-selector .item').forEach(function(el) { el.classList.remove('active'); var text = el.textContent || ''; if ((bitrate > 1440 && text.indexOf('4K') > -1) || (bitrate <= 1440 && text.indexOf(bitrate + '') > -1)) { el.classList.add('active'); } }); }); hls.on(Hls.Events.ERROR, function(event, data) { if (data.fatal) hls.destroy(); }); window.__iyf_hls = hls; }); return true; } window.__iyf_switchQuality = switchToQuality; window.__iyf_getBitrates = function() { var comp = getBitrateData(); return comp ? comp.bitrates.map(function(b) { return { bitrate: b.bitrate, hasPath: !!b.path, isVIP: b.isVIP }; }) : []; }; // ── 捕获 4K/2K 选项的点击,阻止 Angular 处理 ── document.addEventListener('click', function(e) { var target = e.target; var item = null; for (var d = 0; target && d < 5; d++, target = target.parentElement) { if (target.classList && target.classList.contains('item') && target.closest && target.closest('vg-quality-selector')) { item = target; break; } } if (!item) return; var text = item.textContent || ''; var is4K = text.indexOf('4K') > -1; var is2K = /2[Kk]|1440/.test(text); if (is4K || is2K) { e.stopImmediatePropagation(); e.preventDefault(); switchToQuality(is4K ? 2160 : 1440); } }, true); // ── 自动选择最高画质 ── function autoSelectBest() { var comp = getBitrateData(); if (!comp || !comp.bitrates || comp.bitrates.length === 0) return false; var best = null; for (var i = 0; i < comp.bitrates.length; i++) { var b = comp.bitrates[i]; if (b.path && (!best || b.bitrate > best.bitrate)) best = b; } if (!best) return false; // 检查当前是否已经是最高画质 var label = document.querySelector('.vg-quality-selector-label'); var current = label ? label.textContent.trim() : ''; var bestLabel = best.bitrate > 1440 ? '4K' : best.bitrate + 'P'; if (current.indexOf(bestLabel) > -1) return true; return switchToQuality(best.bitrate); } var _autoTries = 0; var _autoTimer = setInterval(function() { _autoTries++; if (autoSelectBest() || _autoTries > 30) clearInterval(_autoTimer); }, 2000); })();`; (document.head || document.documentElement).appendChild(pageScript); pageScript.remove(); // ═════════════════════════════════════════════ // §2 拦截 "下载客户端" 弹窗(兜底) // ═════════════════════════════════════════════ function blockDownloadDialog() { GM_addStyle(` .cdk-overlay-pane:has(app-ask-app-download-dialog) { display: none !important; } .cdk-global-overlay-wrapper:has(app-ask-app-download-dialog) { display: none !important; } `); const startObserver = () => { new MutationObserver(() => { document.querySelectorAll('.cdk-overlay-pane').forEach(pane => { if (pane.querySelector('app-ask-app-download-dialog')) { const backdrop = pane.parentElement?.previousElementSibling; if (backdrop?.classList?.contains('cdk-overlay-backdrop')) backdrop.click(); pane.style.display = 'none'; } }); }).observe(document.body, { childList: true, subtree: true, }); }; if (document.body) startObserver(); else document.addEventListener('DOMContentLoaded', startObserver); } // ═════════════════════════════════════════════ // §3 样式 // ═════════════════════════════════════════════ const ICONS = { expand: ``, compress: ``, pip: ``, }; const STYLES = ` * { scrollbar-width: thin; scrollbar-color: transparent transparent; } *:hover { scrollbar-color: rgba(255,255,255,0.2) transparent; } ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0); border-radius: 3px; } *:hover::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.18); } ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.35); } ::-webkit-scrollbar-corner { background: transparent; } html.iyf-web-fs, html.iyf-web-fs body { overflow: hidden !important; margin: 0 !important; padding: 0 !important; } .web-fullscreen { position: fixed !important; inset: 0 !important; width: 100% !important; height: 100% !important; margin: 0 !important; padding: 0 !important; border: none !important; z-index: 999999 !important; background: #000 !important; box-sizing: border-box !important; } .web-fullscreen video { width: 100% !important; height: 100% !important; object-fit: contain !important; } .web-fullscreen, .web-fullscreen * { scrollbar-width: none !important; } .web-fullscreen::-webkit-scrollbar, .web-fullscreen *::-webkit-scrollbar { display: none !important; } /* 小窗高度时隐藏侧边功能面板 */ @media (max-height: 950px) { app-sticky-block { display: none !important; } } /* 去广告 */ vg-pause-f .vg-vvk-p { display: none !important; } .dabf { display: none !important; } .ps.pggf { display: none !important; } /* 播放器宽度自适应(仅非悬浮模式) */ .video-player:not(.float-player) { max-width: 100% !important; flex: 1 !important; height: auto !important; } .video-player:not(.float-player) .aa-videoplayer-wrap { height: auto !important; } .video-player:not(.float-player) .video-container:not(.small) { width: 100% !important; height: auto !important; } .video-player:not(.float-player) .video-box { width: 100% !important; aspect-ratio: 16/9 !important; } /* 清除容器固定高度 */ .playPageTop, .main, .video-module, .v-page-content, .d-flex.w-100.justify-content-center { height: auto !important; min-height: 0 !important; } /* VIP 状态下隐藏冗余元素 */ .iyf-is-vip app-dn-user-menu-item:has(.iconVIP), .iyf-is-vip app-daily-sign-in-button, .iyf-is-vip .uploadtable { display: none !important; } .web-fs-btn, .pip-btn { cursor: pointer; padding: 0 8px; color: #fff; display: flex; align-items: center; user-select: none; } .web-fs-btn:hover, .pip-btn:hover { color: #00a1d6; } /* 画中画遮罩 */ .iyf-pip-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); backdrop-filter: blur(12px); z-index: 99999; display: flex; align-items: center; justify-content: center; } .iyf-pip-text { color: rgba(255,255,255,0.6); font-size: 18px; letter-spacing: 2px; } `; // ═════════════════════════════════════════════ // §4 画中画 // ═════════════════════════════════════════════ function getVisibleVideo() { const videos = document.querySelectorAll('vg-player video'); for (const v of videos) { if (v.style.display !== 'none' && v.offsetWidth > 100) return v; } return null; } async function togglePiP() { if (document.pictureInPictureElement) { await document.exitPictureInPicture(); return; } const video = getVisibleVideo(); if (!video) return; video.disablePictureInPicture = false; if (video.readyState < 1) { try { await new Promise((resolve, reject) => { video.addEventListener('loadedmetadata', resolve, { once: true }); video.addEventListener('canplay', resolve, { once: true }); setTimeout(() => reject(new Error('timeout')), 8000); }); } catch (e) { return; } } try { await video.requestPictureInPicture(); const player = document.querySelector('vg-player'); if (player) { const overlay = document.createElement('div'); overlay.className = 'iyf-pip-overlay'; overlay.innerHTML = '
画中画播放中
'; player.style.position = 'relative'; player.appendChild(overlay); } video.addEventListener( 'leavepictureinpicture', () => { const ov = document.querySelector('.iyf-pip-overlay'); if (ov) ov.remove(); }, { once: true } ); } catch (e) {} } // ═════════════════════════════════════════════ // §5 播放器控件 // ═════════════════════════════════════════════ function initPlayerControls() { const player = document.querySelector('vg-player'); const fullscreenBtn = document.querySelector('vg-fullscreen'); if (!player || !fullscreenBtn || document.querySelector('.web-fs-btn')) return; const fsBtn = document.createElement('div'); fsBtn.className = 'control-item web-fs-btn'; fsBtn.title = '网页全屏'; fsBtn.innerHTML = ICONS.expand; fsBtn.addEventListener('click', () => { player.classList.toggle('web-fullscreen'); document.documentElement.classList.toggle('iyf-web-fs', player.classList.contains('web-fullscreen')); fsBtn.innerHTML = player.classList.contains('web-fullscreen') ? ICONS.compress : ICONS.expand; }); fullscreenBtn.parentNode.insertBefore(fsBtn, fullscreenBtn); if (document.pictureInPictureEnabled) { const video = getVisibleVideo(); if (video) video.disablePictureInPicture = false; const pipBtn = document.createElement('div'); pipBtn.className = 'control-item pip-btn'; pipBtn.title = '画中画'; pipBtn.innerHTML = ICONS.pip; pipBtn.addEventListener('click', togglePiP); fullscreenBtn.parentNode.insertBefore(pipBtn, fsBtn); } document.addEventListener('keydown', e => { if (e.key === 'Escape' && player.classList.contains('web-fullscreen')) { player.classList.remove('web-fullscreen'); document.documentElement.classList.remove('iyf-web-fs'); fsBtn.innerHTML = ICONS.expand; } }); } // ═════════════════════════════════════════════ // §6 响应式视口 // ═════════════════════════════════════════════ function fixViewport() { const DESIRED = 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=yes'; let meta = document.querySelector('meta[name="viewport"]'); if (!meta) { meta = document.createElement('meta'); meta.name = 'viewport'; (document.head || document.documentElement).appendChild(meta); } if (meta.content !== DESIRED) meta.content = DESIRED; // 清除 Angular 可能设置的 body zoom if (document.body && document.body.style.zoom) { document.body.style.zoom = ''; } } // ═════════════════════════════════════════════ // §7 启动 // ═════════════════════════════════════════════ fixViewport(); blockDownloadDialog(); function detectVIP() { if (document.querySelector('.premium.vip-icon-box')) { document.documentElement.classList.add('iyf-is-vip'); return true; } return false; } function onReady() { GM_addStyle(STYLES); GM_registerMenuCommand('画中画播放', togglePiP); // 清除容器固定高度(Angular 通过 inline style 设置) let heightsCleared = false; function clearFixedHeights() { if (heightsCleared) return; for (const sel of ['.playPageTop', '.main', '.video-module', '.v-page-content']) { const el = document.querySelector(sel); if (el) { el.style.minHeight = '0'; el.style.height = 'auto'; } } // 只要 playPageTop 存在就标记完成,不再重复执行 if (document.querySelector('.playPageTop')) heightsCleared = true; } // 统一 observer:VIP 检测 + 播放器注入 + 容器高度修正 let vipDone = detectVIP(); let playerDone = false; const observer = new MutationObserver(() => { if (!vipDone) vipDone = detectVIP(); if (!heightsCleared) clearFixedHeights(); if (!playerDone && document.querySelector('vg-player')) { initPlayerControls(); playerDone = true; } // 全部完成后 disconnect if (vipDone && heightsCleared && playerDone) observer.disconnect(); }); observer.observe(document.body, { childList: true, subtree: true }); // 兜底:2 秒后尝试一次 setTimeout(() => { clearFixedHeights(); initPlayerControls(); }, 2000); // 视口守卫:仅监听 ,只在 viewport 被改时触发 new MutationObserver(muts => { for (const m of muts) { if (m.type === 'attributes' && m.target.matches?.('meta[name="viewport"]')) { return fixViewport(); } for (const node of m.addedNodes) { if (node.nodeName === 'META' && node.name === 'viewport') { return fixViewport(); } } } }).observe(document.head || document.documentElement, { childList: true, attributes: true, attributeFilter: ['content'] }); } if (document.body) onReady(); else document.addEventListener('DOMContentLoaded', onReady); })();