From b28e245019c0e232ccc76c1853d420a6982f57dd Mon Sep 17 00:00:00 2001 From: meowarex Date: Tue, 7 Apr 2026 18:02:07 +1000 Subject: [PATCH] Cleanup & Normalize Endings --- .gitattributes | 7 ++ plugins/radiant-lyrics-luna/src/index.ts | 103 ++++++++++++----------- 2 files changed, 61 insertions(+), 49 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..352d379 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +*.ts text eol=lf +*.tsx text eol=lf +*.js text eol=lf +*.css text eol=lf +*.json text eol=lf +*.md text eol=lf +*.yaml text eol=lf \ No newline at end of file diff --git a/plugins/radiant-lyrics-luna/src/index.ts b/plugins/radiant-lyrics-luna/src/index.ts index 1b0fc8f..d748200 100644 --- a/plugins/radiant-lyrics-luna/src/index.ts +++ b/plugins/radiant-lyrics-luna/src/index.ts @@ -1668,24 +1668,6 @@ const getReduxState = (preferOriginal = false): any => { return redux.store.getState() as any; }; -/** Tidal Connect / cast: Luna PlayState often does not track remote playback; Redux matches the in-app progress bar. */ -const isRemotePlayback = (state = getReduxState()): boolean => { - if (state?.activePlayer?.activePlayer === "REMOTE_PLAYBACK") return true; - return state?.playbackControls?.playbackContext?.playbackSessionId === "tidal-connect"; -}; - -const reduxPlaybackIsPlaying = (state = getReduxState()): boolean => { - const pc = state?.playbackControls; - if (!pc) return false; - if (pc.playbackState === "NOT_PLAYING") return false; - if (pc.playbackState === "PLAYING") return true; - const apt = state?.accumulatedPlaybackTime?.playbackState; - if (apt === "NOT_PLAYING") return false; - if (apt === "PLAYING") return true; - // Connect often leaves playbackState on IDLE while desiredPlaybackState is PLAYING. - return pc.desiredPlaybackState === "PLAYING"; -}; - const getNativeTrackEntity = (trackId: string): any | null => getReduxState(true)?.entities?.tracks?.entities?.[trackId] ?? null; @@ -2073,7 +2055,6 @@ let scrollAnimPending: { } | null = null; let scrollUnlockTimeout: LunaUnload | null = null; let scrollCleanupTimeout: LunaUnload | null = null; -let postTrackChangeResyncTimeout: LunaUnload | null = null; let animatingEls: HTMLElement[] = []; const clearScrollAnim = (): void => { @@ -2095,13 +2076,6 @@ const clearScrollAnim = (): void => { scrollAnimPending = null; }; -const clearPostTrackChangeResync = (): void => { - if (postTrackChangeResyncTimeout) { - postTrackChangeResyncTimeout(); - postTrackChangeResyncTimeout = null; - } -}; - const applyScrollBounce = ( scrollParent: HTMLElement, referenceIdx: number, @@ -2225,16 +2199,57 @@ let savedScroll: any = null; let savedScrollBy: any = null; let scrollAllowed = false; +// MARKER: Tidal Connect (Casting & Remote Lyrics Syncing) +// Uses Redux to mirror Seek Bar progress because Luna PlayState doesn't track remote playback. +const isRemotePlayback = (state = getReduxState()): boolean => { + if (state?.activePlayer?.activePlayer === "REMOTE_PLAYBACK") return true; + return state?.playbackControls?.playbackContext?.playbackSessionId === "tidal-connect"; +}; + +const reduxPlaybackIsPlaying = (state = getReduxState()): boolean => { + const pc = state?.playbackControls; + if (!pc) return false; + if (pc.playbackState === "NOT_PLAYING") return false; + if (pc.playbackState === "PLAYING") return true; + const apt = state?.accumulatedPlaybackTime?.playbackState; + if (apt === "NOT_PLAYING") return false; + if (apt === "PLAYING") return true; + // Tidal Connect leaves playbackState on IDLE (that's why using desiredPlaybackState) + return pc.desiredPlaybackState === "PLAYING"; +}; + +const getPrimaryArtistName = (value: unknown): string => { + if (!Array.isArray(value) || value.length === 0) return ""; + const first = value[0] as { name?: unknown; artist?: { name?: unknown } } | undefined; + if (!first) return ""; + if (typeof first.name === "string") return first.name; + if (typeof first.artist?.name === "string") return first.artist.name; + return ""; +}; + +const trackInfoFromReduxProductId = (productId: string): TrackInfo | null => { + const ent = getNativeTrackEntity(productId); + if (!ent) return null; + const attr = ent.attributes ?? ent; + const title = String(attr.title ?? ""); + const artist = String(attr.artist?.name ?? getPrimaryArtistName(attr.artists) ?? ""); + const isrc = attr.isrc ?? undefined; + if (!title || !artist) return null; + return { trackId: productId, title, artist, isrc }; +}; + // playback time in ms (interpolated between currentTime updates) let lastPlayerTime = 0; let lastPlayerTimeAt = 0; let wasPlaying = false; -// Remote playback: same interpolation idea, fed from Redux playbackControls.latestCurrentTime (seconds). +// Remote playback time in seconds (interpolation [Redux playbackControls.latestCurrentTime]) let lastRemotePlayerTime = 0; let lastRemotePlayerTimeAt = 0; let wasRemotePlaying = false; +let postTrackChangeResyncTimeout: LunaUnload | null = null; + const getRemotePlaybackMs = (state = getReduxState()): number => { const pc = state?.playbackControls; const raw = Number(pc?.latestCurrentTime); @@ -2294,7 +2309,7 @@ const getPlaybackMs = (): number => { return playerTime * 1000; }; -/** Re-anchor lyric time to the same clock as getPlaybackMs (PlayState or Redux on Connect). */ +/** Re Sync Lyrics to getPlaybackMs (PlayState/Redux on Connect). */ const snapPlaybackInterpolationToPlayer = (): void => { const state = getReduxState(); if (isRemotePlayback(state)) { @@ -2313,13 +2328,11 @@ const snapPlaybackInterpolationToPlayer = (): void => { const hasCurrentSyncAnchor = (): boolean => primaryLineIdx >= 0 && primaryLineIdx < lines.length && activeLineIdxs.size > 0; -const getPrimaryArtistName = (value: unknown): string => { - if (!Array.isArray(value) || value.length === 0) return ""; - const first = value[0] as { name?: unknown; artist?: { name?: unknown } } | undefined; - if (!first) return ""; - if (typeof first.name === "string") return first.name; - if (typeof first.artist?.name === "string") return first.artist.name; - return ""; +const clearPostTrackChangeResync = (): void => { + if (postTrackChangeResyncTimeout) { + postTrackChangeResyncTimeout(); + postTrackChangeResyncTimeout = null; + } }; const scheduleRemoteTrackChangeResync = (attempt = 0): void => { @@ -2347,17 +2360,6 @@ const scheduleRemoteTrackChangeResync = (attempt = 0): void => { ); }; -const trackInfoFromReduxProductId = (productId: string): TrackInfo | null => { - const ent = getNativeTrackEntity(productId); - if (!ent) return null; - const attr = ent.attributes ?? ent; - const title = String(attr.title ?? ""); - const artist = String(attr.artist?.name ?? getPrimaryArtistName(attr.artists) ?? ""); - const isrc = attr.isrc ?? undefined; - if (!title || !artist) return null; - return { trackId: productId, title, artist, isrc }; -}; - // get title + artist from media item (Used everywhere now <3) const getTrackInfo = async (): Promise => { const mi = await MediaItem.fromPlaybackContext(); @@ -2386,6 +2388,9 @@ const getTrackInfo = async (): Promise => { return null; }; + +// MARKER: Lyrics API Fetching (Caching & Romanization) + // fetch syllables from the API (wiped on track change) let cachedLyricsKey: string | null = null; let cachedLyricsData: LyricsApiResponse | null = null; @@ -3490,7 +3495,7 @@ const clearTickLoop = (): void => { const teardown = (): void => { trackChangeToken++; clearTickLoop(); - clearPostTrackChangeResync(); + clearPostTrackChangeResync(); // Tidal Connect (see MARKER block) stopTidalFollowLoop(); clearScrollAnim(); unwatchRerender(); @@ -4132,7 +4137,7 @@ const onTrackChange = async (): Promise => { lines = result.lines; watchForRerender(); startTickLoop(); - scheduleRemoteTrackChangeResync(); + scheduleRemoteTrackChangeResync(); // Tidal Connect (see MARKER block) } else { safeTimeout(unloads, () => { if (token !== trackChangeToken) return; @@ -4158,7 +4163,7 @@ const onTrackChange = async (): Promise => { lines = result.lines; watchForRerender(); startTickLoop(); - scheduleRemoteTrackChangeResync(); + scheduleRemoteTrackChangeResync(); // Tidal Connect (see MARKER block) } } else if (++panelRetries < 20) { safeTimeout(unloads, waitForPanel, 250);