mirror of
https://github.com/meowarex/TidaLuna-Plugins.git
synced 2026-10-15 07:53:13 +11:00
commit
e3d71806b1
3 files changed
+36
-8
No files matched your search
@@ -211,9 +211,10 @@ export interface ArtworkLookup {
|
||||
artwork: AnimatedArtwork | null;
|
||||
/**
|
||||
* Scope the result is valid for, as a dedupe key: the album key only when
|
||||
* the answer was matched by album. A title-only fallback (or a miss) is
|
||||
* specific to this track, so its key scopes to the title — the album's
|
||||
* other tracks must not inherit it.
|
||||
* the answer was matched by album (by name or via the track's ISRC, which
|
||||
* the server resolves to the same album). A title-only fallback (or a
|
||||
* miss) is specific to this track, so its key scopes to the title — the
|
||||
* album's other tracks must not inherit it.
|
||||
*/
|
||||
key: string;
|
||||
}
|
||||
@@ -222,6 +223,13 @@ export interface ArtworkLookup {
|
||||
export const artworkTitleKey = (artist: string, title: string): string =>
|
||||
`${artist}\u0000\u0000${title}`;
|
||||
|
||||
/**
|
||||
* ISRC lookup: the server resolves the exact song by ISRC and answers
|
||||
* from its album, immune to title/album name mismatches.
|
||||
*/
|
||||
const artworkIsrcUrl = (isrc: string): string =>
|
||||
`https://ama.trainswift.net/api/v1/artwork/search?isrc=${encodeURIComponent(isrc)}`;
|
||||
|
||||
function artworkSearchUrl(
|
||||
title: string,
|
||||
artist: string,
|
||||
@@ -234,8 +242,9 @@ function artworkSearchUrl(
|
||||
}
|
||||
|
||||
// Cached including misses; most tracks have none and the answer never changes.
|
||||
// Only album-matched results go under the album key — a title-only fallback
|
||||
// must not stand in for the album's other tracks (see ArtworkLookup).
|
||||
// Only album-matched results (by name or ISRC) go under the album key — a
|
||||
// title-only fallback must not stand in for the album's other tracks (see
|
||||
// ArtworkLookup).
|
||||
const animatedArtworkCache = new Map<string, AnimatedArtwork | null>();
|
||||
const ARTWORK_CACHE_MAX = 200;
|
||||
|
||||
@@ -243,10 +252,12 @@ export async function fetchAnimatedArtwork(
|
||||
title: string,
|
||||
artist: string,
|
||||
album?: string,
|
||||
isrc?: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<ArtworkLookup> {
|
||||
const albumName = album?.trim() || "";
|
||||
const albumPresent = albumName !== "";
|
||||
const isrcTrimmed = isrc?.trim() || "";
|
||||
// Album key, matching artworkKey() in index.ts; without an album the
|
||||
// answer is only ever matched by title, so key by title directly.
|
||||
const cacheKey = albumPresent
|
||||
@@ -301,7 +312,15 @@ export async function fetchAnimatedArtwork(
|
||||
};
|
||||
|
||||
try {
|
||||
// Exact album first; on a miss retry once without the album (server-side disambiguation is imperfect)
|
||||
// ISRC first: the exact match. A hit is resolved through the track's
|
||||
// album, so it is album-scoped (cacheKey); a miss says nothing about
|
||||
// the album (the ISRC may simply be unknown to the source), so fall
|
||||
// through to the name flow without caching it.
|
||||
if (isrcTrimmed) {
|
||||
const byIsrc = await tryFetch(artworkIsrcUrl(isrcTrimmed));
|
||||
if (byIsrc) return { artwork: remember(byIsrc), key: cacheKey };
|
||||
}
|
||||
// Fallback: exact album first; on a miss retry once without the album (server-side disambiguation is imperfect)
|
||||
const withAlbum = await tryFetch(
|
||||
artworkSearchUrl(title, artist, album),
|
||||
);
|
||||
|
||||
@@ -29,7 +29,8 @@ export class AnimatedArtworkLayer {
|
||||
private artwork: { url: string; url_tall: string } | null = null;
|
||||
/**
|
||||
* Scope key of the displayed artwork (see fetchAnimatedArtwork): the album
|
||||
* key only when the answer was matched by album, else the title key.
|
||||
* key only when the answer was matched by album (name or ISRC), else the
|
||||
* title key.
|
||||
*/
|
||||
private artworkKey: string | null = null;
|
||||
private liveSrc: string | null = null;
|
||||
@@ -121,6 +122,7 @@ export class AnimatedArtworkLayer {
|
||||
title: string,
|
||||
artist: string,
|
||||
album: string | undefined,
|
||||
isrc?: string,
|
||||
): Promise<{ done: boolean; key: string }> {
|
||||
// Already showing artwork that covers this track (album-matched, or the
|
||||
// same track again) — keep playing, touch nothing.
|
||||
@@ -143,7 +145,13 @@ export class AnimatedArtworkLayer {
|
||||
if (!document.querySelector<HTMLElement>(ART_TILE_SELECTOR))
|
||||
return { done: false, key };
|
||||
|
||||
const lookup = await fetchAnimatedArtwork(title, artist, album, signal);
|
||||
const lookup = await fetchAnimatedArtwork(
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
isrc,
|
||||
signal,
|
||||
);
|
||||
if (token !== this.loadToken) return { done: true, key: lookup.key };
|
||||
if (!this.tileAlive()) return { done: false, key: lookup.key };
|
||||
|
||||
|
||||
@@ -1152,6 +1152,7 @@ const updateAnimatedArtwork = async (
|
||||
target.title,
|
||||
target.artist,
|
||||
target.album,
|
||||
target.isrc,
|
||||
);
|
||||
// Adopt the effective scope — a title-matched result (or a miss) must not
|
||||
// stand in for the album's other tracks — unless a newer trigger claimed.
|
||||
|
||||
Reference in new issue
Block a user