Prettify music player
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
05828d5947
commit
c1e1966977
@ -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>
|
||||||
|
|||||||
63
src/components/AudioPlayer/AudioPlayer.tsx
Normal file
63
src/components/AudioPlayer/AudioPlayer.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
1
src/components/AudioPlayer/index.ts
Normal file
1
src/components/AudioPlayer/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './AudioPlayer';
|
||||||
@ -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}>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user