2025-02-15 21:16:04 -08:00
|
|
|
import React, { useState, useEffect, useCallback } from 'react';
|
2025-02-15 14:56:58 -08:00
|
|
|
import SongTable from './SongTable';
|
2025-02-15 15:22:07 -08:00
|
|
|
import NowPlaying from './NowPlaying';
|
|
|
|
|
import AddSongPanel from './AddSongPanel';
|
2025-02-15 22:15:59 -08:00
|
|
|
import { API, getDisplayTitle, PlaylistItem } from '../api/player';
|
2025-02-15 16:28:47 -08:00
|
|
|
|
2025-02-15 15:22:07 -08:00
|
|
|
const App: React.FC = () => {
|
|
|
|
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
|
|
|
const [nowPlayingSong, setNowPlayingSong] = useState<string | null>(null);
|
|
|
|
|
const [nowPlayingFileName, setNowPlayingFileName] = useState<string | null>(null);
|
2025-02-15 21:16:04 -08:00
|
|
|
const [volume, setVolume] = useState(100);
|
|
|
|
|
const [volumeSettingIsLocked, setVolumeSettingIsLocked] = useState(false);
|
2025-02-15 16:28:47 -08:00
|
|
|
const [songs, setSongs] = useState<PlaylistItem[]>([]);
|
2025-02-15 21:16:04 -08:00
|
|
|
const [_, setWs] = useState<WebSocket | null>(null);
|
2025-02-15 16:28:47 -08:00
|
|
|
|
2025-02-15 21:16:04 -08:00
|
|
|
const fetchPlaylist = useCallback(async () => {
|
2025-02-15 16:28:47 -08:00
|
|
|
const playlist = await API.getPlaylist();
|
|
|
|
|
setSongs(playlist);
|
2025-02-15 21:16:04 -08:00
|
|
|
}, []);
|
2025-02-15 16:28:47 -08:00
|
|
|
|
2025-02-15 21:16:04 -08:00
|
|
|
const fetchNowPlaying = useCallback(async () => {
|
2025-02-15 16:28:47 -08:00
|
|
|
const nowPlaying = await API.getNowPlaying();
|
2025-02-15 22:15:59 -08:00
|
|
|
setNowPlayingSong(getDisplayTitle(nowPlaying.playingItem));
|
|
|
|
|
setNowPlayingFileName(nowPlaying.playingItem.filename);
|
2025-02-15 16:28:47 -08:00
|
|
|
setIsPlaying(!nowPlaying.isPaused);
|
2025-02-15 21:16:04 -08:00
|
|
|
|
|
|
|
|
if (!volumeSettingIsLocked) {
|
|
|
|
|
setVolume(nowPlaying.volume);
|
|
|
|
|
}
|
|
|
|
|
}, [volumeSettingIsLocked]);
|
2025-02-15 12:15:41 -08:00
|
|
|
|
2025-02-15 22:15:59 -08:00
|
|
|
const handleAddURL = async (url: string) => {
|
2025-02-15 15:22:07 -08:00
|
|
|
const urlToAdd = url.trim();
|
|
|
|
|
if (urlToAdd) {
|
2025-02-15 22:15:59 -08:00
|
|
|
await API.addToPlaylist(urlToAdd);
|
2025-02-15 16:28:47 -08:00
|
|
|
fetchPlaylist();
|
2025-02-15 22:15:59 -08:00
|
|
|
|
|
|
|
|
if (!isPlaying) {
|
|
|
|
|
await API.play();
|
|
|
|
|
}
|
2025-02-15 15:22:07 -08:00
|
|
|
}
|
|
|
|
|
};
|
2025-02-15 12:15:41 -08:00
|
|
|
|
2025-02-15 15:22:07 -08:00
|
|
|
const handleDelete = (index: number) => {
|
|
|
|
|
setSongs(songs.filter((_, i) => i !== index));
|
2025-02-15 16:28:47 -08:00
|
|
|
API.removeFromPlaylist(index);
|
|
|
|
|
fetchPlaylist();
|
|
|
|
|
fetchNowPlaying();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSkipTo = async (index: number) => {
|
|
|
|
|
const song = songs[index];
|
|
|
|
|
if (song.playing) {
|
|
|
|
|
togglePlayPause();
|
|
|
|
|
} else {
|
|
|
|
|
await API.skipTo(index);
|
|
|
|
|
await API.play();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fetchNowPlaying();
|
|
|
|
|
fetchPlaylist();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const togglePlayPause = async () => {
|
|
|
|
|
if (isPlaying) {
|
|
|
|
|
await API.pause();
|
|
|
|
|
} else {
|
|
|
|
|
await API.play();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fetchNowPlaying();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSkip = async () => {
|
|
|
|
|
await API.skip();
|
|
|
|
|
fetchNowPlaying();
|
2025-02-15 15:22:07 -08:00
|
|
|
};
|
2025-02-15 12:15:41 -08:00
|
|
|
|
2025-02-15 16:28:47 -08:00
|
|
|
const handlePrevious = async () => {
|
|
|
|
|
await API.previous();
|
|
|
|
|
fetchNowPlaying();
|
2025-02-15 15:22:07 -08:00
|
|
|
};
|
2025-02-15 12:15:41 -08:00
|
|
|
|
2025-02-15 21:16:04 -08:00
|
|
|
const handleVolumeSettingChange = async (volume: number) => {
|
|
|
|
|
setVolume(volume);
|
|
|
|
|
await API.setVolume(volume);
|
2025-02-15 16:28:47 -08:00
|
|
|
};
|
|
|
|
|
|
2025-02-15 21:16:04 -08:00
|
|
|
const handleWebSocketEvent = useCallback((event: any) => {
|
|
|
|
|
switch (event.event) {
|
|
|
|
|
case 'user_modify':
|
|
|
|
|
case 'end-file':
|
|
|
|
|
case 'playback-restart':
|
2025-02-15 21:40:30 -08:00
|
|
|
case 'metadata_update':
|
2025-02-15 21:16:04 -08:00
|
|
|
fetchPlaylist();
|
|
|
|
|
fetchNowPlaying();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}, [fetchPlaylist, fetchNowPlaying]);
|
|
|
|
|
|
|
|
|
|
// Initial data fetch
|
2025-02-15 16:28:47 -08:00
|
|
|
useEffect(() => {
|
|
|
|
|
fetchPlaylist();
|
|
|
|
|
fetchNowPlaying();
|
2025-02-15 21:16:04 -08:00
|
|
|
}, [fetchPlaylist, fetchNowPlaying]);
|
|
|
|
|
|
|
|
|
|
// WebSocket connection
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const ws = API.subscribeToEvents(handleWebSocketEvent);
|
|
|
|
|
setWs(ws);
|
2025-02-15 16:28:47 -08:00
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
if (ws) {
|
|
|
|
|
ws.close();
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-02-15 21:16:04 -08:00
|
|
|
}, [handleWebSocketEvent]);
|
2025-02-15 16:28:47 -08:00
|
|
|
|
2025-02-15 12:15:41 -08:00
|
|
|
return (
|
2025-02-15 21:16:04 -08:00
|
|
|
<div className="flex items-center justify-center h-screen w-screen bg-black md:py-10">
|
|
|
|
|
<div className="bg-violet-900 w-full md:max-w-2xl h-full md:max-h-xl md:border md:rounded-2xl flex flex-col">
|
2025-02-15 12:15:41 -08:00
|
|
|
<NowPlaying
|
|
|
|
|
className="flex flex-row md:rounded-t-2xl"
|
2025-02-15 15:22:07 -08:00
|
|
|
songName={nowPlayingSong || "(Not Playing)"}
|
|
|
|
|
fileName={nowPlayingFileName || ""}
|
2025-02-15 12:15:41 -08:00
|
|
|
isPlaying={isPlaying}
|
2025-02-15 16:28:47 -08:00
|
|
|
onPlayPause={togglePlayPause}
|
|
|
|
|
onSkip={handleSkip}
|
|
|
|
|
onPrevious={handlePrevious}
|
2025-02-15 21:16:04 -08:00
|
|
|
volume={volume}
|
|
|
|
|
onVolumeSettingChange={handleVolumeSettingChange}
|
|
|
|
|
onVolumeWillChange={() => setVolumeSettingIsLocked(true)}
|
|
|
|
|
onVolumeDidChange={() => setVolumeSettingIsLocked(false)}
|
2025-02-15 12:15:41 -08:00
|
|
|
/>
|
2025-02-15 15:22:07 -08:00
|
|
|
|
|
|
|
|
{songs.length > 0 ? (
|
|
|
|
|
<SongTable
|
|
|
|
|
songs={songs}
|
2025-02-15 16:28:47 -08:00
|
|
|
isPlaying={isPlaying}
|
2025-02-15 15:22:07 -08:00
|
|
|
onDelete={handleDelete}
|
|
|
|
|
onSkipTo={handleSkipTo}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="flex items-center justify-center h-full">
|
|
|
|
|
<div className="text-white text-2xl font-bold">Playlist is empty</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<AddSongPanel onAddURL={handleAddURL} />
|
2025-02-15 12:15:41 -08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default App;
|