Greasy Fork

来自缓存

Greasy Fork is available in English.

学习通视频自动播放与跳转

自动播放学习通视频,播放完成后自动跳转到下一节(目标科目是英语)

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         学习通视频自动播放与跳转
// @namespace    https://github.com/Delta-Water
// @version      2.7
// @description  自动播放学习通视频,播放完成后自动跳转到下一节(目标科目是英语)
// @author       Delta_Water
// @match        https://mooc1.chaoxing.com/mycourse/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=chaoxing.com
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // 配置参数
    const config = {
        autoStart: true,           // 是否自动开始
        autoMute: false,           // 是否自动静音
        videoEndThreshold: 0.98,   // 视频完成阈值
        checkInterval: 2000,       // 检查间隔
        maxRetryCount: 10,         // 最大重试次数
        nextPageWaitTime: 3000     // 进入下一集后的等待时间(毫秒)
    };

    // 全局变量
    let video = null;
    let videojsPlayer = null;
    let currentTaskIndex = -1;
    let isRunning = false;
    let taskList = [];
    let retryCount = 0;
    let isVideoPage = false;

    // 简洁日志
    function log(msg) {
        console.log('[学习通]', msg);
    }

    // 检测当前页面是否为视频页面
    function detectVideoPage() {
        // 方法1: 直接查找视频元素
        video = getVideoElement();
        if (video) {
            log(`找到视频元素: ${video.tagName} ${video.id || video.className}`);
            return true;
        }

        // 方法2: 查找视频播放器相关元素
        const videoPlayers = document.querySelectorAll('.vjs-tech, .video-js, .video-player, [class*="video"]');
        if (videoPlayers.length > 0) {
            log(`找到视频播放器元素: ${videoPlayers.length}个`);
            return true;
        }

        // 方法3: 查找iframe中的视频
        const iframes = document.querySelectorAll('iframe');
        for (let iframe of iframes) {
            try {
                const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
                if (iframeDoc.querySelector('video, .vjs-tech, #video_html5_api')) {
                    log('iframe中发现视频元素');
                    return true;
                }
            } catch (e) {}
        }

        // 方法4: 检查页面标题或内容是否包含视频相关关键词
        const pageText = document.body.textContent.toLowerCase();
        const videoKeywords = ['视频', 'video', '播放', 'play', '观看', 'watch'];
        for (let keyword of videoKeywords) {
            if (pageText.includes(keyword) && pageText.length < 10000) {
                // 进一步检查是否有视频控件
                const controls = document.querySelectorAll('button[title*="播放"], button[title*="play"]');
                if (controls.length > 0) {
                    log(`找到视频相关控件: ${keyword}`);
                    return true;
                }
            }
        }

        return false;
    }

    // 获取视频元素
    function getVideoElement() {
        // 1. 直接通过ID获取
        video = document.getElementById('video_html5_api');
        if (video) return video;

        // 2. 通过类名获取
        video = document.querySelector('.vjs-tech');
        if (video) return video;

        // 3. 查找所有video元素
        const videos = document.querySelectorAll('video');
        if (videos.length > 0) {
            video = videos[0];
            return video;
        }

        // 4. 尝试从iframe中获取
        try {
            const iframes = document.querySelectorAll('iframe');
            for (let iframe of iframes) {
                try {
                    const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
                    video = iframeDoc.querySelector('#video_html5_api') || iframeDoc.querySelector('video');
                    if (video) return video;

                    // 查找嵌套iframe
                    const nestedIframes = iframeDoc.querySelectorAll('iframe');
                    for (let nestedIframe of nestedIframes) {
                        try {
                            const nestedDoc = nestedIframe.contentDocument || nestedIframe.contentWindow.document;
                            video = nestedDoc.querySelector('#video_html5_api') || nestedDoc.querySelector('video');
                            if (video) return video;
                        } catch (e) {}
                    }
                } catch (e) {}
            }
        } catch (e) {}

        return null;
    }

    // 获取所有任务列表
    function getTaskList() {
        taskList = [];

        // 查找所有章节任务
        const tasks = document.querySelectorAll('.posCatalog_name, .chapter_item, .catalog_section li, .timeline li, [onclick*="getTeacherAjax"]');

        tasks.forEach((task, index) => {
            const title = task.textContent.trim();
            const onclick = task.getAttribute('onclick');

            if (onclick && onclick.includes('getTeacherAjax')) {
                taskList.push({
                    element: task,
                    title: title,
                    onclick: onclick,
                    index: index
                });
            }
        });

        if (taskList.length === 0) {
            // 备用方案:查找所有可点击的章节
            const chapterLinks = document.querySelectorAll('a[href*="getTeacherAjax"], span[onclick*="getTeacherAjax"]');
            chapterLinks.forEach((link, index) => {
                const title = link.textContent.trim();
                const onclick = link.getAttribute('onclick') || link.getAttribute('href');
                if (onclick && onclick.includes('getTeacherAjax')) {
                    taskList.push({
                        element: link,
                        title: title,
                        onclick: onclick,
                        index: index
                    });
                }
            });
        }

        log(`找到 ${taskList.length} 个任务`);
        return taskList;
    }

    // 查找当前正在播放的任务
    function findCurrentTask() {
        if (taskList.length === 0) return -1;

        // 查找active状态的元素
        const activeElements = document.querySelectorAll('.current, .active, .currents, .playing, .focus');

        for (let activeEl of activeElements) {
            // 在taskList中查找对应的元素
            for (let i = 0; i < taskList.length; i++) {
                if (taskList[i].element === activeEl ||
                    taskList[i].element.contains(activeEl) ||
                    activeEl.contains(taskList[i].element)) {
                    return i;
                }
            }
        }

        return -1;
    }

    // 播放下一个任务
    function playNextTask() {
        if (taskList.length === 0) {
            getTaskList();
            if (taskList.length === 0) {
                log('未找到任务列表');
                return false;
            }
        }

        let nextIndex = currentTaskIndex + 1;

        // 如果当前任务未知,尝试查找
        if (currentTaskIndex === -1) {
            currentTaskIndex = findCurrentTask();
            if (currentTaskIndex !== -1) {
                nextIndex = currentTaskIndex + 1;
            } else {
                nextIndex = 0;
            }
        }

        // 检查是否所有任务都已完成
        if (nextIndex >= taskList.length) {
            log('所有任务已完成');
            return false;
        }

        // 点击下一个任务
        const nextTask = taskList[nextIndex];
        log(`跳转到任务 ${nextIndex + 1}/${taskList.length}: ${nextTask.title}`);
        nextTask.element.click();

        currentTaskIndex = nextIndex;
        retryCount = 0;

        return true;
    }

    // 跳过当前任务(重试失败时调用)
    function skipCurrentTask() {
        log(`跳过当前任务 ${currentTaskIndex + 1}`);

        // 直接播放下一个任务
        if (playNextTask()) {
            // 重置相关状态
            isVideoPage = false;
            video = null;
            videojsPlayer = null;
            retryCount = 0;

            // 等待页面加载后重新检测
            setTimeout(checkAndPlay, config.nextPageWaitTime);
            return true;
        }
        return false;
    }

    // 阻止暂停功能
    function enableAntiPause() {
        video = getVideoElement();
        if (!video) return false;

        log(`视频信息: ${video.tagName} ${video.id || ''} ${video.className || ''}`);

        // 获取Video.js播放器
        if (typeof videojs !== 'undefined') {
            const players = videojs.getPlayers();
            for (const playerId in players) {
                if (players[playerId].el().contains(video)) {
                    videojsPlayer = players[playerId];
                    log('找到Video.js播放器');
                    break;
                }
            }
        }

        // 阻止原生video的pause方法
        if (!video._originalPause) {
            video._originalPause = video.pause;
            video.pause = () => {
                log('阻止视频暂停');
                video.play().catch(e => {});
            };
        }

        // 针对Video.js播放器的处理
        if (videojsPlayer && !videojsPlayer._originalPause) {
            videojsPlayer._originalPause = videojsPlayer.pause;
            videojsPlayer.pause = () => {
                log('阻止Video.js暂停');
                videojsPlayer.play().catch(e => {});
            };
        }

        return true;
    }

    // 静音功能
    function muteVideo() {
        if (!video) video = getVideoElement();
        if (!video) return;

        video.volume = 0;
        video.muted = true;

        if (videojsPlayer) {
            videojsPlayer.muted(true);
            videojsPlayer.volume(0);
        }

        log('视频已静音');
    }

    // 开始播放当前视频 - 保持原来的播放方法
    function startPlaying() {
        if (!video) video = getVideoElement();
        if (!video) return false;

        // 阻止暂停
        if (!enableAntiPause()) {
            return false;
        }

        // 自动静音
        if (config.autoMute) {
            muteVideo();
        }

        // 如果视频暂停,开始播放
        if (video.paused) {
            log('开始播放视频');
            video.play().catch(e => {
                log('自动播放失败: ' + e.message);
                // 尝试点击播放按钮
                setTimeout(() => {
                    const playButtons = document.querySelectorAll('button, .vjs-big-play-button, [class*="play"]');
                    for (let btn of playButtons) {
                        if (btn.textContent.includes('播放') ||
                            btn.className.includes('play') ||
                            btn.getAttribute('title')?.includes('播放')) {
                            btn.click();
                            log('点击播放按钮');
                            break;
                        }
                    }
                }, 1000);
            });
        } else {
            log('视频已在播放中');
        }

        return true;
    }

    // 监控视频播放
    function monitorVideoPlayback() {
        if (!video) return;

        // 清除之前的监听器
        video.removeEventListener('ended', handleVideoEnded);
        video.removeEventListener('timeupdate', handleTimeUpdate);

        if (videojsPlayer) {
            videojsPlayer.off('ended', handleVideoEnded);
            videojsPlayer.off('timeupdate', handleTimeUpdate);
        }

        // 添加新的监听器
        video.addEventListener('ended', handleVideoEnded);
        video.addEventListener('timeupdate', handleTimeUpdate);

        if (videojsPlayer) {
            videojsPlayer.on('ended', handleVideoEnded);
            videojsPlayer.on('timeupdate', handleTimeUpdate);
        }

        log('开始监控视频播放');
    }

    // 时间更新处理
    function handleTimeUpdate() {
        if (!video || video.duration <= 0) return;

        const progress = video.currentTime / video.duration;
        if (progress >= config.videoEndThreshold && !video.ended) {
            log(`视频即将完成: ${(progress * 100).toFixed(1)}%`);
            // 提前准备跳转
            if (progress > 0.99) {
                handleVideoEnded();
            }
        }
    }

    // 视频结束处理
    function handleVideoEnded() {
        log('视频播放完成,准备跳转到下一节');

        // 等待一小段时间确保视频完全结束
        setTimeout(() => {
            isVideoPage = false;
            video = null;
            videojsPlayer = null;

            // 跳转到下一个任务
            if (playNextTask()) {
                // 使用配置的等待时间
                log(`等待 ${config.nextPageWaitTime}ms 后开始检测`);
                setTimeout(checkAndPlay, config.nextPageWaitTime);
            } else {
                log('没有更多任务');
                isRunning = false;
                updateStatus();
            }
        }, 2000);
    }

    // 检查并播放视频
    function checkAndPlay() {
        retryCount++;

        // 检查是否超过最大重试次数
        if (retryCount > config.maxRetryCount) {
            log(`超过最大重试次数 (${config.maxRetryCount}),自动跳过当前任务`);

            // 自动跳过当前任务
            if (skipCurrentTask()) {
                log('已自动跳过当前任务,尝试下一个任务');
                return;
            } else {
                log('无法跳过当前任务,停止尝试');
                isRunning = false;
                updateStatus();
                return;
            }
        }

        log(`第 ${retryCount} 次检测页面...`);

        // 检测是否为视频页面
        isVideoPage = detectVideoPage();

        if (isVideoPage) {
            log('检测到视频页面,开始处理');
            video = getVideoElement();

            if (video) {
                // 开始播放
                if (startPlaying()) {
                    // 开始监控
                    monitorVideoPlayback();
                    retryCount = 0; // 重置重试计数
                } else {
                    // 重试
                    setTimeout(checkAndPlay, 2000);
                }
            } else {
                // 重试获取视频
                setTimeout(checkAndPlay, 2000);
            }
        } else {
            log('当前页面不是视频页面');

            // 如果当前不是视频页面,等待后尝试跳转
            setTimeout(() => {
                if (!isVideoPage && isRunning) {
                    log('尝试跳转到下一个任务');
                    playNextTask();

                    // 等待后重新检测
                    log(`等待 ${config.nextPageWaitTime}ms 后重新检测`);
                    setTimeout(checkAndPlay, config.nextPageWaitTime);
                }
            }, config.nextPageWaitTime);
        }
    }

    // 主监控循环
    function startMonitoring() {
        if (isRunning) return;
        isRunning = true;

        log('开始自动播放监控');

        // 初始化任务列表
        getTaskList();

        // 查找当前任务
        currentTaskIndex = findCurrentTask();
        if (currentTaskIndex !== -1) {
            log(`当前任务: ${currentTaskIndex + 1}/${taskList.length}`);
        }

        // 开始检测和播放
        checkAndPlay();

        // 定期检查状态
        const statusCheck = setInterval(() => {
            if (!isRunning) {
                clearInterval(statusCheck);
                return;
            }

            // 如果当前是视频页面但没有视频元素,重新检测
            if (isVideoPage && !video) {
                video = getVideoElement();
                if (!video) {
                    log('视频页面丢失视频元素,重新检测');
                    isVideoPage = false;
                    checkAndPlay();
                }
            }

            updateStatus();
        }, 5000);
    }

    // 创建简洁的控制面板
    function createControlPanel() {
        const style = document.createElement('style');
        style.textContent = `
            .cx-control-panel {
                position: fixed;
                bottom: 20px;
                right: 20px;
                z-index: 9999;
                display: flex;
                flex-direction: column;
                gap: 8px;
            }

            .cx-control-btn {
                padding: 8px 16px;
                background: linear-gradient(45deg, #4CAF50, #2E7D32);
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-size: 14px;
                font-weight: bold;
                box-shadow: 0 2px 5px rgba(0,0,0,0.2);
                transition: all 0.3s;
                min-width: 120px;
                text-align: center;
            }

            .cx-control-btn:hover {
                opacity: 0.9;
                transform: translateY(-2px);
            }

            .cx-control-btn:active {
                transform: translateY(0);
            }

            .cx-control-btn.stop {
                background: linear-gradient(45deg, #f44336, #c62828);
            }

            .cx-control-btn.next {
                background: linear-gradient(45deg, #FF9800, #F57C00);
            }

            .cx-control-btn.skip {
                background: linear-gradient(45deg, #9C27B0, #6A1B9A);
            }

            .cx-status {
                position: fixed;
                top: 10px;
                right: 10px;
                background: rgba(0, 0, 0, 0.8);
                color: white;
                padding: 8px 12px;
                border-radius: 4px;
                font-size: 12px;
                z-index: 9998;
                border-left: 3px solid #4CAF50;
                min-width: 200px;
            }
        `;
        document.head.appendChild(style);

        // 状态显示
        const statusDiv = document.createElement('div');
        statusDiv.className = 'cx-status';
        statusDiv.innerHTML = `
            <div style="font-weight: bold; margin-bottom: 4px; color: #4CAF50;">学习通自动播放 v2.7</div>
            <div>状态: <span id="cx-status">准备中</span></div>
            <div>页面: <span id="cx-page">检测中</span></div>
            <div>任务: <span id="cx-task">-/-</span></div>
            <div>重试: <span id="cx-retry">0/${config.maxRetryCount}</span></div>
        `;
        document.body.appendChild(statusDiv);

        // 更新状态
        window.updateStatus = function() {
            const statusEl = document.getElementById('cx-status');
            const pageEl = document.getElementById('cx-page');
            const taskEl = document.getElementById('cx-task');
            const retryEl = document.getElementById('cx-retry');

            if (statusEl) {
                statusEl.textContent = isRunning ? '运行中' : '已停止';
                statusEl.style.color = isRunning ? '#4CAF50' : '#f44336';
            }

            if (pageEl) {
                pageEl.textContent = isVideoPage ? '视频页面' : '非视频页面';
                pageEl.style.color = isVideoPage ? '#4CAF50' : '#FF9800';
            }

            if (taskEl) {
                const current = currentTaskIndex >= 0 ? currentTaskIndex + 1 : '?';
                const total = taskList.length || '?';
                taskEl.textContent = `${current}/${total}`;
            }

            if (retryEl) {
                retryEl.textContent = `${retryCount}/${config.maxRetryCount}`;
                retryEl.style.color = retryCount >= config.maxRetryCount ? '#f44336' :
                                     retryCount > config.maxRetryCount/2 ? '#FF9800' : '#4CAF50';
            }
        };

        // 控制面板 - 精简版(移除静音按钮)
        const panel = document.createElement('div');
        panel.className = 'cx-control-panel';

        // 开始按钮
        const startBtn = document.createElement('button');
        startBtn.className = 'cx-control-btn';
        startBtn.textContent = '▶ 开始';
        startBtn.onclick = () => {
            isRunning = true;
            startMonitoring();
            updateStatus();
        };

        // 停止按钮
        const stopBtn = document.createElement('button');
        stopBtn.className = 'cx-control-btn stop';
        stopBtn.textContent = '⏹️ 停止';
        stopBtn.onclick = () => {
            isRunning = false;
            updateStatus();
        };

        // 下一集按钮
        const nextBtn = document.createElement('button');
        nextBtn.className = 'cx-control-btn next';
        nextBtn.textContent = '⏭️ 下一集';
        nextBtn.onclick = () => {
            if (playNextTask()) {
                setTimeout(() => {
                    video = null;
                    videojsPlayer = null;
                    isVideoPage = false;
                    retryCount = 0;

                    // 等待后重新检测
                    setTimeout(checkAndPlay, config.nextPageWaitTime);
                }, 1000);
            }
        };

        // 跳过按钮
        const skipBtn = document.createElement('button');
        skipBtn.className = 'cx-control-btn skip';
        skipBtn.textContent = '⏩ 跳过';
        skipBtn.onclick = () => {
            if (skipCurrentTask()) {
                log('手动跳过当前任务');
            }
        };

        panel.appendChild(startBtn);
        panel.appendChild(stopBtn);
        panel.appendChild(nextBtn);
        panel.appendChild(skipBtn);
        document.body.appendChild(panel);

        // 定期更新状态
        setInterval(updateStatus, 1000);
    }

    // 初始化
    function init() {
        log('脚本加载完成 v2.7');
        createControlPanel();

        // 自动开始
        if (config.autoStart) {
            setTimeout(() => {
                isRunning = true;
                startMonitoring();
                updateStatus();
            }, 2000);
        }

        // 监听页面变化
        const observer = new MutationObserver(() => {
            if (!video && isRunning) {
                // 页面变化时重新检测
                setTimeout(() => {
                    video = getVideoElement();
                    if (video && !isVideoPage) {
                        isVideoPage = true;
                        checkAndPlay();
                    }
                }, 1000);
            }
        });

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

    // 页面加载完成后初始化
    if (document.readyState === 'loading') {
        window.addEventListener('load', init);
    } else {
        init();
    }

})();