ExteraMusic/src/app/components/Player.tsx

180 lines
6.3 KiB
TypeScript

import { BottomNavigation, BottomNavigationAction, Box, Button, IconButton, Theme, Typography, useTheme } from "@mui/material";
import React, { useEffect, useMemo, useRef, useState } from "react";
import useQueue from "../hooks/useQueue";
import { getSongURL, Song } from "../utils/MusicServer";
import { Pause, PlayArrow, Repeat, RepeatOn, RepeatOne, RepeatOneOn, SkipNext, SkipPrevious } from "@mui/icons-material";
import durationToStr from "../utils/duration";
import { MotionBox } from "./MotionComponents";
import { Variants } from "framer-motion";
const variants = (theme: Theme): Variants => (
{
initial: {
bottom: theme.spacing(-12)
},
final: {
bottom: theme.spacing(0)
}
}
);
export default function Player() {
const theme = useTheme();
const queue = useQueue();
const audioRef = useRef<HTMLAudioElement>(null);
const [song, setSong] = useState<Song | undefined>();
const [paused, setPaused] = useState(audioRef.current?.paused || false);
const [canPlayNext, setCanPlayNext] = useState(false);
const [canPlayPrev, setCanPlayPrev] = useState(false);
const [repeatOne, setRepeatOne] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const songURL = useMemo(
() => getSongURL(song),
[song, queue]
);
const togglePlaying = () => {
const audio = audioRef.current;
if (!audio) return;
if (audio.paused) audio.play();
else audio.pause();
setPaused(audio.paused);
};
const handlePrev = () => {
queue?.prev();
};
const handleNext = () => {
if (repeatOne) {
const audio = audioRef.current;
if (audio) {
audio.currentTime = 0;
audio.play();
}
return;
}
queue?.next();
};
const handleRepeat = () => {
if (!queue) return console.error('No queue bruh');
console.log(`handleRepeat`, queue.repeat, repeatOne);
if (!queue.repeat && !repeatOne) {
queue.setRepeat(true);
} else if (queue.repeat && !repeatOne) {
queue.setRepeat(false);
setRepeatOne(true);
} else if (repeatOne) {
setRepeatOne(false);
}
};
const updateCurrentPlaying = () => {
const audio = audioRef.current;
if (!audio) return;
setCurrentTime(audio.currentTime);
};
useEffect(() => {
const intervalId = setInterval(() => {
updateCurrentPlaying();
}, 500);
return () => {
clearInterval(intervalId);
};
}, [audioRef]);
useEffect(() => {
setSong(queue?.getCurrent());
console.log(`Song updated. ${songURL}`, song);
console.log(`Queue updated.`, queue);
}, [queue!.i]);
useEffect(() => {
setSong(queue?.getCurrent());
console.log(`Song updated. ${songURL}`, song);
}, [queue!.songs]);
useEffect(() => {
setCanPlayNext(queue?.canPlayNext() || false);
setCanPlayPrev(queue?.canPlayPrev() || false);
}, [queue!.songs, queue!.i, queue!.repeat]);
return song && (
<MotionBox
display='flex'
position='absolute'
height={theme.spacing(12)}
width='100%'
maxWidth='100%'
bgcolor='background.paper'
borderColor='divider'
borderTop={0.2}
variants={variants(theme)}
initial='initial'
animate='final'
>
<Box width='100%' height={theme.spacing(6)} p={2}>
{song && songURL && (
<audio onEnded={handleNext} autoPlay src={songURL} ref={audioRef} controls={false} />
)}
<Box display='flex' flexGrow={1} gap={2}>
<img src={song?.thumbnails[0].url} width={64} />
<Box display='flex' flexDirection='column' flexGrow={1}>
<Typography variant='h6' component='div'>
{song?.name}
</Typography>
<Box display='flex' gap={1}>
<Typography color='textSecondary'>
{song?.artist.name}
</Typography>
{song?.album.name !== song?.name && (
<Typography color='textDisabled'>
{song?.album.name}
</Typography>
)}
</Box>
</Box>
<Typography
color='textDisabled'
component='div'
variant='button'
alignSelf='center'
>
{currentTime && durationToStr(currentTime)}
/
{currentTime && song && durationToStr(song.duration)}
</Typography>
<Box display='flex' height='min-content' alignSelf='center' gap={2} flexShrink={0}>
<IconButton
color={(queue!.repeat || repeatOne) ? 'success' : 'inherit'}
onClick={handleRepeat}
>
{queue!.repeat && (
<Repeat />
)}
{repeatOne && (
<RepeatOne />
)}
{!queue!.repeat && !repeatOne && (
<Repeat />
)}
</IconButton>
<IconButton disabled={!canPlayPrev} onClick={handlePrev}>
<SkipPrevious />
</IconButton>
<IconButton onClick={togglePlaying}>
{paused ? <PlayArrow /> : <Pause />}
</IconButton>
<IconButton disabled={!canPlayNext} onClick={handleNext}>
<SkipNext />
</IconButton>
</Box>
</Box>
</Box>
</MotionBox>
);
}