PiPifier

PiPifier is an extension that lets you use every HTML5 video in Picture in Picture mode

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

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este 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         PiPifier
// @namespace    https://github.com/Willian-Zhang/PiPifier
// @version      0.3
// @description  PiPifier is an extension that lets you use every HTML5 video in Picture in Picture mode
// @author       @arno_app <https://twitter.com/arno_app>, @Cacauu_de <https://twitter.com/Cacauu_de>, @Willian <https://github.com/willian-zhang>
// @match        */*
// @grant        none
// ==/UserScript==

//image URLs
var whiteSVG_Icon = `data:image/svg+xml;utf8,<?xml version="1.0" encoding="UTF-8"?>
<svg width="671px" height="441px" viewBox="0 0 671 441" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- Generator: Sketch 40.3 (33839) - http://www.bohemiancoding.com/sketch -->
    <title>PiP_Toolbar_Icon_white</title>
    <desc>Created with Sketch.</desc>
    <defs>
        <polyline id="path-1" points="617.200445 188.359322 617.200445 0 0 0 0 366.254237 252.55902 366.254237"></polyline>
        <mask id="mask-2" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="0" y="0" width="617.200445" height="366.254237" fill="white">
            <use xlink:href="#path-1"></use>
        </mask>
    </defs>
    <g id="UI" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="PiP_Toolbar_Icon_white" transform="translate(-15.000000, -130.000000)">
            <g id="Toolbar-Icon" transform="translate(15.000000, 130.000000)">
                <use id="Combined-Shape" stroke="#FFFFFF" mask="url(#mask-2)" stroke-width="54" xlink:href="#path-1"></use>
                <rect id="Rectangle" fill="#FFFFFF" x="263.020045" y="197.328814" width="407.979955" height="243.671186"></rect>
                <path d="M166.149412,103.362155 L166.149412,245.485858 L131.742911,245.485858 L131.742911,103.706611 L89.1651635,115.230311 L149.582508,5.46510893 L209.999853,115.230311 L166.149412,103.362155 Z" id="Combined-Shape" fill="#FFFFFF" transform="translate(149.582508, 125.475483) rotate(-236.000000) translate(-149.582508, -125.475483) "></path>
            </g>
        </g>
    </g>
</svg>`;
var blackSVG_Icon = `data:image/svg+xml;utf8,<?xml version="1.0" encoding="UTF-8"?>
<svg width="701px" height="701px" viewBox="0 0 701 701" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- Generator: Sketch 40.3 (33839) - http://www.bohemiancoding.com/sketch -->
    <title>PiP_Toolbar_Icon</title>
    <desc>Created with Sketch.</desc>
    <defs>
        <polyline id="path-1" points="617.200445 188.359322 617.200445 0 0 0 0 366.254237 252.55902 366.254237"></polyline>
        <mask id="mask-2" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="0" y="0" width="617.200445" height="366.254237" fill="white">
            <use xlink:href="#path-1"></use>
        </mask>
    </defs>
    <g id="UI" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="PiP_Toolbar_Icon">
            <g id="Toolbar-Icon" transform="translate(15.000000, 130.000000)">
                <use id="Combined-Shape" stroke="#000000" mask="url(#mask-2)" stroke-width="54" xlink:href="#path-1"></use>
                <rect id="Rectangle" fill="#000000" x="263.020045" y="197.328814" width="407.979955" height="243.671186"></rect>
                <path d="M166.149412,103.362155 L166.149412,245.485858 L131.742911,245.485858 L131.742911,103.706611 L89.1651635,115.230311 L149.582508,5.46510893 L209.999853,115.230311 L166.149412,103.362155 Z" id="Combined-Shape" fill="#000000" transform="translate(149.582508, 125.475483) rotate(-236.000000) translate(-149.582508, -125.475483) "></path>
            </g>
        </g>
    </g>
</svg>`;

//safari.self.addEventListener("message", messageHandler); // Message recieved from Swift code
window.onfocus = function() {
    previousResult = null;
    checkForVideo();
}; // Tab selected
new MutationObserver(checkForVideo).observe(document, {subtree: true, childList: true}); // DOM changed

//function dispatchMessage(messageName, parameters) {
//    safari.extension.dispatchMessage(messageName, parameters);
//}

function messageHandler(event) {
    if (event.name === "enablePiP" && getVideo() != null) {
        enablePiP();
    } else if (event.name === "addCustomPiPButtonToPlayer") {
        addCustomPiPButtonToPlayer(event.message)
    }
}
function addCustomPiPButtonToPlayer(message){
    message.callback();
}

var previousResult = null;
var videoCheck = {found: true}
function checkForVideo() {
    if (getVideo() != null) {
        addCustomPiPButtons();
        if (previousResult === null || previousResult === false) {
            //dispatchMessage("videoCheck", {found: true});
            console.warn("videoCheck", {found: true})
            videoCheck = {found: true}
            // enablePiP();
        }
        previousResult = true;
    } else if (window == window.top) {
        if (previousResult === null || previousResult === true) {
            //dispatchMessage("videoCheck", {found: false});
            console.warn("videoCheck", {found: false})
            videoCheck = {found: false}
        }
        previousResult = false;
    }
}

function getVideo() {
    return document.getElementsByTagName('video')[0];
}

async function action(video) {
    if (video.hasAttribute('__pip__')) {
        await document.exitPictureInPicture();
    } else {
        await video.requestPictureInPicture();
        video.setAttribute('__pip__', true);
        video.addEventListener('leavepictureinpicture', event => {
            video.removeAttribute('__pip__');
        }, {
            once: true
        });
    }
}

function enablePiP() {
    let video = getVideo()
    if(video.webkitSetPresentationMode){
        // safari
        video.webkitSetPresentationMode('picture-in-picture');
    }else{
        //chrome
        action(video);
    }
}

//----------------- Custom Button Methods -----------------

var players = [
               {name: "YouTube", shouldAddButton: shouldAddYouTubeButton, addButton: addYouTubeButton},
               {name: "VideoJS", shouldAddButton: shouldAddVideoJSButton, addButton: addVideoJSButton},
               {name: "Netflix", shouldAddButton: shouldAddNetflixButton, addButton: addNetflixButton},
               {name: "Wistia", shouldAddButton: shouldAddWistiaButton, addButton: addWistiaButton},
               //TODO: add other players here
               ];

let pipCheck = function pipCheck(message){
    addCustomPiPButtonToPlayer(message);
}
function addCustomPiPButtons() {
    for (const player of players) {
        if (player.shouldAddButton()) {
            //dispatchMessage("pipCheck", {callback: player.addButton.name}) //Sets the callback to the player's addButton
            pipCheck({callback: player.addButton});
        }
    }
}

//----------------- Player Implementations -------------------------

function shouldAddYouTubeButton() {
    //check if on youtube or player is embedded
    return (location.hostname.match(/^(www\.)?youtube\.com$/)
            || document.getElementsByClassName("ytp-right-controls").length > 0)
    && document.getElementsByClassName('PiPifierButton').length == 0;
}

function addYouTubeButton() {
    if (!shouldAddYouTubeButton()) return;
    var button = document.createElement("button");
    button.className = "ytp-button PiPifierButton";
    button.title = "PiP (by PiPifier)";
    button.onclick = enablePiP;
    //TODO add style
    //button.style.backgroundImage = 'url('+ whiteSVG_Icon + ')';
    var buttonImage = document.createElement("img");
    buttonImage.src = whiteSVG_Icon;
    buttonImage.width = 22;
    buttonImage.height = 36;
    button.appendChild(buttonImage);

    document.getElementsByClassName("ytp-right-controls")[0].appendChild(button);
}


function shouldAddVideoJSButton() {
    return document.getElementsByClassName('vjs-control-bar').length > 0
    && document.getElementsByClassName('PiPifierButton').length == 0;
}


function addVideoJSButton() {
    if (!shouldAddVideoJSButton()) return;
    var button = document.createElement("button");
    button.className = "PiPifierButton vjs-control vjs-button";
    button.title = "PiP (by PiPifier)";
    button.onclick = enablePiP;
    var buttonImage = document.createElement("img");
    buttonImage.src = whiteSVG_Icon;
    buttonImage.width = 16;
    buttonImage.height = 30;
    button.appendChild(buttonImage);
    var fullscreenButton = document.getElementsByClassName("vjs-fullscreen-control")[0];
    fullscreenButton.parentNode.insertBefore(button, fullscreenButton);
}

function shouldAddWistiaButton() {
    return document.getElementsByClassName('wistia_playbar').length > 0
    && document.getElementsByClassName('PiPifierButton').length == 0;
}

function addWistiaButton() {
    if (!shouldAddWistiaButton()) return;
    var button = document.createElement("button");
    button.className = "PiPifierButton w-control w-control--fullscreen w-is-visible";
    button.alt = "Picture in Picture";
    button.title = "PiP (by PiPifier)";
    button.onclick = enablePiP;
    var buttonImage = document.createElement("img");
    buttonImage.src = whiteSVG_Icon;
    buttonImage.width = 28;
    buttonImage.height = 18;
    buttonImage.style.verticalAlign = "middle";
    button.appendChild(buttonImage);
    document.getElementsByClassName("w-control-bar__region--airplay")[0].appendChild(button);
}


function shouldAddNetflixButton() {
    return location.hostname.match('netflix')
    && document.getElementsByClassName('PiPifierButton').length == 0;
}

function addNetflixButton(timeOutCounter) {
    if (!shouldAddNetflixButton()) return;
    if (timeOutCounter == null) timeOutCounter = 0;
    var button = document.createElement("button");
    button.className = "PiPifierButton";
    button.title = "PiP (by PiPifier)";
    button.onclick = enablePiP;
    button.style.backgroundColor = "transparent";
    button.style.border = "none";
    button.style.maxHeight = "inherit";
    button.style.width = "70px";
    button.style.marginRight = "2px";
    var buttonImage = document.createElement("img");
    buttonImage.src = whiteSVG_Icon;
    buttonImage.style.verticalAlign = "middle";
    buttonImage.style.maxHeight = "40%";
    button.appendChild(buttonImage);
    var playerStatusDiv = document.getElementsByClassName("player-status")[0];
    if (playerStatusDiv == null && timeOutCounter < 3) {
        //this is needed because the div is sometimes not reachable on the first load
        //also necessary to count up and stop at some time to avoid endless loop on main netflix page
        setTimeout(function() {addNetflixButton(timeOutCounter+1);}, 3000);
        return;
    }
    playerStatusDiv.insertBefore(button, playerStatusDiv.firstChild);
}