// ==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);
})();