// ==UserScript==
// @name Maximize Video
// @name:zh-CN 视频网页全屏
// @namespace http://www.icycat.com
// @description Maximize all video players.Support Piture-in-picture.
// @description:zh-CN 让所有视频网页全屏,新增画中画功能
// @author 冻猫
// @include *
// @exclude *www.w3school.com.cn*
// @version 11.0
// @grant unsafeWindow
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
var fullStatus = false,
isIframe = false,
autoCheckCount = 0,
parentArray = new Array(),
backStyle = new Object(),
mouse = {
leave: 'listener',
over: 'listener'
},
btnText, target, video, player, controlBtn, picinpicBtn, leftBtn, rightBtn;
//Html5规则[播放器最外层],适用于自适应大小HTML5播放器
var html5Rules = {
'www.acfun.cn': ['.player-container .player'],
'www.bilibili.com': ['#bilibiliPlayer'],
'www.douyu.com': ['#js-player-video-case'],
'www.huya.com': ['#videoContainer'],
'www.twitch.tv': ['.player'],
'www.youtube.com': ['#movie_player']
};
//通用html5播放器
var generalPlayerRules = ['.dplayer', '.video-js', '.jwplayer'];
if (window.top !== window.self) {
isIframe = true;
}
if (navigator.language.toLocaleLowerCase() == 'zh-cn') {
btnText = {
max: '网页全屏',
pip: '画中画',
tip: 'Iframe内视频,请用鼠标点击视频后重试'
};
} else {
btnText = {
max: 'Maximize',
pip: 'PicInPic',
tip: 'Iframe video. Please click on the video and try again'
};
}
var tool = {
getRect: function(element) {
var rect = element.getBoundingClientRect();
var scroll = tool.getScroll();
return {
pageX: rect.left + scroll.left,
pageY: rect.top + scroll.top,
screenX: rect.left,
screenY: rect.top
};
},
isHalfFullClient: function(element) {
var client = tool.getClient();
var rect = tool.getRect(element);
console.log(client);
console.log(rect);
console.log(element.offsetWidth + '+' + element.offsetHeight);
//宽与元素X坐标或高与元素Y坐标
if ((Math.abs(client.width - element.offsetWidth) < 21 && rect.screenX < 20) || (Math.abs(client.height - element.offsetHeight) < 21 && rect.screenY < 10)) {
//居中
if (Math.abs(element.offsetWidth / 2 + rect.screenX - client.width / 2) < 10 && Math.abs(element.offsetHeight / 2 + rect.screenY - client.height / 2) < 10) {
return true;
} else {
return false;
}
} else {
return false;
}
},
isAllFullClient: function(element) {
var client = tool.getClient();
var rect = tool.getRect(element);
//宽与左上角X坐标与高与左上角Y坐标
if ((Math.abs(client.width - element.offsetWidth) < 21 && rect.screenX < 20) && (Math.abs(client.height - element.offsetHeight) < 21 && rect.screenY < 10)) {
return true;
} else {
return false;
}
},
getScroll: function() {
return {
left: document.documentElement.scrollLeft || document.body.scrollLeft,
top: document.documentElement.scrollTop || document.body.scrollTop
};
},
getClient: function() {
return {
width: document.compatMode == 'CSS1Compat' ? document.documentElement.clientWidth : document.body.clientWidth,
height: document.compatMode == 'CSS1Compat' ? document.documentElement.clientHeight : document.body.clientHeight
};
},
addStyle: function(css) {
var style = document.createElement('style');
style.type = 'text/css';
var node = document.createTextNode(css);
style.appendChild(node);
document.head.appendChild(style);
return style;
},
matchRule: function(str, rule) {
return new RegExp("^" + rule.split("*").join(".*") + "$").test(str);
},
createButton: function(id) {
var btn = document.createElement('tbdiv');
btn.id = id;
btn.onclick = function() {
maximize.playerControl();
};
document.body.appendChild(btn);
return btn;
},
addTip: async function(str) {
if (!document.getElementById('catTip')) {
var tip = document.createElement('tbdiv');
tip.id = 'catTip';
tip.innerHTML = str;
tip.style.cssText = 'transition: all 0.8s ease-out;background: none repeat scroll 0 0 #27a9d8;color: #FFFFFF;font: 1.1em "微软雅黑";margin-left: -250px;overflow: hidden;padding: 10px;position: fixed;text-align: center;bottom: 100px;z-index: 300;',
document.body.appendChild(tip);
tip.style.right = -tip.offsetWidth - 5 + 'px';
await new Promise(resolve => {
tip.style.display = 'block';
setTimeout(() => {
tip.style.right = '25px';
resolve('OK');
}, 300);
});
await new Promise(resolve => {
setTimeout(() => {
tip.style.right = -tip.offsetWidth - 5 + 'px';
resolve('OK');
}, 3500);
});
await new Promise(resolve => {
setTimeout(() => {
document.body.removeChild(tip);
resolve('OK');
}, 1000);
});
}
}
};
var setButton = {
init: function() {
//防止页面自身干扰,重新初始化
if (!document.getElementById('playerControlBtn')) {
init();
}
if (isIframe && tool.isHalfFullClient(player)) {
window.parent.postMessage('iframeVideo', '*');
return;
}
this.show();
},
show: function() {
try {
player.removeEventListener('mouseleave', handle.leavePlayer, false);
player.addEventListener('mouseleave', handle.leavePlayer, false);
} catch (e) {
mouse.leave = player.onmouseleave;
player.onmouseleave = function() {
handle.leavePlayer();
player.onmouseleave = mouse.leave;
};
}
if (!fullStatus) {
document.removeEventListener('scroll', handle.scrollFix, false);
document.addEventListener('scroll', handle.scrollFix, false);
}
controlBtn.style.display = 'block';
controlBtn.style.visibility = 'visible';
if (document.pictureInPictureEnabled && player.nodeName != 'OBJECT' && player.nodeName != 'EMBED') {
picinpicBtn.style.display = 'block';
picinpicBtn.style.visibility = 'visible';
}
this.locate();
},
locate: function() {
var playerRect = tool.getRect(player);
controlBtn.style.opacity = '0.5';
controlBtn.innerHTML = btnText.max;
controlBtn.style.top = playerRect.screenY - 20 + 'px';
controlBtn.style.left = playerRect.screenX - 64 + player.offsetWidth + 'px';
picinpicBtn.style.opacity = '0.5';
picinpicBtn.innerHTML = btnText.pip;
picinpicBtn.style.top = controlBtn.style.top;
picinpicBtn.style.left = playerRect.screenX - 64 + player.offsetWidth - 54 + 'px';
}
};
var handle = {
getPlayer: function(e) {
if (fullStatus) {
return;
}
target = e.target;
var hostname = document.location.hostname;
var players = [];
//html5Rules
for (var i in html5Rules) {
if (tool.matchRule(hostname, i)) {
for (var v of html5Rules[i]) {
players = document.querySelectorAll(v);
if (players.length > 0) {
break;
}
}
break;
}
}
//generalPlayerRules
if (players.length == 0) {
for (var v of generalPlayerRules) {
players = document.querySelectorAll(v);
if (players.length > 0) {
break;
}
}
}
//自动识别
if (players.length == 0 && e.target.nodeName != 'VIDEO' && document.querySelectorAll('video').length > 0) {
var videos = document.querySelectorAll('video');
for (var v of videos) {
var vRect = v.getBoundingClientRect();
//鼠标进入视频范围,视频宽高
if (e.clientX >= vRect.x - 2 && e.clientX <= vRect.x + vRect.width + 2 && e.clientY >= vRect.y - 2 && e.clientY <= vRect.y + vRect.height + 2 && v.offsetWidth > 399 && v.offsetHeight > 220) {
players = [];
video = v;
players[0] = handle.autoCheck(v);
autoCheckCount = 1;
break;
}
}
}
if (players.length > 0) {
for (var v of players) {
if (e.path.indexOf(v) > -1) {
player = v;
setButton.init();
return;
}
}
}
switch (e.target.nodeName) {
case 'VIDEO':
case 'OBJECT':
case 'EMBED':
if (e.target.offsetWidth > 399 && e.target.offsetHeight > 220) {
player = e.target;
setButton.init();
}
break;
default:
handle.leavePlayer();
}
},
autoCheck: function(v) {
var tempPlayer, el = v;
while (el = el.parentNode) {
if (Math.abs(v.offsetWidth - el.offsetWidth) < 15 && Math.abs(v.offsetHeight - el.offsetHeight) < 15) {
tempPlayer = el;
} else {
break;
}
}
return tempPlayer;
},
leavePlayer: function() {
if (controlBtn.style.visibility == 'visible') {
controlBtn.style.opacity = '';
controlBtn.style.visibility = '';
picinpicBtn.style.opacity = '';
picinpicBtn.style.visibility = '';
player.removeEventListener('mouseleave', handle.leavePlayer, false);
document.removeEventListener('scroll', handle.scrollFix, false);
}
},
scrollFix: function(e) {
clearTimeout(backStyle.scrollFixTimer);
backStyle.scrollFixTimer = setTimeout(() => {
setButton.locate();
}, 20);
},
hotKey: function(e) {
//默认退出键为ESC。需要修改为其他快捷键的请搜索"keycode",修改为按键对应的数字。
if (e.keyCode == 27) {
maximize.playerControl();
}
//默认画中画快捷键为F2。
if (e.keyCode == 113) {
pictureInPicture();
}
},
receiveMessage: async function(e) {
switch (e.data) {
case 'iframePicInPic':
console.log('messege:iframePicInPic');
if (!document.pictureInPictureElement) {
await document.querySelector('video').requestPictureInPicture()
.catch(error => {
tool.addTip(btnText.tip);
});
} else {
await document.exitPictureInPicture();
}
break;
case 'iframeVideo':
console.log('messege:iframeVideo');
if (!fullStatus) {
player = target;
setButton.init();
}
break;
case 'parentFull':
console.log('messege:parentFull');
player = target;
if (isIframe) {
window.parent.postMessage('parentFull', '*');
}
maximize.checkParent();
maximize.addClass();
if (getComputedStyle(player).left != '0px') {
tool.addStyle('#htmlToothbrush #bodyToothbrush .playerToothbrush {left:0px !important;width:100vw !important;}');
}
fullStatus = true;
break;
case 'parentSmall':
console.log('messege:parentSmall');
if (isIframe) {
window.parent.postMessage('parentSmall', '*');
}
maximize.smallWin();
break;
case 'innerFull':
console.log('messege:innerFull');
if (player.nodeName == 'IFRAME') {
player.contentWindow.postMessage('innerFull', '*');
}
maximize.checkParent();
maximize.fullWin();
break;
case 'innerSmall':
console.log('messege:innerSmall');
if (player.nodeName == 'IFRAME') {
player.contentWindow.postMessage('innerSmall', '*');
}
maximize.smallWin();
break;
}
}
};
var maximize = {
playerControl: function() {
if (!player) {
return;
}
this.checkParent();
if (!fullStatus) {
if (isIframe) {
window.parent.postMessage('parentFull', '*');
}
if (player.nodeName == 'IFRAME') {
player.contentWindow.postMessage('innerFull', '*');
}
this.fullWin();
if (autoCheckCount > 0 && !tool.isHalfFullClient(video)) {
if (autoCheckCount > 10) {
video.classList.add('videoToothbrush');
return;
}
var tempPlayer = handle.autoCheck(video);
autoCheckCount++;
console.log(autoCheckCount);
maximize.playerControl();
player = tempPlayer;
maximize.playerControl();
} else {
autoCheckCount = 0;
}
} else {
if (isIframe) {
window.parent.postMessage('parentSmall', '*');
}
if (player.nodeName == 'IFRAME') {
player.contentWindow.postMessage('innerSmall', '*');
}
this.smallWin();
}
},
checkParent: function() {
if (fullStatus) {
return;
}
parentArray = [];
var full = player;
while (full = full.parentNode) {
if (full.nodeName == 'BODY') {
break;
}
if (full.getAttribute) {
parentArray.push(full);
}
}
},
fullWin: function() {
if (!fullStatus) {
document.removeEventListener('mouseover', handle.getPlayer, false);
backStyle = {
htmlId: document.body.parentNode.id,
bodyId: document.body.id
};
if (document.location.hostname == 'www.youtube.com') {
if (document.querySelector('#movie_player .ytp-size-button .ytp-svg-shadow').getBoundingClientRect().width == 20) {
document.querySelector('.ytp-size-button').click();
backStyle.ytbStageChange = true;
}
}
leftBtn.style.display = 'block';
rightBtn.style.display = 'block';
picinpicBtn.style.display = '';
controlBtn.style.display = '';
this.addClass();
}
fullStatus = true;
},
addClass: function() {
document.body.parentNode.id = 'htmlToothbrush';
document.body.id = 'bodyToothbrush';
for (var v of parentArray) {
v.classList.add('parentToothbrush');
//父元素position:fixed会造成层级错乱
if (getComputedStyle(v).position == 'fixed') {
v.classList.add('absoluteToothbrush');
}
}
player.classList.add('playerToothbrush');
if (player.nodeName == 'VIDEO') {
backStyle.controls = player.controls;
player.controls = true;
}
window.dispatchEvent(new Event('resize'));
},
smallWin: function() {
document.body.parentNode.id = backStyle.htmlId;
document.body.id = backStyle.bodyId;
for (var v of parentArray) {
v.classList.remove('parentToothbrush');
v.classList.remove('absoluteToothbrush');
}
player.classList.remove('playerToothbrush');
if (document.location.hostname == 'www.youtube.com' && backStyle.ytbStageChange) {
document.querySelector('.ytp-size-button').click();
backStyle.ytbStageChange = false;
}
if (player.nodeName == 'VIDEO') {
player.controls = backStyle.controls;
}
leftBtn.style.display = '';
rightBtn.style.display = '';
controlBtn.style.display = '';
document.addEventListener('mouseover', handle.getPlayer, false);
window.dispatchEvent(new Event('resize'));
fullStatus = false;
}
};
var pictureInPicture = function() {
if (!document.pictureInPictureElement) {
if (player) {
if (player.nodeName == 'IFRAME') {
player.contentWindow.postMessage('iframePicInPic', '*');
} else {
player.parentNode.querySelector('video').requestPictureInPicture();
}
} else {
document.querySelector('video').requestPictureInPicture();
}
} else {
document.exitPictureInPicture();
}
}
var init = function() {
picinpicBtn = document.createElement('tbdiv');
picinpicBtn.id = "picinpicBtn";
picinpicBtn.onclick = function() {
pictureInPicture();
};
document.body.appendChild(picinpicBtn);
controlBtn = tool.createButton('playerControlBtn');
leftBtn = tool.createButton('leftFullStackButton');
rightBtn = tool.createButton('rightFullStackButton');
if (getComputedStyle(controlBtn).position != 'fixed') {
tool.addStyle([
'#htmlToothbrush, #bodyToothbrush {overflow:hidden !important;zoom:100% !important}',
'#htmlToothbrush #bodyToothbrush .parentToothbrush {overflow:visible !important;z-index:auto !important;transform:none !important;-webkit-transform-style:flat !important;transition:none !important;contain:none !important;}',
'#htmlToothbrush #bodyToothbrush .absoluteToothbrush {position:absolute !important;}',
'#htmlToothbrush #bodyToothbrush .playerToothbrush {position:fixed !important;top:0px !important;left:1px !important;width:calc(100vw - 2px) !important;height:100vh !important;max-width:none !important;max-height:none !important;min-width:0 !important;min-height:0 !important;margin:0 !important;padding:0 !important;z-index:2147483647 !important;border:none !important;background-color:#000 !important;transform:none !important;}',
'#htmlToothbrush #bodyToothbrush .parentToothbrush video {object-fit:contain !important;}',
'#htmlToothbrush #bodyToothbrush .parentToothbrush .videoToothbrush {width:100vw !important;height:100vh !important;}',
'#playerControlBtn {text-shadow: none;visibility:hidden;opacity:0;display:none;transition: all 0.5s ease;cursor: pointer;font: 12px "微软雅黑";margin:0;width:64px;height:20px;line-height:20px;border:none;text-align: center;position: fixed;z-index:2147483647;background-color: #27A9D8;color: #FFF;} #playerControlBtn:hover {visibility:visible;opacity:1;background-color:#2774D8;}',
'#picinpicBtn {text-shadow: none;visibility:hidden;opacity:0;display:none;transition: all 0.5s ease;cursor: pointer;font: 12px "微软雅黑";margin:0;width:53px;height:20px;line-height:20px;border:none;text-align: center;position: fixed;z-index:2147483647;background-color: #27A9D8;color: #FFF;} #picinpicBtn:hover {visibility:visible;opacity:1;background-color:#2774D8;}',
'#leftFullStackButton{display:none;position:fixed;width:1px;height:100vh;top:0;left:0;z-index:2147483647;background:#000;}',
'#rightFullStackButton{display:none;position:fixed;width:1px;height:100vh;top:0;right:0;z-index:2147483647;background:#000;}'
].join('\n'));
}
};
init();
document.addEventListener('mouseover', handle.getPlayer, false);
document.addEventListener('keydown', handle.hotKey, false);
window.addEventListener('message', handle.receiveMessage, false);
})();