网页视频自由调速神器


实用脚本推广笔记|网页视频自由调速神器

日常上网课、刷课程、追剧看纪录片,总会遇到平台自带倍速档位太少、调节不够灵活的问题?固定几档速度没法贴合自己观看节奏,这款网页视频自定义调速油猴脚本,轻松解决变速难题!

一、脚本核心亮点

  1. 一键快捷调速 自带加减速度按键,点击+/-就能快速增减播放倍速,还支持一键2倍速、一键恢复原速,操作简单顺手。

  2. 调速幅度随心改 默认每次调节幅度为0.5倍,可自行编辑脚本修改参数。想要精细化慢放调0.25倍步进,追求快速跳转改成1倍步进,按需适配观看习惯。

  3. 速度区间可控 可自主设置最高、最低播放速度上限下限,避免速度过快过慢导致视频卡顿、音画不同步,播放稳定性拉满。

  4. 多场景通用兼容 适配绝大多数网页视频,公考网课、教学课件、影视剧集、公开课、本地网页视频均可使用,学习追剧都能用。

  5. 多浏览器适配 依托Tampermonkey油猴插件运行,Chrome、Edge、Firefox等主流浏览器全都支持,安全无广告弹窗。

二、超简单上手步骤

  1. 浏览器应用商店搜索安装篡改猴(Tampermonkey) 插件
  2. 将本调速脚本导入插件内并启用
  3. 打开任意网页视频页面,即可调用调速功能自由变速

三、个性化调速修改小技巧

默认加减为0.5倍速,想调整精细度只需简单编辑脚本:

  1. 打开油猴插件,找到该调速脚本点击编辑
  2. 找到调速函数代码,修改加减后方数值即可
  • 精细慢看:改为0.25,微小幅度调整节奏
  • 大幅提速:改为1.0,快速跳过冗余片段
  1. 修改完成保存,刷新页面立刻生效

同时还能修改最大、最小倍速限制,打造专属自己的变速规则。

四、适用人群&使用场景

✅ 备考学习者:网课倍速听课,提速节省学习时间,难点慢速反复回看 ✅ 追剧爱好者:灵活调速追剧,跳过拖沓剧情 ✅ 办公剪辑人员:视频素材慢放细节核对,快进筛选片段

五、总结

小巧轻便无冗余功能,纯实用向调速脚本。摆脱平台固定倍速束缚,自主掌控视频播放节奏,简单几步就能解锁灵活变速体验,大幅提升线上观看、学习效率,日常上网必备实用小工具!


脚本

当前脚本是在坚果云上存储进度,方便在不同电脑间进行同步,如果你只有一台电脑,不需要同步的事情,那就让AI改写为本地存储进度的方式

// ==UserScript==
// @name         公考网课进度同步+视频加速
// @namespace    http://tampermonkey.net/
// @version      5.0
// @description  粘贴完整视频链接即可自动识别课程文件夹,无需手动处理路径
// @author       豆包
// @match        *://pan.baidu.com/pfile/video*
// @icon         https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/icons/bar-chart-line.svg
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_log
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    let COURSES = GM_getValue("user_courses", []);
    let hiddenCourseKeys = GM_getValue("hiddenCourseKeys", []);
    let showHiddenPanel = false;

    // ==================== 坚果云配置 ====================
    const WEBDAV_ROOT = "https://dav.jianguoyun.com/dav/";
    const ACCOUNT = "替换为你的";
    const AUTH_TOKEN = "替换为你的";
    const SAVE_DIR = "tampermonkey_sync/";
    const JSON_FILE = "progress.json";
    const auth = btoa(unescape(encodeURIComponent(ACCOUNT + ":" + AUTH_TOKEN)));

    // ==================== 视频加速配置 ====================
    let currentSpeed = 1.0;
    const MAX_SPEED = 16;
    const MIN_SPEED = 0.5;

    // ==================== 工具函数 ====================
    function formatTime(seconds) {
        const totalMin = Math.round(seconds / 60);
        const h = Math.floor(totalMin / 60);
        const m = totalMin % 60;
        return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
    }
    function getVideoTime() {
        const v = document.querySelectorAll('video');
        return v.length ? v[0].currentTime || 0 : 0;
    }
    function getCurrentCourse() {
        const url = location.href;
        return COURSES.find(c => url.includes(c.pathFlag)) || null;
    }
    function toast(msg) {
        let t = document.getElementById('syncToast');
        if (t) t.remove();
        let d = document.createElement('div');
        d.id = 'syncToast'; d.textContent = msg;
        document.body.appendChild(d);
        setTimeout(() => d.style.opacity = 0, 2000);
        setTimeout(() => d.remove(), 2500);
    }
    function saveCoursesToLocal() {
        GM_setValue("user_courses", COURSES);
        renderAllList();
    }

    // ==================== 🔥 核心:自动解析百度网盘文件夹路径 ====================
    function autoParsePathFromUrl(url) {
        try {
            // 1. 找到 path= 后面的全部内容
            const pathParam = new URL(url).searchParams.get('path');
            if (!pathParam) return '';

            // 2. 按 / 分割
            const parts = pathParam.split('/').filter(p => p);

            // 3. 去掉最后一个(视频文件),重新拼接成文件夹路径
            const folderParts = parts.slice(0, -1);
            return '/' + folderParts.join('/') + '/';
        } catch (e) {
            return '';
        }
    }

    // ==================== 坚果云同步 ====================
    async function ensureDir() {
        return new Promise(resolve => {
            GM_xmlhttpRequest({
                method: "MKCOL", url: WEBDAV_ROOT + SAVE_DIR,
                headers: { Authorization: "Basic " + auth },
                onload: () => resolve(true), onerror: () => resolve(true)
            });
        });
    }
    function loadProgressJson() {
        return new Promise(resolve => {
            GM_xmlhttpRequest({
                method: "GET", url: WEBDAV_ROOT + SAVE_DIR + JSON_FILE,
                headers: { Authorization: "Basic " + auth },
                onload: r => {
                    try {
                        resolve(r.status === 200 && r.responseText.trim() ? JSON.parse(r.responseText) : {});
                    } catch (e) {
                        resolve({});
                    }
                },
                onerror: () => resolve({})
            });
        });
    }
    async function saveProgress() {
        let cur = getCurrentCourse();
        if (!cur) return toast("⚠️ 当前页面不属于已添加的课程");
        await ensureDir();
        let data = await loadProgressJson();
        data[cur.key] = { url: location.href, time: getVideoTime() };
        GM_xmlhttpRequest({
            method: "PUT", url: WEBDAV_ROOT + SAVE_DIR + JSON_FILE,
            headers: { Authorization: "Basic " + auth, "Content-Type": "application/json" },
            data: JSON.stringify(data, null, 2),
            onload: resp => {
                if (resp.status >= 200 && resp.status < 300) {
                    toast(`✅ 已保存\n${cur.name} ${formatTime(data[cur.key].time)}`);
                    renderAllList();
                } else toast(`❌ 保存失败`);
            },
            onerror: () => toast("⚠️ 网络异常")
        });
    }
    async function openCourse(course) {
        let data = await loadProgressJson();
        let item = data[course.key];
        if (!item || !item.url) return toast(`🆘 ${course.name} 无进度记录`);
        window.open(item.url, '_blank');
        toast(`✅ 打开 ${course.name}`);
        if (course.lectureUrl) setTimeout(() => {
            window.open(course.lectureUrl, '_blank');
            toast(`📖 打开讲义`)
        }, 600);
    }

    // ==================== 课程收纳管理 ====================
    function hideCourse(key) {
        if (!hiddenCourseKeys.includes(key)) {
            hiddenCourseKeys.push(key);
            GM_setValue("hiddenCourseKeys", hiddenCourseKeys);
            renderAllList();
        }
    }
    function showCourse(key) {
        hiddenCourseKeys = hiddenCourseKeys.filter(k => k !== key);
        GM_setValue("hiddenCourseKeys", hiddenCourseKeys);
        renderAllList();
    }
    function toggleHiddenPanel() {
        showHiddenPanel = !showHiddenPanel;
        renderAllList();
    }

    // ==================== 视频调速 ====================
    function getVideoElement() {
        return document.querySelector('video');
    }
    function unlockVideoSpeed() {
        const video = getVideoElement();
        if (!video) return;
        delete video.playbackRate;
        Object.defineProperty(video, 'playbackRate', { writable: true, configurable: true });
    }
    function setVideoSpeed(speed) {
        const video = getVideoElement();
        if (!video) return toast("⚠️ 未找到视频元素");
        speed = Math.max(MIN_SPEED, Math.min(MAX_SPEED, speed));
        currentSpeed = parseFloat(speed.toFixed(1));
        unlockVideoSpeed();
        video.playbackRate = currentSpeed;
        document.getElementById('speedDisplay').innerText = `${currentSpeed}x`;
        toast(`⚡ 倍速:${currentSpeed}x`);
    }
    function speedUp() { setVideoSpeed(currentSpeed + 0.5); }
    function speedDown() { setVideoSpeed(currentSpeed - 0.5); }
    function speed2x() { setVideoSpeed(2.0); }
    function speedReset() { setVideoSpeed(1.0); }

    // ==================== 课程管理弹窗 ====================
    let editCourseIndex = -1;
    function openCourseModal(editIndex = -1) {
        editCourseIndex = editIndex;
        const modal = document.getElementById('courseModal');
        const title = document.getElementById('modalTitle');
        const nameInput = document.getElementById('courseName');
        const pathInput = document.getElementById('coursePath');
        const keyInput = document.getElementById('courseKey');
        const lectureInput = document.getElementById('courseLecture');

        if (editIndex >= 0) {
            title.innerText = "编辑课程";
            const course = COURSES[editIndex];
            nameInput.value = course.name || "";
            pathInput.value = course.pathFlag || "";
            keyInput.value = course.key || "";
            lectureInput.value = course.lectureUrl || "";
        } else {
            title.innerText = "添加新课程";
            nameInput.value = "";
            pathInput.value = "";
            keyInput.value = "course_" + Date.now();
            lectureInput.value = "";
        }
        modal.style.display = "block";
    }
    function closeCourseModal() {
        document.getElementById('courseModal').style.display = "none";
        editCourseIndex = -1;
    }
    function saveCourse() {
        const name = document.getElementById('courseName').value.trim();
        let pathFlag = document.getElementById('coursePath').value.trim();
        const key = document.getElementById('courseKey').value.trim();
        const lectureUrl = document.getElementById('courseLecture').value.trim();

        if (!name || !pathFlag || !key) {
            return toast("⚠️ 课程名称/路径标识/存储key 不能为空!");
        }

        // 自动解析用户粘贴的完整链接
        const autoPath = autoParsePathFromUrl(pathFlag);
        if (autoPath) {
            pathFlag = autoPath;
        }

        const course = { name, pathFlag, key, lectureUrl };
        if (editCourseIndex >= 0) {
            COURSES[editCourseIndex] = course;
            toast("✅ 课程编辑成功!");
        } else {
            COURSES.push(course);
            toast("✅ 课程添加成功!");
        }
        saveCoursesToLocal();
        closeCourseModal();
    }
    function deleteCourse(index) {
        if (!confirm("⚠️ 确定删除该课程?")) return;
        COURSES.splice(index, 1);
        saveCoursesToLocal();
        toast("🗑️ 课程删除成功!");
    }

    // ==================== 渲染列表 ====================
    async function renderAllList() {
        const mainBox = document.getElementById('mainCourseList');
        const hiddenBox = document.getElementById('hiddenCourseList');
        const toggleBtn = document.getElementById('toggleHiddenBtn');
        const allData = await loadProgressJson();

        const mainCourses = COURSES.filter(item => !hiddenCourseKeys.includes(item.key));
        const hideCourses = COURSES.filter(item => hiddenCourseKeys.includes(item.key));

        let mainHtml = "";
        mainCourses.forEach((course, index) => {
            let time = allData[course.key] ? formatTime(allData[course.key].time || 0) : "未记录";
            mainHtml += `
                <div class="course-line">
                    <button class="course-normal-btn" data-key="${course.key}">${course.name}(${time})</button>
                    <button class="edit-btn" data-index="${index}">编辑</button>
                    <button class="del-btn" data-index="${index}">删除</button>
                    <button class="move-hide-btn" data-key="${course.key}">收纳</button>
                </div>
            `;
        });
        mainBox.innerHTML = mainHtml || '<div style="color:#999;padding:4px">暂无课程,点击上方添加课程</div>';

        let hideHtml = "";
        hideCourses.forEach((course, index) => {
            let time = allData[course.key] ? formatTime(allData[course.key].time || 0) : "未记录";
            hideHtml += `
                <div class="course-line">
                    <button class="course-normal-btn" data-key="${course.key}">${course.name}(${time})</button>
                    <button class="move-show-btn" data-key="${course.key}">取出</button>
                </div>
            `;
        });
        hiddenBox.innerHTML = hideHtml;

        toggleBtn.innerText = showHiddenPanel ? "收起隐藏课程" : `显示隐藏课程(${hideCourses.length}门)`;
        hiddenBox.style.display = showHiddenPanel ? "block" : "none";

        document.querySelectorAll('.course-normal-btn').forEach(btn => {
            btn.onclick = () => {
                let k = btn.dataset.key;
                let target = COURSES.find(c => c.key === k);
                openCourse(target);
            }
        });
        document.querySelectorAll('.edit-btn').forEach(btn => { btn.onclick = () => openCourseModal(btn.dataset.index); });
        document.querySelectorAll('.del-btn').forEach(btn => { btn.onclick = () => deleteCourse(btn.dataset.index); });
        document.querySelectorAll('.move-hide-btn').forEach(btn => { btn.onclick = () => hideCourse(btn.dataset.key); });
        document.querySelectorAll('.move-show-btn').forEach(btn => { btn.onclick = () => showCourse(btn.dataset.key); });
    }

    // ==================== 样式 ====================
    GM_addStyle(`
        #studyPanel{position:fixed;z-index:999999;width:320px;background:#fff;border-radius:10px;box-shadow:0 3px 15px rgba(0,0,0,.1);padding:12px;font-size:14px;user-select:none;cursor:grab;top:20px;right:20px}
        #studyPanel:active{cursor:grabbing}

        /* 按钮同行 2:1 布局 */
        .btn-row{display:flex;gap:8px;margin-bottom:10px}
        #saveBtn{flex:2;height:42px;border:none;border-radius:6px;color:#fff;font-size:15px;cursor:pointer;background:#007BFF}
        #saveBtn:hover{background:#0056b3}
        #addCourseBtn{flex:1;height:42px;border:none;border-radius:6px;color:#fff;font-size:15px;cursor:pointer;background:#13ce66}
        #addCourseBtn:hover{background:#0fad56}

        .speed-panel{display:flex;gap:4px;margin:8px 0;padding:8px;background:#f8f9fa;border-radius:6px}
        #speedDisplay{flex:1;height:36px;line-height:36px;text-align:center;background:#e9ecef;border-radius:4px;font-weight:bold;color:#333}
        .speed-btn{flex:1;height:36px;border:none;border-radius:4px;color:#fff;cursor:pointer;font-weight:bold}
        .speed-up{background:#28c76f}
        .speed-down{background:#ff6b35}
        .speed-2x{background:#722ed1}
        .speed-reset{background:#6c757d}

        .title{margin:6px 0;font-weight:bold;color:#666}
        .course-line{display:flex;gap:3px;margin:4px 0;flex-wrap:wrap}
        .course-normal-btn{flex:1;height:34px;border:none;border-radius:4px;background:#f7f7f7;color:#333;text-align:left;padding:0 8px;cursor:pointer}
        .course-normal-btn:hover{background:#e6f7ff;color:#007BFF}
        .edit-btn{width:45px;height:34px;border:none;border-radius:4px;background:#ff9500;color:#fff;cursor:pointer;font-size:12px}
        .del-btn{width:45px;height:34px;border:none;border-radius:4px;background:#f56c6c;color:#fff;cursor:pointer;font-size:12px}
        .move-hide-btn{width:45px;height:34px;border:none;border-radius:4px;background:#ff6b35;color:#fff;cursor:pointer;font-size:12px}
        .move-show-btn{width:45px;height:34px;border:none;border-radius:4px;background:#28c76f;color:#fff;cursor:pointer;font-size:12px}
        #toggleHiddenBtn{width:100%;height:36px;margin-top:8px;border:none;border-radius:6px;background:#6c757d;color:#fff;cursor:pointer}
        #hiddenCourseList{margin-top:6px;padding-top:6px;border-top:1px dashed #ddd}

        .modal{display:none;position:fixed;z-index:9999999;left:0;top:0;width:100%;height:100%;background:rgba(0,0,0,0.5)}
        .modal-content{background:#fff;margin:15% auto;padding:20px;border-radius:8px;width:300px}
        .modal-title{font-weight:bold;margin-bottom:10px}
        .modal-input{width:100%;height:36px;margin:5px 0;padding:0 8px;border:1px solid #ddd;border-radius:4px}
        .modal-btns{display:flex;gap:8px;margin-top:10px}
        .modal-btn{flex:1;height:36px;border:none;border-radius:4px;cursor:pointer}
        .save-btn{background:#13ce66;color:#fff}
        .close-btn{background:#6c757d;color:#fff}
        #syncToast{position:fixed;z-index:9999999;bottom:30px;left:50%;transform:translateX(-50%);background:#333;color:#fff;padding:10px 20px;border-radius:6px;font-size:14px;opacity:1;transition:opacity 0.5s ease;white-space:pre-line;}
    `);

    // ==================== 面板DOM ====================
    const panel = document.createElement('div');
    panel.id = "studyPanel";
    panel.innerHTML = `
    <div class="btn-row">
        <button id="saveBtn">📌 保存进度</button>
        <button id="addCourseBtn">➕ 新课</button>
    </div>

    <div class="title">⚡ 视频倍速控制</div>
    <div class="speed-panel">
        <div id="speedDisplay">1.0x</div>
        <button class="speed-btn speed-up">+</button>
        <button class="speed-btn speed-down">-</button>
        <button class="speed-btn speed-2x">2x</button>
        <button class="speed-btn speed-reset">重置</button>
    </div>

    <div class="title">在用课程(时:分)</div>
    <div id="mainCourseList"></div>
    <button id="toggleHiddenBtn">显示隐藏课程</button>
    <div id="hiddenCourseList"></div>
    `;

    const modal = document.createElement('div');
    modal.id = "courseModal";
    modal.className = "modal";
    modal.innerHTML = `
    <div class="modal-content">
        <div class="modal-title" id="modalTitle">添加新课程</div>
        <input class="modal-input" id="courseName" placeholder="课程名称(必填)">
        <input class="modal-input" id="coursePath" placeholder="粘贴视频完整链接(自动解析)">
        <input class="modal-input" id="courseKey" placeholder="存储key(自动生成)">
        <input class="modal-input" id="courseLecture" placeholder="讲义链接(选填)">
        <div class="modal-btns">
            <button class="modal-btn save-btn">保存</button>
            <button class="modal-btn close-btn">取消</button>
        </div>
    </div>
    `;

    // ==================== 加载 ====================
    window.addEventListener('load', () => {
        document.body.appendChild(panel);
        document.body.appendChild(modal);

        document.querySelector('.speed-up').onclick = speedUp;
        document.querySelector('.speed-down').onclick = speedDown;
        document.querySelector('.speed-2x').onclick = speed2x;
        document.querySelector('.speed-reset').onclick = speedReset;
        setVideoSpeed(1.0);

        document.getElementById('addCourseBtn').onclick = () => openCourseModal();
        document.querySelector('.save-btn').onclick = saveCourse;
        document.querySelector('.close-btn').onclick = closeCourseModal;

        document.getElementById("saveBtn").onclick = saveProgress;
        document.getElementById("toggleHiddenBtn").onclick = toggleHiddenPanel;
    });

    // 拖拽
    function makeDraggable(el) {
        let drag = false, x1, y1;
        el.addEventListener("mousedown", e => {
            if (e.target.tagName === "BUTTON") return;
            drag = true; x1 = e.clientX - el.offsetLeft; y1 = e.clientY - el.offsetTop;
            el.style.cursor = "grabbing"; e.preventDefault();
        });
        document.addEventListener("mousemove", e => {
            if (!drag) return;
            el.style.left = (e.clientX - x1) + "px";
            el.style.top = (e.clientY - y1) + "px";
            el.style.right = "auto";
        });
        document.addEventListener("mouseup", () => { drag = false; el.style.cursor = "grab"; });
    }

    setTimeout(() => {
        renderAllList();
        makeDraggable(panel);
    }, 1000);
})();

文章作者: 摸鱼
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 摸鱼 !
  目录