♻️ refactor(music_player, mpris): add recursion protection, debouncing, and state tracking in player management
This commit is contained in:
3 files changed
+111
-42
No files matched your search
+21
-21
@@ -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
|
||||
@@ -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
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user