diff --git a/packages/web/IpcRendererReact.tsx b/packages/web/IpcRendererReact.tsx index 4898513..ad101f6 100644 --- a/packages/web/IpcRendererReact.tsx +++ b/packages/web/IpcRendererReact.tsx @@ -5,15 +5,13 @@ import useUserLikedTracksIDs, { import { player } from '@/web/store' import useIpcRenderer from '@/web/hooks/useIpcRenderer' import { State as PlayerState } from '@/web/utils/player' -import { useEffect, useMemo, useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { useEffectOnce } from 'react-use' import { useSnapshot } from 'valtio' const IpcRendererReact = () => { const [isPlaying, setIsPlaying] = useState(false) - const playerSnapshot = useSnapshot(player) - const track = useMemo(() => playerSnapshot.track, [playerSnapshot.track]) - const state = useMemo(() => playerSnapshot.state, [playerSnapshot.state]) + const { track, state } = useSnapshot(player) const trackIDRef = useRef(0) // Liked songs ids @@ -51,7 +49,7 @@ const IpcRendererReact = () => { useEffectOnce(() => { // 用于显示 windows taskbar buttons - if (playerSnapshot.track?.id) { + if (track?.id) { window.ipcRenderer?.send(IpcChannels.Pause) } }) diff --git a/packages/web/components/FMCard.tsx b/packages/web/components/FMCard.tsx index 1878260..fc0bff8 100644 --- a/packages/web/components/FMCard.tsx +++ b/packages/web/components/FMCard.tsx @@ -60,8 +60,7 @@ const MediaControls = () => { const FMCard = () => { const navigate = useNavigate() - const playerSnapshot = useSnapshot(player) - const track = useMemo(() => playerSnapshot.fmTrack, [playerSnapshot.fmTrack]) + const { track } = useSnapshot(player) const coverUrl = useMemo( () => resizeImage(track?.al?.picUrl ?? '', 'md'), [track?.al?.picUrl] diff --git a/packages/web/components/New/ArtistRow.tsx b/packages/web/components/New/ArtistRow.tsx index d3a6c6e..7dbdcce 100644 --- a/packages/web/components/New/ArtistRow.tsx +++ b/packages/web/components/New/ArtistRow.tsx @@ -71,7 +71,7 @@ const ArtistRow = ({ minWidth: '96px', }} /> -
+
PLACE
diff --git a/packages/web/components/New/CoverWall.tsx b/packages/web/components/New/CoverWall.tsx index 25c5973..4a3032a 100644 --- a/packages/web/components/New/CoverWall.tsx +++ b/packages/web/components/New/CoverWall.tsx @@ -5,6 +5,23 @@ import useBreakpoint from '@/web/hooks/useBreakpoint' import { useNavigate } from 'react-router-dom' import { prefetchAlbum } from '@/web/api/hooks/useAlbum' +const sizes = { + small: { + sm: 'sm', + md: 'sm', + lg: 'sm', + xl: 'sm', + '2xl': 'md', + }, + large: { + sm: 'md', + md: 'md', + lg: 'md', + xl: 'md', + '2xl': 'lg', + }, +} as const + const CoverWall = ({ albums, }: { @@ -12,22 +29,6 @@ const CoverWall = ({ }) => { const navigate = useNavigate() const breakpoint = useBreakpoint() - const sizes = { - small: { - sm: 'sm', - md: 'sm', - lg: 'sm', - xl: 'sm', - '2xl': 'md', - }, - large: { - sm: 'md', - md: 'md', - lg: 'md', - xl: 'md', - '2xl': 'lg', - }, - } as const return (
) => void onMouseOver?: (e: React.MouseEvent) => void + animation?: boolean }) => { const [loaded, setLoaded] = useState(false) const [error, setError] = useState(false) const animate = useAnimation() - const placeholderAnimate = useAnimation() - const transition = { duration: 0.6, ease } - + const isMobile = useIsMobile() + const isAnimate = animation && !isMobile useEffect(() => setError(false), [src]) - const onload = async () => { + const onLoad = async () => { setLoaded(true) - animate.start({ opacity: 1 }) + if (isAnimate) animate.start({ opacity: 1 }) } const onError = () => { setError(true) } const hidden = error || !loaded + const transition = { duration: 0.6, ease } + const motionProps = isAnimate + ? { + animate, + initial: { opacity: 0 }, + transition, + } + : {} + const placeholderMotionProps = isAnimate + ? { + initial: { opacity: 1 }, + exit: { opacity: 0 }, + transition, + } + : {} return (
{/* Image */} {/* Placeholder / Error fallback */} {hidden && placeholder && ( )} diff --git a/packages/web/components/New/Layout.tsx b/packages/web/components/New/Layout.tsx index d562b65..26208ef 100644 --- a/packages/web/components/New/Layout.tsx +++ b/packages/web/components/New/Layout.tsx @@ -3,13 +3,12 @@ import Player from '@/web/components/New/Player' import MenuBar from '@/web/components/New/MenuBar' import Topbar from '@/web/components/New/Topbar/TopbarDesktop' import { css, cx } from '@emotion/css' -import { useMemo } from 'react' import { player } from '@/web/store' import { useSnapshot } from 'valtio' const Layout = () => { const playerSnapshot = useSnapshot(player) - const showPlayer = useMemo(() => !!playerSnapshot.track, [playerSnapshot]) + const showPlayer = !!playerSnapshot.track return (
{ const playerSnapshot = useSnapshot(player) - const showPlayer = useMemo(() => !!playerSnapshot.track, [playerSnapshot]) + const showPlayer = !!playerSnapshot.track return (
diff --git a/packages/web/components/New/NowPlaying.tsx b/packages/web/components/New/NowPlaying.tsx index fa6c1b8..5b90a49 100644 --- a/packages/web/components/New/NowPlaying.tsx +++ b/packages/web/components/New/NowPlaying.tsx @@ -10,12 +10,7 @@ import { animate, motion, useAnimation } from 'framer-motion' import { ease } from '@/web/utils/const' const Progress = () => { - const playerSnapshot = useSnapshot(player) - const track = useMemo(() => playerSnapshot.track, [playerSnapshot.track]) - const progress = useMemo( - () => playerSnapshot.progress, - [playerSnapshot.progress] - ) + const { track, progress } = useSnapshot(player) return (
@@ -85,10 +80,7 @@ const Cover = () => { } const NowPlaying = () => { - const playerSnapshot = useSnapshot(player) - - const state = useMemo(() => playerSnapshot.state, [playerSnapshot.state]) - const track = useMemo(() => playerSnapshot.track, [playerSnapshot.track]) + const { state, track } = useSnapshot(player) return (
{ - const playerSnapshot = useSnapshot(player) - const bgColor = useCoverColor(playerSnapshot.track?.al?.picUrl ?? '') + const { track, state } = useSnapshot(player) + const bgColor = useCoverColor(track?.al?.picUrl ?? '') const [locked, setLocked] = useState(false) useLockBodyScroll(locked) @@ -49,7 +49,7 @@ const PlayerMobile = () => {
Cover @@ -66,10 +66,10 @@ const PlayerMobile = () => { >
- {playerSnapshot.track?.name} + {track?.name}
- {playerSnapshot.track?.ar?.map(a => a.name).join(', ')} + {track?.ar?.map(a => a.name).join(', ')}
@@ -102,7 +102,7 @@ const PlayerMobile = () => { className='ml-2.5 flex items-center justify-center rounded-full bg-white/20 p-2.5' > diff --git a/packages/web/components/New/PlayingNext.tsx b/packages/web/components/New/PlayingNext.tsx index 0263a4d..8bdf070 100644 --- a/packages/web/components/New/PlayingNext.tsx +++ b/packages/web/components/New/PlayingNext.tsx @@ -1,5 +1,6 @@ import { resizeImage } from '@/web/utils/common' import { player } from '@/web/store' +import { State as PlayerState } from '@/web/utils/player' import { useSnapshot } from 'valtio' import useTracks from '@/web/api/hooks/useTracks' import { css, cx } from '@emotion/css' @@ -8,30 +9,96 @@ import Image from './Image' import Wave from './Wave' import Icon from '@/web/components/Icon' -const PlayingNext = ({ className }: { className?: string }) => { - const playerSnapshot = useSnapshot(player) - const { data: tracks } = useTracks({ ids: playerSnapshot.trackList }) - +const Header = () => { return ( - <> -
-
-
- PLAYING NEXT +
+
+
+ PLAYING NEXT +
+
+
+
-
-
- -
-
- -
+
+
+
+ ) +} + +const Track = ({ + track, + index, + playingTrackIndex, + state, +}: { + track?: Track + index: number + playingTrackIndex: number + state: PlayerState +}) => { + return ( + { + if (e.detail === 2 && track?.id) player.playTrack(track.id) + }} + > + {/* Cover */} + Cover + + {/* Track info */} +
+
+ {track?.name} +
+
+ {track?.ar.map(a => a.name).join(', ')} +
+
+ + {/* Wave icon */} + {playingTrackIndex === index ? ( + + ) : ( +
+ {String(index + 1).padStart(2, '0')} +
+ )} +
+ ) +} + +const PlayingNext = ({ className }: { className?: string }) => { + const { trackList, trackIndex, state } = useSnapshot(player) + const { data: tracks } = useTracks({ ids: trackList }) + return ( + <> +
{ {tracks?.songs?.map((track, index) => ( - { - if (e.detail === 2) player.playTrack(track.id) - }} - > - {/* Cover */} - Cover - - {/* Track info */} -
-
- {track.name} -
-
- {track.ar.map(a => a.name).join(', ')} -
-
- - {/* Wave icon */} - {playerSnapshot.trackIndex === index ? ( - - ) : ( -
- {String(index + 1).padStart(2, '0')} -
- )} -
+ track={track} + index={index} + playingTrackIndex={trackIndex} + state={state} + /> ))} {(tracks?.songs?.length || 0) >= 4 && ( diff --git a/packages/web/components/New/Topbar/SearchBox.tsx b/packages/web/components/New/Topbar/SearchBox.tsx index 60de76d..f56a8c8 100644 --- a/packages/web/components/New/Topbar/SearchBox.tsx +++ b/packages/web/components/New/Topbar/SearchBox.tsx @@ -1,13 +1,30 @@ import { css, cx } from '@emotion/css' import Icon from '../../Icon' +import { breakpoint as bp } from '@/web/utils/const' const SearchBox = () => { return ( -
+
) diff --git a/packages/web/components/New/Topbar/SettingsButton.tsx b/packages/web/components/New/Topbar/SettingsButton.tsx index 045e807..64684b0 100644 --- a/packages/web/components/New/Topbar/SettingsButton.tsx +++ b/packages/web/components/New/Topbar/SettingsButton.tsx @@ -1,8 +1,14 @@ import Icon from '@/web/components/Icon' +import { cx } from '@emotion/css' -const SettingsButton = () => { +const SettingsButton = ({ className }: { className?: string }) => { return ( - ) diff --git a/packages/web/components/New/Topbar/TopbarMobile.tsx b/packages/web/components/New/Topbar/TopbarMobile.tsx index 25be4bd..40f5f14 100644 --- a/packages/web/components/New/Topbar/TopbarMobile.tsx +++ b/packages/web/components/New/Topbar/TopbarMobile.tsx @@ -1,3 +1,4 @@ +import { css } from '@emotion/css' import Avatar from './Avatar' import SearchBox from './SearchBox' import SettingsButton from './SettingsButton' @@ -9,7 +10,7 @@ const TopbarMobile = () => {
-
+
diff --git a/packages/web/components/New/TrackList.tsx b/packages/web/components/New/TrackList.tsx index 8d8983c..a1c6e06 100644 --- a/packages/web/components/New/TrackList.tsx +++ b/packages/web/components/New/TrackList.tsx @@ -16,11 +16,7 @@ const TrackList = ({ onPlay: (id: number) => void className?: string }) => { - const playerSnapshot = useSnapshot(player) - const playingTrack = useMemo( - () => playerSnapshot.track, - [playerSnapshot.track] - ) + const { track: playingTrack, state } = useSnapshot(player) const isMobile = useIsMobile() const handleClick = (e: React.MouseEvent, trackID: number) => { @@ -31,10 +27,7 @@ const TrackList = ({ } } - const playing = useMemo( - () => playerSnapshot.state === 'playing', - [playerSnapshot.state] - ) + const playing = state === 'playing' return (
@@ -51,11 +44,11 @@ const TrackList = ({ {/* Track name */}
- {track.name} + {track.name} {playingTrack?.id === track.id && ( -
+ -
+ )}
diff --git a/packages/web/components/New/Wave.tsx b/packages/web/components/New/Wave.tsx index 87c78eb..70f13bd 100644 --- a/packages/web/components/New/Wave.tsx +++ b/packages/web/components/New/Wave.tsx @@ -1,18 +1,17 @@ import { css, cx, keyframes } from '@emotion/css' -const Wave = ({ playing }: { playing: boolean }) => { - const wave = keyframes` +const wave = keyframes` 0% { transform: scaleY(1) } 50% { transform: scaleY(0.2) } 100% { transform: scaleY(1)} - ` - const animation = css` - transform-origin: bottom; - animation: ${wave} 1s ease-in-out infinite; ` +const animation = css` + transform-origin: bottom; + animation: ${wave} 1s ease-in-out infinite; +` +const delay = ['-100ms', '-500ms', '-1200ms', '-1000ms', '-700ms'] - const delay = ['-100ms', '-500ms', '-1200ms', '-1000ms', '-700ms'] - +const Wave = ({ playing }: { playing: boolean }) => { return (
{[...new Array(5).keys()].map(i => ( diff --git a/packages/web/components/Player.tsx b/packages/web/components/Player.tsx index 75a7c3f..31f6a96 100644 --- a/packages/web/components/Player.tsx +++ b/packages/web/components/Player.tsx @@ -17,19 +17,13 @@ import { useNavigate } from 'react-router-dom' const PlayingTrack = () => { const navigate = useNavigate() - const snappedPlayer = useSnapshot(player) - const track = useMemo(() => snappedPlayer.track, [snappedPlayer.track]) - const trackListSource = useMemo( - () => snappedPlayer.trackListSource, - [snappedPlayer.trackListSource] - ) + const { track, trackListSource, mode } = useSnapshot(player) // Liked songs ids const { data: userLikedSongs } = useUserLikedTracksIDs() const mutationLikeATrack = useMutationLikeATrack() - const hasTrackListSource = - snappedPlayer.mode !== PlayerMode.FM && trackListSource?.type + const hasTrackListSource = mode !== PlayerMode.FM && trackListSource?.type const toAlbum = () => { const id = track?.al?.id diff --git a/packages/web/components/TracksAlbum.tsx b/packages/web/components/TracksAlbum.tsx index 5fb2f52..9616dcd 100644 --- a/packages/web/components/TracksAlbum.tsx +++ b/packages/web/components/TracksAlbum.tsx @@ -220,11 +220,7 @@ const TracksAlbum = ({ [onTrackDoubleClick] ) - const playerSnapshot = useSnapshot(player) - const playingTrack = useMemo( - () => playerSnapshot.track, - [playerSnapshot.track] - ) + const { track } = useSnapshot(player) return (
@@ -255,7 +251,7 @@ const TracksAlbum = ({ onClick={handleClick} isLiked={userLikedSongs?.ids?.includes(track.id) ?? false} isSkeleton={false} - isHighlight={track.id === playingTrack?.id} + isHighlight={track.id === track?.id} /> ))}
diff --git a/packages/web/pages/New/Discover.tsx b/packages/web/pages/New/Discover.tsx index a0756fe..40227de 100644 --- a/packages/web/pages/New/Discover.tsx +++ b/packages/web/pages/New/Discover.tsx @@ -102,7 +102,9 @@ const Discover = () => { return ( - +
+ +
) } diff --git a/packages/web/pages/New/My.tsx b/packages/web/pages/New/My.tsx index 52d8b52..b15e085 100644 --- a/packages/web/pages/New/My.tsx +++ b/packages/web/pages/New/My.tsx @@ -30,12 +30,38 @@ const tabs = [ }, ] -const My = () => { - const { data: artists } = useUserArtists() - const { data: playlists } = useUserPlaylists() +const Albums = () => { const { data: albums } = useUserAlbums() + + return +} + +const Playlists = () => { + const { data: playlists } = useUserPlaylists() + return ( + + ) +} + +const Collections = () => { + // const { data: artists } = useUserArtists() const [selectedTab, setSelectedTab] = useState(tabs[0].id) + return ( +
+ setSelectedTab(id)} + className='px-2.5 lg:px-0' + /> + {selectedTab === 'albums' && } + {selectedTab === 'playlists' && } +
+ ) +} + +const RecentlyListened = () => { const { data: listenedRecords } = useUserListenedRecords({ type: 'week' }) const recentListenedArtistsIDs = useMemo(() => { const artists: { @@ -62,32 +88,22 @@ const My = () => { }, [listenedRecords]) const { data: recentListenedArtists } = useArtists(recentListenedArtistsIDs) + return ( + a.artist)} + placeholderRow={1} + title='RECENTLY LISTENED' + /> + ) +} + +const My = () => { return (
- - a.artist)} - placeholderRow={1} - title='RECENTLY LISTENED' - /> - -
- setSelectedTab(id)} - className='px-2.5 lg:px-0' - /> - -
+ +
) diff --git a/packages/web/utils/const.ts b/packages/web/utils/const.ts index 8f1556f..aa24372 100644 --- a/packages/web/utils/const.ts +++ b/packages/web/utils/const.ts @@ -1 +1,8 @@ export const ease: [number, number, number, number] = [0.4, 0, 0.2, 1] +export const breakpoint = { + sm: '@media (min-width: 640px)', + md: '@media (min-width: 768px)', + lg: '@media (min-width: 1024px)', + xl: '@media (min-width: 1280px)', + '2xl': '@media (min-width: 1536px)', +} diff --git a/packages/web/utils/player.ts b/packages/web/utils/player.ts index 21b5dd0..35fa59c 100644 --- a/packages/web/utils/player.ts +++ b/packages/web/utils/player.ts @@ -72,6 +72,7 @@ export class Player { this.state = State.Ready this._playAudio(false) // just load the audio, not play this._initFM() + this._initMediaSession() window.ipcRenderer?.send(IpcChannels.Repeat, { mode: this._repeatMode }) } @@ -180,7 +181,7 @@ export class Player { _howler.pause() } - private _setupProgressInterval() { + private async _setupProgressInterval() { this._progressInterval = setInterval(() => { if (this.state === State.Playing) this._progress = _howler.seek() }, 1000) @@ -234,6 +235,7 @@ export class Player { } if (this.mode === Mode.TrackList) this._track = track if (this.mode === Mode.FM) this.fmTrack = track + this._updateMediaSessionMetaData() this._playAudio() } @@ -496,6 +498,45 @@ export class Player { this._trackIndex = index this._playTrack() } + + private async _initMediaSession() { + console.log('init') + if ('mediaSession' in navigator === false) return + navigator.mediaSession.setActionHandler('play', () => this.play()) + navigator.mediaSession.setActionHandler('pause', () => this.pause()) + navigator.mediaSession.setActionHandler('previoustrack', () => + this.prevTrack() + ) + navigator.mediaSession.setActionHandler('nexttrack', () => this.nextTrack()) + navigator.mediaSession.setActionHandler('seekto', event => { + if (event.seekTime) this.progress = event.seekTime + }) + } + + private async _updateMediaSessionMetaData() { + if ('mediaSession' in navigator === false || !this.track) return + const track = this.track + const metadata = { + title: track.name, + artist: track.ar.map(a => a.name).join(', '), + album: track.al.name, + artwork: [ + { + src: track.al.picUrl + '?param=256y256', + type: 'image/jpg', + sizes: '256x256', + }, + { + src: track.al.picUrl + '?param=512y512', + type: 'image/jpg', + sizes: '512x512', + }, + ], + length: this.progress, + trackId: track.id, + } + navigator.mediaSession.metadata = new window.MediaMetadata(metadata) + } } if (import.meta.env.DEV) {