Add song progress bar
Deploy HEOS panel / deploy (push) Successful in 25s

This commit is contained in:
2026-09-16 15:03:45 +02:00
parent 58a2dedc03
commit 4dd7e298a5
5 changed files with 103 additions and 6 deletions
+38
View File
@@ -43,6 +43,11 @@ class HeosClient:
self._sock = None
self._buffer = b""
self._lock = threading.Lock()
# pid -> {"cur_pos", "duration"}, filled in from whatever
# unsolicited progress events turn up while we are reading the
# reply to some other command. Dropped on reconnect, since a gap
# in the connection is a gap in what we know.
self._progress = {}
# -- connection management -----------------------------------------
def _connect(self):
@@ -51,6 +56,14 @@ class HeosClient:
sock.settimeout(self.timeout)
self._sock = sock
self._buffer = b""
self._progress = {}
# Without this, HEOS never pushes the now-playing-progress events
# that _exchange() below watches for. Best-effort: a player that
# refuses it still works, it just never shows song progress.
try:
self._exchange("system/register_for_change_events", {"enable": "on"})
except (OSError, HeosError):
pass
def _close(self):
if self._sock is not None:
@@ -106,6 +119,7 @@ class HeosClient:
heos = reply.get("heos", {})
if heos.get("command") != path:
self._watch_progress(heos)
continue # an event or a late reply to something else
if "under process" in heos.get("message", ""):
continue # placeholder ack; the real payload follows
@@ -113,6 +127,30 @@ class HeosClient:
raise HeosError(_failure_text(heos))
return reply
def _watch_progress(self, heos: dict):
"""Skims a passing player_now_playing_progress event for its
pid/cur_pos/duration, the only source of song-position data this
client has -- there is no dedicated listener, so this only catches
what happens to arrive while some other command is being read."""
if heos.get("command") != "event/player_now_playing_progress":
return
message = parse_message(heos.get("message", ""))
pid = message.get("pid")
if not pid:
return
try:
self._progress[pid] = {
"cur_pos": int(message.get("cur_pos", 0)),
"duration": int(message.get("duration", 0)),
}
except ValueError:
pass
def progress_for(self, pid) -> dict:
"""{"cur_pos", "duration"} in ms for a player, or None if no
progress event for it has come through yet this connection."""
return self._progress.get(str(pid))
def heart_beat(self):
"""Keep the socket warm so the first press after an idle spell is
as fast as the rest."""