Udemy Speed Fix - UI Sync

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

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

You will need to install an extension such as Tampermonkey to install this script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

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