HTML5视频播放工具

三大功能 。启用HTML5播放;万能网页全屏;添加快捷键:快进、快退、暂停/播放、音量、下一集、切换(网页)全屏、上下帧、播放速度。支持视频站点:油管、TED、优.土、QQ、B站、西瓜视频、爱奇艺、A站、PPTV、芒果TV、咪咕视频、新浪、微博、网易[娱乐、云课堂、新闻]、搜狐、风行、百度云视频等;直播:斗鱼、YY、虎牙、龙珠、战旗。可增加自定义站点

当前为 2021-12-27 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name       HTML5视频播放工具
// @description 三大功能 。启用HTML5播放;万能网页全屏;添加快捷键:快进、快退、暂停/播放、音量、下一集、切换(网页)全屏、上下帧、播放速度。支持视频站点:油管、TED、优.土、QQ、B站、西瓜视频、爱奇艺、A站、PPTV、芒果TV、咪咕视频、新浪、微博、网易[娱乐、云课堂、新闻]、搜狐、风行、百度云视频等;直播:斗鱼、YY、虎牙、龙珠、战旗。可增加自定义站点
// @homepage   https://bbs.kafan.cn/thread-2093014-1-1.html
// @include    https://*.qq.com/*
// @exclude    https://user.qzone.qq.com/*
// @include    https://www.weiyun.com/video_*
// @include    https://v.youku.com/v_show/id_*
// @include    https://vku.youku.com/live/*
// @include    https://video.tudou.com/v/*
// @include    https://www.iqiyi.com/*
// @include    https://live.bilibili.com/*
// @include    https://www.bilibili.com/*
// @include    https://www.ixigua.com/*
// @include    https://www.acfun.cn/*
// @include    http://v.pptv.com/show/*
// @include    https://v.pptv.com/show/*
// @include    https://www.miguvideo.com/*
// @include    https://tv.sohu.com/*
// @include    https://film.sohu.com/album/*
// @include    https://www.mgtv.com/*
// @version    1.8.7
// @include    https://pan.baidu.com/*
// @include    https://yun.baidu.com/*
// @include    https://*.163.com/*
// @include    https://*.icourse163.org/*
// @include    https://*.sina.com.cn/*
// @include    https://video.sina.cn/*
// @include    http://k.sina.*
// @include    https://k.sina.*
// @include    https://weibo.com/*
// @include    https://*.weibo.com/*
// @include    https://pan.baidu.com/*
// @include    https://yun.baidu.com/*
// @include    http://v.ifeng.com/*
// @include    https://v.ifeng.com/*
// @include    http://news.mtime.com/*
// @include    http://video.mtime.com/*
// @GM_info
// @include    https://www.youtube.com/watch?v=*
// @include    https://www.ted.com/talks/*
// @include    https://www.yy.com/*
// @include    https://www.huya.com/*
// @include    https://v.douyu.com/*
// @include    https://www.douyu.com/*
// @include    http://star.longzhu.com/*
// @include    https://star.longzhu.com/*
// @include    https://www.zhanqi.tv/*
// @run-at     document-start
// @include    */play*
// @include    *play/*
// @include    https://www.meiju11.com/ckplayerx/m3u8.php*
// @include    https://ww4.hanjutvaa.com/index.php*
// @include    *://olevod.com/static/player/*
// @grant      GM_addStyle
// @exclude    https://www.meiju11.com/new/Play/*
// @exclude    https://www.hanjutvaa.com/player/*
// @exclude    *://www.olevod.com/index.php/vod/play/id/*
// @exclude    *://www.olevod.tv/index.php/vod/play/id/*
// @exclude    *://olevod.com/index.php/vod/play/id/*
// @exclude    *://olevod.tv/index.php/vod/play/id/*
// @grant      window.onurlchange
// @grant      unsafeWindow
// @grant      GM_registerMenuCommand
// @grant      GM_setValue
// @grant      GM_getValue
// @namespace  https://greasyfork.org/users/7036
// ==/UserScript==

'use strict';
const w = unsafeWindow || window;
const { host, pathname: path } = location;
const d = document, find = [].find;
let v, _fp, _fs, by; // document.body
const observeOpt = {childList : true, subtree : true};
const noopFn = function(){};
const q = (css, p = d) => p.querySelector(css);
const delElem = e => e.remove();
const $$ = function(c, cb = delElem, doc = d) {
	if (!c || !c.length) return;
	if (typeof c === 'string') c = doc.querySelectorAll(c);
	if (!cb) return c;
	for (let e of c) if (e && cb(e)=== !1) break;
};
const r1 = (regp, s) => regp.test(s) && RegExp.$1;
const log = console.log.bind(
	console,
	'%c脚本[%s] 反馈:%s\n%s',
	'color:#c3c;font-size:1.2em',
	GM_info.script.name,
	GM_info.script.homepage
);
const gmFuncOfCheckMenu = (title, saveName, defaultVal = true) => {
	const r = GM_getValue(saveName, defaultVal);
	if (r) title = '√  '+ title;
	GM_registerMenuCommand(title, () => {
		GM_setValue(saveName, !r);
		location.reload();
	});
	return r;
};
const sleep = ms => new Promise(resolve => { setTimeout(resolve, ms) });
/*
const cookie = new Proxy(noopFn, {
	apply(target, ctx, args) { //清理cookie
		const keys = document.cookie.match(/[^ =;]+(?=\=)/g);
		if (keys) {
			const val = '=; expires=' + new Date(0).toUTCString() +'; domain=.; path=/';
			for (const k of keys) document.cookie = k + val;
		}
		// return Reflect.apply(...arguments);
	},
	get(target, name) { // 读取cookie
		const r = r1(new RegExp(name +'=([^;]*)'), document.cookie);
		if (r) return decodeURIComponent(r);
	},
	set(target, name, value, receiver) { // 写入cookie
		let s, v, expires,
		oneParam = typeof value == 'string';
		if (oneParam) {
			expires = 6;
			v = value;
		} else {
			v = value.val;
			expires = value.expires || 6;
			delete value.expires;
		}
		s = name + '=' + encodeURIComponent(v);

		if (expires && (typeof expires == 'number' || expires.toUTCString)) {
			let date;
			if (typeof expires == 'number') {
				date = new Date();
				date.setTime(expires * 24 * 3600000 + date.getTime());
			} else {
				date = expires;
			}
			s += '; expires=' + date.toUTCString();
		}
		if (!oneParam) for (const k in value) s += '; ' + k + '=' + value[k];
		document.cookie = s;
		return true;
	},
	deleteProperty(target, name, descriptor) {// 删除cookie
		document.cookie = name + '=; path=/; expires='+ new Date(0).toUTCString();
		return true;
	}
});
const onceEvent = (ctx, eName) => new Promise(resolve => ctx.addEventListener(eName, resolve));
const promisify = (fn) => (...args) => new Promise((resolve, reject) => {
    args.push(resolve);
	fn.apply(this, args);
}); */
const hookAttachShadow = (cb) => {
	try {
		const _attachShadow = Element.prototype.attachShadow;
		Element.prototype.attachShadow = function(opt) {
			opt.mode = 'open';
			const shadowRoot = _attachShadow.call(this, opt);
			cb(shadowRoot);
			return shadowRoot;
		};
	} catch (e) {
		console.error('Hack attachShadow error', e);
	}
};
const getStyle = (o, s) => {
	if (o.style[s]) return o.style[s];
	if (getComputedStyle) {
		const x = getComputedStyle(o, '');
		s = s.replace(/([A-Z])/g,'-$1').toLowerCase();
		return x && x.getPropertyValue(s);
	}
};
const doClick = e => {
	if (typeof e === 'string') e = q(e);
	if (e) { e.click ? e.click() : e.dispatchEvent(new MouseEvent('click')) };
};
const clickDualButton = btn => { // 2合1 按钮
	!btn.nextSibling || getStyle(btn, 'display') !== 'none' ? doClick(btn) : doClick(btn.nextSibling);
};
const intervalQuery = (cb, condition, stop = true) => {
	const fn = typeof condition === 'string' ? q.bind(null, condition) : condition;
	const t = setInterval(() => {
		const r = fn();
		if (r) {
			stop && clearInterval(t);
			cb(r);
		}
	}, 300);
	return t;
};
const goNextMV = () => {
	const s = location.pathname;
	const m = s.match(/(\d+)(\D*)$/);
	const d = +m[1] + 1;
	location.assign(s.slice(0, m.index) + d + m[2]);
};
const firefoxVer = r1(/Firefox\/(\d+)/, navigator.userAgent);
const isEdge = / Edge?\//.test(navigator.userAgent);
const fakeUA = ua => Object.defineProperty(navigator, 'userAgent', {
	value: ua,
	writable: false,
	configurable: false,
	enumerable: true
});
const getMainDomain = host => {
	const a = host.split('.');
	let i = a.length - 2;
	if (/^(com?|cc|tv|net|org|gov|edu)$/.test(a[i])) i--;
	return a[i];
};
const ua_chrome = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3626.121 Safari/537.36';

class FullScreen {
	constructor(e) {
		let fn = d.exitFullscreen || d.webkitExitFullscreen || d.mozCancelFullScreen || d.msExitFullscreen || noopFn;
		this.exit = fn.bind(d);
		fn = e.requestFullscreen || e.webkitRequestFullScreen || e.mozRequestFullScreen || e.msRequestFullScreen || noopFn;
		this.enter = fn.bind(e);
	}
	static isFull() {
		return !!(d.fullscreen || d.webkitIsFullScreen || d.mozFullScreen ||
		d.fullscreenElement || d.webkitFullscreenElement || d.mozFullScreenElement);
	}
	toggle() {
		FullScreen.isFull() ? this.exit() : this.enter();
	}
}

//万能网页全屏, 参考了:https://github.com/gooyie/ykh5p
class FullPage {
	constructor(container, onSwitch) {
		this._isFull = !1;
		this._onSwitch = onSwitch;
		this.container = container || FullPage.getPlayerContainer(v);
		GM_addStyle(
			`.gm-fp-body .gm-fp-zTop {
				position: relative !important;
				z-index: 2147483647 !important;
			}
			.gm-fp-wrapper, .gm-fp-body{ overflow:hidden !important; }
			.gm-fp-wrapper .gm-fp-innerBox {
				width: 100% !important;
				height: 100% !important;
			}
			.gm-fp-wrapper {
				display: block !important;
				position: fixed !important;
				width: 100% !important;
				height: 100% !important;
				top: 0 !important;
				left: 0 !important;
				background: #000 !important;
				z-index: 2147483647 !important;
			}`
		);
	}
	static getPlayerContainer(video) {
		let e = video, p = e.parentNode;
		const { clientWidth: wid, clientHeight: h } = e;
		do {
			e = p;
			p = e.parentNode;
		} while (e.nodeName == 'DIV' && p.clientWidth-wid < 5 && p.clientHeight-h < 5);
		//e 为返回值,在此之后不能变了
		while (p !== by) p = p.parentNode || p.host;
		return e;
	}
	static isFull(e) {
		return w.innerWidth - e.clientWidth < 5 && w.innerHeight - e.clientHeight < 5;
	}
	toggle() {
		// assert(this.container);
		if (!this.container.contains(v)) this.container = FullPage.getPlayerContainer(v);
		const cb = this._onSwitch;
		if (!this._isFull && cb) cb(true);
		by.classList.toggle('gm-fp-body');
		let e = v;
		while (e != this.container) {
			e.classList.toggle('gm-fp-innerBox');
			e = e.parentNode;
		}
		e.classList.toggle('gm-fp-wrapper');
		e = e.parentNode;
		while (e != by) {
			e.classList.toggle('gm-fp-zTop');
			e = e.parentNode;
		}
		this._isFull = !this._isFull;
		if (!this._isFull && cb) setTimeout(cb, 199, !1);
	}
}

const u = getMainDomain(host);
const events = {
	on(name, fn) {
		this[name] = fn;
	},
	undo(name) {
		delete this[name];
	},
	handle(name, ...a) {
		return this[name] && this[name].apply(this, a);
	}
};
const app = {
	isLive: !1,
	disableDBLClick: !1,
	multipleV: !1, //多视频页面
	isNumURL: !1, //网址数字分集
	disableKeys: [],
	shellEvent() {
		const fn = ev => {
			if (ev.target.closest('button')) return;
			ev.stopPropagation(); // preventDefault
			ev.stopImmediatePropagation();
			this.toggleFP();
		};
		this.mvShell.addEventListener('mousedown', ev => {
			this.checkMV();
			if (!this.isLive && 1==ev.button) {
				ev.stopPropagation();
				v.currentTime += 5;
			}
		});
		!this.disableDBLClick && this.mvShell.addEventListener('dblclick', fn);
	},
	setShell() {
		const e = this.getDPlayer() || this.getVjsPlayer() ||
			(this.shellCSS && q(this.shellCSS)) ||
			(top != self ? by : FullPage.getPlayerContainer(v));
		if (e && this.mvShell != e) {
			this.mvShell = e;
			this.shellEvent();
		}
	},
	checkMV() {
		if (this.vList) {
			const e = this.findMV();
			// log('app.findMV()',v,e)
			if (e != v) {
				v = e;
				this.btnPlay = this.btnNext = this.btnFP = this.btnFS = _fs = _fp = null;
				if (!this.isLive) {
					v.playbackRate = localStorage.mvPlayRate || 1;
					v.addEventListener('ratechange', ev => {
						localStorage.mvPlayRate = v.playbackRate;
					});
				}
				this.setShell();
			}
		}
		this.checkUI();
		return v;
	},
	getDPlayer() {
		const e = v.closest('.dplayer');
		if (e) {
			this.btnFP = q('.dplayer-full-in-icon > span', e);
			this.btnFS = q('.dplayer-full-icon', e);
			e.closest('body > *').classList.add('gm-dp-zTop');
		}
		return e;
	},
	getVjsPlayer() {
		const e = v.closest('.video-js');
		if (e) {
			this.btnFS = q('.vjs-control-bar > button.vjs-button:nth-last-of-type(1)');
			this.webfullCSS = '.vjs-control-bar > button.vjs-button[title$="全屏"]:nth-last-of-type(2)';
		}
		return e;
	},
	toggleFP() {
		self != top ? top.postMessage({id: 'gm-h5-toggle-iframeWebFull'}, '*')
		: _fp ? _fp.toggle() : clickDualButton(this.btnFP);
	},
	hotKey(e) {
		const t = e.target;
		if (e.ctrlKey || e.altKey || t.contentEditable=='true' ||
			/INPUT|TEXTAREA|SELECT/.test(t.nodeName)) return;
		if (e.shiftKey && ![13,37,39].includes(e.keyCode)) return;
		if (this.disableKeys.includes(e.keyCode)) return;
		if (this.isLive && [37,39,78,88,67,90].includes(e.keyCode)) return;
		if (!this.checkMV()) return;
		if (events.handle('keydown', e)) return;
		if (!e.shiftKey && this.mvShell && this.mvShell.contains(t) && [32,37,39].includes(e.keyCode)) return;
		e.stopImmediatePropagation();
		let n;
		switch (e.keyCode) {
		case 32: //space
			if (this.btnPlay) clickDualButton(this.btnPlay);
			else v.paused ? v.play() : v.pause();
			e.preventDefault();
			break;
		case 37: n = e.shiftKey ? -20 : -5; //left  快退5秒,shift加速
		//right  快进5秒,shift加速
		case 39: n = n || (e.shiftKey ? 20 : 5);
			v.currentTime += n;
			break;
		case 78: // N 下一集
			if (self != top) top.postMessage({id: 'gm-h5-play-next'}, '*');
			else if (this.btnNext) doClick(this.btnNext);
			else if (this.isNumURL) goNextMV();
			break;
		case 38: n = 0.1; //加音量
		case 40: n = n || -0.1; //降音量
			n += v.volume;
			if (0 <= n && n <= 1) v.volume = +n.toFixed(1);
			e.preventDefault();
			break;
		case 13: //回车键。 全屏
			if (e.shiftKey) {
				this.toggleFP();
			} else {
				_fs ? _fs.toggle() : clickDualButton(this.btnFS);
			}
			break;
		case 27: //esc
			if (FullScreen.isFull()) {
				_fs ? _fs.exit() : clickDualButton(this.btnFS);
			} else if (self != top) {
				top.postMessage({id: 'gm-h5-is-iframeWebFull'}, '*');
			} else if (FullPage.isFull(v)) {
				_fp ? _fp.toggle() : clickDualButton(this.btnFP);
			}
			break;
		case 67: n = 0.1; //按键C:加速播放 +0.1
		case 88: n = n || -0.1; //按键X:减速播放 -0.1
			n += v.playbackRate;
			if (0 < n && n <= 16) v.playbackRate = +n.toFixed(2);
			break;
		case 90: //按键Z:正常速度播放
			v.playbackRate = 1;
			break;
		case 70: n = 0.03; //按键F:下一帧
		case 68: n = n || -0.03; //按键D:上一帧
			v.currentTime += n;
			v.pause();
			break;
		default: return;
		}
		e.stopPropagation();
	},
	checkUI() {
		if (this.webfullCSS && !this.btnFP) this.btnFP = q(this.webfullCSS);
		if (this.btnFP) _fp = null;
		else if (!_fp && self == top) _fp = new FullPage(this.mvShell, this.switchFP);

		if (this.fullCSS && !this.btnFS) this.btnFS = q(this.fullCSS);
		if (this.btnFS) _fs = null;
		else if (!_fs) _fs = new FullScreen(v);

		if (this.nextCSS && !this.btnNext) this.btnNext = q(this.nextCSS);
		if (this.playCSS && !this.btnPlay) this.btnPlay = q(this.playCSS);
	},
	switchFP(toFull) {
		if (toFull) {
			for (let e of this.vSet) this.viewObserver.unobserve(e);
		} else {
			for (let e of this.vList) this.viewObserver.observe(e);
		}
	},
	onGrowVList() {
		if (this.vList.length == this.vCount) return;
		if (this.viewObserver) {
			for (let e of this.vList) {
				if (!this.vSet.has(e)) this.viewObserver.observe(e);
			}
		} else {
			const config = {
				rootMargin: '0px',
				threshold: 0.9
			};
			this.viewObserver = new IntersectionObserver(this.onIntersection.bind(this), config);
			for (let e of this.vList) this.viewObserver.observe(e);
		}
		this.vSet = new Set(this.vList);
		this.vCount = this.vList.length;
	},
	onIntersection(entries) {
		if (this.vList.length < 2) return;
		const entry = find.call(entries, k => k.isIntersecting);
		if (!entry || v == entry.target) return;
		v = entry.target;
		_fs = new FullScreen(v);
		_fp = new FullPage(null, this.switchFP);
		events.handle('switchMV');
	},
	bindEvent() {
		$$(this.adsCSS);
		by = d.body;
		log('bind event\n', v);
		events.handle('foundMV');
		window.addEventListener('urlchange', info => this.checkMV()); //TM event: info.url
		if (top != self) {
			top.postMessage({id: 'gm-h5-init-MVframe'}, '*');
			window.addEventListener("message", ev => {
				if (!ev.source || !ev.data || !ev.data.id) return;
				switch (ev.data.id) {
				case 'gm-h5-toggle-fullScreen':
					_fs ? _fs.toggle() : clickDualButton(this.btnFS);
					break;
				}
			}, false);
		}
		if (!this.isLive) {
			v.playbackRate = localStorage.mvPlayRate || 1;
			v.addEventListener('ratechange', ev => {
				localStorage.mvPlayRate = v.playbackRate;
			});
		}
		const fn = ev => {
			events.handle('canplay');
			v.removeEventListener('canplaythrough', fn);
		};
		v.addEventListener('canplaythrough', fn);
		by.addEventListener('keydown', this.hotKey.bind(this));

		this.mvShell ? this.shellEvent() : this.setShell();
		this.checkUI();
		if (this.multipleV) {
			new MutationObserver(this.onGrowVList.bind(this)).observe(by, observeOpt);
			this.vCount = 0;
			this.onGrowVList();
		}
	},
	init() {
		this.switchFP = this.multipleV ? this.switchFP.bind(this) : null;
		this.vList = d.getElementsByTagName('video');
		const fn = e => this.cssMV ? e.matches(this.cssMV) : e.offsetWidth > 9;
		this.findMV = find.bind(this.vList, fn);
		const timer = intervalQuery(e => {
			v = e;
			this.bindEvent();
		}, this.findMV);

		hookAttachShadow(async shadowRoot => {
			await sleep(600);
			events.handle('addShadowRoot', shadowRoot);
			if (v) return;
			if (v = q('video', shadowRoot)) { // v.getRootNode() == shadowRoot
				log('Found MV in ShadowRoot\n', v, shadowRoot);
				if (!this.shellCSS) this.mvShell = shadowRoot.host;
				this.shadowRoot = shadowRoot;
				clearInterval(timer);
				this.bindEvent();

				this.vList = null;
				this.findMV = noopFn;
			}
		});
	}
};

let router = {
	ted() {
		app.fullCSS = 'button[title="Enter Fullscreen"]';
		app.playCSS = 'button[title="play video"]';
		if (!gmFuncOfCheckMenu('TED强制高清', 'ted_forceHD')) return;
		const getHDSource = async () => {
			const pn = r1(/^(\/talks\/\w+)/, path);
			const resp = await fetch(pn + '/metadata.json');
			const data = await resp.json();
			return data.talks[0].downloads.nativeDownloads.high;
		};
		const check = async (rs) => {
			if (!v.src || v.src.startsWith('http')) return;
			$$(app.vList, e => { e.removeAttribute('src') }); // 取消多余的媒体资源请求
			try {
				v.src = await getHDSource();
			} catch(ex) {
				console.error(ex);
			}
		};
		events.on('foundMV', () => {
			new MutationObserver(check).observe(v, {
				attributes: true,
				attributeFilter: ['src']
			});
			check();
		});
	},
	youtube() {
		app.playCSS = 'button.ytp-play-button';
		app.fullCSS = 'button.ytp-fullscreen-button';
		app.disableKeys = [32,70];
		events.on('keydown', ev => {
			switch (ev.keyCode) {
			case 69: //E,替换F键
				v.currentTime += 0.03;
				v.pause();
				return true;
			}
		});
		// GM_addStyle('.ytp-chrome-bottom, .ytp-progress-bar-container .ytp-chapters-container{width:100%!important}');
	},
	qq() {
		app.disableKeys = [32];
		app.nextCSS = '.txp_btn_next';
		app.webfullCSS = '.txp_btn_fake';
		app.fullCSS = '.txp_btn_fullscreen';
		events.on('canplay', () => {
			// if (!q(app.webfullCSS)) app.shellCSS = '.txp_player';
			if (v.muted) {
				doClick('.txp_btn_volume');
				$('.txp_popup_volume').hide();
			}
		});
	},
	youku() {
		events.on('keydown', ev => {
			switch (ev.keyCode) {
			case 32: //空格
				ev.preventDefault();
				v.paused ? v.play() : v.pause();
				return true;
			}
		});
		if (host.startsWith('vku.')) {
			events.on('canplay', () => {
				app.isLive = !q('.spv_progress');
			});
			app.fullCSS = '.live_icon_full';
		} else {
			events.on('foundMV',() => {
				by.addEventListener('keyup', e => e.stopPropagation());
			});
			// localStorage.removeItem('cna');
			// delete cookie.cna;  //全部清除: cookie(); 写入cookie: cookie.cna = 'xxxxx---xxx';
			app.shellCSS = '#ykPlayer';
			app.webfullCSS = '.control-webfullscreen-icon';
			app.fullCSS = '.control-fullscreen-icon';
			app.nextCSS = 'span.icon-next';
		}
	},
	bilibili() {
		app.isLive = host.startsWith('live.');
		if (app.isLive) return;
		const isSquirtle = path.startsWith('/bangumi');
		if (!isSquirtle) app.disableKeys = [32];
		app.shellCSS = 'div[aria-label="哔哩哔哩播放器"]'; // .player #bilibili-player
		app.nextCSS = isSquirtle ? '.squirtle-video-next' : '.bilibili-player-video-btn-next';
		app.webfullCSS = isSquirtle ? '.squirtle-video-pagefullscreen' : '.bilibili-player-video-web-fullscreen';
		app.fullCSS = isSquirtle ? '.squirtle-video-fullscreen' : '.bilibili-player-video-btn-fullscreen';
		events.on('foundMV', () => {
			const s = isSquirtle ? '.squirtle-speed-select-result' : '.bilibili-player-video-btn-speed-name';
			const fn = ev => { q(s).textContent = v.playbackRate + 'X'; };
			v.addEventListener('ratechange', fn);
		});
	},
	iqiyi() {
		app.fullCSS = '.iqp-btn-fullscreen';
		app.webfullCSS = '.iqp-btn-webscreen';
		app.nextCSS = '.iqp-btn-next';
	},
	pptv() {
		app.fullCSS = '.w-zoom-container > div';
		app.webfullCSS = '.w-expand-container > div';
		app.nextCSS = '.w-next';
	},
	mgtv() {
		app.fullCSS = 'mango-screen';
		app.webfullCSS = 'mango-webscreen > a';
		app.nextCSS = 'mango-control-playnext-btn';
	},
	ixigua() {
		app.fullCSS = '.xgplayer-fullscreen';
		app.webfullCSS = '.xgplayer-cssfullscreen';
		app.nextCSS = '.xgplayer-playNext';
		events.on('foundMV', () => {
			v.addEventListener('keydown', app.hotKey.bind(app));
		});
	},
	miguvideo() {
		app.playCSS = '.play-btn';
		app.nextCSS = '.next-btn';
	},
	weibo() {
		app.multipleV = path.startsWith('/u/');
	},
	acfun() {
		app.nextCSS = '.btn-next-part .control-btn';
		app.webfullCSS = '.fullscreen-web';
		app.fullCSS = '.fullscreen-screen';
	},
	['163']() {
		app.multipleV = host.startsWith('news.');
		GM_addStyle('div.video,video{max-height: 100% !important;}');
		return host.split('.').length > 3;
	},
	sohu() {
		app.nextCSS = 'li.on[data-vid]+li a';
		app.fullCSS = '.x-fullscreen-btn';
		app.webfullCSS = '.x-pagefs-btn';
	},
	fun() {
		app.nextCSS = '.btn-item.btn-next';
	},
	le() {
		GM_addStyle('.gm-fp-body .le_head{display:none!important}');
		app.cssMV = '#video video';
		app.shellCSS = '#video';
		app.nextCSS = '.hv_ico_next';
		setInterval(_ => {
			if (!v || v.offsetHeight) return;
			for (const k of Object.values(v.attributes)) if (v.getAttribute(k.name) == ''){
				v.removeAttribute(k.name);
				break;
			}
		}, 600);
	},
	agefans() {
		events.on('keydown', ev => {
			switch (ev.keyCode) {
			case 78: //N
				location.href = location.href.replace(/\d+$/, s => ++s);
				return true;
			}
		});
	},
	hmtv() {
		app.nextCSS = `a[href="${path}"]+a`;
		GM_addStyle('.dplayer-loaded{ background-color:orange !important; }');
	},
	olevod() {
		app.fullCSS = 'button[data-plyr=fullscreen]';
		GM_addStyle('.plyr__video-wrapper{ height:100%!important; padding-bottom:0!important;}');
	}
};

if (!router[u]) { //直播站点
	router = {
		douyu() {
			app.adsCSS = 'a[href*="wan.douyu.com"]';
			app.isLive = !host.startsWith('v.');
			if (app.isLive) {
				app.cssMV = '.layout-Player video';
				app.webfullCSS = 'div[class|=wfs]';
				app.fullCSS = 'div[class|=fs]';
				app.playCSS = 'div[class|=play]';
				path != '/' && document.addEventListener('DOMContentLoaded', ev => {
					$$('.u-specialStateInput', e => {e.checked = true;})
				});
			} else events.on('addShadowRoot', function(r) {
				if (r.host.matches('#demandcontroller-bar')) {
					app.shellCSS = 'div[fullscreen].video';
					app.btnFP = q('.ControllerBar-PageFull', r);
					app.btnFS = q('.ControllerBar-WindowFull', r);
					this.undo('addShadowRoot');
				}
			});
		},
		yy() {
			app.isLive = !path.startsWith('/x/');
			if (app.isLive) {
				app.fullCSS = '.yc__fullscreen-btn';
				app.webfullCSS = '.yc__cinema-mode-btn';
				app.playCSS = '.yc__play-btn';
			}
		},
		huya() {
			if (firefoxVer && firefoxVer < 57) return true;
			app.disableDBLClick = !0;
			app.webfullCSS = '.player-fullpage-btn';
			app.fullCSS = '.player-fullscreen-btn';
			app.playCSS = '#player-btn';
			app.adsCSS = '#player-subscribe-wap,#wrap-income';
			intervalQuery(doClick, '.login-tips-close');
			localStorage['sidebar/ads'] = '{}';
			localStorage['sidebar/state'] = 0;
			localStorage.TT_ROOM_SHIELD_CFG_0_ = '{"10000":1,"20001":1,"20002":1,"20003":1,"30000":1}';
		},
		longzhu() {
			app.fullCSS = 'a.ya-screen-btn';
		},
		zhanqi() {
			localStorage.lastPlayer = 'h5';
			app.fullCSS = '.video-fullscreen';
		}
	};
	if (router[u]) {
		app.isLive = app.isLive || !host.startsWith('v.');
		(!w.chrome || isEdge) && fakeUA(ua_chrome);
	}
}

Reflect.defineProperty(navigator, 'plugins', {
	get() { return { length: 0 } }
});
GM_registerMenuCommand('脚本功能快捷键表' , alert.bind(w,
`双击:切换(网页)全屏
鼠标中键:快进5秒

左右方向键:快退、快进5秒;  + shift: 20秒
上下方向键:音量调节   ESC:退出(网页)全屏
空格键:暂停/播放      N:播放下一集
回车键:切换全屏;   + shift: 切换网页全屏
C:加速0.1倍播放       X:减速0.1倍播放       Z:正常速度播放
D:上一帧     F:下一帧(youtube.com用E键)`
));
if (!router[u] || !router[u]()) app.init();
if (!router[u] && !app.isNumURL) app.isNumURL = /[_\W]\d+(\/|\.[a-z]{3,8})?$/.test(path);