// ==UserScript== // @name cme.yiigle.com 在线教育平台增强脚本 // @namespace http://tampermonkey.net/ // @version 2.3.4 // @description 可实现自动播放并跳转下一集,解除切屏暂停,支持0.25-2.0倍速调节 // @author DF11G2001 // @match https://cme.yiigle.com/teaching/course?* // @grant none // @run-at document-start // @license GPL-3.0 // ==/UserScript== (function() { 'use strict'; // ======================== 1. 早期全局拦截 ======================== const clearHandlers = () => { document.onvisibilitychange = null; document.onwebkitvisibilitychange = null; window.onblur = null; window.onpagehide = null; window.onbeforeunload = null; }; clearHandlers(); const noop = function() {}; window.detectDevtools = noop; window.pausePlayerOnHidden = noop; const origAddEventListener = document.addEventListener; document.addEventListener = function(type, listener, options) { const blockedTypes = ['visibilitychange', 'webkitvisibilitychange', 'mozvisibilitychange', 'msvisibilitychange']; if (blockedTypes.includes(type)) { const listenerStr = listener.toString(); if (listenerStr.includes('pausePlayerOnHidden') || listenerStr.includes('handleVisiable')) { console.log('[拦截] 阻止 ' + type + ' 监听'); return; } } if (type === 'blur' || type === 'pagehide') { const listenerStr = listener.toString(); if (listenerStr.includes('pausePlayerOnHidden')) { console.log('[拦截] 阻止 window.' + type + ' 监听'); return; } } return origAddEventListener.call(this, type, listener, options); }; const origSetInterval = window.setInterval; window.setInterval = function(cb, delay, ...args) { const cbStr = cb.toString(); const blockedKeywords = [ 'checkPlaybackSpeed', 'j2s_getPlaybackRate', 'detectDevtools', 'speedCheckTimer', 'checkProgressAndResume' ]; if (blockedKeywords.some(k => cbStr.includes(k))) { console.log('[拦截] 阻止检测定时器'); return 0; } return origSetInterval.call(this, cb, delay, ...args); }; const origSetTimeout = window.setTimeout; window.setTimeout = function(cb, delay, ...args) { const cbStr = cb.toString(); if (cbStr.includes('detectDevtools')) { console.log('[拦截] 阻止 setTimeout 检测'); return 0; } return origSetTimeout.call(this, cb, delay, ...args); }; const origToString = Function.prototype.toString; Function.prototype.toString = function() { if (this === console.log) { return 'function log() { [native code] }'; } return origToString.call(this); }; // ======================== 2. 视频恢复与倍速应用 ======================== let userPaused = false; let video = null; function getVideo() { return document.querySelector('video'); } function applyStoredSpeed(v) { const keep = localStorage.getItem('auto_speed_keep'); if (keep !== 'true') return; const speed = localStorage.getItem('auto_speed_value'); if (!speed) return; let val = parseFloat(speed); if (isNaN(val) || val < 0.25 || val > 2.0) return; const set = () => { if (!v) return; if (Math.abs(v.playbackRate - val) > 0.01) { v.playbackRate = val; console.log('[倍速] 应用存储速度: ' + val + 'x'); } // 播放器内部对象 if (window.player && typeof window.player.setPlaybackRate === 'function') { window.player.setPlaybackRate(val); } // 更新UI const input = document.querySelector('#speed-control input'); if (input) input.value = val; }; if (v.readyState >= 2) { set(); } else { v.addEventListener('loadedmetadata', set, { once: true }); v.addEventListener('canplay', set, { once: true }); } // 播放时重新应用(防重置) v.addEventListener('play', function onPlay() { setTimeout(() => { if (Math.abs(v.playbackRate - val) > 0.01) { v.playbackRate = val; console.log('[倍速] 播放时重新应用: ' + val + 'x'); } }, 100); }); } function setupVideoListeners(v) { if (!v) return; if (v._listenersSet) return; v._listenersSet = true; v.addEventListener('play', () => { userPaused = false; }); v.addEventListener('pause', () => { setTimeout(() => { if (v && !v.ended && v.paused && !userPaused) { console.log('[恢复] 检测到非用户暂停,恢复播放'); v.play().catch(() => {}); } }, 300); }); } function startRecoveryWatch() { setInterval(() => { const v = getVideo(); if (!v) return; // 恢复播放 if (!v.ended && v.paused && !userPaused) { v.play().catch(() => {}); } // 倍速恢复 const keep = localStorage.getItem('auto_speed_keep'); if (keep === 'true') { const speed = localStorage.getItem('auto_speed_value'); if (speed) { let val = parseFloat(speed); if (!isNaN(val) && val >= 0.25 && val <= 2.0) { if (Math.abs(v.playbackRate - val) > 0.01) { v.playbackRate = val; console.log('[倍速] 定时恢复倍速: ' + val + 'x'); } } } } }, 3000); } // ======================== 3. 自动播放与下一集跳转 ======================== let checkInterval = null; function log(msg) { console.log('[AutoNext] ' + msg); } function getNextVideoItem() { const selectors = ['.item', '.chapter', '.lesson', '.video-item', 'li']; let items = []; for (const sel of selectors) { const found = document.querySelectorAll(sel); if (found.length > 0) { items = found; break; } } if (items.length === 0) return null; for (let i = 0; i < items.length; i++) { const el = items[i]; const isActive = el.classList.contains('active') || el.querySelector('.active'); if (isActive) { const next = items[i + 1]; if (next) { const clickable = next.querySelector('a, .title, .name'); return clickable || next; } return null; } } return null; } function goToNext() { const next = getNextVideoItem(); if (next) { log('跳转下一集'); next.click(); } else { log('没有下一集'); } } function bindEndedEvent(v) { if (!v) return; if (v._autoNextBound) return; v._autoNextBound = true; v.addEventListener('ended', () => { log('视频播放结束'); goToNext(); }); } function playVideo(v) { if (!v) return Promise.reject('No video'); if (!v.paused) return Promise.resolve(); return v.play(); } function showPlayHint() { if (document.getElementById('auto-play-hint')) return; const hint = document.createElement('div'); hint.id = 'auto-play-hint'; hint.style.cssText = ` position: fixed; bottom: 120px; right: 22px; background: #409EFF; color: #fff; padding: 12px 20px; border-radius: 8px; font-size: 16px; cursor: pointer; z-index: 9999; box-shadow: 0 2px 12px rgba(0,0,0,0.3); font-family: sans-serif; `; hint.textContent = '▶ 点击开始自动播放'; hint.addEventListener('click', (e) => { e.stopPropagation(); const v = getVideo(); if (v) { playVideo(v).then(() => { log('用户点击按钮,播放成功'); hint.style.display = 'none'; }).catch(err => log('播放失败:' + err)); } }); document.body.appendChild(hint); const oneClick = () => { const v = getVideo(); if (v && v.paused) { playVideo(v).then(() => { log('用户点击任意处,播放成功'); if (hint.parentNode) hint.style.display = 'none'; }).catch(() => {}); } document.removeEventListener('click', oneClick); document.removeEventListener('touchstart', oneClick); }; document.addEventListener('click', oneClick); document.addEventListener('touchstart', oneClick); log('已显示播放提示,等待用户交互'); } function initAutoPlay() { const v = getVideo(); if (!v) { log('视频元素未找到,继续等待...'); return false; } video = v; bindEndedEvent(v); setupVideoListeners(v); // 应用存储倍速 applyStoredSpeed(v); if (!v.paused) { log('视频已在播放'); return true; } playVideo(v).then(() => { log('自动播放成功'); }).catch(err => { if (err.name === 'NotAllowedError') { log('自动播放被浏览器阻止,等待用户交互'); showPlayHint(); } else { log('播放异常:' + err.message); } }); return true; } function startAutoPlay() { if (checkInterval) return; let attempts = 0; checkInterval = setInterval(() => { attempts++; const v = getVideo(); if (v) { clearInterval(checkInterval); checkInterval = null; initAutoPlay(); } else if (attempts > 30) { clearInterval(checkInterval); checkInterval = null; log('超时未找到视频元素'); } }, 1000); } // ======================== 4. 倍速控制 UI ======================== function createSpeedUI() { const container = document.createElement('div'); container.id = 'speed-control'; container.style.cssText = ` position: fixed; bottom: 20px; right: 20px; background: rgba(0,0,0,0.7); color: #fff; padding: 10px 15px; border-radius: 8px; z-index: 9999; font-family: sans-serif; display: flex; flex-direction: column; gap: 6px; user-select: none; `; const row1 = document.createElement('div'); row1.style.display = 'flex'; row1.style.alignItems = 'center'; row1.style.gap = '8px'; const label = document.createElement('span'); label.textContent = '倍速:'; const input = document.createElement('input'); input.type = 'number'; input.step = '0.05'; input.min = '0.25'; input.max = '2.0'; input.value = '1.0'; input.style.width = '60px'; input.style.padding = '4px'; const btn = document.createElement('button'); btn.textContent = '设置'; btn.style.padding = '4px 10px'; btn.style.cursor = 'pointer'; row1.appendChild(label); row1.appendChild(input); row1.appendChild(btn); container.appendChild(row1); const row2 = document.createElement('div'); row2.style.display = 'flex'; row2.style.alignItems = 'center'; row2.style.gap = '6px'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = 'keep-speed'; const keepLabel = document.createElement('label'); keepLabel.htmlFor = 'keep-speed'; keepLabel.textContent = '保持倍速'; keepLabel.style.cursor = 'pointer'; row2.appendChild(checkbox); row2.appendChild(keepLabel); container.appendChild(row2); document.body.appendChild(container); // 加载存储状态 const storedKeep = localStorage.getItem('auto_speed_keep'); if (storedKeep === 'true') { checkbox.checked = true; } const storedSpeed = localStorage.getItem('auto_speed_value'); if (storedSpeed) { const val = parseFloat(storedSpeed); if (!isNaN(val) && val >= 0.25 && val <= 2.0) { input.value = val; } } const setSpeed = () => { let val = parseFloat(input.value); if (isNaN(val)) val = 1.0; val = Math.min(2.0, Math.max(0.25, val)); input.value = val; const v = getVideo(); if (v) { v.playbackRate = val; if (window.player && typeof window.player.setPlaybackRate === 'function') { window.player.setPlaybackRate(val); } console.log('[倍速] 设置为 ' + val + 'x'); } if (checkbox.checked) { localStorage.setItem('auto_speed_value', String(val)); localStorage.setItem('auto_speed_keep', 'true'); } }; btn.addEventListener('click', setSpeed); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') setSpeed(); }); checkbox.addEventListener('change', () => { if (checkbox.checked) { const curVal = parseFloat(input.value); if (!isNaN(curVal) && curVal >= 0.25 && curVal <= 2.0) { localStorage.setItem('auto_speed_value', String(curVal)); } localStorage.setItem('auto_speed_keep', 'true'); } else { localStorage.setItem('auto_speed_keep', 'false'); } }); if (checkbox.checked) { const curVal = parseFloat(input.value); if (!isNaN(curVal)) { localStorage.setItem('auto_speed_value', String(curVal)); } } } // ======================== 5. 启动 ======================== window.addEventListener('load', () => { createSpeedUI(); startAutoPlay(); startRecoveryWatch(); }); if (document.readyState === 'complete') { createSpeedUI(); startAutoPlay(); startRecoveryWatch(); } })();