diff --git a/src/plugins/synced-lyrics/renderer/lyrics/fetch.ts b/src/plugins/synced-lyrics/renderer/lyrics/fetch.ts index 174ad17d..aee07c15 100644 --- a/src/plugins/synced-lyrics/renderer/lyrics/fetch.ts +++ b/src/plugins/synced-lyrics/renderer/lyrics/fetch.ts @@ -17,7 +17,6 @@ export const [hadSecondAttempt, setHadSecondAttempt] = createSignal(false); // prettier-ignore export const [differentDuration, setDifferentDuration] = createSignal(false); // eslint-disable-next-line prefer-const -export let foundPlainTextLyrics = false; export const extractTimeAndText = ( line: string, @@ -47,7 +46,9 @@ export const extractTimeAndText = ( }; export const makeLyricsRequest = async (extractedSongInfo: SongInfo) => { + setIsFetching(true); setLineLyrics([]); + const songData: Parameters[0] = { title: `${extractedSongInfo.title}`, artist: `${extractedSongInfo.artist}`, @@ -55,14 +56,18 @@ export const makeLyricsRequest = async (extractedSongInfo: SongInfo) => { songDuration: extractedSongInfo.songDuration, }; - const lyrics = await getLyricsList(songData); + let lyrics; + try { + lyrics = await getLyricsList(songData); + } catch {} + setLineLyrics(lyrics ?? []); + setIsFetching(false); }; export const getLyricsList = async ( songData: Pick, ): Promise => { - setIsFetching(true); setIsInstrumental(false); setHadSecondAttempt(false); setDifferentDuration(false); @@ -73,26 +78,21 @@ export const getLyricsList = async ( track_name: songData.title, }); - if (songData.album) { - query.set('album_name', songData.album); + query.set('album_name', songData.album!); + if (query.get('album_name') === 'undefined') { + query.delete('album_name'); } let url = `https://lrclib.net/api/search?${query.toString()}`; let response = await fetch(url); if (!response.ok) { - setIsFetching(false); setDebugInfo('Got non-OK response from server.'); return null; } - let data = (await response.json().catch((e: Error) => { - setDebugInfo(`Error: ${e.message}\n\n${e.stack}`); - - return null; - })) as LRCLIBSearchResponse | null; + let data = await response.json() as LRCLIBSearchResponse; if (!data || !Array.isArray(data)) { - setIsFetching(false); setDebugInfo('Unexpected server response.'); return null; } @@ -108,14 +108,12 @@ export const getLyricsList = async ( response = await fetch(url); if (!response.ok) { - setIsFetching(false); setDebugInfo('Got non-OK response from server. (2)'); return null; } data = (await response.json()) as LRCLIBSearchResponse; if (!Array.isArray(data)) { - setIsFetching(false); setDebugInfo('Unexpected server response. (2)'); return null; } @@ -125,12 +123,26 @@ export const getLyricsList = async ( const filteredResults = []; for (const item of data) { - if (!item.syncedLyrics) continue; - const { artist } = songData; const { artistName } = item; - const ratio = jaroWinkler(artist.toLowerCase(), artistName.toLowerCase()); + const artists = artist.split(/&,/).map((i) => i.trim()); + const itemArtists = artistName.split(/&,/).map((i) => i.trim()); + + const permutations = []; + for (const artistA of artists) { + for (const artistB of itemArtists) { + permutations.push([artistA.toLowerCase(), artistB.toLowerCase()]); + } + } + + for (const artistA of itemArtists) { + for (const artistB of artists) { + permutations.push([artistA.toLowerCase(), artistB.toLowerCase()]); + } + } + + const ratio = Math.max(...permutations.map(([x, y]) => jaroWinkler(x, y))); if (ratio <= 0.9) continue; filteredResults.push(item); @@ -146,23 +158,31 @@ export const getLyricsList = async ( const closestResult = filteredResults[0]; if (!closestResult) { - setIsFetching(false); setDebugInfo('No search result matched the criteria.'); return null; } - // setDebugInfo(JSON.stringify(closestResult, null, 4)); + setDebugInfo(JSON.stringify(closestResult, null, 4)); + + if (Math.abs(closestResult.duration - duration) > 15) { + return null; + } - if (Math.abs(closestResult.duration - duration) > 15) return null; if (Math.abs(closestResult.duration - duration) > 5) { // show message that the timings may be wrong setDifferentDuration(true); } setIsInstrumental(closestResult.instrumental); + if (closestResult.instrumental) { + return null; + } // Separate the lyrics into lines - const raw = closestResult.syncedLyrics.split('\n'); + const raw = closestResult.syncedLyrics?.split('\n') ?? []; + if (!raw.length) { + return null; + } // Add a blank line at the beginning raw.unshift('[0:0.0] '); @@ -186,6 +206,5 @@ export const getLyricsList = async ( line.duration = next.timeInMs - line.timeInMs; } - setIsFetching(false); return syncedLyricList; }; diff --git a/src/providers/song-info-front.ts b/src/providers/song-info-front.ts index b4cbdc9e..63149d4e 100644 --- a/src/providers/song-info-front.ts +++ b/src/providers/song-info-front.ts @@ -200,6 +200,26 @@ export default (api: YoutubePlayer) => { for (const status of ['playing', 'pause'] as const) { video.addEventListener(status, playPausedHandlers[status]); } + + if (!isNaN(video.duration)) { + const { + title, author, + video_id: videoId, + list: playlistId + } = api.getVideoData(); + + const { playerOverlays } = api.getWatchNextResponse(); + + sendSongInfo({ + title, author, videoId, playlistId, + + isUpcoming: false, + lengthSeconds: video.duration, + loading: true, + + uhhh: { playerOverlays } + }); + } } function sendSongInfo(videoData: VideoDataChangeValue) { diff --git a/src/types/youtube-music-desktop-internal.ts b/src/types/youtube-music-desktop-internal.ts index 75852658..68e5fd3c 100644 --- a/src/types/youtube-music-desktop-internal.ts +++ b/src/types/youtube-music-desktop-internal.ts @@ -5,3 +5,17 @@ export interface QueueResponse { autoPlaying?: boolean; continuation?: string; } + +export interface WatchNextResponse { + playerOverlays: { + playerOverlayRenderer: { + browserMediaSession: { + browserMediaSessionRenderer: { + album: { + runs: { text: string; }[] + } + } + } + } + }; +} diff --git a/src/types/youtube-player.ts b/src/types/youtube-player.ts index 96c448cf..995f62fe 100644 --- a/src/types/youtube-player.ts +++ b/src/types/youtube-player.ts @@ -3,6 +3,7 @@ import { VideoDetails } from './video-details'; import { GetPlayerResponse } from './get-player-response'; import { PlayerAPIEvents } from './player-api-events'; +import { WatchNextResponse } from '@/types/youtube-music-desktop-internal'; export interface YoutubePlayer { getInternalApiInterface: ( @@ -427,4 +428,6 @@ export interface YoutubePlayer { addEmbedsConversionTrackingParams: ( ...params: Parameters ) => Return; + + getWatchNextResponse(): WatchNextResponse; }