mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-14 03:41:46 +00:00
feat(synced-lyrics): preferred provider (global/per-song) (#3741)
Co-authored-by: JellyBrick <shlee1503@naver.com>
This commit is contained in:
@ -1,191 +1,21 @@
|
||||
import { createStore } from 'solid-js/store';
|
||||
import * as z from 'zod';
|
||||
|
||||
import { createMemo } from 'solid-js';
|
||||
import type { LyricResult } from '../types';
|
||||
|
||||
import { LRCLib } from './LRCLib';
|
||||
import { LyricsGenius } from './LyricsGenius';
|
||||
import { MusixMatch } from './MusixMatch';
|
||||
import { YTMusic } from './YTMusic';
|
||||
export enum ProviderNames {
|
||||
YTMusic = 'YTMusic',
|
||||
LRCLib = 'LRCLib',
|
||||
MusixMatch = 'MusixMatch',
|
||||
LyricsGenius = 'LyricsGenius',
|
||||
// Megalobiz = 'Megalobiz',
|
||||
}
|
||||
|
||||
import { getSongInfo } from '@/providers/song-info-front';
|
||||
|
||||
import type { LyricProvider, LyricResult } from '../types';
|
||||
import type { SongInfo } from '@/providers/song-info';
|
||||
|
||||
export const providers = {
|
||||
YTMusic: new YTMusic(),
|
||||
LRCLib: new LRCLib(),
|
||||
MusixMatch: new MusixMatch(),
|
||||
LyricsGenius: new LyricsGenius(),
|
||||
// Megalobiz: new Megalobiz(), // Disabled because it is too unstable and slow
|
||||
} as const;
|
||||
|
||||
export type ProviderName = keyof typeof providers;
|
||||
export const providerNames = Object.keys(providers) as ProviderName[];
|
||||
export const ProviderNameSchema = z.enum(ProviderNames);
|
||||
export type ProviderName = z.infer<typeof ProviderNameSchema>;
|
||||
export const providerNames = ProviderNameSchema.options;
|
||||
|
||||
export type ProviderState = {
|
||||
state: 'fetching' | 'done' | 'error';
|
||||
data: LyricResult | null;
|
||||
error: Error | null;
|
||||
};
|
||||
|
||||
type LyricsStore = {
|
||||
provider: ProviderName;
|
||||
current: ProviderState;
|
||||
lyrics: Record<ProviderName, ProviderState>;
|
||||
};
|
||||
|
||||
const initialData = () =>
|
||||
providerNames.reduce(
|
||||
(acc, name) => {
|
||||
acc[name] = { state: 'fetching', data: null, error: null };
|
||||
return acc;
|
||||
},
|
||||
{} as LyricsStore['lyrics'],
|
||||
);
|
||||
|
||||
export const [lyricsStore, setLyricsStore] = createStore<LyricsStore>({
|
||||
provider: providerNames[0],
|
||||
lyrics: initialData(),
|
||||
get current(): ProviderState {
|
||||
return this.lyrics[this.provider];
|
||||
},
|
||||
});
|
||||
|
||||
export const currentLyrics = createMemo(() => {
|
||||
const provider = lyricsStore.provider;
|
||||
return lyricsStore.lyrics[provider];
|
||||
});
|
||||
|
||||
type VideoId = string;
|
||||
|
||||
type SearchCacheData = Record<ProviderName, ProviderState>;
|
||||
interface SearchCache {
|
||||
state: 'loading' | 'done';
|
||||
data: SearchCacheData;
|
||||
}
|
||||
|
||||
// TODO: Maybe use localStorage for the cache.
|
||||
const searchCache = new Map<VideoId, SearchCache>();
|
||||
export const fetchLyrics = (info: SongInfo) => {
|
||||
if (searchCache.has(info.videoId)) {
|
||||
const cache = searchCache.get(info.videoId)!;
|
||||
|
||||
if (cache.state === 'loading') {
|
||||
setTimeout(() => {
|
||||
fetchLyrics(info);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (getSongInfo().videoId === info.videoId) {
|
||||
setLyricsStore('lyrics', () => {
|
||||
// weird bug with solid-js
|
||||
return JSON.parse(JSON.stringify(cache.data)) as typeof cache.data;
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const cache: SearchCache = {
|
||||
state: 'loading',
|
||||
data: initialData(),
|
||||
};
|
||||
|
||||
searchCache.set(info.videoId, cache);
|
||||
if (getSongInfo().videoId === info.videoId) {
|
||||
setLyricsStore('lyrics', () => {
|
||||
// weird bug with solid-js
|
||||
return JSON.parse(JSON.stringify(cache.data)) as typeof cache.data;
|
||||
});
|
||||
}
|
||||
|
||||
const tasks: Promise<void>[] = [];
|
||||
|
||||
// prettier-ignore
|
||||
for (
|
||||
const [providerName, provider] of Object.entries(providers) as [
|
||||
ProviderName,
|
||||
LyricProvider,
|
||||
][]
|
||||
) {
|
||||
const pCache = cache.data[providerName];
|
||||
|
||||
tasks.push(
|
||||
provider
|
||||
.search(info)
|
||||
.then((res) => {
|
||||
pCache.state = 'done';
|
||||
pCache.data = res;
|
||||
|
||||
if (getSongInfo().videoId === info.videoId) {
|
||||
setLyricsStore('lyrics', (old) => {
|
||||
return {
|
||||
...old,
|
||||
[providerName]: {
|
||||
state: 'done',
|
||||
data: res ? { ...res } : null,
|
||||
error: null,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
pCache.state = 'error';
|
||||
pCache.error = error;
|
||||
|
||||
console.error(error);
|
||||
|
||||
if (getSongInfo().videoId === info.videoId) {
|
||||
setLyricsStore('lyrics', (old) => {
|
||||
return {
|
||||
...old,
|
||||
[providerName]: { state: 'error', error, data: null },
|
||||
};
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Promise.allSettled(tasks).then(() => {
|
||||
cache.state = 'done';
|
||||
searchCache.set(info.videoId, cache);
|
||||
});
|
||||
};
|
||||
|
||||
export const retrySearch = (provider: ProviderName, info: SongInfo) => {
|
||||
setLyricsStore('lyrics', (old) => {
|
||||
const pCache = {
|
||||
state: 'fetching',
|
||||
data: null,
|
||||
error: null,
|
||||
};
|
||||
|
||||
return {
|
||||
...old,
|
||||
[provider]: pCache,
|
||||
};
|
||||
});
|
||||
|
||||
providers[provider]
|
||||
.search(info)
|
||||
.then((res) => {
|
||||
setLyricsStore('lyrics', (old) => {
|
||||
return {
|
||||
...old,
|
||||
[provider]: { state: 'done', data: res, error: null },
|
||||
};
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setLyricsStore('lyrics', (old) => {
|
||||
return {
|
||||
...old,
|
||||
[provider]: { state: 'error', data: null, error },
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
13
src/plugins/synced-lyrics/providers/renderer.ts
Normal file
13
src/plugins/synced-lyrics/providers/renderer.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { ProviderNames } from './index';
|
||||
import { YTMusic } from './YTMusic';
|
||||
import { LRCLib } from './LRCLib';
|
||||
import { MusixMatch } from './MusixMatch';
|
||||
import { LyricsGenius } from './LyricsGenius';
|
||||
|
||||
export const providers = {
|
||||
[ProviderNames.YTMusic]: new YTMusic(),
|
||||
[ProviderNames.LRCLib]: new LRCLib(),
|
||||
[ProviderNames.MusixMatch]: new MusixMatch(),
|
||||
[ProviderNames.LyricsGenius]: new LyricsGenius(),
|
||||
// [ProviderNames.Megalobiz]: new Megalobiz(), // Disabled because it is too unstable and slow
|
||||
} as const;
|
||||
Reference in New Issue
Block a user