♻️ refactor(music_player, mpris): add visibility debouncing and handle invalid player positions
This commit is contained in:
2 files changed
+90
-8
No files matched your search
@@ -171,6 +171,10 @@ class MusicPlayer(Box):
|
||||
self._last_player_switch = 0
|
||||
self._switch_debounce_time = 1.0 # 1 second minimum between switches
|
||||
|
||||
# Visibility debouncing to prevent immediate hiding when player closes
|
||||
self._visibility_timeout = None
|
||||
self._hide_delay = 2.0 # 2 seconds delay before hiding when no players
|
||||
|
||||
# Create circular progress bar
|
||||
self.progress_bar = CircularProgressBar(
|
||||
name="player-circle",
|
||||
@@ -239,6 +243,11 @@ class MusicPlayer(Box):
|
||||
GLib.source_remove(self._hover_timeout)
|
||||
self._hover_timeout = None
|
||||
|
||||
# Remove visibility timeout
|
||||
if hasattr(self, "_visibility_timeout") and self._visibility_timeout:
|
||||
GLib.source_remove(self._visibility_timeout)
|
||||
self._visibility_timeout = None
|
||||
|
||||
# Clean up current player service
|
||||
if hasattr(self, 'current_player_service') and self.current_player_service:
|
||||
try:
|
||||
@@ -730,6 +739,20 @@ class MusicPlayer(Box):
|
||||
def has_running_players(self):
|
||||
"""Check if there are any players that actually have media content running"""
|
||||
try:
|
||||
# First check if we have a current player that's still valid
|
||||
if self.current_player and self.current_player_service:
|
||||
try:
|
||||
# Check if current player still exists and has media
|
||||
if hasattr(self.current_player.props, "metadata"):
|
||||
metadata = self.current_player.props.metadata
|
||||
if metadata and len(metadata.keys()) > 0:
|
||||
keys = metadata.keys()
|
||||
if ("xesam:title" in keys or "xesam:artist" in keys or "mpris:length" in keys):
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Check all available players
|
||||
if not hasattr(self.manager, "_manager") or not hasattr(self.manager._manager, "props"):
|
||||
return False
|
||||
|
||||
@@ -762,23 +785,30 @@ class MusicPlayer(Box):
|
||||
or "mpris:length" in keys
|
||||
):
|
||||
return True
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Also check if player is currently playing (even without full metadata)
|
||||
# But be more strict - only count as "has media" if actually playing
|
||||
if hasattr(player.props, "playback_status"):
|
||||
try:
|
||||
status = player.props.playback_status
|
||||
if (status and hasattr(status, 'value_name') and
|
||||
status.value_name == "PLAYERCTL_PLAYBACK_STATUS_PLAYING"):
|
||||
return True
|
||||
except Exception as e:
|
||||
# Double-check that this isn't just a phantom player
|
||||
# by trying to get position or metadata
|
||||
try:
|
||||
player.get_position() # This will fail if player is dead
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return False
|
||||
@@ -792,6 +822,31 @@ class MusicPlayer(Box):
|
||||
self.set_visible(False)
|
||||
return
|
||||
|
||||
# Show component only if there are players with actual media content
|
||||
# Check if there are players with actual media content
|
||||
has_media_players = self.has_running_players()
|
||||
self.set_visible(has_media_players)
|
||||
|
||||
if has_media_players:
|
||||
# Cancel any pending hide timeout
|
||||
if self._visibility_timeout:
|
||||
GLib.source_remove(self._visibility_timeout)
|
||||
self._visibility_timeout = None
|
||||
# Show immediately when media is available
|
||||
self.set_visible(True)
|
||||
else:
|
||||
# Don't hide immediately - use a delay to prevent flickering
|
||||
# when players are restarting or switching
|
||||
if not self._visibility_timeout:
|
||||
self._visibility_timeout = GLib.timeout_add_seconds(
|
||||
int(self._hide_delay),
|
||||
self._delayed_hide
|
||||
)
|
||||
|
||||
def _delayed_hide(self):
|
||||
"""Hide the component after delay if still no media players"""
|
||||
# Double-check that we still don't have media players
|
||||
if not self.has_running_players():
|
||||
self.set_visible(False)
|
||||
|
||||
# Clear the timeout
|
||||
self._visibility_timeout = None
|
||||
return False # Don't repeat the timeout
|
||||
+28
-1
@@ -39,7 +39,7 @@ class PlayerService(Service):
|
||||
self.status = self._player.props.playback_status
|
||||
self.pos_fabricator = Fabricator(
|
||||
interval=1000, # 1s
|
||||
poll_from=lambda f, *_: self._player.get_position(),
|
||||
poll_from=lambda f, *_: self._safe_get_position(),
|
||||
on_changed=lambda f, *_: self.fabricating(),
|
||||
)
|
||||
# Add state tracking to prevent repeated operations
|
||||
@@ -47,6 +47,22 @@ class PlayerService(Service):
|
||||
self._cleanup_done = False
|
||||
self.poll_progress()
|
||||
|
||||
def _safe_get_position(self):
|
||||
"""Safely get player position with error handling"""
|
||||
try:
|
||||
if not self._player or self._cleanup_done:
|
||||
return 0
|
||||
return self._player.get_position()
|
||||
except Exception:
|
||||
# Player is no longer available, stop the fabricator
|
||||
if hasattr(self, 'pos_fabricator') and self.pos_fabricator and self._fabricator_running:
|
||||
try:
|
||||
self.pos_fabricator.stop()
|
||||
self._fabricator_running = False
|
||||
except Exception:
|
||||
pass
|
||||
return 0
|
||||
|
||||
def cleanup(self):
|
||||
"""Clean up resources when service is no longer needed"""
|
||||
if self._cleanup_done:
|
||||
@@ -107,6 +123,10 @@ class PlayerService(Service):
|
||||
|
||||
def fabricating(self):
|
||||
try:
|
||||
# Check if cleanup has been done
|
||||
if self._cleanup_done:
|
||||
return
|
||||
|
||||
# Validate player object
|
||||
if not self._player or not hasattr(self._player, 'get_position'):
|
||||
return
|
||||
@@ -115,6 +135,13 @@ class PlayerService(Service):
|
||||
try:
|
||||
pos = self._player.get_position() / 1_000_000 # seconds
|
||||
except Exception:
|
||||
# Player is no longer available, stop fabricator
|
||||
if self._fabricator_running:
|
||||
try:
|
||||
self.pos_fabricator.stop()
|
||||
self._fabricator_running = False
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
# Get duration safely from multiple sources
|
||||
|
||||
Reference in new issue
Block a user