解锁网页端 4K 画质 + 网页全屏 + 画中画 + 去广告
// ==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
// ==/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: `<svg viewBox="0 0 24 24" width="22" height="22" fill="currentColor"><path d="M3 3h6v2H5v4H3V3zm12 0h6v6h-2V5h-4V3zM3 15h2v4h4v2H3v-6zm16 0h2v6h-6v-2h4v-4z"/></svg>`,
compress: `<svg viewBox="0 0 24 24" width="22" height="22" fill="currentColor"><path d="M9 9H3V7h4V3h2v6zm6-6h2v4h4v2h-6V3zm-6 12v6H7v-4H3v-2h6zm6 0h6v2h-4v4h-2v-6z"/></svg>`,
pip: `<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z"/></svg>`,
};
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 = '<div class="iyf-pip-text">画中画播放中</div>';
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);
// 视口守卫:仅监听 <head>,只在 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);
})();