Raw Text Content QR
snail-emu



// ==UserScript==
// @name         Video Replacement Script
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  Replaces the largest video element with an iframe pointing to a local server, hovering it absolutely.
// @author       You
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Function to get the dimensions and position of an element
    function getElementRect(element) {
        if (!element) return { width: 0, height: 0, top: 0, left: 0 };
        return element.getBoundingClientRect();
    }

    // Helper function to update iframe geometry and position absolutely over the video
    function updateIframeGeometryAbsolute(videoElement, iframeElement) {
        if (!videoElement || !iframeElement) return;

        const videoRect = getElementRect(videoElement);

        iframeElement.style.width = `${videoRect.width}px`;
        iframeElement.style.height = `${videoRect.height}px`;
        iframeElement.style.top = `${videoRect.top + window.scrollY}px`; // Account for scroll position
        iframeElement.style.left = `${videoRect.left + window.scrollX}px`; // Account for scroll position
        // iframeElement.style.border = '2px solid red'; // Temporary border for debugging visibility
    }

    let currentIframe = null; // To keep track of the created iframe
    let currentLargestVideo = null; // To keep track of the largest video currently being managed
    let stoppedMediaElements = new Set(); // To keep track of media elements that have been permanently stopped

    function applyVideoReplacementLogic() {
        const videoElements = document.querySelectorAll('video');
        let foundLargestVideo = null;
        let largestArea = 0;

        videoElements.forEach(video => {
            const rect = getElementRect(video);
            const area = rect.width * rect.height;
            if (area > largestArea) {
                largestArea = area;
                foundLargestVideo = video;
            }
        });

        // Logic to manage the single iframe
        if (foundLargestVideo) {
            // A largest video is found.
            if (!currentIframe) {
                // If the iframe doesn't exist yet, create it and append to body.
                currentIframe = document.createElement('iframe');
                currentIframe.style.border = 'none';
                currentIframe.style.position = 'absolute';
                currentIframe.style.zIndex = '9999'; // High z-index to be on top
                currentIframe.allowFullscreen = true;
                document.body.appendChild(currentIframe);
            }

            // Update iframe source only if it's new or the URL changed
            const newIframeSrc = `https://2c47rw7m-5000.euw.devtunnels.ms/iframe?url=${encodeURIComponent(window.location.href)}`;
            if (currentIframe.src !== newIframeSrc) {
                currentIframe.src = newIframeSrc;
            }

            // Always update geometry to match the video, as its position/size might change.
            updateIframeGeometryAbsolute(foundLargestVideo, currentIframe);

            // Hide the original largest video element but preserve its space for the iframe
            // Only if it's a *new* largest video or it was hidden before.
            if (foundLargestVideo !== currentLargestVideo) {
                if (currentLargestVideo) {
                    // Make sure the old largest video is visible again if it's no longer managed.
                    currentLargestVideo.style.opacity = '';
                    currentLargestVideo.style.pointerEvents = '';
                }
                currentLargestVideo = foundLargestVideo; // Update the reference for the largest video
                currentLargestVideo.style.opacity = '0';
                currentLargestVideo.style.pointerEvents = 'none'; // Prevent interaction with the hidden video
            } else if (currentLargestVideo.style.opacity !== '0') {
                 // Ensure it stays hidden if it's still the current largest video.
                currentLargestVideo.style.opacity = '0';
                currentLargestVideo.style.pointerEvents = 'none';
            }


        } else { // No largest video found
            // If no largest video is found, remove the iframe if it exists.
            if (currentIframe && currentIframe.parentNode) {
                currentIframe.remove();
            }
            currentIframe = null; // Reset iframe reference

            // If there was a previously managed video, make it visible again
            if (currentLargestVideo) {
                currentLargestVideo.style.opacity = '';
                currentLargestVideo.style.pointerEvents = '';
                currentLargestVideo = null;
            }
        }

        // --- Media Playback Prevention Logic ---
        // Stop all other audio/video elements on the page.
        const mediaElements = document.querySelectorAll('video, audio');
        mediaElements.forEach(media => {
            // Stop media UNLESS it is the iframe itself, or an element *inside* the iframe.
            if (media !== currentIframe && !isElementInsideIframe(media, currentIframe)) {
                stopAndDisableMedia(media);
            }
        });
    }

    // Function to stop and disable a media element
    function stopAndDisableMedia(media) {
        // Ensure we don't try to stop the iframe itself or null/undefined elements,
        // and only process elements that haven't been stopped yet.
        if (!media || media === currentIframe || stoppedMediaElements.has(media)) {
            return;
        }

        media.pause();
        media.src = ''; // Clear src to prevent future playback
        try {
            media.load(); // Reload to apply src change. This might throw an error if src is already invalid.
        } catch (e) {
            console.warn('Tampermonkey: Error calling media.load() after clearing src:', e);
        }
        media.removeAttribute('autoplay');
        media.removeAttribute('controls');
        media.muted = true; // Mute it forcefully
        media.volume = 0; // Set volume to 0

        // Prevent event listeners from re-enabling playback by adding more listeners and using preventDefault.
        const preventEvent = (e) => {
            e.stopImmediatePropagation();
            e.preventDefault();
        };
        // Add listeners for common media events that might trigger playback.
        media.addEventListener('play', preventEvent, true);
        media.addEventListener('playing', preventEvent, true);
        media.addEventListener('canplay', preventEvent, true);
        media.addEventListener('canplaythrough', preventEvent, true);
        media.addEventListener('loadeddata', preventEvent, true);
        media.addEventListener('loadedmetadata', preventEvent, true);
        media.addEventListener('progress', preventEvent, true);
        media.addEventListener('seeking', preventEvent, true);
        media.addEventListener('seeked', preventEvent, true);
        media.addEventListener('timeupdate', preventEvent, true); // Added for more robustness
        media.addEventListener('ended', preventEvent, true); // Added for more robustness

        stoppedMediaElements.add(media); // Mark this media element as stopped
    }

    // Helper function to check if an element is inside the iframe's document
    function isElementInsideIframe(element, iframe) {
        if (!iframe || !iframe.contentDocument || !iframe.contentDocument.body) {
            return false;
        }
        try {
            // Check if the element is the iframe itself, or a descendant of the iframe's document body.
            // This will throw if cross-origin.
            return iframe.contentDocument.body.contains(element);
        } catch (e) {
            // Cross-origin iframe security might prevent access.
            // In such cases, if we can't access, we assume it's not inside for safety (to prevent stopping the iframe's media).
            return false;
        }
    }

    // --- Initial Setup ---
    // Ensure all existing media elements are stopped immediately when the script loads.
    document.querySelectorAll('video, audio').forEach(stopAndDisableMedia);

    // Apply the main logic once initially.
    applyVideoReplacementLogic();

    // --- Mutation Observers and Event Listeners ---

    // Interval to re-apply logic and stop rogue media. This is a fallback.
    setInterval(applyVideoReplacementLogic, 1000); // Every second

    // Observer for new media elements being added to the DOM.
    const mediaObserver = new MutationObserver(mutationsList => {
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === 1) { // Element node
                        if (node.tagName === 'VIDEO' || node.tagName === 'AUDIO') {
                            stopAndDisableMedia(node);
                        }
                        // Also check for media elements within newly added subtrees.
                        node.querySelectorAll('video, audio').forEach(stopAndDisableMedia);
                    }
                });
            }
        }
    });
    mediaObserver.observe(document.body, { childList: true, subtree: true });

    // Observer for general DOM changes to re-evaluate video replacement logic.
    // This includes changes to attributes that might affect video size/position.
    let applyLogicTimeout = null;
    const DEBOUNCE_TIME = 100; // milliseconds to wait before applying logic after mutations
    const observer = new MutationObserver((mutations) => {
        let videoChangeDetected = false;
        for (const mutation of mutations) {
            if (mutation.type === 'childList' && (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0)) {
                // If nodes are added or removed, it might affect video presence or layout.
                videoChangeDetected = true;
            } else if (mutation.type === 'attributes') {
                // If attributes on a video element or its parents change, it might affect size/position.
                if (mutation.target.tagName === 'VIDEO' || (mutation.target.closest('video') && mutation.attributeName === 'style')) {
                    videoChangeDetected = true;
                } else if (mutation.target.tagName === 'BODY' || mutation.target.tagName === 'HTML' || mutation.target.contains(currentLargestVideo)) {
                    // Also consider changes to body/html or parents of the current largest video.
                    videoChangeDetected = true;
                }
            }
            if (videoChangeDetected) break;
        }

        if (videoChangeDetected) {
            clearTimeout(applyLogicTimeout);
            applyLogicTimeout = setTimeout(() => {
                applyVideoReplacementLogic();
            }, DEBOUNCE_TIME);
        }
    });
    observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class', 'src', 'autoplay', 'controls', 'width', 'height'] });


    // Detect URL changes for single-page applications.
    let lastUrl = window.location.href;
    const urlChangeObserver = new MutationObserver(() => {
        if (window.location.href !== lastUrl) {
            lastUrl = window.location.href;
            applyVideoReplacementLogic(); // Re-evaluate everything on URL change.
        }
    });
    urlChangeObserver.observe(document, { subtree: true, childList: true }); // Observe document for broad changes in SPAs

    // Handle browser history navigation (back/forward buttons) and programmatic URL changes.
    window.addEventListener('popstate', () => {
        if (window.location.href !== lastUrl) {
            lastUrl = window.location.href;
            applyVideoReplacementLogic();
        }
    });
    window.addEventListener('hashchange', () => {
        if (window.location.href !== lastUrl) {
            lastUrl = window.location.href;
            applyVideoReplacementLogic();
        }
    });


    // Override HTMLMediaElement.prototype.play to prevent any media from playing directly.
    const originalPlay = HTMLMediaElement.prototype.play;
    HTMLMediaElement.prototype.play = function() {
        // Allow play only if it's the iframe itself, or if it's not a video/audio element we are actively managing.
        if (this === currentIframe || (this.tagName !== 'VIDEO' && this.tagName !== 'AUDIO')) {
            return originalPlay.apply(this, arguments);
        }
        // For other media elements, prevent playback.
        console.log('Tampermonkey: Preventing playback of:', this);
        this.pause();
        this.currentTime = 0; // Reset playback position
        return Promise.resolve(); // Return a resolved promise to mimic play() behavior.
    };

    // Override HTMLMediaElement.prototype.load to prevent loading of other media.
    const originalLoad = HTMLMediaElement.prototype.load;
    HTMLMediaElement.prototype.load = function() {
        // Allow load only if it's the iframe itself, or if it's not a video/audio element we are actively managing.
        if (this === currentIframe || (this.tagName !== 'VIDEO' && this.tagName !== 'AUDIO')) {
            return originalLoad.apply(this, arguments);
        }
        console.log('Tampermonkey: Preventing load of:', this);
        this.src = ''; // Clear src if an attempt to load occurs.
        return;
    };

    // Listen for scroll events to reposition the iframe
    window.addEventListener('scroll', () => {
        if (currentIframe && currentLargestVideo) {
            updateIframeGeometryAbsolute(currentLargestVideo, currentIframe);
        }
    }, { passive: true });

    // Listen for resize events to adjust iframe size/position
    window.addEventListener('resize', () => {
        clearTimeout(applyLogicTimeout); // Clear any pending DOM mutation debounce
        applyLogicTimeout = setTimeout(() => {
            applyVideoReplacementLogic();
        }, DEBOUNCE_TIME);
    });

    // Override pushState and replaceState to detect programmatic URL changes.
    (function(history){
        const pushState = history.pushState;
        history.pushState = function() {
            const originalResult = pushState.apply(history, arguments);
            window.dispatchEvent(new Event('popstate')); // Trigger popstate event for consistency.
            return originalResult;
        };

        const replaceState = history.replaceState;
        history.replaceState = function() {
            const originalResult = replaceState.apply(history, arguments);
            window.dispatchEvent(new Event('popstate')); // Trigger popstate event for consistency.
            return originalResult;
        };
    })(window.history);

})();

Read 5 times, last 9 minutes ago