Greasy Fork is available in English.

Video Overlay Vanisher

A tool to eliminate web video player overlays with Shift+D.

  1. // ==UserScript==
  2. // @name Video Overlay Vanisher
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.4.0
  5. // @description A tool to eliminate web video player overlays with Shift+D.
  6. // @author CY Fung
  7. // @icon https://na.cx/i/Hh10VGs.png
  8. // @match https://*/*
  9. // @exclude https://www.youtube.com/live_chat*
  10. // @exclude /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
  11. // @exclude https://*.openai.com/*
  12. // @exclude https://jsfiddle.net/*
  13. // @exclude https://*.jsfiddle.net/*
  14. // @exclude https://fiddle.*.net/*
  15. // @exclude https://*.jshell.net/*
  16. // @exclude https://fiddle.jshell.net/*
  17. // @exclude https://login.*/*
  18. // @exclude https://account.*/*
  19. // @grant GM.getValue
  20. // @run-at document-start
  21. // @inject-into content
  22. // @license MIT
  23. // ==/UserScript==
  24. (function $$() {
  25. 'use strict';
  26.  
  27. const keyCombination = {
  28. key: 'KeyD',
  29. shift: true
  30. }
  31.  
  32. if (document.documentElement == null) return window.requestAnimationFrame($$);
  33.  
  34. console.log("userscript enabled - Don't Overlay Video Player !")
  35.  
  36. function addStyle(styleText) {
  37. const styleNode = document.createElement('style');
  38. styleNode.textContent = styleText;
  39. document.documentElement.appendChild(styleNode);
  40. return styleNode;
  41. }
  42.  
  43.  
  44. // Your code here...
  45.  
  46. addStyle(`
  47.  
  48. [userscript-no-overlay-on] [userscript-no-overlay-hoverable], [userscript-no-overlay-on] [userscript-no-overlay-hoverable] *:not([userscript-no-overlay-hoverable]){
  49. visibility: collapse !important;
  50. }
  51.  
  52. `);
  53.  
  54. var qElm_PossibleHoverByPosition = new WeakMap();
  55. var qElm_Cache = new WeakMap();
  56.  
  57. let doList = [];
  58. // Callback function to execute when mutations are observed
  59. const callbackA = function (mutations, observer) {
  60. // Use traditional 'for loops' for IE 11
  61. for (const mutation of mutations) {
  62. const {
  63. addedNodes
  64. } = mutation;
  65. for (const s of addedNodes) {
  66. if (s.nodeType === 1) doList.push(s);
  67. }
  68. }
  69. if (doList.length == 0) return;
  70. callbackB(100);
  71. };
  72.  
  73.  
  74. function callbackBmicro1(qElm) {
  75. if (!(qElm instanceof HTMLElement)) return;
  76. if (qElm.isConnected === false) return;
  77. let qElmComputedStyle = qElm_Cache.get(qElm);
  78. if (!qElmComputedStyle) {
  79. qElmComputedStyle = getComputedStyle(qElm)
  80. qElm_Cache.set(qElm, qElmComputedStyle);
  81. }
  82. const {
  83. position
  84. } = qElmComputedStyle;
  85.  
  86. if (position == 'absolute' || position == 'fixed') {
  87. qElm_PossibleHoverByPosition.set(qElm, position);
  88. } else {
  89. qElm_PossibleHoverByPosition.delete(qElm);
  90. }
  91.  
  92. }
  93.  
  94.  
  95. const createPipeline = () => {
  96. let pipelineMutex = Promise.resolve();
  97. const pipelineExecution = fn => {
  98. return new Promise((resolve, reject) => {
  99. pipelineMutex = pipelineMutex.then(async () => {
  100. let res;
  101. try {
  102. res = await fn();
  103. } catch (e) {
  104. console.log("Pipeline Error", e);
  105. reject(e);
  106. }
  107. resolve(res);
  108. }).catch(console.warn);
  109. });
  110. };
  111. return pipelineExecution;
  112. }
  113.  
  114. const pipeline = createPipeline();
  115.  
  116.  
  117. async function callbackB(delay) {
  118.  
  119. let res;
  120. do {
  121. res = await pipeline(async () => {
  122.  
  123. if (!doList.length) return;
  124.  
  125. if (delay > 0) {
  126. await new Promise(resolve => setTimeout(resolve, delay));
  127. }
  128.  
  129. if (!doList.length) return;
  130.  
  131. let doListCopy = doList.slice(0);
  132. doList.length = 0;
  133.  
  134. function allParents(elm) {
  135. let res = [];
  136. while ((elm = elm.parentNode) instanceof HTMLElement) res.push(elm);
  137. return res;
  138. }
  139. let possibleContainerSet = null;
  140. const proceeded = new Set();
  141. for (const addedNode of doListCopy) {
  142. if (!addedNode || !addedNode.parentNode) continue;
  143. if (addedNode.isConnected === false) continue;
  144. if (proceeded.has(addedNode)) continue;
  145. proceeded.add(addedNode);
  146. const parentsSet = new Set(allParents(addedNode));
  147. if (possibleContainerSet == null) {
  148. possibleContainerSet = parentsSet;
  149. } else {
  150. for (const possibleParent of possibleContainerSet) {
  151. if (!parentsSet.has(possibleParent)) possibleContainerSet.delete(possibleParent);
  152. }
  153. parentsSet.clear();
  154. }
  155. if (possibleContainerSet.size <= 1) break;
  156. }
  157. if(!possibleContainerSet) return;
  158. proceeded.clear();
  159. doListCopy.length = 0;
  160.  
  161. const possibleContainerSetIt = possibleContainerSet.values();
  162. const possibleContainer = possibleContainerSetIt.next().value;
  163.  
  164. await Promise.resolve();
  165.  
  166. //console.log('possibleContainer',possibleContainer)
  167.  
  168. const elements = possibleContainer ? [...possibleContainer.querySelectorAll('*')] : null;
  169.  
  170. if (elements && elements.length >= 1) {
  171. await new Promise(resolve => setTimeout(resolve, 100));
  172. await Promise.all(elements.map(qElm => Promise.resolve(qElm).then(callbackBmicro1)));
  173. }
  174.  
  175. //console.log('done', doList.length)
  176.  
  177. if (doList.length > 0) {
  178. delay = 100;
  179. return true;
  180. }
  181.  
  182. });
  183. } while (res === true);
  184.  
  185. }
  186.  
  187.  
  188.  
  189. // Create an observer instance linked to the callback function
  190. const observer = new MutationObserver(callbackA);
  191.  
  192. doList = [document.documentElement];
  193. callbackB(0);
  194.  
  195. // Start observing the target node for configured mutations
  196. observer.observe(document.documentElement, {
  197. childList: true,
  198. subtree: true
  199. });
  200.  
  201. let overlayHoverTID = 0;
  202.  
  203. let resizeObserver = null;
  204.  
  205. function resizeCallback(mutations) {
  206.  
  207. //document.documentElement.removeAttribute('userscript-no-overlay-on')
  208. //overlayHoverTID=(overlayHoverTID+1)%2;
  209.  
  210. if (!document.documentElement.hasAttribute('userscript-no-overlay-on')) {
  211.  
  212. if (resizeObserver) {
  213. resizeObserver.disconnect();
  214. resizeObserver = null;
  215. }
  216. return;
  217. }
  218.  
  219. let video = mutations[0].target;
  220. if (!video) return;
  221.  
  222. makeHide(video);
  223. }
  224.  
  225. function makeHide(videoElm) {
  226.  
  227. if (!videoElm) return;
  228. videoElm.setAttribute('ve291', '');
  229.  
  230. let _overlayHoverTID = overlayHoverTID;
  231. overlayHoverTID = (overlayHoverTID + 1) % 2;
  232.  
  233. let rElms = [];
  234.  
  235. for (const qElm of document.querySelectorAll('*')) {
  236. if (qElm_PossibleHoverByPosition.has(qElm)) rElms.push(qElm);
  237. }
  238.  
  239. let replacementTexts = [];
  240.  
  241. function replaceAll(str) {
  242.  
  243. for (const s of replacementTexts) {
  244. if (str.length < s.length) continue;
  245. str = str.replace(s, '');
  246. }
  247.  
  248. return str.trim();
  249.  
  250. }
  251.  
  252. var finalBoundaries = [];
  253.  
  254. function getBoundaryElm() {
  255.  
  256. finalBoundaries.length = 0;
  257.  
  258. let _boundaryElm = videoElm;
  259. let boundaryElm = videoElm;
  260. while (_boundaryElm && replaceAll(_boundaryElm.textContent || '') == replaceAll(videoElm.textContent || '')) {
  261. boundaryElm = _boundaryElm;
  262. finalBoundaries.push(boundaryElm);
  263. _boundaryElm = _boundaryElm.parentNode;
  264. }
  265.  
  266. return boundaryElm;
  267. }
  268.  
  269. for (const s of rElms) {
  270. if (s.contains(videoElm)) continue;
  271. let sText = s.textContent;
  272. if (sText && sText.length > 0) replacementTexts.push(sText);
  273. }
  274. replacementTexts.sort((b, a) => a.length > b.length ? 1 : a.length < b.length ? -1 : 0);
  275.  
  276. getBoundaryElm();
  277.  
  278. let breakControl = false;
  279.  
  280. while (!breakControl) {
  281.  
  282.  
  283. // youtube: boundary element (parent container) with no size.
  284. // ensure boundary element is larger than the child.
  285. var finalBoundaries_entries = finalBoundaries.map(elm => ({
  286. elm,
  287. rect: elm.getBoundingClientRect()
  288. }))
  289.  
  290. for (const entry of finalBoundaries_entries) entry.size = Math.round(entry.rect.width * entry.rect.height || 0);
  291.  
  292. let maxSize = Math.max(...finalBoundaries_entries.map(entry => entry.size))
  293.  
  294. if (!maxSize) continue;
  295.  
  296. finalBoundaries_entries = finalBoundaries_entries.filter(entry => entry.size == maxSize);
  297.  
  298. let bmElm = finalBoundaries_entries[finalBoundaries_entries.length - 1].elm; // outest largest size
  299.  
  300. let bRect = bmElm.getBoundingClientRect();
  301.  
  302. for (const s of rElms) {
  303.  
  304. if (s.contains(videoElm)) continue;
  305.  
  306. let sRect = s.getBoundingClientRect();
  307. if (bRect && sRect) {
  308. if (sRect.width * sRect.height > 0) {
  309. if (sRect.left > bRect.right) continue;
  310. if (sRect.top > bRect.bottom) continue;
  311. if (sRect.right < bRect.left) continue;
  312. if (sRect.bottom < bRect.top) continue;
  313. } else {
  314. continue;
  315. }
  316. }
  317.  
  318. s.setAttribute('userscript-no-overlay-hoverable', overlayHoverTID);
  319. }
  320.  
  321. breakControl = true;
  322.  
  323. }
  324.  
  325.  
  326. for (const s of document.querySelectorAll(`[userscript-no-overlay-hoverable="${_overlayHoverTID}"]`)) s.removeAttribute('userscript-no-overlay-hoverable');
  327.  
  328.  
  329. }
  330.  
  331.  
  332. function getVideoState() {
  333.  
  334. let video = null;
  335.  
  336. let videoElms = document.querySelectorAll('video');
  337. if (!videoElms.length) {
  338. return null;
  339. }
  340.  
  341. let videos = [...videoElms].map(elm => ({
  342. elm,
  343. width: elm.offsetWidth,
  344. height: elm.offsetHeight
  345. }));
  346.  
  347. let maxWidth = Math.max(...videos.map(item => item.width));
  348. let maxHeight = Math.max(...videos.map(item => item.height));
  349.  
  350. if (maxWidth > 0 && maxHeight > 0) {
  351.  
  352. video = videos.filter(item => item.width == maxWidth && item.height == maxHeight)[0] || null;
  353.  
  354. }
  355.  
  356. return video;
  357.  
  358. }
  359.  
  360. function postMessage(target, message, origin) {
  361. let win = null;
  362. if (target instanceof HTMLIFrameElement) {
  363. win = target.contentWindow;
  364. } else if (target && 'postMessage' in target) {
  365. win = target;
  366. }
  367. if (!origin) origin = '*';
  368. if (win && typeof win.postMessage == 'function') {
  369. try {
  370. win.postMessage(message, origin);
  371. } catch (e) { }
  372. }
  373. }
  374.  
  375. function spreadMessage() {
  376.  
  377. for (const iframe of document.getElementsByTagName('iframe')) {
  378. if (+iframe.getAttribute('ve944') === mouseEnteredIframeIId) {
  379. postMessage(iframe, 'do-video-controls-hidden991');
  380. }
  381. }
  382.  
  383. }
  384.  
  385. function tryUnhide() {
  386.  
  387. if (document.documentElement.hasAttribute('userscript-no-overlay-on')) {
  388.  
  389. document.documentElement.removeAttribute('userscript-no-overlay-on')
  390.  
  391. for (const s of document.querySelectorAll('[userscript-no-overlay-hoverable]')) {
  392. s.removeAttribute('userscript-no-overlay-hoverable');
  393. }
  394.  
  395. const videoTarget = document.querySelector('[ve291]');
  396.  
  397. if (videoTarget) {
  398.  
  399. videoTarget.removeAttribute('ve291');
  400.  
  401. /*
  402. requestAnimationFrame(() => {
  403. console.log(12321);
  404.  
  405. // Create a new mouse event
  406. let event = new MouseEvent('mousemove', {
  407. bubbles: true,
  408. cancelable: true,
  409. clientX: 100,
  410. clientY: 100
  411. });
  412.  
  413. // Dispatch the event to the element
  414. videoTarget.dispatchEvent(event);
  415. })
  416. */
  417.  
  418. return true;
  419.  
  420. }
  421.  
  422.  
  423. }
  424. return false;
  425.  
  426. }
  427.  
  428. function keydownAsync() {
  429. if (!tryUnhide()) {
  430. const videoState = getVideoState();
  431. if (videoState === null) {
  432. // console.log('Unable to find any video element. If it is inside Iframe, please click the video inside iframe first.')
  433. spreadMessage();
  434. } else if (videoState && videoState.elm instanceof HTMLVideoElement) {
  435. videoState.elm.dispatchEvent(new CustomEvent('video-controls-hidden675'))
  436. }
  437. }
  438. }
  439.  
  440. document.addEventListener('keydown', function (evt) {
  441.  
  442. if (evt && evt.code == keyCombination.key && evt.shiftKey === keyCombination.shift) {
  443.  
  444. if (evt.isComposing) return;
  445. let evtTarget = evt.target;
  446. if (evtTarget.nodeType == 1) {
  447. if (evtTarget.nodeName == 'INPUT' || evtTarget.nodeName == 'TEXTAREA' || evtTarget.hasAttribute('contenteditable')) return;
  448. }
  449. evtTarget = null;
  450. evt.preventDefault();
  451. evt.stopPropagation();
  452. evt.stopImmediatePropagation();
  453.  
  454. Promise.resolve().then(keydownAsync);
  455.  
  456. }
  457. }, true);
  458.  
  459.  
  460.  
  461. let rafPromise = null;
  462.  
  463. const getRafPromise = () => rafPromise || (rafPromise = new Promise(resolve => {
  464. requestAnimationFrame(hRes => {
  465. rafPromise = null;
  466. resolve(hRes);
  467. });
  468. }));
  469.  
  470.  
  471. const controlsHidden675Async = async (targetVideo)=>{
  472.  
  473. if (resizeObserver) {
  474. resizeObserver.disconnect();
  475. resizeObserver = null;
  476. }
  477. resizeObserver = new ResizeObserver(resizeCallback)
  478. resizeObserver.observe(targetVideo)
  479.  
  480. await getRafPromise();
  481. makeHide(targetVideo);
  482.  
  483. document.documentElement.setAttribute('userscript-no-overlay-on', '');
  484.  
  485. }
  486.  
  487.  
  488.  
  489.  
  490. document.addEventListener('video-controls-hidden675', (evt) => {
  491. let targetVideo = evt.target;
  492. if (!(targetVideo instanceof HTMLVideoElement)) return;
  493.  
  494. Promise.resolve(targetVideo).then(controlsHidden675Async);
  495.  
  496. }, true);
  497.  
  498. let mouseEnteredVideoVId = 0;
  499. let mouseEnteredIframeIId = 0;
  500.  
  501. let di = 0;
  502. let domWeakHash = new WeakMap();
  503. document.addEventListener('mouseenter', (evt) => {
  504. if (evt && evt.target instanceof HTMLVideoElement) {
  505. const videoElm = evt.target;
  506. if (!domWeakHash.has(videoElm)) {
  507. let vid = ++di;
  508. domWeakHash.set(videoElm, vid);
  509. videoElm.setAttribute('ve944', vid);
  510. }
  511. mouseEnteredVideoVId = domWeakHash.get(videoElm);
  512. } else if (evt && evt.target instanceof HTMLIFrameElement) {
  513. const iframeTarget = evt.target;
  514. if (!domWeakHash.has(iframeTarget)) {
  515. let vid = ++di;
  516. domWeakHash.set(iframeTarget, vid);
  517. iframeTarget.setAttribute('ve944', vid);
  518. }
  519. mouseEnteredIframeIId = +iframeTarget.getAttribute('ve944') || 0;
  520. postMessage(iframeTarget, 've761-iframe-entered')
  521. }
  522. }, true)
  523. document.addEventListener('mouseleave', (evt) => {
  524.  
  525. if (evt && evt.target instanceof HTMLVideoElement) {
  526. const videoElm = evt.target;
  527. if (domWeakHash.has(videoElm)) {
  528. mouseEnteredVideoVId = 0;
  529. }
  530. } else if (evt && evt.target instanceof HTMLIFrameElement) {
  531.  
  532. const iframeTarget = evt.target;
  533. if (domWeakHash.has(iframeTarget)) {
  534. mouseEnteredIframeIId = 0;
  535. }
  536. postMessage(iframeTarget, 've762-iframe-leaved')
  537. }
  538. }, true)
  539.  
  540.  
  541. let isInIframeWindow = false;
  542. function controlsHidden991Async() {
  543. let videoTarget = null;
  544. if (mouseEnteredVideoVId > 0 && isInIframeWindow > 0) {
  545. videoTarget = document.querySelector(`video[ve944="${mouseEnteredVideoVId}"]`);
  546. }
  547. if (!videoTarget) {
  548. if (isInIframeWindow) {
  549. spreadMessage();
  550. }
  551. } else {
  552. if (!tryUnhide()) {
  553. videoTarget.dispatchEvent(new CustomEvent('video-controls-hidden675'));
  554. }
  555. }
  556. }
  557. function receiveMessage(event) {
  558. if (!event) return;
  559. if (event.data === 'do-video-controls-hidden991') {
  560. Promise.resolve().then(controlsHidden991Async);
  561. } else if (event.data === 've761-iframe-entered') {
  562. isInIframeWindow = true;
  563. } else if (event.data === 've761-iframe-leaved') {
  564. isInIframeWindow = false;
  565. }
  566. }
  567.  
  568.  
  569. window.addEventListener('message', receiveMessage, false);
  570.  
  571. GM.getValue("dummy"); // dummy
  572.  
  573.  
  574. })();