♻️ refactor(music_player, mpris): add recursion protection, debouncing, and state tracking in player management

This commit is contained in:
sankalp committed 2025-07-11 06:47:39 +05:45
1 parent d34ac0d369
commit 8f12b04a02
3 files changed
+111 -42

No files matched your search

+21 -21
View File
@@ -1,27 +1,27 @@
$wallpaper = /home/sankalp/Pictures/wallpapers/48.png
$background = 0f1417
$foreground = dee3e6
$wallpaper = /home/sankalp/Pictures/wallpapers/17.png
$background = 1a120d
$foreground = f0dfd7
$primary = 89d0ed
$secondary = b3cad5
$tertiary = c4c3eb
$surface = 0f1417
$surface_bright = 353a3d
$outline = 8a9296
$primary = ffb68c
$secondary = e5bfaa
$tertiary = ccc992
$surface = 1a120d
$surface_bright = 413732
$outline = 9f8d84
$error = ff5945
$shadow = 000000
$red = ffb2b9
$green = 95d5a7
$yellow = b8cf84
$blue = afc6ff
$magenta = e4b7f3
$cyan = 82d3e2
$red = ffb59b
$green = b7d085
$yellow = dec56e
$blue = cebdfe
$magenta = fcb0d5
$cyan = 84d5c4
$white = 82d3e0
$red_dim = ff4c5c
$green_dim = 4cb86a
$yellow_dim = 8baa43
$blue_dim = 497dff
$magenta_dim = c360e4
$cyan_dim = 30b5ce
$red_dim = ff6a35
$green_dim = 89ad42
$yellow_dim = bb9b2b
$blue_dim = 8359fc
$magenta_dim = f84ea1
$cyan_dim = 3eb59c
+49 -15
View File
@@ -219,6 +219,11 @@ class MusicPlayer(Box):
2, self.check_active_player
)
# Add recursion protection and debouncing
self._in_player_switch = False
self._last_player_switch_time = 0
self._switch_debounce_ms = 500 # 500ms debounce
# Connect to destroy signal for cleanup
self.connect("destroy", self.on_destroy)
@@ -358,6 +363,10 @@ class MusicPlayer(Box):
def check_active_player(self):
"""Periodically check for the currently playing player and switch if needed"""
# Prevent recursion during player checking
if self._in_player_switch:
return True # Continue the timeout
try:
current_time = time.time()
@@ -473,6 +482,18 @@ class MusicPlayer(Box):
def set_current_player(self, player):
"""Set the current active player"""
# Prevent recursion during player switching
if self._in_player_switch:
return
# Add debouncing to prevent rapid switching
import time
current_time = time.time() * 1000 # Convert to milliseconds
if current_time - self._last_player_switch_time < self._switch_debounce_ms:
return
self._in_player_switch = True
self._last_player_switch_time = current_time
try:
# Validate player before proceeding
if not player or not hasattr(player, 'props') or not hasattr(player.props, 'player_name'):
@@ -493,16 +514,15 @@ class MusicPlayer(Box):
except Exception:
pass
# Disconnect signals - only if they were actually connected
# Use a more robust disconnection approach
try:
# Get all signal handlers and disconnect them
if hasattr(self.current_player_service, 'disconnect'):
# Try to disconnect all signals from this service
self.current_player_service.disconnect()
except Exception:
# If bulk disconnect fails, try individual disconnections
pass
# Disconnect signals safely - avoid bulk disconnect which can cause recursion
# Store signal handler IDs for proper cleanup
if hasattr(self.current_player_service, '_signal_handlers'):
try:
for handler_id in self.current_player_service._signal_handlers:
self.current_player_service.disconnect(handler_id)
self.current_player_service._signal_handlers.clear()
except Exception:
pass
except Exception:
pass
@@ -519,13 +539,24 @@ class MusicPlayer(Box):
self.current_player_service = PlayerService(player=player)
# Connect to player events with error handling
# Connect to player events with error handling and store handler IDs
if self.current_player_service:
try:
self.current_player_service.connect("track-position", self.on_track_position)
self.current_player_service.connect("play", self.on_play)
self.current_player_service.connect("pause", self.on_pause)
self.current_player_service.connect("meta-change", self.on_metadata)
# Initialize signal handlers list for proper cleanup
self.current_player_service._signal_handlers = []
# Connect signals and store handler IDs
handler_id = self.current_player_service.connect("track-position", self.on_track_position)
self.current_player_service._signal_handlers.append(handler_id)
handler_id = self.current_player_service.connect("play", self.on_play)
self.current_player_service._signal_handlers.append(handler_id)
handler_id = self.current_player_service.connect("pause", self.on_pause)
self.current_player_service._signal_handlers.append(handler_id)
handler_id = self.current_player_service.connect("meta-change", self.on_metadata)
self.current_player_service._signal_handlers.append(handler_id)
except Exception:
# If signal connection fails, clean up the service
if hasattr(self.current_player_service, 'cleanup'):
@@ -548,6 +579,9 @@ class MusicPlayer(Box):
# Reset to safe state
self.current_player = None
self.current_player_service = None
finally:
# Always reset recursion protection flag
self._in_player_switch = False
def on_track_position(self, service, pos, dur):
"""Handle track position updates"""
+41 -6
View File
@@ -42,22 +42,44 @@ class PlayerService(Service):
poll_from=lambda f, *_: self._player.get_position(),
on_changed=lambda f, *_: self.fabricating(),
)
# Add state tracking to prevent repeated operations
self._fabricator_running = False
self._cleanup_done = False
self.poll_progress()
def cleanup(self):
"""Clean up resources when service is no longer needed"""
if self._cleanup_done:
return # Already cleaned up
try:
if hasattr(self, 'pos_fabricator') and self.pos_fabricator:
self.pos_fabricator.stop()
if self._fabricator_running:
self.pos_fabricator.stop()
self._fabricator_running = False
self.pos_fabricator = None
self._cleanup_done = True
except Exception as e:
print(f"[DEBUG] Error during PlayerService cleanup: {e}")
def on_seeked(self, player, position):
if self.status.value_name == "PLAYERCTL_PLAYBACK_STATUS_PLAYING":
self.pos_fabricator.start()
if (not self._cleanup_done and
self.status.value_name == "PLAYERCTL_PLAYBACK_STATUS_PLAYING" and
not self._fabricator_running):
try:
self.pos_fabricator.start()
self._fabricator_running = True
except Exception:
pass # Ignore if already started
def set_position(self, pos: float):
self.pos_fabricator.stop()
if not self._cleanup_done and self._fabricator_running:
try:
self.pos_fabricator.stop()
self._fabricator_running = False
except Exception:
pass # Ignore if already stopped
micro_pos = int(pos * 1_000_000)
try:
self._player.set_position(micro_pos)
@@ -65,10 +87,23 @@ class PlayerService(Service):
print(f"Failed to seek: {e}")
def poll_progress(self):
if self._cleanup_done:
return # Don't operate on cleaned up fabricator
if self.status.value_name == "PLAYERCTL_PLAYBACK_STATUS_PLAYING":
self.pos_fabricator.start()
if not self._fabricator_running:
try:
self.pos_fabricator.start()
self._fabricator_running = True
except Exception:
pass # Ignore if already started
else:
self.pos_fabricator.stop()
if self._fabricator_running:
try:
self.pos_fabricator.stop()
self._fabricator_running = False
except Exception:
pass # Ignore if already stopped
def fabricating(self):
try: