8787import logging
8888import time
8989import functools
90+ from typing import Optional
9091from pathlib import Path
9192import components .player
9293import jukebox .cfghandler
100101from jukebox .NvManager import nv_manager
101102from .playcontentcallback import PlayContentCallbacks , PlayCardState
102103from .coverart_cache_manager import CoverartCacheManager
104+ from .resume_position_tracker import ResumePositionTracker
103105
104106logger = logging .getLogger ('jb.PlayerMPD' )
105107cfg = jukebox .cfghandler .get_handler ('jukebox' )
@@ -193,6 +195,8 @@ def __init__(self):
193195 # Change this to last_played_folder and shutdown_state (for restoring)
194196 self .music_player_status ['player_status' ]['last_played_folder' ] = ''
195197
198+ self .resume_position_tracker = ResumePositionTracker ()
199+
196200 self .old_song = None
197201 self .mpd_status = {}
198202 self .mpd_status_poll_interval = 0.25
@@ -270,6 +274,7 @@ def _mpd_status_poll(self):
270274 self .current_folder_status ["LOOP" ] = "OFF"
271275 self .current_folder_status ["SINGLE" ] = "OFF"
272276
277+ self .resume_position_tracker .handle_mpd_status (self .mpd_status )
273278 # Delete the volume key to avoid confusion
274279 # Volume is published via the 'volume' component!
275280 try :
@@ -308,11 +313,13 @@ def update_wait(self):
308313 def play (self ):
309314 with self .mpd_lock :
310315 self .mpd_client .play ()
316+ self .resume_position_tracker .flush ()
311317
312318 @plugs .tag
313319 def stop (self ):
314320 with self .mpd_lock :
315321 self .mpd_client .stop ()
322+ self .resume_position_tracker .flush ()
316323
317324 @plugs .tag
318325 def pause (self , state : int = 1 ):
@@ -323,24 +330,28 @@ def pause(self, state: int = 1):
323330 """
324331 with self .mpd_lock :
325332 self .mpd_client .pause (state )
333+ self .resume_position_tracker .flush ()
326334
327335 @plugs .tag
328336 def prev (self ):
329337 logger .debug ("Prev" )
330338 with self .mpd_lock :
331339 self .mpd_client .previous ()
340+ self .resume_position_tracker .flush ()
332341
333342 @plugs .tag
334343 def next (self ):
335344 """Play next track in current playlist"""
336345 logger .debug ("Next" )
337346 with self .mpd_lock :
338347 self .mpd_client .next ()
348+ self .resume_position_tracker .flush ()
339349
340350 @plugs .tag
341351 def seek (self , new_time ):
342352 with self .mpd_lock :
343353 self .mpd_client .seekcur (new_time )
354+ self .resume_position_tracker .flush ()
344355
345356 @plugs .tag
346357 def rewind (self ):
@@ -351,6 +362,7 @@ def rewind(self):
351362 logger .debug ("Rewind" )
352363 with self .mpd_lock :
353364 self .mpd_client .play (0 )
365+ self .resume_position_tracker .flush ()
354366
355367 @plugs .tag
356368 def replay (self ):
@@ -367,6 +379,7 @@ def toggle(self):
367379 """Toggle pause state, i.e. do a pause / resume depending on current state"""
368380 with self .mpd_lock :
369381 self .mpd_client .pause ()
382+ self .resume_position_tracker .flush ()
370383
371384 @plugs .tag
372385 def replay_if_stopped (self ):
@@ -465,12 +478,35 @@ def move(self):
465478 raise NotImplementedError
466479
467480 @plugs .tag
468- def play_single (self , song_url ):
481+ def play_single (self , song_url , resume = None ):
482+ play_target = ('single' , song_url )
469483 with self .mpd_lock :
484+ if self ._play_or_pause_current (play_target ):
485+ return
470486 self .mpd_client .clear ()
471487 self .mpd_client .addid (song_url )
488+ self ._mpd_resume_from_saved_position (play_target , resume )
472489 self .mpd_client .play ()
473490
491+ def _play_or_pause_current (self , play_target ):
492+ if self .resume_position_tracker .is_current_play_target (play_target ):
493+ if self .mpd_status ['state' ] == 'play' :
494+ # Do nothing
495+ return True
496+ if self .mpd_status ['state' ] == 'pause' :
497+ logger .debug ('Unpausing as the play target is identical' )
498+ self .mpd_client .play ()
499+ return True
500+ return False
501+
502+ def _mpd_resume_from_saved_position (self , play_target , resume : Optional [bool ]):
503+ playlist_position = self .resume_position_tracker .get_playlist_position_by_play_target (play_target ) or 0
504+ seek_position = self .resume_position_tracker .get_seek_position_by_play_target (play_target ) or 0
505+ self .resume_position_tracker .set_current_play_target (play_target )
506+ if resume or (resume is None and self .resume_position_tracker .resume_by_default ):
507+ logger .debug (f'Restoring saved position for { play_target } ' )
508+ self .mpd_client .seek (playlist_position , seek_position )
509+
474510 @plugs .tag
475511 def resume (self ):
476512 with self .mpd_lock :
@@ -482,11 +518,14 @@ def resume(self):
482518 @plugs .tag
483519 def play_card (self , folder : str , recursive : bool = False ):
484520 """
485- Main entry point for trigger music playing from RFID reader. Decodes second swipe options before playing folder content
521+ Deprecated (?) main entry point for trigger music playing from RFID reader.
522+ Decodes second swipe options before playing folder content
486523
487524 Checks for second (or multiple) trigger of the same folder and calls first swipe / second swipe action
488525 accordingly.
489526
527+ Note: The Web UI currently uses play_single/album/folder directly.
528+
490529 :param folder: Folder path relative to music library path
491530 :param recursive: Add folder recursively
492531 """
@@ -554,7 +593,7 @@ def get_folder_content(self, folder: str):
554593 return plc .playlist
555594
556595 @plugs .tag
557- def play_folder (self , folder : str , recursive : bool = False ) -> None :
596+ def play_folder (self , folder : str , recursive : bool = False , resume : Optional [ bool ] = None ) -> None :
558597 """
559598 Playback a music folder.
560599
@@ -565,8 +604,11 @@ def play_folder(self, folder: str, recursive: bool = False) -> None:
565604 :param recursive: Add folder recursively
566605 """
567606 # TODO: This changes the current state -> Need to save last state
607+ play_target = ('folder' , folder , recursive )
568608 with self .mpd_lock :
569609 logger .info (f"Play folder: '{ folder } '" )
610+ if self ._play_or_pause_current (play_target ):
611+ return
570612 self .mpd_client .clear ()
571613
572614 plc = playlistgenerator .PlaylistCollector (components .player .get_music_library_path ())
@@ -586,10 +628,11 @@ def play_folder(self, folder: str, recursive: bool = False) -> None:
586628 if self .current_folder_status is None :
587629 self .current_folder_status = self .music_player_status ['audio_folder_status' ][folder ] = {}
588630
631+ self ._mpd_resume_from_saved_position (play_target , resume )
589632 self .mpd_client .play ()
590633
591634 @plugs .tag
592- def play_album (self , albumartist : str , album : str ):
635+ def play_album (self , albumartist : str , album : str , resume : Optional [ bool ] = None ):
593636 """
594637 Playback a album found in MPD database.
595638
@@ -599,10 +642,14 @@ def play_album(self, albumartist: str, album: str):
599642 :param albumartist: Artist of the Album provided by MPD database
600643 :param album: Album name provided by MPD database
601644 """
645+ play_target = ('album' , albumartist , album )
602646 with self .mpd_lock :
603647 logger .info (f"Play album: '{ album } ' by '{ albumartist } " )
648+ if self ._play_or_pause_current (play_target ):
649+ return
604650 self .mpd_client .clear ()
605651 self .mpd_retry_with_mutex (self .mpd_client .findadd , 'albumartist' , albumartist , 'album' , album )
652+ self ._mpd_resume_from_saved_position (play_target , resume )
606653 self .mpd_client .play ()
607654
608655 @plugs .tag
0 commit comments