Implement Shuffle and Repeat (LoopStatus) support

- Add Shuffle and LoopStatus readwrite properties to WinampMPRIS
- Robust regex-based parsing of Repeat/Random status from Web UI
- Dynamic property change notifications via PropertiesChanged signal
- Optimized update_loop to track and reflect playback mode changes
This commit is contained in:
2026-03-21 10:51:49 -04:00
parent 7fa164f462
commit 33d76dad99

View File

@@ -51,9 +51,13 @@ class WinampMPRIS:
<property name="PlaybackStatus" type="s" access="read"> <property name="PlaybackStatus" type="s" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/> <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property> </property>
<property name="LoopStatus" type="s" access="read" /> <property name="LoopStatus" type="s" access="readwrite">
<property name="Rate" type="d" access="read" /> <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
<property name="Shuffle" type="b" access="read" /> </property>
<property name="Rate" type="d" access="readwrite" />
<property name="Shuffle" type="b" access="readwrite">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property>
<property name="Metadata" type="a{{sv}}" access="read"> <property name="Metadata" type="a{{sv}}" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/> <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property> </property>
@@ -83,6 +87,8 @@ class WinampMPRIS:
self._last_position_us = 0 self._last_position_us = 0
self._total_length_us = 0 self._total_length_us = 0
self._last_update_ts = time.time() self._last_update_ts = time.time()
self._shuffle = False
self._loop_status = "None"
def _request(self, endpoint): def _request(self, endpoint):
print(f"COMMAND RECEIVED: {endpoint}") print(f"COMMAND RECEIVED: {endpoint}")
@@ -127,12 +133,33 @@ class WinampMPRIS:
# --- Player Properties --- # --- Player Properties ---
@property @property
def PlaybackStatus(self): return self._status def PlaybackStatus(self): return self._status
@property @property
def LoopStatus(self): return "None" def LoopStatus(self): return self._loop_status
@LoopStatus.setter
def LoopStatus(self, value):
# MPRIS: "None", "Track", "Playlist"
# Winamp Web Interface has "on" or "off" for Repeat (playlist-wide)
# We'll map "Playlist" and "Track" both to Repeat ON.
if value in ["Playlist", "Track"]:
self._request("playmode?repeat=on")
else:
self._request("playmode?repeat=off")
@property @property
def Rate(self): return 1.0 def Rate(self): return 1.0
@Rate.setter
def Rate(self, value): pass
@property @property
def Shuffle(self): return False def Shuffle(self): return self._shuffle
@Shuffle.setter
def Shuffle(self, value):
if value:
self._request("playmode?random=on")
else:
self._request("playmode?random=off")
@property @property
def CanGoNext(self): return True def CanGoNext(self): return True
@property @property
@@ -223,6 +250,8 @@ def update_loop(player):
last_known_status = "" last_known_status = ""
last_known_album = "" last_known_album = ""
last_time_str = "" last_time_str = ""
last_known_shuffle = False
last_known_loop = "None"
offline_logged = False offline_logged = False
while True: while True:
@@ -271,6 +300,31 @@ def update_loop(player):
player._status = new_status player._status = new_status
# Parse Repeat and Random status
# <p>Repeat is <b>off</b> ... <br>Random is <b>off</b> ... </p>
repeat_tag = None
for p in p_tags:
if "Repeat is" in p.text:
repeat_tag = p
break
if repeat_tag:
# Extract "on" or "off" status
text = repeat_tag.get_text()
is_repeat = False
is_random = False
m_rep = re.search(r"Repeat is\s*(?:<b>)?(\w+)(?:</b>)?", text, re.I)
if m_rep:
is_repeat = m_rep.group(1).lower() == "on"
m_rand = re.search(r"Random is\s*(?:<b>)?(\w+)(?:</b>)?", text, re.I)
if m_rand:
is_random = m_rand.group(1).lower() == "on"
player._loop_status = "Playlist" if is_repeat else "None"
player._shuffle = is_random
# Always update time from Web UI # Always update time from Web UI
match_time = re.search(r"\((\d+:?\d*:\d+) / (\d+:?\d*:\d+)\)", status_raw) match_time = re.search(r"\((\d+:?\d*:\d+) / (\d+:?\d*:\d+)\)", status_raw)
if match_time: if match_time:
@@ -301,7 +355,9 @@ def update_loop(player):
if (player._status != last_known_status or if (player._status != last_known_status or
player._title != last_known_title or player._title != last_known_title or
player._album != last_known_album): player._album != last_known_album or
player._shuffle != last_known_shuffle or
player._loop_status != last_known_loop):
# Fetch art if album/artist changed # Fetch art if album/artist changed
if player._artist != "Unknown" and player._album: if player._artist != "Unknown" and player._album:
@@ -312,15 +368,19 @@ def update_loop(player):
last_known_status = player._status last_known_status = player._status
last_known_title = player._title last_known_title = player._title
last_known_album = player._album last_known_album = player._album
last_known_shuffle = player._shuffle
last_known_loop = player._loop_status
album_str = f" [{player._album}]" if player._album else "" album_str = f" [{player._album}]" if player._album else ""
print(f"UPDATE: [{player._status}] {player._artist}{album_str} - {player._title}") print(f"UPDATE: [{player._status}] Shuffle: {player._shuffle}, Loop: {player._loop_status}, {player._artist}{album_str} - {player._title}")
player.PropertiesChanged( player.PropertiesChanged(
"org.mpris.MediaPlayer2.Player", "org.mpris.MediaPlayer2.Player",
{ {
"PlaybackStatus": player.PlaybackStatus, "PlaybackStatus": player.PlaybackStatus,
"Metadata": player.Metadata "Metadata": player.Metadata,
"Shuffle": player.Shuffle,
"LoopStatus": player.LoopStatus
}, },
[] []
) )