- // ==UserScript==
- // @name Maximize Video(improve)
- // @name:zh-CN 视频网页全屏(改)
- // @namespace https://github.com/ryomahan
- // @description Maximize all video players.Support Piture-in-picture.
- // @description:zh-CN 让所有视频网页全屏,开启画中画功能
- // @author 冻猫, ryomahan
- // @include *
- // @exclude *www.w3school.com.cn*
- // @version 12.5
- // @run-at document-end
- // @license MIT
- // ==/UserScript==
-
- ;(() => {
- const gv = {
- isFull: false,
- isIframe: false,
- autoCheckCount: 0,
- }
-
- //Html5规则[播放器最外层],适用于无法自动识别的自适应大小HTML5播放器
- const 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": ["#ytd-player"],
- "www.miguvideo.com": ["#mod-player"],
- "www.yy.com": ["#player"],
- "*weibo.com": ['[aria-label="Video Player"]', ".html5-video-live .html5-video"],
- "v.huya.com": ["#video_embed_flash>div"],
- }
-
- //通用html5播放器
- const generalPlayerRules = [".dplayer", ".video-js", ".jwplayer", "[data-player]"]
-
- if (window.top !== window.self) {
- gv.isIframe = true
- }
-
- if (navigator.language.toLocaleLowerCase() == "zh-cn") {
- gv.btnText = {
- max: "网页全屏",
- pip: "画中画",
- tip: "Iframe内视频,请用鼠标点击视频后重试",
- }
- } else {
- gv.btnText = {
- max: "Maximize",
- pip: "PicInPic",
- tip: "Iframe video. Please click on the video and try again",
- }
- }
-
- const tool = {
- print(log) {
- const now = new Date()
- const year = now.getFullYear()
- const month = (now.getMonth() + 1 < 10 ? "0" : "") + (now.getMonth() + 1)
- const day = (now.getDate() < 10 ? "0" : "") + now.getDate()
- const hour = (now.getHours() < 10 ? "0" : "") + now.getHours()
- const minute = (now.getMinutes() < 10 ? "0" : "") + now.getMinutes()
- const second = (now.getSeconds() < 10 ? "0" : "") + now.getSeconds()
- const timenow = "[" + year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second + "]"
- console.log(timenow + "[Maximize Video] > " + log)
- },
- getRect(element) {
- const rect = element.getBoundingClientRect()
- const scroll = tool.getScroll()
- return {
- pageX: rect.left + scroll.left,
- pageY: rect.top + scroll.top,
- screenX: rect.left,
- screenY: rect.top,
- }
- },
- isHalfFullClient(element) {
- const client = tool.getClient()
- const 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) < 21 &&
- Math.abs(element.offsetHeight / 2 + rect.screenY - client.height / 2) < 21
- ) {
- return true
- } else {
- return false
- }
- } else {
- return false
- }
- },
- isAllFullClient(element) {
- const client = tool.getClient()
- const 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() {
- return {
- left: document.documentElement.scrollLeft || document.body.scrollLeft,
- top: document.documentElement.scrollTop || document.body.scrollTop,
- }
- },
- getClient() {
- return {
- width: document.compatMode == "CSS1Compat" ? document.documentElement.clientWidth : document.body.clientWidth,
- height: document.compatMode == "CSS1Compat" ? document.documentElement.clientHeight : document.body.clientHeight,
- }
- },
- addStyle(css) {
- const style = document.createElement("style")
- style.type = "text/css"
- const node = document.createTextNode(css)
- style.appendChild(node)
- document.head.appendChild(style)
- return style
- },
- matchRule(str, rule) {
- return new RegExp("^" + rule.split("*").join(".*") + "$").test(str)
- },
- createButton(id) {
- const btn = document.createElement("tbdiv")
- btn.id = id
- btn.onclick = () => {
- maximize.playerControl()
- }
- document.body.appendChild(btn)
- return btn
- },
- async addTip(str) {
- if (!document.getElementById("catTip")) {
- const 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)
- })
- }
- },
- }
-
- const setButton = {
- init() {
- if (!document.getElementById("playerControlBtn")) {
- init()
- }
- if (gv.isIframe && tool.isHalfFullClient(gv.player)) {
- window.parent.postMessage("iframeVideo", "*")
- return
- }
- this.show()
- },
- show() {
- gv.player.removeEventListener("mouseleave", handle.leavePlayer, false)
- gv.player.addEventListener("mouseleave", handle.leavePlayer, false)
-
- if (!gv.isFull) {
- document.removeEventListener("scroll", handle.scrollFix, false)
- document.addEventListener("scroll", handle.scrollFix, false)
- }
- gv.controlBtn.style.display = "block"
- gv.controlBtn.style.visibility = "visible"
- if (document.pictureInPictureEnabled && gv.player.nodeName != "OBJECT" && gv.player.nodeName != "EMBED") {
- gv.picinpicBtn.style.display = "block"
- gv.picinpicBtn.style.visibility = "visible"
- }
- this.locate()
- },
- locate() {
- let escapeHTMLPolicy
- const hasTrustedTypes = Boolean(window.trustedTypes && window.trustedTypes.createPolicy)
- if (hasTrustedTypes) {
- escapeHTMLPolicy = window.trustedTypes.createPolicy('myEscapePolicy', {
- createHTML: (string, sink) => string
- });
- }
- const playerRect = tool.getRect(gv.player)
- gv.controlBtn.style.opacity = "0.5"
- gv.controlBtn.innerHTML = hasTrustedTypes ? escapeHTMLPolicy.createHTML(gv.btnText.max) : gv.btnText.max
- gv.controlBtn.style.top = playerRect.screenY - 20 + "px"
- // 网页全屏按钮位置,Maximize button
- gv.controlBtn.style.left = playerRect.screenX - 64 + gv.player.offsetWidth + "px"
- gv.picinpicBtn.style.opacity = "0.5"
- gv.picinpicBtn.innerHTML = hasTrustedTypes ? escapeHTMLPolicy.createHTML(gv.btnText.pip) : gv.btnText.pip
- gv.picinpicBtn.style.top = gv.controlBtn.style.top
- // 画中画按钮位置,PicInPic button
- gv.picinpicBtn.style.left = playerRect.screenX - 64 + gv.player.offsetWidth - 54 + "px"
- },
- }
-
- const handle = {
- getPlayer(e) {
- if (gv.isFull) {
- return
- }
- gv.mouseoverEl = e.target
- const hostname = document.location.hostname
- let players = []
- for (let i in html5Rules) {
- if (tool.matchRule(hostname, i)) {
- for (let html5Rule of html5Rules[i]) {
- if (document.querySelectorAll(html5Rule).length > 0) {
- for (let player of document.querySelectorAll(html5Rule)) {
- players.push(player)
- }
- }
- }
- break
- }
- }
- if (players.length == 0) {
- for (let generalPlayerRule of generalPlayerRules) {
- if (document.querySelectorAll(generalPlayerRule).length > 0) {
- for (let player of document.querySelectorAll(generalPlayerRule)) {
- players.push(player)
- }
- }
- }
- }
- if (players.length == 0 && e.target.nodeName != "VIDEO" && document.querySelectorAll("video").length > 0) {
- const videos = document.querySelectorAll("video")
- for (let v of videos) {
- const 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 = []
- players[0] = handle.autoCheck(v)
- gv.autoCheckCount = 1
- break
- }
- }
- }
- if (players.length > 0) {
- const path = e.path || e.composedPath()
- for (let v of players) {
- if (path.indexOf(v) > -1) {
- gv.player = v
- setButton.init()
- return
- }
- }
- }
- switch (e.target.nodeName) {
- case "VIDEO":
- case "OBJECT":
- case "EMBED":
- if (e.target.offsetWidth > 399 && e.target.offsetHeight > 220) {
- gv.player = e.target
- setButton.init()
- }
- break
- default:
- handle.leavePlayer()
- }
- },
- autoCheck(v) {
- let tempPlayer,
- el = v
- gv.playerChilds = []
- gv.playerChilds.push(v)
- while ((el = el.parentNode)) {
- if (Math.abs(v.offsetWidth - el.offsetWidth) < 15 && Math.abs(v.offsetHeight - el.offsetHeight) < 15) {
- tempPlayer = el
- gv.playerChilds.push(el)
- } else {
- break
- }
- }
- return tempPlayer
- },
- leavePlayer() {
- if (gv.controlBtn.style.visibility == "visible") {
- gv.controlBtn.style.opacity = ""
- gv.controlBtn.style.visibility = ""
- gv.picinpicBtn.style.opacity = ""
- gv.picinpicBtn.style.visibility = ""
- gv.player.removeEventListener("mouseleave", handle.leavePlayer, false)
- document.removeEventListener("scroll", handle.scrollFix, false)
- }
- },
- scrollFix(e) {
- clearTimeout(gv.scrollFixTimer)
- gv.scrollFixTimer = setTimeout(() => {
- setButton.locate()
- }, 20)
- },
- hotKey(e) {
- //默认退出键为ESC。需要修改为其他快捷键的请搜索"keycode",修改为按键对应的数字。
- if (e.keyCode == 27) {
- maximize.playerControl()
- }
- //默认画中画快捷键为F2。
- if (e.keyCode == 113) {
- handle.pictureInPicture()
- }
- },
- async receiveMessage(e) {
- switch (e.data) {
- case "iframePicInPic":
- tool.print("messege:iframePicInPic")
- if (!document.pictureInPictureElement) {
- await document
- .querySelector("video")
- .requestPictureInPicture()
- .catch((error) => {
- tool.addTip(gv.btnText.tip)
- })
- } else {
- await document.exitPictureInPicture()
- }
- break
- case "iframeVideo":
- tool.print("messege:iframeVideo")
- if (!gv.isFull) {
- gv.player = gv.mouseoverEl
- setButton.init()
- }
- break
- case "parentFull":
- tool.print("messege:parentFull")
- gv.player = gv.mouseoverEl
- if (gv.isIframe) {
- window.parent.postMessage("parentFull", "*")
- }
- maximize.checkParent()
- maximize.fullWin()
- if (getComputedStyle(gv.player).left != "0px") {
- tool.addStyle("#htmlToothbrush #bodyToothbrush .playerToothbrush {left:0px !important;width:100vw !important;}")
- }
- gv.isFull = true
- break
- case "parentSmall":
- tool.print("messege:parentSmall")
- if (gv.isIframe) {
- window.parent.postMessage("parentSmall", "*")
- }
- maximize.smallWin()
- break
- case "innerFull":
- tool.print("messege:innerFull")
- if (gv.player.nodeName == "IFRAME") {
- gv.player.contentWindow.postMessage("innerFull", "*")
- }
- maximize.checkParent()
- maximize.fullWin()
- break
- case "innerSmall":
- tool.print("messege:innerSmall")
- if (gv.player.nodeName == "IFRAME") {
- gv.player.contentWindow.postMessage("innerSmall", "*")
- }
- maximize.smallWin()
- break
- }
- },
- pictureInPicture() {
- if (!document.pictureInPictureElement) {
- if (gv.player) {
- if (gv.player.nodeName == "IFRAME") {
- gv.player.contentWindow.postMessage("iframePicInPic", "*")
- } else {
- gv.player.parentNode.querySelector("video").requestPictureInPicture()
- }
- } else {
- document.querySelector("video").requestPictureInPicture()
- }
- } else {
- document.exitPictureInPicture()
- }
- },
- }
-
- const maximize = {
- playerControl() {
- if (!gv.player) {
- return
- }
- this.checkParent()
- if (!gv.isFull) {
- if (gv.isIframe) {
- window.parent.postMessage("parentFull", "*")
- }
- if (gv.player.nodeName == "IFRAME") {
- gv.player.contentWindow.postMessage("innerFull", "*")
- }
- this.fullWin()
- if (gv.autoCheckCount > 0 && !tool.isHalfFullClient(gv.playerChilds[0])) {
- if (gv.autoCheckCount > 10) {
- for (let v of gv.playerChilds) {
- v.classList.add("videoToothbrush")
- }
- return
- }
- const tempPlayer = handle.autoCheck(gv.playerChilds[0])
- gv.autoCheckCount++
- maximize.playerControl()
- gv.player = tempPlayer
- maximize.playerControl()
- } else {
- gv.autoCheckCount = 0
- }
- } else {
- if (gv.isIframe) {
- window.parent.postMessage("parentSmall", "*")
- }
- if (gv.player.nodeName == "IFRAME") {
- gv.player.contentWindow.postMessage("innerSmall", "*")
- }
- this.smallWin()
- }
- },
- checkParent() {
- if (gv.isFull) {
- return
- }
- gv.playerParents = []
- let full = gv.player
- while ((full = full.parentNode)) {
- if (full.nodeName == "BODY") {
- break
- }
- if (full.getAttribute) {
- gv.playerParents.push(full)
- }
- }
- },
- fullWin() {
- if (!gv.isFull) {
- document.removeEventListener("mouseover", handle.getPlayer, false)
- gv.backHtmlId = document.body.parentNode.id
- gv.backBodyId = document.body.id
- gv.leftBtn.style.display = "block"
- gv.rightBtn.style.display = "block"
- gv.picinpicBtn.style.display = ""
- gv.controlBtn.style.display = ""
- this.addClass()
- const hostname = document.location.hostname
-
- // 为 Youtube 做特殊处理
- if (
- hostname.includes("www.youtube.com")
- && !document.querySelector("#player-theater-container #movie_player")
- && document.querySelector(".html5-video-container").clientWidth - document.querySelector(".ytp-chrome-bottom").clientWidth > 24
- ) {
- document.querySelector("#movie_player .ytp-size-button").click()
- gv.ytbStageChange = true
- }
-
- // 为 B 站做特殊处理
- if (hostname.includes("bilibili")) {
- document.querySelector(".right-container").style.display = "none"
- document.querySelector("#biliMainHeader").style.display = "none"
- }
- }
- gv.isFull = true
- },
- addClass() {
- document.body.parentNode.id = "htmlToothbrush"
- document.body.id = "bodyToothbrush"
- for (let v of gv.playerParents) {
- v.classList.add("parentToothbrush")
- //父元素position:fixed会造成层级错乱
- if (getComputedStyle(v).position == "fixed") {
- v.classList.add("absoluteToothbrush")
- }
- }
- gv.player.classList.add("playerToothbrush")
- if (gv.player.nodeName == "VIDEO") {
- gv.backControls = gv.player.controls
- gv.player.controls = true
- }
- window.dispatchEvent(new Event("resize"))
- },
- smallWin() {
- document.body.parentNode.id = gv.backHtmlId
- document.body.id = gv.backBodyId
- for (let v of gv.playerParents) {
- v.classList.remove("parentToothbrush")
- v.classList.remove("absoluteToothbrush")
- }
- gv.player.classList.remove("playerToothbrush")
- if (document.location.hostname == "www.youtube.com" && gv.ytbStageChange && document.querySelector("#player-theater-container #movie_player")) {
- document.querySelector("#movie_player .ytp-size-button").click()
- gv.ytbStageChange = false
- }
- if (gv.player.nodeName == "VIDEO") {
- gv.player.controls = gv.backControls
- }
- gv.leftBtn.style.display = ""
- gv.rightBtn.style.display = ""
- gv.controlBtn.style.display = ""
- document.addEventListener("mouseover", handle.getPlayer, false)
- window.dispatchEvent(new Event("resize"))
- const hostname = document.location.hostname
- if (hostname.includes("bilibili")) {
- document.querySelector(".right-container").style.removeProperty("display")
- document.querySelector("#biliMainHeader").style.removeProperty("display")
- }
- gv.isFull = false
- },
- }
-
- const init = () => {
- gv.picinpicBtn = document.createElement("tbdiv")
- gv.picinpicBtn.id = "picinpicBtn"
- gv.picinpicBtn.onclick = () => {
- handle.pictureInPicture()
- }
- document.body.appendChild(gv.picinpicBtn)
- gv.controlBtn = tool.createButton("playerControlBtn")
- gv.leftBtn = tool.createButton("leftFullStackButton")
- gv.rightBtn = tool.createButton("rightFullStackButton")
-
- if (getComputedStyle(gv.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:0px !important;width:100vw !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:2147483646 !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")
- )
- }
- document.addEventListener("mouseover", handle.getPlayer, false)
- document.addEventListener("keydown", handle.hotKey, false)
- window.addEventListener("message", handle.receiveMessage, false)
- tool.print("Ready")
- }
-
- init()
- })()