From 043b1cc231e12a6415fdf0482af7cb1102054fe7 Mon Sep 17 00:00:00 2001 From: VampireChicken12 Date: Mon, 6 Nov 2023 19:07:36 -0500 Subject: [PATCH 1/2] fix(loop button): check box wouldn't check / uncheck when changed from player right click menu --- src/features/loopButton/index.ts | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/features/loopButton/index.ts b/src/features/loopButton/index.ts index 8441b71d..625c8158 100644 --- a/src/features/loopButton/index.ts +++ b/src/features/loopButton/index.ts @@ -1,7 +1,7 @@ import { waitForSpecificMessage } from "@/src/utils/utilities"; import { loopButtonClickListener, makeLoopIcon } from "./utils"; -import { addFeatureItemToMenu, removeFeatureItemFromMenu } from "../featureMenu/utils"; -import eventManager from "@/src/utils/EventManager"; +import { addFeatureItemToMenu, getFeatureMenuItem, removeFeatureItemFromMenu } from "../featureMenu/utils"; +import eventManager, { type FeatureName } from "@/src/utils/EventManager"; export async function addLoopButton() { // Wait for the "options" message from the content script @@ -28,6 +28,30 @@ export async function addLoopButton() { listener: loopButtonClickListener, isToggle: true }); + const loopChangedHandler = (mutationList: MutationRecord[]) => { + for (const mutation of mutationList) { + if (mutation.type === "attributes") { + const { attributeName, target } = mutation; + if (attributeName === "loop") { + const { loop } = target as HTMLVideoElement; + const featureName: FeatureName = "loopButton"; + // Get the feature menu + const featureMenu = document.querySelector("#yte-feature-menu") as HTMLDivElement | null; + if (!featureMenu) return; + + // Check if the feature item already exists in the menu + const featureExistsInMenu = featureMenu.querySelector(`#yte-feature-${featureName}`) as HTMLDivElement | null; + if (featureExistsInMenu) { + const menuItem = getFeatureMenuItem(featureName); + if (!menuItem) return; + menuItem.ariaChecked = loop ? "true" : "false"; + } + } + } + } + }; + const loopChangeMutationObserver = new MutationObserver(loopChangedHandler); + loopChangeMutationObserver.observe(videoElement, { attributes: true, attributeFilter: ["loop"] }); } export function removeLoopButton() { removeFeatureItemFromMenu("loopButton"); From b765a144b0031fa3d3bf446e2b9ea1c370d8a76c Mon Sep 17 00:00:00 2001 From: VampireChicken12 Date: Mon, 6 Nov 2023 20:23:40 -0500 Subject: [PATCH 2/2] fix(player speed): Player speed is now restored to what it was. When disabling player speed option The previous speed is restored --- src/features/playerSpeed/index.ts | 157 ++++++++++++++++++++---------- src/pages/content/index.tsx | 15 +-- 2 files changed, 109 insertions(+), 63 deletions(-) diff --git a/src/features/playerSpeed/index.ts b/src/features/playerSpeed/index.ts index dcc511bc..6a7effa8 100644 --- a/src/features/playerSpeed/index.ts +++ b/src/features/playerSpeed/index.ts @@ -1,74 +1,125 @@ import type { YouTubePlayerDiv } from "@/src/types"; +import eventManager from "@/src/utils/EventManager"; import { isWatchPage, isShortsPage, browserColorLog, waitForSpecificMessage } from "@/src/utils/utilities"; /** - * Sets the player speed based on the options received from a specific message. + * Sets the player speed based on the given speed. + * + * @param playerSpeed - The playback speed to set. + * @returns {Promise} A promise that resolves once the player speed is set. + */ +export async function setPlayerSpeed(playerSpeed: number): Promise; +/** + * Sets the player speed based on the options received * It sets the playback speed if the option is enabled and a valid speed is specified. * + * @param options - Options for setting the player speed. * @returns {Promise} A promise that resolves once the player speed is set. */ -export default async function setPlayerSpeed(options?: { playerSpeed?: number; enableForcedPlaybackSpeed?: boolean }): Promise { - if (options) { - const { enableForcedPlaybackSpeed, playerSpeed } = options; - if (!enableForcedPlaybackSpeed) return; - if (!playerSpeed) return; - // Get the player element - const playerContainer = isWatchPage() - ? (document.querySelector("div#movie_player") as YouTubePlayerDiv | null) - : isShortsPage() - ? (document.querySelector("div#shorts-player") as YouTubePlayerDiv | null) - : null; - const video = document.querySelector("video.html5-main-video") as HTMLVideoElement | null; - // If player element is not available, return - if (!playerContainer) return; - - // If setPlaybackRate method is not available in the player, return - if (!playerContainer.setPlaybackRate) return; - - // Log the message indicating the player speed being set - browserColorLog(`Setting player speed to ${playerSpeed}`, "FgMagenta"); - - // Set the playback speed - playerContainer.setPlaybackRate(playerSpeed); - // Set the video playback speed - if (video) video.playbackRate = playerSpeed; - } else { +export async function setPlayerSpeed(): Promise; +export async function setPlayerSpeed(input?: number): Promise { + let playerSpeed = 1; + let enablePlayerSpeed = true; + // If the input is a number, set the player speed to the given number + if (input === undefined) { // Wait for the "options" message from the content script const optionsData = await waitForSpecificMessage("options", "request_data", "content"); if (!optionsData) return; const { data: { options } } = optionsData; - // Extract the necessary properties from the options object - const { player_speed, enable_forced_playback_speed } = options; + ({ player_speed: playerSpeed, enable_forced_playback_speed: enablePlayerSpeed } = options); + } else if (typeof input === "number") { + playerSpeed = input; + } + // If the player speed is not specified, return + if (!playerSpeed) return; + // If forced playback speed option is disabled, return + if (!enablePlayerSpeed) return; + // Get the player element + const playerContainer = isWatchPage() + ? (document.querySelector("div#movie_player") as YouTubePlayerDiv | null) + : isShortsPage() + ? (document.querySelector("div#shorts-player") as YouTubePlayerDiv | null) + : null; + // If player element is not available, return + if (!playerContainer) return; + const video = document.querySelector("video.html5-main-video") as HTMLVideoElement | null; + + // If setPlaybackRate method is not available in the player, return + if (!playerContainer.setPlaybackRate) return; + const playerVideoData = await playerContainer.getVideoData(); + // If the video is live return + if (playerVideoData.isLive) return; + // Log the message indicating the player speed being set + browserColorLog(`Setting player speed to ${playerSpeed}`, "FgMagenta"); - // If forced playback speed option is disabled, return - if (!enable_forced_playback_speed) return; + // Set the playback speed + playerContainer.setPlaybackRate(playerSpeed); + // Set the video playback speed + if (video) video.playbackRate = playerSpeed; +} +// Restore the player speed to the last saved player speed +export function restorePlayerSpeed() { + // Get the saved player speed from the local storage + const playerSpeed = window.localStorage.getItem("playerSpeed"); + // If the player speed is not available, return + if (!playerSpeed) return; + // Get the player element + const playerContainer = isWatchPage() + ? (document.querySelector("div#movie_player") as YouTubePlayerDiv | null) + : isShortsPage() + ? (document.querySelector("div#shorts-player") as YouTubePlayerDiv | null) + : null; + const video = document.querySelector("video.html5-main-video") as HTMLVideoElement | null; + // If player element is not available, return + if (!playerContainer) return; + // If setPlaybackRate method is not available in the player, return + if (!playerContainer.setPlaybackRate) return; + if (!video) return; + // Log the message indicating the player speed being set + browserColorLog(`Restoring player speed to ${playerSpeed}`, "FgMagenta"); + // Set the playback speed + playerContainer.setPlaybackRate(Number(playerSpeed)); + // Set the video playback speed + video.playbackRate = Number(playerSpeed); +} +export function setupPlaybackSpeedChangeListener() { + const settingsMenu = document.querySelector("div.ytp-settings-menu:not(#yte-feature-menu)"); - // If player speed is not specified, return - if (!player_speed) return; + // Function to handle the playback speed click event + function handlePlaybackSpeedClick(node: HTMLDivElement): void { + // Extract the playback speed value + const speedValue = node.textContent?.trim(); + // If the playback speed is not available, return + if (!speedValue) return; + const playerSpeed = speedValue === "Normal" ? 1 : Number(speedValue); + window.localStorage.setItem("playerSpeed", String(playerSpeed)); + } - // Get the player element - const playerContainer = isWatchPage() - ? (document.querySelector("div#movie_player") as YouTubePlayerDiv | null) - : isShortsPage() - ? (document.querySelector("div#shorts-player") as YouTubePlayerDiv | null) - : null; - // If player element is not available, return - if (!playerContainer) return; - const video = document.querySelector("video.html5-main-video") as HTMLVideoElement | null; + // Create an observer instance + const observer = new MutationObserver((mutationsList) => { + for (const mutation of mutationsList) { + if (mutation.type === "childList") { + const titleElement: HTMLSpanElement | null = document.querySelector("div.ytp-panel > div.ytp-panel-header > span.ytp-panel-title"); + if (titleElement && titleElement.textContent && titleElement.textContent.includes("Playback speed")) { + const menuItems: NodeListOf = document.querySelectorAll("div.ytp-panel-menu div.ytp-menuitem"); + menuItems.forEach((node: HTMLDivElement) => { + eventManager.addEventListener(node, "click", () => handlePlaybackSpeedClick(node), "playerSpeed"); + }); + } else { + const menuItems: NodeListOf = document.querySelectorAll("div.ytp-panel-menu div.ytp-menuitem"); + menuItems.forEach((node: HTMLDivElement) => { + eventManager.removeEventListener(node, "click", "playerSpeed"); + }); + } + } + } + }); - // If setPlaybackRate method is not available in the player, return - if (!playerContainer.setPlaybackRate) return; - const playerVideoData = await playerContainer.getVideoData(); - // If the video is live return - if (playerVideoData.isLive) return; - // Log the message indicating the player speed being set - browserColorLog(`Setting player speed to ${player_speed}`, "FgMagenta"); + const config: MutationObserverInit = { childList: true, subtree: true }; - // Set the playback speed - playerContainer.setPlaybackRate(player_speed); - // Set the video playback speed - if (video) video.playbackRate = player_speed; + if (settingsMenu) { + observer.observe(settingsMenu, config); } } diff --git a/src/pages/content/index.tsx b/src/pages/content/index.tsx index bfdbb1a4..b6683d93 100644 --- a/src/pages/content/index.tsx +++ b/src/pages/content/index.tsx @@ -3,7 +3,7 @@ import { addLoopButton, removeLoopButton } from "@/src/features/loopButton"; import { addMaximizePlayerButton, removeMaximizePlayerButton } from "@/src/features/maximizePlayerButton"; import { maximizePlayer } from "@/src/features/maximizePlayerButton/utils"; import setPlayerQuality from "@/src/features/playerQuality"; -import setPlayerSpeed from "@/src/features/playerSpeed"; +import { restorePlayerSpeed, setPlayerSpeed, setupPlaybackSpeedChangeListener } from "@/src/features/playerSpeed"; import { removeRemainingTimeDisplay, setupRemainingTime } from "@/src/features/remainingTime"; import enableRememberVolume from "@/src/features/rememberVolume"; import { addScreenshotButton, removeScreenshotButton } from "@/src/features/screenshotButton"; @@ -76,6 +76,7 @@ window.onload = function () { addMaximizePlayerButton(); addScreenshotButton(); enableRememberVolume(); + setupPlaybackSpeedChangeListener(); setPlayerQuality(); setPlayerSpeed(); volumeBoost(); @@ -128,15 +129,9 @@ window.onload = function () { data: { playerSpeed, enableForcedPlaybackSpeed } } = message; if (enableForcedPlaybackSpeed && playerSpeed) { - setPlayerSpeed({ - enableForcedPlaybackSpeed, - playerSpeed: Number(playerSpeed) - }); - } else { - setPlayerSpeed({ - enableForcedPlaybackSpeed: false, - playerSpeed: 1 - }); + setPlayerSpeed(Number(playerSpeed)); + } else if (!enableForcedPlaybackSpeed) { + restorePlayerSpeed(); } break; }