From 744247143b58ab771ee4c120c73bd8e345ad8e84 Mon Sep 17 00:00:00 2001 From: qier222 Date: Sat, 2 Apr 2022 18:46:08 +0800 Subject: [PATCH] feat: updates --- src/renderer/components/Player.tsx | 2 +- src/renderer/components/TracksAlbum.tsx | 9 ++-- src/renderer/components/TracksGrid.tsx | 58 ++++++++++++++------ src/renderer/components/TracksList.tsx | 25 +++++---- src/renderer/hooks/useAlbum.ts | 10 ++++ src/renderer/hooks/usePlaylist.ts | 10 ++++ src/renderer/pages/Album.tsx | 7 ++- src/renderer/pages/Artist.tsx | 31 +++++++++-- src/renderer/pages/Playlist.tsx | 6 +-- src/renderer/pages/Search/Search.tsx | 35 ++++++++++++- src/renderer/utils/player.ts | 70 ++++++++++++++++--------- 11 files changed, 195 insertions(+), 68 deletions(-) diff --git a/src/renderer/components/Player.tsx b/src/renderer/components/Player.tsx index e3f4bfb..4920728 100644 --- a/src/renderer/components/Player.tsx +++ b/src/renderer/components/Player.tsx @@ -33,7 +33,7 @@ const PlayingTrack = () => { )} {!track?.al?.picUrl && ( diff --git a/src/renderer/components/TracksAlbum.tsx b/src/renderer/components/TracksAlbum.tsx index 0af5715..9df72e7 100644 --- a/src/renderer/components/TracksAlbum.tsx +++ b/src/renderer/components/TracksAlbum.tsx @@ -49,19 +49,22 @@ const Track = memo( isLiked = false, isSkeleton = false, isHighlight = false, - subtitle = undefined, onClick, }: { track: Track isLiked?: boolean isSkeleton?: boolean isHighlight?: boolean - subtitle?: string onClick: (e: React.MouseEvent, trackID: number) => void }) => { if (enableRenderLog) console.debug(`Rendering TracksAlbum.tsx Track ${track.name}`) + const subtitle = useMemo( + () => track.tns?.at(0) ?? track.alia?.at(0), + [track.alia, track.tns] + ) + return (
onClick(e, track.id)} @@ -125,7 +128,6 @@ const Track = memo( )} {subtitle && ( ))}
diff --git a/src/renderer/components/TracksGrid.tsx b/src/renderer/components/TracksGrid.tsx index 674217d..c4f03eb 100644 --- a/src/renderer/components/TracksGrid.tsx +++ b/src/renderer/components/TracksGrid.tsx @@ -1,5 +1,6 @@ import ArtistInline from '@/components/ArtistsInline' import Skeleton from '@/components/Skeleton' +import { player } from '@/store' import { resizeImage } from '@/utils/common' import SvgIcon from './SvgIcon' @@ -7,13 +8,16 @@ const Track = ({ track, isSkeleton = false, isHighlight = false, + onClick, }: { track: Track - isSkeleton: boolean - isHighlight: boolean + isSkeleton?: boolean + isHighlight?: boolean + onClick: (e: React.MouseEvent, trackID: number) => void }) => { return (
onClick(e, track.id)} className={classNames( 'group grid w-full rounded-xl after:scale-[.98] after:rounded-xl ', 'grid-cols-1 py-1.5 px-2', @@ -27,30 +31,30 @@ const Track = ({
{/* Cover */}
- {!isSkeleton && ( + {isSkeleton ? ( + + ) : ( )} - {isSkeleton && ( - - )}
{/* Track name & Artists */}
- {!isSkeleton && ( + {isSkeleton ? ( + PLACEHOLDER12345 + ) : (
{track.name}
)} - {isSkeleton && ( - PLACEHOLDER12345 - )}
{isSkeleton ? ( @@ -60,10 +64,23 @@ const Track = ({ {track.mark === 1318912 && ( )} - + )}
@@ -84,6 +101,16 @@ const TrackGrid = ({ onTrackDoubleClick?: (trackID: number) => void cols?: number }) => { + const handleClick = (e: React.MouseEvent, trackID: number) => { + if (e.detail === 2) onTrackDoubleClick?.(trackID) + } + + const playerSnapshot = useSnapshot(player) + const playingTrack = useMemo( + () => playerSnapshot.track, + [playerSnapshot.track] + ) + return (
{tracks.map((track, index) => ( ))}
diff --git a/src/renderer/components/TracksList.tsx b/src/renderer/components/TracksList.tsx index 85e64e8..4c439bf 100644 --- a/src/renderer/components/TracksList.tsx +++ b/src/renderer/components/TracksList.tsx @@ -13,17 +13,20 @@ const Track = memo( track, isLiked = false, isSkeleton = false, - isPlaying = false, - subtitle = undefined, + isHighlight = false, onClick, }: { track: Track isLiked?: boolean isSkeleton?: boolean - isPlaying?: boolean - subtitle?: string + isHighlight?: boolean onClick: (e: React.MouseEvent, trackID: number) => void }) => { + const subtitle = useMemo( + () => track.tns?.at(0) ?? track.alia?.at(0), + [track.alia, track.tns] + ) + return (
onClick(e, track.id)} @@ -31,9 +34,9 @@ const Track = memo( 'group grid w-full rounded-xl after:scale-[.98] after:rounded-xl ', 'grid-cols-12 p-2 pr-4', !isSkeleton && - !isPlaying && + !isHighlight && 'btn-hover-animation after:bg-gray-100 dark:after:bg-white/10', - !isSkeleton && isPlaying && 'bg-brand-50 dark:bg-gray-800' + !isSkeleton && isHighlight && 'bg-brand-50 dark:bg-gray-800' )} > {/* Track info */} @@ -58,7 +61,7 @@ const Track = memo(
{track.name} @@ -67,7 +70,7 @@ const Track = memo( title={subtitle} className={classNames( 'ml-1', - isPlaying ? 'text-brand-500/[.8]' : 'text-gray-400' + isHighlight ? 'text-brand-500/[.8]' : 'text-gray-400' )} > ({subtitle}) @@ -79,7 +82,7 @@ const Track = memo(
{track.al.name} @@ -147,7 +150,7 @@ const Track = memo(
fetch(params), + { + staleTime: Infinity, + } + ) +} + export async function prefetchAlbum(params: FetchAlbumParams) { await reactQueryClient.prefetchQuery( [AlbumApiNames.FETCH_ALBUM, params.id], diff --git a/src/renderer/hooks/usePlaylist.ts b/src/renderer/hooks/usePlaylist.ts index f475ce3..98b5b02 100644 --- a/src/renderer/hooks/usePlaylist.ts +++ b/src/renderer/hooks/usePlaylist.ts @@ -28,6 +28,16 @@ export default function usePlaylist( ) } +export function fetchPlaylistWithReactQuery(params: FetchPlaylistParams) { + return reactQueryClient.fetchQuery( + [PlaylistApiNames.FETCH_PLAYLIST, params], + () => fetch(params), + { + staleTime: 3600000, + } + ) +} + export async function prefetchPlaylist(params: FetchPlaylistParams) { await reactQueryClient.prefetchQuery( [PlaylistApiNames.FETCH_PLAYLIST, params], diff --git a/src/renderer/pages/Album.tsx b/src/renderer/pages/Album.tsx index 7da64a6..ebe9630 100644 --- a/src/renderer/pages/Album.tsx +++ b/src/renderer/pages/Album.tsx @@ -296,12 +296,11 @@ const Album = () => { }) const handlePlay = async (trackID: number | null = null) => { - const realAlbum = album?.album - if (!realAlbum) { - toast('Failed to play album') + if (!album?.album.id) { + toast('无法播放专辑,该专辑不存在') return } - await player.playAlbum(realAlbum, trackID) + await player.playAlbum(album.album.id, trackID) } return ( diff --git a/src/renderer/pages/Artist.tsx b/src/renderer/pages/Artist.tsx index 39338b1..b9ae455 100644 --- a/src/renderer/pages/Artist.tsx +++ b/src/renderer/pages/Artist.tsx @@ -9,6 +9,7 @@ import TracksGrid from '@/components/TracksGrid' import CoverRow, { Subtitle } from '@/components/CoverRow' import Skeleton from '@/components/Skeleton' import useTracks from '@/hooks/useTracks' +import { player } from '@/store' const Header = ({ artist }: { artist: Artist | undefined }) => { const coverImage = resizeImage(artist?.img1v1Url || '', 'md') @@ -51,18 +52,27 @@ const LatestRelease = ({ album: Album | undefined isLoading: boolean }) => { + const navigate = useNavigate() + const toAlbum = () => navigate(`/album/${album?.id}`) return (
最新发行
-
+
{isLoading ? ( ) : ( - + )} -
+
{album?.name}
@@ -84,6 +94,20 @@ const PopularTracks = ({ ids: tracks?.slice(0, 10)?.map(t => t.id) ?? [], }) + const handlePlay = useCallback( + (trackID: number | null = null) => { + if (!tracks?.length) { + toast('无法播放歌单') + return + } + player.playAList( + tracks.map(t => t.id), + trackID + ) + }, + [tracks] + ) + return (
@@ -93,6 +117,7 @@ const PopularTracks = ({
diff --git a/src/renderer/pages/Playlist.tsx b/src/renderer/pages/Playlist.tsx index 100cba9..3671d32 100644 --- a/src/renderer/pages/Playlist.tsx +++ b/src/renderer/pages/Playlist.tsx @@ -215,11 +215,11 @@ const Playlist = () => { const handlePlay = useCallback( (trackID: number | null = null) => { - if (!playlist) { - toast('Failed to play playlist') + if (!playlist?.playlist?.id) { + toast('无法播放歌单') return } - player.playPlaylist(playlist.playlist, trackID) + player.playPlaylist(playlist.playlist.id, trackID) }, [playlist] ) diff --git a/src/renderer/pages/Search/Search.tsx b/src/renderer/pages/Search/Search.tsx index d06bbee..fd26d47 100644 --- a/src/renderer/pages/Search/Search.tsx +++ b/src/renderer/pages/Search/Search.tsx @@ -6,6 +6,7 @@ import { } from '@/api/search' import Cover from '@/components/Cover' import TrackGrid from '@/components/TracksGrid' +import { player } from '@/store' import { resizeImage } from '@/utils/common' const Artists = ({ artists }: { artists: Artist[] }) => { @@ -98,6 +99,33 @@ const Search = () => { () => search({ keywords, type: searchType }) ) + const handlePlayTracks = useCallback( + (trackID: number | null = null) => { + const tracks = searchResult?.result.song.songs + if (!tracks?.length) { + toast('无法播放歌单') + return + } + player.playAList( + tracks.map(t => t.id), + trackID + ) + }, + [searchResult?.result.song.songs] + ) + + const navigate = useNavigate() + const navigateBestMatch = (match: Artist | Album) => { + if ((match as Artist).albumSize !== undefined) { + navigate(`/artist/${match.id}`) + return + } + if ((match as Album).artist !== undefined) { + navigate(`/album/${match.id}`) + return + } + } + return (
@@ -111,6 +139,7 @@ const Search = () => {
{bestMatch.map(match => (
navigateBestMatch(match)} key={`${match.id}${match.picUrl}`} className='btn-hover-animation flex items-center p-3 after:rounded-xl after:bg-gray-100 dark:after:bg-white/10' > @@ -154,7 +183,11 @@ const Search = () => { {searchResult?.result.song.songs && (
歌曲
- +
)}
diff --git a/src/renderer/utils/player.ts b/src/renderer/utils/player.ts index 7119dc4..5486883 100644 --- a/src/renderer/utils/player.ts +++ b/src/renderer/utils/player.ts @@ -9,6 +9,8 @@ import { cacheAudio } from '@/api/yesplaymusic' import { clamp } from 'lodash-es' import axios from 'axios' import { resizeImage } from './common' +import { fetchPlaylistWithReactQuery } from '@/hooks/usePlaylist' +import { fetchAlbumWithReactQuery } from '@/hooks/useAlbum' type TrackID = number enum TrackListSourceType { @@ -114,7 +116,7 @@ export class Player { * Get/Set progress of current track */ get progress(): number { - return this._progress + return this.state === State.LOADING ? 0 : this._progress } set progress(value) { this._progress = value @@ -179,9 +181,10 @@ export class Player { * Play audio via howler */ private async _playAudio() { + this._progress = 0 const { audio, id } = await this._fetchAudioSource(this.trackID) if (!audio) { - toast('Failed to load audio source') + toast('无法播放此歌曲') return } Howler.unload() @@ -228,7 +231,7 @@ export class Player { const loadMoreTracks = async () => { if (this.fmTrackList.length <= 5) { const response = await fetchPersonalFMWithReactQuery() - this.fmTrackList.push(...(response?.data?.map(r => r.id) ?? {})) + this.fmTrackList.push(...(response?.data?.map(r => r.id) ?? [])) } } const prefetchNextTrack = async () => { @@ -291,6 +294,7 @@ export class Player { * Play previous track */ prevTrack() { + this._progress = 0 if (this.mode === Mode.FM) { toast('Personal FM not support previous track') return @@ -307,13 +311,14 @@ export class Player { * Play next track */ nextTrack(forceFM: boolean = false) { + this._progress = 0 if (forceFM || this.mode === Mode.FM) { this.mode = Mode.FM this._nextFMTrack() return } if (this._nextTrackIndex === undefined) { - toast('No next track') + toast('没有下一首了') this.pause() return } @@ -322,41 +327,54 @@ export class Player { } /** - * Play a playlist - * @param {Playlist} playlist - * @param {null|number=} autoPlayTrackID + * 播放一个track id列表 + * @param {number[]} list + * @param {null|number} autoPlayTrackID */ - async playPlaylist(playlist: Playlist, autoPlayTrackID?: null | number) { - if (!playlist?.trackIds?.length) return - this.trackListSource = { - type: TrackListSourceType.PLAYLIST, - id: playlist.id, - } + playAList(list: TrackID[], autoPlayTrackID?: null | number) { this.mode = Mode.PLAYLIST - this.trackList = playlist.trackIds.map(t => t.id) + this.trackList = list this._trackIndex = autoPlayTrackID - ? playlist.trackIds.findIndex(t => t.id === autoPlayTrackID) + ? list.findIndex(t => t === autoPlayTrackID) : 0 this._playTrack() } /** - * Play am album - * @param {Album} album + * Play a playlist + * @param {number} playlistID * @param {null|number=} autoPlayTrackID */ - async playAlbum(album: Album, autoPlayTrackID?: null | number) { + async playPlaylist(playlistID: number, autoPlayTrackID?: null | number) { + const playlist = await fetchPlaylistWithReactQuery({ id: playlistID }) + if (!playlist?.playlist?.trackIds?.length) return + this.trackListSource = { + type: TrackListSourceType.PLAYLIST, + id: playlistID, + } + this.playAList( + playlist.playlist.trackIds.map(t => t.id), + autoPlayTrackID + ) + } + + /** + * Play am album + * @param {number} albumID + * @param {null|number=} autoPlayTrackID + */ + async playAlbum(albumID: number, autoPlayTrackID?: null | number) { + const album = await fetchAlbumWithReactQuery({ id: albumID }) if (!album?.songs?.length) return this.trackListSource = { type: TrackListSourceType.ALBUM, - id: album.id, + id: albumID, } - this.mode = Mode.PLAYLIST - this.trackList = album.songs.map(t => t.id) - this._trackIndex = autoPlayTrackID - ? album.songs.findIndex(t => t.id === autoPlayTrackID) - : 0 this._playTrack() + this.playAList( + album.songs.map(t => t.id), + autoPlayTrackID + ) } /** @@ -380,7 +398,7 @@ export class Player { */ async initFM() { const response = await fetchPersonalFMWithReactQuery() - this.fmTrackList.push(...(response?.data?.map(r => r.id) ?? {})) + this.fmTrackList.push(...(response?.data?.map(r => r.id) ?? [])) const trackId = this.fmTrackList[0] const track = await this._fetchTrack(trackId) @@ -401,7 +419,7 @@ export class Player { */ async playTrack(trackID: TrackID) { const index = this.trackList.findIndex(t => t === trackID) - if (!index) toast('Failed to play: This track is not in the playlist') + if (!index) toast('播放失败,歌曲不在列表内') this._trackIndex = index this._playTrack() }