feat(jellyfin): support Jellyfin 12.x authentication headers and capabilities schema
This commit is contained in:
+16
-9
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user