YesPlayMusic/packages/web/components/New/TrackListHeader.tsx

199 lines
5.7 KiB
TypeScript
Raw Normal View History

2022-06-12 15:29:14 +08:00
import {
formatDate,
formatDuration,
isIOS,
isSafari,
resizeImage,
} from '@/web/utils/common'
2022-05-29 17:53:27 +08:00
import { css, cx } from '@emotion/css'
import Icon from '@/web/components/Icon'
import dayjs from 'dayjs'
import Image from './Image'
2022-06-09 20:16:05 +08:00
import useIsMobile from '@/web/hooks/useIsMobile'
2022-06-12 15:29:14 +08:00
import { memo, useEffect, useMemo, useRef } from 'react'
import Hls from 'hls.js'
import useVideoCover from '@/web/hooks/useVideoCover'
import { motion } from 'framer-motion'
import { ease } from '@/web/utils/const'
import { injectGlobal } from '@emotion/css'
injectGlobal`
.plyr__video-wrapper,
.plyr--video {
background-color: transparent !important;
}
`
const VideoCover = ({ source }: { source?: string }) => {
2022-06-14 23:23:34 +08:00
const ref = useRef<HTMLVideoElement>(null)
const hls = useRef<Hls>(new Hls())
2022-06-12 15:29:14 +08:00
2022-06-14 23:23:34 +08:00
useEffect(() => {
if (source && Hls.isSupported()) {
const video = document.querySelector('#video-cover') as HTMLVideoElement
hls.current.loadSource(source)
hls.current.attachMedia(video)
2022-06-12 15:29:14 +08:00
}
2022-06-14 23:23:34 +08:00
}, [source])
2022-06-12 15:29:14 +08:00
return (
<div className='z-10 aspect-square overflow-hidden rounded-24'>
2022-06-14 23:23:34 +08:00
<video id='video-cover' ref={ref} autoPlay muted loop />
2022-06-12 15:29:14 +08:00
</div>
)
}
const Cover = memo(
({ album, playlist }: { album?: Album; playlist?: Playlist }) => {
const isMobile = useIsMobile()
const { data: videoCover } = useVideoCover({
id: album?.id,
name: album?.name,
artist: album?.artist.name,
})
const cover = album?.picUrl || playlist?.coverImgUrl || ''
return (
<>
<div className='relative z-10 aspect-square w-full overflow-auto rounded-24 '>
<Image
className='absolute inset-0 h-full w-full'
src={resizeImage(cover, 'lg')}
/>
{videoCover && (
<motion.div
initial={{ opacity: isIOS ? 1 : 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.6, ease }}
className='absolute inset-0 h-full w-full'
>
{isSafari ? (
<video
src={videoCover}
className='h-full w-full'
autoPlay
loop
muted
playsInline
></video>
) : (
<VideoCover source={videoCover} />
)}
</motion.div>
)}
</div>
{/* Blur bg */}
{!isMobile && (
<img
className={cx(
'absolute z-0 object-cover opacity-70',
css`
top: -400px;
left: -370px;
width: 1572px;
height: 528px;
filter: blur(256px) saturate(1.2);
`
)}
src={resizeImage(cover, 'sm')}
/>
)}
</>
)
}
)
Cover.displayName = 'Cover'
2022-05-29 17:53:27 +08:00
const TrackListHeader = ({
album,
2022-06-06 01:00:25 +08:00
playlist,
2022-05-29 17:53:27 +08:00
onPlay,
}: {
album?: Album
2022-06-06 01:00:25 +08:00
playlist?: Playlist
2022-05-29 17:53:27 +08:00
onPlay: () => void
}) => {
2022-06-12 15:29:14 +08:00
const isMobile = useIsMobile()
2022-05-29 17:53:27 +08:00
const albumDuration = useMemo(() => {
const duration = album?.songs?.reduce((acc, cur) => acc + cur.dt, 0) || 0
return formatDuration(duration, 'en', 'hh[hr] mm[min]')
}, [album?.songs])
2022-06-06 01:00:25 +08:00
2022-05-29 17:53:27 +08:00
return (
<div
className={cx(
2022-06-12 15:29:14 +08:00
'z-10 mx-2.5 rounded-48 p-8 dark:bg-white/10',
2022-06-09 20:16:05 +08:00
'lg:mx-0 lg:grid lg:grid-rows-1 lg:gap-10 lg:rounded-none lg:p-0 lg:dark:bg-transparent',
!isMobile &&
css`
grid-template-columns: 318px auto;
`
2022-05-29 17:53:27 +08:00
)}
>
2022-06-12 15:29:14 +08:00
<Cover {...{ album, playlist }} />
2022-05-29 17:53:27 +08:00
2022-06-06 01:00:25 +08:00
<div className='flex flex-col justify-between'>
2022-05-29 17:53:27 +08:00
<div>
2022-06-09 20:16:05 +08:00
{/* Name */}
<div className='mt-2.5 text-28 font-semibold dark:text-night-50 lg:mt-0 lg:text-36 lg:font-medium'>
2022-06-06 01:00:25 +08:00
{album?.name || playlist?.name}
2022-05-29 17:53:27 +08:00
</div>
2022-06-09 20:16:05 +08:00
{/* Creator */}
<div className='mt-2.5 text-24 font-medium dark:text-night-400 lg:mt-6'>
2022-06-06 01:00:25 +08:00
{album?.artist.name || playlist?.creator.nickname}
2022-05-29 17:53:27 +08:00
</div>
2022-06-09 20:16:05 +08:00
{/* Extra info */}
<div className='mt-1 flex items-center text-12 font-medium dark:text-night-400 lg:text-14 lg:font-bold'>
{/* Album info */}
2022-06-06 01:00:25 +08:00
{!!album && (
<>
{album?.mark === 1056768 && (
2022-06-09 20:16:05 +08:00
<Icon
name='explicit'
className='mb-px mr-1 h-3 w-3 lg:h-3.5 lg:w-3.5 '
/>
2022-06-06 01:00:25 +08:00
)}{' '}
{dayjs(album?.publishTime || 0).year()} · {album?.songs.length}{' '}
2022-06-09 20:16:05 +08:00
tracks, {albumDuration}
2022-06-06 01:00:25 +08:00
</>
)}
2022-06-09 20:16:05 +08:00
{/* Playlist info */}
2022-06-06 01:00:25 +08:00
{!!playlist && (
<>
Updated at {formatDate(playlist?.updateTime || 0, 'en')} ·{' '}
2022-06-09 20:16:05 +08:00
{playlist.trackCount} tracks
2022-06-06 01:00:25 +08:00
</>
)}
2022-05-29 17:53:27 +08:00
</div>
2022-06-09 20:16:05 +08:00
{/* Description */}
{!isMobile && (
<div className='line-clamp-3 mt-6 whitespace-pre-wrap text-14 font-bold dark:text-night-400 '>
{album?.description || playlist?.description}
</div>
)}
2022-05-29 17:53:27 +08:00
</div>
2022-06-09 20:16:05 +08:00
{/* Actions */}
<div className='mt-11 flex items-end justify-between lg:z-10 lg:mt-4 lg:justify-start'>
<div className='flex items-end'>
<button className='mr-2.5 h-14 w-14 rounded-full dark:bg-white/10 lg:mr-6 lg:h-[72px] lg:w-[72px]'></button>
<button className='h-14 w-14 rounded-full dark:bg-white/10 lg:mr-6 lg:h-[72px] lg:w-[72px]'></button>
</div>
2022-05-29 17:53:27 +08:00
<button
2022-06-06 01:00:25 +08:00
onClick={() => onPlay()}
2022-06-09 20:16:05 +08:00
className='h-14 w-[125px] rounded-full dark:bg-brand-700 lg:h-[72px] lg:w-[170px]'
2022-05-29 17:53:27 +08:00
></button>
</div>
</div>
</div>
)
}
export default TrackListHeader