diff --git a/documentation/builders/card-database.md b/documentation/builders/card-database.md
index 340933750..1c2f92d56 100644
--- a/documentation/builders/card-database.md
+++ b/documentation/builders/card-database.md
@@ -21,15 +21,17 @@ using the alias option:
'0002':
# A RPC command using the alias definition with one arguments
# Here: Trigger music playback through the card interface
- alias: play_card
- args: [path/to/folder]
+ alias: play_folder
+ args:
+ - /path/to/folder
'0003':
# A RPC command using keyword arguments. Args and kwargs can also be mixed.
# Args and Kwargs translate directly into the function python call
# Some as in '0002' but using kwargs
- alias: play_card
+ alias: play_album
kwargs:
- folder: path/to/folder
+ albumartist: Some Artist Name
+ recursive: A song Name
```
> [!NOTE]
diff --git a/documentation/builders/rpc-commands.md b/documentation/builders/rpc-commands.md
index a45e0b643..09579bceb 100644
--- a/documentation/builders/rpc-commands.md
+++ b/documentation/builders/rpc-commands.md
@@ -37,21 +37,21 @@ The keyword ``method`` is optional. If needs to be used depends on the function
## Aliases
Not so complicated, right? It will get even easier. For common commands we have defined aliases. An alias simply maps
-to a pre-defined RPC command, e.g. ``play_card`` maps to ``player.ctrl.play_card``.
+to a pre-defined RPC command, e.g. ``play_folder`` maps to ``player.ctrl.play_folder``.
Instead of
```yml
package: player
plugin: ctrl
-method: play_card
+method: play_folder
args: [path/to/folder]
```
you can simply specify instead:
```yml
-alias: play_card
+alias: play_folder
args: [path/to/folder]
```
@@ -60,10 +60,10 @@ Using in alias is optional. But if the keyword is present in the configuration i
## Arguments
Arguments can be specified in similar fashion to Python function arguments: as positional arguments and / or
-keyword arguments. Let's check out play_card, which is defined as:
+keyword arguments. Let's check out play_folder, which is defined as:
```python
-play_card(...) -> player.ctrl.play_card(folder: str, recursive: bool = False)
+play_folder(...) -> player.ctrl.play_folder(folder: str, recursive: bool = False)
:noindex:
:param folder: Folder path relative to music library path
@@ -79,19 +79,19 @@ In the following examples, we will always use the alias for smaller configuratio
do exactly the same, but use different ways of specifying the command.
```yml
-alias: play_card
+alias: play_folder
args: [path/to/folder, True]
```
```yml
-alias: play_card
+alias: play_folder
args: [path/to/folder]
kwargs:
recursive: True
```
```yml
-alias: play_card
+alias: play_folder
kwargs:
folder: path/to/folder
recursive: True
diff --git a/documentation/developers/docstring/README.md b/documentation/developers/docstring/README.md
index c48c34a41..b46ae1542 100644
--- a/documentation/developers/docstring/README.md
+++ b/documentation/developers/docstring/README.md
@@ -40,14 +40,12 @@
* [replay](#components.playermpd.PlayerMPD.replay)
* [toggle](#components.playermpd.PlayerMPD.toggle)
* [replay\_if\_stopped](#components.playermpd.PlayerMPD.replay_if_stopped)
- * [play\_card](#components.playermpd.PlayerMPD.play_card)
* [get\_single\_coverart](#components.playermpd.PlayerMPD.get_single_coverart)
* [get\_folder\_content](#components.playermpd.PlayerMPD.get_folder_content)
* [play\_folder](#components.playermpd.PlayerMPD.play_folder)
* [play\_album](#components.playermpd.PlayerMPD.play_album)
* [get\_volume](#components.playermpd.PlayerMPD.get_volume)
* [set\_volume](#components.playermpd.PlayerMPD.set_volume)
- * [play\_card\_callbacks](#components.playermpd.play_card_callbacks)
* [components.playermpd.coverart\_cache\_manager](#components.playermpd.coverart_cache_manager)
* [components.rpc\_command\_alias](#components.rpc_command_alias)
* [components.synchronisation.rfidcards](#components.synchronisation.rfidcards)
@@ -852,11 +850,6 @@ Internal status
- last played folder: Needed to detect second swipe
-Saving {'player_status': {'last_played_folder': 'TraumfaengerStarkeLieder', 'CURRENTSONGPOS': '0', 'CURRENTFILENAME': 'TraumfaengerStarkeLieder/01.mp3'},
-'audio_folder_status':
-{'TraumfaengerStarkeLieder': {'ELAPSED': '1.0', 'CURRENTFILENAME': 'TraumfaengerStarkeLieder/01.mp3', 'CURRENTSONGPOS': '0', 'PLAYSTATUS': 'stop', 'RESUME': 'OFF', 'SHUFFLE': 'OFF', 'LOOP': 'OFF', 'SINGLE': 'OFF'},
-'Giraffenaffen': {'ELAPSED': '1.0', 'CURRENTFILENAME': 'TraumfaengerStarkeLieder/01.mp3', 'CURRENTSONGPOS': '0', 'PLAYSTATUS': 'play', 'RESUME': 'OFF', 'SHUFFLE': 'OFF', 'LOOP': 'OFF', 'SINGLE': 'OFF'}}}
-
References:
https://github.com/Mic92/python-mpd2
https://python-mpd2.readthedocs.io/en/latest/topics/commands.html
@@ -975,25 +968,6 @@ Re-start playing the last-played folder unless playlist is still playing
> but we keep it as it is specifically implemented in box 2.X
-
-
-#### play\_card
-
-```python
-@plugs.tag
-def play_card(folder: str, recursive: bool = False)
-```
-
-Main entry point for trigger music playing from RFID reader. Decodes second swipe options before playing folder content
-
-Checks for second (or multiple) trigger of the same folder and calls first swipe / second swipe action
-accordingly.
-
-**Arguments**:
-
-- `folder`: Folder path relative to music library path
-- `recursive`: Add folder recursively
-
#### get\_single\_coverart
@@ -1089,22 +1063,6 @@ For volume control do not use directly, but use through the plugin 'volume',
as the user may have configured a volume control manager other than MPD
-
-
-#### play\_card\_callbacks
-
-Callback handler instance for play_card events.
-
-- is executed when play_card function is called
-States:
-- See :class:`PlayCardState`
-See :class:`PlayContentCallbacks`
-
-
-
-
-# components.playermpd.coverart\_cache\_manager
-
# components.rpc\_command\_alias
diff --git a/resources/default-settings/cards.example.yaml b/resources/default-settings/cards.example.yaml
index e01a45ba6..762a10887 100644
--- a/resources/default-settings/cards.example.yaml
+++ b/resources/default-settings/cards.example.yaml
@@ -53,10 +53,10 @@
'T':
package: player
plugin: ctrl
- method: play_card
+ method: play_folder
args: [path/to/music/folder]
'G':
- alias: play_card
+ alias: play_folder
args: path/to/music/folder
'V+':
alias: change_volume
diff --git a/src/jukebox/components/playermpd/__init__.py b/src/jukebox/components/playermpd/__init__.py
index 772b8c654..c647b5ff2 100644
--- a/src/jukebox/components/playermpd/__init__.py
+++ b/src/jukebox/components/playermpd/__init__.py
@@ -41,12 +41,6 @@
Internal status
- last played folder: Needed to detect second swipe
-
-Saving {'player_status': {'last_played_folder': 'TraumfaengerStarkeLieder', 'CURRENTSONGPOS': '0', 'CURRENTFILENAME': 'TraumfaengerStarkeLieder/01.mp3'},
-'audio_folder_status':
-{'TraumfaengerStarkeLieder': {'ELAPSED': '1.0', 'CURRENTFILENAME': 'TraumfaengerStarkeLieder/01.mp3', 'CURRENTSONGPOS': '0', 'PLAYSTATUS': 'stop', 'RESUME': 'OFF', 'SHUFFLE': 'OFF', 'LOOP': 'OFF', 'SINGLE': 'OFF'},
-'Giraffenaffen': {'ELAPSED': '1.0', 'CURRENTFILENAME': 'TraumfaengerStarkeLieder/01.mp3', 'CURRENTSONGPOS': '0', 'PLAYSTATUS': 'play', 'RESUME': 'OFF', 'SHUFFLE': 'OFF', 'LOOP': 'OFF', 'SINGLE': 'OFF'}}}
-
References:
https://github.com/Mic92/python-mpd2
https://python-mpd2.readthedocs.io/en/latest/topics/commands.html
@@ -87,6 +81,7 @@
import logging
import time
import functools
+from typing import Callable
from pathlib import Path
import components.player
import jukebox.cfghandler
@@ -151,8 +146,7 @@ def __init__(self):
'play': self.play,
'skip': self.next,
'rewind': self.rewind,
- 'replay': self.replay,
- 'replay_if_stopped': self.replay_if_stopped}
+ 'replay': self.replay}
self.second_swipe_action = None
self.decode_2nd_swipe_option()
@@ -196,24 +190,24 @@ def __init__(self):
self.music_player_status['audio_folder_status'] = {}
self.music_player_status.save_to_json()
self.current_folder_status = {}
- self.music_player_status['player_status']['last_played_folder'] = ''
- else:
- last_played_folder = self.music_player_status['player_status'].get('last_played_folder')
- if last_played_folder:
- # current_folder_status is a dict, but last_played_folder a str
- self.current_folder_status = self.music_player_status['audio_folder_status'][last_played_folder]
- # Restore the playlist status in mpd
- # But what about playback position?
- self.mpd_client.clear()
- # This could fail and cause load fail of entire package:
- # self.mpd_client.add(last_played_folder)
- logger.info(f"Last Played Folder: {last_played_folder}")
+ # self.music_player_status['player_status']['last_played_folder'] = ''
+ # else:
+ # last_played_folder = self.music_player_status['player_status'].get('last_played_folder')
+ # if last_played_folder:
+ # # current_folder_status is a dict, but last_played_folder a str
+ # self.current_folder_status = self.music_player_status['audio_folder_status'][last_played_folder]
+ # # Restore the playlist status in mpd
+ # # But what about playback position?
+ # self.mpd_client.clear()
+ # # This could fail and cause load fail of entire package:
+ # # self.mpd_client.add(last_played_folder)
+ # logger.info(f"Last Played Folder: {last_played_folder}")
# Clear last folder played, as we actually did not play any folder yet
# Needed for second swipe detection
# TODO: This will loose the last_played_folder information is the box is started and closed with playing anything...
# Change this to last_played_folder and shutdown_state (for restoring)
- self.music_player_status['player_status']['last_played_folder'] = ''
+ # self.music_player_status['player_status']['last_played_folder'] = ''
self.old_song = None
self.mpd_status = {}
@@ -326,6 +320,10 @@ def update_wait(self):
self._db_wait_for_update(state)
return state
+ # ---------------
+ # Player
+ # ---------------
+
@plugs.tag
def play(self):
with self.mpd_lock:
@@ -415,7 +413,8 @@ def replay(self):
Will reset settings to folder config"""
logger.debug("Replay")
with self.mpd_lock:
- self.play_folder(self.music_player_status['player_status']['last_played_folder'])
+ # TODO: Restore from PlayPositionTracker and play
+ return
@plugs.tag
def toggle(self):
@@ -423,18 +422,6 @@ def toggle(self):
with self.mpd_lock:
self.mpd_client.pause()
- @plugs.tag
- def replay_if_stopped(self):
- """
- Re-start playing the last-played folder unless playlist is still playing
-
- > [!NOTE]
- > To me this seems much like the behaviour of play,
- > but we keep it as it is specifically implemented in box 2.X"""
- with self.mpd_lock:
- if self.mpd_status['state'] == 'stop':
- self.play_folder(self.music_player_status['player_status']['last_played_folder'])
-
# Shuffle
def _shuffle(self, random):
# As long as we don't work with waiting lists (aka playlist), this implementation is ok!
@@ -519,13 +506,6 @@ def move(self):
# MPDClient.swapid(song1, song2)
raise NotImplementedError
- @plugs.tag
- def play_single(self, song_url):
- with self.mpd_lock:
- self.mpd_client.clear()
- self.mpd_client.addid(song_url)
- self.mpd_client.play()
-
@plugs.tag
def resume(self):
with self.mpd_lock:
@@ -534,45 +514,76 @@ def resume(self):
self.mpd_client.seek(songpos, elapsed)
self.mpd_client.play()
- @plugs.tag
- def play_card(self, folder: str, recursive: bool = False):
- """
- Main entry point for trigger music playing from RFID reader. Decodes second swipe options before playing folder content
-
- Checks for second (or multiple) trigger of the same folder and calls first swipe / second swipe action
- accordingly.
+ # ---------------
+ # Play songs
+ # ---------------
- :param folder: Folder path relative to music library path
- :param recursive: Add folder recursively
- """
- # Developers notes:
- #
- # * 2nd swipe trigger may also happen, if playlist has already stopped playing
- # --> Generally, treat as first swipe
- # * 2nd swipe of same Card ID may also happen if a different song has been played in between from WebUI
- # --> Treat as first swipe
- # * With place-not-swipe: Card is placed on reader until playlist expieres. Music stop. Card is removed and
- # placed again on the reader: Should be like first swipe
- # * TODO: last_played_folder is restored after box start, so first swipe of last played card may look like
- # second swipe
- #
- logger.debug(f"last_played_folder = {self.music_player_status['player_status']['last_played_folder']}")
+ def call_with_second_swipe(self, identifier: str, action: Callable, **kwargs):
+ logger.debug(f"last_played_identifier = {self.music_player_status['player_status']['last_played_identifier']}")
with self.mpd_lock:
- is_second_swipe = self.music_player_status['player_status']['last_played_folder'] == folder
+ is_second_swipe = self.music_player_status['player_status']['last_played_identifier'] == identifier
+
if self.second_swipe_action is not None and is_second_swipe:
logger.debug('Calling second swipe action')
-
- # run callbacks before second_swipe_action is invoked
- play_card_callbacks.run_callbacks(folder, PlayCardState.secondSwipe)
-
+ if 'folder' in kwargs: # TODO: Legacy code from previous implementation. should be adapted
+ play_card_callbacks.run_callbacks(kwargs.get('folder'), PlayCardState.secondSwipe)
self.second_swipe_action()
else:
logger.debug('Calling first swipe action')
+ if 'folder' in kwargs: # TODO: Legacy code from previous implementation. should be adapted
+ play_card_callbacks.run_callbacks(kwargs.get('folder'), PlayCardState.firstSwipe)
+ action(**kwargs)
+ self.music_player_status['player_status']['last_played_identifier'] = identifier
+
+ @plugs.tag
+ def play_card(self, identifier: str, **kwargs):
+ actions = {
+ 'play_single': self.play_single,
+ 'play_album': self.play_album,
+ 'play_folder': self.play_folder
+ }
+
+ action = actions.get(identifier)
+ if action:
+ self.call_with_second_swipe(identifier, action, **kwargs)
+ else:
+ logger.error(f"Function '{identifier}' does not exist.")
+
+ @plugs.tag
+ def play_single(self, song_url: str):
+ with self.mpd_lock:
+ self.mpd_client.clear()
+ self.mpd_client.addid(song_url)
+ self.mpd_client.play()
+
+ @plugs.tag
+ def play_album(self, albumartist: str, album: str):
+ with self.mpd_lock:
+ self.mpd_client.clear()
+ self.mpd_retry_with_mutex(self.mpd_client.findadd, 'albumartist', albumartist, 'album', album)
+ self.mpd_client.play()
+
+ @plugs.tag
+ def play_folder(self, folder: str, recursive: bool = False):
+ with self.mpd_lock:
+ self.mpd_client.clear()
+
+ plc = playlistgenerator.PlaylistCollector(components.player.get_music_library_path())
+ plc.parse(folder, recursive)
+ uri = '--unset--'
+ try:
+ for uri in plc:
+ self.mpd_client.addid(uri)
+ except mpd.base.CommandError as e:
+ logger.error(f"{e.__class__.__qualname__}: {e} at uri {uri}")
+ except Exception as e:
+ logger.error(f"{e.__class__.__qualname__}: {e} at uri {uri}")
- # run callbacks before play_folder is invoked
- play_card_callbacks.run_callbacks(folder, PlayCardState.firstSwipe)
+ self.mpd_client.play()
- self.play_folder(folder, recursive)
+ # ---------------
+ # Cover Art
+ # ---------------
@plugs.tag
def get_single_coverart(self, song_url):
@@ -595,6 +606,10 @@ def flush_coverart_cache(self):
return self.coverart_cache_manager.flush_cache()
+ # ---------------
+ # Playlists
+ # ---------------
+
@plugs.tag
def get_folder_content(self, folder: str):
"""
@@ -608,58 +623,6 @@ def get_folder_content(self, folder: str):
plc.get_directory_content(folder)
return plc.playlist
- @plugs.tag
- def play_folder(self, folder: str, recursive: bool = False) -> None:
- """
- Playback a music folder.
-
- Folder content is added to the playlist as described by :mod:`jukebox.playlistgenerator`.
- The playlist is cleared first.
-
- :param folder: Folder path relative to music library path
- :param recursive: Add folder recursively
- """
- # TODO: This changes the current state -> Need to save last state
- with self.mpd_lock:
- logger.info(f"Play folder: '{folder}'")
- self.mpd_client.clear()
-
- plc = playlistgenerator.PlaylistCollector(components.player.get_music_library_path())
- plc.parse(folder, recursive)
- uri = '--unset--'
- try:
- for uri in plc:
- self.mpd_client.addid(uri)
- except mpd.base.CommandError as e:
- logger.error(f"{e.__class__.__qualname__}: {e} at uri {uri}")
- except Exception as e:
- logger.error(f"{e.__class__.__qualname__}: {e} at uri {uri}")
-
- self.music_player_status['player_status']['last_played_folder'] = folder
-
- self.current_folder_status = self.music_player_status['audio_folder_status'].get(folder)
- if self.current_folder_status is None:
- self.current_folder_status = self.music_player_status['audio_folder_status'][folder] = {}
-
- self.mpd_client.play()
-
- @plugs.tag
- def play_album(self, albumartist: str, album: str):
- """
- Playback a album found in MPD database.
-
- All album songs are added to the playlist
- The playlist is cleared first.
-
- :param albumartist: Artist of the Album provided by MPD database
- :param album: Album name provided by MPD database
- """
- with self.mpd_lock:
- logger.info(f"Play album: '{album}' by '{albumartist}")
- self.mpd_client.clear()
- self.mpd_retry_with_mutex(self.mpd_client.findadd, 'albumartist', albumartist, 'album', album)
- self.mpd_client.play()
-
@plugs.tag
def queue_load(self, folder):
# There was something playing before -> stop and save state
@@ -713,6 +676,10 @@ def get_song_by_url(self, song_url):
return song
+ # ---------------
+ # Volume
+ # ---------------
+
def get_volume(self):
"""
Get the current volume
diff --git a/src/jukebox/components/rpc_command_alias.py b/src/jukebox/components/rpc_command_alias.py
index 5a7820733..c880e7d32 100644
--- a/src/jukebox/components/rpc_command_alias.py
+++ b/src/jukebox/components/rpc_command_alias.py
@@ -12,12 +12,6 @@
# --------------------------------------------------------------
cmd_alias_definitions = {
# Player
- 'play_card': {
- 'title': 'Play music folder triggered by card swipe',
- 'note': "This function you'll want to use most often",
- 'package': 'player',
- 'plugin': 'ctrl',
- 'method': 'play_card'},
'play_album': {
'title': 'Play Album triggered by card swipe',
'note': "This function plays the content of a given album",
diff --git a/src/jukebox/jukebox/utils.py b/src/jukebox/jukebox/utils.py
index 4cc0270ae..1fd222c7a 100644
--- a/src/jukebox/jukebox/utils.py
+++ b/src/jukebox/jukebox/utils.py
@@ -210,9 +210,9 @@ def generate_cmd_alias_rst(stream):
print(".. |--| unicode:: U+2014", file=stream)
print(".. |->| unicode:: U+21d2\n", file=stream)
- # 'play_card':
- # Executes : player.ctrl.play_card()
- # Signature : player.ctrl.play_card(folder=None)
+ # 'play_folder':
+ # Executes : player.ctrl.play_folder()
+ # Signature : player.ctrl.play_folder(folder=None)
# Description: Main entry point for trigger music playing from RFID reader
for cmd, action in cmd_alias_definitions.items():
try: