Backend optimizations, frontend optimizations, and UI design improvements

Backend optimizations:
- GZip middleware for compressed responses
- Concurrent WebSocket broadcast
- Skip status polling when no clients connected
- Deduplicated token validation with caching
- Fire-and-forget HA state callbacks
- Single stat() per browser item
- Metadata caching (LRU)
- M3U playlist optimization
- Autostart setup (Task Scheduler + hidden VBS launcher)

Frontend code optimizations:
- Fix thumbnail blob URL memory leak
- Fix WebSocket ping interval leak on reconnect
- Skip artwork re-fetch when same track playing
- Deduplicate volume slider logic
- Extract magic numbers into named constants
- Standardize error handling with toast notifications
- Cache play/pause SVG constants
- Loading state management for async buttons
- Request deduplication for rapid clicks
- Cache 30+ DOM element references
- Deduplicate volume updates over WebSocket

Frontend design improvements:
- Progress bar seek thumb and hover expansion
- Custom themed scrollbars
- Toast notification accent border strips
- Keyboard focus-visible states
- Album art ambient glow effect
- Animated sliding tab indicator
- Mini-player top progress line
- Empty state SVG illustrations
- Responsive tablet breakpoint (601-900px)
- Horizontal player layout on wide screens (>900px)
- Glassmorphism mini-player with backdrop blur
- Vinyl spin animation (toggleable)
- Table horizontal scroll on narrow screens

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 20:38:35 +03:00
parent d1ec27cb7b
commit 84b985e6df
13 changed files with 926 additions and 348 deletions

View File

@@ -49,24 +49,27 @@ class ConnectionManager:
)
async def broadcast(self, message: dict[str, Any]) -> None:
"""Broadcast a message to all connected clients."""
"""Broadcast a message to all connected clients concurrently."""
async with self._lock:
connections = list(self._active_connections)
if not connections:
return
disconnected = []
for websocket in connections:
async def _send(ws: WebSocket) -> WebSocket | None:
try:
await websocket.send_json(message)
await ws.send_json(message)
return None
except Exception as e:
logger.debug("Failed to send to client: %s", e)
disconnected.append(websocket)
return ws
results = await asyncio.gather(*(_send(ws) for ws in connections))
# Clean up disconnected clients
for ws in disconnected:
await self.disconnect(ws)
for ws in results:
if ws is not None:
await self.disconnect(ws)
async def broadcast_scripts_changed(self) -> None:
"""Notify all connected clients that scripts have changed."""
@@ -156,26 +159,25 @@ class ConnectionManager:
async with self._lock:
has_clients = len(self._active_connections) > 0
if has_clients:
status = await get_status_func()
status_dict = status.model_dump()
if not has_clients:
await asyncio.sleep(self._poll_interval)
continue
# Only broadcast on actual state changes
# Let HA handle position interpolation during playback
if self.status_changed(self._last_status, status_dict):
self._last_status = status_dict
self._last_broadcast_time = time.time()
await self.broadcast(
{"type": "status_update", "data": status_dict}
)
logger.debug("Broadcast sent: status change")
else:
# Update cached status even without broadcast
self._last_status = status_dict
status = await get_status_func()
status_dict = status.model_dump()
# Only broadcast on actual state changes
# Let HA handle position interpolation during playback
if self.status_changed(self._last_status, status_dict):
self._last_status = status_dict
self._last_broadcast_time = time.time()
await self.broadcast(
{"type": "status_update", "data": status_dict}
)
logger.debug("Broadcast sent: status change")
else:
# Still update cache for when clients connect
status = await get_status_func()
self._last_status = status.model_dump()
# Update cached status even without broadcast
self._last_status = status_dict
await asyncio.sleep(self._poll_interval)