- 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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
- Radio: Move controls from bottom bar to topbar above globe
(guild/channel selectors, now playing, volume, connection, stop)
- Radio: Theme selector moved inline into topbar-right
- Radio: Globe now fills remaining space below topbar
- Soundboard: Fix connection modal not opening when voiceStats is null
(modal now renders with '---' values when no voice data available)
- Both: Modal condition changed from `showConnModal && voiceStats` to
just `showConnModal`, handling null voiceStats gracefully
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Globe was black because Radio Garden CDN (rg-tiles.b-cdn.net) returns
403 without Referer: radio.garden header. Added server-side tile proxy
/api/radio/tile/:z/:x/:y with in-memory cache (max 500 tiles).
- Added radio_voicestats SSE broadcast (every 5s) with voice ping,
gateway ping, status, channel name, and connected-since timestamp.
- Added clickable "Verbunden" connection indicator in RadioTab bottom
bar with live ping display and connection details modal (matching
soundboard's existing modal pattern).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace static earth texture with tile-based rendering from CDN.
Tiles load progressively (zoom 1→2→3 base), then dynamically
load zoom 4-6 as user zooms in. Mercator→equirectangular
reprojection via strip-based canvas compositing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Search API: read title/subtitle/url from _source.page (nested)
- Channel click: extract correct ID from URL (last segment)
- Replace earth texture with higher-res 4096x2048 original
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
NASA's image server blocks cross-origin requests. Downloaded the
3600x1800 Black Marble texture and serve it from web/public/.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Set renderer pixel ratio to window.devicePixelRatio for crisp
rendering on Retina/HiDPI displays (was defaulting to 1)
- Upgraded Earth texture from low-res three-globe example (~2K)
to NASA Black Marble 3600x1800 for sharp detail when zooming
- Added pointResolution(6) for cleaner point geometry
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pointRadius was recalculated on every controls change event (including
rotation frames), causing 50k+ points to re-render each frame.
Now only triggers when altitude changes >5%, keeping rotation smooth.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- SSE handler now distinguishes per-guild events (single NowPlaying)
from snapshots (Record<string, NowPlaying>) to prevent state corruption
- Theme selector moved left (right: 72px) to not overlap favorites button
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Points now shrink proportionally when zooming in, preventing
overlap in dense areas and making individual stations clickable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 5 themes: Sunset (default), Midnight, Forest, Ocean, Cherry
- Theme selector dots in top-right corner, persisted to localStorage
- Globe accent colors (points, atmosphere) update with theme
- Globe pauses auto-rotation for 5s on any interaction (click, drag, scroll)
- All radio CSS vars scoped to .radio-container[data-theme]
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Server: inlineVolume on AudioResource, POST /api/radio/volume endpoint
- Volume persisted per guild, broadcast via SSE to all clients
- Frontend: volume slider in bottom bar with debounced API calls
- Volume icon changes based on level (muted/low/normal)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rotation continued while browsing stations, making selection impossible.
Now stops immediately when clicking any radio point.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Radio Garden API Client (30K+ Orte, Sender-Suche, Stream-URL Auflösung)
- Discord Voice Streaming via ffmpeg (PCM Pipeline)
- Interactive 3D Globe (globe.gl) mit allen Radiosender-Standorten
- Sender-Panel mit Play/Stop/Favoriten
- Live-Suche nach Sendern und Städten
- Now-Playing Bar mit Equalizer-Animation
- Guild/Voice-Channel Auswahl
- SSE Broadcasting für Live-Updates
- Favoriten-System mit Persistenz
- Responsive Design (Mobile/Tablet/Desktop)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>