小红书视频 自动播放&自动取消静音

小红书网页版:新打开视频笔记时,自动播放并取消静音。

// ==UserScript==
// @name         小红书视频 自动播放&自动取消静音
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  小红书网页版:新打开视频笔记时,自动播放并取消静音。
// @author       GVAAA
// @match        *://www.xiaohongshu.com/*
// @icon         https://www.xiaohongshu.com/favicon.ico
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    /**
     * 核心处理函数:对指定的 video 元素进行自动播放和取消静音操作
     * @param {HTMLVideoElement} video - 视频DOM元素
     */
    function handleVideoPlayback(video) {
        // 添加一个标记,防止重复处理同一个 video 元素
        if (video.__autoPlayAndUnmuteHandled) {
            return;
        }
        video.__autoPlayAndUnmuteHandled = true;

        // 1. 尝试直接播放视频
        // .play() 方法会返回一个 Promise
        const playPromise = video.play();

        if (playPromise !== undefined) {
            playPromise.then(_ => {
                // 播放成功后,确保取消静音
                video.muted = false;
                if (video.volume < 0.1) { // 如果音量过低,则设置为100%
                    video.volume = 1.0;
                }
                console.log('XHS Helper: 视频已自动播放并取消静音。');
            }).catch(error => {
                // 自动播放失败(通常是由于浏览器策略限制)
                // 此时,我们仍然确保一旦用户手动播放,声音是开启的
                console.error('XHS Helper: 自动播放被浏览器阻止。', error);
                video.muted = false; // 预先取消静音,等待用户交互
            });
        }

        // 2. 无论是否自动播放成功,都立即设置取消静音
        // 这样即使用户需要手动点击播放,声音也已经准备好了
        video.muted = false;

        // 3. 添加 'play' 事件监听器作为保险
        // 如果视频因任何原因(例如用户手动暂停再播放)进入播放状态,都再次确保其为非静音状态
        video.addEventListener('play', () => {
            if (video.muted) {
                video.muted = false;
            }
        });
    }

    /**
     * 扫描整个文档,寻找并处理所有的 video 元素
     */
    function scanAndProcessVideos() {
        document.querySelectorAll('video').forEach(handleVideoPlayback);
    }

    /**
     * 设置一个DOM变动观察器 (MutationObserver)
     * 这是应对小红书动态加载内容(如弹窗或无刷新跳转)的关键
     */
    function setupMutationObserver() {
        if (window.__xhsVideoObserver) return;
        window.__xhsVideoObserver = true;

        const observer = new MutationObserver(mutations => {
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (node.nodeType === 1) { // 仅处理元素节点
                        // 如果新添加的节点本身就是 video
                        if (node.tagName === 'VIDEO') {
                            handleVideoPlayback(node);
                        }
                        // 否则,在其子树中查找 video
                        else if (node.querySelectorAll) {
                            node.querySelectorAll('video').forEach(handleVideoPlayback);
                        }
                    }
                }
            }
        });

        observer.observe(document.body, {
            childList: true, // 观察子节点的增删
            subtree: true    // 观察所有后代节点
        });
    }

    /**
     * 等待 <body> 元素加载完毕后再执行关键操作
     * @param {Function} callback - 待执行的回调函数
     */
    function onBodyReady(callback) {
        if (document.body) {
            callback();
        } else {
            const observer = new MutationObserver(() => {
                if (document.body) {
                    observer.disconnect();
                    callback();
                }
            });
            observer.observe(document.documentElement, { childList: true });
        }
    }

    // --- 脚本执行入口 ---

    // 1. 等待 body 加载后,立即扫描并设置观察器
    onBodyReady(() => {
        scanAndProcessVideos();
        setupMutationObserver();
    });

    // 2. 为确保万无一失,在 DOM 完全加载后再次扫描
    window.addEventListener('DOMContentLoaded', scanAndProcessVideos);
})();