Udemy Fix Video Controls

Fix stupid idiot video controls of Udemy not disappearing.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Udemy Fix Video Controls
// @namespace    https://github.com/Equiel-1703
// @version      1.2.1
// @description  Fix stupid idiot video controls of Udemy not disappearing.
// @author       Henrique Rodrigues Barraz
// @license 	 GPL-3.0
// @match        https://www.udemy.com/course/*
// @icon         https://www.udemy.com/staticx/udemy/images/v7/apple-touch-icon.png
// ==/UserScript==

(function() {
    "use strict";

	// Function to add CSS code to the page
    const addCSS = function (cssText) {
		let newStyleSection = document.createElement("style");

		newStyleSection.textContent = cssText;

		document.querySelector("head").appendChild(newStyleSection);
	}

	// New CSS classes
	const animateOpacity = "animate-opacity";
	const animateBottom = "animate-bottom";
	const hideUIClass = "hide-UI";
	const unhideUIClass = "unhide-UI";
	const hideMouse = "hide-mouse";
	const noBottomSpace = "no-bottom-space";

	// New CSS code to be added
	const newCSS = `
	.animate-opacity {
		transition-duration: 0.7s;
		transition-property: opacity;
	}

	.animate-bottom {
		transition-duration: 0.5s;
		transition-property: bottom;
	}

	.hide-UI {
		opacity: 0%;
	}

	.unhide-UI {
		opacity: 100%;
	}

	.hide-mouse {
		cursor: none;
	}

	.no-bottom-space {
		bottom: 0rem;
	}
	`;
	
	// Add this new CSS to the <head> of the page
	addCSS(newCSS);

	// Elements Classes to be found and manipulated
	const controlBarClass = "div.shaka-control-bar--control-bar-container--OfnMI";
	const videoUIElementsClass = "div.user-activity--hide-when-user-inactive--Oc6Cn";
	const videoContainerClass = "div.curriculum-item-view--content--aaJOw";
	const captionsClass = "div.captions-display--captions-container--PqdGQ";
	
	// Flag to check if the video changes are being monitored or not
	let monitoringVideoChanges = false;
	// Global var to store the cursor position
	let cursorPosition = { x: 0, y: 0 };
	
	// Function to track the cursor position
	const trackCursorPosition = () => {
		document.addEventListener("mousemove", (event) => {
			cursorPosition = { x: event.clientX, y: event.clientY };
		});
	};

	// Function to fix the video controls
	const fixUdemyVideoControls = () => {
		// Get the captions div
		const captionsDiv = document.querySelector(captionsClass);
	
		// Check if returned null
		if (captionsDiv == null) {
			console.log("UdemyVideoFix> Fuck, no captions div found.");
		} else {
			captionsDiv.classList.add(animateBottom);
		}

		// Get all video ui (progress bar, title etc)
		const videoUIElements = document.querySelectorAll(videoUIElementsClass + ", " + controlBarClass)
	
		console.log("UdemyVideoFix> Found video UI elements: ", videoUIElements);
	
		// Add animate opacity class for all video UI elements
		videoUIElements.forEach((el) => {
			el.classList.add(animateOpacity);
		});
	
		// Now, I need the video container div
		const videoContainerDiv = document.querySelector(videoContainerClass);
	
		// Check if returned null
		if (videoContainerDiv == null) {
			console.log("UdemyVideoFix> Fuck, something went really wrong. Couldn't find VideoContainer div.");
			return;
		}
		
		// Flag indicating if UI is visible
		let uiVisible = true;
	
		// Function to hide UI elements
		const hideUI = () => {
			if (!uiVisible) {
				return;
			}

			if (captionsDiv) {
				captionsDiv.classList.add(noBottomSpace);
			}
	
			videoUIElements.forEach((vUI) => {
				if (!vUI.classList.contains(hideUIClass)) {
					vUI.classList.add(hideUIClass);
				}
				vUI.classList.remove(unhideUIClass);
			});
	
			videoContainerDiv.classList.add(hideMouse);
	
			uiVisible = false;
		};

		// Function to show UI elements
		const showUI = () => {
			resetMouseTimeout();

			if (uiVisible) {
				return;
			}

			if (captionsDiv) {
				captionsDiv.classList.remove(noBottomSpace);
			}
	
			videoUIElements.forEach((vUI) => {
				if (!vUI.classList.contains(unhideUIClass)) {
					vUI.classList.add(unhideUIClass);
				}
				vUI.classList.remove(hideUIClass);
			});
	
			videoContainerDiv.classList.remove(hideMouse);
	
			uiVisible = true;	
		};

		// Helper function. Returns true if the cursor is inside 'elementRec'.
		const cursorIsInsideElement = (elementRec) => {
			if (cursorPosition.x >= elementRec.left && cursorPosition.x <= elementRec.right &&
				cursorPosition.y >= elementRec.top && cursorPosition.y <= elementRec.bottom) {
				return true;
			}
			return false;
		};

		// Function to hide UI elements if the cursor is not inside any of them
		const conditionalHideUIElements = () => {
			let canHideCursor = true;

			for (const vUI of videoUIElements) {
				let vUIRec = vUI.getBoundingClientRect();

				if (cursorIsInsideElement(vUIRec)) {
					canHideCursor = false;
					break;
				}
			}

			if (canHideCursor) {
				hideUI();
			} else {
				resetMouseTimeout();
			}
		};

		// Timeout settings
		const timeoutDelayMS = 4_000; // 4 sec to controls hide after no mouse movement
		let timeoutID = null;

		const resetMouseTimeout = () => {
			// Clear previous timeout (if any)
			clearTimeout(timeoutID);

			// Set a new timeout
			timeoutID = setTimeout(conditionalHideUIElements, timeoutDelayMS);
		};
	
		// I will add a listener to detect when the mouse hover the video container.
		videoContainerDiv.addEventListener("mouseenter", showUI);
	
		// And a listener to detect when the mouse leaves the div
		videoContainerDiv.addEventListener("mouseleave", hideUI);
	
		// Add listener to show UI when mouse is moved (while hovering the video only)
		videoContainerDiv.addEventListener("mousemove", showUI);

		// By default, we will show the UI once this function is called
		showUI();
	};

	// This function will monitor the body to check when the video elements are loaded, then fix the video controls
	const monitorElementsLoadAndFix = () => {
		const observer = new MutationObserver((_mutationsList, observer) => {
			let videoContainerDiv = document.querySelector(videoContainerClass);
			let controlBarDiv = document.querySelector(controlBarClass);

			// Check if the divs were loaded
			if (videoContainerDiv && controlBarDiv) {
				console.log("UdemyVideoFix> Video container found: ", videoContainerDiv);
				console.log("UdemyVideoFix> Control bar found: ", controlBarDiv);

				observer.disconnect(); // Stop observing once the divs are found
				
				// Fix the video controls
				fixUdemyVideoControls();

				// Start monitoring for next and previous video changes if not already monitoring
				if (!monitoringVideoChanges) {
					monitorNextPreviousVideo();
				}
			}
		});
		
		// Start observing the body to check when the divs are loaded
		observer.observe(document.body, { childList: true, subtree: true });
	};

	// This function will monitor the video container div to check when the video changes
	const monitorNextPreviousVideo = () => {
		// Get the video container div
		const videoContainerDiv = document.querySelector(videoContainerClass);

		// Check if the div was found
		if (videoContainerDiv == null) {
			console.log("UdemyVideoFix> Video container not found.");
			return;
		} else {
			console.log("UdemyVideoFix> Monitoring for next and previous video changes.");
		}

		// Observer to check when the video container changes
		const observer = new MutationObserver((mutationsList, _observer) => {
			mutationsList.forEach((mutation) => {
				if (mutation.type === "childList") {
					mutation.addedNodes.forEach((node) => {
						if (node.nodeName === "SECTION") {
							console.log("UdemyVideoFix> Video changed. Fixing new video controls.");
		
							// Restart monitoring for video elements load and then fix the video controls
							monitorElementsLoadAndFix();
						}
					});
				}
			});
		});

		// Start observing the video container div
		observer.observe(videoContainerDiv, { childList: true, subtree: false });

		// Set the flag to true
		monitoringVideoChanges = true;
	};

	// Start tracking the cursor position
	trackCursorPosition();
	// Start monitoring for video elements load
	monitorElementsLoadAndFix();
})();