fix: Implement global music player context to prevent duplicate players

- Create MusicPlayerContext for global music player state management
- Replace local music player state with global context in App.tsx
- Remove duplicate PersistentMusicPlayer from Music Storage page
- Update Music Storage to use global context instead of local state
- Wrap entire app with MusicPlayerProvider for consistent state
- Ensure single music player instance across all pages
- Fix duplicate player issue when playing songs from different pages

Now there's only one music player that works consistently across
the entire application, preventing duplicate players and conflicts.
This commit is contained in:
Geert Rademakes 2025-08-06 15:37:16 +02:00
parent 15cad58c80
commit 01c017a5e6
3 changed files with 70 additions and 23 deletions

View File

@ -9,6 +9,7 @@ import { Configuration } from "./pages/Configuration";
import { MusicStorage } from "./pages/MusicStorage";
import { S3Configuration } from "./pages/S3Configuration";
import { PersistentMusicPlayer } from "./components/PersistentMusicPlayer";
import { MusicPlayerProvider, useMusicPlayer } from "./contexts/MusicPlayerContext";
import { useXmlParser } from "./hooks/useXmlParser";
import { usePaginatedSongs } from "./hooks/usePaginatedSongs";
import { formatTotalDuration } from "./utils/formatters";
@ -70,12 +71,12 @@ const findPlaylistByName = (playlists: PlaylistNode[], name: string): PlaylistNo
return [];
};
export default function RekordboxReader() {
const RekordboxReader: React.FC = () => {
const { playlists, setPlaylists, loading: xmlLoading } = useXmlParser();
const [selectedSong, setSelectedSong] = useState<Song | null>(null);
const [isDatabaseInitialized, setIsDatabaseInitialized] = useState(false);
const [isSwitchingPlaylist, setIsSwitchingPlaylist] = useState(false);
const [currentPlayingSong, setCurrentPlayingSong] = useState<Song | null>(null);
const { currentSong, playSong, closePlayer } = useMusicPlayer();
// Memoized song selection handler to prevent unnecessary re-renders
const handleSongSelect = useCallback((song: Song) => {
@ -86,14 +87,14 @@ export default function RekordboxReader() {
const handlePlaySong = useCallback((song: Song) => {
// Check if song has S3 file
if (song.s3File?.hasS3File) {
setCurrentPlayingSong(song);
playSong(song);
}
}, []);
}, [playSong]);
// Handle closing the music player
const handleCloseMusicPlayer = useCallback(() => {
setCurrentPlayingSong(null);
}, []);
closePlayer();
}, [closePlayer]);
// Format total duration for display
const getFormattedTotalDuration = useCallback((durationSeconds?: number): string => {
@ -680,9 +681,19 @@ export default function RekordboxReader() {
{/* Persistent Music Player */}
<PersistentMusicPlayer
currentSong={currentPlayingSong}
currentSong={currentSong}
onClose={handleCloseMusicPlayer}
/>
</Box>
);
}
};
const RekordboxReaderApp: React.FC = () => {
return (
<MusicPlayerProvider>
<RekordboxReader />
</MusicPlayerProvider>
);
};
export default RekordboxReaderApp;

View File

@ -0,0 +1,46 @@
import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';
import type { Song } from '../types/interfaces';
interface MusicPlayerContextType {
currentSong: Song | null;
playSong: (song: Song) => void;
closePlayer: () => void;
}
const MusicPlayerContext = createContext<MusicPlayerContextType | undefined>(undefined);
interface MusicPlayerProviderProps {
children: ReactNode;
}
export const MusicPlayerProvider: React.FC<MusicPlayerProviderProps> = ({ children }) => {
const [currentSong, setCurrentSong] = useState<Song | null>(null);
const playSong = useCallback((song: Song) => {
setCurrentSong(song);
}, []);
const closePlayer = useCallback(() => {
setCurrentSong(null);
}, []);
const value: MusicPlayerContextType = {
currentSong,
playSong,
closePlayer,
};
return (
<MusicPlayerContext.Provider value={value}>
{children}
</MusicPlayerContext.Provider>
);
};
export const useMusicPlayer = (): MusicPlayerContextType => {
const context = useContext(MusicPlayerContext);
if (context === undefined) {
throw new Error('useMusicPlayer must be used within a MusicPlayerProvider');
}
return context;
};

View File

@ -25,7 +25,7 @@ import {
import { FiPlay, FiTrash2, FiMusic, FiRefreshCw } from 'react-icons/fi';
import { MusicUpload } from '../components/MusicUpload';
import { SongMatching } from '../components/SongMatching';
import { PersistentMusicPlayer } from '../components/PersistentMusicPlayer';
import { useMusicPlayer } from '../contexts/MusicPlayerContext';
import type { Song } from '../types/interfaces';
interface MusicFile {
@ -43,9 +43,9 @@ interface MusicFile {
export const MusicStorage: React.FC = () => {
const [musicFiles, setMusicFiles] = useState<MusicFile[]>([]);
const [currentPlayingSong, setCurrentPlayingSong] = useState<Song | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [isSyncing, setIsSyncing] = useState(false);
const { playSong } = useMusicPlayer();
const toast = useToast();
// Load music files on component mount
@ -132,9 +132,7 @@ export const MusicStorage: React.FC = () => {
if (response.ok) {
setMusicFiles(prev => prev.filter(file => file._id !== fileId));
if (currentPlayingSong?.s3File?.musicFileId === fileId) {
setCurrentPlayingSong(null);
}
// The persistent player will handle removing the song if it was playing this file
toast({
title: 'File Deleted',
description: 'Music file deleted successfully',
@ -177,7 +175,7 @@ export const MusicStorage: React.FC = () => {
},
};
setCurrentPlayingSong(song);
playSong(song);
} catch (error) {
console.error('Error playing music file:', error);
toast({
@ -190,11 +188,6 @@ export const MusicStorage: React.FC = () => {
}
};
// Handle closing the music player
const handleCloseMusicPlayer = () => {
setCurrentPlayingSong(null);
};
const formatFileSize = (bytes: number): string => {
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
if (bytes === 0) return '0 Bytes';
@ -389,10 +382,7 @@ export const MusicStorage: React.FC = () => {
</Tabs>
</VStack>
{/* Persistent Music Player */}
<PersistentMusicPlayer
currentSong={currentPlayingSong}
onClose={handleCloseMusicPlayer}
/>
{/* The PersistentMusicPlayer component is now managed by the global context */}
</Box>
);
};