Add systemd user service and enhance offline robustness

This commit is contained in:
2026-03-19 03:00:47 -04:00
parent f597c9656c
commit 853a451d0a
3 changed files with 111 additions and 22 deletions

View File

@@ -61,7 +61,9 @@ class WinampMPRIS:
<property name="CanPause" type="b" access="read" />
<property name="CanControl" type="b" access="read" />
<property name="CanSeek" type="b" access="read" />
<property name="Position" type="x" access="read" />
<property name="Position" type="x" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
</property>
<property name="Volume" type="d" access="read" />
</interface>
</node>
@@ -74,6 +76,9 @@ class WinampMPRIS:
self._title = "Winamp"
self._artist = "Unknown"
self._status = "Stopped"
self._last_position_us = 0
self._total_length_us = 0
self._last_update_ts = time.time()
def _request(self, endpoint):
try:
@@ -129,8 +134,17 @@ class WinampMPRIS:
def CanControl(self): return True
@property
def CanSeek(self): return False
@property
def Position(self): return 0
def Position(self):
if self._status == "Playing":
elapsed_us = int((time.time() - self._last_update_ts) * 1e6)
current_pos = self._last_position_us + elapsed_us
if self._total_length_us > 0:
return min(current_pos, self._total_length_us)
return current_pos
return self._last_position_us
@property
def Volume(self): return 1.0
@@ -139,21 +153,32 @@ class WinampMPRIS:
# Metadata is a{sv}, so values MUST be wrapped in GLib.Variant
return {
'mpris:trackid': GLib.Variant('o', '/org/mpris/MediaPlayer2/winamp/track/0'),
'mpris:length': GLib.Variant('x', 0),
'mpris:length': GLib.Variant('x', self._total_length_us),
'xesam:title': GLib.Variant('s', self._title),
'xesam:artist': GLib.Variant('as', [self._artist]),
'xesam:album': GLib.Variant('s', ''),
'mpris:artUrl': GLib.Variant('s', 'https://webamp.org/favicon.ico')
}
def parse_time_to_us(time_str):
"""Parses MM:SS or H:MM:SS to microseconds."""
parts = list(map(int, time_str.split(':')))
if len(parts) == 2: # MM:SS
return (parts[0] * 60 + parts[1]) * 1_000_000
elif len(parts) == 3: # H:MM:SS
return (parts[0] * 3600 + parts[1] * 60 + parts[2]) * 1_000_000
return 0
def update_loop(player):
last_known_title = ""
last_known_status = ""
offline_logged = False
while True:
try:
r = requests.get(f"{BASE_URL}/main", auth=AUTH, timeout=2)
if r.status_code == 200:
offline_logged = False
soup = BeautifulSoup(r.text, 'html.parser')
p_tags = soup.find_all('p')
status_raw = p_tags[0].text if p_tags else ""
@@ -164,27 +189,37 @@ def update_loop(player):
new_artist = "Unknown"
new_title = "Winamp"
new_pos_us = 0
new_len_us = 0
match = re.search(r"track \d+ - (.*?) - (.*?) -", status_raw)
if match:
new_artist = match.group(1).strip()
new_title = match.group(2).strip()
# Metadata match
match_meta = re.search(r"track \d+ - (.*?) - (.*?) -", status_raw)
if match_meta:
new_artist = match_meta.group(1).strip()
new_title = match_meta.group(2).strip()
elif "track" in status_raw:
new_title = status_raw.split('-')[0].replace("Playing track ", "").strip()
# Time match: (1:06 / 2:48)
match_time = re.search(r"\((\d+:?\d*:\d+) / (\d+:?\d*:\d+)\)", status_raw)
if match_time:
new_pos_us = parse_time_to_us(match_time.group(1))
new_len_us = parse_time_to_us(match_time.group(2))
# Update internal state
player._status = new_status
player._artist = new_artist
player._title = new_title
player._last_position_us = new_pos_us
player._total_length_us = new_len_us
player._last_update_ts = time.time()
if new_status != last_known_status or new_title != last_known_title:
player._status = new_status
player._artist = new_artist
player._title = new_title
last_known_status = new_status
last_known_title = new_title
print(f"UPDATE: [{player._status}] {player._artist} - {player._title}")
print(f"UPDATE: [{player._status}] {player._artist} - {player._title} ({match_time.group(1) if match_time else '0:00'})")
# pydbus PropertiesChanged helper will wrap these in Variants
# based on the introspection XML.
# Metadata is a{sv}, so its value (the dict) will be wrapped in Variant('a{sv}', ...)
# For that to work, the dict's values must already be Variants.
player.PropertiesChanged(
"org.mpris.MediaPlayer2.Player",
{
@@ -194,10 +229,30 @@ def update_loop(player):
[]
)
except Exception as e:
print(f"Update error: {e}")
import traceback
traceback.print_exc()
except (requests.exceptions.RequestException, Exception) as e:
if not offline_logged:
print(f"Winamp Web Interface offline or unreachable: {e}")
offline_logged = True
# Reset state when offline
player._status = "Stopped"
player._artist = "Unknown"
player._title = "Winamp (Offline)"
player._last_position_us = 0
player._total_length_us = 0
player._last_update_ts = time.time()
if last_known_status != "Stopped":
last_known_status = "Stopped"
player.PropertiesChanged(
"org.mpris.MediaPlayer2.Player",
{
"PlaybackStatus": player.PlaybackStatus,
"Metadata": player.Metadata
},
[]
)
time.sleep(2)
if __name__ == "__main__":