diff --git a/README.md b/README.md index b5ebeec..7e93fc9 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,9 @@ For developement: Xcode 8 4. Enable 'Run PiPifier'
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. @@ -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) diff --git a/userscript/all.user.js b/userscript/all.user.js new file mode 100644 index 0000000..45f8e56 --- /dev/null +++ b/userscript/all.user.js @@ -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 , @Cacauu_de , @Willian +// @match */* +// @grant none +// ==/UserScript== + +//image URLs +var whiteSVG_Icon = `data:image/svg+xml;utf8, + + + PiP_Toolbar_Icon_white + Created with Sketch. + + + + + + + + + + + + + + + +`; +var blackSVG_Icon = `data:image/svg+xml;utf8, + + + PiP_Toolbar_Icon + Created with Sketch. + + + + + + + + + + + + + + + +`; + +//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); +} \ No newline at end of file