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:
@@ -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
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user