您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
修复视频完成检测和自动切换问题
// ==UserScript== // @name 超星慕课自动播放防切屏 v2.9 // @namespace http://tampermonkey.net/ // @version 2.9 // @description 修复视频完成检测和自动切换问题 // @author Assistant // @match https://mooc1.chaoxing.com/* // @run-at document-start // @grant unsafeWindow // @license MIT // ==/UserScript== (function() { 'use strict'; const window = unsafeWindow; const isInIframe = window.self !== window.top; const scriptId = 'chaoxingAutoPlayScript_' + (isInIframe ? 'iframe' : 'main'); if (window[scriptId]) { return; } window[scriptId] = true; // === 防切屏代码 === const blackListedEvents = new Set([ "visibilitychange", "blur", "focus", "pagehide", "freeze", "resume", "mouseleave", "mouseout", "keyup", "keydown" ]); const scriptPrefix = "[超星慕课增强" + (isInIframe ? "-iframe" : "") + "]"; const log = console.log.bind(console, `%c${scriptPrefix}`, 'color: #1E88E5; font-weight: bold;'); const warn = console.warn.bind(console, `%c${scriptPrefix}`, 'color: #FB8C00; font-weight: bold;'); const error = console.error.bind(console, `%c${scriptPrefix}`, 'color: #D32F2F; font-weight: bold;'); try { Object.defineProperty(document, 'hidden', { value: false, configurable: true }); Object.defineProperty(document, 'visibilityState', { value: "visible", configurable: true }); Object.defineProperty(document, 'hasFocus', { value: () => true, configurable: true }); const originalAddEventListener = EventTarget.prototype.addEventListener; EventTarget.prototype.addEventListener = function(type, listener, options) { if (blackListedEvents.has(type.toLowerCase())) { log(`BLOCKED addEventListener: ${type}`); return; } return originalAddEventListener.call(this, type, listener, options); }; } catch (e) { error("防切屏实现失败:", e); } log("防切屏功能已启用"); if (isInIframe) { log("检测到在iframe中,只启用防切屏功能"); return; } log("在主页面中,启用完整功能"); function createDebugWindow() { const existingWindows = document.querySelectorAll('[id^="chaoxing-debug-window"]'); existingWindows.forEach(win => win.remove()); const debugWindow = document.createElement('div'); debugWindow.id = 'chaoxing-debug-window-main'; debugWindow.innerHTML = ` <div style="background: rgba(0,0,0,0.9); color: white; padding: 15px; border-radius: 10px; font-size: 13px; max-width: 400px; box-shadow: 0 6px 20px rgba(0,0,0,0.4); border: 2px solid #4CAF50;"> <div style="font-weight: bold; margin-bottom: 10px; color: #4CAF50; text-align: center;">🎓 超星自动学习助手 v2.9</div> <div id="debug-status" style="margin-bottom: 8px; color: #FFD54F;">初始化中...</div> <div id="debug-details" style="font-size: 11px; opacity: 0.8; margin-bottom: 8px; color: #B0BEC5;">正在检测页面状态...</div> <div id="debug-video" style="font-size: 10px; opacity: 0.7; margin-bottom: 8px; color: #81C784;">视频: 检测中</div> <div id="debug-task" style="font-size: 10px; opacity: 0.7; margin-bottom: 8px; color: #81C784;">任务: 检测中</div> <div id="debug-progress" style="font-size: 10px; opacity: 0.7; margin-bottom: 10px; color: #81C784;"></div> <div style="text-align: center; font-size: 10px; margin-top: 8px; opacity: 0.7;"> <a href="https://www.hbut.edu.cn" target="_blank" style="color: #B0BEC5; text-decoration: none;">脚本来自湖北工业大学</a> </div> <div style="margin-top: 10px; text-align: center;"> <button id="toggle-auto-play" style="padding: 6px 12px; margin: 2px; border: none; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 11px;">开启</button> <button id="next-chapter" style="padding: 6px 12px; margin: 2px; border: none; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 11px;">下一章</button> <button id="try-play" style="padding: 6px 12px; margin: 2px; border: none; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 11px;">手动播放</button> </div> </div> `; debugWindow.style.cssText = ` position: fixed; bottom: 20px; right: 20px; z-index: 99999; cursor: move; user-select: none; `; // 拖拽功能 let isDragging = false; let startX, startY, startLeft, startTop; debugWindow.addEventListener('mousedown', function(e) { isDragging = true; startX = e.clientX; startY = e.clientY; const rect = debugWindow.getBoundingClientRect(); startLeft = rect.left; startTop = rect.top; e.preventDefault(); }); document.addEventListener('mousemove', function(e) { if (!isDragging) return; const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; debugWindow.style.left = (startLeft + deltaX) + 'px'; debugWindow.style.top = (startTop + deltaY) + 'px'; debugWindow.style.right = 'auto'; debugWindow.style.bottom = 'auto'; }); document.addEventListener('mouseup', function() { isDragging = false; }); return debugWindow; } class AutoPlayController { constructor() { this.isEnabled = true; this.debugWindow = null; this.statusElement = null; this.detailsElement = null; this.videoElement = null; this.taskElement = null; this.progressElement = null; this.checkInterval = null; this.retryCount = 0; this.maxRetries = 3; this.taskCompletedCount = 0; this.requiredCompletedTasks = 3; // 增加确认次数 this.deepScanCount = 0; this.maxDeepScan = 15; this.currentPlayer = null; this.videoEndedDetected = false; this.lastVideoTime = 0; this.videoStuckCount = 0; this.switchingChapter = false; } init() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.setup()); } else { this.setup(); } } setup() { this.createUI(); this.startAutoPlay(); } createUI() { this.debugWindow = createDebugWindow(); document.body.appendChild(this.debugWindow); this.statusElement = document.getElementById('debug-status'); this.detailsElement = document.getElementById('debug-details'); this.videoElement = document.getElementById('debug-video'); this.taskElement = document.getElementById('debug-task'); this.progressElement = document.getElementById('debug-progress'); document.getElementById('toggle-auto-play').addEventListener('click', () => { this.toggle(); }); document.getElementById('next-chapter').addEventListener('click', () => { this.clickNextChapter(); }); document.getElementById('try-play').addEventListener('click', () => { this.manualPlay(); }); this.updateUI(); } updateStatus(status, details = '', video = '', task = '', progress = '') { if (this.statusElement) { this.statusElement.textContent = status; } if (this.detailsElement && details) { this.detailsElement.textContent = details; } if (this.videoElement && video) { this.videoElement.textContent = '视频: ' + video; } if (this.taskElement && task) { this.taskElement.textContent = '任务: ' + task; } if (this.progressElement && progress) { this.progressElement.textContent = progress; } log(status + (details ? ' - ' + details : '')); } updateUI() { const toggleBtn = document.getElementById('toggle-auto-play'); if (toggleBtn) { toggleBtn.textContent = this.isEnabled ? '关闭' : '开启'; toggleBtn.style.backgroundColor = this.isEnabled ? '#f44336' : '#4CAF50'; toggleBtn.style.color = 'white'; } } toggle() { this.isEnabled = !this.isEnabled; this.updateUI(); if (this.isEnabled) { this.startAutoPlay(); this.updateStatus('自动播放已开启', '重新开始检测'); } else { this.stopAutoPlay(); this.updateStatus('自动播放已关闭', '停止所有检测'); } } startAutoPlay() { if (this.checkInterval) { clearInterval(this.checkInterval); } this.updateStatus('启动中...', '等待页面和播放器加载'); this.retryCount = 0; this.taskCompletedCount = 0; this.deepScanCount = 0; this.currentPlayer = null; this.videoEndedDetected = false; this.lastVideoTime = 0; this.videoStuckCount = 0; this.switchingChapter = false; setTimeout(() => { this.checkAndPlay(); // 缩短检查间隔到3秒,更频繁地监控视频状态 this.checkInterval = setInterval(() => { if (this.isEnabled && !this.switchingChapter) { this.checkAndPlay(); } }, 3000); }, 3000); } stopAutoPlay() { if (this.checkInterval) { clearInterval(this.checkInterval); this.checkInterval = null; } } // 深度扫描所有iframe查找播放器 deepScanForPlayer() { this.deepScanCount++; const allFrames = []; const iframes = document.querySelectorAll('iframe'); for (let iframe of iframes) { allFrames.push({ element: iframe, path: 'main->' + (iframe.id || iframe.className || 'unnamed'), src: iframe.src }); try { const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document; if (iframeDoc) { const nestedIframes = iframeDoc.querySelectorAll('iframe'); for (let nested of nestedIframes) { allFrames.push({ element: nested, path: 'main->' + (iframe.id || 'unnamed') + '->' + (nested.id || 'unnamed'), src: nested.src, parent: iframe }); } } } catch (e) { // 跨域iframe,跳过 } } // 在每个iframe中查找播放器 for (let frame of allFrames) { try { const doc = frame.element.contentDocument || frame.element.contentWindow?.document; if (doc) { const playerInfo = this.findPlayerInDocument(doc); if (playerInfo.found) { // 为播放器添加事件监听 this.setupVideoEventListeners(playerInfo); return { success: true, iframe: frame.element, document: doc, player: playerInfo, path: frame.path }; } } } catch (e) { // 跨域iframe,继续下一个 } } return { success: false, message: '未找到播放器' }; } // 为视频添加事件监听器 setupVideoEventListeners(player) { if (player.video && !player.video._chaoxingListenersAdded) { player.video._chaoxingListenersAdded = true; // 监听视频结束事件 player.video.addEventListener('ended', () => { this.videoEndedDetected = true; log('检测到视频播放结束'); this.updateStatus('视频播放完成', '检测到ended事件', '✓ 播放完成', '等待确认'); }); // 监听播放进度 player.video.addEventListener('timeupdate', () => { this.lastVideoTime = player.video.currentTime; }); // 监听播放状态变化 player.video.addEventListener('play', () => { this.updateStatus('视频开始播放', '检测到play事件', '▶️ 播放中'); }); player.video.addEventListener('pause', () => { this.updateStatus('视频暂停', '检测到pause事件', '⏸️ 已暂停'); }); } } findPlayerInDocument(doc) { if (!doc) return { found: false }; // 查找Video.js播放器 const videoJsContainers = doc.querySelectorAll('#video, .video-js, [class*="video-js"]'); for (let container of videoJsContainers) { const video = container.querySelector('video') || doc.querySelector('#video_html5_api'); if (video || container.classList.contains('video-js')) { return { found: true, type: 'Video.js', container: container, video: video, playButton: container.querySelector('.vjs-big-play-button') || container.querySelector('.vjs-play-control') }; } } // 查找普通video元素 const videos = doc.querySelectorAll('video'); for (let video of videos) { return { found: true, type: 'HTML5 Video', video: video, container: video.parentElement, playButton: video.parentElement.querySelector('.play-button, [class*="play"]') }; } return { found: false }; } async checkAndPlay() { try { if (this.switchingChapter) { this.updateStatus('正在切换章节', '等待页面加载完成'); return; } // 查找播放器 const scanResult = this.deepScanForPlayer(); if (!scanResult.success) { if (this.deepScanCount >= this.maxDeepScan) { this.updateStatus('扫描完成', '未找到播放器', '未检测到', '未检测到'); this.deepScanCount = 0; } else { this.updateStatus('扫描播放器', `${this.deepScanCount}/${this.maxDeepScan}`, '扫描中', '扫描中'); } return; } // 更新当前播放器 this.currentPlayer = scanResult.player; const { document: playerDoc, player, path } = scanResult; // 分析任务和视频状态 const status = this.analyzeCompleteStatus(playerDoc, player); this.updateStatus('监控播放状态', status.description, status.videoStatus, status.taskStatus, `时长: ${Math.floor(status.currentTime)}/${Math.floor(status.duration)}s (${status.progressPercent}%)`); // 检查是否需要切换章节 if (this.shouldSwitchChapter(status)) { this.taskCompletedCount++; this.updateStatus('任务完成确认', `确认次数: ${this.taskCompletedCount}/${this.requiredCompletedTasks}`, '完成确认中', `${this.taskCompletedCount}/${this.requiredCompletedTasks}`); if (this.taskCompletedCount >= this.requiredCompletedTasks) { this.updateStatus('准备切换章节', '所有条件已满足'); this.taskCompletedCount = 0; this.switchingChapter = true; setTimeout(() => { this.findAndClickNextChapter(); }, 2000); return; } } else { this.taskCompletedCount = 0; this.videoEndedDetected = false; // 重置视频结束标记 } // 尝试播放视频 if (!status.isPlaying && status.canPlay && !status.isCompleted) { const playResult = this.tryPlayVideo(player); if (playResult.success) { this.updateStatus('播放操作完成', playResult.message, '启动播放'); } else { this.updateStatus('播放尝试', playResult.message, '播放失败'); } return; } // 检测视频卡住 if (status.isPlaying) { if (Math.abs(status.currentTime - this.lastVideoTime) < 0.1) { this.videoStuckCount++; if (this.videoStuckCount > 5) { // 连续5次检测到时间没变化 this.updateStatus('视频可能卡住', '尝试重新播放', '疑似卡住'); this.tryPlayVideo(player); this.videoStuckCount = 0; } } else { this.videoStuckCount = 0; } this.lastVideoTime = status.currentTime; } } catch (error) { this.updateStatus('系统错误', error.message, '异常', '错误'); error('检查过程出错:', error); } } // 判断是否应该切换章节 shouldSwitchChapter(status) { // 条件1: 视频播放完成 (通过ended事件检测) const videoEnded = this.videoEndedDetected; // 条件2: 播放进度接近100% (98%以上) const progressCompleted = status.progressPercent >= 98; // 条件3: 任务点都已完成 const tasksCompleted = status.totalTasks > 0 ? (status.completedTasks === status.totalTasks) : true; // 条件4: 视频当前不在播放 const notPlaying = !status.isPlaying; log(`切换条件检查: ended=${videoEnded}, progress=${progressCompleted}(${status.progressPercent}%), tasks=${tasksCompleted}(${status.completedTasks}/${status.totalTasks}), notPlaying=${notPlaying}`); // 满足以下任一组合条件就切换: // 1. 视频已结束 且 任务完成 // 2. 进度98%以上 且 任务完成 且 不在播放 return (videoEnded && tasksCompleted) || (progressCompleted && tasksCompleted && notPlaying); } analyzeCompleteStatus(doc, player) { let isPlaying = false; let canPlay = false; let currentTime = 0; let duration = 0; let playerState = '检测中'; let videoStatus = '检测中'; // 分析视频状态 if (player.video) { currentTime = player.video.currentTime || 0; duration = player.video.duration || 0; isPlaying = !player.video.paused && currentTime > 0; canPlay = player.video.readyState >= 3; if (player.video.ended) { videoStatus = '已播放完成'; this.videoEndedDetected = true; } else if (isPlaying) { videoStatus = '播放中'; } else if (canPlay) { videoStatus = '可播放'; } else { videoStatus = '加载中'; } } // 分析Video.js状态 if (player.container && player.container.classList.contains('video-js')) { const isPaused = player.container.classList.contains('vjs-paused'); const hasStarted = player.container.classList.contains('vjs-has-started'); if (!isPaused && hasStarted) { isPlaying = true; playerState = 'VJS播放中'; } else if (isPaused && hasStarted) { playerState = 'VJS已暂停'; canPlay = true; } else { playerState = 'VJS未开始'; canPlay = true; } } // 分析任务点 const allJobIcons = doc.querySelectorAll('.ans-job-icon'); const finishedJobIcons = doc.querySelectorAll('.ans-job-icon.ans-job-icon-clear[aria-label*="任务点已完成"]'); const progressPercent = duration > 0 ? Math.round((currentTime / duration) * 100) : 0; // 任务状态 let taskStatus = '检测中'; if (allJobIcons.length > 0) { if (finishedJobIcons.length === allJobIcons.length) { taskStatus = '全部完成'; } else { taskStatus = `${finishedJobIcons.length}/${allJobIcons.length}完成`; } } else { taskStatus = '无任务点'; } const isCompleted = this.shouldSwitchChapter({ progressPercent, totalTasks: allJobIcons.length, completedTasks: finishedJobIcons.length, isPlaying }); return { isCompleted, isPlaying, canPlay, playerState, videoStatus, taskStatus, totalTasks: allJobIcons.length, completedTasks: finishedJobIcons.length, currentTime, duration, progressPercent, description: `${playerState || videoStatus} | ${taskStatus}` }; } tryPlayVideo(player) { try { let clicked = false; // 方法1: 点击播放按钮 if (player.playButton && this.isElementVisible(player.playButton)) { this.clickElement(player.playButton); clicked = true; } // 方法2: 直接调用video.play() if (player.video && typeof player.video.play === 'function') { const playPromise = player.video.play(); if (playPromise && typeof playPromise.then === 'function') { playPromise.catch(e => { warn('视频播放被阻止:', e.message); }); } clicked = true; } return { success: clicked, message: clicked ? `播放操作完成 (${player.type})` : '未找到可点击的播放元素' }; } catch (error) { return { success: false, message: '播放操作异常: ' + error.message }; } } clickElement(element) { try { if (element.click) { element.click(); } element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: element.ownerDocument.defaultView })); if (element.tabIndex >= 0) { element.focus(); element.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', code: 'Space', keyCode: 32, bubbles: true })); } } catch (error) { warn('点击元素失败:', error); } } findAndClickNextChapter() { try { const courseTree = document.getElementById('coursetree'); if (!courseTree) { this.updateStatus('未找到课程目录', '无法进行章节切换'); this.switchingChapter = false; return; } const chapterLinks = courseTree.querySelectorAll('.posCatalog_select'); const currentActiveChapter = courseTree.querySelector('.posCatalog_select.posCatalog_active'); if (!currentActiveChapter) { this.updateStatus('未找到当前章节', '无法确定切换目标'); this.switchingChapter = false; return; } let currentIndex = Array.from(chapterLinks).indexOf(currentActiveChapter); if (currentIndex === -1) { this.updateStatus('无法定位当前章节', '切换逻辑错误'); this.switchingChapter = false; return; } // 寻找下一个未完成的章节 for (let i = currentIndex + 1; i < chapterLinks.length; i++) { const chapter = chapterLinks[i]; const isCompleted = this.isChapterCompleted(chapter); if (!isCompleted) { this.updateStatus('找到下一个未完成章节', `切换到第 ${i + 1} 个章节`); const chapterLink = chapter.querySelector('a, .posCatalog_name'); if (chapterLink) { chapterLink.click(); this.updateStatus('已切换章节', '等待新页面加载'); // 切换后重置所有状态 setTimeout(() => { this.retryCount = 0; this.taskCompletedCount = 0; this.deepScanCount = 0; this.currentPlayer = null; this.videoEndedDetected = false; this.lastVideoTime = 0; this.videoStuckCount = 0; this.switchingChapter = false; this.updateStatus('章节切换完成', '重新开始检测'); }, 5000); // 等待5秒让新页面完全加载 return; } } } this.updateStatus('🎉 课程已完成!', '恭喜完成所有章节学习!'); this.switchingChapter = false; } catch (error) { this.updateStatus('章节切换失败', error.message); this.switchingChapter = false; error('章节切换失败:', error); } } isChapterCompleted(chapterElement) { const completedIcon = chapterElement.querySelector('.icon_Completed'); if (!completedIcon) { return false; } const hoverTips = chapterElement.querySelector('.prevHoverTips'); return hoverTips && hoverTips.textContent.includes('已完成'); } manualPlay() { this.updateStatus('手动播放模式', '立即执行检测'); this.deepScanCount = 0; this.switchingChapter = false; this.checkAndPlay(); } clickNextChapter() { this.updateStatus('手动切换章节', '正在寻找下一章节'); this.switchingChapter = true; this.findAndClickNextChapter(); } isElementVisible(element) { if (!element) return false; try { const rect = element.getBoundingClientRect(); const style = element.ownerDocument.defaultView.getComputedStyle(element); return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0' && rect.width > 0 && rect.height > 0; } catch (e) { return false; } } } const autoPlayController = new AutoPlayController(); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { autoPlayController.init(); }, 2000); }); } else { setTimeout(() => { autoPlayController.init(); }, 2000); } log("超星防切屏和自动播放脚本 v2.8 已启动 - 智能切换版"); })();