Streaming: Bildschirmauswahl-Picker, Passwort optional, Windows-Toast-Notifications
- Electron: setDisplayMediaRequestHandler zeigt jetzt immer einen modalen Picker mit Thumbnails statt automatisch die letzte Quelle zu verwenden - Passwort bei Streams ist jetzt optional (Server + Frontend) - Streams ohne Passwort: direkter Beitritt ohne Modal - hasPassword wird korrekt im stream_available Event übertragen - Windows Toast-Notifications via Electron Notification API für "Stream gestartet" und "Neuer Stream" Events - Browser-Variante nutzt weiterhin Web Notification API Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
fa964318f2
commit
e146a28416
4 changed files with 123 additions and 21 deletions
|
|
@ -162,6 +162,9 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
setIsBroadcasting(true);
|
||||
isBroadcastingRef.current = true;
|
||||
setStarting(false);
|
||||
if ((window as any).electronAPI?.showNotification) {
|
||||
(window as any).electronAPI.showNotification('Stream gestartet', 'Dein Stream ist jetzt live!');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'stream_available':
|
||||
|
|
@ -173,15 +176,15 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
title: msg.title,
|
||||
startedAt: new Date().toISOString(),
|
||||
viewerCount: 0,
|
||||
hasPassword: true,
|
||||
hasPassword: !!msg.hasPassword,
|
||||
}];
|
||||
});
|
||||
// Toast notification for new stream
|
||||
if (Notification.permission === 'granted') {
|
||||
new Notification('Neuer Stream', {
|
||||
body: `${msg.broadcasterName} streamt: ${msg.title}`,
|
||||
icon: '/assets/icon.png',
|
||||
});
|
||||
const notifBody = `${msg.broadcasterName} streamt: ${msg.title}`;
|
||||
if ((window as any).electronAPI?.showNotification) {
|
||||
(window as any).electronAPI.showNotification('Neuer Stream', notifBody);
|
||||
} else if (Notification.permission === 'granted') {
|
||||
new Notification('Neuer Stream', { body: notifBody, icon: '/assets/icon.png' });
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -343,7 +346,6 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
// ── Start broadcasting ──
|
||||
const startBroadcast = useCallback(async () => {
|
||||
if (!userName.trim()) { setError('Bitte gib einen Namen ein.'); return; }
|
||||
if (!streamPassword.trim()) { setError('Passwort ist Pflicht.'); return; }
|
||||
if (!navigator.mediaDevices?.getDisplayMedia) {
|
||||
setError('Dein Browser unterstützt keine Bildschirmfreigabe.');
|
||||
return;
|
||||
|
|
@ -365,7 +367,7 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
connectWs();
|
||||
const waitForWs = () => {
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||
wsSend({ type: 'start_broadcast', name: userName.trim(), title: streamTitle.trim() || 'Screen Share', password: streamPassword.trim() });
|
||||
wsSend({ type: 'start_broadcast', name: userName.trim(), title: streamTitle.trim() || 'Screen Share', password: streamPassword.trim() || undefined });
|
||||
} else { setTimeout(waitForWs, 100); }
|
||||
};
|
||||
waitForWs();
|
||||
|
|
@ -393,9 +395,25 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
}, [wsSend]);
|
||||
|
||||
// ── Join as viewer ──
|
||||
const joinStreamDirectly = useCallback((streamId: string) => {
|
||||
setError(null);
|
||||
setViewing({ streamId, phase: 'connecting' });
|
||||
connectWs();
|
||||
const waitForWs = () => {
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||
wsSend({ type: 'join_viewer', name: userName.trim() || 'Viewer', streamId });
|
||||
} else { setTimeout(waitForWs, 100); }
|
||||
};
|
||||
waitForWs();
|
||||
}, [userName, connectWs, wsSend]);
|
||||
|
||||
const openJoinModal = useCallback((s: StreamInfo) => {
|
||||
setJoinModal({ streamId: s.id, streamTitle: s.title, broadcasterName: s.broadcasterName, password: '', error: null });
|
||||
}, []);
|
||||
if (s.hasPassword) {
|
||||
setJoinModal({ streamId: s.id, streamTitle: s.title, broadcasterName: s.broadcasterName, password: '', error: null });
|
||||
} else {
|
||||
joinStreamDirectly(s.id);
|
||||
}
|
||||
}, [joinStreamDirectly]);
|
||||
|
||||
const submitJoinModal = useCallback(() => {
|
||||
if (!joinModal) return;
|
||||
|
|
@ -587,7 +605,7 @@ export default function StreamingTab({ data }: { data: any }) {
|
|||
<input
|
||||
className="stream-input stream-input-password"
|
||||
type="password"
|
||||
placeholder="Passwort"
|
||||
placeholder="Passwort (optional)"
|
||||
value={streamPassword}
|
||||
onChange={e => setStreamPassword(e.target.value)}
|
||||
disabled={isBroadcasting}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue