Prettify music player
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
apatil 2025-05-07 20:22:45 +01:00
parent 05828d5947
commit c1e1966977
4 changed files with 75 additions and 8 deletions

View File

@ -1,24 +1,27 @@
'use client'; 'use client';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { import {
Container, Typography, List, ListItem, ListItemText, Box, Button, Input, useMediaQuery Container, Typography, List, ListItem, ListItemText, Box, Button, Input
} from '@mui/material'; } from '@mui/material';
import UploadIcon from '@mui/icons-material/Upload'; import UploadIcon from '@mui/icons-material/Upload';
import { useTheme } from '@mui/material/styles'; import AudioPlayer from '@/components/AudioPlayer/AudioPlayer';
export default function Home() { export default function Home() {
const [tracks, setTracks] = useState<string[]>([]); const [tracks, setTracks] = useState<string[]>([]);
const [currentTrack, setCurrentTrack] = useState<string | null>(null); const [currentTrack, setCurrentTrack] = useState<string | null>(null);
const [uploading, setUploading] = useState(false); const [uploading, setUploading] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
useEffect(() => { useEffect(() => {
fetchTracks(); fetchTracks();
}, []); }, []);
const fetchTracks = async () => { const fetchTracks = async () => {
const res = await fetch('/api/tracks'); const res = await fetch('/api/tracks');
const data = await res.json(); const data = await res.json();
setTracks(data); setTracks(data);
}; };
const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => { const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files?.length) return; if (!e.target.files?.length) return;
const formData = new FormData(); const formData = new FormData();
@ -33,6 +36,7 @@ export default function Home() {
fetchTracks(); fetchTracks();
} }
}; };
return ( return (
<Container maxWidth="md" sx={{ paddingTop: 4 }}> <Container maxWidth="md" sx={{ paddingTop: 4 }}>
<Typography variant="h4" gutterBottom textAlign="center"> <Typography variant="h4" gutterBottom textAlign="center">
@ -44,7 +48,7 @@ export default function Home() {
id="upload-mp3" id="upload-mp3"
type="file" type="file"
sx={{ display: 'none' }} sx={{ display: 'none' }}
inputProps={{ accept: '.mp3' }} inputProps={{ accept: '.mp3, .m4a, audio/mpeg, audio/mp4, audio/x-m4a' }}
onChange={handleUpload} onChange={handleUpload}
/> />
<Button variant="contained" component="span" startIcon={<UploadIcon />} disabled={uploading}> <Button variant="contained" component="span" startIcon={<UploadIcon />} disabled={uploading}>
@ -61,8 +65,7 @@ export default function Home() {
</List> </List>
{currentTrack && ( {currentTrack && (
<Box textAlign="center" mt={3}> <Box textAlign="center" mt={3}>
<Typography variant="subtitle1" gutterBottom>{currentTrack}</Typography> <AudioPlayer title={`${currentTrack}`} src={`/api/music/${encodeURIComponent(currentTrack)}`} />
<audio controls autoPlay src={`/api/music/${encodeURIComponent(currentTrack)}`} style={{ width: isMobile ? '100%' : '60%' }} />
</Box> </Box>
)} )}
</Container> </Container>

View File

@ -0,0 +1,63 @@
'use client';
import { Box, Typography, IconButton, Slider, useTheme } from '@mui/material';
import { PlayArrow, Pause, VolumeUp } from '@mui/icons-material';
import { useRef, useState } from 'react';
export default function AudioPlayer({ src, title }: { src: string; title: string }) {
const audioRef = useRef<HTMLAudioElement | null>(null);
const [playing, setPlaying] = useState(false);
const [volume, setVolume] = useState(100);
const theme = useTheme();
const togglePlay = () => {
const audio = audioRef.current;
if (!audio) return;
if (playing) {
audio.pause();
} else {
audio.play();
}
setPlaying(!playing);
};
const handleVolumeChange = (event: Event, newValue: number | number[]) => {
const audio = audioRef.current;
if (!audio) return;
const vol = typeof newValue === 'number' ? newValue : newValue[0];
setVolume(vol);
audio.volume = vol / 100;
};
return (
<Box
sx={{
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
bgcolor: theme.palette.background.paper,
p: 2,
borderRadius: 2,
boxShadow: 3,
width: '100%',
maxWidth: 400,
mx: 'auto',
}}
>
<Typography variant="subtitle1" gutterBottom>{title}</Typography>
<Box display="flex" alignItems="center" gap={1}>
<IconButton onClick={togglePlay}>
{playing ? <Pause /> : <PlayArrow />}
</IconButton>
<VolumeUp />
<Slider
value={volume}
onChange={handleVolumeChange}
aria-label="Volume"
sx={{ width: 100 }}
/>
</Box>
<audio ref={audioRef} src={src} onEnded={() => setPlaying(false)} />
</Box>
);
}

View File

@ -0,0 +1 @@
export * from './AudioPlayer';

View File

@ -27,7 +27,7 @@ export default function Header() {
setMobileOpen(!mobileOpen) setMobileOpen(!mobileOpen)
} }
const drawer = ( const drawer = (
<Box onClick={handleDrawerToggle}> <Box onClick={handleDrawerToggle} component={'div'}>
<List sx={{ pt: '6rem' }}> <List sx={{ pt: '6rem' }}>
{navItems.map((item) => ( {navItems.map((item) => (
<ListItem key={item.label}> <ListItem key={item.label}>