From 47e41dea9b706e9f49b66616f373aa4d89cf9a4d Mon Sep 17 00:00:00 2001 From: qier222 Date: Tue, 12 Jul 2022 22:42:50 +0800 Subject: [PATCH] feat: updates --- packages/electron/main/appleMusic.ts | 86 +++++++++++++------ packages/electron/main/cache.ts | 53 ++++++++++-- packages/electron/main/db.ts | 10 +-- packages/electron/main/ipcMain.ts | 38 ++++++-- packages/electron/main/server.ts | 17 ++-- packages/electron/migrations/init.sql | 3 +- packages/shared/AppleMusic.ts | 30 +++++++ packages/shared/CacheAPIs.ts | 19 ++-- packages/shared/IpcChannels.ts | 17 ++-- packages/web/api/hooks/useArtists.ts | 14 +-- .../web/api/hooks/useUserListenedRecords.ts | 2 +- packages/web/components/New/ArtistRow.tsx | 43 ++++++---- packages/web/components/New/PlayingNext.tsx | 2 +- .../New/Topbar/NavigationButtons.tsx | 8 +- .../web/components/New/Topbar/SearchBox.tsx | 4 +- .../components/New/Topbar/TopbarDesktop.tsx | 9 +- .../web/components/New/TrackListHeader.tsx | 34 ++++++-- packages/web/hooks/useAppleMusicAlbum.ts | 26 ++++++ packages/web/hooks/useAppleMusicArtist.ts | 33 +++++++ packages/web/hooks/useVideoCover.ts | 18 +--- .../pages/New/Artist/Header/ArtistInfo.tsx | 13 ++- .../web/pages/New/Artist/Header/Header.tsx | 24 +++++- packages/web/states/scrollPositions.ts | 1 - packages/web/utils/common.ts | 6 ++ 24 files changed, 380 insertions(+), 130 deletions(-) create mode 100644 packages/shared/AppleMusic.ts create mode 100644 packages/web/hooks/useAppleMusicAlbum.ts create mode 100644 packages/web/hooks/useAppleMusicArtist.ts diff --git a/packages/electron/main/appleMusic.ts b/packages/electron/main/appleMusic.ts index dfdfcd5..5199d09 100644 --- a/packages/electron/main/appleMusic.ts +++ b/packages/electron/main/appleMusic.ts @@ -1,56 +1,86 @@ import log from './log' import axios from 'axios' +import { AppleMusicAlbum, AppleMusicArtist } from '@/shared/AppleMusic' -const token = - 'Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IldlYlBsYXlLaWQifQ.eyJpc3MiOiJBTVBXZWJQbGF5IiwiaWF0IjoxNjQ2NjU1MDgwLCJleHAiOjE2NjIyMDcwODB9.pyOrt2FmP0cHkzYtO8KiEzQL2t1qpRszzxIYbLH7faXSddo6PQei771Ja3aGwGVU4hD99lZAw7bwat60iBcGiQ' +const headers = { + Authority: 'amp-api.music.apple.com', + Accept: '*/*', + Authorization: + 'Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IldlYlBsYXlLaWQifQ.eyJpc3MiOiJBTVBXZWJQbGF5IiwiaWF0IjoxNjQ2NjU1MDgwLCJleHAiOjE2NjIyMDcwODB9.pyOrt2FmP0cHkzYtO8KiEzQL2t1qpRszzxIYbLH7faXSddo6PQei771Ja3aGwGVU4hD99lZAw7bwat60iBcGiQ', + Referer: 'https://music.apple.com/', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + 'User-Agent': + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Cider/1.5.1 Chrome/100.0.4896.160 Electron/18.3.3 Safari/537.36', + 'Accept-Encoding': 'gzip', +} -export const getCoverVideo = async ({ +export const getAlbum = async ({ name, artist, }: { name: string artist: string -}): Promise => { +}): Promise => { const keyword = `${artist} ${name}` - log.debug(`[appleMusic] getCoverVideo: ${keyword}`) + log.debug(`[appleMusic] getAlbum: ${keyword}`) const searchResult = await axios({ method: 'GET', + headers, url: 'https://amp-api.music.apple.com/v1/catalog/us/search', params: { term: keyword, types: 'albums', - 'fields[albums]': 'artistName,name,editorialVideo', + 'fields[albums]': 'artistName,name,editorialVideo,editorialNotes', platform: 'web', - limit: '1', - }, - headers: { - Authority: 'amp-api.music.apple.com', - Accept: '*/*', - Authorization: token, - Referer: 'http://localhost:9000/', - 'Sec-Fetch-Dest': 'empty', - 'Sec-Fetch-Mode': 'cors', - 'Sec-Fetch-Site': 'cross-site', - 'User-Agent': - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Cider/1.5.1 Chrome/100.0.4896.160 Electron/18.3.3 Safari/537.36', - 'Accept-Encoding': 'gzip', + limit: '5', + l: 'en-us', // TODO: get from settings }, }).catch(e => { log.debug('[appleMusic] Search album error', e) }) - const album = searchResult?.data?.results?.albums?.data?.[0] + const albums = searchResult?.data?.results?.albums?.data as AppleMusicAlbum[] + const album = + albums.find( + a => + a.attributes.name.toLowerCase() === name.toLowerCase() && + a.attributes.artistName.toLowerCase() === artist.toLowerCase() + ) || albums[0] if (!album) { log.debug('[appleMusic] No album found on apple music') return } - log.debug( - `[appleMusic] Got ${album?.id}: ${album?.attributes?.name} by ${album?.attributes?.artistName}` - ) - const url = album?.attributes?.editorialVideo?.motionSquareVideo1x1?.video - if (!url) { - log.debug('[appleMusic] Album does not have video cover') - } - return url + return album +} + +export const getArtist = async ( + name: string +): Promise => { + const searchResult = await axios({ + method: 'GET', + url: 'https://amp-api.music.apple.com/v1/catalog/us/search', + headers, + params: { + term: name, + types: 'artists', + 'fields[artists]': 'url,name,artwork,editorialVideo,artistBio', + 'omit[resource:artists]': 'relationships', + platform: 'web', + limit: '1', + l: 'en-us', // TODO: get from settings + }, + }).catch(e => { + log.debug('[appleMusic] Search artist error', e) + }) + + const artist = searchResult?.data?.results?.artists?.data?.[0] + if ( + artist && + artist?.attributes?.name?.toLowerCase() === name.toLowerCase() + ) { + return artist + } } diff --git a/packages/electron/main/cache.ts b/packages/electron/main/cache.ts index eba7ae1..c1d842c 100644 --- a/packages/electron/main/cache.ts +++ b/packages/electron/main/cache.ts @@ -112,13 +112,23 @@ class Cache { }) break } - case APIs.VideoCover: { + case APIs.AppleMusicAlbum: { if (!data.id) return - db.upsert(Tables.VideoCover, { + db.upsert(Tables.AppleMusicAlbum, { id: data.id, - url: data.url || 'no', - queriedAt: Date.now(), + json: data.album ? JSON.stringify(data.album) : 'no', + updatedAt: Date.now(), }) + break + } + case APIs.AppleMusicArtist: { + if (!data) return + db.upsert(Tables.AppleMusicArtist, { + id: data.id, + json: data.artist ? JSON.stringify(data.artist) : 'no', + updatedAt: Date.now(), + }) + break } } } @@ -130,6 +140,7 @@ class Cache { case APIs.Personalized: case APIs.RecommendResource: case APIs.UserArtists: + case APIs.ListenedRecords: case APIs.Likelist: { const data = db.find(Tables.AccountData, api) if (data?.json) return JSON.parse(data.json) @@ -179,8 +190,14 @@ class Cache { case APIs.Artist: { if (isNaN(Number(params?.id))) return const data = db.find(Tables.Artist, params.id) - if (data?.json) return JSON.parse(data.json) - break + const fromAppleData = db.find(Tables.AppleMusicArtist, params.id) + const fromApple = fromAppleData?.json && JSON.parse(fromAppleData.json) + const fromNetease = data?.json && JSON.parse(data.json) + if (fromNetease && fromApple && fromApple !== 'no') { + fromNetease.artist.img1v1Url = fromApple.attributes.artwork.url + fromNetease.artist.briefDesc = fromApple.attributes.artistBio + } + return fromNetease ? fromNetease : undefined } case APIs.ArtistAlbum: { if (isNaN(Number(params?.id))) return @@ -208,9 +225,29 @@ class Cache { if (isNaN(Number(params?.id))) return return db.find(Tables.CoverColor, params.id)?.color } - case APIs.VideoCover: { + case APIs.Artists: { + if (!params.ids?.length) return + const artists = db.findMany(Tables.Artist, params.ids) + if (artists.length !== params.ids.length) return + const result = artists.map(a => JSON.parse(a.json)) + result.sort((a, b) => { + const indexA: number = params.ids.indexOf(a.artist.id) + const indexB: number = params.ids.indexOf(b.artist.id) + return indexA - indexB + }) + return result + } + case APIs.AppleMusicAlbum: { if (isNaN(Number(params?.id))) return - return db.find(Tables.VideoCover, params.id)?.url + const data = db.find(Tables.AppleMusicAlbum, params.id) + if (data?.json && data.json !== 'no') return JSON.parse(data.json) + break + } + case APIs.AppleMusicArtist: { + if (isNaN(Number(params?.id))) return + const data = db.find(Tables.AppleMusicArtist, params.id) + if (data?.json && data.json !== 'no') return JSON.parse(data.json) + break } } } diff --git a/packages/electron/main/db.ts b/packages/electron/main/db.ts index 90aec2b..e52d8a5 100644 --- a/packages/electron/main/db.ts +++ b/packages/electron/main/db.ts @@ -18,7 +18,8 @@ export const enum Tables { AccountData = 'AccountData', CoverColor = 'CoverColor', AppData = 'AppData', - VideoCover = 'VideoCover', + AppleMusicAlbum = 'AppleMusicAlbum', + AppleMusicArtist = 'AppleMusicArtist', } interface CommonTableStructure { id: number @@ -62,11 +63,8 @@ export interface TablesStructures { id: 'appVersion' | 'skippedVersion' value: string } - [Tables.VideoCover]: { - id: number - url: string - queriedAt: number - } + [Tables.AppleMusicAlbum]: CommonTableStructure + [Tables.AppleMusicArtist]: CommonTableStructure } type TableNames = keyof TablesStructures diff --git a/packages/electron/main/ipcMain.ts b/packages/electron/main/ipcMain.ts index 1ab04f3..74a404d 100644 --- a/packages/electron/main/ipcMain.ts +++ b/packages/electron/main/ipcMain.ts @@ -12,7 +12,7 @@ import { Thumbar } from './windowsTaskbar' import fastFolderSize from 'fast-folder-size' import path from 'path' import prettyBytes from 'pretty-bytes' -import { getCoverVideo } from './appleMusic' +import { getArtist, getAlbum } from './appleMusic' const on = ( channel: T, @@ -155,19 +155,39 @@ function initOtherIpcMain() { }) /** - * 获取动态封面 + * 从Apple Music获取专辑信息 */ - handle(IpcChannels.GetVideoCover, async (event, { id, name, artist }) => { - const fromCache = cache.get(APIs.VideoCover, { id }) - if (fromCache) { - return fromCache === 'no' ? undefined : fromCache - } + handle( + IpcChannels.GetAlbumFromAppleMusic, + async (event, { id, name, artist }) => { + const fromCache = cache.get(APIs.AppleMusicAlbum, { id }) + if (fromCache) { + return fromCache === 'no' ? undefined : fromCache + } - const fromApple = await getCoverVideo({ name, artist }) - cache.set(APIs.VideoCover, { id, url: fromApple || 'no' }) + const fromApple = await getAlbum({ name, artist }) + cache.set(APIs.AppleMusicAlbum, { id, album: fromApple }) + return fromApple + } + ) + + /** + * 从Apple Music获取歌手信息 + **/ + handle(IpcChannels.GetArtistFromAppleMusic, async (event, { id, name }) => { + const fromApple = await getArtist(name) + cache.set(APIs.AppleMusicArtist, { id, artist: fromApple }) return fromApple }) + /** + * 从缓存读取Apple Music歌手信息 + */ + on(IpcChannels.GetArtistFromAppleMusic, (event, { id }) => { + const artist = cache.get(APIs.AppleMusicArtist, id) + event.returnValue = artist === 'no' ? undefined : artist + }) + /** * 导出tables到json文件,方便查看table大小(dev环境) */ diff --git a/packages/electron/main/server.ts b/packages/electron/main/server.ts index c161029..9c6d476 100644 --- a/packages/electron/main/server.ts +++ b/packages/electron/main/server.ts @@ -12,6 +12,7 @@ import type { FetchAudioSourceResponse } from '@/shared/api/Track' import UNM from '@unblockneteasemusic/rust-napi' import { APIs as CacheAPIs } from '@/shared/CacheAPIs' import { isProd } from './utils' +import { APIs } from '@/shared/CacheAPIs' class Server { port = Number( @@ -20,6 +21,8 @@ class Server { : process.env.ELECTRON_DEV_NETEASE_API_PORT ?? 3000 ) app = express() + // eslint-disable-next-line @typescript-eslint/no-var-requires + netease = require('NeteaseCloudMusicApi') as any constructor() { log.info('[server] starting http server') @@ -28,16 +31,16 @@ class Server { this.getAudioUrlHandler() this.neteaseHandler() this.cacheAudioHandler() - this.serveStaticForProd() + this.serveStaticForProduction() this.listen() } neteaseHandler() { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const neteaseApi = require('NeteaseCloudMusicApi') as (params: any) => any[] - - Object.entries(neteaseApi).forEach(([name, handler]) => { - if (['serveNcmApi', 'getModulesDefinitions', 'song_url'].includes(name)) { + Object.entries(this.netease).forEach(([name, handler]: [string, any]) => { + // 例外处理 + if ( + ['serveNcmApi', 'getModulesDefinitions', APIs.SongUrl].includes(name) + ) { return } @@ -72,7 +75,7 @@ class Server { }) } - serveStaticForProd() { + serveStaticForProduction() { if (isProd) { this.app.use('/', express.static(path.join(__dirname, '../web/'))) } diff --git a/packages/electron/migrations/init.sql b/packages/electron/migrations/init.sql index da49040..9f1d908 100644 --- a/packages/electron/migrations/init.sql +++ b/packages/electron/migrations/init.sql @@ -9,4 +9,5 @@ CREATE TABLE IF NOT EXISTS "Track" ("id" integer NOT NULL,"json" text NOT NULL," CREATE TABLE IF NOT EXISTS "AppData" ("id" text NOT NULL,"value" text, PRIMARY KEY (id)); CREATE TABLE IF NOT EXISTS "CoverColor" ("id" integer NOT NULL,"color" text NOT NULL, "queriedAt" int NOT NULL, PRIMARY KEY (id)); CREATE TABLE IF NOT EXISTS "Audio" ("id" integer NOT NULL,"br" int NOT NULL,"type" text NOT NULL,"source" text NOT NULL,"updatedAt" int NOT NULL, "queriedAt" int NOT NULL, PRIMARY KEY (id)); -CREATE TABLE IF NOT EXISTS "VideoCover" ("id" integer NOT NULL,"url" text NOT NULL,"updatedAt" int NOT NULL, "queriedAt" int NOT NULL, PRIMARY KEY (id)); +CREATE TABLE IF NOT EXISTS "AppleMusicArtist" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id)); +CREATE TABLE IF NOT EXISTS "AppleMusicAlbum" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id)); diff --git a/packages/shared/AppleMusic.ts b/packages/shared/AppleMusic.ts new file mode 100644 index 0000000..5558ea6 --- /dev/null +++ b/packages/shared/AppleMusic.ts @@ -0,0 +1,30 @@ +export interface AppleMusicAlbum { + attributes: { + name: string + artistName: string + editorialVideo: { + motionSquareVideo1x1: { + video: string + } + } + editorialNotes: { + short: string + standard: string + } + } +} + +export interface AppleMusicArtist { + attributes: { + name: string + artistBio: string + editorialVideo: { + motionArtistSquare1x1: { + video: string + } + } + artwork: { + url: string + } + } +} diff --git a/packages/shared/CacheAPIs.ts b/packages/shared/CacheAPIs.ts index ec27932..2613cd4 100644 --- a/packages/shared/CacheAPIs.ts +++ b/packages/shared/CacheAPIs.ts @@ -21,6 +21,7 @@ import { FetchPlaylistResponse, FetchRecommendedPlaylistsResponse, } from './api/Playlists' +import { AppleMusicAlbum, AppleMusicArtist } from 'AppleMusic' export const enum APIs { Album = 'album', @@ -41,8 +42,10 @@ export const enum APIs { ListenedRecords = 'user/record', // not netease api + Artists = 'artistsNotNetease', CoverColor = 'cover_color', - VideoCover = 'video_cover', + AppleMusicAlbum = 'apple_music_album', + AppleMusicArtist = 'apple_music_artist', } export interface APIsParams { @@ -61,9 +64,12 @@ export interface APIsParams { [APIs.UserArtists]: void [APIs.UserPlaylist]: void [APIs.SimilarArtist]: { id: number } - [APIs.CoverColor]: { id: number } - [APIs.VideoCover]: { id: number } [APIs.ListenedRecords]: { id: number; type: number } + + [APIs.Artists]: { ids: number[] } + [APIs.CoverColor]: { id: number } + [APIs.AppleMusicAlbum]: { id: number } + [APIs.AppleMusicArtist]: { id: number } } export interface APIsResponse { @@ -82,7 +88,10 @@ export interface APIsResponse { [APIs.UserArtists]: FetchUserArtistsResponse [APIs.UserPlaylist]: FetchUserPlaylistsResponse [APIs.SimilarArtist]: FetchSimilarArtistsResponse - [APIs.CoverColor]: string | undefined - [APIs.VideoCover]: string | undefined [APIs.ListenedRecords]: FetchListenedRecordsResponse + + [APIs.Artists]: FetchArtistResponse[] + [APIs.CoverColor]: string | undefined + [APIs.AppleMusicAlbum]: AppleMusicAlbum | 'no' + [APIs.AppleMusicArtist]: AppleMusicArtist | 'no' } diff --git a/packages/shared/IpcChannels.ts b/packages/shared/IpcChannels.ts index 9f82468..869a58b 100644 --- a/packages/shared/IpcChannels.ts +++ b/packages/shared/IpcChannels.ts @@ -1,3 +1,4 @@ +import { AppleMusicAlbum, AppleMusicArtist } from './AppleMusic' import { APIs } from './CacheAPIs' import { RepeatMode } from './playerDataTypes' @@ -22,8 +23,8 @@ export const enum IpcChannels { SyncSettings = 'SyncSettings', GetAudioCacheSize = 'GetAudioCacheSize', ResetWindowSize = 'ResetWindowSize', - GetVideoCover = 'GetVideoCover', - SetVideoCover = 'SetVideoCover', + GetAlbumFromAppleMusic = 'GetAlbumFromAppleMusic', + GetArtistFromAppleMusic = 'GetArtistFromAppleMusic', } // ipcMain.on params @@ -59,8 +60,12 @@ export interface IpcChannelsParams { [IpcChannels.SyncSettings]: any [IpcChannels.GetAudioCacheSize]: void [IpcChannels.ResetWindowSize]: void - [IpcChannels.GetVideoCover]: { id: number; name: string; artist: string } - [IpcChannels.SetVideoCover]: { id: number; url: string } + [IpcChannels.GetAlbumFromAppleMusic]: { + id: number + name: string + artist: string + } + [IpcChannels.GetArtistFromAppleMusic]: { id: number; name: string } } // ipcRenderer.on params @@ -82,6 +87,6 @@ export interface IpcChannelsReturns { [IpcChannels.Like]: void [IpcChannels.Repeat]: RepeatMode [IpcChannels.GetAudioCacheSize]: void - [IpcChannels.GetVideoCover]: string | undefined - [IpcChannels.SetVideoCover]: void + [IpcChannels.GetAlbumFromAppleMusic]: AppleMusicAlbum | undefined + [IpcChannels.GetArtistFromAppleMusic]: AppleMusicArtist | undefined } diff --git a/packages/web/api/hooks/useArtists.ts b/packages/web/api/hooks/useArtists.ts index d66268c..f3b5c1f 100644 --- a/packages/web/api/hooks/useArtists.ts +++ b/packages/web/api/hooks/useArtists.ts @@ -15,13 +15,13 @@ export default function useArtists(ids: number[]) { { enabled: !!ids && ids.length > 0, staleTime: 5 * 60 * 1000, // 5 mins - // placeholderData: (): FetchArtistResponse[] => - // window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, { - // api: APIs.Artist, - // query: { - // ids, - // }, - // }), + initialData: (): FetchArtistResponse[] => + window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, { + api: APIs.Artists, + query: { + ids, + }, + }), } ) } diff --git a/packages/web/api/hooks/useUserListenedRecords.ts b/packages/web/api/hooks/useUserListenedRecords.ts index 5b57e5d..812e7eb 100644 --- a/packages/web/api/hooks/useUserListenedRecords.ts +++ b/packages/web/api/hooks/useUserListenedRecords.ts @@ -23,7 +23,7 @@ export default function useUserListenedRecords(params: { enabled: !!uid, placeholderData: (): FetchListenedRecordsResponse => window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, { - api: APIs.UserArtists, + api: APIs.ListenedRecords, }), } ) diff --git a/packages/web/components/New/ArtistRow.tsx b/packages/web/components/New/ArtistRow.tsx index 1f955d2..5deedcd 100644 --- a/packages/web/components/New/ArtistRow.tsx +++ b/packages/web/components/New/ArtistRow.tsx @@ -33,6 +33,30 @@ const Artist = ({ artist }: { artist: Artist }) => { ) } +const Placeholder = ({ row }: { row: number }) => { + return ( +
+ {[...new Array(row * 5).keys()].map(i => ( +
+
+
+ NAME +
+
+ ))} +
+ ) +} + const ArtistRow = ({ artists, title, @@ -70,24 +94,7 @@ const ArtistRow = ({ )} {/* Placeholder */} - {placeholderRow && !artists && ( -
- {[...new Array(placeholderRow * 5).keys()].map(i => ( -
-
-
- PLACE -
-
- ))} -
- )} + {placeholderRow && !artists && }
) } diff --git a/packages/web/components/New/PlayingNext.tsx b/packages/web/components/New/PlayingNext.tsx index 9d419ec..5aef900 100644 --- a/packages/web/components/New/PlayingNext.tsx +++ b/packages/web/components/New/PlayingNext.tsx @@ -130,7 +130,7 @@ const TrackList = ({ className }: { className?: string }) => { overscan={10} components={{ Header: () =>
, - Footer: () =>
, + Footer: () =>
, }} itemContent={(index, track) => ( { await controlsBack.start({ x: -5 }) await controlsBack.start({ x: 0 }) }} - className='app-region-no-drag rounded-full bg-day-600 p-2.5 dark:bg-night-600' + className='app-region-no-drag rounded-full bg-white/10 p-2.5 text-white/40 backdrop-blur-3xl' > - + diff --git a/packages/web/components/New/Topbar/SearchBox.tsx b/packages/web/components/New/Topbar/SearchBox.tsx index b576a74..a347670 100644 --- a/packages/web/components/New/Topbar/SearchBox.tsx +++ b/packages/web/components/New/Topbar/SearchBox.tsx @@ -11,7 +11,7 @@ const SearchBox = () => { return (
{ { const { hideTopbarBackground } = useSnapshot(uiStates) + const location = useLocation() + const isPageHaveBlurBG = + location.pathname.startsWith('/album/') || + location.pathname.startsWith('/artist/') || + location.pathname.startsWith('/playlist/') + const show = !hideTopbarBackground || !isPageHaveBlurBG return ( <> - {!hideTopbarBackground && ( + {show && ( { const Cover = memo( ({ album, playlist }: { album?: Album; playlist?: Playlist }) => { - const isMobile = useIsMobile() - const { data: videoCover } = useVideoCover({ + const { data: albumFromApple } = useAppleMusicAlbum({ id: album?.id, name: album?.name, artist: album?.artist.name, }) + const { data: videoCoverFromRemote } = useVideoCover({ + id: album?.id, + name: album?.name, + artist: album?.artist.name, + enabled: !window.env?.isElectron, + }) + const videoCover = + albumFromApple?.attributes?.editorialVideo?.motionSquareVideo1x1?.video || + videoCoverFromRemote const cover = album?.picUrl || playlist?.coverImgUrl || '' return ( @@ -111,6 +121,12 @@ const TrackListHeader = ({ const duration = album?.songs?.reduce((acc, cur) => acc + cur.dt, 0) || 0 return formatDuration(duration, 'en', 'hh[hr] mm[min]') }, [album?.songs]) + const { data: albumFromApple, isLoading: isLoadingAlbumFromApple } = + useAppleMusicAlbum({ + id: album?.id, + name: album?.name, + artist: album?.artist.name, + }) return (
- {album?.description || playlist?.description} -
+
)}
diff --git a/packages/web/hooks/useAppleMusicAlbum.ts b/packages/web/hooks/useAppleMusicAlbum.ts new file mode 100644 index 0000000..a3ab51e --- /dev/null +++ b/packages/web/hooks/useAppleMusicAlbum.ts @@ -0,0 +1,26 @@ +import { IpcChannels } from '@/shared/IpcChannels' +import { useQuery } from 'react-query' + +export default function useAppleMusicAlbum(props: { + id?: number + name?: string + artist?: string +}) { + const { id, name, artist } = props + return useQuery( + ['useAppleMusicAlbum', props], + async () => { + if (!id || !name || !artist) return + return window.ipcRenderer?.invoke(IpcChannels.GetAlbumFromAppleMusic, { + id, + name, + artist, + }) + }, + { + enabled: !!id && !!name && !!artist, + refetchOnWindowFocus: false, + refetchInterval: false, + } + ) +} diff --git a/packages/web/hooks/useAppleMusicArtist.ts b/packages/web/hooks/useAppleMusicArtist.ts new file mode 100644 index 0000000..69dfaf9 --- /dev/null +++ b/packages/web/hooks/useAppleMusicArtist.ts @@ -0,0 +1,33 @@ +import { AppleMusicArtist } from '@/shared/AppleMusic' +import { APIs } from '@/shared/CacheAPIs' +import { IpcChannels } from '@/shared/IpcChannels' +import { useQuery } from 'react-query' + +export default function useAppleMusicArtist(props: { + id?: number + name?: string +}) { + const { id, name } = props + return useQuery( + ['useAppleMusicArtist', props], + async () => { + if (!id || !name) return + return window.ipcRenderer?.invoke(IpcChannels.GetArtistFromAppleMusic, { + id, + name, + }) + }, + { + enabled: !!id && !!name, + refetchOnWindowFocus: false, + refetchInterval: false, + initialData: (): AppleMusicArtist => + window.ipcRenderer?.sendSync(IpcChannels.GetApiCacheSync, { + api: APIs.AppleMusicArtist, + query: { + id, + }, + }), + } + ) +} diff --git a/packages/web/hooks/useVideoCover.ts b/packages/web/hooks/useVideoCover.ts index b323ff0..4555e49 100644 --- a/packages/web/hooks/useVideoCover.ts +++ b/packages/web/hooks/useVideoCover.ts @@ -1,4 +1,3 @@ -import { IpcChannels } from '@/shared/IpcChannels' import axios from 'axios' import { useQuery } from 'react-query' @@ -6,25 +5,14 @@ export default function useVideoCover(props: { id?: number name?: string artist?: string + enabled?: boolean }) { - const { id, name, artist } = props + const { id, name, artist, enabled = true } = props return useQuery( ['useVideoCover', props], async () => { if (!id || !name || !artist) return - const fromMainProcess = await window.ipcRenderer?.invoke( - IpcChannels.GetVideoCover, - { - id, - name, - artist, - } - ) - if (fromMainProcess) { - return fromMainProcess - } - const fromRemote = await axios.get('/yesplaymusic/video-cover', { params: props, }) @@ -33,7 +21,7 @@ export default function useVideoCover(props: { } }, { - enabled: !!id && !!name && !!artist, + enabled: !!id && !!name && !!artist && enabled, refetchOnWindowFocus: false, refetchInterval: false, } diff --git a/packages/web/pages/New/Artist/Header/ArtistInfo.tsx b/packages/web/pages/New/Artist/Header/ArtistInfo.tsx index 4a9529d..88749fc 100644 --- a/packages/web/pages/New/Artist/Header/ArtistInfo.tsx +++ b/packages/web/pages/New/Artist/Header/ArtistInfo.tsx @@ -1,13 +1,20 @@ import useIsMobile from '@/web/hooks/useIsMobile' +import useAppleMusicArtist from '@/web/hooks/useAppleMusicArtist' const ArtistInfo = ({ artist }: { artist?: Artist }) => { const isMobile = useIsMobile() + const { data: artistFromApple, isLoading: isLoadingArtistFromApple } = + useAppleMusicArtist({ + id: artist?.id, + name: artist?.name, + }) + return (
{artist?.name}
-
+
Artist
@@ -16,9 +23,9 @@ const ArtistInfo = ({ artist }: { artist?: Artist }) => {
{/* Description */} - {!isMobile && ( + {!isMobile && !isLoadingArtistFromApple && (
- {artist?.briefDesc} + {artistFromApple?.attributes?.artistBio || artist?.briefDesc}
)}
diff --git a/packages/web/pages/New/Artist/Header/Header.tsx b/packages/web/pages/New/Artist/Header/Header.tsx index 51bb885..12a4b92 100644 --- a/packages/web/pages/New/Artist/Header/Header.tsx +++ b/packages/web/pages/New/Artist/Header/Header.tsx @@ -6,8 +6,15 @@ import BlurBackground from '@/web/components/New/BlurBackground' import ArtistInfo from './ArtistInfo' import Actions from './Actions' import LatestRelease from './LatestRelease' +import useAppleMusicArtist from '@/web/hooks/useAppleMusicArtist' const Header = ({ artist }: { artist?: Artist }) => { + const { data: artistFromApple, isLoading: isLoadingArtistFromApple } = + useAppleMusicArtist({ + id: artist?.id, + name: artist?.name, + }) + return (
{ grid-area: cover; ` )} - src={resizeImage(artist?.img1v1Url || '', 'lg')} + src={resizeImage( + isLoadingArtistFromApple + ? '' + : artistFromApple?.attributes?.artwork?.url || + artist?.img1v1Url || + '', + 'lg' + )} /> - +