v2.0 16x 倍速 自动存盘 一键加速智慧树视频播放,进度正确记录
// ==UserScript==
// @name 智慧树视频加速
// @namespace zhihuishu-speedup
// @version 2.0
// @description 自动加速智慧树视频播放并正确记录进度
// @match https://studyvideoh5.zhihuishu.com/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
const SPEED = 16; // 实际倍速
const SAVE_INTERVAL = 20; // 秒,存盘间隔
const LOG = true; // 控制台日志
const log = (...args) => LOG && console.log('[加速]', ...args);
// ── 工具函数 ──
const realPBR = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate');
function findVM() {
const search = (node, depth) => {
if (!node || depth > 30) return null;
if (node.totalStudyTime !== undefined) return node;
if (node.$data && 'totalStudyTime' in node.$data) return node;
if (node.$children) {
for (const c of node.$children) {
const r = search(c, depth + 1);
if (r) return r;
}
}
return null;
};
const seen = new Set();
const els = document.querySelectorAll('div, section, main');
for (const el of els) {
const v = el.__vue__;
if (v && !seen.has(v)) {
seen.add(v);
const found = search(v, 0);
if (found) return found;
}
}
for (const el of els) {
const v = el.__vue__;
if (v && v.$root && !seen.has(v.$root)) {
seen.add(v.$root);
const found = search(v.$root, 0);
if (found) return found;
}
}
return null;
}
// ── 主逻辑 ──
let active = false;
let timers = [];
let currentLessonId = null;
function clearAllTimers() {
timers.forEach(t => clearInterval(t));
timers = [];
}
function applySpeedup() {
const video = document.querySelector('#container video');
if (!video) return;
const vm = findVM();
if (!vm) { log('未找到 Vue 实例,1s 后重试'); setTimeout(applySpeedup, 1000); return; }
if (active && vm.lessonId === currentLessonId) return;
clearAllTimers();
active = true;
currentLessonId = vm.lessonId;
log('已定位 Vue 实例', { lessonId: vm.lessonId, totalStudyTime: vm.totalStudyTime });
// ── 1. 劫持 playbackRate getter ──
Object.defineProperty(video, 'playbackRate', {
get() { return 1.0; },
set(v) { realPBR.set.call(this, v); },
configurable: true,
enumerable: true,
});
realPBR.set.call(video, SPEED);
log('播放速率 =', SPEED, 'x');
// ── 2. 记住上次 watchPointPost 最后记录的位置 ──
let lastPos = 0;
if (vm.watchPointPost && vm.watchPointPost.length > 0) {
const parts = vm.watchPointPost.split(',');
lastPos = parseInt(parts[parts.length - 1]) || 0;
}
// ── 3. 补偿 totalStudyTime + 完整填充 watchPointPost ──
timers.push(setInterval(() => {
if (video.paused || video.ended) return;
const pageRate = vm.playRate || 1;
const extra = 5 * (SPEED - pageRate);
vm.totalStudyTime += extra;
if (vm.totalTimeFinish !== undefined) vm.totalTimeFinish += extra;
vm.playTimes += extra;
const currentPos = parseInt(vm.totalStudyTime / 5) + 2;
if (currentPos > lastPos) {
const entries = [];
for (let p = lastPos + 1; p <= currentPos; p++) {
entries.push(p);
}
if (entries.length > 0) {
const prefix = (vm.watchPointPost == null || vm.watchPointPost === '') ? '' : vm.watchPointPost + ',';
vm.watchPointPost = prefix + entries.join(',');
lastPos = currentPos;
}
}
}, 1990));
// ── 4. 定期存盘 ──
timers.push(setInterval(() => {
if (typeof vm.svcpEveryOnceInAWhile !== 'function') return;
if (vm.isSaving) return;
vm.playTimes = Math.max(vm.playTimes || 0, 10);
try {
vm.svcpEveryOnceInAWhile();
log('存盘触发');
setTimeout(() => {
if (vm.watchPointPost === '' || vm.watchPointPost == null) {
lastPos = parseInt(vm.totalStudyTime / 5) + 2;
}
}, 3000);
} catch (e) { log('存盘出错:', e.message); }
}, SAVE_INTERVAL * 1000));
// ── 5. 视频结束处理 ──
const onEnded = () => {
log('视频结束,执行最终存盘');
if (vm.totalStudyTime < video.duration) {
const targetPos = parseInt(video.duration / 5) + 2;
if (targetPos > lastPos) {
const entries = [];
for (let p = lastPos + 1; p <= targetPos; p++) entries.push(p);
const prefix = (vm.watchPointPost == null || vm.watchPointPost === '') ? '' : vm.watchPointPost + ',';
vm.watchPointPost = prefix + entries.join(',');
}
vm.totalStudyTime = Math.ceil(video.duration) + 10;
}
vm.playTimes = Math.max(vm.playTimes || 0, 10);
setTimeout(() => {
try {
if (typeof vm.svcpEveryOnceInAWhile === 'function') vm.svcpEveryOnceInAWhile();
setTimeout(() => {
if (typeof vm.saveDdsjkTimeInWhileFn === 'function') vm.saveDdsjkTimeInWhileFn(null, null, null, 7, true);
log('最终存盘完成');
}, 3000);
} catch (e) { log('最终存盘出错:', e.message); }
}, 1000);
};
video.addEventListener('ended', onEnded, { once: true });
// ── 6. 监控 ──
timers.push(setInterval(() => {
if (!video || video.ended) return;
const pct = video.duration > 0 ? ((vm.totalStudyTime / video.duration) * 100).toFixed(0) : '?';
const pts = (vm.watchPointPost || '').split(',').filter(Boolean).length;
log(`${video.currentTime?.toFixed(0)}/${video.duration?.toFixed(0)}s | tst=${vm.totalStudyTime} (${pct}%) | pts=${pts}`);
}, 5000));
}
// ── 自动检测视频加载/切换 ──
function waitAndApply() {
const check = () => {
const video = document.querySelector('#container video');
if (video && video.duration > 0 && !video.paused) {
applySpeedup();
}
};
setTimeout(check, 3000);
const observer = new MutationObserver(() => {
const video = document.querySelector('#container video');
if (video) {
const vm = findVM();
if (vm && vm.lessonId !== currentLessonId) {
log('检测到新课程视频,重新应用');
active = false;
setTimeout(check, 2000);
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
document.addEventListener('play', (e) => {
if (e.target.tagName === 'VIDEO') {
setTimeout(check, 1000);
}
}, true);
}
// ── 启动 ──
waitAndApply();
log('脚本已加载,等待视频...');
window.__stopSpeedup = () => {
clearAllTimers();
active = false;
const video = document.querySelector('#container video');
if (video) {
delete video.playbackRate;
realPBR.set.call(video, 1);
}
log('已停止');
};
})();
在控制台输入以下命令即可恢复正常播放速度:
__stopSpeedup()
const SPEED = 16; 改成你想要的倍数即可,建议不超过 16。