Smarter/more granular event handling
This commit is contained in:
@@ -46,6 +46,15 @@ export interface SearchResponse {
|
||||
results: SearchResult[];
|
||||
}
|
||||
|
||||
export enum ServerEvent {
|
||||
PlaylistUpdate = "playlist_update",
|
||||
NowPlayingUpdate = "now_playing_update",
|
||||
VolumeUpdate = "volume_update",
|
||||
FavoritesUpdate = "favorites_update",
|
||||
MetadataUpdate = "metadata_update",
|
||||
MPDUpdate = "mpd_update",
|
||||
}
|
||||
|
||||
export const API = {
|
||||
async getPlaylist(): Promise<PlaylistItem[]> {
|
||||
const response = await fetch('/api/playlist');
|
||||
|
||||
@@ -3,9 +3,9 @@ import SongTable from './SongTable';
|
||||
import NowPlaying from './NowPlaying';
|
||||
import AddSongPanel from './AddSongPanel';
|
||||
import { TabView, Tab } from './TabView';
|
||||
import { API, getDisplayTitle, PlaylistItem } from '../api/player';
|
||||
import { useEventWebSocket } from '../hooks/useEventWebsocket';
|
||||
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",
|
||||
@@ -96,14 +96,17 @@ const App: React.FC = () => {
|
||||
}, []);
|
||||
|
||||
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);
|
||||
|
||||
if (!volumeSettingIsLocked) {
|
||||
setVolume(nowPlaying.volume);
|
||||
}
|
||||
setVolume(nowPlaying.volume);
|
||||
}, [volumeSettingIsLocked]);
|
||||
|
||||
const handleAddURL = async (url: string) => {
|
||||
@@ -148,23 +151,41 @@ const App: React.FC = () => {
|
||||
await API.setVolume(volume);
|
||||
};
|
||||
|
||||
const handleWebSocketEvent = useCallback((event: any) => {
|
||||
const handleWebSocketEvent = useCallback((message: MessageEvent) => {
|
||||
const event = JSON.parse(message.data);
|
||||
switch (event.event) {
|
||||
case 'user_modify':
|
||||
case 'end-file':
|
||||
case 'playback-restart':
|
||||
case 'metadata_update':
|
||||
case ServerEvent.PlaylistUpdate:
|
||||
case ServerEvent.NowPlayingUpdate:
|
||||
case ServerEvent.MetadataUpdate:
|
||||
case ServerEvent.MPDUpdate:
|
||||
fetchPlaylist();
|
||||
fetchNowPlaying();
|
||||
break;
|
||||
case 'favorites_update':
|
||||
case ServerEvent.VolumeUpdate:
|
||||
if (!volumeSettingIsLocked) {
|
||||
fetchNowPlaying();
|
||||
}
|
||||
|
||||
break;
|
||||
case ServerEvent.FavoritesUpdate:
|
||||
fetchFavorites();
|
||||
break;
|
||||
}
|
||||
}, [fetchPlaylist, fetchNowPlaying, fetchFavorites]);
|
||||
|
||||
// Use the hook
|
||||
useEventWebSocket(handleWebSocketEvent);
|
||||
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(() => {
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { API } from '../api/player';
|
||||
|
||||
export const useEventWebSocket = (onEvent: (event: any) => void) => {
|
||||
const [ws, setWs] = useState<WebSocket | null>(null);
|
||||
const [wsReconnectTimeout, setWsReconnectTimeout] = useState<number | null>(null);
|
||||
|
||||
const connectWebSocket = useCallback(() => {
|
||||
// Clear any existing reconnection timeout
|
||||
if (wsReconnectTimeout) {
|
||||
window.clearTimeout(wsReconnectTimeout);
|
||||
setWsReconnectTimeout(null);
|
||||
}
|
||||
|
||||
// Close existing websocket if it exists
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
const websocket = API.subscribeToEvents(onEvent);
|
||||
|
||||
websocket.addEventListener('close', () => {
|
||||
console.log('WebSocket closed. Attempting to reconnect...');
|
||||
|
||||
// Attempt to reconnect after 2 seconds
|
||||
const timeout = window.setTimeout(() => {
|
||||
connectWebSocket();
|
||||
}, 2000);
|
||||
|
||||
setWsReconnectTimeout(timeout);
|
||||
});
|
||||
|
||||
websocket.addEventListener('error', (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
websocket.close();
|
||||
});
|
||||
|
||||
setWs(websocket);
|
||||
}, [wsReconnectTimeout, onEvent]);
|
||||
|
||||
// Check WebSocket health periodically
|
||||
useEffect(() => {
|
||||
const healthCheck = setInterval(() => {
|
||||
if (!ws || ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING) {
|
||||
console.log('WebSocket unhealthy, reconnecting...');
|
||||
connectWebSocket();
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
return () => {
|
||||
clearInterval(healthCheck);
|
||||
};
|
||||
}, [ws, connectWebSocket]);
|
||||
|
||||
// Initial WebSocket connection
|
||||
useEffect(() => {
|
||||
connectWebSocket();
|
||||
|
||||
return () => {
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
if (wsReconnectTimeout) {
|
||||
window.clearTimeout(wsReconnectTimeout);
|
||||
}
|
||||
};
|
||||
}, [connectWebSocket]);
|
||||
|
||||
return ws;
|
||||
};
|
||||
Reference in New Issue
Block a user