Facebook Reels Enhancer

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
    }
})();