Codebase audit fixes: stability, performance, accessibility
- Fix CORS: set allow_credentials=False (token auth, not cookies) - Add threading.Lock for position cache thread safety - Add shutdown_executor() for clean ThreadPoolExecutor cleanup - Dedicated ThreadPoolExecutors for script/callback execution - Fix Mutagen file handle leaks with try/finally close - Reduce idle WebSocket polling (0.5s → 2.0s when no clients) - Add :focus-visible styles for playback control buttons - Add aria-label to icon-only header buttons - Dynamic album art alt text for screen readers - Persist MDI icon cache to localStorage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import threading
|
||||
import time as _time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Optional, Any
|
||||
|
||||
@@ -16,8 +18,10 @@ _executor = ThreadPoolExecutor(max_workers=2, thread_name_prefix="winrt")
|
||||
# Global storage for current album art (as bytes)
|
||||
_current_album_art_bytes: bytes | None = None
|
||||
|
||||
# Lock protecting _position_cache and _track_skip_pending from concurrent access
|
||||
_position_lock = threading.Lock()
|
||||
|
||||
# Global storage for position tracking
|
||||
import time as _time
|
||||
_position_cache = {
|
||||
"track_id": "",
|
||||
"base_position": 0.0,
|
||||
@@ -224,124 +228,125 @@ def _sync_get_media_status() -> dict[str, Any]:
|
||||
is_playing = result["state"] == "playing"
|
||||
current_title = result.get('title', '')
|
||||
|
||||
# Check if track skip is pending and title changed
|
||||
skip_just_completed = False
|
||||
if _track_skip_pending["active"]:
|
||||
if current_title and current_title != _track_skip_pending["old_title"]:
|
||||
# Title changed - clear the skip flag and start grace period
|
||||
_track_skip_pending["active"] = False
|
||||
_track_skip_pending["old_title"] = ""
|
||||
_track_skip_pending["grace_until"] = current_time + 300.0 # Long grace period
|
||||
_track_skip_pending["stale_pos"] = -999 # Reset stale position tracking
|
||||
skip_just_completed = True
|
||||
# Reset position cache for new track
|
||||
new_track_id = f"{current_title}:{result.get('artist', '')}:{result.get('duration', 0)}"
|
||||
_position_cache["track_id"] = new_track_id
|
||||
with _position_lock:
|
||||
# Check if track skip is pending and title changed
|
||||
skip_just_completed = False
|
||||
if _track_skip_pending["active"]:
|
||||
if current_title and current_title != _track_skip_pending["old_title"]:
|
||||
# Title changed - clear the skip flag and start grace period
|
||||
_track_skip_pending["active"] = False
|
||||
_track_skip_pending["old_title"] = ""
|
||||
_track_skip_pending["grace_until"] = current_time + 300.0 # Long grace period
|
||||
_track_skip_pending["stale_pos"] = -999 # Reset stale position tracking
|
||||
skip_just_completed = True
|
||||
# Reset position cache for new track
|
||||
new_track_id = f"{current_title}:{result.get('artist', '')}:{result.get('duration', 0)}"
|
||||
_position_cache["track_id"] = new_track_id
|
||||
_position_cache["base_position"] = 0.0
|
||||
_position_cache["base_time"] = current_time
|
||||
_position_cache["last_smtc_pos"] = -999 # Force fresh start
|
||||
_position_cache["is_playing"] = is_playing
|
||||
logger.debug(f"Track skip complete, new title: {current_title}, grace until: {_track_skip_pending['grace_until']}")
|
||||
elif current_time - _track_skip_pending["skip_time"] > 5.0:
|
||||
# Timeout after 5 seconds
|
||||
_track_skip_pending["active"] = False
|
||||
logger.debug("Track skip timeout")
|
||||
|
||||
# Check if we're in grace period (after skip, ignore high SMTC positions)
|
||||
in_grace_period = current_time < _track_skip_pending.get("grace_until", 0)
|
||||
|
||||
# If track skip is pending or just completed, use cached/reset position
|
||||
if _track_skip_pending["active"]:
|
||||
pos = 0.0
|
||||
_position_cache["base_position"] = 0.0
|
||||
_position_cache["base_time"] = current_time
|
||||
_position_cache["last_smtc_pos"] = -999 # Force fresh start
|
||||
_position_cache["is_playing"] = is_playing
|
||||
logger.debug(f"Track skip complete, new title: {current_title}, grace until: {_track_skip_pending['grace_until']}")
|
||||
elif current_time - _track_skip_pending["skip_time"] > 5.0:
|
||||
# Timeout after 5 seconds
|
||||
_track_skip_pending["active"] = False
|
||||
logger.debug("Track skip timeout")
|
||||
elif skip_just_completed:
|
||||
# Just completed skip - interpolate from 0
|
||||
if is_playing:
|
||||
elapsed = current_time - _position_cache["base_time"]
|
||||
pos = elapsed
|
||||
else:
|
||||
pos = 0.0
|
||||
elif in_grace_period:
|
||||
# Grace period after track skip
|
||||
# SMTC position is stale (from old track) and won't update until seek/pause
|
||||
# We interpolate from 0 and only trust SMTC when it changes or reports low value
|
||||
|
||||
# Check if we're in grace period (after skip, ignore high SMTC positions)
|
||||
in_grace_period = current_time < _track_skip_pending.get("grace_until", 0)
|
||||
# Calculate interpolated position from start of new track
|
||||
if is_playing:
|
||||
elapsed = current_time - _position_cache.get("base_time", current_time)
|
||||
interpolated_pos = _position_cache.get("base_position", 0.0) + elapsed
|
||||
else:
|
||||
interpolated_pos = _position_cache.get("base_position", 0.0)
|
||||
|
||||
# If track skip is pending or just completed, use cached/reset position
|
||||
if _track_skip_pending["active"]:
|
||||
pos = 0.0
|
||||
_position_cache["base_position"] = 0.0
|
||||
_position_cache["base_time"] = current_time
|
||||
_position_cache["is_playing"] = is_playing
|
||||
elif skip_just_completed:
|
||||
# Just completed skip - interpolate from 0
|
||||
if is_playing:
|
||||
elapsed = current_time - _position_cache["base_time"]
|
||||
pos = elapsed
|
||||
# Get the stale position we've been tracking
|
||||
stale_pos = _track_skip_pending.get("stale_pos", -999)
|
||||
|
||||
# Detect if SMTC position changed significantly from the stale value (user seeked)
|
||||
smtc_changed = stale_pos >= 0 and abs(smtc_pos - stale_pos) > 3.0
|
||||
|
||||
# Trust SMTC if:
|
||||
# 1. It reports a low position (indicating new track started)
|
||||
# 2. It changed from the stale value (user seeked)
|
||||
if smtc_pos < 10.0 or smtc_changed:
|
||||
# SMTC is now trustworthy
|
||||
_position_cache["base_position"] = smtc_pos
|
||||
_position_cache["base_time"] = current_time
|
||||
_position_cache["last_smtc_pos"] = smtc_pos
|
||||
_position_cache["is_playing"] = is_playing
|
||||
pos = smtc_pos
|
||||
_track_skip_pending["grace_until"] = 0
|
||||
_track_skip_pending["stale_pos"] = -999
|
||||
logger.debug(f"Grace period: accepting SMTC pos {smtc_pos} (low={smtc_pos < 10}, changed={smtc_changed})")
|
||||
else:
|
||||
# SMTC is stale - keep interpolating
|
||||
pos = interpolated_pos
|
||||
# Record the stale position for change detection
|
||||
if stale_pos < 0:
|
||||
_track_skip_pending["stale_pos"] = smtc_pos
|
||||
# Keep grace period active indefinitely while SMTC is stale
|
||||
_track_skip_pending["grace_until"] = current_time + 300.0
|
||||
logger.debug(f"Grace period: SMTC stale ({smtc_pos}), using interpolated {interpolated_pos}")
|
||||
else:
|
||||
pos = 0.0
|
||||
elif in_grace_period:
|
||||
# Grace period after track skip
|
||||
# SMTC position is stale (from old track) and won't update until seek/pause
|
||||
# We interpolate from 0 and only trust SMTC when it changes or reports low value
|
||||
# Normal position tracking
|
||||
# Create track ID from title + artist + duration
|
||||
track_id = f"{current_title}:{result.get('artist', '')}:{result.get('duration', 0)}"
|
||||
|
||||
# Calculate interpolated position from start of new track
|
||||
if is_playing:
|
||||
elapsed = current_time - _position_cache.get("base_time", current_time)
|
||||
interpolated_pos = _position_cache.get("base_position", 0.0) + elapsed
|
||||
else:
|
||||
interpolated_pos = _position_cache.get("base_position", 0.0)
|
||||
# Detect if SMTC position changed (new track, seek, or state change)
|
||||
smtc_pos_changed = abs(smtc_pos - _position_cache.get("last_smtc_pos", -999)) > 0.5
|
||||
track_changed = track_id != _position_cache.get("track_id", "")
|
||||
|
||||
# Get the stale position we've been tracking
|
||||
stale_pos = _track_skip_pending.get("stale_pos", -999)
|
||||
if smtc_pos_changed or track_changed:
|
||||
# SMTC updated - store new baseline
|
||||
_position_cache["track_id"] = track_id
|
||||
_position_cache["last_smtc_pos"] = smtc_pos
|
||||
_position_cache["base_position"] = smtc_pos
|
||||
_position_cache["base_time"] = current_time
|
||||
_position_cache["is_playing"] = is_playing
|
||||
pos = smtc_pos
|
||||
elif is_playing:
|
||||
# Interpolate position based on elapsed time
|
||||
elapsed = current_time - _position_cache.get("base_time", current_time)
|
||||
pos = _position_cache.get("base_position", smtc_pos) + elapsed
|
||||
else:
|
||||
# Paused - use base position
|
||||
pos = _position_cache.get("base_position", smtc_pos)
|
||||
|
||||
# Detect if SMTC position changed significantly from the stale value (user seeked)
|
||||
smtc_changed = stale_pos >= 0 and abs(smtc_pos - stale_pos) > 3.0
|
||||
# Update playing state
|
||||
if _position_cache.get("is_playing") != is_playing:
|
||||
_position_cache["base_position"] = pos if is_playing else _position_cache.get("base_position", smtc_pos)
|
||||
_position_cache["base_time"] = current_time
|
||||
_position_cache["is_playing"] = is_playing
|
||||
|
||||
# Trust SMTC if:
|
||||
# 1. It reports a low position (indicating new track started)
|
||||
# 2. It changed from the stale value (user seeked)
|
||||
if smtc_pos < 10.0 or smtc_changed:
|
||||
# SMTC is now trustworthy
|
||||
_position_cache["base_position"] = smtc_pos
|
||||
_position_cache["base_time"] = current_time
|
||||
_position_cache["last_smtc_pos"] = smtc_pos
|
||||
_position_cache["is_playing"] = is_playing
|
||||
pos = smtc_pos
|
||||
_track_skip_pending["grace_until"] = 0
|
||||
_track_skip_pending["stale_pos"] = -999
|
||||
logger.debug(f"Grace period: accepting SMTC pos {smtc_pos} (low={smtc_pos < 10}, changed={smtc_changed})")
|
||||
else:
|
||||
# SMTC is stale - keep interpolating
|
||||
pos = interpolated_pos
|
||||
# Record the stale position for change detection
|
||||
if stale_pos < 0:
|
||||
_track_skip_pending["stale_pos"] = smtc_pos
|
||||
# Keep grace period active indefinitely while SMTC is stale
|
||||
_track_skip_pending["grace_until"] = current_time + 300.0
|
||||
logger.debug(f"Grace period: SMTC stale ({smtc_pos}), using interpolated {interpolated_pos}")
|
||||
else:
|
||||
# Normal position tracking
|
||||
# Create track ID from title + artist + duration
|
||||
track_id = f"{current_title}:{result.get('artist', '')}:{result.get('duration', 0)}"
|
||||
|
||||
# Detect if SMTC position changed (new track, seek, or state change)
|
||||
smtc_pos_changed = abs(smtc_pos - _position_cache.get("last_smtc_pos", -999)) > 0.5
|
||||
track_changed = track_id != _position_cache.get("track_id", "")
|
||||
|
||||
if smtc_pos_changed or track_changed:
|
||||
# SMTC updated - store new baseline
|
||||
_position_cache["track_id"] = track_id
|
||||
_position_cache["last_smtc_pos"] = smtc_pos
|
||||
_position_cache["base_position"] = smtc_pos
|
||||
_position_cache["base_time"] = current_time
|
||||
_position_cache["is_playing"] = is_playing
|
||||
pos = smtc_pos
|
||||
elif is_playing:
|
||||
# Interpolate position based on elapsed time
|
||||
elapsed = current_time - _position_cache.get("base_time", current_time)
|
||||
pos = _position_cache.get("base_position", smtc_pos) + elapsed
|
||||
else:
|
||||
# Paused - use base position
|
||||
pos = _position_cache.get("base_position", smtc_pos)
|
||||
|
||||
# Update playing state
|
||||
if _position_cache.get("is_playing") != is_playing:
|
||||
_position_cache["base_position"] = pos if is_playing else _position_cache.get("base_position", smtc_pos)
|
||||
_position_cache["base_time"] = current_time
|
||||
_position_cache["is_playing"] = is_playing
|
||||
|
||||
# Sanity check: position should be non-negative and <= duration
|
||||
if pos >= 0:
|
||||
if result["duration"] and pos <= result["duration"]:
|
||||
result["position"] = pos
|
||||
elif result["duration"] and pos > result["duration"]:
|
||||
result["position"] = result["duration"]
|
||||
elif not result["duration"]:
|
||||
result["position"] = pos
|
||||
# Sanity check: position should be non-negative and <= duration
|
||||
if pos >= 0:
|
||||
if result["duration"] and pos <= result["duration"]:
|
||||
result["position"] = pos
|
||||
elif result["duration"] and pos > result["duration"]:
|
||||
result["position"] = result["duration"]
|
||||
elif not result["duration"]:
|
||||
result["position"] = pos
|
||||
|
||||
logger.debug(f"Timeline: duration={result['duration']}, position={result['position']}")
|
||||
except Exception as e:
|
||||
@@ -483,6 +488,11 @@ def _sync_seek(position: float) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def shutdown_executor() -> None:
|
||||
"""Shut down the WinRT thread pool executor."""
|
||||
_executor.shutdown(wait=False)
|
||||
|
||||
|
||||
class WindowsMediaController(MediaController):
|
||||
"""Media controller for Windows using WinRT and pycaw."""
|
||||
|
||||
@@ -602,10 +612,10 @@ class WindowsMediaController(MediaController):
|
||||
|
||||
result = await self._run_command("next")
|
||||
if result:
|
||||
# Set flag to force position to 0 until title changes
|
||||
_track_skip_pending["active"] = True
|
||||
_track_skip_pending["old_title"] = old_title
|
||||
_track_skip_pending["skip_time"] = _time.time()
|
||||
with _position_lock:
|
||||
_track_skip_pending["active"] = True
|
||||
_track_skip_pending["old_title"] = old_title
|
||||
_track_skip_pending["skip_time"] = _time.time()
|
||||
logger.debug(f"Track skip initiated, old title: {old_title}")
|
||||
return result
|
||||
|
||||
@@ -620,10 +630,10 @@ class WindowsMediaController(MediaController):
|
||||
|
||||
result = await self._run_command("previous")
|
||||
if result:
|
||||
# Set flag to force position to 0 until title changes
|
||||
_track_skip_pending["active"] = True
|
||||
_track_skip_pending["old_title"] = old_title
|
||||
_track_skip_pending["skip_time"] = _time.time()
|
||||
with _position_lock:
|
||||
_track_skip_pending["active"] = True
|
||||
_track_skip_pending["old_title"] = old_title
|
||||
_track_skip_pending["skip_time"] = _time.time()
|
||||
logger.debug(f"Track skip initiated, old title: {old_title}")
|
||||
return result
|
||||
|
||||
|
||||
Reference in New Issue
Block a user