您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fixes Udemy bug: if UI shows 2x speed, ensure video plays at 2x after lecture change.
// ==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(); })();