# Исследование: нативный Kotlin-клиент Jellyfin для Android и Android TV на libVLC Дата сбора: 2026-10-11. Документ собран из шести параллельных исследований (веб-поиск, чтение исходников, Maven/GitHub API). Все ссылки проверялись на доступность; непроверенные помечены «(не проверено)». ## Содержание 1. [Jellyfin API, официальные SDK, протокол](#часть-1-jellyfin-api-официальные-sdk-и-протокольная-документация) 2. [Существующие open-source клиенты: что заимствовать](#часть-2-обзор-open-source-jellyfin-клиентов-и-смежных-проектов) 3. [libVLC на Android: артефакты, API, субтитры, стриминг, лицензии](#часть-3-libvlc-на-android-как-движок-воспроизведения) 4. [Android-платформа: Compose for TV, медиа-интеграция, дистрибуция](#часть-4-android-платформа) 5. [Доменный слой воспроизведения: DeviceProfile, транскодирование, субтитры, изображения, тестовые медиа](#часть-5-доменный-слой-воспроизведения) 6. [Инструментарий разработки: сборка, эмуляторы Android TV с пультом, просмотр UI](#часть-6-инструментарий-разработки) 7. [Гибридный движок воспроизведения, тестовый ТВ, план прототипа](#часть-7-гибридный-движок-воспроизведения-дополнение-от-2026-10-12) ## Ключевые выводы (сводка) - **Сервер и SDK.** Актуальный сервер — Jellyfin 12.2 (октябрь 2026, схема версий сменилась с 10.x на 12.x). Официальный `jellyfin-sdk-kotlin` 1.9.2 (LGPL-3.0, Ktor) покрывает API, discovery и WebSocket; использовать его как есть. С 12.0 legacy-авторизация выключена: только заголовок `Authorization: MediaBrowser ...` и query-параметр `ApiKey` там, где заголовок невозможен (URL для libVLC). - **libVLC.** Артефакт `org.videolan.android:libvlc-all:3.7.7` (обновляется ежемесячно; 4.0 только EAP). Нативные библиотеки 40–63 МБ на ABI, поэтому только AAB или ABI-splits. Произвольных HTTP-заголовков нет, TLS через GnuTLS (нужен обработчик `Dialog`), HLS-seek слабый, Dolby Vision без RPU. Сильные стороны: ASS/PGS/VobSub рендерит сам, passthrough, любые контейнеры. - **Референсы.** Ни один живой Kotlin-клиент не использует libVLC как основной движок; официальный Android TV клиент удалил его в 2024 из-за плохого управления жизненным циклом и размера APK. Лучшие образцы: Swiftfin (DeviceProfile для VLC), jellyfin-androidtv (архитектура playback-модулей, профиль под Android-железо), Findroid (`BasePlayer` поверх стороннего движка), Wholphin (Compose for TV без Leanback). - **UI для TV.** Leanback официально deprecated, для нового проекта только Compose for TV (`androidx.tv:tv-material:1.1.0`, stable). TV-специфичные lazy-списки удалены, используются обычные `LazyRow/LazyColumn` с `BringIntoViewSpec`. libVLC можно обернуть в media3 `SimpleBasePlayer`/`BasePlayer` и отдать в `MediaSession` для Now Playing, PiP и медиакнопок; готовые шаблоны есть в jellyfin-androidtv и Findroid. - **Лицензия.** Бинарный libVLC де-факто GPLv3-совместим, поэтому для приложения брать GPL-2.0-or-later или GPL-3.0, не GPL-2.0-only. Имя «Jellyfin» и логотип в названии и иконке использовать нельзя. - **Инструменты.** Android Studio 2026.2.1, AGP 9.4, Gradle 9.8, Kotlin 2.4, Compose for TV `tv-material 1.1.0`. Android TV эмулятор управляется стрелками клавиатуры и `adb shell input keyevent`; рецепт запуска из командной строки в части 6. --- # Часть 1. Jellyfin API, официальные SDK и протокольная документация _Исходный заголовок: Jellyfin: API сервера, официальные SDK и протокольная документация — отчёт для нового Kotlin-клиента (Android + Android TV, libVLC) Дата исследования: 2026-10-11. Все URL ниже были проверены (HTTP 200 / содержимое прочитано), кроме помеченных «(не проверено)». ## 0. Самое важное — контекст версий на октябрь 2026 | Что | Версия / дата | Источник | |---|---|---| | Jellyfin Server **12.2** (текущий stable) | 05.10.2026 | [GitHub release v12.2](https://github.com/jellyfin/jellyfin/releases/tag/v12.2) | | Jellyfin 12.1 | 15.09.2026 | [GitHub release v12.1](https://github.com/jellyfin/jellyfin/releases/tag/v12.1) | | Jellyfin **12.0** (бывший «10.12», смена схемы версий) | 07.09.2026 | [Release post 12.0](https://jellyfin.org/posts/jellyfin-release-12.0/) | | Jellyfin 10.11.0 | 19.10.2025 | [Release post 10.11.0](https://jellyfin.org/posts/jellyfin-release-10.11.0/) | | Jellyfin 10.10.0 | 26.10.2024 | [Release post 10.10.0](https://jellyfin.org/posts/jellyfin-release-10.10.0/) | | Jellyfin 10.9.0 | 11.05.2024 | [Release post 10.9.0](https://jellyfin.org/posts/jellyfin-release-10.9.0/) | | jellyfin-sdk-kotlin **1.9.2** (API 12.2.0, min server 12.0.0) | 06.10.2026 | [Releases](https://github.com/jellyfin/jellyfin-sdk-kotlin/releases), Maven Central metadata | | jellyfin-sdk-kotlin 1.8.12 (ветка для 10.11.x; min 10.11.0) | 26.07.2026 | там же | Ключевые следствия для нового клиента: 1. **Legacy-авторизация выключена по умолчанию с 12.0** (`X-Emby-Authorization`, `X-Emby-Token`, `X-MediaBrowser-Token`, query `api_key`, префиксы `/emby/*` и `/mediabrowser/*` удалены). Нужно использовать только `Authorization: MediaBrowser ...` и query-параметр `ApiKey` (с большой буквы) там, где заголовок невозможен (например, URL для libVLC). 2. HLS-эндпоинты (`master.m3u8` и др.) с 12.0 **скрыты из OpenAPI** — их нет в сгенерированном SDK; нужно использовать `MediaSourceInfo.TranscodingUrl` из ответа `PlaybackInfo`. 3. SDK 1.9.x несовместим с серверами ≤10.11; если хотите поддерживать 10.11 — придётся собирать на 1.8.x или писать свой клиент поверх OpenAPI. 4. В SDK 1.9 (под API 12) **переименованы классы операций** относительно старых примеров: `PlayStateApi.reportPlayback*` → `SessionApi.reportPlayback*`, `ItemsApi` → `LibraryApi` (`getItems`, `getResumeItems`, `getLatestMedia`), `TvShowsApi` → `ShowApi`, `VideosApi` → `VideoApi`, `UserViewsApi` → `UserViewApi`, `DynamicHlsApi` отсутствует. Проверено по Dokka 1.9.x. --- ## 1. Официальная документация API, аутентификация, OpenAPI **Важно:** на jellyfin.org **нет** отдельного раздела «API docs» (`/docs/general/server/api/` → 404). Официальные источники по API — это: интерактивный ReDoc на api.jellyfin.org, сам OpenAPI JSON, документация SDK (kotlin-sdk.jellyfin.org / typescript-sdk.jellyfin.org), release-посты и gist мейнтейнера SDK Niels van Velzen. - [api.jellyfin.org (ReDoc)](https://api.jellyfin.org/) — официальный интерактивный справочник API (рендерится из OpenAPI JS'ом, поэтому curl и простая загрузка страницы видят только заголовок страницы). Ссылка на него стоит в официальном contributing-гайде как «API documentation». - [OpenAPI JSON stable](https://api.jellyfin.org/openapi/jellyfin-openapi-stable.json) — актуальная спецификация stable-сервера (сейчас 12.2.0, ~1.9 МБ, 294 пути). **Gotcha:** с CDN api.jellyfin.org скачивание у меня обрывалось на 14 КБ; надёжнее брать с зеркала ниже. - [OpenAPI JSON unstable](https://api.jellyfin.org/openapi/jellyfin-openapi-unstable.json) — спецификация master-ветки. - [Архив OpenAPI по версиям](https://repo.jellyfin.org/files/openapi/stable/) — файлы `jellyfin-openapi-<версия>.json` для каждого релиза: от 10.9.0 через 10.10.7, 10.11.0…10.11.11 до 12.0, 12.1, 12.2 (например, [jellyfin-openapi-12.2.json](https://repo.jellyfin.org/files/openapi/stable/jellyfin-openapi-12.2.json), [jellyfin-openapi-10.11.0.json](https://repo.jellyfin.org/files/openapi/stable/jellyfin-openapi-10.11.0.json)). Именно это «версионирование API по релизам сервера»: отдельного номера версии API нет, `info.version` = версии сервера. Удобно для diff'ов между версиями (см. раздел 7). Есть и каталог [unstable](https://repo.jellyfin.org/files/openapi/unstable/). - [Gist Niels van Velzen «Jellyfin authorization»](https://gist.github.com/nielsvanvelzen/ea047d9028f676185832e51ffaf12a6f) — де-факто официальное описание схемы авторизации (на него ссылается release-пост 10.11). Схема `MediaBrowser`, параметры через запятую, значения в двойных кавычках и URL-encoded, ключи регистрозависимые, неизвестные ключи игнорируются. Перечисляет устаревшие методы (query `api_key`, заголовки `X-Emby-Token`, `X-MediaBrowser-Token`, `X-Emby-Authorization`) и подтверждает их отключение в 12.0. Рекомендует хешировать имя пользователя в `DeviceId`. Формат заголовка (из gist + исходника SDK `AuthorizationHeaderBuilder.kt`): ``` Authorization: MediaBrowser Client="MyApp", Device="Pixel 8", DeviceId="<уникальный id>", Version="1.0.0", Token="" ``` До логина — тот же заголовок без `Token`. Для API-ключа достаточно `Authorization: MediaBrowser Token=""`. В OpenAPI это security scheme `CustomAuthentication` (apiKey, header `Authorization`). - [Исходник серверной проверки авторизации `AuthorizationContext.cs`](https://github.com/jellyfin/jellyfin/blob/master/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs) — «источник истины»: порядок поиска токена: заголовок `Authorization` → (только при `EnableLegacyAuthorization`) `X-Emby-Token`, `X-MediaBrowser-Token` → query `ApiKey` (всегда) → (legacy) query `api_key`; схема `Emby` принимается только в legacy-режиме. Проверено grep'ом по master. - [SDK: AuthorizationHeaderBuilder.kt](https://github.com/jellyfin/jellyfin-sdk-kotlin/blob/master/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/util/AuthorizationHeaderBuilder.kt) — эталонная реализация сборки заголовка (`Client`, `Version`, `DeviceId`, `Device`, `Token`; `encodeURLPart` для значений). - [Issue #15962 «Default EnableLegacyAuthorization=false breaks Jellyseerr»](https://github.com/jellyfin/jellyfin/issues/15962) — позиция мейнтейнеров: депрекация объявлена в 2023, в 10.11 добавлен opt-in флаг для тестирования, в 12.0 стало opt-out, в следующем мажоре legacy-опция будет удалена совсем; «откатывать не планируем». Временный обход для админов — `EnableLegacyAuthorization` в `system.xml` (см. также [форум-тред](https://forum.jellyfin.org/printthread.php?tid=15085)). - [Help Net Security о 12.0](https://www.helpnetsecurity.com/2026/09/08/jellyfin-12-0-security-fixes/) — новостная заметка про отключение legacy-логинов и security-фиксы 12.0 (вторичный источник). **DeviceId / идентификация клиента** - Сервер хранит **один access token на `DeviceId`**: если на одном устройстве залогинены два пользователя Jellyfin с одинаковым `DeviceId`, второй логин инвалидирует первый. Рекомендация (gist + сообщество): `DeviceId = hash(installationId + username)` или отдельный id на пользователя. - `Client`/`Version`/`Device` показываются в дашборде сервера (Devices, Sessions) и влияют на «Playback reporting». - В SDK это `ClientInfo(name, version)` и `DeviceInfo(id, name)` ([Dokka ClientInfo](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-model/org.jellyfin.sdk.model/-client-info/index.html), [Dokka DeviceInfo](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-model/org.jellyfin.sdk.model/-device-info/index.html)); с 1.9.0 в `DeviceInfo` можно задать язык API (сервер локализует ответы; на сервере с 12.0 поддерживается `Accept-Language`). - Эндпоинты устройств: `GET /Devices`, `GET /Devices/Info`, `GET/POST /Devices/Options`; разлогин — `POST /Sessions/Logout`; текущий пользователь — `GET /Users/Me`; публичный список пользователей для экрана логина — `GET /Users/Public`. **Логин по паролю:** `POST /Users/AuthenticateByName` с телом `{"Username": "...", "Pw": "..."}` (пользователь без пароля — пустая строка) → `AuthenticationResult {User, SessionInfo, AccessToken, ServerId}`; 401 при неверных данных. **Quick Connect** (все пути проверены по OpenAPI 12.2): - `GET /QuickConnect/Enabled` → bool. - `POST /QuickConnect/Initiate` → `QuickConnectResult {Authenticated, Secret, Code, DeviceId, DeviceName, AppName, AppVersion, DateAdded}`. **`GET /QuickConnect/Initiate` удалён в 12.0** — только POST. - `GET /QuickConnect/Connect?secret=...` — опрос состояния (SDK рекомендует раз в ~5 с). - `POST /Users/AuthenticateWithQuickConnect` с телом `{"Secret": "..."}` → `AuthenticationResult`. - `POST /QuickConnect/Authorize?code=...&userId=...` — вызывается с уже залогиненного устройства (если хотите, чтобы ваш клиент мог *подтверждать* чужие коды). - [Kotlin SDK guide: Authentication](https://kotlin-sdk.jellyfin.org/guide/authentication.html) — готовые сниппеты для `authenticateUserByName`, Quick Connect, `api.update(accessToken=...)`. - [Docs: Quick Connect (пользовательская)](https://jellyfin.org/docs/general/server/quick-connect/) — поведение фичи, код из 6 символов, включён по умолчанию, список клиентов, умеющих логиниться/авторизовывать. **Неофициальные зеркала (с осторожностью):** - [mintlify.wiki: jellyfin/api/authentication/overview](https://mintlify.wiki/jellyfin/jellyfin/api/authentication/overview) — AI-сгенерированное «зеркало» документации, не официальное; примеры curl корректны, но детали (например, про `api_key`) местами противоречивы. --- ## 2. jellyfin-sdk-kotlin и legacy-репозиторий - [github.com/jellyfin/jellyfin-sdk-kotlin](https://github.com/jellyfin/jellyfin-sdk-kotlin) — официальный Kotlin SDK (JVM + Android), сгенерирован из OpenAPI сервера. Лицензия **LGPL-3.0** (совместимо с GPL-приложением при обычном линковании). ~2190 коммитов, активно поддерживается (релизы в сентябре–октябре 2026). - **Важно про `jellyfin-apiclient-java`:** [github.com/jellyfin/jellyfin-apiclient-java](https://github.com/jellyfin/jellyfin-apiclient-java) теперь **редиректит на jellyfin-sdk-kotlin** — это тот же репозиторий, переименованный; отдельного deprecated Java-клиента больше нет. Старое Java-API было окончательно убрано в SDK v1.7 («Java helper utilities removed», см. миграцию v1.7). - [kotlin-sdk.jellyfin.org — Getting started](https://kotlin-sdk.jellyfin.org/guide/getting-started.html) — Maven Central, `implementation("org.jellyfin.sdk:jellyfin-core:$sdkVersion")`, DSL `createJellyfin { clientInfo = ClientInfo(name, version); context = androidContext }`, `jellyfin.createApi(baseUrl, accessToken, clientInfo, deviceInfo, httpClientOptions)`. Указано: Java 8+, «Android 4.4+» (устарело — см. ниже), desugaring для Android < 8. Snapshot-репозиторий `master-SNAPSHOT` на `https://s01.oss.sonatype.org/content/repositories/snapshots/` (работоспособность snapshot-репо не проверена). В разделе «Projects using the SDK» перечислены Findroid и Homedia — можно добавить свой проект PR'ом. - [Kotlin SDK guide: WebSockets](https://kotlin-sdk.jellyfin.org/guide/websockets.html) — см. раздел 5. - [Dokka (API reference)](https://kotlin-sdk.jellyfin.org/dokka/) — полный справочник по модулям. - [Releases](https://github.com/jellyfin/jellyfin-sdk-kotlin/releases) и [v1.9.0](https://github.com/jellyfin/jellyfin-sdk-kotlin/releases/tag/v1.9.0): 1.9.0 (08.09.2026) — под Jellyfin 12.0, **minSdk 21, compileSdk 37**, Kotlin 2.4.20, OkHttp 5.5.0, «no longer compatible with Jellyfin 10.11 and earlier»; 1.9.1 — фикс отмены WebSocket-подписок; 1.9.2 (06.10.2026) — OpenAPI 12.2.0. 1.8.12 (26.07.2026) — бэкпорт для 10.11.x. - Maven Central: группа `org.jellyfin.sdk`, артефакты (проверено по [repo1.maven.org](https://repo1.maven.org/maven2/org/jellyfin/sdk/)): `jellyfin-core` (+ `-android`, `-jvm`), `jellyfin-api` (+ `-jvm`), `jellyfin-api-okhttp`, `jellyfin-model` (+ `-jvm`), `jellyfin-platform-android`, и устаревший `jellyfin-api-ktor` (последняя 1.7.1). `maven-metadata.xml` для `jellyfin-core`: latest/release = **1.9.2**. - **Сетевой стек — НЕ Ktor.** [Migrate to v1.7](https://kotlin-sdk.jellyfin.org/migration/v1.7.html): OkHttp 4 стал дефолтом, SDK перестал управлять потоками (вызовы нельзя делать на main thread), `User-Agent` формируется из `ClientInfo`; [Migrate to v1.8](https://kotlin-sdk.jellyfin.org/migration/v1.8.html): пакет `jellyfin-api-ktor` удалён. В 1.9 — OkHttp 5.5. Это удобно: тот же OkHttp можно переиспользовать для картинок (Coil) и при желании настраивать TLS. - Версии зависимостей (из [gradle/libs.versions.toml](https://github.com/jellyfin/jellyfin-sdk-kotlin/blob/master/gradle/libs.versions.toml) на master): kotlin 2.4.20, kotlinx-coroutines 1.11.0, kotlinx-serialization 1.11.0, okhttp 5.5.0, koin 4.2.2, kotlin-logging 8.0.4, slf4j 2.0.19. - [HttpClientOptions.kt](https://github.com/jellyfin/jellyfin-sdk-kotlin/blob/master/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/client/HttpClientOptions.kt): `followRedirects=true`, `requestTimeout=30s`, `connectTimeout=6s`, `socketTimeout=30s`, `socketReconnectPolicy=ExponentialDelayReconnect()`. Явной настройки доверенных сертификатов в опциях нет (не проверено глубже) — для self-signed придётся либо Android Network Security Config, либо смотреть, как подменить OkHttp-клиент. - [Jellyfin.kt](https://github.com/jellyfin/jellyfin-sdk-kotlin/blob/master/jellyfin-core/src/commonMain/kotlin/org/jellyfin/sdk/Jellyfin.kt): `Jellyfin.minimumVersion = ServerVersion(12,0,0,0)`, `Jellyfin.apiVersion` = версия OpenAPI, по которой сгенерирован SDK. - Пакет расширений [org.jellyfin.sdk.api.client.extensions](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.client.extensions/index.html): свойства `api.libraryApi`, `api.mediaInfoApi`, `api.sessionApi`, `api.userDataApi`, `api.showApi`, `api.userViewApi`, `api.videoApi`, `api.audioApi`, `api.imageApi`, `api.subtitleApi`, `api.trickPlayApi`, `api.mediaSegmentApi`, `api.lyricApi`, `api.liveTvApi`, `api.syncPlayApi`, `api.quickConnectApi`, `api.searchApi`, `api.systemApi`, `api.userApi` и т. д.; функции `authenticateUserByName`, `authenticateWithQuickConnect`, generic `api.get/post/delete(pathTemplate, …)` для эндпоинтов, которых нет в генерации (например, HLS). - Список всех классов операций 1.9: [org.jellyfin.sdk.api.operations](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/index.html) (46 классов: `LibraryApi`, `MediaInfoApi`, `SessionApi`, `ShowApi`, `UserViewApi`, `VideoApi`, `AudioApi`, `ImageApi`, `SubtitleApi`, `TrickPlayApi`, `MediaSegmentApi`, `LyricApi`, `LiveTvApi`, `SyncPlayApi`, `QuickConnectApi`, `AuthenticationApi`, `UserApi`, `UserDataApi`, `SearchApi`, `SystemApi`, `PlaylistApi`, `CollectionApi`, `PersonApi`, `GenreApi`, `StudioApi`, …). - Полезные Dokka-страницы (проверены): [MediaInfoApi.getPostedPlaybackInfo](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-media-info-api/get-posted-playback-info.html), [SessionApi.reportPlaybackProgress](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-session-api/report-playback-progress.html), [VideoApi.getVideoStreamUrl](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-video-api/get-video-stream-url.html), [AudioApi.getUniversalAudioStreamUrl](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-audio-api/get-universal-audio-stream-url.html), [ImageApi.getItemImageUrl](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-image-api/get-item-image-url.html), [SubtitleApi.getSubtitleUrl](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-subtitle-api/get-subtitle-url.html), [TrickPlayApi.getTrickplayTileImageUrl](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-trick-play-api/get-trickplay-tile-image-url.html), [MediaSegmentApi.getItemSegments](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-media-segment-api/get-item-segments.html), [LibraryApi.getItems](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-library-api/get-items.html), [LibraryApi.getResumeItems](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-library-api/get-resume-items.html), [LibraryApi.getLatestMedia](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-library-api/get-latest-media.html), [ShowApi.getNextUp](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-show-api/get-next-up.html), [UserViewApi.getUserViews](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-user-view-api/get-user-views.html), [SearchApi.getSearchHints](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-search-api/get-search-hints.html), [SessionApi.postFullCapabilities](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-session-api/post-full-capabilities.html), [SyncPlayApi.syncPlayJoinGroup](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-sync-play-api/sync-play-join-group.html), [LyricApi.getLyrics](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-lyric-api/get-lyrics.html), [SystemApi.getPublicSystemInfo](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-api/org.jellyfin.sdk.api.operations/-system-api/get-public-system-info.html). - DSL для DeviceProfile: [org.jellyfin.sdk.model.deviceprofile](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-model/org.jellyfin.sdk.model.deviceprofile/index.html) — `buildDeviceProfile { directPlayProfile {…}; transcodingProfile {…}; codecProfile {…}; subtitleProfile(...) }`, `buildUpon`. - Тики: [org.jellyfin.sdk.model.extensions](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-model/org.jellyfin.sdk.model.extensions/index.html) — `Long.ticks: Duration`, `Duration.inWholeTicks`. - Discovery (jellyfin-core): [org.jellyfin.sdk.discovery](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-core/org.jellyfin.sdk.discovery/index.html), [DiscoveryService](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-core/org.jellyfin.sdk.discovery/-discovery-service/index.html) — `discoverLocalServers(timeout, maxServers): Flow`, `getAddressCandidates(input)`, `getRecommendedServers(input|servers, minimumScore)`. Детали в разделе 6. Отдельной страницы guide по discovery на сайте нет (404); пример есть в Getting started (`jellyfin.discovery.getRecommendedServers(candidates, RecommendedServerInfoScore.GOOD)`). - Пример проекта: [samples/kotlin-cli](https://github.com/jellyfin/jellyfin-sdk-kotlin/tree/master/samples/kotlin-cli) — CLI с командами discovery, observe (websocket) и т. п. - Эталонные потребители SDK: [jellyfin-androidtv](https://github.com/jellyfin/jellyfin-androidtv) (официальный TV-клиент на SDK), [jellyfin-android](https://github.com/jellyfin/jellyfin-android), [Findroid](https://github.com/jarnedemeulemeester/findroid) (сторонний нативный Kotlin-клиент на SDK, GPL). - Для сравнения — [jellyfin-sdk-typescript](https://github.com/jellyfin/jellyfin-sdk-typescript) / [typescript-sdk.jellyfin.org](https://typescript-sdk.jellyfin.org/): таблица совместимости SDK↔сервер (1.0.0 → 12.x, 0.12/0.13 → 10.11.x, 0.11 → 10.10.x, 0.9/0.10 → 10.9.x), тот же паттерн ClientInfo/DeviceInfo/discovery/auth. --- ## 3. Воспроизведение (PlaybackInfo, DeviceProfile, стримы, прогресс, субтитры, trickplay, UserData) Единицы времени: **1 tick = 100 нс; 1 с = 10 000 000 ticks**. Все `*Ticks` в API (`RunTimeTicks`, `PositionTicks`, `StartTimeTicks`, `StartPositionTicks`, `PlaybackPositionTicks`, `StartTicks/EndTicks`) — в этих единицах. libVLC оперирует миллисекундами → `ms * 10_000`. ### PlaybackInfo и DeviceProfile - `POST /Items/{itemId}/PlaybackInfo` (query: `userId`, `maxStreamingBitrate`, `startTimeTicks`, `audioStreamIndex`, `subtitleStreamIndex`, `maxAudioChannels`, `mediaSourceId`, `liveStreamId`, `autoOpenLiveStream`, `enableDirectPlay`, `enableDirectStream`, `enableTranscoding`, `allowVideoStreamCopy`, `allowAudioStreamCopy`; тело `PlaybackInfoDto` с теми же полями + `DeviceProfile`, `AlwaysBurnInSubtitleWhenTranscoding`). Ответ `PlaybackInfoResponse { MediaSources: [MediaSourceInfo], PlaySessionId, ErrorCode }`. Есть и `GET`-вариант без профиля (бесполезен для решения о транскодировании). - `MediaSourceInfo` (проверено по спеке 12.2): `Id`, `Container`, `Protocol`, `Path`, `Size`, `Bitrate`, `RunTimeTicks`, `SupportsDirectPlay`, `SupportsDirectStream`, `SupportsTranscoding`, `TranscodingUrl`, `TranscodingSubProtocol` (`http`|`hls`), `TranscodingContainer`, `MediaStreams`, `MediaAttachments`, `DefaultAudioStreamIndex`, `DefaultSubtitleStreamIndex`, `RequiredHttpHeaders`, `IsInfiniteStream`, `LiveStreamId`, `RequiresOpening/OpenToken/RequiresClosing` (Live TV), `HasSegments` (есть media segments), `ETag`. - Логика выбора: сервер сравнивает `DeviceProfile` с ffprobe-данными файла: контейнер+кодеки проходят `DirectPlayProfiles` и `CodecProfiles` → **DirectPlay** (`/Videos/{id}/stream?static=true`); видео проходит, но контейнер/аудио/субтитры нет → **DirectStream** (ремукс, `TranscodingUrl` c `allowVideoStreamCopy`); иначе **Transcode** (`TranscodingUrl`, обычно HLS). Enum `PlayMethod`: `DirectPlay | DirectStream | Transcode` — его нужно сообщать в прогрессе. - `DeviceProfile` поля: `Name`, `Id`, `MaxStreamingBitrate`, `MaxStaticBitrate`, `MusicStreamingTranscodingBitrate`, `MaxStaticMusicBitrate`, `DirectPlayProfiles[] {Container, AudioCodec, VideoCodec, Type(Audio|Video|Photo|Subtitle|Lyric)}`, `TranscodingProfiles[] {Container, Type, VideoCodec, AudioCodec, Protocol(http|hls), Context(Streaming|Static), MaxAudioChannels, MinSegments, SegmentLength, BreakOnNonKeyFrames, CopyTimestamps, EnableSubtitlesInManifest, Conditions, EnableAudioVbrEncoding, …}`, `ContainerProfiles`, `CodecProfiles[] {Type(Video|VideoAudio|Audio), Codec, Container, SubContainer, Conditions, ApplyConditions}`, `SubtitleProfiles[] {Format, Method, Language, Container, DidlMode}`. Условия (`ProfileConditionValue`): `Width, Height, VideoBitDepth, VideoBitrate, VideoFramerate, VideoLevel, VideoProfile, AudioChannels, AudioBitrate, AudioSampleRate, AudioBitDepth, RefFrames, IsAnamorphic, IsInterlaced, IsSecondaryAudio, VideoCodecTag, IsAvc, VideoRangeType, NumAudioStreams, NumVideoStreams, NumStreams (10.11+), VideoRotation (12.x)`. Схема DeviceProfile одинакова в 10.10.7 / 10.11.11 / 12.2.0 — можно писать один профиль. - Для libVLC: он умеет почти всё (MKV/MP4/TS/AVI, H.264/HEVC/VP9/AV1 через MediaCodec или софт, AC3/EAC3/DTS/TrueHD/AAC/FLAC/Opus, PGS/ASS/SRT/VTT), поэтому агрессивный DirectPlay-профиль + HLS/TS fallback (как у официальных клиентов) — разумная стартовая точка. Опирайтесь на реальные профили: - [jellyfin-androidtv: util/profile/deviceProfile.kt](https://github.com/jellyfin/jellyfin-androidtv/blob/master/app/src/main/java/org/jellyfin/androidtv/util/profile/deviceProfile.kt) — `buildDeviceProfile { … }` с несколькими `transcodingProfile` (TS+HLS, MP4+HLS), `directPlayProfile`, множеством `codecProfile` (HDR-range, AV1/HEVC условия), `subtitleProfile(VTT/WEBVTT, embedded=true, hls=true, external=true)`; рядом `deviceProfileReport.kt` и `codec/` с проверкой MediaCodec-возможностей. - [jellyfin-android: DeviceProfileBuilder.kt](https://github.com/jellyfin/jellyfin-android/blob/master/app/src/main/java/org/jellyfin/mobile/player/deviceprofile/DeviceProfileBuilder.kt) — профиль для ExoPlayer + отдельный `getExternalPlayerProfile()` (для внешних плееров, по сути «играй всё, maxStreamingBitrate = Int.MAX_VALUE», субтитры EMBED + EXTERNAL) — ближайший аналог того, что нужно для libVLC. - [jellyfin-android: MediaSourceResolver.kt](https://github.com/jellyfin/jellyfin-android/blob/master/app/src/main/java/org/jellyfin/mobile/player/source/MediaSourceResolver.kt) — как официальный клиент выбирает MediaSource/PlayMethod из ответа PlaybackInfo. - [jellyfin-web: browserDeviceProfile.js](https://github.com/jellyfin/jellyfin-web/blob/master/src/scripts/browserDeviceProfile.js) и [playbackmanager.js](https://github.com/jellyfin/jellyfin-web/blob/master/src/components/playback/playbackmanager.js) — эталон полного playback-цикла (PlaybackInfo → выбор → отчёты → стоп), включая периодичность прогресса и обработку `TranscodingUrl`. - [Docs: Codec Support](https://jellyfin.org/docs/general/clients/codec-support/) — таблицы кодеков/контейнеров по клиентам (Android / Android TV) и принцип «Direct Play всё, DirectStream при неподдерживаемом контейнере/аудио/субтитрах, Transcode при неподдерживаемом видео». - [Docs: Transcoding](https://jellyfin.org/docs/general/post-install/transcoding/) — серверная сторона (HW accel), полезно для понимания причин транскодирования. ### URL потоков - Direct play: `GET /Videos/{itemId}/stream` или `/Videos/{itemId}/stream.{container}` с `static=true&mediaSourceId=...&deviceId=...` (+ `tag`/ETag). Полный список query-параметров (проверено по спеке): `container, static, params, tag, deviceProfileId, playSessionId, segmentContainer, segmentLength, minSegments, mediaSourceId, deviceId, audioCodec, videoCodec, enableAutoStreamCopy, allowVideoStreamCopy, allowAudioStreamCopy, audioSampleRate, maxAudioBitDepth, audioBitRate, audioChannels, maxAudioChannels, profile, level, framerate, maxFramerate, copyTimestamps, startTimeTicks, width, height, maxWidth, maxHeight, videoBitRate, subtitleStreamIndex, subtitleMethod, maxRefFrames, maxVideoBitDepth, requireAvc, deInterlace, requireNonAnamorphic, transcodingMaxAudioChannels, cpuCoreLimit, liveStreamId, enableMpegtsM2TsMode, subtitleCodec, transcodeReasons, audioStreamIndex, videoStreamIndex, context, streamOptions, enableAudioVbrEncoding`. - Transcode/DirectStream: берите готовый `MediaSourceInfo.TranscodingUrl` (относительный путь `/videos/{id}/master.m3u8?...` или `/Videos/{id}/stream?...`, уже содержит `PlaySessionId`, `DeviceId`, `MediaSourceId`, индексы потоков и ключ `ApiKey`; с 10.11 генерируемые URL используют `ApiKey` вместо `api_key`). Склеивайте с `baseUrl`. - HLS-маршруты существуют, но с 12.0 **скрыты из OpenAPI** (`[ApiExplorerSettings(IgnoreApi = true)]` в [DynamicHlsController.cs](https://github.com/jellyfin/jellyfin/blob/master/Jellyfin.Api/Controllers/DynamicHlsController.cs)): `GET /Videos/{itemId}/master.m3u8`, `/Videos/{itemId}/main.m3u8`, `/Videos/{itemId}/live.m3u8`, `/Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}`, аналогично `/Audio/{itemId}/master.m3u8`. Релиз-пост 12.0 прямо говорит: «endpoints not in the OpenAPI specification … should not be used» — т. е. формировать их вручную не рекомендуется, используйте `TranscodingUrl`. - **Gotcha для libVLC:** плееру нужен URL без возможности выставить заголовок `Authorization` → используйте query `ApiKey=` (не legacy `api_key`!). libVLC 3 позволяет задать только User-Agent/referrer-подобные опции, не произвольные заголовки (общее знание, не проверялось в этом исследовании). - Музыка: `GET /Audio/{itemId}/universal` (`container[]`, `audioCodec`, `maxStreamingBitrate`, `transcodingContainer`, `transcodingProtocol`, `maxAudioSampleRate`, `enableRedirection`, …) — сервер сам решает direct/transcode без PlaybackInfo. Скачивание: `GET /Items/{itemId}/Download`. - Trailers/Intros: `GET /Items/{itemId}/Intros` (pre-roll), `RemoteTrailers` в `BaseItemDto`. ### Отчёты о воспроизведении (сессии) - `POST /Sessions/Playing` — тело `PlaybackStartInfo`; `POST /Sessions/Playing/Progress` — `PlaybackProgressInfo`; `POST /Sessions/Playing/Stopped` — `PlaybackStopInfo`; `POST /Sessions/Playing/Ping?playSessionId=` — keep-alive транскодера. Поля Start/Progress: `ItemId`, `MediaSourceId`, `PlaySessionId`, `PositionTicks`, `IsPaused`, `IsMuted`, `VolumeLevel`, `CanSeek`, `PlayMethod`, `AudioStreamIndex`, `SubtitleStreamIndex`, `RepeatMode`, `PlaybackOrder`, `NowPlayingQueue`, `PlaylistItemId`, `LiveStreamId`, `SessionId`, `PlaybackStartTimeTicks`, `AspectRatio`, `Brightness`. Stop: `ItemId`, `MediaSourceId`, `PlaySessionId`, `PositionTicks`, `Failed`, `NextMediaType`, `LiveStreamId`. Исходник: [PlaystateController.cs](https://github.com/jellyfin/jellyfin/blob/master/Jellyfin.Api/Controllers/PlaystateController.cs). - **Старые `POST/DELETE /PlayingItems/{itemId}[/Progress]` (OnPlayback*) депрекированы в 10.11 и удалены из спеки в 12.0** — не используйте. Также `POST/DELETE /Users/{userId}/PlayedItems/{id}` помечены `Obsolete` в коде; актуальные: `POST/DELETE /UserPlayedItems/{itemId}` (`?userId&datePlayed`), `POST/DELETE /UserFavoriteItems/{itemId}`, `GET/POST /UserItems/{itemId}/UserData`, `POST/DELETE /UserItems/{itemId}/Rating`. - Периодичность прогресса: веб-клиент шлёт прогресс примерно раз в 10 с и при событиях pause/seek/stream switch (значение 10 с — по памяти, не проверено в этом исследовании; смотрите `playbackmanager.js`). Без прогресса сервер через таймаут считает сессию мёртвой и убивает транскодер — отсюда `Ping`. - **Resume / UserData:** `BaseItemDto.UserData: UserItemDataDto {PlaybackPositionTicks, PlayedPercentage, Played, PlayCount, IsFavorite, Likes, LastPlayedDate, UnplayedItemCount, Rating, Key, ItemId}`. Список «Продолжить просмотр»: `GET /UserItems/Resume?userId=&limit=&mediaTypes=Video&fields=…&excludeActiveSessions=` (пути `/Users/{userId}/Items/Resume`, `/Users/{userId}/Items/Latest` в спеках 10.10.7+ уже отсутствуют — используйте `userId` как query). - Для «кнопки Resume» нужны `fields=MediaSources,Chapters,Trickplay,MediaStreams` и `enableUserData=true` в запросах. ### Субтитры - `SubtitleDeliveryMethod` enum: `Encode` (вжечь), `Embed` (в контейнере, плеер читает сам), `External` (отдельный URL), `Hls` (в манифесте), `Drop`. В `DeviceProfile.SubtitleProfiles` объявляете, какие форматы и каким методом умеете (например, для libVLC: `srt/subrip, ass, ssa, vtt, pgssub, dvdsub, dvbsub` как `Embed`; `srt, ass, ssa, vtt` как `External`). - Внешний поток: `GET /Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/Stream.{format}` и `GET /Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/{startPositionTicks}/Stream.{format}` (query: `copyTimestamps`, `addVttTimeMap`, `endPositionTicks`). HLS-плейлист субтитров: `GET /Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/subtitles.m3u8`. Загрузка/удаление: `POST /Videos/{itemId}/Subtitles`, `DELETE /Videos/{itemId}/Subtitles/{index}`; поиск онлайн: `GET /Items/{itemId}/RemoteSearch/Subtitles/{language}`, `POST .../Subtitles/{subtitleId}`. Исходник: [SubtitleController.cs](https://github.com/jellyfin/jellyfin/blob/master/Jellyfin.Api/Controllers/SubtitleController.cs). В ответе PlaybackInfo каждый `MediaStream` типа Subtitle несёт `DeliveryMethod`/`DeliveryUrl`/`IsExternal` (имена этих полей MediaStream не перепроверял по спеке — «не проверено»), что позволяет не собирать URL вручную. В 12.2 графические субтитры отдаются как raw-файлы с Range-поддержкой. - Для libVLC внешние субтитры удобно подключать через `Media.addSlave(Subtitle, url)`. ### Trickplay (с 10.9) и главы - Данные: `BaseItemDto.Trickplay` (нужен `fields=Trickplay`) — словарь `mediaSourceId → width → TrickplayInfoDto {Width, Height, TileWidth, TileHeight, ThumbnailCount, Interval (мс), Bandwidth}` ([Dokka TrickplayInfoDto](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-model/org.jellyfin.sdk.model.api/-trickplay-info-dto/index.html)). - Картинки-тайлы: `GET /Videos/{itemId}/Trickplay/{width}/{index}.jpg?mediaSourceId=`; HLS image playlist: `GET /Videos/{itemId}/Trickplay/{width}/tiles.m3u8`. Исходник: [TrickplayController.cs](https://github.com/jellyfin/jellyfin/blob/master/Jellyfin.Api/Controllers/TrickplayController.cs). Индекс тайла = `floor(positionMs / Interval) / (TileWidth*TileHeight)`, позиция внутри — остаток. - Главы: `BaseItemDto.Chapters[] ChapterInfo {StartPositionTicks, Name, ImageTag, ImagePath, ImageDateModified}` (`fields=Chapters`); картинка главы — `GET /Items/{itemId}/Images/Chapter/{chapterIndex}?tag=`. ### Media Segments (intro/outro skip, с 10.10) - `GET /MediaSegments/{itemId}?includeSegmentTypes=Intro&includeSegmentTypes=Outro` → `QueryResult`; `MediaSegmentType`: `Unknown, Commercial, Preview, Recap, Outro, Intro`. `MediaSourceInfo.HasSegments` подсказывает, стоит ли вообще запрашивать. Исходник: [MediaSegmentsController.cs](https://github.com/jellyfin/jellyfin/blob/master/Jellyfin.Api/Controllers/MediaSegmentsController.cs). Сегменты создают плагины (Chapter Segments Provider, Intro Skipper); сервер лишь хранит. - [Docs: Media Segments](https://jellyfin.org/docs/general/server/metadata/media-segments) — описание фичи для пользователей/админов, типы сегментов. - Эталон поведения в клиенте: [jellyfin-androidtv ui/playback/segment](https://github.com/jellyfin/jellyfin-androidtv/tree/master/app/src/main/java/org/jellyfin/androidtv/ui/playback/segment) ([MediaSegmentRepository.kt](https://github.com/jellyfin/jellyfin-androidtv/blob/master/app/src/main/java/org/jellyfin/androidtv/ui/playback/segment/MediaSegmentRepository.kt)): действия per type (Skip / Ask to skip / Nothing), минимум 1 с для автоскипа, 3 с для кнопки, автоскрытие кнопки через 8 с. --- ## 4. API библиотеки (browsing) Всё проверено по OpenAPI 12.2.0: - `GET /UserViews?userId=&includeExternalContent=&presetViews=&includeHidden=` — корневые «библиотеки» пользователя (`CollectionType`: movies, tvshows, music, books, …). `GET /UserViews/GroupingOptions`. - `GET /Items` — универсальный запрос. Ключевые параметры: `userId, parentId, recursive, includeItemTypes[] (BaseItemKind: Movie, Series, Season, Episode, MusicAlbum, Audio, BoxSet, Playlist, …), excludeItemTypes, searchTerm, sortBy[] (ItemSortBy: SortName, DateCreated, PremiereDate, DatePlayed, CommunityRating, Random, Runtime, ProductionYear, SeriesSortName, …), sortOrder[] (Ascending|Descending), filters[] (ItemFilter: IsUnplayed, IsPlayed, IsFavorite, IsResumable, IsFolder, …), fields[] (ItemFields: Overview, Genres, People, MediaSources, MediaStreams, Chapters, Trickplay, ChildCount, DateCreated, ProviderIds, PrimaryImageAspectRatio, Path, …), startIndex, limit, enableTotalRecordCount, enableImages, imageTypeLimit, enableImageTypes[], enableUserData, genres/genreIds, studios/studioIds, years, tags, officialRatings, person/personIds/personTypes, artistIds/albumArtistIds, ids, isPlayed, isFavorite, minCommunityRating, hasSubtitles, isHd, is4K, nameStartsWith/nameStartsWithOrGreater/nameLessThan (алфавитная навигация), collapseBoxSetItems, audioLanguages, subtitleLanguages, mediaTypes`. **Gotcha 12.0:** `recursive` теперь применяется автоматически при фильтрах, но только если задан `includeItemTypes` — результаты могут отличаться от 10.11. - `GET /Items/{itemId}?userId=` — карточка; `POST /Items/{itemId}` — обновление метаданных; `DELETE`. - Сериалы: `GET /Shows/NextUp?userId=&seriesId=&parentId=&limit=&enableResumable=&enableRewatching=&nextUpDateCutoff=&fields=`; `GET /Shows/{seriesId}/Seasons`, `GET /Shows/{seriesId}/Episodes?seasonId=&userId=&fields=`; похожие: `GET /Items/{itemId}/Similar` (также `/Movies/{id}/Similar`, `/Shows/{id}/Similar`; в 10.11 добавлены фильтр по тегам и случайная сортировка). - Новинки: `GET /Items/Latest?userId=&parentId=&includeItemTypes=&limit=&groupItems=&fields=`. Продолжить: `GET /UserItems/Resume` (см. выше). Рекомендации: `GET /Items/Suggestions`. Счётчики: `GET /Items/Counts`. Фильтры для UI: `GET /Items/Filters`, `GET /Items/Filters2` (жанры/теги/годы/рейтинги для выбранной библиотеки). - Поиск: `GET /Search/Hints?searchTerm=&userId=&includeItemTypes=&limit=&includePeople/Media/Genres/Studios/Artists` (быстрые подсказки, `SearchHint`) **или** `GET /Items?searchTerm=&recursive=true&includeItemTypes=Movie,Series,Episode&fields=…` (полные `BaseItemDto`, то, что делает веб-клиент). - Коллекции (BoxSet): `POST /Collections?name=&ids=&parentId=&isLocked=`, `POST/DELETE /Collections/{collectionId}/Items?ids=`, содержимое — `GET /Items?parentId={boxSetId}`; `GET /Items/{itemId}/Collections` (новое в 12.x: в каких коллекциях состоит элемент). - Плейлисты: `POST /Playlists` (тело `CreatePlaylistDto`), `GET/POST /Playlists/{playlistId}`, `GET/POST/DELETE /Playlists/{playlistId}/Items`, `POST .../Items/{itemId}/Move/{newIndex}`, доступы `GET/POST/DELETE /Playlists/{playlistId}/Users[/{userId}]`. В 12.0 плейлисты/коллекции вынесены из родительского элемента в отдельное хранилище (быстрее для больших списков). - Люди/жанры/студии/годы: `GET /Persons?searchTerm=&personTypes=Actor&appearsInItemId=`, `GET /Persons/{name}`; `GET /Genres`, `GET /Genres/{genreName}`; `GET /Studios`, `GET /Studios/{name}`; `GET /Years`, `GET /Years/{year}`; музыка: `GET /Artists`, `/Artists/AlbumArtists`, `/MusicGenres/{genreName}` — **помечены deprecated в 12.0** (рекомендация релиза: `GetPersons`/`GetGenre`/`GetItems` с типами). Фильтрация по ним — через `/Items?genreIds=…`, `personIds=…`, `studioIds=…`. - Картинки: `GET /Items/{itemId}/Images/{imageType}[/{imageIndex}]` с query `tag, maxWidth, maxHeight, width, height, fillWidth, fillHeight, quality, format (jpg|png|webp|…), blur, backgroundColor, foregroundLayer, percentPlayed, unplayedCount` (+ `HEAD`). Также «длинный» путь `/Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}`. `ImageType`: `Primary, Art, Backdrop, Banner, Logo, Thumb, Disc, Box, Screenshot, Menu, Chapter, BoxRear, Profile`. Теги берите из `BaseItemDto.ImageTags{type→tag}`, `BackdropImageTags[]`, `ParentBackdropItemId/ParentBackdropImageTags`, `SeriesPrimaryImageTag`, `AlbumPrimaryImageTag`, `ParentLogoItemId/ParentLogoImageTag`, `ParentThumbItemId/ParentThumbImageTag` — `tag` даёт корректное кеширование/инвалидацию. **BlurHash:** `BaseItemDto.ImageBlurHashes {ImageType → {tag → blurhash}}` ([Dokka](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-model/org.jellyfin.sdk.model.api/-base-item-dto/image-blur-hashes.html)). Картинки людей/жанров/студий: `/Persons/{name}/Images/{imageType}`, `/Genres/{name}/Images/{imageType}`, `/Studios/{name}/Images/{imageType}`, `/Artists/{name}/Images/{imageType}/{imageIndex}`. **Gotcha 12.0:** сервер больше не апскейлит картинку выше исходного разрешения. - Брендинг сервера для экрана логина: `GET /Branding/Configuration` (LoginDisclaimer, SplashscreenEnabled), `GET /Branding/Splashscreen`, `GET /Branding/Css`. - Live TV (для полноты): `GET /LiveTv/Channels`, `/LiveTv/Channels/{channelId}`, `GET/POST /LiveTv/Programs`, `/LiveTv/Programs/Recommended`, запись/стрим: `/LiveTv/LiveRecordings/{recordingId}/stream`, `/LiveTv/LiveStreamFiles/{streamId}/stream.{container}`; для живого эфира PlaybackInfo с `autoOpenLiveStream=true` возвращает `LiveStreamId`, который надо закрывать (`POST /LiveStreams/Close`, не перепроверял путь). 12.0: Live TV больше не возвращает недостижимые server-local URL. - Lyrics (10.9+): `GET /Audio/{itemId}/Lyrics` (`LyricDto`, с 10.11 — ELRC-cues по словам), `GET /Audio/{itemId}/RemoteSearch/Lyrics`, `GET /Providers/Lyrics/{lyricId}`; флаг `BaseItemDto.HasLyrics`. --- ## 5. Realtime: WebSocket и удалённое управление - Эндпоинт: `ws(s):///socket` (константа `SOCKET_URL = "/socket"` в [DefaultSocketApi.kt](https://github.com/jellyfin/jellyfin-sdk-kotlin/blob/master/jellyfin-api/src/commonMain/kotlin/org/jellyfin/sdk/api/sockets/DefaultSocketApi.kt)). Авторизация — query-параметры (`ApiKey` + id устройства; при отключённом legacy сервер отвечает «Token is required», если прислать `api_key` — см. форум-тред выше). В OpenAPI самого `/socket` нет, но все схемы сообщений описаны (`WebSocketMessage`, `InboundWebSocketMessage`, `OutboundWebSocketMessage` и ~40 конкретных типов). - Формат сообщения: JSON `{ "MessageType": "", "MessageId": "", "Data": … }`. Enum `SessionMessageType` (12.2): `ForceKeepAlive, GeneralCommand, UserDataChanged, Sessions, Play, SyncPlayCommand, SyncPlayGroupUpdate, Playstate, RestartRequired, ServerShuttingDown, ServerRestarting, LibraryChanged, UserDeleted, UserUpdated, SeriesTimerCreated, TimerCreated, SeriesTimerCancelled, TimerCancelled, RefreshProgress, ScheduledTaskEnded, PackageInstallationCancelled/Failed/Completed, PackageInstalling, PackageUninstalled, ActivityLogEntry, ScheduledTasksInfo, ActivityLogEntryStart/Stop, SessionsStart/Stop, ScheduledTasksInfoStart/Stop, KeepAlive` ([Dokka SessionMessageType](https://kotlin-sdk.jellyfin.org/dokka/jellyfin-model/org.jellyfin.sdk.model.api/-session-message-type/index.html)). - **KeepAlive:** сервер после подключения присылает `ForceKeepAlive` с `Data` = таймаут в секундах; клиент обязан периодически слать `{"MessageType":"KeepAlive"}` (`InboundKeepAliveMessage`), иначе соединение закроется. В Kotlin SDK это делает `DefaultSocketApi` автоматически, плюс реконнект по `SocketReconnectPolicy.ExponentialDelayReconnect`. - **Подписки:** периодические данные надо явно запросить: `SessionsStart` с `Data: "initialDelayMs,intervalMs"` (например `"0,1500"`), `SessionsStop`; аналогично `ScheduledTasksInfoStart/Stop`, `ActivityLogEntryStart/Stop`. SDK шлёт start/stop сам при появлении/исчезновении подписчиков (интервал 1 с). - События, важные для клиента: `UserDataChanged` (`UserDataChangeInfo {UserId, UserDataList[]}` — обновлять прогресс/избранное в UI), `LibraryChanged` (`LibraryUpdateInfo {ItemsAdded, ItemsUpdated, ItemsRemoved, FoldersAddedTo, FoldersRemovedFrom, CollectionFolders, IsEmpty}` — инвалидировать кэш), `ServerRestarting` / `ServerShuttingDown` / `RestartRequired` (показать баннер, переподключаться; HTTP во время рестарта отвечает **503 с `Retry-After`** — введено в 10.11), `RefreshProgress`, `UserUpdated/UserDeleted`. - **Удалённое управление («Play to» / Cast на ваш клиент):** чтобы ваш клиент появился в списке целей у других клиентов, после логина отправьте возможности: `POST /Sessions/Capabilities?playableMediaTypes=Video,Audio&supportedCommands=…&supportsMediaControl=true&supportsPersistentIdentifier=true` или `POST /Sessions/Capabilities/Full` с телом `ClientCapabilitiesDto {PlayableMediaTypes, SupportedCommands[GeneralCommandType], SupportsMediaControl, SupportsPersistentIdentifier, DeviceProfile, AppStoreUrl, IconUrl}`. Далее по WebSocket приходят: `Play` (`PlayRequest {ItemIds, StartPositionTicks, PlayCommand: PlayNow|PlayNext|PlayLast|PlayInstantMix|PlayShuffle, MediaSourceId, AudioStreamIndex, SubtitleStreamIndex, StartIndex, ControllingUserId}`), `Playstate` (`PlaystateRequest {Command: Stop|Pause|Unpause|NextTrack|PreviousTrack|Seek|Rewind|FastForward|PlayPause, SeekPositionTicks}`), `GeneralCommand` (`GeneralCommandType`: `SetVolume, Mute/Unmute/ToggleMute, SetAudioStreamIndex, SetSubtitleStreamIndex, DisplayMessage, DisplayContent, GoHome, GoToSearch, Back, Select, MoveUp/Down/Left/Right, SetRepeatMode, SetShuffleQueue, SetPlaybackOrder, SetMaxStreamingBitrate, PlayTrailers, ToggleFullscreen, ToggleOsd, SendString, SendKey, …` с `Arguments` map). SDK даёт `api.webSocket.subscribeGeneralCommand(...)`, `subscribePlayStateCommands(...)`, `subscribeSyncPlayCommands(...)`, `subscribe()`. - **Управление другими сессиями (ваш клиент как пульт):** `GET /Sessions?controllableByUserId=&activeWithinSeconds=`; `POST /Sessions/{sessionId}/Playing?playCommand=PlayNow&itemIds=…&startPositionTicks=…&mediaSourceId=&audioStreamIndex=&subtitleStreamIndex=&startIndex=`; `POST /Sessions/{sessionId}/Playing/{command}` (`PlaystateCommand`, `?seekPositionTicks=`); `POST /Sessions/{sessionId}/Command/{command}` или `POST /Sessions/{sessionId}/Command` (тело `GeneralCommand`); `POST /Sessions/{sessionId}/Message` (`MessageCommand {Header, Text, TimeoutMs}`); `POST /Sessions/{sessionId}/Viewing` («сейчас просматривает»); `POST/DELETE /Sessions/{sessionId}/User/{userId}`. Исходник: [SessionController.cs](https://github.com/jellyfin/jellyfin/blob/master/Jellyfin.Api/Controllers/SessionController.cs). С 10.11 не-админ может подписываться на свои сессии. - Заметки релизов: 12.0 — «WebSockets have moved to SDK subscriptions, duplicate `/socket` connections fixed»; 12.1 — закрытие сокета больше не роняет request handler. --- ## 6. Обнаружение серверов в LAN, публичная информация, мульти-сервер, reverse proxy / TLS - **UDP discovery:** broadcast на порт **7359/UDP** строки `who is JellyfinServer?`; сервер отвечает JSON `ServerDiscoveryInfo { Address, Id, Name, EndpointAddress }` (модель: [ServerDiscoveryInfo.cs](https://github.com/jellyfin/jellyfin/blob/master/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs); схема есть и в OpenAPI). Эталон клиента — [LocalServerDiscovery.kt](https://github.com/jellyfin/jellyfin-sdk-kotlin/blob/master/jellyfin-core/src/jvmCommonMain/kotlin/org/jellyfin/sdk/discovery/LocalServerDiscovery.kt): `DISCOVERY_MESSAGE = "who is JellyfinServer?"`, `DISCOVERY_PORT = 7359`, буфер 1024 байта, таймаут 500 мс, максимум 15 серверов, `DatagramSocket` с broadcast. Работает только внутри подсети; на Android нужен `WifiManager.MulticastLock` для надёжного приёма (общая практика, не проверено в SDK). - [Docs: Networking](https://jellyfin.org/docs/general/post-install/networking/) — порты 8096/TCP (HTTP), 8920/TCP (HTTPS, по умолчанию выключен), 7359/UDP (discovery, не настраивается), 1900/UDP (DLNA — отдельный плагин), замечание о «Known Proxies» и рекомендация терминировать HTTPS на reverse proxy. - **Публичная информация без авторизации:** `GET /System/Info/Public` → `PublicSystemInfo {LocalAddress, ServerName, Version, ProductName, OperatingSystem, Id, StartupWizardCompleted}` (используйте `Id` как ключ сервера в вашем хранилище, `Version` — для проверки совместимости); `GET /System/Ping` (строка «Jellyfin Server»); `GET /System/Endpoint` (`IsLocal`, `IsInNetwork` — сервер сообщает, считает ли он вас локальным); `GET /Branding/Configuration`; `GET /Users/Public`. Полный `GET /System/Info` требует авторизации. - **«Умный» подбор адреса (SDK):** [AddressCandidateHelper.kt](https://github.com/jellyfin/jellyfin-sdk-kotlin/blob/master/jellyfin-core/src/jvmCommonMain/kotlin/org/jellyfin/sdk/discovery/AddressCandidateHelper.kt) — из пользовательского ввода (`192.168.1.5`, `host`, `https://host/jellyfin`) строит кандидаты: добавляет `http://`/`https://`, порты 8096/8920, сохраняет base path; [RecommendedServerDiscovery.kt](https://github.com/jellyfin/jellyfin-sdk-kotlin/blob/master/jellyfin-core/src/commonMain/kotlin/org/jellyfin/sdk/discovery/RecommendedServerDiscovery.kt) — параллельно дёргает `/System/Info/Public`, измеряет `responseTime`, присваивает `RecommendedServerInfoScore` GREAT/GOOD/OK/BAD по версии (`< minimumServerVersion` → OK с issue `UnsupportedServerVersion`; устаревшая, но поддерживаемая → GOOD; HTTPS предпочтительнее HTTP) и возвращает `RecommendedServerInfo {address, responseTime, score, issues, systemInfo}`. Эта логика стоит копирования даже без SDK. - **Мульти-сервер / мульти-пользователь:** в API нет понятия «аккаунта» — клиент сам хранит список `(serverId, baseUrl[, альтернативные URL LAN/WAN], userId, accessToken, deviceId)`. `AuthenticationResult.ServerId` и `PublicSystemInfo.Id` одинаковы — используйте для дедупликации одного сервера, найденного по разным адресам (так делают официальные клиенты). Для нескольких пользователей на одном устройстве используйте разные `DeviceId` (один токен на DeviceId). Токены не истекают сами, но могут быть отозваны в дашборде (с 12.1 отзыв устройства разлогинивает сессии) — на 401 показывать повторный логин. - [Docs: Reverse Proxy](https://jellyfin.org/docs/general/post-install/networking/reverse-proxy/) — требования, влияющие на клиент: прокси должен пропускать **WebSocket** (`/socket`), корректно пробрасывать `X-Forwarded-For/Proto/Host` (иначе сервер считает всех «удалёнными» и может резать битрейт/доступ), поддержка base URL (например `https://example.com/jellyfin` — ваш клиент обязан уметь путь в baseUrl), HTTP/3 опционален. Все остальные гайды (Caddy/Nginx/Traefik/HAProxy/Apache) — подразделы этой страницы. - **Self-signed сертификаты:** Jellyfin сам может слушать 8920 с PKCS#12-сертификатом, но документация настойчиво рекомендует TLS на прокси. На Android 7+ пользовательские CA не доверяются приложениям по умолчанию — нужен `network_security_config.xml` с `` и/или собственный trust manager в OkHttp (SDK использует OkHttp; как подменить `OkHttpClient` в SDK 1.9 — не проверено). Также учитывайте cleartext HTTP (`usesCleartextTraffic`) — большинство домашних серверов работают по http://. - Поведение при старте/рестарте сервера: HTTP **503 + `Retry-After` (секунды)** — клиенту следует ретраить, а не показывать «сервер недоступен» (release notes 10.11). --- ## 7. Прочие полезные документы: фичи, клиенты, сообщество, release notes и ломающие изменения **Фичи** - [Docs: Media Segments](https://jellyfin.org/docs/general/server/metadata/media-segments) — см. раздел 3. - [Docs: Quick Connect](https://jellyfin.org/docs/general/server/quick-connect/) — см. раздел 1. - [Docs: Codec Support](https://jellyfin.org/docs/general/clients/codec-support/) — таблицы по клиентам. - [Docs: Live TV](https://jellyfin.org/docs/general/server/live-tv/) и [Live TV Setup Guide](https://jellyfin.org/docs/general/server/live-tv/setup-guide/) — тюнеры (HDHomeRun, M3U), гайды (XMLTV, Schedules Direct); клиентской части там нет, API — см. раздел 4. - [Docs: Music (lyrics)](https://jellyfin.org/docs/general/server/media/music/) — форматы `.lrc/.elrc/.txt`, синхронизированные/несинхронизированные тексты. - [Docs: Books](https://jellyfin.org/docs/general/server/media/books/) — в 12.0 книги/комиксы стали нативными (плагин Bookshelf депрекирован). - Отдельных страниц документации по **SyncPlay** и **Trickplay** на jellyfin.org нет (проверил несколько путей — 404). Источники: OpenAPI (тег SyncPlay: `POST /SyncPlay/New, Join, Leave, Ping, Ready, Buffering, Pause, Unpause, Seek, Stop, NextItem, PreviousItem, Queue, SetNewQueue, SetPlaylistItem, MovePlaylistItem, RemoveFromPlaylist, SetRepeatMode, SetShuffleMode, SetIgnoreWait`, `GET /SyncPlay/List`, `GET /SyncPlay/{id}` (10.11+)), WebSocket-сообщения `SyncPlayCommand`/`SyncPlayGroupUpdate`, серверный [SyncPlayController.cs](https://github.com/jellyfin/jellyfin/blob/master/Jellyfin.Api/Controllers/SyncPlayController.cs) и эталонная реализация в веб-клиенте [jellyfin-web/src/plugins/syncPlay](https://github.com/jellyfin/jellyfin-web/tree/master/src/plugins/syncPlay) (синхронизация часов через `GET /GetUtcTime` и `SyncPlay/Ping`). Известные грабли: сервер ждёт `Ready` от всех участников до 30 с и ставит группу на паузу ([issue #18121](https://github.com/jellyfin/jellyfin/issues/18121) — упомянут в поиске, содержимое не читал). - Trickplay: единственная «документация» — [release post 10.9.0](https://jellyfin.org/posts/jellyfin-release-10.9.0/) + контроллер/схемы (раздел 3). В 10.10 добавлено извлечение только по keyframes; в 10.11 trickplay-данные переехали в отдельную подпапку и чистятся при пересканировании. **Клиенты и требования к листингу** - [Downloads → Clients](https://jellyfin.org/downloads/clients/) — только официальные клиенты (Android, Android TV/Fire TV, Desktop/JMP, Kodi/JellyCon, iOS, Roku, WebOS, Tizen, Xbox) с вкладками Recommended/All. - [Docs: Clients](https://jellyfin.org/docs/general/clients/) — **критерии для попадания стороннего клиента в официальный список**: Jellyfin как first-class (настраиваемый URL), FOSS-лицензия из списка FSF, ≥1 года активной разработки или заметный интерес, хотя бы одна уникальная фича относительно уже перечисленных для платформы, соблюдение community standards и branding; подача — PR в файл данных клиентов в репозитории jellyfin.org (точный путь файла не проверял). - [Docs: Branding](https://jellyfin.org/docs/general/contributing/branding/) — **важно для именования вашего приложения**: нельзя называть продукт «Jellyfin …», избегать паттернов «Jelly[слово]» и «[слово]fin», нельзя использовать логотип без разрешения; можно писать «X, a Jellyfin client» и использовать цветовой градиент при уникальной форме логотипа. Ассеты — [jellyfin-ux](https://github.com/jellyfin/jellyfin-ux). - [awesome-jellyfin CLIENTS.md](https://github.com/awesome-jellyfin/awesome-jellyfin/blob/main/CLIENTS.md) — большой список сторонних клиентов. Android: Findroid, Fladder, Jellyflix, AFinity, JellyCine, Linthra, Mellow, Moonfin, Coppelia, Finamp (музыка), Jellify, Fintunes, JellyBook и др.; Android TV: Elefin, Wholphin, Void, Fladder, Moonfin, Ghosten-Player, AfuseKt TV, Plezy, VidHub, Yamby. Полезно для анализа конкурентов и поиска «уникальной фичи» (libVLC-клиентов среди них почти нет). - [Findroid](https://github.com/jarnedemeulemeester/findroid), [Streamyfin](https://github.com/streamyfin/streamyfin) — крупнейшие сторонние клиенты (Kotlin+SDK и React Native соответственно), полезны как референс поведения. **Контрибьюция и сообщество** - [Docs: Contributing](https://jellyfin.org/docs/general/contributing/) и [Development](https://jellyfin.org/docs/general/contributing/development/) — fork/feature-branch/PR, обсуждение крупных идей в [jellyfin-meta Discussions](https://github.com/jellyfin/jellyfin-meta/discussions), ссылки на style guides и API docs. - [Contact](https://jellyfin.org/contact/) — Matrix (предпочтительно): `#jellyfin:matrix.org`, `#jellyfin-dev:matrix.org`, **`#jellyfin-dev-client:matrix.org`** и **`#jellyfin-dev-android:matrix.org`** (прямо про сторонние/Android-клиенты), `#jellyfin-troubleshooting`, Space `#jellyfinorg:matrix.org`; Discord-мост `https://discord.gg/zHBxVSXdBV`; форум [forum.jellyfin.org](https://forum.jellyfin.org) (раздел [Development](https://forum.jellyfin.org/f-development)); фичреквесты [features.jellyfin.org](https://features.jellyfin.org) (HEAD вернул 405 — сайт отвечает только на GET, ссылка рабочая). - README SDK ссылается на Matrix-комнату `+jellyfin-android-dev:matrix.org` (space для Android-разработки). **Release notes и ломающие изменения API (10.9 → 12.2)** - [10.9.0 (2024-05-11)](https://jellyfin.org/posts/jellyfin-release-10.9.0/): Trickplay; корректные HTTP-коды у всех эндпоинтов; удалён EasyPassword (PIN) — остался Quick Connect; DLNA вынесен в плагин; плейлисты приватны по умолчанию. - [10.10.0 (2024-10-26)](https://jellyfin.org/posts/jellyfin-release-10.10.0/): Media Segments (хранение на сервере, провайдеры — плагины); keyframe-only trickplay; выбор провайдеров лирики; удалены «network paths» в библиотеках (сломало Jellyfin4Kodi); клиенты в основном совместимы. - [10.11.0 (2025-10-19)](https://jellyfin.org/posts/jellyfin-release-10.11.0/) + [GitHub v10.11.0](https://github.com/jellyfin/jellyfin/releases/tag/v10.11.0): депрекированы `OnPlaybackStart/Progress/Stopped` → `ReportPlayback*` (`/Sessions/Playing*`); добавлен серверный флаг для отключения legacy-авторизации (для тестирования клиентов); **503 + `Retry-After`** при старте/рестарте; `GET /System/Info/Storage`, Backup API, `GET /SyncPlay/{id}`; `ratingScore` для возрастных рейтингов; ELRC-лирика; `parentPrimaryImageItemId` стал Guid; явные дефолты enum'ов в моделях; генерируемые URL используют `ApiKey`; библиотека переехала на EF Core (`jellyfin.db`). Diff OpenAPI 10.10.7→10.11.11, который я сделал локально: удалён только `GET /System/WakeOnLanInfo`; добавлены `/Backup*`, `/SyncPlay/{id}`, `/System/Configuration/Branding`, `/System/Info/Storage`. - [12.0 (2026-09-07)](https://jellyfin.org/posts/jellyfin-release-12.0/) + [GitHub v12.0](https://github.com/jellyfin/jellyfin/releases/tag/v12.0): смена версионирования (10.12 → 12.0; клиенты, парсящие версии, должны это учитывать); **legacy-авторизация выключена по умолчанию, в т. ч. миграцией на существующих серверах**; удалены префиксы `/emby/*`, `/mediabrowser/*`; удалены `GET /QuickConnect/Initiate` (только POST), `POST /Users/{userId}/EasyPassword`, `GET /Items/{itemId}/CriticReviews`, `GET /Environment/NetworkShares`, `POST /System/MediaEncoder/Path`, `GET /LiveTv/Recordings/Groups/{groupId}`; HLS-контроллеры скрыты из OpenAPI; Swashbuckle v10 (SDK надо регенерировать); `GetItems` асинхронный и меняет семантику `recursive`; `ItemByName`-ответы урезаны, люди дедуплицированы; `UserDto.HasPassword` бесполезен; `Accept-Language`; имена пользователей case-insensitive; картинки не апскейлятся; obsolete (пока работают): `GetTrailers`, `GetArtists/GetAlbumArtists`, `GetArtistByName`, `GetMusicGenre`, instant-mix по жанру, Startup-роуты. Политика: «не используйте эндпоинты/параметры, которых нет в OpenAPI или помеченные obsolete; депрекация держится один мажорный цикл». Мой локальный diff OpenAPI 10.11.11→12.2.0 (25 удалённых операций): все HLS (`/Videos/{id}/master.m3u8|main.m3u8|live.m3u8|hls/*|hls1/*`, `/Audio/{id}/master.m3u8|main.m3u8|hls/*|hls1/*`), `POST/DELETE /PlayingItems/{itemId}`, `POST /PlayingItems/{itemId}/Progress`, `GET /Artists/InstantMix`, `GET /MusicGenres`, `GET /LiveTv/Recordings/Groups`, `GET /LiveTv/Recordings/Series`, `GET /Items/{itemId}/CriticReviews`, `GET /Environment/NetworkShares`, `GET /Tmdb/ClientConfiguration`, `DELETE /Videos/ActiveEncodings`; добавлен `GET /Items/{itemId}/Collections`. - [12.1 (2026-09-15)](https://github.com/jellyfin/jellyfin/releases/tag/v12.1): только фиксы — отзыв устройства разлогинивает сессии, forced-субтитры перекрывают запомненные, trickplay берёт правильный видеопоток, `/UserViews` не течёт по памяти, `Filters2` возвращает теги, фикс WebSocket. - [12.2 (2026-10-05)](https://github.com/jellyfin/jellyfin/releases/tag/v12.2): фиксы — Dolby Vision p8 → тег `hvc1`, графические субтитры с Range, исправлено имя типа `BaseItemKind.Channel` (проверьте, если матчите строки типов), Next Up при повторном просмотре, фильтры по людям. - [State of the Fin 2026-01-06](https://jellyfin.org/posts/state-of-the-fin-2026-01-06) — анонс обсуждения смены версии на 12.0, новый «Experimental» layout веба по умолчанию, тема через CSS-переменные. --- ## 8. Сторонние гайды / статьи («как написать клиент Jellyfin») Качественных полноценных туториалов мало; большинство материалов — справочники SDK. Что реально полезно: - [Gist nielsvanvelzen — Jellyfin authorization](https://gist.github.com/nielsvanvelzen/ea047d9028f676185832e51ffaf12a6f) — лучший короткий текст по авторизации от мейнтейнера SDK (см. раздел 1). - [Gist kylehowells — «Jellyfin Video Playback.md»](https://gist.github.com/kylehowells/74f538c766a244a3666319860a937030) — неформальная заметка: пример тела `POST /Items/{id}/PlaybackInfo?UserId=` с DeviceProfile, пример ответа с `MediaSources`, `TranscodingUrl` (`/videos//master.m3u8?...`), direct-play `/Videos/{mediaSourceId}/stream.mp4`, внешние субтитры `/Videos//Subtitles/5/0/Stream.vtt`. JSON в примерах «рукописный» (не парсится), версия сервера не указана, но как ориентир по потоку — ок. Последняя активность 06.2025. - [Kotlin SDK Getting started](https://kotlin-sdk.jellyfin.org/guide/getting-started.html) / [Authentication](https://kotlin-sdk.jellyfin.org/guide/authentication.html) / [WebSockets](https://kotlin-sdk.jellyfin.org/guide/websockets.html) — официальные мини-туториалы; вместе с `samples/kotlin-cli` это фактически «how to write a client». - [typescript-sdk.jellyfin.org](https://typescript-sdk.jellyfin.org/) — README с пошаговым сценарием: ClientInfo/DeviceInfo → `getAddressCandidates`/`getRecommendedServers` → `createApi` → `getPublicSystemInfo` → `authenticateUserByName` → `getUserViews` → logout. Хороший чеклист для порта. - [mintlify.wiki Jellyfin API «overview»](https://mintlify.wiki/jellyfin/jellyfin/api/authentication/overview) — AI-сгенерированная документация; curl/JS/Python примеры логина корректны, но не считайте её официальной. - [Jellywatch: What's New in 10.10/10.11](https://jellywatch.app/blog/jellyfin-10-10-10-11-upgrade-guide-new-features-2026), [Jellywatch: 12.0 release](https://jellywatch.app/blog/jellyfin-12-release-everything-you-need-to-know) — сторонний блог; обзорно, местами путаница «10.12 vs 12.0», проверяйте по официальным постам (не проверено содержание детально). - [deepwiki: jellyfin-meta — OpenAPI endpoint domains & playback lifecycle](https://deepwiki.com/jellyfin/jellyfin-meta/3.2-openapi-specification:-endpoint-domains-and-playback-lifecycle) — AI-сгенерированная wiki по репозиторию; при двух попытках вернула 429, содержимое не проверено. - [PR mia-cx/pendia #91](https://github.com/mia-cx/pendia/pull/91) — сторонний сервер, реализующий Jellyfin API (PlaybackInfo → DeviceProfile → direct/remux/transcode, `/Sessions/Playing*`); интересен как «обратная» документация того, что ожидают клиенты (октябрь 2026; содержимое PR не читал подробно). - Сообщество Dart: [dart_jellyfin docs](https://pub.dev/documentation/dart_jellyfin/0.2.0/) — неофициальная библиотека с описанием Quick Connect-потока и замечанием про один токен на DeviceId. --- ## Краткий чеклист практических «граблей» для вашего клиента 1. Только `Authorization: MediaBrowser Client=…, Device=…, DeviceId=…, Version=…, Token=…`; в URL для libVLC — `?ApiKey=`. Никаких `X-Emby-*`/`api_key`. 2. `DeviceId` уникален на пару (установка, пользователь); иначе логины вытесняют друг друга. 3. Решение о способе воспроизведения принимает сервер по `DeviceProfile` в `POST /Items/{id}/PlaybackInfo`; используйте `TranscodingUrl` как есть, `PlaySessionId` — во всех отчётах и в `Ping`. 4. Все времена — тики по 100 нс; `PositionTicks = ms * 10_000`. 5. Отчёты: `POST /Sessions/Playing`, `/Progress` (периодически + при pause/seek), `/Stopped` (обязательно, иначе сессия «висит» и транскодер не освобождается). 6. Запрашивайте `fields=` явно (`MediaSources, MediaStreams, Chapters, Trickplay, Overview, Genres, People, …`) — по умолчанию `BaseItemDto` неполный. 7. Используйте `tag` в URL картинок и `ImageBlurHashes` для плейсхолдеров. 8. WebSocket `/socket`: отвечать `KeepAlive` на `ForceKeepAlive`, отправить `/Sessions/Capabilities(/Full)`, чтобы принимать `Play`/`Playstate`/`GeneralCommand`. 9. Обрабатывайте 503 + `Retry-After` и `ServerRestarting`. 10. Поддержка base path в URL сервера (`https://host/jellyfin`), cleartext HTTP, пользовательские CA (network security config). 11. Название приложения — не «Jellyfin…», не «Jelly*», не «*fin» (branding guide), иначе в официальный список клиентов не попадёте. 12. Для совместимости с 10.11 и 12.x одновременно SDK не подходит (1.8 ↔ 1.9 ломают друг друга); либо целиться только в 12.x с SDK 1.9, либо писать тонкий клиент по OpenAPI и держать совместимость самостоятельно (разница в путях между 10.11.11 и 12.2 — см. список выше). Для анализа использовались OpenAPI-спецификации 10.10.7 / 10.11.11 / 12.2.0 и выдержки исходников SDK (в репозиторий не входят). --- # Часть 2. Обзор open-source Jellyfin-клиентов и смежных проектов Дата среза: 2026-10-11. Звёзды/даты последнего push взяты из GitHub API в этот день (если не указано иное). Всё, что не удалось подтвердить напрямую, помечено «(не проверено)». ## 2.0 Главные выводы 1. **Ни один живой Kotlin-клиент Jellyfin не использует libVLC как основной движок.** Официальный `jellyfin-androidtv` удалил libVLC в июне 2024 (PR #3639, релиз 0.17). Единственный актуальный Kotlin-клиент с libVLC-бэкендом — **JellyPlay** (raulshma/jellyplay, 31★, бета, `libvlc-all 3.7.5`), где VLC — третий «запасной» движок после ExoPlayer и libmpv. Все остальные крупные native-клиенты (Findroid, Wholphin, AFinity, Void, JellyCine) выбрали пару **media3/ExoPlayer + libmpv**. 2. **Лучший референс по DeviceProfile для VLC-движка — Swiftfin** (`Shared/Objects/VideoPlayerType/VideoPlayerType+VLC.swift`, `Shared/Extensions/JellyfinAPI/DeviceProfile.swift`): полные списки direct-play/transcoding/subtitle/codec-профилей для libVLC, режимы совместимости, пользовательские профили. Swiftfin в 2026 перешёл с VLCKit на **SwiftVLC (libVLC 4.0)** и добавил экспериментальный **MPV**-бэкенд (MPVUI). 3. **Лучший референс по DeviceProfile для Android (MediaCodec-зондирование, HDR, passthrough, known-defects по моделям)** — `jellyfin-androidtv/app/src/main/java/org/jellyfin/androidtv/util/profile/` (использует DSL `buildDeviceProfile` из jellyfin-sdk-kotlin) и `jellyfin-android/.../player/deviceprofile/DeviceProfileBuilder.kt`. 4. **Уроки удаления libVLC из jellyfin-androidtv**: (а) устаревший артефакт `libvlc-all 3.5.1` (2022) с незакрытым CVE — F-Droid поднял вопрос (issue #3623); (б) «плохая» реализация JNI-биндингов: объекты не всегда уничтожались → утечки и краши при use-after-destroy; (в) смешанный код ExoPlayer+VLC в одном `VideoManager.java` → проблемы с памятью; (г) размер APK 107 МБ → 24 МБ после удаления. Сейчас на Maven Central есть `org.videolan.android:libvlc-all` **3.7.7** (ветка 3.x) и **4.0.0-eap29** (обновлено 2026-10-01), т.е. причина (а) снята. 5. Пользователи требовали libVLC ради **ASS/SSA и PGS субтитров, DTS-HD/TrueHD, Dolby Vision в MKV**, которые ExoPlayer не тянул. Сегодня официальный клиент закрывает ASS через `io.github.peerless2012:ass-media` (libass для media3) — это альтернатива, которую стоит знать. 6. Брендинг: название «Jellyfin» и логотип — зарегистрированные товарные знаки Jellyfin, Inc.; сторонним приложениям нельзя называться «Jellyfin X», нежелательны «Jelly[word]»/«[word]fin», логотип (и его повороты/вставки) запрещён без разрешения; можно использовать градиент цветов и писать «клиент для Jellyfin» в описании. --- ## 2.1 Официальные проекты Jellyfin ### jellyfin-androidtv — официальный Android TV-клиент | | | |---|---| | URL | https://github.com/jellyfin/jellyfin-androidtv | | Лицензия | GPL-2.0 | | Стек | Kotlin (565 .kt / 36 .java файлов на master), Koin DI, Coil 3, jellyfin-sdk-kotlin 1.8.12 | | Плеер | media3/ExoPlayer 1.11.1 + `org.jellyfin.media3:media3-ffmpeg-decoder` (аудио DTS/TrueHD и т.п.) + `io.github.peerless2012:ass-media` 0.5.1 (libass). Внешние плееры через Intent: VLC, mpv, MX Player, Vimu (`ui/playback/external/*ExternalPlayerApi.kt`) | | Цель | только TV (Android TV, Shield, Fire TV), minSdk 23 (с 0.20) | | UI | гибрид: Leanback 1.2.0 + Compose 1.12.1 (в 0.20 настройки и MainActivity переведены на Compose; 36 XML-layout ещё остались) | | Статус | активно: 4.6k★, push 2026-10-08; стабильная 0.19.10 (авг 2026), 0.20.0-beta.3 (24 сен 2026), требует сервер 10.11 | **История libVLC (проверено по PR/issue):** - До 0.16.x включительно в `app/.../ui/playback/VideoManager.java` жили оба движка: ExoPlayer и `org.videolan.libvlc.LibVLC/MediaPlayer` (артефакт `org.videolan.android:libvlc-all:3.5.1`). VLC инициализировался опциями `--network-caching`, `--audio-time-stretch`, `--avcodec-skiploopfilter`, `--android-display-chroma`, `--vout=android-opaque,android-display`, опциональный `--video-filter=deinterlace`; субтитры рисовались во второй `SurfaceView` (`setSubtitlesView`), выбор дорожек через `getSpuTracks()/setSpuTrack()`, задержка через `setSpuDelay`. Полезно как список граблей, а не как образец. - 2023-02-23 PR #2534 «Mark LibVLC as experimental»: «много крашей, потому что наша реализация просто плохая». - Discussion #2546 (2023-02): мейнтейнер nielsvanvelzen — JNI-биндинги «не всегда корректно уничтожаются» → утечки/краши; старый код playback «end-of-life»; новый playback-модуль стартует только с ExoPlayer; libVLC/mpv «возможно позже, но с нуля, с тестами». - 2024-06-02 PR #3639 «Remove LibVLC» (релиз 0.17.0), закрывает issue #3623 «CVE-2023-5217 — update libvlc» (поднято из-за F-Droid): последняя стабильная libvlc-jni 3.5.1 от августа 2022, незакрытая CVE; «смешение ExoPlayer и LibVLC могло вызывать проблемы с памятью»; артефакты сборки 107 МБ → 24 МБ. Мейнтейнер: «LibVLC может вернуться, но возможно выберем mpv или FFmpeg». - Issue #3937 (авг 2024, «вернуть выбор плеера») закрыт как not planned. Форумный тред «SOLVED: Bring back libvlc» — совет ставить 0.16.11 либо внешний VLC/mpv. - Issue #3888: после удаления гибридные DV/HDR10-файлы дают чёрный экран в ExoPlayer — closed/not planned. **Что заимствовать:** - Архитектуру playback-модулей: `playback/core` (PlaybackManager, PlayerBackend-абстракция, очередь с QueueSupplier/OrderIndexProvider, MediaStreamResolver, TimedEventService), `playback/jellyfin` (JellyfinMediaStreamResolver, PlaySessionService + socket-репортинг, MediaSegmentService, lyrics), `playback/media3/exoplayer` (ExoPlayerBackend, `support/*` — отчёт о поддержке формата, `mapping/*` — маппинг кодеков) и `playback/media3/session`. Это готовый чертёж, куда вместо ExoPlayerBackend подставляется VlcBackend. - `util/profile/deviceProfile.kt` + `MediaCodecCapabilitiesTest`, `codec/*CodecCapabilities.kt`, `audioPassthrough.kt`, `KnownDefects.kt` (чёрные списки моделей Fire TV с багом DoVi/HDR10+, Shield с нераспознанным DV Profile 7). Профиль учитывает downmix, bitstream AC3/EAC3/DTS/TrueHD (auto-детект passthrough), `assDirectPlay`/`pgsDirectPlay`, пользовательские AVC/HEVC level override, принудительное включение/выключение HDR-форматов, два transcoding-профиля HLS (TS и fMP4) с `enableSubtitlesInManifest`. - Внешние плееры: `VlcExternalPlayerApi.kt` реализует интенты VLC (`subtitles_location`, `position`, коды результата) — полезно для fallback «открыть в VLC». - Боли из issues: TV-навигация и фокус на Leanback тяжело поддерживать (поэтому идёт миграция на Compose), нестабильность DV/HDR на Fire TV, отсутствие субтитров нужных форматов в ExoPlayer. ### jellyfin-android — официальный мобильный клиент | | | |---|---| | URL | https://github.com/jellyfin/jellyfin-android | | Лицензия | GPL-2.0 | | Стек | Kotlin, WebView-обёртка jellyfin-web + нативный плеер; Koin 4.2.2, Compose 1.12.1 (частично), jellyfin-sdk 1.8.12 | | Плеер | media3/ExoPlayer 1.11.1 (+hls, +cast, +ffmpeg-decoder), Chromecast только в proprietary-flavor | | Цель | телефон/планшет | | Статус | 2.8k★, push 2026-10-09, активен | **Что заимствовать:** `app/src/main/java/org/jellyfin/mobile/player/deviceprofile/DeviceProfileBuilder.kt` — профиль строится динамически по `MediaCodecList`: таблица контейнер→допустимые видео/аудио-кодеки, `CodecProfile` c `VIDEO_PROFILE` из реальных возможностей декодера, набор `FORCED_AUDIO_CODECS` (кодеки от FFmpeg-расширения, которые MediaCodecList не объявляет), отдельный `getExternalPlayerProfile()` (всё direct-play, без transcoding — ровно то, что нужно для VLC-режима «играй всё сам»), профили субтитров EMBED/EXTERNAL с опцией `exoPlayerDirectPlayAss`. Также `player/source/MediaSourceResolver.kt`, `ExternalSubtitleStream.kt`, `QueueManager`, `TrickplayHelper`, `mediasegments/`, `PlayerService`/MediaSession. ### jellyfin-sdk-kotlin | | | |---|---| | URL | https://github.com/jellyfin/jellyfin-sdk-kotlin (docs: https://kotlin-sdk.jellyfin.org) | | Лицензия | LGPL-3.0 | | Стек | Kotlin, Android + JVM; модули `jellyfin-core`, `jellyfin-api`, `jellyfin-model`, `jellyfin-api-okhttp`; код генерируется из `openapi.json` | | Версия | **1.9.2** на Maven Central (обновлено 2026-10-06); клиенты в основном на 1.8.12 (10.11 SDK) | | Статус | 125★, push 2026-10-08 | **Что заимствовать:** использовать как есть. Есть DSL `org.jellyfin.sdk.model.deviceprofile.buildDeviceProfile { directPlayProfile {…}; transcodingProfile {…}; codecProfile { conditions { … } } }` (видно в jellyfin-androidtv), server discovery, socket API. Binary-compatibility validator — пример дисциплины для публичного API модуля плеера. ### Swiftfin (iOS/tvOS) — главный референс по VLC-профилям | | | |---|---| | URL | https://github.com/jellyfin/Swiftfin ; docs: https://github.com/jellyfin/Swiftfin/blob/main/Documentation/players.md | | Лицензия | MPL-2.0 | | Стек | Swift, SwiftUI, jellyfin-sdk-swift 3.3.0 | | Плеер | три бэкенда: `VideoPlayerType.avPlayer` (native), `.vlc` — **SwiftVLC 1.0.0 (harflabs/SwiftVLC, libVLC 4.0, MIT)**, `.mpv` — **MPVUI 0.5.0 (LePips/MPVUI)**, пока за флагом `Experimental.mpvPlayer`. Прокси: `Shared/Objects/MediaPlayerManager/MediaPlayerProxy/MediaPlayerProxy+{VLC,MPV,AVPlayer}.swift` | | Цель | iOS 18+, tvOS 26+ | | Статус | 4.2k★, push 2026-10-10, активен | **Что заимствовать (конкретные файлы):** - `Shared/Objects/VideoPlayerType/VideoPlayerType+VLC.swift`: `_vlcDirectPlayProfiles` (30 аудио- и ~25 видео-кодеков, AV1 только при `PlaybackCapabilities.supportsAV1`), `_vlcTranscodingProfiles` (HLS, `isBreakOnNonKeyFrames`, `maxAudioChannels 8`, `minSegments 2`, контейнер mp4; **отдельный профиль для VC1**, чтобы обойти серверное ограничение `_supportedHlsVideoCodecs`), `_vlcSubtitleProfiles` (embed: ass, ssa, pgssub, dvbsub, dvdsub, subrip, vtt, ttml, mov_text, sami, teletext…; external: subrip, ass, ssa, …; encode/burn-in: dvbsub, dvdsub, pgssub, vtt, xsub — т.е. внешние bitmap-субтитры VLC не грузит, их прожигают), `_vlcCodecProfiles` (условия `videoRangeType` для h264/hevc/av1/vp9, `isAnamorphic`, `isInterlaced`, список HDR-режимов `vlcHDRProfiles`: HLG/HDR10/HDR10+/DoVi-with-*). - `Shared/Extensions/JellyfinAPI/DeviceProfile.swift`: `DeviceProfile.build(for:compatibilityMode:maxBitrate:maxResolution:)` — режимы `auto / mostCompatible / directPlay / custom`, принудительный burn-in субтитров (`forceSubtitleBurnIn`), методы `canPlay(...)` для проверки direct-play локально; `PlaybackDeviceProfile.swift` — пользовательские профили в UI (`CustomDeviceProfilesView`). - `MediaPlayerProxy+VLC.swift`: выбор дорожек по индексу `MediaStream.index` ↔ `player.audioTracks/subtitleTracks`, `setAudioDelay`/`setSubtitleDelay`, протоколы `MediaPlayerSubtitleConfigurable`/`MediaPlayerOffsetConfigurable`; `TextSubtitlePresentation.swift` — overlay текстовых субтитров поверх MPV (у VLC субтитры рисует сам движок). - Документ players.md фиксирует ограничения VLC: нет TLS 1.3 в libvlc, нет DTS-HD/TrueHD/MLP, нет PiP, external-display только зеркалом, повышенное энергопотребление при software-decoding, DV Profile 5 не поддерживается, HDR→SDR tonemapping «точность цвета может плавать». ### jellyfin-web / jellyfin-vue - **jellyfin-web** — https://github.com/jellyfin/jellyfin-web, GPL-2.0, JS/TS (webpack+vite), 3.9k★, push 2026-10-11, v13.0.0. Плеер — HTML5 `