fix(jellyfin): eingeschlafene Sitzung erkennen und selbst neu aufbauen
Beim zweiten Film ging nichts mehr ('Session abgelaufen') — ARIA musste Jellyfin
von Hand neu starten. Ursache: Jellyfin listet Clients noch, wenn die App laengst
weg ist, und quittiert den Play-Befehl auch dann mit Erfolg. Es lief nur nichts.
- /Sessions wird jetzt mit activeWithinSeconds gefiltert (Play + devices) —
Karteileichen tauchen gar nicht erst als Ziel auf.
- _jellyfin_playing(): nach dem Play-Befehl wird nachgesehen, ob NowPlayingItem
wirklich gesetzt ist (bis ~6s). Ohne das meldet man faelschlich Erfolg.
- Laeuft nichts und ist 'host' gesetzt: Jellyfin wird ueber den Host-Agenten neu
gestartet (host_app_launch), frische Sitzung geholt, einmal nachgelegt — also
genau der Handgriff, den ARIA vorher manuell machen musste. Ohne 'host' gibt es
eine klare Handlungsanweisung statt eines stillen Fehlschlags.
- Tool-Beschreibung draengt auf 'host', damit die Selbstheilung greift.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+74
-8
@@ -1549,14 +1549,18 @@ META_TOOLS = [
|
||||
"description": (
|
||||
"Startet die Wiedergabe eines Titels auf einem Jellyfin-Geraet. Ablauf: "
|
||||
"1. jellyfin_search -> itemId, 2. jellyfin_devices -> Geraet/Sitzung, "
|
||||
"3. jellyfin_play. Laeuft die Jellyfin-App auf dem Zielgeraet noch nicht, "
|
||||
"vorher host_app_launch (query='Jellyfin') aufrufen."
|
||||
"3. jellyfin_play. Prueft selbst nach, ob die Wiedergabe wirklich "
|
||||
"angelaufen ist. GIB IMMER 'host' MIT (Name des Host-Agenten am "
|
||||
"Fernseher, z.B. 'Fire-TV Schlafzimmer'): dann starte ich Jellyfin bei "
|
||||
"einer eingeschlafenen Sitzung automatisch neu und lege nochmal nach, "
|
||||
"statt faelschlich Erfolg zu melden."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"item_id": {"type": "string", "description": "itemId aus jellyfin_search."},
|
||||
"device": {"type": "string", "description": "Geraete-/Sitzungsname aus jellyfin_devices (z.B. 'Fire TV')."},
|
||||
"host": {"type": "string", "description": "Host-Agent am Geraet (fuer automatischen Neustart bei toter Sitzung)."},
|
||||
"server": {"type": "string", "description": "Server-Name (optional)."},
|
||||
"resume": {"type": "boolean", "description": "Wenn moeglich fortsetzen statt von vorn (Default true)."},
|
||||
},
|
||||
@@ -2876,9 +2880,15 @@ class Agent:
|
||||
except Exception as exc:
|
||||
return None, f"Jellyfin nicht erreichbar: {exc}"
|
||||
|
||||
def _jellyfin_find_session(self, server: dict, device: str):
|
||||
"""Sucht die Sitzung eines Geraets. Gibt (session, fehlertext)."""
|
||||
sessions, err = self._jellyfin_call(server, "/Sessions")
|
||||
def _jellyfin_find_session(self, server: dict, device: str, fresh_sec: int = 600):
|
||||
"""Sucht die Sitzung eines Geraets. Gibt (session, fehlertext).
|
||||
|
||||
`activeWithinSeconds` filtert eingeschlafene Sitzungen aus: Jellyfin
|
||||
listet Clients noch eine Weile, nachdem die App laengst weg ist — ein
|
||||
Play-Befehl an so eine Karteileiche verpufft wortlos.
|
||||
"""
|
||||
sessions, err = self._jellyfin_call(
|
||||
server, "/Sessions", params={"activeWithinSeconds": fresh_sec})
|
||||
if err:
|
||||
return None, err
|
||||
d = (device or "").strip().lower()
|
||||
@@ -2895,6 +2905,27 @@ class Agent:
|
||||
f"Geraet GEOEFFNET sein — sonst vorher host_app_launch "
|
||||
f"(query='Jellyfin') aufrufen.")
|
||||
|
||||
def _jellyfin_playing(self, server: dict, sess: dict, item_id: str,
|
||||
tries: int = 4, wait: float = 1.5) -> bool:
|
||||
"""Prueft, ob auf der Sitzung wirklich etwas abgespielt wird.
|
||||
|
||||
Jellyfin quittiert einen Play-Befehl auch dann mit 204, wenn die Sitzung
|
||||
laengst tot ist — ohne diese Kontrolle meldet man faelschlich Erfolg.
|
||||
"""
|
||||
want = (item_id or "").strip()
|
||||
for _ in range(max(1, tries)):
|
||||
time.sleep(wait)
|
||||
sessions, err = self._jellyfin_call(server, "/Sessions", timeout=10.0)
|
||||
if err:
|
||||
continue
|
||||
for s in (sessions or []):
|
||||
if s.get("Id") != sess.get("Id"):
|
||||
continue
|
||||
now = s.get("NowPlayingItem") or {}
|
||||
if now and (not want or now.get("Id") == want):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _dispatch_jellyfin(self, name: str, arguments: dict) -> str:
|
||||
server = self._jellyfin_server(arguments.get("server") or "")
|
||||
if not server:
|
||||
@@ -2925,7 +2956,10 @@ class Agent:
|
||||
return f"Treffer auf {label}:\n" + "\n".join(lines)
|
||||
|
||||
if name == "jellyfin_devices":
|
||||
sessions, err = self._jellyfin_call(server, "/Sessions")
|
||||
# Nur Sitzungen, die zuletzt aktiv waren — sonst listet Jellyfin
|
||||
# auch laengst geschlossene Apps, an die kein Befehl mehr geht.
|
||||
sessions, err = self._jellyfin_call(
|
||||
server, "/Sessions", params={"activeWithinSeconds": 600})
|
||||
if err:
|
||||
return f"FEHLER: {err}"
|
||||
out = []
|
||||
@@ -2967,8 +3001,40 @@ class Agent:
|
||||
params=play_params)
|
||||
if err:
|
||||
return f"FEHLER: {err}"
|
||||
return (f"OK — Wiedergabe auf '{sess.get('DeviceName')}' gestartet"
|
||||
f"{note} (Server {label}).")
|
||||
|
||||
# Nachsehen, ob wirklich etwas laeuft. Eine eingeschlafene Sitzung
|
||||
# nimmt den Befehl wortlos an, ohne dass der Film startet — genau
|
||||
# der Fall "beim zweiten Mal ging es nicht".
|
||||
if self._jellyfin_playing(server, sess, item_id):
|
||||
return (f"OK — Wiedergabe auf '{sess.get('DeviceName')}' gestartet"
|
||||
f"{note} (Server {label}).")
|
||||
|
||||
# Sitzung war tot. Wenn wir den zugehoerigen Agenten kennen:
|
||||
# Jellyfin dort neu starten, frische Sitzung holen, einmal
|
||||
# nachlegen. Sonst ehrlich sagen, was zu tun ist.
|
||||
host = (arguments.get("host") or "").strip()
|
||||
if not host:
|
||||
return ("FEHLER: Der Befehl wurde angenommen, aber es laeuft nichts — "
|
||||
"die Jellyfin-Sitzung auf dem Geraet war eingeschlafen. "
|
||||
"Starte die Jellyfin-App neu (host_app_launch mit "
|
||||
"query='Jellyfin') und versuche es erneut. Tipp: Gib "
|
||||
"jellyfin_play den Parameter 'host' mit (Name des "
|
||||
"Host-Agenten), dann mache ich das automatisch.")
|
||||
self._dispatch_host("host_app_launch", {"host": host, "query": "Jellyfin"})
|
||||
time.sleep(6) # App braucht einen Moment, bis sie sich anmeldet
|
||||
sess2, err2 = self._jellyfin_find_session(server, device, fresh_sec=120)
|
||||
if err2:
|
||||
return (f"FEHLER: Jellyfin auf '{host}' neu gestartet, aber es meldet "
|
||||
f"sich keine Sitzung: {err2}")
|
||||
self._jellyfin_call(server, f"/Sessions/{sess2['Id']}/Playing",
|
||||
method="POST", params=play_params)
|
||||
if self._jellyfin_playing(server, sess2, item_id):
|
||||
return (f"OK — Wiedergabe laeuft auf '{sess2.get('DeviceName')}'"
|
||||
f"{note}. (Die Sitzung war eingeschlafen, ich habe Jellyfin "
|
||||
f"auf '{host}' neu gestartet.)")
|
||||
return (f"FEHLER: Auch nach dem Neustart von Jellyfin auf '{host}' startet "
|
||||
f"die Wiedergabe nicht. Laeuft die App auf dem Bildschirm? "
|
||||
f"Ggf. dort einmal anmelden.")
|
||||
|
||||
if name == "jellyfin_control":
|
||||
device = (arguments.get("device") or "").strip()
|
||||
|
||||
Reference in New Issue
Block a user