karusic/front/src/components/AudioPlayer.tsx

348 lines
9.8 KiB
TypeScript

import { SyntheticEvent, useEffect, useRef, useState } from 'react';
import {
MdFastForward,
MdFastRewind,
MdGraphicEq,
MdLooksOne,
MdNavigateBefore,
MdNavigateNext,
MdPause,
MdPlayArrow,
MdRepeat,
MdRepeatOne,
MdStop,
MdTrendingFlat,
} from 'react-icons/md';
import { useActivePlaylistService } from '@/service/ActivePlaylist';
import { useSpecificAlbum } from '@/service/Album';
import { useSpecificArtists } from '@/service/Artist';
import { useSpecificGender } from '@/service/Gender';
import { useSpecificTrack } from '@/service/Track';
import { DataUrlAccess } from '@/utils/data-url-access';
import { useColorThemeValue } from '@/theme/ThemeContext';
import { isNullOrUndefined } from '@/utils/validator';
import { Icon } from './Icon';
import { Flex, Text } from '@/ui';
export enum PlayMode {
PLAY_ONE,
PLAY_ALL,
PLAY_ONE_LOOP,
PLAY_ALL_LOOP,
}
const playModeIcon = {
[PlayMode.PLAY_ONE]: <MdLooksOne size="30px" />,
[PlayMode.PLAY_ALL]: <MdTrendingFlat size="30px" />,
[PlayMode.PLAY_ONE_LOOP]: <MdRepeatOne size="30px" />,
[PlayMode.PLAY_ALL_LOOP]: <MdRepeat size="30px" />,
};
export type AudioPlayerProps = {};
const formatTime = (time) => {
if (time && !isNaN(time)) {
const minutes = Math.floor(time / 60);
const formatMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
const seconds = Math.floor(time % 60);
const formatSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`;
return `${formatMinutes}:${formatSeconds}`;
}
return '00:00';
};
export const AudioPlayer = ({ }: AudioPlayerProps) => {
const { playTrackList, trackOffset, previous, next, first } =
useActivePlaylistService();
const audioRef = useRef<HTMLAudioElement>(null);
const [isPlaying, setIsPlaying] = useState<boolean>(false);
const [timeProgress, setTimeProgress] = useState<number>(0);
const [playingMode, setPlayingMode] = useState<PlayMode>(PlayMode.PLAY_ALL);
const [duration, setDuration] = useState<number>(0);
const { dataTrack } = useSpecificTrack(
trackOffset !== undefined ? playTrackList[trackOffset] : undefined
);
const { dataAlbum } = useSpecificAlbum(dataTrack?.albumId);
const { dataGender } = useSpecificGender(dataTrack?.genderId);
const { dataArtists } = useSpecificArtists(dataTrack?.artists);
const [mediaSource, setMediaSource] = useState<string>('');
useEffect(() => {
setMediaSource(
dataTrack && dataTrack?.dataId
? DataUrlAccess.getUrl(dataTrack?.dataId)
: ''
);
}, [dataTrack, setMediaSource]);
const backColor = useColorThemeValue('back.100', 'back.800');
const configButton = {
borderRadius: 'full',
background: '#00000000',
_hover: {
boxShadow: 'outline-over',
bgColor: 'brand.500',
},
width: "50px",
height: "50px",
padding: "5px",
};
useEffect(() => {
if (!audioRef || !audioRef.current) {
return;
}
if (isPlaying) {
audioRef.current.play();
} else {
audioRef.current.pause();
}
}, [isPlaying, audioRef]);
const onAudioEnded = () => {
if (playTrackList.length === 0 || isNullOrUndefined(trackOffset)) {
return;
}
if (playingMode === PlayMode.PLAY_ALL_LOOP) {
if (playTrackList.length == trackOffset + 1) {
first();
} else {
next();
}
} else if (playingMode === PlayMode.PLAY_ALL) {
next();
} else if (playingMode === PlayMode.PLAY_ONE_LOOP) {
onSeek(0);
onPlay();
}
};
const onSeek = (newValue) => {
console.log(`onSeek: ${newValue}`);
if (!audioRef || !audioRef.current) {
return;
}
audioRef.current.currentTime = newValue;
};
const onPlay = () => {
if (!audioRef || !audioRef.current) {
return;
}
if (isPlaying) {
audioRef.current.pause();
} else {
audioRef.current.play();
}
};
const onStop = () => {
if (!audioRef || !audioRef.current) {
return;
}
if (audioRef.current.currentTime == 0 && audioRef.current.paused) {
// TODO remove curent playing value
} else {
audioRef.current.pause();
audioRef.current.currentTime = 0;
}
};
const onNavigatePrevious = () => {
previous();
};
const onFastRewind = () => {
if (!audioRef || !audioRef.current) {
return;
}
audioRef.current.currentTime -= 10;
};
const onFastForward = () => {
if (!audioRef || !audioRef.current) {
return;
}
audioRef.current.currentTime += 10;
};
const onNavigateNext = () => {
next();
};
const onTypePlay = () => {
setPlayingMode((value: PlayMode) => {
if (value === PlayMode.PLAY_ONE) {
return PlayMode.PLAY_ALL;
} else if (value === PlayMode.PLAY_ALL) {
return PlayMode.PLAY_ONE_LOOP;
} else if (value === PlayMode.PLAY_ONE_LOOP) {
return PlayMode.PLAY_ALL_LOOP;
} else {
return PlayMode.PLAY_ONE;
}
});
};
/**
* Call when meta-data is updated
*/
function onChangeMetadata(): void {
const seconds = audioRef.current?.duration;
if (seconds !== undefined) {
setDuration(seconds);
}
}
const onTimeUpdate = () => {
if (!audioRef || !audioRef.current) {
return;
}
console.log(`onTimeUpdate ${audioRef.current.currentTime}`);
setTimeProgress(audioRef.current.currentTime);
};
const onDurationChange = (event) => { };
const onChangeStateToPlay = () => {
setIsPlaying(true);
};
const onChangeStateToPause = () => {
setIsPlaying(false);
};
return (
<>
{!isNullOrUndefined(trackOffset) && (
<Flex
style={{
position: "absolute",
height: "150px",
minHeight: "150px",
padding: "5px 10px 5px 10px",
margin: "0 15px 0 15px",
bottom: 0,
left: 0,
right: 0,
zIndex: 1000,
borderWidth: "1px",
borderColor: "brand.900",
background: backColor,
borderRadius: "10px 10px 0 0",
}}
direction="column"
>
<Text
fontSize="20px"
fontWeight="bold"
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
}}
// noOfLines={1}
>
{dataTrack?.name ?? '???'}
</Text>
<Text
fontSize="16px"
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
//noOfLines:1
}}
>
{dataArtists.map((data) => data.name).join(', ')} /{' '}
{dataAlbum && dataAlbum?.name}
{dataGender && ` / ${dataGender.name}`}
</Text>
<Flex style={{ width: "100%", padding: "0 15px 0 15px" }}>
<>TODO ... </>
{/* <Slider.Root
defaultValue={[0]}
value={[timeProgress]}
min={0}
max={duration}
step={0.1}
onChange={onSeek}
variant="outline"
// focusThumbOnChange={false}
>
<SliderTrack bg="gray.200" height="10px" borderRadius="full">
{/ * <SliderFilledTrack bg="brand.600" /> * /}
</SliderTrack>
</Slider.Root> */}
</Flex>
<Flex>
<Text
fontSize="16px"
style={{
alignContent: "left",
userSelect: "none",
marginRight: "auto",
overflow: "hidden",
// noOfLines={1},
}}
>
{formatTime(timeProgress)}
</Text>
<Text fontSize="16px" style={{ alignContent: "left", userSelect: "none" }}>
{formatTime(duration)}
</Text>
</Flex>
{/* <Flex gap="5px">
<IconButton
{...configButton}
aria-label={'Play'}
onClick={onPlay}
>
{isPlaying ? (
<MdPause size="30px" />
) : (
<MdPlayArrow size="30px" />
)}
</IconButton>
<IconButton
{...configButton}
aria-label={'Stop'}
onClick={onStop}
><MdStop size="30px" /></IconButton>
<IconButton
{...configButton}
aria-label={'Previous track'}
onClick={onNavigatePrevious}
marginLeft="auto"
><MdNavigateBefore size="30px" /> </IconButton>
<IconButton
{...configButton}
aria-label={'jump 15sec in past'}
onClick={onFastRewind}
><MdFastRewind size="30px" /></IconButton>
<IconButton
{...configButton}
aria-label={'jump 15sec in future'}
onClick={onFastForward}
><MdFastForward style={{ width: "100%", height: "100%" }} /></IconButton>
<IconButton
{...configButton}
aria-label={'Next track'}
marginRight="auto"
onClick={onNavigateNext}
><MdNavigateNext style={{ width: "100%", height: "100%" }} /></IconButton>
<IconButton
{...configButton}
aria-label={'continue to the end'}
onClick={onTypePlay}
>{playModeIcon[playingMode]}</IconButton>
</Flex> */}
</Flex>
)}
<audio
src={mediaSource}
ref={audioRef}
//preload={true}
onPlay={onChangeStateToPlay}
onPause={onChangeStateToPause}
onTimeUpdate={onTimeUpdate}
onDurationChange={onDurationChange}
onLoadedMetadata={onChangeMetadata}
autoPlay={true}
onEnded={onAudioEnded}
/>
</>
);
};