348 lines
9.8 KiB
TypeScript
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}
|
|
/>
|
|
</>
|
|
);
|
|
};
|