Source code for mopidy.mpd.protocol.status

import pykka

from mopidy.core import PlaybackState
from mopidy.mpd import exceptions, protocol, translator

#: Subsystems that can be registered with idle command.
SUBSYSTEMS = [
    "database",
    "mixer",
    "options",
    "output",
    "player",
    "playlist",
    "stored_playlist",
    "update",
]


[docs]@protocol.commands.add("clearerror") def clearerror(context): """ *musicpd.org, status section:* ``clearerror`` Clears the current error message in status (this is also accomplished by any command that starts playback). """ raise exceptions.MpdNotImplemented # TODO
[docs]@protocol.commands.add("currentsong") def currentsong(context): """ *musicpd.org, status section:* ``currentsong`` Displays the song info of the current song (same song that is identified in status). """ tl_track = context.core.playback.get_current_tl_track().get() stream_title = context.core.playback.get_stream_title().get() if tl_track is not None: position = context.core.tracklist.index(tl_track).get() return translator.track_to_mpd_format( tl_track, position=position, stream_title=stream_title )
[docs]@protocol.commands.add("idle") def idle(context, *subsystems): """ *musicpd.org, status section:* ``idle [SUBSYSTEMS...]`` Waits until there is a noteworthy change in one or more of MPD's subsystems. As soon as there is one, it lists all changed systems in a line in the format ``changed: SUBSYSTEM``, where ``SUBSYSTEM`` is one of the following: - ``database``: the song database has been modified after update. - ``update``: a database update has started or finished. If the database was modified during the update, the database event is also emitted. - ``stored_playlist``: a stored playlist has been modified, renamed, created or deleted - ``playlist``: the current playlist has been modified - ``player``: the player has been started, stopped or seeked - ``mixer``: the volume has been changed - ``output``: an audio output has been enabled or disabled - ``options``: options like repeat, random, crossfade, replay gain While a client is waiting for idle results, the server disables timeouts, allowing a client to wait for events as long as MPD runs. The idle command can be canceled by sending the command ``noidle`` (no other commands are allowed). MPD will then leave idle mode and print results immediately; might be empty at this time. If the optional ``SUBSYSTEMS`` argument is used, MPD will only send notifications when something changed in one of the specified subsystems. """ # TODO: test against valid subsystems if not subsystems: subsystems = SUBSYSTEMS for subsystem in subsystems: context.subscriptions.add(subsystem) active = context.subscriptions.intersection(context.events) if not active: context.session.prevent_timeout = True return response = [] context.events = set() context.subscriptions = set() for subsystem in active: response.append(f"changed: {subsystem}") return response
[docs]@protocol.commands.add("noidle", list_command=False) def noidle(context): """See :meth:`_status_idle`.""" if not context.subscriptions: return context.subscriptions = set() context.events = set() context.session.prevent_timeout = False
[docs]@protocol.commands.add("stats") def stats(context): """ *musicpd.org, status section:* ``stats`` Displays statistics. - ``artists``: number of artists - ``songs``: number of albums - ``uptime``: daemon uptime in seconds - ``db_playtime``: sum of all song times in the db - ``db_update``: last db update in UNIX time - ``playtime``: time length of music played """ return { "artists": 0, # TODO "albums": 0, # TODO "songs": 0, # TODO "uptime": 0, # TODO "db_playtime": 0, # TODO "db_update": 0, # TODO "playtime": 0, # TODO }
[docs]@protocol.commands.add("status") def status(context): """ *musicpd.org, status section:* ``status`` Reports the current status of the player and the volume level. - ``volume``: 0-100 or -1 - ``repeat``: 0 or 1 - ``single``: 0 or 1 - ``consume``: 0 or 1 - ``playlist``: 31-bit unsigned integer, the playlist version number - ``playlistlength``: integer, the length of the playlist - ``state``: play, stop, or pause - ``song``: playlist song number of the current song stopped on or playing - ``songid``: playlist songid of the current song stopped on or playing - ``nextsong``: playlist song number of the next song to be played - ``nextsongid``: playlist songid of the next song to be played - ``time``: total time elapsed (of current playing/paused song) - ``elapsed``: Total time elapsed within the current song, but with higher resolution. - ``bitrate``: instantaneous bitrate in kbps - ``xfade``: crossfade in seconds - ``audio``: sampleRate``:bits``:channels - ``updatings_db``: job id - ``error``: if there is an error, returns message here *Clarifications based on experience implementing* - ``volume``: can also be -1 if no output is set. - ``elapsed``: Higher resolution means time in seconds with three decimal places for millisecond precision. """ tl_track = context.core.playback.get_current_tl_track() next_tlid = context.core.tracklist.get_next_tlid() futures = { "tracklist.length": context.core.tracklist.get_length(), "tracklist.version": context.core.tracklist.get_version(), "mixer.volume": context.core.mixer.get_volume(), "tracklist.consume": context.core.tracklist.get_consume(), "tracklist.random": context.core.tracklist.get_random(), "tracklist.repeat": context.core.tracklist.get_repeat(), "tracklist.single": context.core.tracklist.get_single(), "playback.state": context.core.playback.get_state(), "playback.current_tl_track": tl_track, "tracklist.index": context.core.tracklist.index(tl_track.get()), "tracklist.next_tlid": next_tlid, "tracklist.next_index": context.core.tracklist.index( tlid=next_tlid.get() ), "playback.time_position": context.core.playback.get_time_position(), } pykka.get_all(futures.values()) result = [ ("volume", _status_volume(futures)), ("repeat", _status_repeat(futures)), ("random", _status_random(futures)), ("single", _status_single(futures)), ("consume", _status_consume(futures)), ("playlist", _status_playlist_version(futures)), ("playlistlength", _status_playlist_length(futures)), ("xfade", _status_xfade(futures)), ("state", _status_state(futures)), ] if futures["playback.current_tl_track"].get() is not None: result.append(("song", _status_songpos(futures))) result.append(("songid", _status_songid(futures))) if futures["tracklist.next_tlid"].get() is not None: result.append(("nextsong", _status_nextsongpos(futures))) result.append(("nextsongid", _status_nextsongid(futures))) if futures["playback.state"].get() in ( PlaybackState.PLAYING, PlaybackState.PAUSED, ): result.append(("time", _status_time(futures))) result.append(("elapsed", _status_time_elapsed(futures))) result.append(("bitrate", _status_bitrate(futures))) return result
def _status_bitrate(futures): current_tl_track = futures["playback.current_tl_track"].get() if current_tl_track is None: return 0 if current_tl_track.track.bitrate is None: return 0 return current_tl_track.track.bitrate def _status_consume(futures): if futures["tracklist.consume"].get(): return 1 else: return 0 def _status_playlist_length(futures): return futures["tracklist.length"].get() def _status_playlist_version(futures): return futures["tracklist.version"].get() def _status_random(futures): return int(futures["tracklist.random"].get()) def _status_repeat(futures): return int(futures["tracklist.repeat"].get()) def _status_single(futures): return int(futures["tracklist.single"].get()) def _status_songid(futures): current_tl_track = futures["playback.current_tl_track"].get() if current_tl_track is not None: return current_tl_track.tlid else: return _status_songpos(futures) def _status_songpos(futures): return futures["tracklist.index"].get() def _status_nextsongid(futures): return futures["tracklist.next_tlid"].get() def _status_nextsongpos(futures): return futures["tracklist.next_index"].get() def _status_state(futures): state = futures["playback.state"].get() if state == PlaybackState.PLAYING: return "play" elif state == PlaybackState.STOPPED: return "stop" elif state == PlaybackState.PAUSED: return "pause" def _status_time(futures): position = futures["playback.time_position"].get() // 1000 total = _status_time_total(futures) // 1000 return f"{position:d}:{total:d}" def _status_time_elapsed(futures): elapsed = futures["playback.time_position"].get() / 1000.0 return f"{elapsed:.3f}" def _status_time_total(futures): current_tl_track = futures["playback.current_tl_track"].get() if current_tl_track is None: return 0 elif current_tl_track.track.length is None: return 0 else: return current_tl_track.track.length def _status_volume(futures): volume = futures["mixer.volume"].get() if volume is not None: return volume else: return -1 def _status_xfade(futures): return 0 # Not supported