ACT.YouTube.DM.PiP-button

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

目前為 2022-04-06 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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            20220406.1
// @license            GPL-3.0-or-later
// @namespace          ACTCD/Userscripts
// @supportURL         https://github.com/ACTCD/Userscripts#contact
// @homepageURL        https://github.com/ACTCD/Userscripts
// @match              *://*.youtube.com/*
// @grant              none
// @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 => {
        if (document.pictureInPictureElement) {
            document.exitPictureInPicture();
        } else {
            document.querySelector("video[src]")?.requestPictureInPicture();
        }
        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 PiP_Fix = e => e.stopPropagation();
    const onEnterPip = e => pip_button_act(path2);
    const onExitPip = e => pip_button_act(path1);
    const pip_init = video => {
        if (!video || video.nodeName != 'VIDEO' || !video.hasAttribute("src")) return;
        video.addEventListener('webkitpresentationmodechanged', PiP_Fix, true);
        video.addEventListener("leavepictureinpicture", onExitPip);
        video.addEventListener('enterpictureinpicture', onEnterPip);
    }
    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 });

})();