mirror of
https://github.com/badaix/snapcast.git
synced 2025-06-27 23:17:04 +02:00
Add mpd status mapping
This commit is contained in:
parent
6e6b63ec26
commit
283c3d2c9b
3 changed files with 68 additions and 8 deletions
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue