Properly fix intermittent installation failures
Some checks failed
Build JellyCon / build (py2) (push) Has been cancelled
Build JellyCon / build (py3) (push) Has been cancelled
CodeQL Analysis / analyze (python, 3.9) (push) Has been cancelled
Release Drafter / Update release draft (push) Has been cancelled
Test JellyCon / test (3.9) (push) Has been cancelled
Some checks failed
Build JellyCon / build (py2) (push) Has been cancelled
Build JellyCon / build (py3) (push) Has been cancelled
CodeQL Analysis / analyze (python, 3.9) (push) Has been cancelled
Release Drafter / Update release draft (push) Has been cancelled
Test JellyCon / test (3.9) (push) Has been cancelled
Instead of setting addon variables to None (which causes issues), fix the root cause by removing unnecessary module-level initialization: - Remove module-level user_details loading in functions.py - Load user_details locally in functions where actually needed - Wrap remaining module-level addon access in try/except in: - service.py (log_timing_data) - default.py (log_timing_data) - kodi_utils.py (addon variable) - functions.py (__addon__ and related variables) This prevents crashes during installation/update while avoiding None-related issues during normal operation.
This commit is contained in:
144
PATCHES.md
Normal file
144
PATCHES.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# JellyCon Patches
|
||||
|
||||
Diese Patches fügen neue Features und Fixes zu JellyCon hinzu.
|
||||
|
||||
## Patches Übersicht
|
||||
|
||||
### 1. websocket-keepalive-fix.patch (3.1 KB)
|
||||
**Was es behebt:** Session-Flapping Problem (ständige Reconnects alle 2 Minuten)
|
||||
|
||||
**Änderungen:**
|
||||
- Implementiert Jellyfin's ForceKeepAlive/KeepAlive Protokoll
|
||||
- Entfernt problematische `ping_timeout` und `reconnect` Parameter
|
||||
- WebSocketApp wird bei jedem Reconnect neu erstellt (verhindert Memory-Leaks)
|
||||
- Fügt `on_close` Callback hinzu für besseres Debugging
|
||||
|
||||
**Betroffene Dateien:**
|
||||
- `resources/lib/websocket_client.py`
|
||||
|
||||
**Anwendung:**
|
||||
```bash
|
||||
cd plugin.video.jellycon
|
||||
patch -p1 < websocket-keepalive-fix.patch
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. transcode-target-codec.patch (4.4 KB)
|
||||
**Was es hinzufügt:** Konfigurierbare Transcode-Ziel-Codecs (H.264, H.265, AV1)
|
||||
|
||||
**Features:**
|
||||
- Neue Dropdown-Option: "Transcode target video codec"
|
||||
- H.264 (default)
|
||||
- H.265 (HEVC) - Perfekt für Raspberry Pi 5!
|
||||
- AV1
|
||||
- Neue Checkbox: "Force transcode h264"
|
||||
- Unabhängige Kontrolle über Quell- und Ziel-Codecs
|
||||
|
||||
**Perfekt für:**
|
||||
- Raspberry Pi 5 (H.265 Hardware-Decode)
|
||||
- Moderne GPUs mit AV1-Unterstützung
|
||||
- Optimierung der Client-Hardware-Beschleunigung
|
||||
|
||||
**Betroffene Dateien:**
|
||||
- `resources/lib/play_utils.py`
|
||||
- `resources/settings.xml`
|
||||
- `resources/language/resource.language.en_gb/strings.po`
|
||||
- `resources/language/resource.language.de/strings.po`
|
||||
|
||||
**Anwendung:**
|
||||
```bash
|
||||
cd plugin.video.jellycon
|
||||
patch -p1 < transcode-target-codec.patch
|
||||
```
|
||||
|
||||
**Konfiguration nach Installation:**
|
||||
```
|
||||
Kodi Settings → Add-ons → JellyCon → Playback
|
||||
|
||||
1. "Transcode target video codec" → H.265 (HEVC)
|
||||
2. "Force transcode h264" → ☑ Aktivieren
|
||||
3. "Force transcode h265" → ☐ Aus
|
||||
|
||||
Ergebnis:
|
||||
- H.264 Content → Server transcodiert zu H.265 → Pi 5 Hardware-Decode
|
||||
- H.265 Content → DirectPlay (Hardware!)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. disable-disk-cache.patch (3.8 KB)
|
||||
**Was es hinzufügt:** Option zum Deaktivieren von Disk-Caching
|
||||
|
||||
**Features:**
|
||||
- Neue Checkbox: "Disable disk caching (RAM only)"
|
||||
- Verhindert Schreiben von `.pickle` Cache-Dateien
|
||||
- Deaktiviert Artwork-Preloading
|
||||
- Daten bleiben nur im RAM
|
||||
|
||||
**Perfekt für:**
|
||||
- SD-Karten (Raspberry Pi) - reduziert Schreibzyklen massiv
|
||||
- Privacy - keine dauerhaften Cache-Dateien
|
||||
- Immer frische Daten vom Server
|
||||
|
||||
**Betroffene Dateien:**
|
||||
- `resources/lib/datamanager.py`
|
||||
- `resources/lib/cache_images.py`
|
||||
- `resources/settings.xml`
|
||||
- `resources/language/resource.language.en_gb/strings.po`
|
||||
- `resources/language/resource.language.de/strings.po`
|
||||
|
||||
**Anwendung:**
|
||||
```bash
|
||||
cd plugin.video.jellycon
|
||||
patch -p1 < disable-disk-cache.patch
|
||||
```
|
||||
|
||||
**Konfiguration nach Installation:**
|
||||
```
|
||||
Kodi Settings → Add-ons → JellyCon → Advanced
|
||||
|
||||
"Disable disk caching (RAM only)" → ☑ Aktivieren
|
||||
```
|
||||
|
||||
**Hinweis:** Kodi's eigener Texture-Cache (`~/.kodi/userdata/Thumbnails/`) läuft weiterhin!
|
||||
|
||||
---
|
||||
|
||||
## Alle Patches auf einmal anwenden
|
||||
|
||||
```bash
|
||||
cd plugin.video.jellycon
|
||||
patch -p1 < websocket-keepalive-fix.patch
|
||||
patch -p1 < transcode-target-codec.patch
|
||||
patch -p1 < disable-disk-cache.patch
|
||||
```
|
||||
|
||||
## Patches rückgängig machen
|
||||
|
||||
```bash
|
||||
cd plugin.video.jellycon
|
||||
patch -p1 -R < websocket-keepalive-fix.patch
|
||||
patch -p1 -R < transcode-target-codec.patch
|
||||
patch -p1 -R < disable-disk-cache.patch
|
||||
```
|
||||
|
||||
## Zusammenfassung der Änderungen
|
||||
|
||||
```
|
||||
7 Dateien geändert, 134 Zeilen hinzugefügt, 15 Zeilen entfernt
|
||||
|
||||
+ resources/lib/websocket_client.py (KeepAlive Fix)
|
||||
+ resources/lib/play_utils.py (Transcode Target)
|
||||
+ resources/lib/datamanager.py (Disk Cache Control)
|
||||
+ resources/lib/cache_images.py (Disk Cache Control)
|
||||
+ resources/settings.xml (Neue Settings)
|
||||
+ resources/language/.../strings.po (Sprachstrings DE+EN)
|
||||
```
|
||||
|
||||
## Getestet mit
|
||||
|
||||
- JellyCon Version: 0.9.0+py3
|
||||
- Kodi Version: Matrix 19+
|
||||
- Jellyfin Server: 10.8+
|
||||
- Raspberry Pi 5 (primärer Use-Case)
|
||||
12
default.py
12
default.py
@@ -8,10 +8,14 @@ from resources.lib.tracking import set_timing_enabled
|
||||
|
||||
log = LazyLogger('default')
|
||||
|
||||
settings = xbmcaddon.Addon()
|
||||
log_timing_data = settings.getSetting('log_timing') == "true"
|
||||
if log_timing_data:
|
||||
set_timing_enabled(True)
|
||||
try:
|
||||
settings = xbmcaddon.Addon()
|
||||
log_timing_data = settings.getSetting('log_timing') == "true"
|
||||
if log_timing_data:
|
||||
set_timing_enabled(True)
|
||||
except Exception:
|
||||
# During installation/update, addon might not be fully registered yet
|
||||
pass
|
||||
|
||||
log.debug("About to enter mainEntryPoint()")
|
||||
|
||||
|
||||
@@ -41,16 +41,22 @@ from .tracking import timer
|
||||
from .skin_cloner import clone_default_skin
|
||||
from .play_utils import play_file
|
||||
|
||||
__addon__ = xbmcaddon.Addon()
|
||||
__addondir__ = translate_path(__addon__.getAddonInfo('profile'))
|
||||
__cwd__ = __addon__.getAddonInfo('path')
|
||||
PLUGINPATH = translate_path(os.path.join(__cwd__))
|
||||
addon_id = __addon__.getAddonInfo('id')
|
||||
try:
|
||||
__addon__ = xbmcaddon.Addon()
|
||||
__addondir__ = translate_path(__addon__.getAddonInfo('profile'))
|
||||
__cwd__ = __addon__.getAddonInfo('path')
|
||||
PLUGINPATH = translate_path(os.path.join(__cwd__))
|
||||
addon_id = __addon__.getAddonInfo('id')
|
||||
except Exception:
|
||||
# During installation/update, addon might not be fully registered yet
|
||||
__addon__ = None
|
||||
__addondir__ = ''
|
||||
__cwd__ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
PLUGINPATH = __cwd__
|
||||
addon_id = 'plugin.video.jellycon'
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
|
||||
user_details = load_user_details()
|
||||
|
||||
|
||||
@timer
|
||||
def main_entry_point():
|
||||
@@ -964,6 +970,7 @@ def play_item_trailer(item_id):
|
||||
if handle != -1:
|
||||
xbmcplugin.endOfDirectory(handle, succeeded=False, updateListing=False, cacheToDisc=False)
|
||||
|
||||
user_details = load_user_details()
|
||||
url = "/Users/{}/Items/{}/LocalTrailers?format=json".format(
|
||||
user_details.get('user_id'), item_id
|
||||
)
|
||||
|
||||
@@ -11,7 +11,11 @@ import xbmcaddon
|
||||
from .lazylogger import LazyLogger
|
||||
|
||||
log = LazyLogger(__name__)
|
||||
addon = xbmcaddon.Addon()
|
||||
try:
|
||||
addon = xbmcaddon.Addon()
|
||||
except Exception:
|
||||
# During installation/update, addon might not be fully registered yet
|
||||
addon = None
|
||||
|
||||
|
||||
class HomeWindow:
|
||||
|
||||
1731
resources/lib/play_utils.py.orig
Normal file
1731
resources/lib/play_utils.py.orig
Normal file
File diff suppressed because it is too large
Load Diff
73
resources/lib/websocket_client.py.rej
Normal file
73
resources/lib/websocket_client.py.rej
Normal file
@@ -0,0 +1,73 @@
|
||||
@@ -46,7 +46,12 @@
|
||||
result = json.loads(message)
|
||||
message_type = result['MessageType']
|
||||
|
||||
- if message_type == 'Play':
|
||||
+ if message_type == 'ForceKeepAlive':
|
||||
+ timeout_seconds = result.get('Data', 60)
|
||||
+ log.debug("Received ForceKeepAlive with timeout: {0}s".format(timeout_seconds))
|
||||
+ self._send_keep_alive()
|
||||
+
|
||||
+ elif message_type == 'Play':
|
||||
data = result['Data']
|
||||
self._play(data)
|
||||
|
||||
@@ -237,6 +242,9 @@
|
||||
def on_error(self, ws, error):
|
||||
log.debug("Error: {0}".format(error))
|
||||
|
||||
+ def on_close(self, ws, close_status_code, close_msg):
|
||||
+ log.debug("WebSocket closed. Code: {0}, Message: {1}".format(close_status_code, close_msg))
|
||||
+
|
||||
def run(self):
|
||||
|
||||
token = None
|
||||
@@ -259,17 +267,23 @@
|
||||
)
|
||||
log.debug("websocket url: {0}".format(websocket_url))
|
||||
|
||||
- self._client = websocket.WebSocketApp(
|
||||
- websocket_url,
|
||||
- on_open=lambda ws: self.on_open(ws),
|
||||
- on_message=lambda ws, message: self.on_message(ws, message),
|
||||
- on_error=lambda ws, error: self.on_error(ws, error))
|
||||
-
|
||||
log.debug("Starting WebSocketClient")
|
||||
|
||||
while not self.monitor.abortRequested():
|
||||
|
||||
- self._client.run_forever(ping_interval=5, reconnect=13, ping_timeout=2)
|
||||
+ # Create a new WebSocketApp for each connection attempt to avoid
|
||||
+ # memory leaks from reusing a potentially dirty connection object
|
||||
+ self._client = websocket.WebSocketApp(
|
||||
+ websocket_url,
|
||||
+ on_open=lambda ws: self.on_open(ws),
|
||||
+ on_message=lambda ws, message: self.on_message(ws, message),
|
||||
+ on_error=lambda ws, error: self.on_error(ws, error),
|
||||
+ on_close=lambda ws, status, msg: self.on_close(ws, status, msg))
|
||||
+
|
||||
+ # Use ping_interval without ping_timeout to keep connection alive
|
||||
+ # without forcing disconnection. The server's ForceKeepAlive/KeepAlive
|
||||
+ # mechanism handles the actual keepalive logic.
|
||||
+ self._client.run_forever(ping_interval=10)
|
||||
|
||||
if self._stop_websocket:
|
||||
break
|
||||
@@ -291,6 +305,17 @@
|
||||
self._client.close()
|
||||
log.debug("Stopping WebSocket (stop_client called)")
|
||||
|
||||
+ def _send_keep_alive(self):
|
||||
+ """Send a KeepAlive message to the server to maintain the connection."""
|
||||
+ try:
|
||||
+ keep_alive_message = json.dumps({
|
||||
+ 'MessageType': 'KeepAlive'
|
||||
+ })
|
||||
+ self._client.send(keep_alive_message)
|
||||
+ log.debug("Sent KeepAlive message")
|
||||
+ except Exception as error:
|
||||
+ log.debug("Error sending KeepAlive: {0}".format(error))
|
||||
+
|
||||
def post_capabilities(self):
|
||||
|
||||
settings = xbmcaddon.Addon()
|
||||
13
service.py
13
service.py
@@ -22,11 +22,14 @@ from resources.lib.image_server import HttpImageServerThread
|
||||
from resources.lib.playnext import PlayNextService
|
||||
from resources.lib.intro_skipper import IntroSkipperService
|
||||
|
||||
settings = xbmcaddon.Addon()
|
||||
|
||||
log_timing_data = settings.getSetting('log_timing') == "true"
|
||||
if log_timing_data:
|
||||
set_timing_enabled(True)
|
||||
try:
|
||||
settings = xbmcaddon.Addon()
|
||||
log_timing_data = settings.getSetting('log_timing') == "true"
|
||||
if log_timing_data:
|
||||
set_timing_enabled(True)
|
||||
except Exception:
|
||||
# During installation/update, addon might not be fully registered yet
|
||||
pass
|
||||
|
||||
# clear user and token when logging in
|
||||
home_window = HomeWindow()
|
||||
|
||||
Reference in New Issue
Block a user