Commit graph

86 commits

Author SHA1 Message Date
Daniel
8bfc2b1b0a Fix: Deploy via Docker-Socket statt SSH
Runner laeuft auf adriahub - nutzt Docker-Socket direkt.
Secrets (Discord Tokens, Admin PWD) als CI-Variablen.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 14:03:37 +01:00
Daniel
0301ace4a0 Add: Auto-Deploy nach erfolgreichem CI Build
- Deploy-Stage in CI: SSH auf adriahub nach Build
- deploy.sh auf adriahub: pull + recreate Container
- Nur auf main Branch, nach erfolgreichem Build

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 14:00:45 +01:00
Daniel
7ef4eefc55 Add: Update-Dialog fuer Electron Auto-Updater
- main.js: IPC-Events an Renderer senden wenn Update bereit
- main.js: install-update IPC Handler (quitAndInstall)
- App.tsx: Update-Banner "Neues Update verfuegbar!" mit Restart-Button
- styles.css: Update-Bar Styling passend zum Design-System

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 13:53:26 +01:00
Daniel
06ab7f523b Add: Stream-Warnung beim Beenden der Electron App
Dialog "Stream laeuft noch!" erscheint nur wenn ein aktiver
Video-Stream erkannt wird. Ohne Stream schliesst die App normal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 13:49:53 +01:00
Daniel
5eab3c1956 Add: Auto-Updater fuer Electron App
- Server: /updates Endpoint fuer Squirrel.Windows Update-Feed
- Electron: autoUpdater mit 30min Check-Intervall
- Preload: IPC-Events fuer Update-Status ans Frontend

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 13:45:27 +01:00
Daniel
c8799710ac Fix: Screen Capture in Electron App aktivieren
setDisplayMediaRequestHandler hinzugefügt damit getDisplayMedia
in der Electron App funktioniert (Streaming/Bildschirmfreigabe).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 13:17:32 +01:00
Daniel
0646a1b46e Fix: Downloads aus /data/downloads servieren + Electron Build fixen
- Downloads-Pfad von relativem App-Pfad auf DATA_DIR/downloads geändert
- Electron package.json: author Feld ergänzt (Squirrel-Requirement)
- forge.config.js: fehlende Icon-Referenzen entfernt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 12:51:53 +01:00
Daniel
17dcd6073f V1.5.0: Version bump + Download-Button Redesign
- Version auf 1.5.0 in allen Packages, CI, Dockerfile, Electron
- Download-Button im gleichen Design wie Version-Badge (bg-secondary, radius, hover accent)
- CI Registry auf adriahub (192.168.1.100:9080) umgestellt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 12:38:51 +01:00
d6082f8dcf CI: Fix registry to use port 9080 (same as GitLab) 2026-03-07 11:31:13 +00:00
8bb6cec01b CI: Switch registry to adriahub (192.168.1.100:5050) 2026-03-07 11:24:38 +00:00
Daniel
09813b626f Watch Together: Embed-Fehlerbehandlung, klickbare Queue, Video-Titel
- YouTube onError Handler: Erkennt Error 101/150 (Embedding deaktiviert),
  zeigt Fehlermeldung + "Auf YouTube oeffnen" Link, auto-skip nach 3s
- Queue-Items klickbar fuer Host (play_video mit Index)
- Video-Titel werden via noembed.com oEmbed API geholt
- Server-Endpoint: GET /api/watch-together/video-info?url=...
- "Hinzufuegen" Button zeigt Ladezustand waehrend Titel-Fetch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 11:19:59 +01:00
Daniel
e4895a792c Fix: WebSocket-Konflikt zwischen Streaming und Watch Together
Root Cause: ws-Library killt WS-Verbindungen mit HTTP 400 wenn
mehrere WebSocketServer mit { server, path } registriert werden.
Der erste WSS (streaming) hat abortHandshake() fuer watch-together
Verbindungen aufgerufen.

- Beide WSS auf noServer-Modus umgestellt
- Zentrales upgrade-Routing in index.ts nach Pathname
- Frontend: connectWs prueft jetzt auch CONNECTING-State
- Frontend: onclose clobbert wsRef nur noch wenn gleicher Socket
- Frontend: waitForWs hat jetzt 10s Timeout statt Endlosschleife

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 11:02:36 +01:00
Daniel
e748fc97e9 Fix: Watch Together Raum-Erstellung + Download-Button
- Server/Frontend Protokoll-Mismatch behoben (create_room, join_room, room_created, room_joined, playback_state)
- togglePlay sendet jetzt korrekt pause/resume statt toggle_play
- Download-Button: SPA-Fallback fuer /downloads/ verhindert, 404 statt Reload
- download-Attribut am Link hinzugefuegt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 02:48:43 +01:00
Daniel
73f247ada3 Watch Together Plugin + Electron Desktop App mit Ad-Blocker
Neuer Tab: Watch Together - gemeinsam Videos schauen (w2g.tv-Style)
- Raum-System mit optionalem Passwort und Host-Kontrolle
- Video-Queue mit Hinzufuegen/Entfernen/Umordnen
- YouTube (IFrame API) + direkte Video-URLs (.mp4, .webm)
- Synchronisierte Wiedergabe via WebSocket (/ws/watch-together)
- Server-autoritative Playback-State mit Drift-Korrektur (2.5s Sync-Pulse)
- Host-Transfer bei Disconnect, Room-Cleanup nach 30s

Electron Desktop App (electron/):
- Wrapper fuer Gaming Hub mit integriertem Ad-Blocker
- uBlock-Style Request-Filtering via session.webRequest
- 100+ Ad-Domains + YouTube-spezifische Filter
- Download-Button im Web-Header (nur sichtbar wenn nicht in Electron)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 02:40:59 +01:00
Daniel
4943bbf4a1 Streams oeffnen standardmaessig in neuem Fenster mit Passwortabfrage
Klick auf Stream-Tile oeffnet neues Fenster via ?viewStream= URL.
Passwort-Modal erscheint automatisch im neuen Fenster nach dem Laden.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 02:11:16 +01:00
Daniel
59ebfc91a9 Fix: Auto-Join via ?viewStream= URL-Parameter funktioniert jetzt
App.tsx wechselt automatisch zum Streaming-Tab wenn ?viewStream= in der URL steht.
StreamingTab nutzt ref-basierten Ansatz: Stream-ID wird beim Mount gespeichert,
Passwort-Modal oeffnet sich sobald die Stream-Liste vom Server geladen ist.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 02:07:49 +01:00
Daniel
470bef62e4 Streaming: Stale-Stream Fix, Broadcast+View gleichzeitig, 3-Punkt-Menü
Server:
- Dual-Role: Client kann gleichzeitig broadcasten UND zuschauen
  (broadcastStreamId + viewingStreamId statt single role)
- POST /api/streaming/disconnect Beacon-Endpoint fuer
  zuverlaessigen Cleanup bei Page-Unload
- Heartbeat auf 5s reduziert (schnellere Erkennung)

Frontend:
- pagehide + sendBeacon: Streams werden sofort aufgeraeumt wenn
  Browser geschlossen/neugeladen wird
- ICE Routing: Broadcaster-Map wird zuerst geprueft, dann Viewer-PC
  → Broadcast + View im selben Tab moeglich
- 3-Punkt-Menü mit Stream-Details, "In neuem Fenster oeffnen" und
  "Link teilen" (Clipboard)
- Auto-Join via ?viewStream=... Query-Parameter (fuer geteilte Links)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:56:14 +01:00
Daniel
813e017036 Fix: Umlaut-Darstellung im Zurueck-Button korrigiert
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:45:47 +01:00
Daniel
3ef25fc10a Beforeunload-Warnung + Vollbild-Button fuer Viewer
- beforeunload Event verhindert versehentliches Verlassen/Reload
  waehrend Broadcasting oder Viewing aktiv ist
- Vollbild-Button im Viewer-Header (Fullscreen API)
- Fullscreen-State wird korrekt getrackt und Icon wechselt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:37:21 +01:00
Daniel
2ee36789b2 Stream auf 60 FPS + 8 Mbps Bitrate angehoben
- getDisplayMedia: frameRate ideal 60, 1920x1080
- WebRTC Sender: maxFramerate 60, maxBitrate 8 Mbps pro Viewer-PC

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:34:55 +01:00
Daniel
3f9b446f27 Fix: Schwarzes Bild bei Viewern - WebRTC Race Conditions behoben
- Doppelter Offer entfernt (onnegotiationneeded + explizit createOffer)
- ICE Candidate Queuing: Candidates werden gepuffert bis setRemoteDescription
  fertig ist, statt sie zu verwerfen
- Cleanup bei Re-Offer: vorherige PeerConnection wird sauber geschlossen
- Pending Candidates werden bei Disconnect/Leave aufgeraeumt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:16:09 +01:00
Daniel
c9378f4cdb Fix: Tab-Auswahl bleibt nach Reload erhalten (localStorage)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:10:01 +01:00
Daniel
4aed4e70ab fix(streaming): reliable disconnect + mandatory stream password
Disconnect:
- Server-side heartbeat ping/pong every 10s with 25s timeout
- Detects and cleans up dead connections (browser closed, network lost)
- ws.terminate() on heartbeat timeout triggers handleDisconnect

Password:
- Stream password is mandatory (server rejects start_broadcast without)
- Password stored server-side, never sent to clients
- Viewers must enter password via modal before joining
- Lock icon on tiles, WRONG_PASSWORD error shown in modal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 01:00:48 +01:00
Daniel
dacfde4328 fix(streaming): resolve stale closure preventing remote WebRTC connections
The WebSocket onmessage handler captured isBroadcasting=false at creation
time. When ICE candidates arrived from remote viewers, the handler looked
up viewerPcRef instead of peerConnectionsRef, dropping all candidates.

Fix: use refs (isBroadcastingRef, viewingRef, handleWsMessageRef) so the
WS handler always reads current state. connectWs() now has [] deps and
delegates to handleWsMessageRef.current for every message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:51:09 +01:00
Daniel
29bcf67121 feat(streaming): add Screen Streaming plugin with WebRTC
New plugin: browser-based screen sharing via Chrome Screen Capture API.
Multi-stream grid layout (Rustdesk-style tiles) with live previews.

- Server: WebSocket signaling at /ws/streaming (SDP/ICE relay)
- Server: http.createServer for WebSocket attachment
- Frontend: StreamingTab with broadcaster/viewer modes
- Frontend: tile grid, fullscreen viewer, LIVE badges
- Supports multiple concurrent streams
- Peer-to-peer video via WebRTC (no video through server)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:39:49 +01:00
Daniel
9ff8a38547 feat(soundboard): download modal with filename input + fix yt-dlp binary
- Add download modal: filename input, progress phases (input/downloading/done/error)
- Refactor backend: shared handleUrlDownload() with optional custom filename + rename
- Fix Dockerfile: use yt-dlp_linux standalone binary (no Python dependency)
- Modal shows URL type badge (YouTube/Instagram/MP3), spinner, retry on error

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:59:31 +01:00
Daniel
0b2ba0ef86 feat(soundboard): add comprehensive yt-dlp and URL download logging
Every step now logged with timestamps, exit codes, stdout/stderr
streaming in real-time. yt-dlp runs with --verbose flag. Routes log
URL type detection, download progress, normalization, play status.
Error messages include HTTP status codes and specific yt-dlp errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:47:24 +01:00
Daniel
df937f3e40 fix(soundboard): auto-prepend https:// for URL import
URLs pasted without protocol (e.g. instagram.com/reel/...) now work.
normalizeUrl() adds https:// if missing. Input type changed from
"url" to "text" to prevent browser validation blocking paste.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:45:41 +01:00
Daniel
200f03c1f8 feat(soundboard): extend URL download to support YouTube & Instagram
yt-dlp extracts audio as MP3 from YouTube and Instagram links.
Direct MP3 links continue to work as before. URL input field now shows
a type indicator (YT/IG/MP3) and validates all three formats.

Backend: downloadWithYtDlp() spawns yt-dlp with --extract-audio,
saves to SOUNDS_DIR, normalizes if enabled. New /download-url route
for save-only without auto-play. play-url route extended for all types.

Frontend: isSupportedUrl() validates YouTube/Instagram/MP3, dynamic
icon changes per URL type, disabled state when URL is unsupported.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:38:09 +01:00
Daniel
06326de465 feat(radio): add 2-second pre-buffer to reduce audio lag
Buffer 384 KB of PCM data (~2 seconds at 48kHz stereo) via PassThrough
stream before starting Discord playback. Falls back to immediate start
after 3s timeout for slow streams. Cleanup integrated into stopAudio.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:28:58 +01:00
Daniel
7786d02f86 fix: globe clickable after tab switch — deferred init with ResizeObserver
Globe.gl needs non-zero container dimensions for initialization and click
handling. With the tab persistence fix (display:none for hidden tabs), the
globe container starts at 0×0 when radio isn't the first tab. Added a
separate ResizeObserver that detects when the container becomes visible
and triggers globe initialization via containerVisible state dependency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:17:48 +01:00
Daniel
056024d753 fix: preserve Radio tab play state across tab switches
All tab components are now always mounted and hidden via CSS
(display: none/contents) instead of conditionally rendered.
This prevents React state from being destroyed on tab switch,
so nowPlaying, SSE handlers, and stop buttons keep working.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:04:49 +01:00
Daniel
09396dafce feat(lolstats): add Riot API hybrid — show ALL game modes (URF, Brawl, etc.)
op.gg REST API doesn't track featured game modes (URF, ARAM Mayhem/Brawl).
Now uses Riot API for match history when RIOT_API_KEY env var is set,
falling back to op.gg REST for profile/ranked stats (no key needed).

- Add Riot API match fetcher with region routing (europe/americas/asia/sea)
- Add DDragon champion ID→name mapping for Riot API matches
- Add queue ID→name mapping (420=Ranked, 450=ARAM, 900=URF, etc.)
- Transform Riot match data to existing MatchEntry interface
- Batch match detail requests (5 at a time) for rate limit safety
- Keep op.gg REST as fallback when no API key is configured

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:54:53 +01:00
Daniel
87279933c3 refactor(lolstats): switch from MCP to op.gg REST API for fresh data
- Profile now fetched via REST API (summoner lookup + summary endpoint)
- Match history via REST API games endpoint (proper JSON, no parser)
- All 10 players per game returned directly (no separate detail fetch)
- DDragon champion ID→name mapping loaded at startup
- Fixed summoner_id lookup to use # separator (was using - which failed)
- MCP kept as fallback for match detail and edge cases
- Frontend: find "me" by summoner name instead of assuming index 0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:41:48 +01:00
Daniel
f4c8cce2f9 feat(lolstats): add data renewal — auto-refresh stale op.gg data
Uses op.gg REST API to trigger summoner data renewal before fetching
stats via MCP. Adds Update button in profile header for manual refresh.
Flow: lookup summoner_id → POST renewal → poll until finish → re-fetch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:22:32 +01:00
Daniel
40c596fbfa feat: add LoL Stats plugin — op.gg-powered player lookup
New plugin for League of Legends stats tracking, similar to op.gg:
- Search summoners by Riot ID (Name#Tag) + region
- Profile overview: rank, tier, LP, win rate, ladder position
- Top champions with KDA and win rates
- Match history with KDA, CS, items, game duration
- Expandable match details showing all 10 players
- Recent searches persisted across restarts

Uses op.gg MCP server (no API key needed, no 24h expiration).
Backend: server/src/plugins/lolstats/ (3 files)
Frontend: web/src/plugins/lolstats/ (2 files)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:04:45 +01:00
Daniel
24b4dadb0f fix: move hub-state.json to /data/sounds/ for persistence
Root cause: only /data/sounds/ survives container recreation (it's the
volume-mounted directory). /data/hub-state.json was written to the
container's ephemeral layer and lost on every redeploy.

- State file now saved to /data/sounds/hub-state.json
- Auto-migrates from legacy /data/hub-state.json if found
- Favorites and radio volumes will now persist across deploys

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:28:03 +01:00
Daniel
55f311c612 fix: state persistence diagnostics + fallback write
- loadState: logs DATA_DIR path, writability check, lists files, shows
  radio_favorites count on load
- saveState: read-back verification after atomic write, fallback to
  direct write if rename fails
- /api/health: shows state diagnostics (file exists, file size, keys,
  favorites count in memory vs disk, lastSaveOk)
- Helps diagnose why favorites are not persisting across deploys

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:22:07 +01:00
Daniel
fcf4ba86ce chore: globe texture q90 statt q85
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:13:53 +01:00
Daniel
5793a8f358 Optimize: Resize NASA Blue Marble from 28MB to 1.1MB
Original was 21600x10800 (full NASA resolution) - way too large
for a WebGL texture. Resized to 4096x2048 (max useful for globe.gl)
at JPEG quality 85 with progressive loading.

28 MB → 1.1 MB = 96% smaller, dramatically faster initial load.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:10:02 +01:00
Daniel
dd71d763cd Feat: Show member count in soundboard channel dropdown
Add voice channel member count (non-bot users) to soundboard
channel API response and display it in the dropdown, matching
the radio plugin's existing behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:02:00 +01:00
Daniel
9aefc3d470 Harden state persistence: atomic writes + backup fallback
- Atomic save: write to .tmp file then rename (prevents corruption
  if container is killed mid-write)
- Backup: .bak copy created on successful load, used as fallback
  if main file is corrupted
- Startup log shows loaded keys (verifies favorites survived)

Ensures radio_favorites and radio_volumes survive container
updates, crashes, and forced restarts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 19:58:23 +01:00
Daniel
262cc9f213 Fix: Radio sidebar close + topbar layout consistency
1. FAB button (favorites star) now hidden when any panel is open.
   Previously the FAB (z-index:20) covered the panel close button
   (z-index:15), making it impossible to close the station sidebar.

2. Added margin-left:auto to radio-topbar-right so theme dots
   always stay right-aligned. Previously they sat left when no
   station was playing (missing flex:1 now-playing spacer).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 19:51:04 +01:00
Daniel
b4f3c7db4d Fix: Stop globe rotation while station panel is open
Auto-rotation now stops completely when a station list or
favorites sidebar is visible. Resumes only when all panels
are closed. Interaction-based pause (drag/scroll) also
respects open panels - won't resume after 5s timeout if
a sidebar is still showing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 19:40:35 +01:00
Daniel
1b2fbe27ed Fix: Size-based dot radius for radio stations
Restore d.size-dependent point radius so larger cities (more
stations) show bigger dots like Radio Garden. Formula:
radius = clamp(0.12, 0.45, 0.06 + size * 0.005)

Zoom scaling also respects per-station size.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 19:37:34 +01:00
Daniel
a923463f83 Fix: Crisp radio station dots with zoom-scaling
Replace sprite-based markers (objectsData + SpriteMaterial with
soft 64x64 gradient texture → blurry at zoom) with optimized
point meshes:

- pointResolution(24): smooth 24-sided circles (no hexagons)
- pointAltitude(0.001): nearly flat on surface (no cylinder effect)
- sqrt-based zoom scaling: dots shrink when zooming in, grow when
  zooming out → visually consistent at all zoom levels
- Removed three.js Sprite/SpriteMaterial/CanvasTexture imports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:30:42 +01:00
Daniel
99421f4577 Fix radio marker object placement 2026-03-06 16:15:44 +01:00
Daniel
3d59eda3da Fix radio marker sprites and size scaling 2026-03-06 14:08:09 +01:00
Daniel
cf113f65ca Update README: NASA Blue Marble texture + sprite particles docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:06:40 +01:00
Daniel
d55aaf71b1 Render radio stations as sprite particles 2026-03-06 13:58:50 +01:00