雨课堂阻止自动暂停播放

阻止雨课堂视频在失去焦点时自动暂停

// ==UserScript==
// @name             雨课堂阻止自动暂停播放
// @name:en          Yuketang Anti Auto Pause
// @name:es          Evitar pausa automática en Yuketang
// @namespace        https://tampermonkey.net/
// @version          1.0
// @description      阻止雨课堂视频在失去焦点时自动暂停
// @description:en   Prevent Yuketang videos from auto-pausing when window loses focus
// @description:es   Evita que los vídeos de Yuketang se pausen automáticamente al perder el foco de la ventana
// @author           Anonym
// @match            https://*.yuketang.cn/*
// @grant            none
// @license          MIT
// ==/UserScript==

(function () {
    'use strict';

    console.log('🚀 雨课堂防暂停解决方案 v1.0');
    console.log('');

    // 防止重复执行
    if (window.YuketangAntiPause) {
        console.log('⚠️ 防暂停脚本已在运行中');
        return;
    }

    // 标记脚本已运行
    window.YuketangAntiPause = {
        version: '1.0',
        status: 'active',
        stats: {
            videoPausePrevented: 0,
            eventBlocked: 0,
            startTime: new Date()
        },
        // 面板控制函数
        togglePanel: () => {
            console.log('🔧 togglePanel 被调用');
            const panel = document.getElementById('yuketang-anti-pause-panel');
            const content = document.getElementById('panel-content');
            const mini = document.getElementById('panel-mini');

            if (!panel || !content || !mini) {
                console.log('❌ 找不到面板元素');
                return;
            }

            if (content.style.display === 'none') {
                // 展开面板
                console.log('🔧 展开面板');
                content.style.display = 'block';
                mini.style.display = 'none';
                panel.style.padding = '12px';
                panel.style.minWidth = '160px';
                panel.style.opacity = '1';
            } else {
                // 最小化面板
                console.log('🔧 最小化面板');
                content.style.display = 'none';
                mini.style.display = 'block';
                panel.style.padding = '8px';
                panel.style.minWidth = '32px';
                panel.style.opacity = '0.5';
            }
        }
    };

    const stats = window.YuketangAntiPause.stats;

    // ==================== 核心功能区域 ====================

    // 1. 视频暂停拦截 - 最重要的功能
    console.log('🎯 正在保护视频播放...');
    const protectVideos = () => {
        const videos = document.querySelectorAll('video');
        videos.forEach((video, index) => {
            if (!video._antiPauseProtected) {
                const originalPause = video.pause;

                video.pause = function () {
                    stats.videoPausePrevented++;
                    console.log(`🛡️ 阻止视频${index + 1}暂停 (第${stats.videoPausePrevented}次)`);
                    return Promise.resolve();
                };

                video._antiPauseProtected = true;
                console.log(`✅ 已保护视频${index + 1}`);
            }
        });
        return videos.length;
    };

    const videoCount = protectVideos();
    if (videoCount === 0) {
        console.log('⏳ 暂未发现视频,将持续监控...');
    }

    // 2. 页面可见性伪装 - 核心欺骗机制
    console.log('🎭 正在伪装页面可见性...');

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

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

        console.log('✅ 页面可见性伪装成功');
    } catch (e) {
        console.log('⚠️ 页面可见性伪装失败:', e.message);
    }

    // 3. jQuery事件拦截 - 雨课堂特有机制
    if (window.$ && window.$.fn && window.$.fn.trigger) {
        console.log('🔧 正在设置jQuery事件拦截...');

        const originalTrigger = window.$.fn.trigger;
        let controlToggleCount = 0;
        let lastToggleLogTime = 0;

        window.$.fn.trigger = function (event, data) {
            if (typeof event === 'string') {
                // 拦截所有暂停相关事件
                if (event.includes('pause') && !event.includes('toggle')) {
                    stats.eventBlocked++;
                    console.log(`🚫 拦截暂停事件: "${event}"`);
                    return this;
                }

                // 控制toggle事件日志频率
                if (event === 'control.toggle') {
                    controlToggleCount++;
                    const now = Date.now();

                    if (now - lastToggleLogTime > 5000) {
                        console.log(`🔄 已处理${controlToggleCount}次control.toggle事件`);
                        lastToggleLogTime = now;
                    }
                }
            }

            return originalTrigger.call(this, event, data);
        };

        console.log('✅ jQuery事件拦截设置完成');
    } else {
        console.log('⚠️ 未检测到jQuery,跳过相关拦截');
    }

    // 4. 事件监听器保护
    console.log('🛡️ 正在设置事件监听器保护...');

    // 清理现有的危险事件处理器
    const dangerousEvents = ['onblur', 'onfocus', 'onvisibilitychange', 'onpagehide', 'onpageshow'];
    dangerousEvents.forEach(event => {
        if (window[event]) {
            window[event] = null;
            console.log(`🧹 清理了window.${event}`);
        }
    });

    // 拦截新的事件监听器添加
    const originalAddEventListener = EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener = function (type, listener, options) {
        const blockedEvents = ['visibilitychange', 'blur', 'focus', 'pagehide', 'pageshow'];

        if (blockedEvents.includes(type)) {
            console.log(`🚫 阻止添加事件监听器: ${type}`);
            return;
        }

        return originalAddEventListener.call(this, type, listener, options);
    };

    console.log('✅ 事件监听器保护设置完成');

    // ==================== 监控和恢复机制 ====================

    // 5. 定期检查和恢复视频播放
    const keepVideoPlaying = () => {
        const videos = document.querySelectorAll('video');
        videos.forEach((video, index) => {
            if (video.paused && !video.ended && video.readyState >= 2) {
                console.log(`🔄 自动恢复视频${index + 1}播放`);
                video.play().catch(() => { });
            }
        });

        // 检查新视频
        if (videos.length > videoCount) {
            protectVideos();
        }
    };

    setInterval(keepVideoPlaying, 3000);

    // 6. 状态监控面板
    console.log('📊 正在创建状态监控面板...');

    const createStatusPanel = () => {
        // 移除旧面板
        const existingPanel = document.getElementById('yuketang-anti-pause-panel');
        if (existingPanel) {
            existingPanel.remove();
        }

        const panel = document.createElement('div');
        panel.id = 'yuketang-anti-pause-panel';
        panel.innerHTML = `
            <div id="panel-content">
                <div style="font-weight: bold; color: #fff; margin-bottom: 4px;">
                    🛡️ 雨课堂防暂停 v1.0
                </div>
                <div id="panel-stats" style="font-size: 11px; line-height: 1.3;"></div>
                <div style="margin-top: 6px;">
                    <button id="minimize-btn"
                            style="padding: 2px 6px; font-size: 10px; border: none; border-radius: 2px; cursor: pointer;">
                        最小化
                    </button>
                </div>
            </div>
            <div id="panel-mini" style="display: none; text-align: center; font-size: 16px; line-height: 1;">
                🛡️
            </div>
        `;

        panel.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: linear-gradient(135deg, #4CAF50, #45a049);
            color: white;
            padding: 12px;
            border-radius: 8px;
            font-size: 12px;
            font-family: Arial, sans-serif;
            z-index: 999999;
            min-width: 160px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            backdrop-filter: blur(10px);
            border: 1px solid rgba(255,255,255,0.2);
            cursor: pointer;
            transition: all 0.3s ease;
        `;

        document.body.appendChild(panel);

        // 添加事件监听器
        const minimizeBtn = document.getElementById('minimize-btn');
        if (minimizeBtn) {
            minimizeBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                console.log('🔧 点击了最小化按钮');
                window.YuketangAntiPause.togglePanel();
            });
        }

        // 添加面板点击事件(用于最小化状态下的展开)
        panel.addEventListener('click', (e) => {
            const mini = document.getElementById('panel-mini');
            if (mini && mini.style.display !== 'none') {
                console.log('🔧 点击了最小化面板');
                window.YuketangAntiPause.togglePanel();
            }
        });

        // 更新状态信息
        const updateStats = () => {
            const statsEl = document.getElementById('panel-stats');
            if (statsEl) {
                const runTime = Math.floor((new Date() - stats.startTime) / 1000);
                statsEl.innerHTML = `
                    状态: <span style="color: #90EE90;">● 运行中</span><br>
                    运行时间: ${runTime}秒<br>
                    拦截暂停: ${stats.videoPausePrevented}次<br>
                    阻止事件: ${stats.eventBlocked}次<br>
                    页面焦点: ${document.hasFocus() ? '✓ 有' : '✗ 无'}
                `;
            }
        };

        setInterval(updateStats, 2000);
        updateStats();

        console.log('✅ 状态监控面板创建完成');
    };

    createStatusPanel();

    // 7. 错误处理
    window.addEventListener('error', (e) => {
        if (e.message && e.message.includes('pause')) {
            console.log('🛡️ 拦截暂停相关错误');
            e.preventDefault();
        }
    }, true);

    // ==================== 启动完成 ====================

    console.log('');
    console.log('🎉 雨课堂防暂停终极解决方案启动完成!');
    console.log('');
    console.log('🔧 已启用功能:');
    console.log('   ✅ 视频暂停拦截');
    console.log('   ✅ 页面可见性伪装');
    console.log('   ✅ jQuery事件拦截');
    console.log('   ✅ 事件监听器保护');
    console.log('   ✅ 自动恢复播放');
    console.log('   ✅ 状态监控面板');
    console.log('   ✅ 优化日志输出');
    console.log('');
    console.log('💡 现在可以安全地切换窗口或标签页,视频将继续播放!');
    console.log('📱 右上角的绿色面板显示实时状态信息');
    console.log('');

})();