// ==UserScript== // @name TC视频助手 // @namespace chd-course-auto-next // @version 1.0.0 // @description 自动播放视频、自动下一节,并提供悬浮状态面板 // @match https://course-online.chd.edu.cn/* // @run-at document-idle // @license MIT // ==/UserScript== (function () { 'use strict'; // ========================================== // 全局状态 // ========================================== let watchedVideo = null; let changing = false; let completedCount = 0; let autoPlayEnabled = true; let autoNextEnabled = true; let currentTitle = '等待视频...'; let currentStatus = '初始化'; // ========================================== // 创建状态面板 // ========================================== function createPanel() { if (document.getElementById('chd-auto-panel')) { return; } const panel = document.createElement('div'); panel.id = 'chd-auto-panel'; panel.innerHTML = `
🎓 畅课助手
状态 初始化
视频 等待...
播放 等待
进度 00:00 / 00:00
已完成 0 节
等待脚本启动...
`; document.body.appendChild(panel); // ========================================== // CSS // ========================================== const style = document.createElement('style'); style.textContent = ` #chd-auto-panel { position: fixed; top: 80px; right: 20px; width: 280px; z-index: 999999999; background: rgba(25, 25, 28, 0.96); color: #ffffff; border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.35); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif; font-size: 13px; overflow: hidden; user-select: none; } #chd-panel-header { height: 42px; display: flex; align-items: center; justify-content: space-between; padding: 0 12px; background: rgba(55,55,60,0.95); font-size: 14px; font-weight: 600; } #chd-collapse { width: 26px; height: 26px; border: none; border-radius: 6px; background: rgba(255,255,255,0.12); color: white; cursor: pointer; font-size: 18px; line-height: 20px; } #chd-collapse:hover { background: rgba(255,255,255,0.22); } #chd-panel-body { padding: 12px; } .chd-status-row { display: flex; justify-content: space-between; margin-bottom: 9px; line-height: 18px; } .chd-status-row span:first-child { color: #aaaaaa; } .chd-status-row span:last-child { max-width: 175px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #ffffff; } .chd-divider { height: 1px; background: rgba(255,255,255,0.10); margin: 10px 0; } .chd-switch-row { display: flex; align-items: center; justify-content: space-between; margin: 9px 0; color: #dddddd; cursor: pointer; } .chd-switch-row input { width: 16px; height: 16px; cursor: pointer; } #chd-log { min-height: 34px; color: #8fc7ff; font-size: 12px; line-height: 17px; word-break: break-all; } #chd-status { color: #63e6be; } `; document.head.appendChild(style); // ========================================== // 折叠 // ========================================== document .getElementById('chd-collapse') .addEventListener('click', function () { const body = document.getElementById('chd-panel-body'); if (body.style.display === 'none') { body.style.display = 'block'; this.textContent = '−'; } else { body.style.display = 'none'; this.textContent = '+'; } }); // ========================================== // 开关 // ========================================== document .getElementById('chd-auto-play') .addEventListener('change', function () { autoPlayEnabled = this.checked; addLog( autoPlayEnabled ? '自动播放:开启' : '自动播放:关闭' ); }); document .getElementById('chd-auto-next') .addEventListener('change', function () { autoNextEnabled = this.checked; addLog( autoNextEnabled ? '自动下一节:开启' : '自动下一节:关闭' ); }); } // ========================================== // 更新状态 // ========================================== function setStatus(text) { currentStatus = text; const element = document.getElementById('chd-status'); if (element) { element.textContent = text; } } // ========================================== // 日志 // ========================================== function addLog(text) { console.log('[畅课助手]', text); const element = document.getElementById('chd-log'); if (element) { element.textContent = text; } } // ========================================== // 查找视频 // ========================================== function getVideo() { return document.querySelector('video'); } // ========================================== // 查找下一节 // ========================================== function getNextButton() { const list = document.querySelectorAll('button, a'); for (const element of list) { if ( element.textContent.trim() === '下一个' ) { return element; } } return null; } // ========================================== // 获取课程名称 // ========================================== function getTitle() { const title = document.querySelector('.activity-title'); if (title) { return title.textContent.trim(); } return '当前视频'; } // ========================================== // 自动播放 // ========================================== function autoPlay(video) { if (!autoPlayEnabled) { return; } if (!video.paused) { setStatus('播放中'); return; } setStatus('尝试播放'); addLog('正在尝试自动播放...'); const promise = video.play(); if (promise !== undefined) { promise .then(function () { setStatus('播放中'); addLog('✓ 自动播放成功'); }) .catch(function (error) { console.warn(error); setStatus('需要播放'); addLog( '浏览器阻止自动播放' ); clickPlayButton(); }); } } // ========================================== // Video.js 播放按钮 // ========================================== function clickPlayButton() { const selectors = [ '.vjs-big-play-button', '.vjs-play-control', 'button.vjs-big-play-button', 'button.vjs-play-control' ]; for (const selector of selectors) { const button = document.querySelector(selector); if (button) { addLog( '找到播放器按钮,尝试点击' ); button.click(); return; } } addLog( '未找到播放器播放按钮' ); } // ========================================== // 视频结束 // ========================================== function videoEnded() { completedCount++; updateCompleted(); setStatus('视频完成'); addLog( '✓ 视频播放完成' ); if (!autoNextEnabled) { addLog( '自动下一节已关闭' ); return; } if (changing) { return; } const next = getNextButton(); if (!next) { setStatus('等待下一节'); addLog( '未找到“下一个”按钮' ); return; } changing = true; setStatus('切换中'); addLog( '准备进入下一节...' ); setTimeout(function () { next.click(); addLog( '→ 已点击“下一个”' ); setTimeout(function () { changing = false; checkVideo(); }, 2000); }, 1000); } // ========================================== // 更新完成数量 // ========================================== function updateCompleted() { const element = document.getElementById( 'chd-completed' ); if (element) { element.textContent = completedCount + ' 节'; } } // ========================================== // 更新播放进度 // ========================================== function updateProgress(video) { if (!video) { return; } const current = video.currentTime || 0; const duration = video.duration || 0; function format(time) { if (!isFinite(time)) { return '00:00'; } const minutes = Math.floor(time / 60); const seconds = Math.floor(time % 60); return String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0'); } const element = document.getElementById( 'chd-progress' ); if (element) { element.textContent = format(current) + ' / ' + format(duration); } const playStatus = document.getElementById( 'chd-play-status' ); if (playStatus) { if (video.ended) { playStatus.textContent = '已完成'; } else if (video.paused) { playStatus.textContent = '已暂停'; } else { playStatus.textContent = '播放中'; } } } // ========================================== // 检查视频 // ========================================== function checkVideo() { const video = getVideo(); if (!video) { setStatus('等待视频'); return; } if (video === watchedVideo) { updateProgress(video); return; } watchedVideo = video; currentTitle = getTitle(); const titleElement = document.getElementById( 'chd-video-title' ); if (titleElement) { titleElement.textContent = currentTitle; } setStatus('检测到新视频'); addLog( '✓ 检测到新视频' ); video.addEventListener( 'ended', videoEnded ); video.addEventListener( 'play', function () { setStatus('播放中'); addLog( '▶ 视频开始播放' ); } ); video.addEventListener( 'pause', function () { if (!video.ended) { setStatus('已暂停'); addLog( '⏸ 视频暂停' ); } } ); setTimeout(function () { autoPlay(video); }, 500); } // ========================================== // 创建面板 // ========================================== createPanel(); // ========================================== // 定时检查 // ========================================== setInterval(function () { if (!changing) { checkVideo(); } }, 1000); // ========================================== // 初始化 // ========================================== checkVideo(); })();