// ==UserScript== // @name 陕西继续教育-视频辅助 // @version 5.1 // @description 隐身倍速、防暂停、自动静音、自动下一节、悬浮窗可拖拽 // @author FengJB // @match *://mooc1.xidian.edu.cn/* // @noframes // @license MIT // @grant none // ==/UserScript== (function() { 'use strict'; // ================= 全局配置 ================= let currentRate = parseFloat(localStorage.getItem('xd_rate')) || 1.25; let isMuted = localStorage.getItem('xd_muted') === 'true'; let autoNext = localStorage.getItem('xd_auto_next') === 'true'; let targetVideo = null; // ========================================== // 拖拽功能函数 function makeDraggable(elmnt, handle) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; if (handle) { handle.onmousedown = dragMouseDown; } else { elmnt.onmousedown = dragMouseDown; } function dragMouseDown(e) { e = e || window.event; e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; // 计算新位置 let newTop = elmnt.offsetTop - pos2; let newLeft = elmnt.offsetLeft - pos1; // 防止拖出屏幕外 if (newTop < 0) newTop = 0; if (newLeft < 0) newLeft = 0; elmnt.style.top = newTop + "px"; elmnt.style.left = newLeft + "px"; elmnt.style.right = 'auto'; // 确保 right 属性不干扰 } function closeDragElement() { document.onmouseup = null; document.onmousemove = null; // 保存位置到 localStorage localStorage.setItem('xd_panel_left', elmnt.offsetLeft); localStorage.setItem('xd_panel_top', elmnt.offsetTop); } } // 1. 创建悬浮控制面板 UI function createUI() { if (document.getElementById('xd-helper-panel')) return; const panel = document.createElement('div'); panel.id = 'xd-helper-panel'; panel.innerHTML = `
📺 网课助手 未连接视频
* 拖拽上方标题可移动面板
`; Object.assign(panel.style, { position: 'fixed', top: '80px', right: '20px', width: '220px', backgroundColor: 'rgba(255, 255, 255, 0.95)', border: '1px solid #1890ff', borderRadius: '8px', padding: '12px', zIndex: '999999', boxShadow: '0 4px 12px rgba(0,0,0,0.15)', fontFamily: 'sans-serif', fontSize: '14px', color: '#333' }); // 恢复上次拖拽的位置 const savedLeft = localStorage.getItem('xd_panel_left'); const savedTop = localStorage.getItem('xd_panel_top'); if (savedLeft !== null && savedTop !== null) { panel.style.left = savedLeft + 'px'; panel.style.top = savedTop + 'px'; panel.style.right = 'auto'; } document.body.appendChild(panel); // 初始化拖拽功能,绑定在头部 const header = document.getElementById('xd-panel-header'); header.style.cursor = 'move'; makeDraggable(panel, header); const rateSelect = document.getElementById('xd-rate-select'); const muteCheckbox = document.getElementById('xd-mute-checkbox'); const autoNextCheckbox = document.getElementById('xd-autonext-checkbox'); const nextBtn = document.getElementById('xd-next-btn'); rateSelect.value = currentRate; muteCheckbox.checked = isMuted; autoNextCheckbox.checked = autoNext; rateSelect.addEventListener('change', (e) => { currentRate = parseFloat(e.target.value); localStorage.setItem('xd_rate', currentRate); applySettings(); }); muteCheckbox.addEventListener('change', (e) => { isMuted = e.target.checked; localStorage.setItem('xd_muted', isMuted); applySettings(); }); autoNextCheckbox.addEventListener('change', (e) => { autoNext = e.target.checked; localStorage.setItem('xd_auto_next', autoNext); }); nextBtn.addEventListener('click', () => { clickNextButton(); }); } // 2. 寻找并点击“下一节”按钮 function clickNextButton() { let nextBtn = document.querySelector('.nextChapter') || document.querySelector('#prevNextFocusNext') || document.querySelector('.prev_next .next') || document.querySelector('a[onclick*="next"]'); if (!nextBtn) { const links = document.querySelectorAll('a, div, button'); for (let el of links) { if (el.innerText && el.innerText.trim() === '下一节') { nextBtn = el; break; } } } if (nextBtn) { console.log('【网课助手】找到下一节按钮,正在点击...', nextBtn); nextBtn.click(); } else { console.warn('【网课助手】未找到下一节按钮,请检查页面结构。'); } } // 3. 穿透 iframe 找视频 function findVideo() { if (targetVideo && !targetVideo.paused) return targetVideo; let video = document.querySelector('video'); if (video) return video; const iframes = document.querySelectorAll('iframe'); for (let iframe of iframes) { try { const doc = iframe.contentDocument || iframe.contentWindow.document; video = doc.querySelector('video'); if (video) return video; const innerIframes = doc.querySelectorAll('iframe'); for (let inner of innerIframes) { const innerDoc = inner.contentDocument || inner.contentWindow.document; video = innerDoc.querySelector('video'); if (video) return video; } } catch (e) {} } return null; } // 4. 倍速隐身劫持 function hijackVideo(video) { if (!video || video.dataset.xdHijacked) return; const win = video.ownerDocument.defaultView; const nativeDescriptor = Object.getOwnPropertyDescriptor(win.HTMLMediaElement.prototype, 'playbackRate'); if (!nativeDescriptor) return; let internalRate = currentRate; try { Object.defineProperty(video, 'playbackRate', { configurable: true, get: function() { return 1.0; }, // 永远告诉平台是1.0 set: function(val) { if (val === 1.0) { nativeDescriptor.set.call(video, internalRate); } else { internalRate = val; nativeDescriptor.set.call(video, val); } } }); video.addEventListener('ratechange', (e) => e.stopImmediatePropagation(), true); video.dataset.xdHijacked = 'true'; nativeDescriptor.set.call(video, currentRate); } catch (e) {} } function applySettings() { if (!targetVideo) return; targetVideo.muted = isMuted; targetVideo.volume = isMuted ? 0 : 1; try { targetVideo.playbackRate = currentRate; } catch(e) {} } // 5. 绑定视频事件 function bindVideoEvents(video) { if (video.dataset.xdBound) return; video.dataset.xdBound = 'true'; const statusEl = document.getElementById('xd-status'); if (statusEl) { statusEl.innerText = '已连接(隐身) ✅'; statusEl.style.color = 'green'; } hijackVideo(video); applySettings(); // 核心:监听视频播放结束事件 video.addEventListener('ended', () => { console.log('【网课助手】当前视频播放结束!'); if (autoNext) { if (statusEl) { statusEl.innerText = '3秒后自动跳转...'; statusEl.style.color = 'blue'; } setTimeout(() => { clickNextButton(); }, 3000); } else { if (statusEl) { statusEl.innerText = '等待手动切下一节'; statusEl.style.color = 'orange'; } } }); // 防切屏暂停机制 video.addEventListener('pause', () => { if (!video.ended) { setTimeout(() => { video.play().catch(e => {}); }, 1000); } }); video.play().catch(() => { console.log('【网课助手】自动播放被阻止,请手动点击播放'); }); } // 6. 主循环检测 function loop() { createUI(); const video = findVideo(); if (video && video !== targetVideo) { targetVideo = video; bindVideoEvents(targetVideo); } else if (targetVideo) { if (targetVideo.muted !== isMuted) targetVideo.muted = isMuted; } setTimeout(loop, 2000); } // 7. 防失焦暂停 const blockEvent = (e) => e.stopImmediatePropagation(); ['blur', 'visibilitychange'].forEach(ev => window.addEventListener(ev, blockEvent, true)); window.addEventListener('load', loop); })();