Greasy Fork

Greasy Fork is available in English.

Udemy Speed Fix - UI Sync

Fixes Udemy bug: if UI shows 2x speed, ensure video plays at 2x after lecture change.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Udemy Speed Fix - UI Sync
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Fixes Udemy bug: if UI shows 2x speed, ensure video plays at 2x after lecture change.
// @author       Ameer Jamal
// @match        https://*.udemy.com/course/*/learn/lecture/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=udemy.com
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function () {
    'use strict';

    // === CONFIG ===
    const ACTION_DELAY_MS = 1000;

    // === STATE ===
    let initialLoadCheckDone = false;

    function log(message) {
        console.log("[Udemy Speed Fix v1.1] " + message);
    }

    function extractSpeedFromElement(el) {
        if (!el) return null;
        const text = el.textContent.trim();
        const match = text.match(/^([0-9]\.?[0-9]*)x$/);
        if (match) {
            const value = parseFloat(match[1]);
            return isNaN(value) ? null : { text, value };
        }
        return null;
    }

    function findSpeedFromButton() {
        const speedButton = document.querySelector('button[data-purpose="playback-rate-button"]');
        if (!speedButton) return null;

        const innerSpan = speedButton.querySelector('span');
        return extractSpeedFromElement(innerSpan) || extractSpeedFromElement(speedButton);
    }

    function findSpeedFromControlBars() {
        const controlBarSelectors = [
            'div[class*="control-bar--control-bar--"]',
            'div[data-purpose="video-controls"]'
        ];
        for (const selector of controlBarSelectors) {
            const bar = document.querySelector(selector);
            if (!bar) continue;
            const buttons = bar.querySelectorAll('button');
            for (const btn of buttons) {
                const info = extractSpeedFromElement(btn);
                if (info) return info;
            }
        }
        return null;
    }

    function getCurrentDisplayedSpeedInfo() {
        const fromButton = findSpeedFromButton();
        if (fromButton) {
            log(`Found speed via playback-rate-button: ${fromButton.text}`);
            return fromButton;
        }
        const fromBar = findSpeedFromControlBars();
        if (fromBar) {
            log(`Found speed via control bar: ${fromBar.text}`);
            return fromBar;
        }
        log("Could not find current speed display element.");
        return null;
    }

    function applySpeedFix() {
        log("Attempting to apply speed fix...");
        const videoElement = document.querySelector('video');

        if (!videoElement) {
            log("No video element found on the page.");
            return;
        }

        const displayedSpeedInfo = getCurrentDisplayedSpeedInfo();

        if (displayedSpeedInfo && typeof displayedSpeedInfo.value === 'number' && !isNaN(displayedSpeedInfo.value)) {
            if (videoElement.playbackRate !== displayedSpeedInfo.value) {
                log(`Mismatch! UI shows ${displayedSpeedInfo.text}, video playbackRate is ${videoElement.playbackRate}. Setting to ${displayedSpeedInfo.value}.`);
                videoElement.playbackRate = displayedSpeedInfo.value;
            } else {
                log(`UI shows ${displayedSpeedInfo.text}, and video playbackRate is already correct.`);
            }
        } else {
            log("Could not determine a valid displayed speed from UI.");
        }
    }

    function observeUrlChange(callback) {
        let lastUrl = location.href;
        new MutationObserver(() => {
            const currentUrl = location.href;
            if (currentUrl !== lastUrl) {
                lastUrl = currentUrl;
                callback();
            }
        }).observe(document, { subtree: true, childList: true });
    }

    function runInitialSpeedFix() {
        if (document.readyState === 'complete') {
            if (!initialLoadCheckDone) {
                log("Document complete. Performing initial speed check.");
                setTimeout(applySpeedFix, ACTION_DELAY_MS);
                initialLoadCheckDone = true;
            }
        } else {
            setTimeout(runInitialSpeedFix, 500);
        }
    }

    observeUrlChange(() => {
        log(`URL changed to ${window.location.href}`);
        setTimeout(applySpeedFix, ACTION_DELAY_MS);
    });

    log("Script loaded. Monitoring for SPA URL changes.");
    runInitialSpeedFix();
})();