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:
parent
15cad58c80
commit
01c017a5e6
@ -9,6 +9,7 @@ import { Configuration } from "./pages/Configuration";
|
|||||||
import { MusicStorage } from "./pages/MusicStorage";
|
import { MusicStorage } from "./pages/MusicStorage";
|
||||||
import { S3Configuration } from "./pages/S3Configuration";
|
import { S3Configuration } from "./pages/S3Configuration";
|
||||||
import { PersistentMusicPlayer } from "./components/PersistentMusicPlayer";
|
import { PersistentMusicPlayer } from "./components/PersistentMusicPlayer";
|
||||||
|
import { MusicPlayerProvider, useMusicPlayer } from "./contexts/MusicPlayerContext";
|
||||||
import { useXmlParser } from "./hooks/useXmlParser";
|
import { useXmlParser } from "./hooks/useXmlParser";
|
||||||
import { usePaginatedSongs } from "./hooks/usePaginatedSongs";
|
import { usePaginatedSongs } from "./hooks/usePaginatedSongs";
|
||||||
import { formatTotalDuration } from "./utils/formatters";
|
import { formatTotalDuration } from "./utils/formatters";
|
||||||
@ -70,12 +71,12 @@ const findPlaylistByName = (playlists: PlaylistNode[], name: string): PlaylistNo
|
|||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RekordboxReader() {
|
const RekordboxReader: React.FC = () => {
|
||||||
const { playlists, setPlaylists, loading: xmlLoading } = useXmlParser();
|
const { playlists, setPlaylists, loading: xmlLoading } = useXmlParser();
|
||||||
const [selectedSong, setSelectedSong] = useState<Song | null>(null);
|
const [selectedSong, setSelectedSong] = useState<Song | null>(null);
|
||||||
const [isDatabaseInitialized, setIsDatabaseInitialized] = useState(false);
|
const [isDatabaseInitialized, setIsDatabaseInitialized] = useState(false);
|
||||||
const [isSwitchingPlaylist, setIsSwitchingPlaylist] = 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
|
// Memoized song selection handler to prevent unnecessary re-renders
|
||||||
const handleSongSelect = useCallback((song: Song) => {
|
const handleSongSelect = useCallback((song: Song) => {
|
||||||
@ -86,14 +87,14 @@ export default function RekordboxReader() {
|
|||||||
const handlePlaySong = useCallback((song: Song) => {
|
const handlePlaySong = useCallback((song: Song) => {
|
||||||
// Check if song has S3 file
|
// Check if song has S3 file
|
||||||
if (song.s3File?.hasS3File) {
|
if (song.s3File?.hasS3File) {
|
||||||
setCurrentPlayingSong(song);
|
playSong(song);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [playSong]);
|
||||||
|
|
||||||
// Handle closing the music player
|
// Handle closing the music player
|
||||||
const handleCloseMusicPlayer = useCallback(() => {
|
const handleCloseMusicPlayer = useCallback(() => {
|
||||||
setCurrentPlayingSong(null);
|
closePlayer();
|
||||||
}, []);
|
}, [closePlayer]);
|
||||||
|
|
||||||
// Format total duration for display
|
// Format total duration for display
|
||||||
const getFormattedTotalDuration = useCallback((durationSeconds?: number): string => {
|
const getFormattedTotalDuration = useCallback((durationSeconds?: number): string => {
|
||||||
@ -680,9 +681,19 @@ export default function RekordboxReader() {
|
|||||||
|
|
||||||
{/* Persistent Music Player */}
|
{/* Persistent Music Player */}
|
||||||
<PersistentMusicPlayer
|
<PersistentMusicPlayer
|
||||||
currentSong={currentPlayingSong}
|
currentSong={currentSong}
|
||||||
onClose={handleCloseMusicPlayer}
|
onClose={handleCloseMusicPlayer}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const RekordboxReaderApp: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<MusicPlayerProvider>
|
||||||
|
<RekordboxReader />
|
||||||
|
</MusicPlayerProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RekordboxReaderApp;
|
||||||
|
|||||||
46
packages/frontend/src/contexts/MusicPlayerContext.tsx
Normal file
46
packages/frontend/src/contexts/MusicPlayerContext.tsx
Normal 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;
|
||||||
|
};
|
||||||
@ -25,7 +25,7 @@ import {
|
|||||||
import { FiPlay, FiTrash2, FiMusic, FiRefreshCw } from 'react-icons/fi';
|
import { FiPlay, FiTrash2, FiMusic, FiRefreshCw } from 'react-icons/fi';
|
||||||
import { MusicUpload } from '../components/MusicUpload';
|
import { MusicUpload } from '../components/MusicUpload';
|
||||||
import { SongMatching } from '../components/SongMatching';
|
import { SongMatching } from '../components/SongMatching';
|
||||||
import { PersistentMusicPlayer } from '../components/PersistentMusicPlayer';
|
import { useMusicPlayer } from '../contexts/MusicPlayerContext';
|
||||||
import type { Song } from '../types/interfaces';
|
import type { Song } from '../types/interfaces';
|
||||||
|
|
||||||
interface MusicFile {
|
interface MusicFile {
|
||||||
@ -43,9 +43,9 @@ interface MusicFile {
|
|||||||
|
|
||||||
export const MusicStorage: React.FC = () => {
|
export const MusicStorage: React.FC = () => {
|
||||||
const [musicFiles, setMusicFiles] = useState<MusicFile[]>([]);
|
const [musicFiles, setMusicFiles] = useState<MusicFile[]>([]);
|
||||||
const [currentPlayingSong, setCurrentPlayingSong] = useState<Song | null>(null);
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isSyncing, setIsSyncing] = useState(false);
|
const [isSyncing, setIsSyncing] = useState(false);
|
||||||
|
const { playSong } = useMusicPlayer();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
// Load music files on component mount
|
// Load music files on component mount
|
||||||
@ -132,9 +132,7 @@ export const MusicStorage: React.FC = () => {
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
setMusicFiles(prev => prev.filter(file => file._id !== fileId));
|
setMusicFiles(prev => prev.filter(file => file._id !== fileId));
|
||||||
if (currentPlayingSong?.s3File?.musicFileId === fileId) {
|
// The persistent player will handle removing the song if it was playing this file
|
||||||
setCurrentPlayingSong(null);
|
|
||||||
}
|
|
||||||
toast({
|
toast({
|
||||||
title: 'File Deleted',
|
title: 'File Deleted',
|
||||||
description: 'Music file deleted successfully',
|
description: 'Music file deleted successfully',
|
||||||
@ -177,7 +175,7 @@ export const MusicStorage: React.FC = () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
setCurrentPlayingSong(song);
|
playSong(song);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error playing music file:', error);
|
console.error('Error playing music file:', error);
|
||||||
toast({
|
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 formatFileSize = (bytes: number): string => {
|
||||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||||
if (bytes === 0) return '0 Bytes';
|
if (bytes === 0) return '0 Bytes';
|
||||||
@ -389,10 +382,7 @@ export const MusicStorage: React.FC = () => {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</VStack>
|
</VStack>
|
||||||
{/* Persistent Music Player */}
|
{/* Persistent Music Player */}
|
||||||
<PersistentMusicPlayer
|
{/* The PersistentMusicPlayer component is now managed by the global context */}
|
||||||
currentSong={currentPlayingSong}
|
|
||||||
onClose={handleCloseMusicPlayer}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user