Compare commits

...

2 Commits

Author SHA1 Message Date
ba7b6e4755 [FEAT] Play all the playlist in background when phone is stop.
the explanation is the android does not support to update the GUI interface
when the application is down, but it support some update of the playing file
in the audio tag.
2025-03-23 21:01:54 +01:00
3beafab7e1 [FEAT] upgrade pependency in front lock file 2025-03-23 21:00:11 +01:00
4 changed files with 1698 additions and 1333 deletions

2684
front/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,7 @@ import { useSpecificGender } from '@/service/Gender';
import { useSpecificTrack } from '@/service/Track'; import { useSpecificTrack } from '@/service/Track';
import { DataUrlAccess } from '@/utils/data-url-access'; import { DataUrlAccess } from '@/utils/data-url-access';
import { isNullOrUndefined } from '@/utils/validator'; import { isNullOrUndefined } from '@/utils/validator';
import { usePageVisibility } from '@/utils/visibleook';
import { Slider } from './ui/slider'; import { Slider } from './ui/slider';
@ -89,6 +90,7 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
: '' : ''
); );
}, [dataTrack, setMediaSource]); }, [dataTrack, setMediaSource]);
const { isVisible } = usePageVisibility();
const backColor = useColorModeValue('back.100', 'back.800'); const backColor = useColorModeValue('back.100', 'back.800');
const configButton = { const configButton = {
borderRadius: 'full', borderRadius: 'full',
@ -201,7 +203,6 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
if (!audioRef || !audioRef.current) { if (!audioRef || !audioRef.current) {
return; return;
} }
console.log(`onTimeUpdate ${audioRef.current.currentTime}`);
setTimeProgress(audioRef.current.currentTime); setTimeProgress(audioRef.current.currentTime);
}; };
const onDurationChange = (event) => {}; const onDurationChange = (event) => {};
@ -221,150 +222,154 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
}; };
return ( return (
<> <>
{!isNullOrUndefined(trackOffset) && ( {isVisible && (
<Flex <>
position="absolute" {!isNullOrUndefined(trackOffset) && (
height="150px" <Flex
minHeight="150px" position="absolute"
paddingY="5px" height="150px"
paddingX="10px" minHeight="150px"
marginX="15px" paddingY="5px"
bottom={0} paddingX="10px"
//top="calc(100% - 150px)" marginX="15px"
left={0} bottom={0}
right={0} //top="calc(100% - 150px)"
zIndex={1000} left={0}
borderWidth="1px" right={0}
borderColor="brand.900" zIndex={1000}
bgColor={backColor} borderWidth="1px"
borderTopRadius="10px" borderColor="brand.900"
direction="column" bgColor={backColor}
> borderTopRadius="10px"
<Text direction="column"
alignContent="left"
fontSize="20px"
fontWeight="bold"
userSelect="none"
marginRight="auto"
overflow="hidden"
// noOfLines={1}
>
{dataTrack?.name ?? '???'}
</Text>
<Text
alignContent="left"
fontSize="16px"
userSelect="none"
marginRight="auto"
overflow="hidden"
// noOfLines={1}
>
{dataArtists.map((data) => data.name).join(', ')} /{' '}
{dataAlbum && dataAlbum?.name}
{dataGender && ` / ${dataGender.name}`}
</Text>
<Box width="full" paddingX="15px">
<Slider
defaultValue={[0]}
value={[timeProgress]}
min={0}
max={duration}
step={0.1}
onValueChange={(e) => onSeek(e.value)}
variant="outline"
colorPalette="brand"
marks={marks()}
//focusCapture={false}
> >
<SliderTrack <Text
bg="brand.200" alignContent="left"
height="10px" fontSize="20px"
borderRadius="full" fontWeight="bold"
></SliderTrack> userSelect="none"
</Slider> marginRight="auto"
</Box> overflow="hidden"
<Flex> // noOfLines={1}
<Text >
alignContent="left" {dataTrack?.name ?? '???'}
fontSize="16px" </Text>
userSelect="none" <Text
marginRight="auto" alignContent="left"
overflow="hidden" fontSize="16px"
// noOfLines={1} userSelect="none"
> marginRight="auto"
{formatTime(timeProgress)} overflow="hidden"
</Text> // noOfLines={1}
<Text alignContent="left" fontSize="16px" userSelect="none"> >
{formatTime(duration)} {dataArtists.map((data) => data.name).join(', ')} /{' '}
</Text> {dataAlbum && dataAlbum?.name}
</Flex> {dataGender && ` / ${dataGender.name}`}
<Flex gap="5px"> </Text>
<IconButton <Box width="full" paddingX="15px">
{...configButton} <Slider
aria-label={'Play'} defaultValue={[0]}
onClick={onPlay} value={[timeProgress]}
variant="ghost" min={0}
> max={duration}
{isPlaying ? ( step={0.1}
<MdPause style={{ width: '100%', height: '100%' }} /> onValueChange={(e) => onSeek(e.value)}
) : ( variant="outline"
<MdPlayArrow style={{ width: '100%', height: '100%' }} /> colorPalette="brand"
)} marks={marks()}
</IconButton> //focusCapture={false}
<IconButton >
{...configButton} <SliderTrack
aria-label={'Stop'} bg="brand.200"
onClick={onStop} height="10px"
variant="ghost" borderRadius="full"
> ></SliderTrack>
<MdStop style={{ width: '100%', height: '100%' }} /> </Slider>
</IconButton> </Box>
<IconButton <Flex>
{...configButton} <Text
aria-label={'Previous track'} alignContent="left"
onClick={onNavigatePrevious} fontSize="16px"
marginLeft="auto" userSelect="none"
variant="ghost" marginRight="auto"
> overflow="hidden"
<MdNavigateBefore // noOfLines={1}
style={{ width: '100%', height: '100%' }} >
/>{' '} {formatTime(timeProgress)}
</IconButton> </Text>
<IconButton <Text alignContent="left" fontSize="16px" userSelect="none">
{...configButton} {formatTime(duration)}
aria-label={'jump 15sec in past'} </Text>
onClick={onFastRewind} </Flex>
variant="ghost" <Flex gap="5px">
> <IconButton
<MdFastRewind style={{ width: '100%', height: '100%' }} /> {...configButton}
</IconButton> aria-label={'Play'}
<IconButton onClick={onPlay}
{...configButton} variant="ghost"
aria-label={'jump 15sec in future'} >
onClick={onFastForward} {isPlaying ? (
variant="ghost" <MdPause style={{ width: '100%', height: '100%' }} />
> ) : (
<MdFastForward style={{ width: '100%', height: '100%' }} /> <MdPlayArrow style={{ width: '100%', height: '100%' }} />
</IconButton> )}
<IconButton </IconButton>
{...configButton} <IconButton
aria-label={'Next track'} {...configButton}
marginRight="auto" aria-label={'Stop'}
onClick={onNavigateNext} onClick={onStop}
variant="ghost" variant="ghost"
> >
<MdNavigateNext style={{ width: '100%', height: '100%' }} /> <MdStop style={{ width: '100%', height: '100%' }} />
</IconButton> </IconButton>
<IconButton <IconButton
{...configButton} {...configButton}
aria-label={'continue to the end'} aria-label={'Previous track'}
onClick={onTypePlay} onClick={onNavigatePrevious}
variant="ghost" marginLeft="auto"
> variant="ghost"
{playModeIcon[playingMode]} >
</IconButton> <MdNavigateBefore
</Flex> style={{ width: '100%', height: '100%' }}
</Flex> />{' '}
</IconButton>
<IconButton
{...configButton}
aria-label={'jump 15sec in past'}
onClick={onFastRewind}
variant="ghost"
>
<MdFastRewind style={{ width: '100%', height: '100%' }} />
</IconButton>
<IconButton
{...configButton}
aria-label={'jump 15sec in future'}
onClick={onFastForward}
variant="ghost"
>
<MdFastForward style={{ width: '100%', height: '100%' }} />
</IconButton>
<IconButton
{...configButton}
aria-label={'Next track'}
marginRight="auto"
onClick={onNavigateNext}
variant="ghost"
>
<MdNavigateNext style={{ width: '100%', height: '100%' }} />
</IconButton>
<IconButton
{...configButton}
aria-label={'continue to the end'}
onClick={onTypePlay}
variant="ghost"
>
{playModeIcon[playingMode]}
</IconButton>
</Flex>
</Flex>
)}
</>
)} )}
<chakra.audio <chakra.audio

View File

@ -15,6 +15,7 @@ import { HomePage } from '@/scene/home/HomePage';
import { SSORoutes } from '@/scene/sso/SSORoutes'; import { SSORoutes } from '@/scene/sso/SSORoutes';
import { TrackRoutes } from '@/scene/track/TrackRoutes'; import { TrackRoutes } from '@/scene/track/TrackRoutes';
import { useHasRight } from '@/service/session'; import { useHasRight } from '@/service/session';
import { usePageVisibility } from '@/utils/visibleook';
import { AddPage } from './home/AddPage'; import { AddPage } from './home/AddPage';
import { SettingsPage } from './home/SettingsPage'; import { SettingsPage } from './home/SettingsPage';
@ -22,6 +23,7 @@ import { OnAirPage } from './onAir/OnAirPage';
export const AppRoutes = () => { export const AppRoutes = () => {
const { isReadable } = useHasRight('USER'); const { isReadable } = useHasRight('USER');
const { isVisible } = usePageVisibility();
return ( return (
<HistoryRouter <HistoryRouter
// @ts-expect-error // @ts-expect-error
@ -31,22 +33,27 @@ export const AppRoutes = () => {
<Routes> <Routes>
{/* Need to keep it in all case, it is the only way to log-in */} {/* Need to keep it in all case, it is the only way to log-in */}
<Route path="sso/*" element={<SSORoutes />} /> <Route path="sso/*" element={<SSORoutes />} />
{isReadable ? ( {/* Disable full display to prevent update of GUI when the application is hided */}
{isVisible && (
<> <>
<Route path="/" element={<Navigate to="home" replace />} /> {isReadable ? (
<Route path="home/*" element={<HomePage />} /> <>
<Route path="help/*" element={<HelpPage />} /> <Route path="/" element={<Navigate to="home" replace />} />
<Route path="settings/*" element={<SettingsPage />} /> <Route path="home/*" element={<HomePage />} />
<Route path="add/*" element={<AddPage />} /> <Route path="help/*" element={<HelpPage />} />
<Route path="on-air/*" element={<OnAirPage />} /> <Route path="settings/*" element={<SettingsPage />} />
<Route path="artist/*" element={<ArtistRoutes />} /> <Route path="add/*" element={<AddPage />} />
<Route path="album/*" element={<AlbumRoutes />} /> <Route path="on-air/*" element={<OnAirPage />} />
<Route path="gender/*" element={<GenderRoutes />} /> <Route path="artist/*" element={<ArtistRoutes />} />
<Route path="track/*" element={<TrackRoutes />} /> <Route path="album/*" element={<AlbumRoutes />} />
<Route path="*" element={<Error404 />} /> <Route path="gender/*" element={<GenderRoutes />} />
<Route path="track/*" element={<TrackRoutes />} />
<Route path="*" element={<Error404 />} />
</>
) : (
<Route path="*" element={<Error401 />} />
)}
</> </>
) : (
<Route path="*" element={<Error401 />} />
)} )}
</Routes> </Routes>
</HistoryRouter> </HistoryRouter>

View File

@ -0,0 +1,19 @@
import { useEffect, useState } from 'react';
export const usePageVisibility = () => {
const [isVisible, setIsVisible] = useState(!document.hidden);
useEffect(() => {
const handleVisibilityChange = () => {
setIsVisible(!document.hidden);
};
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, []);
return { isVisible };
};