fix: Resolve scrolling and audio playback issues

- Fix scrolling on Music Storage page by adding proper overflow handling
- Add height constraints and flex layout for better tab panel scrolling
- Update streaming endpoint to use presigned URLs instead of direct URLs
- Improve audio error handling with better error messages
- Update MusicPlayer component with dark theme styling
- Add loading indicators and error states for better UX
- Fix audio playback for files synced from S3 subdirectories

The Music Storage page now has proper scrolling behavior and
audio playback should work correctly for all music files.
This commit is contained in:
Geert Rademakes 2025-08-06 15:05:33 +02:00
parent 3317a69004
commit 1bb1f7d0d5
3 changed files with 52 additions and 22 deletions

View File

@ -235,10 +235,11 @@ router.get('/:id/stream', async (req, res) => {
return res.status(404).json({ error: 'Music file not found' }); return res.status(404).json({ error: 'Music file not found' });
} }
const streamingUrl = await s3Service.getStreamingUrl(musicFile.s3Key); // Use presigned URL for secure access instead of direct URL
const presignedUrl = await s3Service.getPresignedUrl(musicFile.s3Key, 3600); // 1 hour expiry
res.json({ res.json({
streamingUrl, streamingUrl: presignedUrl,
musicFile, musicFile,
}); });
} catch (error) { } catch (error) {

View File

@ -185,7 +185,7 @@ export const MusicPlayer: React.FC<MusicPlayerProps> = ({
} }
return ( return (
<VStack spacing={4} align="stretch" w="full" p={4} bg="gray.50" borderRadius="lg"> <VStack spacing={4} align="stretch" w="full" p={4} bg="gray.800" borderRadius="lg" borderColor="gray.700" borderWidth="1px">
{/* Audio element */} {/* Audio element */}
<audio <audio
ref={audioRef} ref={audioRef}
@ -197,21 +197,23 @@ export const MusicPlayer: React.FC<MusicPlayerProps> = ({
console.error('Audio error:', e); console.error('Audio error:', e);
toast({ toast({
title: 'Playback Error', title: 'Playback Error',
description: 'Failed to play audio file', description: 'Failed to play audio file. The file may be corrupted or the streaming URL may have expired.',
status: 'error', status: 'error',
duration: 3000, duration: 5000,
isClosable: true, isClosable: true,
}); });
}} }}
onLoadStart={() => setIsLoading(true)}
onCanPlay={() => setIsLoading(false)}
/> />
{/* Track info */} {/* Track info */}
<VStack spacing={1} align="center"> <VStack spacing={1} align="center">
<Text fontWeight="bold" fontSize="lg" noOfLines={1}> <Text fontWeight="bold" fontSize="lg" noOfLines={1} color="white">
{musicFile.title || musicFile.originalName} {musicFile.title || musicFile.originalName}
</Text> </Text>
{musicFile.artist && ( {musicFile.artist && (
<Text fontSize="sm" color="gray.600" noOfLines={1}> <Text fontSize="sm" color="gray.400" noOfLines={1}>
{musicFile.artist} {musicFile.artist}
</Text> </Text>
)} )}
@ -230,13 +232,14 @@ export const MusicPlayer: React.FC<MusicPlayerProps> = ({
onChange={handleSeek} onChange={handleSeek}
isDisabled={isLoading} isDisabled={isLoading}
size="sm" size="sm"
colorScheme="blue"
> >
<SliderTrack> <SliderTrack bg="gray.700">
<SliderFilledTrack /> <SliderFilledTrack bg="blue.400" />
</SliderTrack> </SliderTrack>
<SliderThumb /> <SliderThumb bg="blue.400" />
</Slider> </Slider>
<HStack justify="space-between" w="full" fontSize="xs" color="gray.600"> <HStack justify="space-between" w="full" fontSize="xs" color="gray.400">
<Text>{formatTime(currentTime)}</Text> <Text>{formatTime(currentTime)}</Text>
<Text>{formatTime(duration)}</Text> <Text>{formatTime(duration)}</Text>
</HStack> </HStack>
@ -250,6 +253,8 @@ export const MusicPlayer: React.FC<MusicPlayerProps> = ({
onClick={skipBackward} onClick={skipBackward}
size="sm" size="sm"
variant="ghost" variant="ghost"
color="gray.400"
_hover={{ bg: "gray.700" }}
/> />
<IconButton <IconButton
@ -260,6 +265,7 @@ export const MusicPlayer: React.FC<MusicPlayerProps> = ({
colorScheme="blue" colorScheme="blue"
isLoading={isLoading} isLoading={isLoading}
isDisabled={!streamingUrl} isDisabled={!streamingUrl}
_hover={{ bg: "blue.700" }}
/> />
<IconButton <IconButton
@ -268,6 +274,8 @@ export const MusicPlayer: React.FC<MusicPlayerProps> = ({
onClick={skipForward} onClick={skipForward}
size="sm" size="sm"
variant="ghost" variant="ghost"
color="gray.400"
_hover={{ bg: "gray.700" }}
/> />
</HStack> </HStack>
@ -279,21 +287,40 @@ export const MusicPlayer: React.FC<MusicPlayerProps> = ({
onClick={toggleMute} onClick={toggleMute}
size="sm" size="sm"
variant="ghost" variant="ghost"
color="gray.400"
_hover={{ bg: "gray.700" }}
/> />
<Slider <Slider
value={isMuted ? 0 : volume} value={isMuted ? 0 : volume}
onChange={handleVolumeChange}
min={0}
max={1} max={1}
step={0.1} step={0.1}
onChange={handleVolumeChange}
size="sm" size="sm"
w="100px" w="100px"
colorScheme="blue"
> >
<SliderTrack> <SliderTrack bg="gray.700">
<SliderFilledTrack /> <SliderFilledTrack bg="blue.400" />
</SliderTrack> </SliderTrack>
<SliderThumb /> <SliderThumb bg="blue.400" />
</Slider> </Slider>
</HStack> </HStack>
{/* Loading indicator */}
{isLoading && (
<Text fontSize="sm" color="gray.500" textAlign="center">
Loading audio...
</Text>
)}
{/* Error state */}
{!streamingUrl && (
<Text fontSize="sm" color="red.400" textAlign="center">
Unable to load audio file
</Text>
)}
</VStack> </VStack>
); );
}; };

View File

@ -178,6 +178,8 @@ export const MusicStorage: React.FC = () => {
minH="100vh" minH="100vh"
bg="gray.900" bg="gray.900"
color="gray.100" color="gray.100"
overflowY="auto"
height="100vh"
> >
<VStack spacing={6} align="stretch"> <VStack spacing={6} align="stretch">
<Heading size="lg" textAlign="center" color="white"> <Heading size="lg" textAlign="center" color="white">
@ -195,8 +197,8 @@ export const MusicStorage: React.FC = () => {
</Box> </Box>
</Alert> </Alert>
<Tabs variant="enclosed" colorScheme="blue"> <Tabs variant="enclosed" colorScheme="blue" height="calc(100vh - 200px)" display="flex" flexDirection="column">
<TabList bg="gray.800" borderColor="gray.700"> <TabList bg="gray.800" borderColor="gray.700" flexShrink={0}>
<Tab color="gray.300" _selected={{ bg: "gray.700", color: "white", borderColor: "gray.600" }}> <Tab color="gray.300" _selected={{ bg: "gray.700", color: "white", borderColor: "gray.600" }}>
Upload Music Upload Music
</Tab> </Tab>
@ -211,9 +213,9 @@ export const MusicStorage: React.FC = () => {
</Tab> </Tab>
</TabList> </TabList>
<TabPanels> <TabPanels flex={1} overflow="hidden">
{/* Upload Tab */} {/* Upload Tab */}
<TabPanel bg="gray.900"> <TabPanel bg="gray.900" height="100%" overflowY="auto">
<VStack spacing={6} align="stretch"> <VStack spacing={6} align="stretch">
<Box> <Box>
<Heading size="md" mb={4} color="white"> <Heading size="md" mb={4} color="white">
@ -229,7 +231,7 @@ export const MusicStorage: React.FC = () => {
</TabPanel> </TabPanel>
{/* Library Tab */} {/* Library Tab */}
<TabPanel bg="gray.900"> <TabPanel bg="gray.900" height="100%" overflowY="auto">
<VStack spacing={4} align="stretch"> <VStack spacing={4} align="stretch">
<HStack justify="space-between"> <HStack justify="space-between">
<Heading size="md" color="white">Music Library</Heading> <Heading size="md" color="white">Music Library</Heading>
@ -335,12 +337,12 @@ export const MusicStorage: React.FC = () => {
</TabPanel> </TabPanel>
{/* Song Matching Tab */} {/* Song Matching Tab */}
<TabPanel bg="gray.900"> <TabPanel bg="gray.900" height="100%" overflowY="auto">
<SongMatching /> <SongMatching />
</TabPanel> </TabPanel>
{/* Player Tab */} {/* Player Tab */}
<TabPanel bg="gray.900"> <TabPanel bg="gray.900" height="100%" overflowY="auto">
<VStack spacing={4} align="stretch"> <VStack spacing={4} align="stretch">
<Heading size="md" color="white">Music Player</Heading> <Heading size="md" color="white">Music Player</Heading>
{selectedFile ? ( {selectedFile ? (