ACT.YouTube.DM.PiP-button

Add a PiP button to the player to easy enter Picture-in-Picture mode.

作者のサイトでサポートを受ける。または、このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==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 });
})();