// ==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.3 // @grant unsafeWindow // @run-at document-end // @downloadURL none // ==/UserScript== (function() { 'use strict'; var fullStatus = false, isIframe = false, autoCheckCount = 0, parentArray = [], backStyle = {}, 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'], 'www.yy.com': ['#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); 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); 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 = []; 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; } } 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) { var path = e.path || e.composedPath(); for (var v of players) { if (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 = ''; try { player.removeEventListener('mouseleave', handle.leavePlayer, false); } catch (e) {} 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.fullWin(); 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++; 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 .parentToothbrush .bilibili-player-video {margin:0 !important;}', '#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); })();