Super PiP

A simple script to add a Picture in Picture button to multiple Sites.

  1. // ==UserScript==
  2. // @name Super PiP
  3. // @namespace https://flawcra.cc/
  4. // @match *://*/*
  5. // @grant none
  6. // @version 1.1.1-GitHub
  7. // @author FlawCra
  8. // @license Apache License 2.0
  9. // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0iI0Y5RjlGOSI+CiAgPHBhdGggc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNNC41IDQuNWwxNSAxNW0wIDBWOC4yNW0wIDExLjI1SDguMjUiIC8+Cjwvc3ZnPg==
  10. // @description A simple script to add a Picture in Picture button to multiple Sites.
  11. // ==/UserScript==
  12. const SVG = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="#F9F9F9">
  13. <path stroke-linecap="round" stroke-linejoin="round" d="M4.5 4.5l15 15m0 0V8.25m0 11.25H8.25" />
  14. </svg>`;
  15.  
  16. const SVG64 = `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZT0iI0Y5RjlGOSI+CiAgPHBhdGggc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNNC41IDQuNWwxNSAxNW0wIDBWOC4yNW0wIDExLjI1SDguMjUiIC8+Cjwvc3ZnPg==`;
  17.  
  18. const netflix = () => {
  19. if (document.querySelector("#popout-btn")) return;
  20. let data = {};
  21. data.masterDiv = document.querySelector(`[data-uia="controls-standard"] > div > div:nth-child(3) > div > div:nth-child(3)`)
  22. if (!data.masterDiv) return;
  23.  
  24. data.exampleSpacer = data.masterDiv.querySelector(`[class^="default-ltr-"]`);
  25. data.spacerClass = data.exampleSpacer.getAttribute("class");
  26. data.spacerStyle = data.exampleSpacer.getAttribute("style");
  27.  
  28. data.exampleIcon = data.masterDiv.querySelector(`.medium`);
  29. data.iconClass = data.exampleIcon.getAttribute("class");
  30.  
  31. data.exampleButton = data.exampleIcon.querySelector("button");
  32. data.buttonClass = data.exampleButton.getAttribute("class");
  33.  
  34. data.exampleButtonDiv = data.exampleButton.querySelector("div");
  35. data.buttonDivClass = data.exampleButtonDiv.getAttribute("class");
  36. data.buttonDivRole = data.exampleButtonDiv.getAttribute("role");
  37.  
  38. data.buttonDiv = document.createElement("div");
  39. data.buttonDiv.setAttribute("class", data.buttonDivClass);
  40. data.buttonDiv.setAttribute("role", data.buttonDivRole);
  41. data.buttonDiv.innerHTML = SVG;
  42.  
  43. data.button = document.createElement("button");
  44. data.button.setAttribute("class", data.buttonClass);
  45. data.button.setAttribute("id", "popout-btn");
  46. data.button.addEventListener("click", () => {
  47. const videoElement = document.querySelector(`[data-uia="video-canvas"]`).querySelector("video");
  48. if (document.pictureInPictureElement) {
  49. document.exitPictureInPicture();
  50. } else {
  51. videoElement.requestPictureInPicture();
  52. }
  53. });
  54.  
  55. data.button.addEventListener("mouseenter", () => {
  56. data.button.classList.add("active");
  57. });
  58.  
  59. data.button.addEventListener("mouseleave", () => {
  60. data.button.classList.remove("active");
  61. });
  62.  
  63. data.button.appendChild(data.buttonDiv);
  64.  
  65. data.icon = document.createElement("div");
  66. data.icon.setAttribute("class", data.iconClass);
  67. data.icon.appendChild(data.button);
  68.  
  69. data.spacer = document.createElement("div");
  70. data.spacer.setAttribute("class", data.spacerClass);
  71. data.spacer.setAttribute("style", data.spacerStyle);
  72.  
  73. data.masterDiv.appendChild(data.spacer);
  74. data.masterDiv.appendChild(data.icon);
  75. };
  76.  
  77. matchDomain(`https:\/\/(.*)\.?netflix\.com(.*)`, () => loop(netflix));
  78.  
  79. const youtube = () => {
  80. const elem = document.querySelector(".ytp-pip-button");
  81. if (!elem) return;
  82. if (elem.style.display == "") return;
  83.  
  84. elem.style.display = "";
  85. elem.innerHTML = SVG;
  86. };
  87.  
  88. matchDomain(`https:\/\/(.*)\.?youtube\.com(.*)`, () => loop(youtube));
  89.  
  90. const disneyPlus = () => {
  91. let data = {};
  92. data.masterDiv = document.querySelector(".controls__right");
  93. if (!data.masterDiv) return;
  94.  
  95. if(document.pictureInPictureElement && document.pictureInPictureElement != document.querySelector(`[id^="hudson-"].video_view--theater`).querySelector("video")) {
  96. const videoElement = document.querySelector(`[id^="hudson-"].video_view--theater`).querySelector("video");
  97. if (videoElement.hasAttribute("disablepictureinpicture")) videoElement.removeAttribute("disablepictureinpicture");
  98. videoElement.requestPictureInPicture();
  99. return;
  100. }
  101.  
  102. data.button = data.masterDiv.querySelector(`[data-tooltip="PiP"]`);
  103. if (data.button) return;
  104.  
  105. data.button = document.createElement("button");
  106. data.button.setAttribute("type", "button");
  107. data.button.setAttribute("aria-label", "Picture in Picture");
  108. data.button.setAttribute("class", "control-icon-btn fullscreen-icon tooltip__left");
  109. data.button.setAttribute("role", "button");
  110. data.button.setAttribute("data-tooltip", "PiP");
  111. data.button.addEventListener("click", () => {
  112. const videoElement = document.querySelector(`[id^="hudson-"].video_view--theater`).querySelector("video");
  113. if (videoElement.hasAttribute("disablepictureinpicture")) videoElement.removeAttribute("disablepictureinpicture");
  114. if (document.pictureInPictureElement) {
  115. document.exitPictureInPicture();
  116. } else {
  117. videoElement.requestPictureInPicture();
  118. }
  119. });
  120.  
  121. data.buttonDiv = document.createElement("div");
  122. data.buttonDiv.setAttribute("class", "focus-hack-div");
  123. data.buttonDiv.setAttribute("tabindex", "-1");
  124.  
  125. data.buttonDiv.innerHTML = SVG;
  126.  
  127. data.button.appendChild(data.buttonDiv);
  128.  
  129. data.masterDiv.appendChild(data.button);
  130. };
  131.  
  132. matchDomain(`https:\/\/(.*)\.?disneyplus\.com(.*)`, () => loop(disneyPlus));
  133.  
  134. const amazonPrimeVideo = () => {
  135. let data = {};
  136. data.masterDiv = document.querySelector(".atvwebplayersdk-hideabletopbuttons-container");
  137. if (!data.masterDiv) return;
  138. data.button = data.masterDiv.querySelector(`[data-tooltip="PiP"]`);
  139. if (data.button) return;
  140.  
  141. data.div = document.createElement("div");
  142. data.div.setAttribute("class", "f1qd5172 f7mv6lt");
  143.  
  144. data.span = document.createElement("span");
  145.  
  146. data.spandiv = document.createElement("div");
  147. data.spandiv.setAttribute("class", "fewcsle fcmecz0");
  148.  
  149. data.button = document.createElement("button");
  150. data.button.setAttribute("class", "fqye4e3 f1ly7q5u fk9c3ap fz9ydgy f1xrlb00 f1hy0e6n fgbpje3 f1uteees f1h2a8xb f760yrh f1mic5r1 f13ipev8 atvwebplayersdk-subtitleaudiomenu-button f130s5ag f15v4vpu frcngjs f12ossvl f45h");
  151. data.button.setAttribute("data-tooltip", "PiP");
  152. data.button.setAttribute("aria-label", "Picture in Picture");
  153. data.button.setAttribute("style", "padding: 0px; min-width: 0px;");
  154. data.button.addEventListener("click", () => {
  155. const videoElement = document.querySelector(".scalingVideoContainer video");
  156. if (videoElement.hasAttribute("disablepictureinpicture")) videoElement.removeAttribute("disablepictureinpicture");
  157. if (document.pictureInPictureElement) {
  158. document.exitPictureInPicture();
  159. } else {
  160. videoElement.requestPictureInPicture();
  161. }
  162. });
  163.  
  164. data.buttondiv = document.createElement("div");
  165. data.buttondiv.setAttribute("class", "f45h");
  166.  
  167. data.buttondivimg = document.createElement("img");
  168. data.buttondivimg.setAttribute("class", "fuorrko");
  169. data.buttondivimg.style.color = "#F9F9F9";
  170. data.buttondivimg.src = SVG64;
  171.  
  172. data.buttondiv.appendChild(data.buttondivimg);
  173. data.button.appendChild(data.buttondiv);
  174.  
  175. data.spandivdiv = document.createElement("div");
  176. data.spandivdiv.setAttribute("class", "f1wp6x33");
  177.  
  178. data.spandivdivdiv = document.createElement("div");
  179. data.spandivdivdiv.setAttribute("class", "fhjv49j f1svrrcm f1tep9b4 fqlubke");
  180. data.spandivdivdiv.innerText = "Picture in Picture";
  181.  
  182. data.spandivdiv.appendChild(data.spandivdivdiv);
  183. data.spandiv.appendChild(data.button);
  184. data.spandiv.appendChild(data.spandivdiv);
  185. data.span.appendChild(data.spandiv);
  186. data.div.appendChild(data.span);
  187.  
  188. document.querySelector(".atvwebplayersdk-hideabletopbuttons-container").lastChild.remove();
  189.  
  190. data.masterDiv.appendChild(data.div);
  191. };
  192.  
  193. matchDomain(`https:\/\/www\.amazon\.[a-z]{2,3}\/-\/[a-z]{2}\/gp\/video\/detail\/[A-Za-z0-9]+`, () => loop(amazonPrimeVideo));
  194. matchDomain(`https:\/\/www\.amazon\.[a-z]{2,3}\/-\/[a-z]{2}\/dp\/[A-Za-z0-9]+`, () => loop(amazonPrimeVideo));
  195.  
  196.  
  197. function loop(func) {
  198. setInterval(() => {
  199. func();
  200. }, 1);
  201. }
  202.  
  203. function matchDomain(domains, cb) {
  204. const url = location.href;
  205. if (typeof domains === 'string') { domains = [domains]; }
  206. if (domains.some(domain => new RegExp(domain).test(url))) {
  207. cb();
  208. }
  209. }
  210.  
  211. function waitDOMElement(selector, tagName = '', callback, multiple = false) {
  212. new window.MutationObserver(function (mutations) {
  213. for (const mutation of mutations) {
  214. for (const node of mutation.addedNodes) {
  215. if (!tagName || (node.tagName === tagName)) {
  216. if (node.matches(selector)) {
  217. callback(node);
  218. if (!multiple) { this.disconnect(); }
  219. }
  220. }
  221. }
  222. }
  223. }).observe(document, {
  224. subtree: true,
  225. childList: true
  226. });
  227. }
  228.  
  229. function removeDOMElement(...elements) {
  230. for (const element of elements) {
  231. if (element) { element.remove(); }
  232. }
  233. }
  234.  
  235. function removeClassesByPrefix(el, prefix) {
  236. for (const clazz of el.classList) {
  237. if (clazz.startsWith(prefix)) {
  238. el.classList.remove(clazz);
  239. }
  240. }
  241. }
  242.  
  243. function blockElement(selector, blockAlways = false) {
  244. new window.MutationObserver(function (mutations) {
  245. for (const mutation of mutations) {
  246. for (const node of mutation.addedNodes) {
  247. if (node instanceof window.HTMLElement) {
  248. if (node.matches(selector)) {
  249. removeDOMElement(node);
  250. if (!blockAlways) {
  251. this.disconnect(); // Stop watching for element being added after one removal
  252. }
  253. }
  254. }
  255. }
  256. }
  257. }).observe(document, { subtree: true, childList: true });
  258. }