[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 { DataUrlAccess } from '@/utils/data-url-access';
|
||||
import { isNullOrUndefined } from '@/utils/validator';
|
||||
import { usePageVisibility } from '@/utils/visibleook';
|
||||
|
||||
import { Slider } from './ui/slider';
|
||||
|
||||
@ -89,6 +90,7 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
: ''
|
||||
);
|
||||
}, [dataTrack, setMediaSource]);
|
||||
const { isVisible } = usePageVisibility();
|
||||
const backColor = useColorModeValue('back.100', 'back.800');
|
||||
const configButton = {
|
||||
borderRadius: 'full',
|
||||
@ -201,7 +203,6 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
if (!audioRef || !audioRef.current) {
|
||||
return;
|
||||
}
|
||||
console.log(`onTimeUpdate ${audioRef.current.currentTime}`);
|
||||
setTimeProgress(audioRef.current.currentTime);
|
||||
};
|
||||
const onDurationChange = (event) => {};
|
||||
@ -221,150 +222,154 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{!isNullOrUndefined(trackOffset) && (
|
||||
<Flex
|
||||
position="absolute"
|
||||
height="150px"
|
||||
minHeight="150px"
|
||||
paddingY="5px"
|
||||
paddingX="10px"
|
||||
marginX="15px"
|
||||
bottom={0}
|
||||
//top="calc(100% - 150px)"
|
||||
left={0}
|
||||
right={0}
|
||||
zIndex={1000}
|
||||
borderWidth="1px"
|
||||
borderColor="brand.900"
|
||||
bgColor={backColor}
|
||||
borderTopRadius="10px"
|
||||
direction="column"
|
||||
>
|
||||
<Text
|
||||
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}
|
||||
{isVisible && (
|
||||
<>
|
||||
{!isNullOrUndefined(trackOffset) && (
|
||||
<Flex
|
||||
position="absolute"
|
||||
height="150px"
|
||||
minHeight="150px"
|
||||
paddingY="5px"
|
||||
paddingX="10px"
|
||||
marginX="15px"
|
||||
bottom={0}
|
||||
//top="calc(100% - 150px)"
|
||||
left={0}
|
||||
right={0}
|
||||
zIndex={1000}
|
||||
borderWidth="1px"
|
||||
borderColor="brand.900"
|
||||
bgColor={backColor}
|
||||
borderTopRadius="10px"
|
||||
direction="column"
|
||||
>
|
||||
<SliderTrack
|
||||
bg="brand.200"
|
||||
height="10px"
|
||||
borderRadius="full"
|
||||
></SliderTrack>
|
||||
</Slider>
|
||||
</Box>
|
||||
<Flex>
|
||||
<Text
|
||||
alignContent="left"
|
||||
fontSize="16px"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
// noOfLines={1}
|
||||
>
|
||||
{formatTime(timeProgress)}
|
||||
</Text>
|
||||
<Text alignContent="left" fontSize="16px" userSelect="none">
|
||||
{formatTime(duration)}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex gap="5px">
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Play'}
|
||||
onClick={onPlay}
|
||||
variant="ghost"
|
||||
>
|
||||
{isPlaying ? (
|
||||
<MdPause style={{ width: '100%', height: '100%' }} />
|
||||
) : (
|
||||
<MdPlayArrow style={{ width: '100%', height: '100%' }} />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Stop'}
|
||||
onClick={onStop}
|
||||
variant="ghost"
|
||||
>
|
||||
<MdStop style={{ width: '100%', height: '100%' }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Previous track'}
|
||||
onClick={onNavigatePrevious}
|
||||
marginLeft="auto"
|
||||
variant="ghost"
|
||||
>
|
||||
<MdNavigateBefore
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
/>{' '}
|
||||
</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>
|
||||
<Text
|
||||
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
|
||||
bg="brand.200"
|
||||
height="10px"
|
||||
borderRadius="full"
|
||||
></SliderTrack>
|
||||
</Slider>
|
||||
</Box>
|
||||
<Flex>
|
||||
<Text
|
||||
alignContent="left"
|
||||
fontSize="16px"
|
||||
userSelect="none"
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
// noOfLines={1}
|
||||
>
|
||||
{formatTime(timeProgress)}
|
||||
</Text>
|
||||
<Text alignContent="left" fontSize="16px" userSelect="none">
|
||||
{formatTime(duration)}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex gap="5px">
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Play'}
|
||||
onClick={onPlay}
|
||||
variant="ghost"
|
||||
>
|
||||
{isPlaying ? (
|
||||
<MdPause style={{ width: '100%', height: '100%' }} />
|
||||
) : (
|
||||
<MdPlayArrow style={{ width: '100%', height: '100%' }} />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Stop'}
|
||||
onClick={onStop}
|
||||
variant="ghost"
|
||||
>
|
||||
<MdStop style={{ width: '100%', height: '100%' }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Previous track'}
|
||||
onClick={onNavigatePrevious}
|
||||
marginLeft="auto"
|
||||
variant="ghost"
|
||||
>
|
||||
<MdNavigateBefore
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
/>{' '}
|
||||
</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
|
||||
|
@ -15,6 +15,7 @@ import { HomePage } from '@/scene/home/HomePage';
|
||||
import { SSORoutes } from '@/scene/sso/SSORoutes';
|
||||
import { TrackRoutes } from '@/scene/track/TrackRoutes';
|
||||
import { useHasRight } from '@/service/session';
|
||||
import { usePageVisibility } from '@/utils/visibleook';
|
||||
|
||||
import { AddPage } from './home/AddPage';
|
||||
import { SettingsPage } from './home/SettingsPage';
|
||||
@ -22,6 +23,7 @@ import { OnAirPage } from './onAir/OnAirPage';
|
||||
|
||||
export const AppRoutes = () => {
|
||||
const { isReadable } = useHasRight('USER');
|
||||
const { isVisible } = usePageVisibility();
|
||||
return (
|
||||
<HistoryRouter
|
||||
// @ts-expect-error
|
||||
@ -31,22 +33,27 @@ export const AppRoutes = () => {
|
||||
<Routes>
|
||||
{/* Need to keep it in all case, it is the only way to log-in */}
|
||||
<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 />} />
|
||||
<Route path="home/*" element={<HomePage />} />
|
||||
<Route path="help/*" element={<HelpPage />} />
|
||||
<Route path="settings/*" element={<SettingsPage />} />
|
||||
<Route path="add/*" element={<AddPage />} />
|
||||
<Route path="on-air/*" element={<OnAirPage />} />
|
||||
<Route path="artist/*" element={<ArtistRoutes />} />
|
||||
<Route path="album/*" element={<AlbumRoutes />} />
|
||||
<Route path="gender/*" element={<GenderRoutes />} />
|
||||
<Route path="track/*" element={<TrackRoutes />} />
|
||||
<Route path="*" element={<Error404 />} />
|
||||
{isReadable ? (
|
||||
<>
|
||||
<Route path="/" element={<Navigate to="home" replace />} />
|
||||
<Route path="home/*" element={<HomePage />} />
|
||||
<Route path="help/*" element={<HelpPage />} />
|
||||
<Route path="settings/*" element={<SettingsPage />} />
|
||||
<Route path="add/*" element={<AddPage />} />
|
||||
<Route path="on-air/*" element={<OnAirPage />} />
|
||||
<Route path="artist/*" element={<ArtistRoutes />} />
|
||||
<Route path="album/*" element={<AlbumRoutes />} />
|
||||
<Route path="gender/*" element={<GenderRoutes />} />
|
||||
<Route path="track/*" element={<TrackRoutes />} />
|
||||
<Route path="*" element={<Error404 />} />
|
||||
</>
|
||||
) : (
|
||||
<Route path="*" element={<Error401 />} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Route path="*" element={<Error401 />} />
|
||||
)}
|
||||
</Routes>
|
||||
</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