[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.
This commit is contained in:
parent
3beafab7e1
commit
ba7b6e4755
@ -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
|
||||||
|
@ -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>
|
||||||
|
19
front/src/utils/visibleook.tsx
Normal file
19
front/src/utils/visibleook.tsx
Normal 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 };
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user