import React, { useState, useEffect, useCallback } from 'react';
import SongTable from './SongTable';
import NowPlaying from './NowPlaying';
import AddSongPanel from './AddSongPanel';
import { TabView, Tab } from './TabView';
import { API, getDisplayTitle, PlaylistItem, ServerEvent } from '../api/player';
import { FaMusic, FaHeart } from 'react-icons/fa';
import useWebSocket from 'react-use-websocket';
enum Tabs {
Playlist = "playlist",
Favorites = "favorites",
}
const EmptyContent: React.FC<{ label: string}> = ({label}) => (
);
interface SonglistContentProps {
songs: PlaylistItem[];
isPlaying: boolean;
onNeedsRefresh: () => void;
}
const PlaylistContent: React.FC = ({ songs, isPlaying, onNeedsRefresh }) => {
const handleDelete = (index: number) => {
API.removeFromPlaylist(index);
onNeedsRefresh();
};
const handleSkipTo = (index: number) => {
API.skipTo(index);
onNeedsRefresh();
};
return (
songs.length > 0 ? (
) : (
)
);
};
const FavoritesContent: React.FC = ({ songs, isPlaying, onNeedsRefresh }) => {
const handleDelete = (index: number) => {
API.removeFromFavorites(index);
onNeedsRefresh();
};
const handleSkipTo = (index: number) => {
API.replaceCurrentFile(songs[index].filename);
API.play();
onNeedsRefresh();
};
return (
songs.length > 0 ? (
) : (
)
);
};
const App: React.FC = () => {
const [isPlaying, setIsPlaying] = useState(false);
const [nowPlayingSong, setNowPlayingSong] = useState(null);
const [nowPlayingFileName, setNowPlayingFileName] = useState(null);
const [volume, setVolume] = useState(100);
const [volumeSettingIsLocked, setVolumeSettingIsLocked] = useState(false);
const [playlist, setPlaylist] = useState([]);
const [favorites, setFavorites] = useState([]);
const [selectedTab, setSelectedTab] = useState(Tabs.Playlist);
const fetchPlaylist = useCallback(async () => {
const playlist = await API.getPlaylist();
setPlaylist(playlist);
}, []);
const fetchFavorites = useCallback(async () => {
const favorites = await API.getFavorites();
setFavorites(favorites);
}, []);
const fetchNowPlaying = useCallback(async () => {
if (volumeSettingIsLocked) {
// We are actively changing the volume, which we do actually want to send events
// continuously to the server, but we don't want to refresh our state while doing that.
return;
}
const nowPlaying = await API.getNowPlaying();
setNowPlayingSong(getDisplayTitle(nowPlaying.playingItem));
setNowPlayingFileName(nowPlaying.playingItem.filename);
setIsPlaying(!nowPlaying.isPaused);
setVolume(nowPlaying.volume);
}, [volumeSettingIsLocked]);
const handleAddURL = async (url: string) => {
const urlToAdd = url.trim();
if (urlToAdd) {
if (selectedTab === Tabs.Favorites) {
await API.addToFavorites(urlToAdd);
fetchFavorites();
} else {
await API.addToPlaylist(urlToAdd);
fetchPlaylist();
}
if (!isPlaying) {
await API.play();
}
}
};
const togglePlayPause = async () => {
if (isPlaying) {
await API.pause();
} else {
await API.play();
}
fetchNowPlaying();
};
const handleSkip = async () => {
await API.skip();
fetchNowPlaying();
};
const handlePrevious = async () => {
await API.previous();
fetchNowPlaying();
};
const handleVolumeSettingChange = async (volume: number) => {
setVolume(volume);
await API.setVolume(volume);
};
const handleWebSocketEvent = useCallback((message: MessageEvent) => {
const event = JSON.parse(message.data);
switch (event.event) {
case ServerEvent.PlaylistUpdate:
case ServerEvent.NowPlayingUpdate:
case ServerEvent.MetadataUpdate:
case ServerEvent.MPDUpdate:
fetchPlaylist();
fetchNowPlaying();
break;
case ServerEvent.VolumeUpdate:
if (!volumeSettingIsLocked) {
fetchNowPlaying();
}
break;
case ServerEvent.FavoritesUpdate:
fetchFavorites();
break;
}
}, [fetchPlaylist, fetchNowPlaying, fetchFavorites]);
useWebSocket('/api/events', {
onOpen: () => {
console.log('WebSocket connected');
},
onClose: () => {
console.log('WebSocket disconnected');
},
onError: (error) => {
console.error('WebSocket error:', error);
},
onMessage: handleWebSocketEvent,
shouldReconnect: () => true,
});
// Handle visibility changes
useEffect(() => {
const handleVisibilityChange = () => {
if (document.visibilityState === 'visible') {
fetchPlaylist();
fetchNowPlaying();
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, [fetchPlaylist, fetchNowPlaying]);
const refreshContent = () => {
fetchPlaylist();
fetchNowPlaying();
fetchFavorites();
}
// Initial data fetch
useEffect(() => {
fetchPlaylist();
fetchNowPlaying();
fetchFavorites();
}, [fetchPlaylist, fetchNowPlaying, fetchFavorites]);
return (
setVolumeSettingIsLocked(true)}
onVolumeDidChange={() => setVolumeSettingIsLocked(false)}
/>
}>
}>
({ ...f, playing: f.filename === nowPlayingFileName }))}
isPlaying={isPlaying}
onNeedsRefresh={refreshContent}
/>
);
};
export default App;