Facebook Reels Enhancer

Enable controls on Facebook Reels videos, auto-unmute, and prevent pausing when switching tabs.

// ==UserScript==
// @name         Facebook Reels Enhancer
// @namespace    UserScript
// @match        https://www.facebook.com/*
// @version      2.3
// @license      MIT
// @author       Pyrvox
// @description  Enable controls on Facebook Reels videos, auto-unmute, and prevent pausing when switching tabs.
// @icon         https://www.google.com/s2/favicons?sz=64&domain=facebook.com
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Wait for document to be ready before adding styles
    function addStyles() {
        if (!document.head) {
            setTimeout(addStyles, 10);
            return;
        }

        // Add CSS for better controls handling
        const style = document.createElement('style');
        style.textContent = `
            /* Disable overlay that blocks controls */
            .x1ey2m1c.x78zum5.xdt5ytf.xozqiw3.x17qophe.x13a6bvl.x10l6tqk.x1hkcv85,
            .x1ey2m1c.x78zum5.xdt5ytf.xozqiw3.x17qophe.x13a6bvl.x10l6tqk {
                pointer-events: none !important;
                opacity: 0.01 !important;
            }

            /* Make video controls always visible and clickable */
            video::-webkit-media-controls {
                opacity: 1 !important;
                visibility: visible !important;
                z-index: 99999 !important;
            }

            /* Make video container clickable */
            .x1ey2m1c.x78zum5.xdt5ytf.xozqiw3.x17qophe.x13a6bvl.x10l6tqk video {
                pointer-events: auto !important;
                position: relative;
                z-index: 9999 !important;
            }

            /* Disable other potential overlay elements */
            [role="presentation"],
            [data-pagelet],
            [data-visualcompletion] {
                pointer-events: none !important;
            }

            /* Make sure clickable elements remain clickable */
            [role="button"],
            [tabindex],
            button,
            a {
                pointer-events: auto !important;
            }
        `;
        document.head.appendChild(style);
    }

    // Enhanced unmute function
    function attemptUnmute(video) {
        if (!(video instanceof HTMLVideoElement)) return;

        if (video.muted || video.volume === 0) {
            video.muted = false;
            video.volume = 1.0;

            if (video.audioTracks && video.audioTracks.length > 0) {
                for (let track of video.audioTracks) {
                    track.enabled = true;
                }
            }
            video.dispatchEvent(new Event('volumechange', { bubbles: true }));
        }
    }

    // Enable controls for Reels videos
    function enableReelControls(video) {
        if (!video || video.hasAttribute('controls') || !location.href.includes('reel')) return;

        // Add controls to the video
        video.setAttribute('controls', '');
        video.setAttribute('playsinline', '');

        // Style the video and handle overlays
        setTimeout(() => {
            try {
                // Style the video
                Object.assign(video.style, {
                    'position': 'relative',
                    'zIndex': '99999',
                    'pointerEvents': 'auto',
                    'width': '100%',
                    'height': 'auto',
                    'maxHeight': '100vh'
                });

                // Find and disable overlays
                const overlays = document.querySelectorAll(`
                    .x1ey2m1c.x78zum5.xdt5ytf.xozqiw3.x17qophe.x13a6bvl.x10l6tqk,
                    [role="presentation"],
                    [data-pagelet],
                    [data-visualcompletion]
                `);

                overlays.forEach(overlay => {
                    if (!overlay.contains(video)) {
                        overlay.style.pointerEvents = 'none';
                        overlay.style.opacity = '0.01';
                    }
                });

                // Ensure video is in view
                video.scrollIntoViewIfNeeded();
            } catch (e) {
                console.error('Error in enableReelControls:', e);
            }
        }, 100);
    }

    // Handle play events
    function handlePlayEvent(event) {
        try {
            const target = event.target;
            if (!(target instanceof HTMLVideoElement)) return;

            enableReelControls(target);
            attemptUnmute(target);

            // Additional check for muted state after a short delay
            setTimeout(() => attemptUnmute(target), 500);
        } catch (e) {
            console.error('Error in handlePlayEvent:', e);
        }
    }

    // Handle click events for mute/unmute buttons
    function handleClickEvent(event) {
        try {
            const target = event.target;
            const isMuteButton = target.closest(`
                [aria-label*="mute"],
                [aria-label*="sound"],
                [role="button"][aria-pressed],
                [aria-label*="sonido"],
                [aria-label*="silenciar"]
            `);

            if (isMuteButton) {
                setTimeout(() => {
                    document.querySelectorAll('video').forEach(attemptUnmute);
                }, 100);
            }
        } catch (e) {
            console.error('Error in handleClickEvent:', e);
        }
    }

    // Prevent pausing when switching tabs
    function handleVisibilityChange() {
        try {
            // Force visible state
            Object.defineProperty(document, 'visibilityState', {
                get: () => 'visible',
                configurable: true
            });

            Object.defineProperty(document, 'hidden', {
                get: () => false,
                configurable: true
            });

            // Resume any paused videos
            document.querySelectorAll('video').forEach(video => {
                if (video.paused && video.readyState >= 2) {
                    video.play().catch(e => console.log('Auto-play failed:', e));
                }
            });
        } catch (e) {
            console.error('Error in handleVisibilityChange:', e);
        }
    }

    // Override pause method
    function initPauseHandler() {
        const originalPause = HTMLMediaElement.prototype.pause;
        HTMLMediaElement.prototype.pause = function() {
            try {
                if (this.closest('[href*="reel"], [data-pagelet*="reel"], [class*="reel"]')) {
                    return; // Prevent reels from pausing
                }
                return originalPause.apply(this, arguments);
            } catch (e) {
                console.error('Error in pause handler:', e);
                return originalPause.apply(this, arguments);
            }
        };
        return originalPause;
    }

    // Initialize
    function init() {
        try {
            console.log('Facebook Reels Enhancer initialized');

            // Add styles first
            addStyles();

            // Set up pause handler
            const originalPause = initPauseHandler();

            // Set up event listeners
            document.addEventListener('play', handlePlayEvent, true);
            document.addEventListener('click', handleClickEvent, true);
            document.addEventListener('visibilitychange', handleVisibilityChange, true);

            // Set up MutationObserver for dynamically loaded content
            const observer = new MutationObserver(mutations => {
                for (const mutation of mutations) {
                    if (mutation.addedNodes) {
                        mutation.addedNodes.forEach(node => {
                            // Check if node is a video
                            if (node.nodeName === 'VIDEO') {
                                enableReelControls(node);
                                attemptUnmute(node);
                            }
                            // Check for video elements within added nodes
                            else if (node.querySelectorAll) {
                                const videos = node.querySelectorAll('video');
                                videos.forEach(video => {
                                    enableReelControls(video);
                                    attemptUnmute(video);
                                });
                            }
                        });
                    }
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });

            // Initial check for videos
            document.querySelectorAll('video').forEach(video => {
                enableReelControls(video);
                attemptUnmute(video);
            });

            // Clean up on page unload
            window.addEventListener('beforeunload', () => {
                document.removeEventListener('play', handlePlayEvent, true);
                document.removeEventListener('click', handleClickEvent, true);
                document.removeEventListener('visibilitychange', handleVisibilityChange, true);
                observer.disconnect();
                HTMLMediaElement.prototype.pause = originalPause;
            });
        } catch (e) {
            console.error('Error initializing Facebook Reels Enhancer:', e);
        }
    }

    // Start the script
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        // If document is already loaded, wait a bit to ensure everything is ready
        setTimeout(init, 500);
    }
})();