智慧树视频加速

v2.0 16x 倍速 自动存盘 一键加速智慧树视频播放,进度正确记录

功能特性

⚡16 倍速播放
💾自动定期存盘
📊进度正确记录
🔄自动检测课程切换
🛡️绕过倍速检测
🛑一键停止恢复

使用方法

  1. 打开智慧树课程视频页面,进入你要刷的那节课
  2. 按 F12 打开浏览器开发者工具(Mac 用 ⌘ + ⌥ + I)
  3. 切到顶部的 Console(控制台)标签页
  4. 复制下方完整代码,粘贴到控制台中,按 Enter 回车执行
先点击播放视频,确认视频正在播放后再执行脚本。脚本会自动将播放速度提升到 16 倍。

完整代码

JavaScript
// ==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('已停止');
  };
})();

停止加速

在控制台输入以下命令即可恢复正常播放速度:

Console
__stopSpeedup()

常见问题

Q: 脚本执行后没有反应?
确保视频已经在播放状态。先点击播放按钮,再执行脚本。
Q: 切换下一节课需要重新执行吗?
不需要。脚本会自动检测课程切换并重新应用加速。
Q: 进度会正确记录吗?
会。脚本会补偿 totalStudyTime 并填充 watchPointPost,每 20 秒自动存盘一次,视频结束时还会执行最终存盘。
Q: 倍速可以调整吗?
可以。代码开头 const SPEED = 16; 改成你想要的倍数即可,建议不超过 16。
Q: 刷新页面后需要重新执行吗?
是的,控制台脚本不会持久化。如果需要自动执行,可以安装 Tampermonkey 浏览器扩展,将代码作为用户脚本添加。