Greasy Fork is available in English.

Video Overlay Vanisher

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

// ==UserScript==
// @name         Video Overlay Vanisher
// @namespace    http://tampermonkey.net/
// @version      1.4.0
// @description  A tool to eliminate web video player overlays with Shift+D.
// @author       CY Fung
// @icon         https://na.cx/i/Hh10VGs.png
// @match        https://*/*
// @exclude      https://www.youtube.com/live_chat*
// @exclude      /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
// @exclude      https://*.openai.com/*
// @exclude      https://jsfiddle.net/*
// @exclude      https://*.jsfiddle.net/*
// @exclude      https://fiddle.*.net/*
// @exclude      https://*.jshell.net/*
// @exclude      https://fiddle.jshell.net/*
// @exclude      https://login.*/*
// @exclude      https://account.*/*
// @grant        GM.getValue
// @run-at       document-start
// @inject-into  content
// @license      MIT
// ==/UserScript==
(function $$() {
    'use strict';

    const keyCombination = {
        key: 'KeyD',
        shift: true
    }

    if (document.documentElement == null) return window.requestAnimationFrame($$);

    console.log("userscript enabled - Don't Overlay Video Player !")

    function addStyle(styleText) {
        const styleNode = document.createElement('style');
        styleNode.textContent = styleText;
        document.documentElement.appendChild(styleNode);
        return styleNode;
    }


    // Your code here...

    addStyle(`

[userscript-no-overlay-on] [userscript-no-overlay-hoverable], [userscript-no-overlay-on] [userscript-no-overlay-hoverable] *:not([userscript-no-overlay-hoverable]){
    visibility: collapse !important;
}

`);

    var qElm_PossibleHoverByPosition = new WeakMap();
    var qElm_Cache = new WeakMap();

    let doList = [];
    // Callback function to execute when mutations are observed
    const callbackA = function (mutations, observer) {
        // Use traditional 'for loops' for IE 11
        for (const mutation of mutations) {
            const {
                addedNodes
            } = mutation;
            for (const s of addedNodes) {
                if (s.nodeType === 1) doList.push(s);
            }
        }
        if (doList.length == 0) return;
        callbackB(100);
    };


    function callbackBmicro1(qElm) {
        if (!(qElm instanceof HTMLElement)) return;
        if (qElm.isConnected === false) return;
        let qElmComputedStyle = qElm_Cache.get(qElm);
        if (!qElmComputedStyle) {
            qElmComputedStyle = getComputedStyle(qElm)
            qElm_Cache.set(qElm, qElmComputedStyle);
        }
        const {
            position
        } = qElmComputedStyle;

        if (position == 'absolute' || position == 'fixed') {
            qElm_PossibleHoverByPosition.set(qElm, position);
        } else {
            qElm_PossibleHoverByPosition.delete(qElm);
        }

    }


    const createPipeline = () => {
        let pipelineMutex = Promise.resolve();
        const pipelineExecution = fn => {
            return new Promise((resolve, reject) => {
                pipelineMutex = pipelineMutex.then(async () => {
                    let res;
                    try {
                        res = await fn();
                    } catch (e) {
                        console.log("Pipeline Error", e);
                        reject(e);
                    }
                    resolve(res);
                }).catch(console.warn);
            });
        };
        return pipelineExecution;
    }

    const pipeline = createPipeline();


    async function callbackB(delay) {

        let res;
        do {
            res = await pipeline(async () => {

                if (!doList.length) return;

                if (delay > 0) {
                    await new Promise(resolve => setTimeout(resolve, delay));
                }

                if (!doList.length) return;

                let doListCopy = doList.slice(0);
                doList.length = 0;

                function allParents(elm) {
                    let res = [];
                    while ((elm = elm.parentNode) instanceof HTMLElement) res.push(elm);
                    return res;
                }
                let possibleContainerSet = null;
                const proceeded = new Set();
                for (const addedNode of doListCopy) {
                    if (!addedNode || !addedNode.parentNode) continue;
                    if (addedNode.isConnected === false) continue;
                    if (proceeded.has(addedNode)) continue;
                    proceeded.add(addedNode);
                    const parentsSet = new Set(allParents(addedNode));
                    if (possibleContainerSet == null) {
                        possibleContainerSet = parentsSet;
                    } else {
                        for (const possibleParent of possibleContainerSet) {
                            if (!parentsSet.has(possibleParent)) possibleContainerSet.delete(possibleParent);
                        }
                        parentsSet.clear();
                    }
                    if (possibleContainerSet.size <= 1) break;
                }
                if(!possibleContainerSet) return;
                proceeded.clear();
                doListCopy.length = 0;

                const possibleContainerSetIt = possibleContainerSet.values();
                const possibleContainer = possibleContainerSetIt.next().value;

                await Promise.resolve();

                //console.log('possibleContainer',possibleContainer)

                const elements = possibleContainer ? [...possibleContainer.querySelectorAll('*')] : null;

                if (elements && elements.length >= 1) {
                    await new Promise(resolve => setTimeout(resolve, 100));
                    await Promise.all(elements.map(qElm => Promise.resolve(qElm).then(callbackBmicro1)));
                }

                //console.log('done', doList.length)

                if (doList.length > 0) {
                    delay = 100;
                    return true;
                }

            });
        } while (res === true);

    }



    // Create an observer instance linked to the callback function
    const observer = new MutationObserver(callbackA);

    doList = [document.documentElement];
    callbackB(0);

    // Start observing the target node for configured mutations
    observer.observe(document.documentElement, {
        childList: true,
        subtree: true
    });

    let overlayHoverTID = 0;

    let resizeObserver = null;

    function resizeCallback(mutations) {

        //document.documentElement.removeAttribute('userscript-no-overlay-on')
        //overlayHoverTID=(overlayHoverTID+1)%2;

        if (!document.documentElement.hasAttribute('userscript-no-overlay-on')) {

            if (resizeObserver) {
                resizeObserver.disconnect();
                resizeObserver = null;
            }
            return;
        }

        let video = mutations[0].target;
        if (!video) return;

        makeHide(video);
    }

    function makeHide(videoElm) {

        if (!videoElm) return;
        videoElm.setAttribute('ve291', '');

        let _overlayHoverTID = overlayHoverTID;
        overlayHoverTID = (overlayHoverTID + 1) % 2;

        let rElms = [];

        for (const qElm of document.querySelectorAll('*')) {
            if (qElm_PossibleHoverByPosition.has(qElm)) rElms.push(qElm);
        }

        let replacementTexts = [];

        function replaceAll(str) {

            for (const s of replacementTexts) {
                if (str.length < s.length) continue;
                str = str.replace(s, '');
            }

            return str.trim();

        }

        var finalBoundaries = [];

        function getBoundaryElm() {

            finalBoundaries.length = 0;

            let _boundaryElm = videoElm;
            let boundaryElm = videoElm;
            while (_boundaryElm && replaceAll(_boundaryElm.textContent || '') == replaceAll(videoElm.textContent || '')) {
                boundaryElm = _boundaryElm;
                finalBoundaries.push(boundaryElm);
                _boundaryElm = _boundaryElm.parentNode;
            }

            return boundaryElm;
        }

        for (const s of rElms) {
            if (s.contains(videoElm)) continue;
            let sText = s.textContent;
            if (sText && sText.length > 0) replacementTexts.push(sText);
        }
        replacementTexts.sort((b, a) => a.length > b.length ? 1 : a.length < b.length ? -1 : 0);

        getBoundaryElm();

        let breakControl = false;

        while (!breakControl) {


            // youtube: boundary element (parent container) with no size.
            // ensure boundary element is larger than the child.
            var finalBoundaries_entries = finalBoundaries.map(elm => ({
                elm,
                rect: elm.getBoundingClientRect()
            }))

            for (const entry of finalBoundaries_entries) entry.size = Math.round(entry.rect.width * entry.rect.height || 0);

            let maxSize = Math.max(...finalBoundaries_entries.map(entry => entry.size))

            if (!maxSize) continue;

            finalBoundaries_entries = finalBoundaries_entries.filter(entry => entry.size == maxSize);

            let bmElm = finalBoundaries_entries[finalBoundaries_entries.length - 1].elm; // outest largest size

            let bRect = bmElm.getBoundingClientRect();

            for (const s of rElms) {

                if (s.contains(videoElm)) continue;

                let sRect = s.getBoundingClientRect();
                if (bRect && sRect) {
                    if (sRect.width * sRect.height > 0) {
                        if (sRect.left > bRect.right) continue;
                        if (sRect.top > bRect.bottom) continue;
                        if (sRect.right < bRect.left) continue;
                        if (sRect.bottom < bRect.top) continue;
                    } else {
                        continue;
                    }
                }

                s.setAttribute('userscript-no-overlay-hoverable', overlayHoverTID);
            }

            breakControl = true;

        }


        for (const s of document.querySelectorAll(`[userscript-no-overlay-hoverable="${_overlayHoverTID}"]`)) s.removeAttribute('userscript-no-overlay-hoverable');


    }


    function getVideoState() {

        let video = null;

        let videoElms = document.querySelectorAll('video');
        if (!videoElms.length) {
            return null;
        }

        let videos = [...videoElms].map(elm => ({
            elm,
            width: elm.offsetWidth,
            height: elm.offsetHeight
        }));

        let maxWidth = Math.max(...videos.map(item => item.width));
        let maxHeight = Math.max(...videos.map(item => item.height));

        if (maxWidth > 0 && maxHeight > 0) {

            video = videos.filter(item => item.width == maxWidth && item.height == maxHeight)[0] || null;

        }

        return video;

    }

    function postMessage(target, message, origin) {
        let win = null;
        if (target instanceof HTMLIFrameElement) {
            win = target.contentWindow;
        } else if (target && 'postMessage' in target) {
            win = target;
        }
        if (!origin) origin = '*';
        if (win && typeof win.postMessage == 'function') {
            try {
                win.postMessage(message, origin);
            } catch (e) { }
        }
    }

    function spreadMessage() {

        for (const iframe of document.getElementsByTagName('iframe')) {
            if (+iframe.getAttribute('ve944') === mouseEnteredIframeIId) {
                postMessage(iframe, 'do-video-controls-hidden991');
            }
        }

    }

    function tryUnhide() {

        if (document.documentElement.hasAttribute('userscript-no-overlay-on')) {

            document.documentElement.removeAttribute('userscript-no-overlay-on')

            for (const s of document.querySelectorAll('[userscript-no-overlay-hoverable]')) {
                s.removeAttribute('userscript-no-overlay-hoverable');
            }

            const videoTarget = document.querySelector('[ve291]');

            if (videoTarget) {

                videoTarget.removeAttribute('ve291');

                /*
                    requestAnimationFrame(() => {
                        console.log(12321);

                        // Create a new mouse event
                        let event = new MouseEvent('mousemove', {
                            bubbles: true,
                            cancelable: true,
                            clientX: 100,
                            clientY: 100
                            });

                        // Dispatch the event to the element
                        videoTarget.dispatchEvent(event);
                    })
                */

                return true;

            }


        }
        return false;

    }

    function keydownAsync() {
        if (!tryUnhide()) {
            const videoState = getVideoState();
            if (videoState === null) {
                // console.log('Unable to find any video element. If it is inside Iframe, please click the video inside iframe first.')
                spreadMessage();
            } else if (videoState && videoState.elm instanceof HTMLVideoElement) {
                videoState.elm.dispatchEvent(new CustomEvent('video-controls-hidden675'))
            }
        }
    }

    document.addEventListener('keydown', function (evt) {

        if (evt && evt.code == keyCombination.key && evt.shiftKey === keyCombination.shift) {

            if (evt.isComposing) return;
            let evtTarget = evt.target;
            if (evtTarget.nodeType == 1) {
                if (evtTarget.nodeName == 'INPUT' || evtTarget.nodeName == 'TEXTAREA' || evtTarget.hasAttribute('contenteditable')) return;
            }
            evtTarget = null;
            evt.preventDefault();
            evt.stopPropagation();
            evt.stopImmediatePropagation();

            Promise.resolve().then(keydownAsync);

        }
    }, true);



    let rafPromise = null;

    const getRafPromise = () => rafPromise || (rafPromise = new Promise(resolve => {
      requestAnimationFrame(hRes => {
        rafPromise = null;
        resolve(hRes);
      });
    }));


    const controlsHidden675Async = async (targetVideo)=>{

        if (resizeObserver) {
            resizeObserver.disconnect();
            resizeObserver = null;
        }
        resizeObserver = new ResizeObserver(resizeCallback)
        resizeObserver.observe(targetVideo)

        await getRafPromise();
        makeHide(targetVideo);

        document.documentElement.setAttribute('userscript-no-overlay-on', '');

    }




    document.addEventListener('video-controls-hidden675', (evt) => {
        let targetVideo = evt.target;
        if (!(targetVideo instanceof HTMLVideoElement)) return;

        Promise.resolve(targetVideo).then(controlsHidden675Async);

    }, true);

    let mouseEnteredVideoVId = 0;
    let mouseEnteredIframeIId = 0;

    let di = 0;
    let domWeakHash = new WeakMap();
    document.addEventListener('mouseenter', (evt) => {
        if (evt && evt.target instanceof HTMLVideoElement) {
            const videoElm = evt.target;
            if (!domWeakHash.has(videoElm)) {
                let vid = ++di;
                domWeakHash.set(videoElm, vid);
                videoElm.setAttribute('ve944', vid);
            }
            mouseEnteredVideoVId = domWeakHash.get(videoElm);
        } else if (evt && evt.target instanceof HTMLIFrameElement) {
            const iframeTarget = evt.target;
            if (!domWeakHash.has(iframeTarget)) {
                let vid = ++di;
                domWeakHash.set(iframeTarget, vid);
                iframeTarget.setAttribute('ve944', vid);
            }
            mouseEnteredIframeIId = +iframeTarget.getAttribute('ve944') || 0;
            postMessage(iframeTarget, 've761-iframe-entered')
        }
    }, true)
    document.addEventListener('mouseleave', (evt) => {

        if (evt && evt.target instanceof HTMLVideoElement) {
            const videoElm = evt.target;
            if (domWeakHash.has(videoElm)) {
                mouseEnteredVideoVId = 0;
            }
        } else if (evt && evt.target instanceof HTMLIFrameElement) {

            const iframeTarget = evt.target;
            if (domWeakHash.has(iframeTarget)) {
                mouseEnteredIframeIId = 0;
            }
            postMessage(iframeTarget, 've762-iframe-leaved')
        }
    }, true)


    let isInIframeWindow = false;
    function controlsHidden991Async() {
        let videoTarget = null;
        if (mouseEnteredVideoVId > 0 && isInIframeWindow > 0) {
            videoTarget = document.querySelector(`video[ve944="${mouseEnteredVideoVId}"]`);
        }
        if (!videoTarget) {
            if (isInIframeWindow) {
                spreadMessage();
            }
        } else {
            if (!tryUnhide()) {
                videoTarget.dispatchEvent(new CustomEvent('video-controls-hidden675'));
            }
        }
    }
    function receiveMessage(event) {
        if (!event) return;
        if (event.data === 'do-video-controls-hidden991') {
            Promise.resolve().then(controlsHidden991Async);
        } else if (event.data === 've761-iframe-entered') {
            isInIframeWindow = true;
        } else if (event.data === 've761-iframe-leaved') {
            isInIframeWindow = false;
        }
    }


    window.addEventListener('message', receiveMessage, false);

    GM.getValue("dummy"); // dummy


})();