Feature: Live Stream-Liste + Toast Notifications
- stream_available/stream_ended WS-Events verarbeiten - WS sofort beim Mount verbinden (nicht nur beim Broadcasting) - WS reconnect immer aktiv (nicht nur bei aktivem Stream) - Toast Notifications: neuer Stream, Update verfügbar/bereit - Notification Permission beim App-Start anfragen Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
939137cc77
commit
3455e20a96
2 changed files with 51 additions and 8 deletions
|
|
@ -37,6 +37,13 @@ export default function App() {
|
||||||
const [pluginData, setPluginData] = useState<Record<string, any>>({});
|
const [pluginData, setPluginData] = useState<Record<string, any>>({});
|
||||||
const eventSourceRef = useRef<EventSource | null>(null);
|
const eventSourceRef = useRef<EventSource | null>(null);
|
||||||
|
|
||||||
|
// Request notification permission
|
||||||
|
useEffect(() => {
|
||||||
|
if ('Notification' in window && Notification.permission === 'default') {
|
||||||
|
Notification.requestPermission();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Fetch plugin list
|
// Fetch plugin list
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('/api/plugins')
|
fetch('/api/plugins')
|
||||||
|
|
@ -98,8 +105,18 @@ export default function App() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const api = (window as any).electronAPI;
|
const api = (window as any).electronAPI;
|
||||||
if (!api?.onUpdateAvailable) return;
|
if (!api?.onUpdateAvailable) return;
|
||||||
api.onUpdateAvailable(() => setUpdateState('downloading'));
|
api.onUpdateAvailable(() => {
|
||||||
api.onUpdateReady(() => setUpdateState('ready'));
|
setUpdateState('downloading');
|
||||||
|
if (Notification.permission === 'granted') {
|
||||||
|
new Notification('Gaming Hub Update', { body: 'Ein Update wird heruntergeladen...' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
api.onUpdateReady(() => {
|
||||||
|
setUpdateState('ready');
|
||||||
|
if (Notification.permission === 'granted') {
|
||||||
|
new Notification('Gaming Hub Update bereit', { body: 'Klicke OK um das Update zu installieren.' });
|
||||||
|
}
|
||||||
|
});
|
||||||
api.onUpdateNotAvailable?.(() => setUpdateState('uptodate'));
|
api.onUpdateNotAvailable?.(() => setUpdateState('uptodate'));
|
||||||
api.onUpdateError?.(() => setUpdateState('error'));
|
api.onUpdateError?.(() => setUpdateState('error'));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
||||||
|
|
@ -165,9 +165,28 @@ export default function StreamingTab({ data }: { data: any }) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'stream_available':
|
case 'stream_available':
|
||||||
|
setStreams(prev => {
|
||||||
|
if (prev.some(s => s.id === msg.streamId)) return prev;
|
||||||
|
return [...prev, {
|
||||||
|
id: msg.streamId,
|
||||||
|
broadcasterName: msg.broadcasterName,
|
||||||
|
title: msg.title,
|
||||||
|
startedAt: new Date().toISOString(),
|
||||||
|
viewerCount: 0,
|
||||||
|
hasPassword: true,
|
||||||
|
}];
|
||||||
|
});
|
||||||
|
// Toast notification for new stream
|
||||||
|
if (Notification.permission === 'granted') {
|
||||||
|
new Notification('Neuer Stream', {
|
||||||
|
body: `${msg.broadcasterName} streamt: ${msg.title}`,
|
||||||
|
icon: '/assets/icon.png',
|
||||||
|
});
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'stream_ended':
|
case 'stream_ended':
|
||||||
|
setStreams(prev => prev.filter(s => s.id !== msg.streamId));
|
||||||
if (viewingRef.current?.streamId === msg.streamId) {
|
if (viewingRef.current?.streamId === msg.streamId) {
|
||||||
cleanupViewer();
|
cleanupViewer();
|
||||||
setViewing(null);
|
setViewing(null);
|
||||||
|
|
@ -303,17 +322,24 @@ export default function StreamingTab({ data }: { data: any }) {
|
||||||
|
|
||||||
ws.onclose = () => {
|
ws.onclose = () => {
|
||||||
wsRef.current = null;
|
wsRef.current = null;
|
||||||
if (isBroadcastingRef.current || viewingRef.current) {
|
// Always reconnect to keep stream list in sync
|
||||||
reconnectTimerRef.current = setTimeout(() => {
|
reconnectTimerRef.current = setTimeout(() => {
|
||||||
reconnectDelayRef.current = Math.min(reconnectDelayRef.current * 2, 10000);
|
reconnectDelayRef.current = Math.min(reconnectDelayRef.current * 2, 10000);
|
||||||
connectWs();
|
connectWs();
|
||||||
}, reconnectDelayRef.current);
|
}, reconnectDelayRef.current);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onerror = () => { ws.close(); };
|
ws.onerror = () => { ws.close(); };
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// ── Connect WS on mount for live stream updates ──
|
||||||
|
useEffect(() => {
|
||||||
|
connectWs();
|
||||||
|
return () => {
|
||||||
|
if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
|
||||||
|
};
|
||||||
|
}, [connectWs]);
|
||||||
|
|
||||||
// ── Start broadcasting ──
|
// ── Start broadcasting ──
|
||||||
const startBroadcast = useCallback(async () => {
|
const startBroadcast = useCallback(async () => {
|
||||||
if (!userName.trim()) { setError('Bitte gib einen Namen ein.'); return; }
|
if (!userName.trim()) { setError('Bitte gib einen Namen ein.'); return; }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue