Greasy Fork

Greasy Fork is available in English.

视频网页全屏(改)

让所有视频网页全屏,开启画中画功能

当前为 2024-05-15 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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.1
// @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": ["#movie_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() {
      const playerRect = tool.getRect(gv.player)
      gv.controlBtn.style.opacity = "0.5"
      gv.controlBtn.innerHTML = 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 = 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
        if (document.location.hostname == "www.youtube.com" && !document.querySelector("#player-theater-container #movie_player")) {
          document.querySelector("#movie_player .ytp-size-button").click()
          gv.ytbStageChange = true
        }
        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
        if (hostname.includes("bilibili")) {
            document.querySelector(".right-container").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")
      }
      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()
})()