Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ For developement: Xcode 8
4. Enable 'Run PiPifier'<br />
5. Select 'Run PiPifier' on a website with a video

# Userscript
https://greasyfork.org/scripts/374160-pipifier

# How can I help?
If you are a developer feel free to make any addition to improve PiPifier.

Expand All @@ -57,4 +60,5 @@ If you are user and want a native PiP button in any video player please make a r
The code is a bit dirty and there are some unused parts in it (Tried to add more features before I needed to focus on other things). If you want to help me clean it up feel free to do so :)

# Contact
Made by [@arno_app](https://twitter.com/arno_app) with an important bug fix by [@Cacauu_de](https://twitter.com/Cacauu_de).
Made by [@arno_app](https://twitter.com/arno_app) with an important bug fix by [@Cacauu_de](https://twitter.com/Cacauu_de)
Userscript port & Chrome support: [Willian](https://github.com/willian-zhang)
250 changes: 250 additions & 0 deletions userscript/all.user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
// ==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);
}