YouTube Picture-in-Picture

Add a button to enable Picture-in-Picture mode on YouTube

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name        YouTube Picture-in-Picture
// @description Add a button to enable Picture-in-Picture mode on YouTube
// @author      dinoosauro
// @license     mit
// @match       *://*.youtube.com/*
// @version     1.0.3
// @namespace   https://github.com/dinoosauro/yt-picture-in-picture-trigger
// ==/UserScript==

(() => {
    const isMobile = window.location.hostname.startsWith("m.");
    const selector = isMobile ? ".slim-video-action-bar-actions" : "ytd-watch-metadata ytd-menu-renderer #top-level-buttons-computed";
    /**
     * The main container for the button
     */
    const main = document.createElement(`${isMobile ? "" : "yt-"}button-view-model`);
    isMobile ? main.classList.add("yt-spec-button-view-model", "slim_video_action_bar_renderer_button") : main.classList.add("ytd-menu-renderer");
    main.onclick = () => {
        document.pictureInPictureElement ? document.exitPictureInPicture() : document.querySelector("video").requestPictureInPicture();
    };
    /**
     * Checks if the Picture-in-Picture button has been appended to the div
     */
    const checkIfMainAppended = () => Array.from(document.querySelector(selector)?.children ?? []).includes(main);
    /**
     * Create the Picture-in-Picture button
     */
    const mainFn = (() => {
        if (main.childElementCount === 0) {
            isMainCreated = true;
            const btnView = document.createElement(isMobile ? "yt-button-shape" : "button-view-model");
            isMobile ? btnView.classList.add("yt-spec-button-shape-next__button-shape-wiz-class") : btnView.classList.add("yt-spec-button-view-model", "style-scope", "ytd-menu-renderer");
            const btn = document.createElement("button");
            btn.classList.add("yt-spec-button-shape-next", "yt-spec-button-shape-next--tonal", "yt-spec-button-shape-next--mono", "yt-spec-button-shape-next--size-m", "yt-spec-button-shape-next--icon-leading");

            // The Picture-in-Picture icon, provided from Microsoft's Fluent UI Icons (since Google's Material Design icons are too big)
            const iconContainer = document.createElement("div");
            iconContainer.classList.add("yt-spec-button-shape-next__icon");
            iconContainer.setAttribute("aria-hidden", "true");
            const c3Icon = Object.assign(document.createElement("c3-icon"), { style: "width: 24px; height: 24px;" });
            const shapeSpan = document.createElement("span");
            shapeSpan.classList.add("yt-icon-shape", "yt-spec-icon-shape");
            const shapeDiv = Object.assign(document.createElement("div"), {
                style: "width: 100%; height: 100%; display: block; fill: currentcolor;",
            });

            // Add svg properties for the icon
            const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
            for (const [key, val] of [["enable-background", "new 0 0 24 24"], ["height", "24"], ["viewBox", "0 0 24 24"], ["width", "24"], ["focusable", "false"], ["aria-hidden", "true"], ["style", "pointer-events: none; display: inherit; width: 100%; height: 100%;"]]) svg.setAttribute(key, val);
            const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
            path.setAttribute("d", "M2 6.25C2 4.45507 3.45507 3 5.25 3H18.75C20.5449 3 22 4.45507 22 6.25V12H20.5V6.25C20.5 5.2835 19.7165 4.5 18.75 4.5H5.25C4.2835 4.5 3.5 5.2835 3.5 6.25V15.75C3.5 16.7165 4.2835 17.5 5.25 17.5H11V19H5.25C3.45507 19 2 17.5449 2 15.75V6.25ZM14 13C12.8954 13 12 13.8954 12 15V20C12 21.1046 12.8954 22 14 22H21C22.1046 22 23 21.1046 23 20V15C23 13.8954 22.1046 13 21 13H14Z"); // Icon from Fluent UI System Icons: https://github.com/microsoft/fluentui-system-icons/blob/main/assets/Picture%20In%20Picture/SVG/ic_fluent_picture_in_picture_24_regular.svg
            svg.append(path);
            shapeDiv.append(svg);
            
            shapeSpan.append(shapeDiv);
            c3Icon.append(shapeSpan);
            iconContainer.append(c3Icon);

            // Create the "Picture-in-Picture" text
            const text = Object.assign(document.createElement("div"), { textContent: "Picture-in-Picture" });
            text.classList.add("yt-spec-button-shape-next__button-text-content");


            // Touch annimation div
            const touch = Object.assign(document.createElement("yt-touch-feedback-shape"), { style: "border-radius: inherit;" });
            const touchDiv = document.createElement("div");
            touchDiv.classList.add("yt-spec-touch-feedback-shape", "yt-spec-touch-feedback-shape--touch-response");

            // Append everything to the main div
            touch.append(touchDiv);
            btn.append(iconContainer, text, touchDiv);
            btnView.append(btn);
            main.append(btnView);
        }
        if (!checkIfMainAppended()) {
            document.querySelector(selector).append(main);
            setTimeout(() => {
                window.dispatchEvent(new Event("resize")); // Fix display overflow issues in the button div by simulating a window resize.
            }, 100);
        }
    });


    setInterval(() => {
        // Add again the Picture-in-Picture button if it's no longer there (but there's a valid selector)
        !checkIfMainAppended() && document.querySelector(selector) && mainFn();
    }, 1000);


})()