feat: 实现私人FM (#1453)

* feat: 实现私人FM

* 根据建议修改

* fix: APP启动时FMCard无法加载数据

* fix: coverUrl使用useMemo

* fix: 在私人FM模式下禁用单曲循环

* fix: 私人FM模式下禁用部分按钮

* fix: 限制FMCard的歌手长度

* fix: 移除ArtistsInline的clamp参数,并将其作为隐式Fallback
This commit is contained in:
memorydream 2022-04-02 02:13:48 +08:00 committed by GitHub
parent e99c4833f7
commit 719a3a60d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 351 additions and 40 deletions

View file

@ -1,26 +1,81 @@
import { average } from 'color.js'
import { colord } from 'colord'
import { player } from '@/store'
import { resizeImage } from '@/utils/common'
import SvgIcon from '@/components/SvgIcon'
import ArtistInline from '@/components/ArtistsInline'
import { State as PlayerState, Mode as PlayerMode } from '@/utils/player'
enum ACTION {
DISLIKE = 'dislike',
PLAY = 'play',
NEXT = 'next',
const MediaControls = () => {
const classes =
'btn-pressed-animation btn-hover-animation mr-1 cursor-default rounded-lg p-1.5 transition duration-200 after:bg-white/10'
const playerSnapshot = useSnapshot(player)
const state = useMemo(() => playerSnapshot.state, [playerSnapshot.state])
const playOrPause = () => {
if (playerSnapshot.mode === PlayerMode.FM) {
player.playOrPause()
} else {
player.playFM()
}
}
return (
<div>
<button
key='dislike'
className={classes}
onClick={() => player.fmTrash()}
>
<SvgIcon name='dislike' className='h-6 w-6' />
</button>
<button key='play' className={classes} onClick={playOrPause}>
<SvgIcon
className='h-6 w-6'
name={
playerSnapshot.mode === PlayerMode.FM &&
[PlayerState.PLAYING, PlayerState.LOADING].includes(state)
? 'pause'
: 'play'
}
/>
</button>
<button
key='next'
className={classes}
onClick={() => player.nextTrack(true)}
>
<SvgIcon name='next' className='h-6 w-6' />
</button>
</div>
)
}
const FMCard = () => {
const coverUrl =
'https://p1.music.126.net/lEzPSOjusKaRXKXT3987lQ==/109951166035876388.jpg?param=512y512'
const [background, setBackground] = useState('')
const playerSnapshot = useSnapshot(player)
const track = useMemo(() => playerSnapshot.fmTrack, [playerSnapshot.fmTrack])
const coverUrl = useMemo(
() => resizeImage(playerSnapshot.fmTrack?.al?.picUrl ?? '', 'md'),
[playerSnapshot.fmTrack]
)
useEffect(() => {
average(coverUrl, { amount: 1, format: 'hex', sample: 1 }).then(color => {
const to = colord(color as string)
.darken(0.15)
.rotate(-5)
.toHex()
setBackground(`linear-gradient(to bottom right, ${color}, ${to})`)
})
if (coverUrl) {
average(coverUrl, { amount: 1, format: 'hex', sample: 1 }).then(color => {
const to = colord(color as string)
.darken(0.15)
.rotate(-5)
.toHex()
setBackground(`linear-gradient(to bottom right, ${color}, ${to})`)
})
} else {
setBackground(`linear-gradient(to bottom right, #66ccff, #ee0000)`)
}
}, [coverUrl])
return (
@ -28,28 +83,20 @@ const FMCard = () => {
className='relative flex h-[198px] overflow-hidden rounded-2xl p-4'
style={{ background }}
>
<img className='rounded-lg shadow-2xl' src={coverUrl} />
{coverUrl && <img className='rounded-lg shadow-2xl' src={coverUrl} />}
<div className='ml-5 flex w-full flex-col justify-between text-white'>
{/* Track info */}
<div>
<div className='text-xl font-semibold'>How Can I Make It OK?</div>
<div className='opacity-75'>Wolf Alice</div>
<div className='text-xl font-semibold'>{track?.name}</div>
<ArtistInline
className='line-clamp-2 opacity-75'
artists={track?.ar ?? []}
/>
</div>
<div className='-mb-1 flex items-center justify-between'>
{/* Actions */}
<div>
{Object.values(ACTION).map(action => (
<button
key={action}
className='btn-pressed-animation btn-hover-animation mr-1 cursor-default rounded-lg p-1.5 transition duration-200 after:bg-white/10'
>
<SvgIcon name={action} className='h-6 w-6' />
</button>
))}
</div>
<MediaControls />
{/* FM logo */}
<div className='right-4 bottom-5 flex text-white opacity-20'>
@ -62,4 +109,11 @@ const FMCard = () => {
)
}
/**
* player的构造函数中调用initFM
* APP启动时不会加载私人FM的数据
*
*/
player.initFM()
export default FMCard