Add mpd status mapping

This commit is contained in:
badaix 2021-05-28 09:06:23 +02:00
parent 6e6b63ec26
commit 283c3d2c9b
3 changed files with 68 additions and 8 deletions

View file

@ -79,7 +79,50 @@ defaults = {
'stream': 'default', 'stream': 'default',
} }
# Player.Status
status_mapping = {
# https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html#properties
'state': ['playbackStatus', lambda val: {'play': 'playing', 'pause': 'paused', 'stop': 'stopped'}[val]], # R/O - play => playing, pause => paused, stop => stopped
'repeat': ['loopStatus', lambda val: {'0': 'none', '1': 'track', '2': 'playlist'}[val]], # R/W - 0 => none, 1 => track, n/a => playlist
# 'Rate d (Playback_Rate) R/W
'random': ['shuffle', lambda val: {'0': False, '1': True}[val]], # R/W - 0 => false, 1 => true
# 'Metadata a{sv} (Metadata_Map) Read only
'volume': ['volume', int], # R/W - 0-100 => 0-100
'elapsed': ['position', float], # R/O - seconds? ms?
# 'MinimumRate d (Playback_Rate) Read only
# 'MaximumRate d (Playback_Rate) Read only
# 'CanGoNext b Read only
# 'CanGoPrevious b Read only
# 'CanPlay b Read only
# 'CanPause b Read only
# 'CanSeek b Read only
# 'CanControl b Read only
# https://mpd.readthedocs.io/en/stable/protocol.html#status
# partition: the name of the current partition (see Partition commands)
# single 2: 0, 1, or oneshot 6
# consume 2: 0 or 1
# playlist: 31-bit unsigned integer, the playlist version number
# playlistlength: integer, the length of the playlist
# song: playlist song number of the current song stopped on or playing
# songid: playlist songid of the current song stopped on or playing
# nextsong 2: playlist song number of the next song to be played
# nextsongid 2: playlist songid of the next song to be played
# time: total time elapsed (of current playing/paused song) in seconds (deprecated, use elapsed instead)
'duration': ['duration', float], # duration 5: Duration of the current song in seconds.
# bitrate: instantaneous bitrate in kbps
# xfade: crossfade in seconds
# mixrampdb: mixramp threshold in dB
# mixrampdelay: mixrampdelay in seconds
# audio: The format emitted by the decoder plugin during playback, format: samplerate:bits:channels. See Global Audio Format for a detailed explanation.
# updating_db: job id
# error: if there is an error, returns message here
# Snapcast
'mute': ['mute', lambda val: {'0': False, '1': True}[val]] # R/W true/false
}
# Player.Metadata
# MPD to Snapcast tag mapping: <mpd tag>: [<snapcast tag>, <type>, <is list?>] # MPD to Snapcast tag mapping: <mpd tag>: [<snapcast tag>, <type>, <is list?>]
tag_mapping = { tag_mapping = {
'id': ['trackId', str, False], 'id': ['trackId', str, False],
@ -445,7 +488,6 @@ class MPDWrapper(object):
logger.warning("Can't cast value %r to %s" % logger.warning("Can't cast value %r to %s" %
(value, tag_mapping[key][1])) (value, tag_mapping[key][1]))
# Stream: populate some missings tags with stream's name
logger.debug(f'snapcast meta: {snapmeta}') logger.debug(f'snapcast meta: {snapmeta}')
# Hack for web radio: # Hack for web radio:
@ -462,9 +504,8 @@ class MPDWrapper(object):
snapmeta['artist'] = [fields[0]] snapmeta['artist'] = [fields[0]]
snapmeta['title'] = fields[1] snapmeta['title'] = fields[1]
send({"jsonrpc": "2.0", "method": "Stream.OnMetadata", "params": snapmeta}) send({"jsonrpc": "2.0", "method": "Player.Metadata", "params": snapmeta})
snapmeta['artUrl'] = 'http://127.0.0.1:1780/launcher-icon.png'
album_key = 'musicbrainzAlbumId' album_key = 'musicbrainzAlbumId'
try: try:
if not album_key in snapmeta: if not album_key in snapmeta:
@ -500,7 +541,7 @@ class MPDWrapper(object):
f'Error while getting cover for {snapmeta[album_key]}: {e}') f'Error while getting cover for {snapmeta[album_key]}: {e}')
logger.info(f'Snapmeta: {snapmeta}') logger.info(f'Snapmeta: {snapmeta}')
send({"jsonrpc": "2.0", "method": "Stream.OnMetadata", "params": snapmeta}) send({"jsonrpc": "2.0", "method": "Player.Metadata", "params": snapmeta})
def _update_properties(self, force=False): def _update_properties(self, force=False):
old_status = self._status old_status = self._status
@ -511,6 +552,22 @@ class MPDWrapper(object):
logger.info(f'new status: {new_status}') logger.info(f'new status: {new_status}')
self._time = new_time = int(time.time()) self._time = new_time = int(time.time())
snapstatus = {}
for key, value in new_status.items():
try:
mapped_key = status_mapping[key][0]
mapped_val = status_mapping[key][1](value)
snapstatus[mapped_key] = mapped_val
logger.debug(
f'key: {key}, value: {value}, mapped key: {mapped_key}, mapped value: {mapped_val}')
except KeyError:
logger.warning(f'tag "{key}" not supported')
except (ValueError, TypeError):
logger.warning("Can't cast value %r to %s" %
(value, status_mapping[key][1]))
send({"jsonrpc": "2.0", "method": "Player.Status", "params": snapstatus})
if not new_status: if not new_status:
logger.debug("_update_properties: failed to get new status") logger.debug("_update_properties: failed to get new status")
return return

View file

@ -356,6 +356,9 @@ class MPDWrapper(object):
self._metadata[ self._metadata[
'mpris:trackid'] = f'/org/mpris/MediaPlayer2/Track/{self._metadata["mpris:trackid"]}' 'mpris:trackid'] = f'/org/mpris/MediaPlayer2/Track/{self._metadata["mpris:trackid"]}'
if not 'mpris:artUrl' in self._metadata:
self._metadata['mpris:artUrl'] = f'http://{self._params["host"]}:{self._params["port"]}/launcher-icon.png'
logger.info(f'mpris meta: {self._metadata}') logger.info(f'mpris meta: {self._metadata}')
self.notify_about_track(self._metadata) self.notify_about_track(self._metadata)
@ -466,7 +469,7 @@ class MPDWrapper(object):
url = f'http://{self._params["host"]}:{self._params["port"]}/jsonrpc' url = f'http://{self._params["host"]}:{self._params["port"]}/jsonrpc'
logger.info(f'url: {url}') logger.info(f'url: {url}')
self._req_id += 1 self._req_id += 1
requests.post(url, json=j) self.websocket.send(json.dumps(j))
@property @property
def metadata(self): def metadata(self):
@ -474,8 +477,8 @@ class MPDWrapper(object):
def notify_about_track(self, meta, state='play'): def notify_about_track(self, meta, state='play'):
uri = 'sound' uri = 'sound'
if 'mpris:artUrl' in meta: # if 'mpris:artUrl' in meta:
uri = meta['mpris:artUrl'] # uri = meta['mpris:artUrl']
title = 'Unknown Title' title = 'Unknown Title'
if 'xesam:title' in meta: if 'xesam:title' in meta:

View file

@ -255,7 +255,7 @@ void PcmStream::onControlMsg(const std::string& msg)
{ {
jsonrpcpp::notification_ptr notification = dynamic_pointer_cast<jsonrpcpp::Notification>(entity); jsonrpcpp::notification_ptr notification = dynamic_pointer_cast<jsonrpcpp::Notification>(entity);
LOG(INFO, LOG_TAG) << "Notification method: " << notification->method() << ", params: " << notification->params().to_json() << "\n"; LOG(INFO, LOG_TAG) << "Notification method: " << notification->method() << ", params: " << notification->params().to_json() << "\n";
if (notification->method() == "Stream.OnMetadata") if (notification->method() == "Player.Metadata")
{ {
setMeta(notification->params().to_json()); setMeta(notification->params().to_json());
} }