fix: improve karaoke modal

This commit is contained in:
2026-06-18 00:08:24 +04:00
parent b12952f85e
commit 0c339e7201
+92 -18
View File
@@ -1,16 +1,22 @@
'use client' 'use client'
import {useState, forwardRef} from 'react' import {useState, useEffect, forwardRef} from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import Fab from '@mui/material/Fab' import Fab from '@mui/material/Fab'
import Dialog from '@mui/material/Dialog' import Dialog from '@mui/material/Dialog'
import Slide from '@mui/material/Slide' import Slide from '@mui/material/Slide'
import Box from '@mui/material/Box' import Box from '@mui/material/Box'
import Skeleton from '@mui/material/Skeleton'
import IconButton from '@mui/material/IconButton' import IconButton from '@mui/material/IconButton'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import Tooltip from '@mui/material/Tooltip' import Tooltip from '@mui/material/Tooltip'
import useMediaQuery from '@mui/material/useMediaQuery' import useMediaQuery from '@mui/material/useMediaQuery'
import {useTheme} from '@mui/material/styles' import {useTheme, keyframes} from '@mui/material/styles'
const pulse = keyframes`
0%, 100% { opacity: 0.18; transform: scale(1); }
50% { opacity: 0.55; transform: scale(1.2); }
`
import MicIcon from '@mui/icons-material/Mic' import MicIcon from '@mui/icons-material/Mic'
import CloseIcon from '@mui/icons-material/Close' import CloseIcon from '@mui/icons-material/Close'
@@ -33,9 +39,14 @@ function toEmbedUrl(url) {
export default function KaraokeModal({url, titre, artistes}) { export default function KaraokeModal({url, titre, artistes}) {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [loaded, setLoaded] = useState(false)
const theme = useTheme() const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('sm')) const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
useEffect(() => {
if (!open) setLoaded(false)
}, [open])
return ( return (
<> <>
<Tooltip title='Karaoké' placement='left'> <Tooltip title='Karaoké' placement='left'>
@@ -58,12 +69,16 @@ export default function KaraokeModal({url, titre, artistes}) {
<Dialog <Dialog
open={open} open={open}
onClose={() => setOpen(false)} onClose={() => setOpen(false)}
TransitionComponent={SlideUp} slots={{transition: SlideUp}}
maxWidth={false} maxWidth={false}
slotProps={{ sx={{
backdrop: {sx: {backdropFilter: 'blur(6px)', bgcolor: 'rgba(0,0,0,0.85)'}} '& .MuiDialog-container': {
alignItems: {xs: 'flex-end', sm: 'center'},
}
}} }}
PaperProps={{ slotProps={{
backdrop: {sx: {backdropFilter: 'blur(6px)', bgcolor: 'rgba(0,0,0,0.85)'}},
paper: {
sx: { sx: {
bgcolor: '#000', bgcolor: '#000',
overflow: 'hidden', overflow: 'hidden',
@@ -74,39 +89,43 @@ export default function KaraokeModal({url, titre, artistes}) {
maxHeight: 'none', maxHeight: 'none',
m: 0, m: 0,
borderRadius: 0, borderRadius: 0,
boxShadow: 'none',
} : { } : {
width: 'calc(88vh * 9 / 16)', width: 'calc(88vh * 9 / 16)',
height: '88vh', height: '88vh',
maxWidth: 'none', maxWidth: 'none',
maxHeight: 'none', maxHeight: 'none',
borderRadius: 3, borderRadius: '16px',
boxShadow: '0 24px 64px rgba(0,0,0,0.7)',
}) })
} }
}
}} }}
> >
{/* Header superposé */} {/* Header superposé en gradient */}
<Box sx={{ <Box sx={{
position: 'absolute', position: 'absolute',
top: 0, top: 0,
left: 0, left: 0,
right: 0, right: 0,
zIndex: 1, zIndex: 2,
px: 2, px: 2,
pt: {xs: 'env(safe-area-inset-top, 12px)', sm: '12px'}, pt: 2,
pb: 3, pb: 4,
background: 'linear-gradient(to bottom, rgba(0,0,0,0.75) 0%, transparent 100%)', background: 'linear-gradient(to bottom, rgba(0,0,0,0.8) 0%, transparent 100%)',
display: 'flex', display: 'flex',
alignItems: 'flex-start', alignItems: 'flex-start',
justifyContent: 'space-between', justifyContent: 'space-between',
pointerEvents: 'none',
}}> }}>
<Box> <Box sx={{pointerEvents: 'none'}}>
{titre && ( {titre && (
<Typography variant='subtitle1' sx={{color: '#fff', fontWeight: 700, lineHeight: 1.2}}> <Typography variant='subtitle1' sx={{color: '#fff', fontWeight: 700, lineHeight: 1.2, textShadow: '0 1px 4px rgba(0,0,0,0.5)'}}>
{titre} {titre}
</Typography> </Typography>
)} )}
{artistes?.length > 0 && ( {artistes?.length > 0 && (
<Typography variant='caption' sx={{color: 'rgba(255,255,255,0.7)'}}> <Typography variant='caption' sx={{color: 'rgba(255,255,255,0.65)', textShadow: '0 1px 4px rgba(0,0,0,0.5)'}}>
{artistes.map(a => a.alias).join(', ')} {artistes.map(a => a.alias).join(', ')}
</Typography> </Typography>
)} )}
@@ -116,10 +135,11 @@ export default function KaraokeModal({url, titre, artistes}) {
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
aria-label='fermer' aria-label='fermer'
sx={{ sx={{
pointerEvents: 'auto',
color: '#fff', color: '#fff',
bgcolor: 'rgba(255,255,255,0.15)', bgcolor: 'rgba(255,255,255,0.15)',
backdropFilter: 'blur(4px)', backdropFilter: 'blur(8px)',
mt: 0.5, border: '1px solid rgba(255,255,255,0.12)',
'&:hover': {bgcolor: 'rgba(255,255,255,0.25)'}, '&:hover': {bgcolor: 'rgba(255,255,255,0.25)'},
}} }}
> >
@@ -128,14 +148,68 @@ export default function KaraokeModal({url, titre, artistes}) {
</Box> </Box>
{open && ( {open && (
<Box sx={{position: 'absolute', inset: 0, overflow: 'hidden'}}>
{/* Skeleton affiché pendant le chargement */}
{!loaded && (
<>
<Skeleton
variant='rectangular'
animation='wave'
sx={{position: 'absolute', inset: 0, bgcolor: 'rgba(255,255,255,0.06)', transform: 'none'}}
/>
<Box sx={{
position: 'absolute',
inset: 0,
zIndex: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 1.5,
}}>
<MicIcon sx={{fontSize: 44, color: 'rgba(255,255,255,0.55)', animation: `${pulse} 1.6s ease-in-out infinite`}} />
<Typography
variant='caption'
sx={{color: 'rgba(255,255,255,0.35)', letterSpacing: '0.15em', textTransform: 'uppercase'}}
>
Chargement
</Typography>
</Box>
</>
)}
{/*
Le player PeerTube impose un ratio 16:9 en interne.
On étire l'iframe en 16:9 sur toute la hauteur du conteneur portrait,
on le centre horizontalement, et overflow:hidden croppe les côtés.
Le centre visible correspond exactement à la zone vidéo verticale.
*/}
<Box sx={{
position: 'absolute',
top: 0,
left: '50%',
transform: 'translateX(-50%)',
height: '100%',
aspectRatio: '16 / 9',
}}>
<iframe <iframe
src={toEmbedUrl(url)} src={toEmbedUrl(url)}
style={{width: '100%', height: '100%', border: 'none', display: 'block'}} onLoad={() => setLoaded(true)}
style={{
width: '100%',
height: '100%',
border: 'none',
display: 'block',
opacity: loaded ? 1 : 0,
transition: 'opacity 0.4s ease',
}}
allowFullScreen allowFullScreen
allow='autoplay; fullscreen' allow='autoplay; fullscreen'
sandbox='allow-same-origin allow-scripts allow-popups allow-forms' sandbox='allow-same-origin allow-scripts allow-popups allow-forms'
title='Karaoké' title='Karaoké'
/> />
</Box>
</Box>
)} )}
</Dialog> </Dialog>
</> </>