- // ==UserScript==
- // @name ACT.YouTube.DM.PiP-button
- // @name:zh-CN ACT.YouTube.DM.画中画按钮
- // @description Add a PiP button to the player to easy enter Picture-in-Picture mode.
- // @description:zh-CN 为播放器添加画中画按钮,轻松进入画中画模式。
- // @author ACTCD
- // @version 20221105.1
- // @license GPL-3.0-or-later
- // @namespace ACTCD/Userscripts
- // @supportURL https://github.com/ACTCD/Userscripts
- // @homepageURL https://github.com/ACTCD/Userscripts
- // @match *://*.youtube.com/*
- // @grant none
- // @inject-into content
- // @run-at document-start
- // ==/UserScript==
-
- (function () {
- "use strict";
-
- if (!document.pictureInPictureEnabled) {
- console.log("Your browser cannot use picture-in-picture right now");
- return;
- }
-
- // Create PiP Button
- const pip_button = document.createElement("button");
- pip_button.title = "Picture-in-Picture";
- const svg = document.createElement("svg");
- svg.setAttribute("width", "100%");
- svg.setAttribute("height", "100%");
- svg.setAttribute("viewBox", "-8 -6 36 36");
- const path = document.createElement("path");
- const path1 =
- "M2.5,17A1.5,1.5,0,0,1,1,15.5v-9A1.5,1.5,0,0,1,2.5,5h13A1.5,1.5,0,0,1,17,6.5V10h1V6.5A2.5,2.5,0,0,0,15.5,4H2.5A2.5,2.5,0,0,0,0,6.5v9A2.5,2.5,0,0,0,2.5,18H7V17Z M18.5,11h-8A2.5,2.5,0,0,0,8,13.5v5A2.5,2.5,0,0,0,10.5,21h8A2.5,2.5,0,0,0,21,18.5v-5A2.5,2.5,0,0,0,18.5,11Z";
- const path2 =
- "M18.5,11H18v1h.5A1.5,1.5,0,0,1,20,13.5v5A1.5,1.5,0,0,1,18.5,20h-8A1.5,1.5,0,0,1,9,18.5V18H8v.5A2.5,2.5,0,0,0,10.5,21h8A2.5,2.5,0,0,0,21,18.5v-5A2.5,2.5,0,0,0,18.5,11Z M14.5,4H2.5A2.5,2.5,0,0,0,0,6.5v8A2.5,2.5,0,0,0,2.5,17h12A2.5,2.5,0,0,0,17,14.5v-8A2.5,2.5,0,0,0,14.5,4Z";
- path.setAttribute("fill", "#fff");
- svg.append(path);
- if (location.hostname == "m.youtube.com") {
- pip_button.style.setProperty("position", "absolute");
- pip_button.style.setProperty("z-index", "100");
- pip_button.style.setProperty("top", "0px");
- pip_button.style.setProperty("left", "4px");
- svg.setAttribute("width", "42px");
- } else {
- pip_button.className = "ytp-button";
- path.id = "ACT_PiP_Path";
- const use = document.createElement("use");
- use.className = "ytp-svg-shadow";
- use.setAttribute("href", "#" + path.id);
- path.before(use);
- }
- pip_button.addEventListener(
- "click",
- (event) => {
- const video = document.querySelector("video[src]");
- if (!video) return;
- if (video.webkitPresentationMode === undefined) {
- if (document.pictureInPictureElement) {
- document.exitPictureInPicture();
- } else {
- video.requestPictureInPicture();
- }
- } else {
- if (video.webkitPresentationMode != "inline") {
- video.webkitSetPresentationMode("inline");
- } else {
- video.webkitSetPresentationMode("picture-in-picture");
- }
- }
- event.preventDefault();
- event.stopImmediatePropagation();
- },
- true,
- );
- const pip_button_act = (pathx) => {
- path.setAttribute("d", pathx);
- pip_button.innerHTML = svg.outerHTML;
- };
- pip_button_act(path1);
-
- // Insert PiP Button (desktop) // Fixed once for unreliable @run-at document-start
- document.querySelector(".ytp-miniplayer-button")?.before(pip_button);
-
- // Video element initialization
- const enterpictureinpicture = (e) => pip_button_act(path2);
- const leavepictureinpicture = (e) => pip_button_act(path1);
- const webkitpresentationmodechanged = (event) => {
- event.target.webkitPresentationMode == "picture-in-picture"
- ? (pip_button_act(path2), event.stopImmediatePropagation())
- : pip_button_act(path1);
- };
- const pip_init = (video) => {
- if (!video || video.nodeName != "VIDEO" || !video.hasAttribute("src"))
- return;
- if (video.webkitPresentationMode === undefined) {
- video.addEventListener("enterpictureinpicture", enterpictureinpicture);
- video.addEventListener("leavepictureinpicture", leavepictureinpicture);
- } else {
- video.addEventListener(
- "webkitpresentationmodechanged",
- webkitpresentationmodechanged,
- true,
- );
- }
- };
- pip_init(document.querySelector("video[src]"));
-
- // Dynamic adjustment
- new MutationObserver((mutationList) => {
- mutationList.forEach((mutation) => {
- if (mutation.type == "childList") {
- mutation.addedNodes.forEach((node) => {
- if (node.nodeType != Node.ELEMENT_NODE) return;
- node.nodeName == "VIDEO" && pip_init(node);
- node.classList.contains("ytp-miniplayer-button") &&
- node.before(pip_button); // Insert PiP Button (desktop)
- });
- mutation.removedNodes.forEach((node) => {
- if (node.nodeType != Node.ELEMENT_NODE) return;
- node.classList.contains("ytp-miniplayer-button") &&
- pip_button.remove();
- node.id == "player-control-overlay" && pip_button.remove();
- });
- }
- if (mutation.type == "attributes") {
- mutation.target.nodeName == "VIDEO" &&
- mutation.attributeName == "src" &&
- pip_init(mutation.target);
- if (
- mutation.target.id == "player-control-overlay" &&
- mutation.attributeName == "class"
- ) {
- // Insert PiP Button (mobile)
- mutation.target.classList.contains("fadein")
- ? document.querySelector("#player")?.append(pip_button)
- : pip_button.remove();
- }
- if (
- mutation.attributeName == "class" &&
- mutation.target ==
- document.querySelector(".player-controls-top")?.parentNode
- ) {
- mutation.target.classList.contains("player-controls-hide")
- ? pip_button.remove()
- : document
- .querySelector("#player-control-overlay")
- ?.classList.contains("fadein") &&
- document.querySelector("#player")?.append(pip_button);
- }
- if (
- mutation.target.id == "player" &&
- mutation.attributeName == "hidden"
- ) {
- mutation.target.hasAttribute("hidden") &&
- (pip_button.remove(), document.exitPictureInPicture());
- }
- }
- });
- }).observe(document, { subtree: true, childList: true, attributes: true });
- })();