Skip to content

Commit a9ab571

Browse files
hoffiepabera
andauthored
Fix PlayerMPD.prev/next() when stopped (#2326)
* utils: Add get_config_action This abstracts away the functionality to resolve a given config option to an action in a pre-defined dict. Co-authored-by: Christian Hoffmann <[email protected]> * Fix PlayerMPD.prev/next() when stopped * Avoid MPD-related crashes during all prev/next() calls. * Explicitly handle prev() in stopped state, configurable via `playermpd.stopped_prev_action`. * Explicitly handle next() in stopped state, configurable via `playermpd.stopped_next_action`. * Explicitly handle next() when reaching the end of the playlist: jukebox-daemon will now ignore the action by default (similar to v2). It can also be configured to rewind the playlist instead by setting the new config option `playermpd.end_of_playlist_next_action: rewind` or to stop playing. Fixes #2294 Fixes #2327 Co-authored-by: pabera <[email protected]> --------- Co-authored-by: pabera <[email protected]>
1 parent afd0e47 commit a9ab571

File tree

3 files changed

+76
-2
lines changed

3 files changed

+76
-2
lines changed

resources/default-settings/jukebox.default.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ playermpd:
8787
update_on_startup: true
8888
check_user_rights: true
8989
mpd_conf: ~/.config/mpd/mpd.conf
90+
# Must be one of: 'none', 'stop', 'rewind':
91+
end_of_playlist_next_action: none
92+
# Must be one of: 'none', 'prev', 'rewind':
93+
stopped_prev_action: prev
94+
# Must be one of: 'none', 'next', 'rewind':
95+
stopped_next_action: next
9096
rpc:
9197
tcp_port: 5555
9298
websocket_port: 5556

src/jukebox/components/playermpd/__init__.py

+57-2
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,28 @@ def __init__(self):
156156
self.second_swipe_action = None
157157
self.decode_2nd_swipe_option()
158158

159+
self.end_of_playlist_next_action = utils.get_config_action(cfg,
160+
'playermpd',
161+
'end_of_playlist_next_action',
162+
{'rewind': self.rewind,
163+
'stop': self.stop,
164+
'none': lambda: None},
165+
logger)
166+
self.stopped_prev_action = utils.get_config_action(cfg,
167+
'playermpd',
168+
'stopped_prev_action',
169+
{'rewind': self.rewind,
170+
'prev': self._prev_in_stopped_state,
171+
'none': lambda: None},
172+
logger)
173+
self.stopped_next_action = utils.get_current_song(cfg,
174+
'playermpd',
175+
'stopped_next_action',
176+
{'rewind': self.rewind,
177+
'next': self._next_in_stopped_state,
178+
'none': lambda: None},
179+
logger)
180+
159181
self.mpd_client = mpd.MPDClient()
160182
self.coverart_cache_manager = CoverartCacheManager()
161183

@@ -327,15 +349,48 @@ def pause(self, state: int = 1):
327349
@plugs.tag
328350
def prev(self):
329351
logger.debug("Prev")
352+
if self.mpd_status['state'] == 'stop':
353+
logger.debug('Player is stopped, calling stopped_prev_action')
354+
return self.stopped_prev_action()
355+
try:
356+
with self.mpd_lock:
357+
self.mpd_client.previous()
358+
except mpd.base.CommandError:
359+
# This shouldn't happen in reality, but we still catch
360+
# this error to avoid crashing the player thread:
361+
logger.warning('Failed to go to previous song, ignoring')
362+
363+
def _prev_in_stopped_state(self):
330364
with self.mpd_lock:
331-
self.mpd_client.previous()
365+
self.mpd_client.play(max(0, int(self.mpd_status['pos']) - 1))
332366

333367
@plugs.tag
334368
def next(self):
335369
"""Play next track in current playlist"""
336370
logger.debug("Next")
371+
if self.mpd_status['state'] == 'stop':
372+
logger.debug('Player is stopped, calling stopped_next_action')
373+
return self.stopped_next_action()
374+
playlist_len = int(self.mpd_status.get('playlistlength', -1))
375+
current_pos = int(self.mpd_status.get('pos', 0))
376+
if current_pos == playlist_len - 1:
377+
logger.debug(f'next() called during last song ({current_pos}) of '
378+
f'playlist (len={playlist_len}), running end_of_playlist_next_action.')
379+
return self.end_of_playlist_next_action()
380+
try:
381+
with self.mpd_lock:
382+
self.mpd_client.next()
383+
except mpd.base.CommandError:
384+
# This shouldn't happen in reality, but we still catch
385+
# this error to avoid crashing the player thread:
386+
logger.warning('Failed to go to next song, ignoring')
387+
388+
def _next_in_stopped_state(self):
389+
pos = int(self.mpd_status['pos']) + 1
390+
if pos > int(self.mpd_status['playlistlength']) - 1:
391+
return self.end_of_playlist_next_action()
337392
with self.mpd_lock:
338-
self.mpd_client.next()
393+
self.mpd_client.play(pos)
339394

340395
@plugs.tag
341396
def seek(self, new_time):

src/jukebox/jukebox/utils.py

+13
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,19 @@ def rpc_call_to_str(cfg_rpc_call: Dict, with_args=True) -> str:
183183
return readable
184184

185185

186+
def get_config_action(cfg, section, option, default, valid_actions_dict, logger):
187+
"""
188+
Looks up the given {section}.{option} config option and returns
189+
the associated entry from valid_actions_dict, if valid. Falls back to the given
190+
default otherwise.
191+
"""
192+
action = cfg.setndefault(section, option, value='').lower()
193+
if action not in valid_actions_dict:
194+
logger.error(f"Config {section}.{option} must be one of {valid_actions_dict.keys()}. Using default '{default}'")
195+
action = default
196+
return valid_actions_dict[action]
197+
198+
186199
def indent(doc, spaces=4):
187200
lines = doc.split('\n')
188201
for i in range(0, len(lines)):

0 commit comments

Comments
 (0)