视频网页全屏(改)

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

当前为 2025-09-28 提交的版本,查看 最新版本

// ==UserScript==
// @name                视频网页全屏(改)
// @name:en             Maximize Video(Modify)
// @name:zh-CN          视频网页全屏(改)
// @name:zh-TW          視頻網頁全屏(改)
// @namespace           https://greasyfork.org/zh-CN/users/178351-yesilin
// @description         让所有视频网页全屏,开启画中画功能
// @description:en      Maximize all video players.Support Piture-in-picture.
// @description:zh-CN   让所有视频网页全屏,开启画中画功能
// @description:zh-TW   讓所有視頻網頁全屏,開啟畫中畫功能
// @author              冻猫, ryomahan, YeSilin
// @include             *
// @exclude             *www.w3school.com.cn*
// @version             12.5.10
// @run-at              document-end
// @license             MIT
// @icon                data:image/svg+xml;base64,PHN2ZyBpZD0i5Zu+5bGCXzEiIGRhdGEtbmFtZT0i5Zu+5bGCIDEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDIwMCAxOTUuNDMiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDojZmYzMzVmO308L3N0eWxlPjwvZGVmcz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0xMDEsMTE0Ljg5czY1LjIzLS42NCw3NS4wNyw0Ni44OWwtMTguNTYsNS4zMSwzLjE0LDIwLjQyYTUyLDUyLDAsMCwxLTIzLjUtMS4xNyw1NC41NSw1NC41NSwwLDAsMS0zNy02Ny43MXEuMzgtMS4yNy44MS0yLjUyWiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAtMi4yOSkiLz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0xMDcuNiwxMDAuMTFzMzEuOS01Ni43Myw3OC4wNS00MS40N2wtNC45LDE4LjcxTDIwMCw4NC44NWE1Mi40NCw1Mi40NCwwLDAsMS0xMi43NiwxOS43OCw1NC41LDU0LjUsMCwwLDEtNzcuMDUtMS40OWMtLjY1LS42OC0xLjI4LTEuMzctMS45LTIuMDhaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIC0yLjI5KSIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTk2Ljg2LDk1LjY0UzUwLjQsNTAuMTMsNzcsOS41N0w5My44OSwxOSwxMDYuMzMsMi4yOWE1Miw1MiwwLDAsMSwxNiwxNy40Myw1NC42MSw1NC42MSwwLDAsMS0yMS41MSw3NC4xNmMtLjc4LjQzLTEuNTcuODQtMi4zNiwxLjIzWiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAtMi4yOSkiLz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik04OC4zLDEwMy4zcy01NiwzMy04OC4zLTMuMTlMMTMuNDUsODYuMjNsLTEzLTE2QTUxLjc0LDUxLjc0LDAsMCwxLDIxLjQyLDU5LjY1YTU0LjU0LDU0LjU0LDAsMCwxLDY2LDM5Ljg4Yy4yMi44OC40MiwxLjc2LjU5LDIuNjVaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIC0yLjI5KSIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTkwLjM4LDExMy4yNHMxNy40OSw2Mi42OC0yNS44NCw4NC40N2wtOS43OC0xNi41My0xOSw4LjI5YTUxLjQxLDUxLjQxLDAsMCwxLTQuODktMjMsNTQuNDksNTQuNDksMCwwLDEsNTUuNjYtNTMuM2MuOTQsMCwxLjg4LjA2LDIuODIuMTNaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIC0yLjI5KSIvPjwvc3ZnPg==
// ==/UserScript==

(() => {
  // 全局变量存储对象 (global variables)
  const gv = {
    isFull: false, // 是否处于全屏状态
    isIframe: false, // 当前页面是否在iframe中
    autoCheckCount: 0, // 自动检测计数器
    btnText: {}, // 按钮文本(多语言支持)
    player: null, // 当前激活的视频播放器元素
    playerChilds: [], // 播放器子元素列表
    playerParents: [], // 播放器父元素列表
    backHtmlId: "", // 全屏前html元素的id
    backBodyId: "", // 全屏前body元素的id
    backControls: null, // 全屏前视频控件状态
    ytbStageChange: false, // YouTube舞台模式切换标记
    scrollFixTimer: null, // 滚动修正定时器
    mouseoverEl: null, // 鼠标悬停元素
    // 按钮元素
    picinpicBtn: null,
    controlBtn: null,
    leftBtn: null,
    rightBtn: null,
  };

  // 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]"];

  // 判断当前页面是否在iframe中
  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 = {
    /**
     * 带时间戳的日志打印
     * @param {string} log - 日志内容
     */
    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);
    },

    /**
     * 获取元素的位置信息
     * @param {HTMLElement} element - 目标元素
     * @returns {Object} 包含页面坐标和屏幕坐标的对象
     */
    getRect(element) {
      const rect = element.getBoundingClientRect();
      const scroll = tool.getScroll();
      return {
        pageX: rect.left + scroll.left, // 元素左上角在页面中的X坐标
        pageY: rect.top + scroll.top, // 元素左上角在页面中的Y坐标
        screenX: rect.left, // 元素左上角在视口中的X坐标
        screenY: rect.top, // 元素左上角在视口中的Y坐标
      };
    },

    /**
     * 判断元素是否半全屏显示(宽或高接近视口)
     * @param {HTMLElement} element - 目标元素
     * @returns {boolean} 是否半全屏
     */
    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;
      }
    },

    /**
     * 判断元素是否完全全屏显示(宽和高都接近视口)
     * @param {HTMLElement} element - 目标元素
     * @returns {boolean} 是否完全全屏
     */
    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;
      }
    },

    /**
     * 获取页面滚动距离
     * @returns {Object} 包含左右和上下滚动距离的对象
     */
    getScroll() {
      return {
        left: document.documentElement.scrollLeft || document.body.scrollLeft,
        top: document.documentElement.scrollTop || document.body.scrollTop,
      };
    },

    /**
     * 获取视口尺寸
     * @returns {Object} 包含视口宽高的对象
     */
    getClient() {
      return {
        width: document.compatMode == "CSS1Compat" ? document.documentElement.clientWidth : document.body.clientWidth,
        height:
          document.compatMode == "CSS1Compat" ? document.documentElement.clientHeight : document.body.clientHeight,
      };
    },

    /**
     * 向页面添加CSS样式
     * @param {string} css - CSS样式字符串
     * @returns {HTMLElement} 创建的style元素
     */
    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;
    },

    /**
     * 匹配字符串与规则(支持通配符*)
     * @param {string} str - 要匹配的字符串
     * @param {string} rule - 包含*的规则字符串
     * @returns {boolean} 是否匹配
     */
    matchRule(str, rule) {
      return new RegExp("^" + rule.split("*").join(".*") + "$").test(str);
    },

    /**
     * 创建按钮元素
     * @param {string} id - 按钮id
     * @returns {HTMLElement} 创建的按钮元素
     */
    createButton(id) {
      const btn = document.createElement("tbdiv");
      btn.id = id;
      btn.onclick = () => {
        maximize.playerControl();
      };
      document.body.appendChild(btn);
      return btn;
    },

    /**
     * 显示提示信息
     * @param {string} str - 提示文本
     * @returns {Promise} 提示显示完成的Promise
     */
    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();
      }
      // 如果在iframe中且播放器半全屏,向父窗口发送消息
      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";

      // 支持画中画功能且播放器不是OBJECT/EMBED时显示画中画按钮
      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;
      // 处理可信类型(Trusted Types)安全策略
      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";
      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;
      gv.picinpicBtn.style.left = playerRect.screenX - 64 + gv.player.offsetWidth - 54 + "px";
    },
  };

  // 事件处理相关方法
  const handle = {
    /**
     * 获取鼠标悬停的播放器元素
     * @param {MouseEvent} e - 鼠标事件对象
     */
    getPlayer(e) {
      if (gv.isFull) {
        return;
      }
      gv.mouseoverEl = e.target;
      const hostname = document.location.hostname;
      let players = [];

      // 1. 优先使用站点特定规则匹配播放器
      for (let i in html5Rules) {
        if (tool.matchRule(hostname, i)) {
          for (let html5Rule of html5Rules[i]) {
            const elements = document.querySelectorAll(html5Rule);
            if (elements.length > 0) {
              players.push(...elements); // 直接扩展,避免二次查询
            }
          }
          break;
        }
      }

      // 2. 站点规则未匹配到,使用通用规则
      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);
            }
          }
        }
      }

      // 3. 通用规则未匹配到,尝试直接匹配video元素
      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;
          }
        }
      }

      // 4. 从匹配到的播放器中找到鼠标所在的那个
      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;
          }
        }
      }

      // 5. 直接处理视频相关元素(VIDEO/OBJECT/EMBED)
      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();
      }
    },

    /**
     * 自动检测播放器的父容器(寻找与视频尺寸相近的容器)
     * @param {HTMLElement} v - 视频元素
     * @returns {HTMLElement} 最合适的播放器容器
     */
    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);
    },

    /**
     * 处理键盘快捷键
     * @param {KeyboardEvent} e - 键盘事件对象
     */
    hotKey(e) {
      // ESC键:切换全屏状态(默认)
      if (e.keyCode == 27) {
        maximize.playerControl();
      }
      // F2键:切换画中画(默认)
      if (e.keyCode == 113) {
        handle.pictureInPicture();
      }
    },

    /**
     * 处理鼠标中键点击事件
     * @param {MouseEvent} e - 鼠标事件对象
     */
    mouseMiddleClick(e) {
      // 只在全屏状态下响应鼠标中键(button=1表示中键)
      if (gv.isFull && e.button === 1) {
        e.preventDefault(); // 阻止默认行为(如打开链接)
        maximize.playerControl(); // 退出全屏
      }
    },

    /**
     * 处理跨窗口消息
     * @param {MessageEvent} e - 消息事件对象
     */
    async receiveMessage(e) {
      switch (e.data) {
        case "iframePicInPic":
          tool.print("messege:iframePicInPic");
          // 处理iframe中的画中画请求
          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");
          // 处理iframe中的视频全屏请求
          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");
          // 处理iframe内部的全屏请求
          if (gv.player.nodeName == "IFRAME") {
            gv.player.contentWindow.postMessage("innerFull", "*");
          }
          maximize.checkParent();
          maximize.fullWin();
          break;
        case "innerSmall":
          tool.print("messege:innerSmall");
          // 处理iframe内部的退出全屏请求
          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") {
            // 向iframe发送画中画请求
            gv.player.contentWindow.postMessage("iframePicInPic", "*");
          } else {
            // 直接请求画中画
            gv.player.parentNode.querySelector("video").requestPictureInPicture();
          }
        } else {
          // 没有指定播放器时使用第一个video元素
          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();

        // 自动调整播放器容器(最多尝试10次)
        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;
      // 遍历父节点直到body
      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);
        // 保存原始id(用于恢复)
        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(".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.removeProperty("display");
          document.querySelector("#biliMainHeader")?.style.removeProperty("display");
        }
      }
      gv.isFull = true;
    },

    /**
     * 为全屏状态添加样式类
     */
    addClass() {
      // 修改根元素id(用于CSS选择器定位)
      document.body.parentNode.id = "htmlToothbrush";
      document.body.id = "bodyToothbrush";
      // 为父元素添加样式类
      for (let v of gv.playerParents) {
        v.classList.add("parentToothbrush");
        // 处理fixed定位的父元素(避免层级问题)
        if (getComputedStyle(v).position == "fixed") {
          v.classList.add("absoluteToothbrush");
        }
      }
      // 为播放器添加样式类
      gv.player.classList.add("playerToothbrush");
      // 确保video元素显示控件
      if (gv.player.nodeName == "VIDEO") {
        gv.backControls = gv.player.controls;
        gv.player.controls = true;
      }
      // 触发resize事件(刷新播放器尺寸)
      window.dispatchEvent(new Event("resize"));
    },

    /**
     * 退出全屏状态(恢复原始状态)
     */
    smallWin() {
      // 恢复原始id
      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");

      // YouTube特殊处理:恢复舞台模式
      if (document.location.hostname == "www.youtube.com" && gv.ytbStageChange) {
        document.querySelector("#movie_player .ytp-size-button").click();
        gv.ytbStageChange = false;
      }

      // 恢复video控件状态
      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);
      // 触发resize事件
      window.dispatchEvent(new Event("resize"));
      // B站特殊处理:隐藏侧边栏和头部
      const hostname = document.location.hostname;
      if (hostname.includes("bilibili")) {
        document.querySelector(".right-container")?.style.setProperty("display", "none");
        document.querySelector("#biliMainHeader")?.style.setProperty("display", "none");
      }
      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(
        [
          // 针对B站播放器的特殊样式
          "#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;}",
          // 修正fixed定位的父元素
          "#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);
    // 添加鼠标中键点击事件监听
    document.addEventListener("mousedown", handle.mouseMiddleClick, false);

    tool.print("Ready");
  };

  // 初始化脚本
  init();
})();