) : (
diff --git a/src/renderer/components/FMCard.tsx b/src/renderer/components/FMCard.tsx
index a10b8cf..88d9c3d 100644
--- a/src/renderer/components/FMCard.tsx
+++ b/src/renderer/components/FMCard.tsx
@@ -61,22 +61,24 @@ const FMCard = () => {
const playerSnapshot = useSnapshot(player)
const track = useMemo(() => playerSnapshot.fmTrack, [playerSnapshot.fmTrack])
const coverUrl = useMemo(
- () => resizeImage(playerSnapshot.fmTrack?.al?.picUrl ?? '', 'md'),
- [playerSnapshot.fmTrack]
+ () => resizeImage(track?.al?.picUrl ?? '', 'md'),
+ [track?.al?.picUrl]
)
useEffect(() => {
- const cover = resizeImage(playerSnapshot.fmTrack?.al?.picUrl ?? '', 'xs')
+ const cover = resizeImage(track?.al?.picUrl ?? '', 'xs')
if (cover) {
average(cover, { amount: 1, format: 'hex', sample: 1 }).then(color => {
let c = colord(color as string)
- if (c.isLight()) c = c.darken(0.15)
- else if (c.isDark()) c = c.lighten(0.1)
+ const hsl = c.toHsl()
+ if (hsl.s > 50) c = colord({ ...hsl, s: 50 })
+ if (hsl.l > 50) c = colord({ ...c.toHsl(), l: 50 })
+ if (hsl.l < 30) c = colord({ ...c.toHsl(), l: 30 })
const to = c.darken(0.15).rotate(-5).toHex()
- setBackground(`linear-gradient(to bottom right, ${c.toHex()}, ${to})`)
+ setBackground(`linear-gradient(to bottom, ${c.toHex()}, ${to})`)
})
}
- }, [playerSnapshot.fmTrack?.al?.picUrl])
+ }, [track?.al?.picUrl])
return (
{
+ // const ease = [0.5, 0.2, 0.2, 0.8]
+ console.log('rendering')
+
+ const playerSnapshot = useSnapshot(player)
+ const track = useMemo(() => playerSnapshot.track, [playerSnapshot.track])
+ const { data: lyricRaw } = useLyric({ id: track?.id ?? 0 })
+
+ const lyric = useMemo(() => {
+ return lyricRaw && lyricParser(lyricRaw)
+ }, [lyricRaw])
+
+ const progress = playerSnapshot.progress + 0.3
+ const currentLine = useMemo(() => {
+ const index =
+ (lyric?.lyric.findIndex(({ time }) => time > progress) ?? 1) - 1
+ return {
+ index: index < 1 ? 0 : index,
+ time: lyric?.lyric?.[index]?.time ?? 0,
+ }
+ }, [lyric?.lyric, progress])
+
+ const displayLines = useMemo(() => {
+ const index = currentLine.index
+ const lines =
+ lyric?.lyric.slice(index === 0 ? 0 : index - 1, currentLine.index + 7) ??
+ []
+ if (index === 0) {
+ lines.unshift({
+ time: 0,
+ content: '',
+ rawTime: '[00:00:00]',
+ })
+ }
+ return lines
+ }, [currentLine.index, lyric?.lyric])
+
+ const variants = {
+ initial: { opacity: [0, 0.2], y: ['24%', 0] },
+ current: {
+ opacity: 1,
+ y: 0,
+ transition: {
+ ease: [0.5, 0.2, 0.2, 0.8],
+ duration: 0.7,
+ },
+ },
+ rest: (index: number) => ({
+ opacity: 0.2,
+ y: 0,
+ transition: {
+ delay: index * 0.04,
+ ease: [0.5, 0.2, 0.2, 0.8],
+ duration: 0.7,
+ },
+ }),
+ exit: {
+ opacity: 0,
+ y: -132,
+ height: 0,
+ paddingTop: 0,
+ paddingBottom: 0,
+ transition: {
+ duration: 0.7,
+ ease: [0.5, 0.2, 0.2, 0.8],
+ },
+ },
+ }
+
+ return (
+
+ {displayLines.map(({ content, time }, index) => {
+ return (
+
+ {content}
+
+ )
+ })}
+
+ )
+}
+
+export default Lyric
diff --git a/src/renderer/components/Lyric/Lyric2.tsx b/src/renderer/components/Lyric/Lyric2.tsx
new file mode 100644
index 0000000..53aef50
--- /dev/null
+++ b/src/renderer/components/Lyric/Lyric2.tsx
@@ -0,0 +1,98 @@
+import useLyric from '@/hooks/useLyric'
+import { player } from '@/store'
+import { motion, useMotionValue } from 'framer-motion'
+import { lyricParser } from '@/utils/lyric'
+import { useWindowSize } from 'react-use'
+
+const Lyric = ({ className }: { className?: string }) => {
+ // const ease = [0.5, 0.2, 0.2, 0.8]
+
+ const playerSnapshot = useSnapshot(player)
+ const track = useMemo(() => playerSnapshot.track, [playerSnapshot.track])
+ const { data: lyricRaw } = useLyric({ id: track?.id ?? 0 })
+
+ const lyric = useMemo(() => {
+ return lyricRaw && lyricParser(lyricRaw)
+ }, [lyricRaw])
+
+ const [progress, setProgress] = useState(0)
+ useEffect(() => {
+ const timer = setInterval(() => {
+ setProgress(player.howler.seek() + 0.3)
+ }, 300)
+ return () => clearInterval(timer)
+ }, [])
+ const currentIndex = useMemo(() => {
+ return (lyric?.lyric.findIndex(({ time }) => time > progress) ?? 1) - 1
+ }, [lyric?.lyric, progress])
+
+ const y = useMotionValue(1000)
+ const { height: windowHight } = useWindowSize()
+
+ useEffect(() => {
+ const top = (
+ document.getElementById('lyrics')?.children?.[currentIndex] as any
+ )?.offsetTop
+ if (top) {
+ y.set((windowHight / 9) * 4 - top)
+ }
+ }, [currentIndex, windowHight, y])
+
+ useEffect(() => {
+ y.set(0)
+ }, [track, y])
+
+ return (
+
+ {lyric?.lyric.map(({ content, time }, index) => {
+ return (
+ currentIndex && index < currentIndex + 8
+ ? 0.2
+ : 0,
+ transitionProperty:
+ index > currentIndex - 2 && index < currentIndex + 8
+ ? 'transform, opacity'
+ : 'none',
+ transitionTimingFunction:
+ index > currentIndex - 2 && index < currentIndex + 8
+ ? 'cubic-bezier(0.5, 0.2, 0.2, 0.8)'
+ : 'none',
+ transitionDelay: `${
+ index < currentIndex + 8 && index > currentIndex
+ ? 0.04 * (index - currentIndex)
+ : 0
+ }s`,
+ }}
+ >
+ {content}
+
+ )
+ })}
+
+ )
+}
+
+export default Lyric
diff --git a/src/renderer/components/Lyric/LyricPanel.tsx b/src/renderer/components/Lyric/LyricPanel.tsx
new file mode 100644
index 0000000..14038bf
--- /dev/null
+++ b/src/renderer/components/Lyric/LyricPanel.tsx
@@ -0,0 +1,82 @@
+import Player from './Player'
+import { player, state } from '@/store'
+import { resizeImage } from '@/utils/common'
+import { average } from 'color.js'
+import { colord } from 'colord'
+import IconButton from '../IconButton'
+import SvgIcon from '../SvgIcon'
+import Lyric from './Lyric'
+import { motion, AnimatePresence } from 'framer-motion'
+import Lyric2 from './Lyric2'
+
+const LyricPanel = () => {
+ const stateSnapshot = useSnapshot(state)
+ const playerSnapshot = useSnapshot(player)
+ const track = useMemo(() => playerSnapshot.track, [playerSnapshot.track])
+
+ const [bgColor, setBgColor] = useState({ from: '#222', to: '#222' })
+ useEffect(() => {
+ const cover = resizeImage(track?.al?.picUrl ?? '', 'xs')
+ if (cover) {
+ average(cover, { amount: 1, format: 'hex', sample: 1 }).then(color => {
+ let c = colord(color as string)
+ const hsl = c.toHsl()
+ if (hsl.s > 50) c = colord({ ...hsl, s: 50 })
+ if (hsl.l > 50) c = colord({ ...c.toHsl(), l: 50 })
+ if (hsl.l < 30) c = colord({ ...c.toHsl(), l: 30 })
+ const to = c.darken(0.15).rotate(-5).toHex()
+ setBgColor({
+ from: c.toHex(),
+ to,
+ })
+ })
+ }
+ }, [track?.al?.picUrl])
+
+ return (
+
+ {stateSnapshot.uiStates.showLyricPanel && (
+
+ {/* Drag area */}
+
+
+
+ {/* */}
+
+
+
+ (state.uiStates.showLyricPanel = false)}>
+
+
+
+
+ )}
+
+ )
+}
+
+export default LyricPanel
diff --git a/src/renderer/components/Lyric/Player.tsx b/src/renderer/components/Lyric/Player.tsx
new file mode 100644
index 0000000..cb30a67
--- /dev/null
+++ b/src/renderer/components/Lyric/Player.tsx
@@ -0,0 +1,149 @@
+import useUserLikedTracksIDs, {
+ useMutationLikeATrack,
+} from '@/hooks/useUserLikedTracksIDs'
+import { player, state } from '@/store'
+import { resizeImage } from '@/utils/common'
+
+import ArtistInline from '../ArtistsInline'
+import Cover from '../Cover'
+import IconButton from '../IconButton'
+import SvgIcon from '../SvgIcon'
+import { State as PlayerState, Mode as PlayerMode } from '@/utils/player'
+
+const PlayingTrack = () => {
+ const playerSnapshot = useSnapshot(player)
+ const track = useMemo(() => playerSnapshot.track, [playerSnapshot.track])
+ const navigate = useNavigate()
+
+ const toAlbum = () => {
+ const id = track?.al?.id
+ if (!id) return
+ navigate(`/album/${id}`)
+ state.uiStates.showLyricPanel = false
+ }
+
+ const trackListSource = useMemo(
+ () => playerSnapshot.trackListSource,
+ [playerSnapshot.trackListSource]
+ )
+ const toTrackListSource = () => {
+ if (!trackListSource?.type) return
+
+ navigate(`/${trackListSource.type}/${trackListSource.id}`)
+ state.uiStates.showLyricPanel = false
+ }
+
+ return (
+
+
+ {track?.name}
+
+
+
+
+ {' '}
+ -{' '}
+
+ {track?.al.name}
+
+
+
+
+ )
+}
+
+const LikeButton = ({ track }: { track: Track | undefined | null }) => {
+ const { data: userLikedSongs } = useUserLikedTracksIDs()
+ const mutationLikeATrack = useMutationLikeATrack()
+
+ return (
+
+ track?.id && mutationLikeATrack.mutate(track.id)}
+ >
+
+
+
+ )
+}
+
+const Controls = () => {
+ const playerSnapshot = useSnapshot(player)
+ const state = useMemo(() => playerSnapshot.state, [playerSnapshot.state])
+ const track = useMemo(() => playerSnapshot.track, [playerSnapshot.track])
+ const mode = useMemo(() => playerSnapshot.mode, [playerSnapshot.mode])
+
+ return (
+
+ {mode === PlayerMode.PLAYLIST && (
+ track && player.prevTrack()}
+ disabled={!track}
+ >
+
+
+ )}
+ {mode === PlayerMode.FM && (
+ player.fmTrash()}>
+
+
+ )}
+ track && player.playOrPause()}
+ disabled={!track}
+ className='after:rounded-xl'
+ >
+
+
+ track && player.nextTrack()} disabled={!track}>
+
+
+
+ )
+}
+
+const Player = ({ className }: { className?: string }) => {
+ const playerSnapshot = useSnapshot(player)
+ const track = useMemo(() => playerSnapshot.track, [playerSnapshot.track])
+
+ return (
+
+ )
+}
+
+export default Player
diff --git a/src/renderer/components/Lyric/index.ts b/src/renderer/components/Lyric/index.ts
new file mode 100644
index 0000000..565b8db
--- /dev/null
+++ b/src/renderer/components/Lyric/index.ts
@@ -0,0 +1,3 @@
+import LyricPanel from './LyricPanel'
+
+export default LyricPanel
diff --git a/src/renderer/components/Player.tsx b/src/renderer/components/Player.tsx
index f6f49e1..aed8f22 100644
--- a/src/renderer/components/Player.tsx
+++ b/src/renderer/components/Player.tsx
@@ -5,7 +5,7 @@ import SvgIcon from '@/components/SvgIcon'
import useUserLikedTracksIDs, {
useMutationLikeATrack,
} from '@/hooks/useUserLikedTracksIDs'
-import { player } from '@/store'
+import { player, state } from '@/store'
import { resizeImage } from '@/utils/common'
import { State as PlayerState, Mode as PlayerMode } from '@/utils/player'
@@ -152,7 +152,9 @@ const Others = () => {
toast('施工中...')}>
-
toast('施工中...')}>
+
+ {/* Lyric */}
+ (state.uiStates.showLyricPanel = true)}>
diff --git a/src/renderer/components/TitleBar.tsx b/src/renderer/components/TitleBar.tsx
index 9bb5035..2ae80db 100644
--- a/src/renderer/components/TitleBar.tsx
+++ b/src/renderer/components/TitleBar.tsx
@@ -2,16 +2,16 @@ import SvgIcon from './SvgIcon'
const TitleBar = () => {
return (
-