Create Lekte component to replace default player
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
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'
|
||||
|
||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL || '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, teks}) {
|
||||
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)
|
||||
|
||||
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()
|
||||
setPosition(0)
|
||||
audioRef.current = new Audio(audio)
|
||||
setIsPlaying(false)
|
||||
setVolume(100)
|
||||
}, [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={teks.tit}
|
||||
src={teks?.kouveti?.formats?.thumbnail ? `${apiUrl}${teks.kouveti.formats.thumbnail.url}` : '/logo-192x192.png'}
|
||||
width={teks?.kouveti?.formats?.thumbnail ? teks.kouveti.formats.thumbnail.width : 192}
|
||||
height={teks?.kouveti?.formats?.thumbnail ? teks.kouveti.formats.thumbnail.height : 192}
|
||||
/>
|
||||
</CoverImage>
|
||||
<Box sx={{ml: 1.5, minWidth: 0}}>
|
||||
<Typography fontWeight={500}>
|
||||
{teks.awtis.map(a => a.alias).join(', ')}
|
||||
</Typography>
|
||||
<Typography>
|
||||
<Link underline='hover' sx={{cursor: 'pointer'}} target='_blank' rel='noreferrer' href={url}>
|
||||
<strong>{teks.tit}</strong>
|
||||
</Link>
|
||||
</Typography>
|
||||
<Typography variant='caption'>
|
||||
<i>{teks.lanne}</i>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Slider
|
||||
aria-label='time-indicator'
|
||||
size='small'
|
||||
defaultValue={0}
|
||||
value={position}
|
||||
min={0}
|
||||
step={1}
|
||||
max={duration}
|
||||
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,
|
||||
teks: PropTypes.object.isRequired
|
||||
}
|
||||
Reference in New Issue
Block a user