Merge pull request #65 from meowarex/dev

Rewrite Timeouts + Bug Fixes
This commit is contained in:
Meow Meow
2026-02-20 15:31:55 +11:00
committed by GitHub
+66 -28
View File
@@ -1,6 +1,6 @@
// MARKER: Core Setup // MARKER: Core Setup
import { LunaUnload, Tracer, ftch } from "@luna/core"; import { LunaUnload, Tracer, ftch } from "@luna/core";
import { StyleTag, PlayState, MediaItem, observePromise, observe } from "@luna/lib"; import { StyleTag, PlayState, MediaItem, observePromise, observe, safeInterval, safeTimeout } from "@luna/lib";
import { settings, Settings } from "./Settings"; import { settings, Settings } from "./Settings";
// Interpret integer backgroundScale (e.g., 10=1.0x, 20=2.0x) // Interpret integer backgroundScale (e.g., 10=1.0x, 20=2.0x)
const getScaledMultiplier = (): number => { const getScaledMultiplier = (): number => {
@@ -235,7 +235,7 @@ const updateButtonStates = function (): void {
if (settings.hideUIEnabled && !isHidden) { if (settings.hideUIEnabled && !isHidden) {
hideButton.style.display = "flex"; hideButton.style.display = "flex";
// Small delay to ensure display is set first, then fade in // Small delay to ensure display is set first, then fade in
setTimeout(() => { safeTimeout(unloads, () => {
hideButton.style.opacity = "1"; hideButton.style.opacity = "1";
hideButton.style.visibility = "visible"; hideButton.style.visibility = "visible";
hideButton.style.pointerEvents = "auto"; hideButton.style.pointerEvents = "auto";
@@ -261,7 +261,7 @@ const updateButtonStates = function (): void {
unhideButton.classList.remove("hide-immediately"); unhideButton.classList.remove("hide-immediately");
unhideButton.classList.remove("auto-faded"); unhideButton.classList.remove("auto-faded");
// Small delay to ensure display is set first, then fade in - (Works for unhide button.. but not hide button.. because uhh idk) // Small delay to ensure display is set first, then fade in - (Works for unhide button.. but not hide button.. because uhh idk)
setTimeout(() => { safeTimeout(unloads, () => {
unhideButton.style.opacity = "1"; unhideButton.style.opacity = "1";
unhideButton.style.visibility = "visible"; unhideButton.style.visibility = "visible";
unhideButton.style.pointerEvents = "auto"; unhideButton.style.pointerEvents = "auto";
@@ -284,11 +284,11 @@ const updateButtonStates = function (): void {
unhideButton.style.pointerEvents = "none"; unhideButton.style.pointerEvents = "none";
unhideButton.classList.remove("auto-faded"); unhideButton.classList.remove("auto-faded");
// Keep display: flex to maintain transitions, then hide after fade // Keep display: flex to maintain transitions, then hide after fade
setTimeout(() => { safeTimeout(unloads, () => {
if (unhideButton.style.opacity === "0") { if (unhideButton.style.opacity === "0") {
unhideButton.style.display = "none"; unhideButton.style.display = "none";
} }
}, 500); // Wait for transition to complete }, 500);
} }
} }
}; };
@@ -307,7 +307,7 @@ const toggleRadiantLyrics = function (): void {
if (nowPlayingContainer) if (nowPlayingContainer)
nowPlayingContainer.classList.remove("radiant-lyrics-ui-hidden"); nowPlayingContainer.classList.remove("radiant-lyrics-ui-hidden");
document.body.classList.remove("radiant-lyrics-ui-hidden"); document.body.classList.remove("radiant-lyrics-ui-hidden");
setTimeout(() => { safeTimeout(unloads, () => {
if (!isHidden) { if (!isHidden) {
updateRadiantLyricsStyles(); updateRadiantLyricsStyles();
} }
@@ -316,7 +316,7 @@ const toggleRadiantLyrics = function (): void {
} else { } else {
isHidden = !isHidden; isHidden = !isHidden;
updateButtonStates(); updateButtonStates();
setTimeout(() => { safeTimeout(unloads, () => {
updateRadiantLyricsStyles(); updateRadiantLyricsStyles();
if (nowPlayingContainer) if (nowPlayingContainer)
nowPlayingContainer.classList.add("radiant-lyrics-ui-hidden"); nowPlayingContainer.classList.add("radiant-lyrics-ui-hidden");
@@ -327,13 +327,13 @@ const toggleRadiantLyrics = function (): void {
// Create buttons // Create buttons
const createHideUIButton = function (): void { const createHideUIButton = function (): void {
setTimeout(() => { safeTimeout(unloads, () => {
if (!settings.hideUIEnabled) return; if (!settings.hideUIEnabled) return;
const fullscreenButton = document.querySelector( const fullscreenButton = document.querySelector(
'[data-test="request-fullscreen"]', '[data-test="request-fullscreen"]',
); );
if (!fullscreenButton || !fullscreenButton.parentElement) { if (!fullscreenButton || !fullscreenButton.parentElement) {
setTimeout(() => createHideUIButton(), 1000); safeTimeout(unloads, () => createHideUIButton(), 1000);
return; return;
} }
if (document.querySelector(".hide-ui-button")) return; if (document.querySelector(".hide-ui-button")) return;
@@ -370,7 +370,7 @@ const createHideUIButton = function (): void {
}); });
hideUIButton.onclick = toggleRadiantLyrics; hideUIButton.onclick = toggleRadiantLyrics;
buttonContainer.insertBefore(hideUIButton, fullscreenButton.nextSibling); buttonContainer.insertBefore(hideUIButton, fullscreenButton.nextSibling);
setTimeout(() => { safeTimeout(unloads, () => {
if (settings.hideUIEnabled && !isHidden) { if (settings.hideUIEnabled && !isHidden) {
hideUIButton.style.opacity = "1"; hideUIButton.style.opacity = "1";
hideUIButton.style.visibility = "visible"; hideUIButton.style.visibility = "visible";
@@ -381,14 +381,14 @@ const createHideUIButton = function (): void {
}; };
const createUnhideUIButton = function (): void { const createUnhideUIButton = function (): void {
setTimeout(() => { safeTimeout(unloads, () => {
if (!settings.hideUIEnabled) return; if (!settings.hideUIEnabled) return;
if (document.querySelector(".unhide-ui-button")) return; if (document.querySelector(".unhide-ui-button")) return;
const nowPlayingContainer = document.querySelector( const nowPlayingContainer = document.querySelector(
'[class*="_nowPlayingContainer"]', '[class*="_nowPlayingContainer"]',
) as HTMLElement; ) as HTMLElement;
if (!nowPlayingContainer) { if (!nowPlayingContainer) {
setTimeout(() => createUnhideUIButton(), 1000); safeTimeout(unloads, () => createUnhideUIButton(), 1000);
return; return;
} }
const unhideUIButton = document.createElement("button"); const unhideUIButton = document.createElement("button");
@@ -405,7 +405,7 @@ const createUnhideUIButton = function (): void {
unhideUIButton.addEventListener("mouseleave", () => { unhideUIButton.addEventListener("mouseleave", () => {
unhideUIButton.style.backgroundColor = "rgba(255,255,255,0.2)"; unhideUIButton.style.backgroundColor = "rgba(255,255,255,0.2)";
unhideUIButton.style.transform = "scale(1)"; unhideUIButton.style.transform = "scale(1)";
window.setTimeout(() => { safeTimeout(unloads, () => {
if (isHidden && !unhideUIButton.matches(":hover")) { if (isHidden && !unhideUIButton.matches(":hover")) {
unhideUIButton.classList.add("auto-faded"); unhideUIButton.classList.add("auto-faded");
} }
@@ -462,10 +462,10 @@ const applyScaledPixelSize = (img: HTMLImageElement | null): void => {
// Update Cover Art background for Now Playing and Global // Update Cover Art background for Now Playing and Global
function updateCoverArtBackground(method: number = 0): void { function updateCoverArtBackground(method: number = 0): void {
if (method === 1) { if (method === 1) {
setTimeout(() => { safeTimeout(unloads, () => {
updateCoverArtBackground(); updateCoverArtBackground();
return;
}, 2000); }, 2000);
return;
} }
let coverArtImageElement = document.querySelector( let coverArtImageElement = document.querySelector(
@@ -1130,7 +1130,7 @@ const createStickyLyricsDropdown = (): void => {
// Navigate to Lyrics & open dropdown // Navigate to Lyrics & open dropdown
lyricsTab.click(); lyricsTab.click();
// Delay to let the tab activate // Delay to let the tab activate
setTimeout(() => openDropdown(), 150); safeTimeout(unloads, () => openDropdown(), 150);
return; return;
} }
// Toggle dropdown // Toggle dropdown
@@ -1202,7 +1202,7 @@ const handleStickyLyricsTrackChange = (): void => {
// Process the track change and update tab state // Process the track change and update tab state
// Tidal takes a while to process the track change sometimes :( // Tidal takes a while to process the track change sometimes :(
setTimeout(() => { safeTimeout(unloads, () => {
if (!settings.stickyLyrics) return; if (!settings.stickyLyrics) return;
const lyricsTab = document.querySelector( const lyricsTab = document.querySelector(
@@ -1213,23 +1213,20 @@ const handleStickyLyricsTrackChange = (): void => {
) as HTMLElement; ) as HTMLElement;
if (!lyricsTab) { if (!lyricsTab) {
// fall back to play queue
if (playQueueTab) playQueueTab.click(); if (playQueueTab) playQueueTab.click();
return; return;
} }
// Attempt to switch to lyrics
lyricsTab.click(); lyricsTab.click();
// Verify we actually stayed on lyrics after a short delay // Verify we actually stayed on lyrics after a short delay
// TODO: Make not shitty (one day maybe) // TODO: Make not shitty (one day maybe)
setTimeout(() => { safeTimeout(unloads, () => {
if (!settings.stickyLyrics) return; if (!settings.stickyLyrics) return;
const onLyrics = document.querySelector( const onLyrics = document.querySelector(
'[data-test="tabs-lyrics"][aria-selected="true"]', '[data-test="tabs-lyrics"][aria-selected="true"]',
); );
if (!onLyrics && playQueueTab) { if (!onLyrics && playQueueTab) {
// Got redirected away from lyrics - fall back to play queue
playQueueTab.click(); playQueueTab.click();
} }
}, 800); }, 800);
@@ -1252,6 +1249,15 @@ function setupStickyLyricsObserver(): void {
} }
}); });
// Apply word lyrics when lyrics container appears or reappears
observe<HTMLElement>(unloads, '[data-test="lyrics-lines"]', () => {
if (lyricsData) {
reapplyWordLyrics();
} else if (settings.lyricsStyle !== 0) {
onTrackChange();
}
});
// sticky lyrics track changes // sticky lyrics track changes
onGlobalTrackChange(() => { onGlobalTrackChange(() => {
if (settings.stickyLyrics) { if (settings.stickyLyrics) {
@@ -1300,7 +1306,7 @@ interface WordLyricsResponse {
// syllable state // syllable state
let trackChangeToken = 0; let trackChangeToken = 0;
let lyricsData: WordLine[] | null = null; let lyricsData: WordLine[] | null = null;
let tickLoopId: number | null = null; let tickLoopUnload: LunaUnload | null = null;
let isActive = false; let isActive = false;
let savedTidalClasses: string[] | null = null; let savedTidalClasses: string[] | null = null;
@@ -1687,11 +1693,10 @@ const unwatchRerender = (): void => {
} }
}; };
// clear tick loop
const clearTickLoop = (): void => { const clearTickLoop = (): void => {
if (tickLoopId !== null) { if (tickLoopUnload !== null) {
clearInterval(tickLoopId); tickLoopUnload();
tickLoopId = null; tickLoopUnload = null;
} }
}; };
@@ -1866,7 +1871,7 @@ const startTickLoop = (): void => {
let lastLogTime = 0; let lastLogTime = 0;
tickLoopId = window.setInterval(() => { tickLoopUnload = safeInterval(unloads, () => {
if (!isActive || lines.length === 0) return; if (!isActive || lines.length === 0) return;
const nowMs = getPlaybackMs(); const nowMs = getPlaybackMs();
@@ -2041,6 +2046,28 @@ const onTrackChange = async (): Promise<void> => {
startTickLoop(); startTickLoop();
}; };
// Reapply word lyrics (for tab switch back)
const reapplyWordLyrics = (): void => {
if (settings.lyricsStyle === 0 || !lyricsData) return;
clearTickLoop();
unwatchRerender();
unhookUserScroll();
unhookSyncButton();
unlockScroll();
activeWordEl = null;
activeLineIdx = -1;
isActive = true;
hideTidalLyrics();
const result = buildWordSpans();
allWords = result.words;
lines = result.lines;
watchForRerender();
startTickLoop();
console.log("[RL-Syllable] Reapplied word lyrics (cached)");
};
// Called by Settings or dropdown // Called by Settings or dropdown
const toggle = (): void => { const toggle = (): void => {
teardown(); teardown();
@@ -2063,10 +2090,21 @@ const setupTrackChangeListener = (): void => {
for (const listener of trackChangeListeners) listener(); for (const listener of trackChangeListeners) listener();
}); });
// Fire if already playing // Applies on app reopen (most ppl close the app while smthn playing)
let hasFiredInitial = false;
if (PlayState.playbackContext?.actualProductId) { if (PlayState.playbackContext?.actualProductId) {
hasFiredInitial = true;
for (const listener of trackChangeListeners) listener(); for (const listener of trackChangeListeners) listener();
} }
if (!hasFiredInitial) {
PlayState.onState(unloads, (state) => {
if (hasFiredInitial) return;
if (state === "PLAYING" && PlayState.playbackContext?.actualProductId) {
hasFiredInitial = true;
for (const listener of trackChangeListeners) listener();
}
});
}
}; };
function setupHeaderObserver(): void { function setupHeaderObserver(): void {