// ==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);
}