feat(jellyfin): support Jellyfin 12.x authentication headers and capabilities schema

This commit is contained in:
bart
2026-11-02 22:14:13 +01:00
parent 9fe1c66a31
commit 91657aad56
+16 -9
View File
@@ -40,11 +40,14 @@ class JellyfinClient:
def _get_headers(self) -> Dict[str, str]: def _get_headers(self) -> Dict[str, str]:
headers = { headers = {
"Authorization": self.auth_header,
"X-Emby-Authorization": self.auth_header, "X-Emby-Authorization": self.auth_header,
"Content-Type": "application/json", "Content-Type": "application/json",
"Accept": "application/json",
} }
if self.auth_token: if self.auth_token:
headers["X-Emby-Token"] = self.auth_token headers["X-Emby-Token"] = self.auth_token
headers["Authorization"] = f'{self.auth_header}, Token="{self.auth_token}"'
return headers return headers
async def start(self) -> None: async def start(self) -> None:
@@ -64,7 +67,6 @@ class JellyfinClient:
self._ws_task = asyncio.create_task(self._connection_manager()) self._ws_task = asyncio.create_task(self._connection_manager())
self._report_task = asyncio.create_task(self._progress_reporter()) self._report_task = asyncio.create_task(self._progress_reporter())
async def _connection_manager(self) -> None: async def _connection_manager(self) -> None:
"""Keeps the connection and WebSocket session alive with auto-reconnect.""" """Keeps the connection and WebSocket session alive with auto-reconnect."""
while self._running: while self._running:
@@ -90,7 +92,7 @@ class JellyfinClient:
"Pw": self.config.password or "", "Pw": self.config.password or "",
} }
logger.info("Authenticating to Jellyfin server: %s as %s", self.config.server_url, self.config.username) logger.info("Authenticating to Jellyfin server: %s as %s", self.config.server_url, self.config.username)
async with self.session.post(url, json=payload) as resp: async with self.session.post(url, json=payload, headers=self._get_headers()) as resp:
if resp.status != 200: if resp.status != 200:
err_text = await resp.text() err_text = await resp.text()
raise RuntimeError(f"Authentication failed ({resp.status}): {err_text}") raise RuntimeError(f"Authentication failed ({resp.status}): {err_text}")
@@ -98,6 +100,7 @@ class JellyfinClient:
self.auth_token = data.get("AccessToken") self.auth_token = data.get("AccessToken")
self.user_id = data.get("User", {}).get("Id") self.user_id = data.get("User", {}).get("Id")
self.session.headers["X-Emby-Token"] = self.auth_token self.session.headers["X-Emby-Token"] = self.auth_token
self.session.headers["Authorization"] = f'{self.auth_header}, Token="{self.auth_token}"'
logger.info("Authenticated successfully with Jellyfin (UserId: %s)", self.user_id) logger.info("Authenticated successfully with Jellyfin (UserId: %s)", self.user_id)
async def _report_capabilities(self) -> None: async def _report_capabilities(self) -> None:
@@ -106,19 +109,18 @@ class JellyfinClient:
capabilities = { capabilities = {
"PlayableMediaTypes": ["Video", "Audio"], "PlayableMediaTypes": ["Video", "Audio"],
"SupportedCommands": [ "SupportedCommands": [
"Play", "VolumeUp",
"Playstate", "VolumeDown",
"PlayNext",
"SetVolume",
"Mute", "Mute",
"Unmute", "Unmute",
"ToggleMute", "ToggleMute",
"SetVolume",
"SetAudioStreamIndex", "SetAudioStreamIndex",
"SetSubtitleStreamIndex", "SetSubtitleStreamIndex",
"Seek",
"DisplayMessage", "DisplayMessage",
], ],
"SupportsMediaControl": true if True else True, "SupportsMediaControl": True,
"SupportsPersistentIdentifier": True,
"DeviceProfile": { "DeviceProfile": {
"Name": "HTPC MPV Player", "Name": "HTPC MPV Player",
"DirectPlayProfiles": [ "DirectPlayProfiles": [
@@ -139,8 +141,13 @@ class JellyfinClient:
f"{self.config.server_url.replace('http://', 'ws://').replace('https://', 'wss://')}" f"{self.config.server_url.replace('http://', 'ws://').replace('https://', 'wss://')}"
f"/socket?api_key={self.auth_token}&deviceId={self.config.device_id}" f"/socket?api_key={self.auth_token}&deviceId={self.config.device_id}"
) )
ws_headers = {
"Authorization": f'MediaBrowser Token="{self.auth_token}"',
"X-Emby-Token": self.auth_token,
}
logger.info("Connecting to Jellyfin WebSocket: %s", ws_url) logger.info("Connecting to Jellyfin WebSocket: %s", ws_url)
async with self.session.ws_connect(ws_url) as ws: async with self.session.ws_connect(ws_url, headers=ws_headers) as ws:
self.ws = ws self.ws = ws
logger.info("Jellyfin WebSocket connected; waiting for remote control commands") logger.info("Jellyfin WebSocket connected; waiting for remote control commands")
async for msg in ws: async for msg in ws: