270 lines
7.9 KiB
JavaScript
270 lines
7.9 KiB
JavaScript
import {useState, useEffect, useRef} from 'react'
|
|
import PropTypes from 'prop-types'
|
|
import {styled, useTheme} from '@mui/material/styles'
|
|
import Box from '@mui/material/Box'
|
|
import Typography from '@mui/material/Typography'
|
|
import Slider from '@mui/material/Slider'
|
|
import IconButton from '@mui/material/IconButton'
|
|
import Stack from '@mui/material/Stack'
|
|
import PauseRounded from '@mui/icons-material/PauseRounded'
|
|
import PlayArrowRounded from '@mui/icons-material/PlayArrowRounded'
|
|
import VolumeUpRounded from '@mui/icons-material/VolumeUpRounded'
|
|
import VolumeDownRounded from '@mui/icons-material/VolumeDownRounded'
|
|
import Image from 'next/image'
|
|
import {grey} from '@mui/material/colors'
|
|
import {Link} from '@mui/material'
|
|
|
|
import {getAlias} from '../../lib/utils/format'
|
|
|
|
const IMAGE_URL = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
|
|
|
|
const Widget = styled('div')(({theme}) => ({
|
|
padding: 16,
|
|
borderRadius: 16,
|
|
width: 343,
|
|
maxWidth: '100%',
|
|
margin: 'auto',
|
|
position: 'relative',
|
|
zIndex: 1,
|
|
backgroundColor:
|
|
theme.palette.mode === 'dark' ? 'rgba(0,0,0,0.6)' : grey[200],
|
|
backdropFilter: 'blur(40px)',
|
|
}))
|
|
|
|
const CoverImage = styled('div')({
|
|
width: 100,
|
|
height: 100,
|
|
objectFit: 'cover',
|
|
overflow: 'hidden',
|
|
flexShrink: 0,
|
|
borderRadius: 8,
|
|
backgroundColor: 'rgba(0,0,0,0.08)',
|
|
'& > img': {
|
|
width: '100%',
|
|
},
|
|
})
|
|
|
|
const TinyText = styled(Typography)({
|
|
fontSize: '0.75rem',
|
|
opacity: 0.38,
|
|
fontWeight: 500,
|
|
letterSpacing: 0.2,
|
|
})
|
|
|
|
export default function Lekte({audio, url, parole}) {
|
|
const audioRef = useRef(new Audio(audio))
|
|
const intervalRef = useRef()
|
|
const isReady = useRef(false)
|
|
const {duration} = audioRef.current
|
|
const theme = useTheme()
|
|
const [position, setPosition] = useState(0)
|
|
const [volume, setVolume] = useState(100)
|
|
const [isPlaying, setIsPlaying] = useState(false)
|
|
const alias = getAlias(parole.artistes, parole.prioriteArtistes)
|
|
|
|
function formatDuration(value) {
|
|
const minute = Math.floor(value / 60)
|
|
const secondLeft = value - (minute * 60)
|
|
if (Number.isNaN(minute) || Number.isNaN(secondLeft)) {
|
|
return 'Information indisponible'
|
|
}
|
|
|
|
return `${minute}:${secondLeft <= 9 ? `0${secondLeft}` : secondLeft}`
|
|
}
|
|
|
|
const mainIconColor = theme.palette.mode === 'dark' ? '#fff' : '#000'
|
|
const lightIconColor = theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.4)' : grey[900]
|
|
|
|
const startTimer = () => {
|
|
clearInterval(intervalRef.current)
|
|
intervalRef.current = setInterval(() => {
|
|
setPosition(Math.round(audioRef.current.currentTime))
|
|
}, [1000])
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (isReady.current) {
|
|
audioRef.current.play()
|
|
setIsPlaying(true)
|
|
startTimer()
|
|
} else {
|
|
isReady.current = true
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (isPlaying) {
|
|
audioRef.current.play()
|
|
} else {
|
|
audioRef.current.pause()
|
|
}
|
|
}, [isPlaying])
|
|
|
|
useEffect(() =>
|
|
() => {
|
|
audioRef.current.pause()
|
|
clearInterval(intervalRef.current)
|
|
}
|
|
, [])
|
|
|
|
useEffect(() => {
|
|
if (isPlaying) {
|
|
audioRef.current.play()
|
|
startTimer()
|
|
} else {
|
|
clearInterval(intervalRef.current)
|
|
audioRef.current.pause()
|
|
}
|
|
}, [isPlaying])
|
|
|
|
useEffect(() => {
|
|
audioRef.current.pause()
|
|
audioRef.current = new Audio(audio)
|
|
setIsPlaying(false)
|
|
setVolume(100)
|
|
setPosition(0)
|
|
}, [audio])
|
|
|
|
const handleChangePosition = value => {
|
|
setPosition(value)
|
|
audioRef.current.currentTime = value
|
|
}
|
|
|
|
const handleChangeVolume = value => {
|
|
setVolume(value)
|
|
audioRef.current.volume = value / 100
|
|
}
|
|
|
|
return (
|
|
<Box sx={{width: '80%', position: 'relative', zIndex: 0}}>
|
|
<Widget>
|
|
<Box sx={{display: 'flex', alignItems: 'center'}}>
|
|
<CoverImage>
|
|
<Image
|
|
alt={parole.titre}
|
|
src={parole?.couverture?.data?.attributes?.formats?.thumbnail ? `${IMAGE_URL}${parole.couverture.data.attributes.formats.thumbnail.url}` : '/logo-192x192.png'}
|
|
width={parole?.couverture?.data?.attributes?.formats?.thumbnail ? parole.couverture.data.attributes.formats.thumbnail.width : 192}
|
|
height={parole?.couverture?.data?.attributes?.formats?.thumbnail ? parole.couverture.data.attributes.formats.thumbnail.height : 192}
|
|
/>
|
|
</CoverImage>
|
|
<Box sx={{ml: 1.5, minWidth: 0}}>
|
|
<Typography fontWeight={500}>
|
|
{alias}
|
|
</Typography>
|
|
<Typography>
|
|
<Link underline='hover' sx={{cursor: 'pointer'}} target='_blank' rel='noreferrer' href={url}>
|
|
<strong>{parole.titre}</strong>
|
|
</Link>
|
|
</Typography>
|
|
<Typography variant='caption'>
|
|
<i>{parole.annee}</i>
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
<Slider
|
|
aria-label='time-indicator'
|
|
size='small'
|
|
defaultValue={position}
|
|
value={position || 0}
|
|
min={0}
|
|
step={1}
|
|
max={duration || 100}
|
|
sx={{
|
|
color: theme.palette.mode === 'dark' ? '#fff' : 'rgba(0,0,0,0.87)',
|
|
height: 4,
|
|
'& .MuiSlider-thumb': {
|
|
width: 8,
|
|
height: 8,
|
|
transition: '0.3s cubic-bezier(.47,1.64,.41,.8)',
|
|
'&:before': {
|
|
boxShadow: '0 2px 12px 0 rgba(0,0,0,0.4)',
|
|
},
|
|
'&:hover, &.Mui-focusVisible': {
|
|
boxShadow: `0px 0px 0px 8px ${
|
|
theme.palette.mode === 'dark'
|
|
? 'rgb(255 255 255 / 16%)'
|
|
: 'rgb(0 0 0 / 16%)'
|
|
}`,
|
|
},
|
|
'&.Mui-active': {
|
|
width: 20,
|
|
height: 20,
|
|
},
|
|
},
|
|
'& .MuiSlider-rail': {
|
|
opacity: 0.28,
|
|
},
|
|
}}
|
|
onChange={(_, value) => handleChangePosition(value)}
|
|
/>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
mt: -2,
|
|
}}
|
|
>
|
|
<TinyText>{formatDuration(position)}</TinyText>
|
|
<TinyText>-{formatDuration(Math.round(duration) - position)}</TinyText>
|
|
</Box>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
mt: -1,
|
|
}}
|
|
>
|
|
<IconButton
|
|
aria-label={isPlaying ? 'pause' : 'play'}
|
|
onClick={() => setIsPlaying(!isPlaying)}
|
|
>
|
|
{isPlaying ? (
|
|
<PauseRounded sx={{fontSize: '3rem'}} htmlColor={mainIconColor} />
|
|
) : (
|
|
<PlayArrowRounded
|
|
sx={{fontSize: '3rem'}}
|
|
htmlColor={mainIconColor}
|
|
/>
|
|
)}
|
|
</IconButton>
|
|
</Box>
|
|
<Stack spacing={2} direction='row' sx={{mb: 1, px: 1}} alignItems='center'>
|
|
<VolumeDownRounded htmlColor={lightIconColor} />
|
|
<Slider
|
|
aria-label='Volume'
|
|
defaultValue={volume}
|
|
value={volume}
|
|
sx={{
|
|
color: theme.palette.mode === 'dark' ? '#fff' : 'rgba(0,0,0,0.87)',
|
|
'& .MuiSlider-track': {
|
|
border: 'none',
|
|
},
|
|
'& .MuiSlider-thumb': {
|
|
width: 24,
|
|
height: 24,
|
|
backgroundColor: '#fff',
|
|
'&:before': {
|
|
boxShadow: '0 4px 8px rgba(0,0,0,0.4)',
|
|
},
|
|
'&:hover, &.Mui-focusVisible, &.Mui-active': {
|
|
boxShadow: 'none',
|
|
},
|
|
},
|
|
}}
|
|
onChange={(_, value) => handleChangeVolume(value)}
|
|
/>
|
|
<VolumeUpRounded htmlColor={lightIconColor} />
|
|
</Stack>
|
|
</Widget>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
Lekte.propTypes = {
|
|
audio: PropTypes.string.isRequired,
|
|
url: PropTypes.string.isRequired,
|
|
parole: PropTypes.object.isRequired
|
|
}
|