51 KiB
media3/ExoPlayer как основной движок Jellyfin-клиента для Android TV: что умеет, что нет
Дата: 2026-10-12. Версии: media3 1.11.1 (2026-09-10), jellyfin-androidtv master 0032f4a (2026-10-12), Jellyfin server 12.2, jellyfin-sdk-kotlin 1.9.2.
Метод: исходники читались напрямую (клоны androidx/media main 8c6678b = 1.11.1, jellyfin/jellyfin-androidtv master, jellyfin/jellyfin-androidx-media, peerless2012/libass-android, anilbeesetti/nextlib, moneytoo/Player (Just Player), xbmc/xbmc raw). Issues/PR проверялись через GitHub API или загрузкой страниц. Всё, что не подтверждено кодом или первоисточником, помечено (не проверено).
0. TL;DR
| Тема | Вывод |
|---|---|
| DV P5 / P8.x / P10 | Работает, если есть DV-декодер (video/dolby-vision) и дисплей сообщает HDR_TYPE_DOLBY_VISION. Без DV-дисплея media3 сам переключается на HEVC/AVC/AV1 для P4/P8/P9/P10 (кроме 10.0). Для P5 фолбэка нет. |
| DV P7 (FEL/MEL, BD-remux) | В media3 нет фолбэка на HEVC (DvheDtb исключён в getAlternativeCodecMimeType). FEL никогда не обрабатывается (только BL+RPU, если декодер SoC умеет). Фикс фолбэка: PR #3160, ещё открыт. |
| DV в MKV | MatroskaExtractor парсит BlockAdditionMapping с BlockAddIDType = dvcC/dvvC → MIME video/dolby-vision. В MP4 парсятся dvcC/dvvC/dvwC. |
| HDR10 / HLG / HDR10+ | Статические метаданные идут через ColorInfo.hdrStaticInfo → KEY_HDR_STATIC_INFO. HDR10+ для HEVC/AV1 передаётся внутри потока (SEI/OBU), для VP9 в MKV через BlockAdditional T.35 → PARAMETER_KEY_HDR10_PLUS_INFO. Гибриды DV+HDR10+ ломаются на устройствах MediaTek (Fire TV 4K/Max, Google TV Streamer). |
| Tunneling | Включается через DefaultTrackSelector.Parameters.setTunnelingEnabled(true). Полезен для 4K/HDR на слабых SoC, но ломает часть сценариев (точный seek в конец, видео без аудио, эффекты). jellyfin-androidtv его не включает. |
| Частота обновления | media3 сам умеет только C.VIDEO_CHANGE_FRAME_RATE_STRATEGY_ONLY_IF_SEAMLESS (через Surface.setFrameRate). На ТВ переключение почти всегда «не бесшовное», поэтому Kodi, Just Player и jellyfin-androidtv ставят WindowManager.LayoutParams.preferredDisplayModeId. |
| Режим HDR | Приложение не может явно включить HDR-режим HDMI. Он переключается автоматически, если HDR-кадры идут в SurfaceView (зависит от прошивки и настройки «Match dynamic range»). Обязательно SurfaceView, не TextureView. Обратного переключения в SDR после остановки может не быть: #3406. |
| Passthrough | AC3, E-AC3, E-AC3-JOC (Atmos), DTS, DTS-HD, DTS:X P2 (ENCODING_DTS_UHD_P2, API 34) и TrueHD (ENCODING_DOLBY_TRUEHD) через DefaultAudioSink + AudioCapabilities. DTS-HD MA пока идёт как ENCODING_DTS_HD; PR #3234 под ENCODING_DTS_HD_MA открыт. |
| FFmpeg-расширение | Официальное media3-decoder-ffmpeg декодирует только аудио: ExperimentalFfmpegVideoRenderer в 1.11.1 пустая заглушка. Готовый AAR: org.jellyfin.media3:media3-ffmpeg-decoder:1.9.0+1 (только аудио, FFmpeg 6.0, LGPL-сборка, POM помечен GPL-3.0). |
| Программное видео | Официально VP9 (libvpx) и AV1 (dav1d с 1.9.0, раньше libgav1); ни то, ни другое не публикуется в Maven. Сторонний io.github.anilbeesetti:nextlib-media3ext:1.11.1-0.16.0 добавляет FFmpeg-видео (h264, hevc, mpeg1/2, vp8, vp9, av1-dav1d), но вывод 8-bit RGBA/YUV420P, то есть без HDR. VC-1, MPEG-4 ASP и WMV ни в одной готовой сборке нет. |
| ASS/SSA | io.github.peerless2012:ass-media:0.5.1 (libass, MIT) поддерживает вложенные шрифты MKV. В jellyfin-androidtv подключён за опцией libass_enabled (по умолчанию false), режим AssRenderType.OVERLAY_OPEN_GL. |
| PGS / VobSub / DVB | Внутри MKV (S_HDMV/PGS, S_VOBSUB, S_DVBSUB) и DVB в TS работают. Внешние .sup и .idx/.sub через SubtitleConfiguration фактически не годятся (не проверено). |
| Контейнеры | MKV: header stripping поддерживается, zlib нет (#2361), ordered chapters и linked segments нет. M2TS (192-байтные пакеты) не поддерживается (#608). ASF/WMV, ISO, VIDEO_TS и BDMV не поддерживаются. AVI есть (AviExtractor). |
1. Dolby Vision в media3
1.1 Профили и сопоставление
Источник: libraries/common/.../CodecSpecificDataUtil.java и libraries/exoplayer/.../mediacodec/MediaCodecUtil.java.
| Профиль DV | Константа CodecProfileLevel |
Codec string | Базовый слой | Фолбэк media3 без DV-дисплея |
|---|---|---|---|---|
| 4 | DolbyVisionProfileDvheDtr |
dvhe.04 | HEVC | → video/hevc |
| 5 | DolbyVisionProfileDvheStn |
dvhe.05 | IPTPQc2, без обратной совместимости | нет |
| 7 | DolbyVisionProfileDvheDtb |
dvhe.07 | HEVC Main10 HDR10 + EL | нет, явно исключён: «deprecated and is not always backward compatible» |
| 8.1 / 8.2 / 8.4 | DolbyVisionProfileDvheSt |
dvhe.08 (dvh1.08 в MP4) | HEVC HDR10 / SDR / HLG | → video/hevc |
| 9 | DolbyVisionProfileDvavSe |
dvav.09 | AVC | → video/avc |
| 10.x | DolbyVisionProfileDvav110 |
dav1.10 | AV1 | → video/av01, кроме 10.0 (PQ + full range означает отсутствие совместимости) |
- Поддержку P10 добавили в 1.10.0 (PR #2830). В 1.10.1 исправили артефакты из-за initialization data для DV-AV1 (#3153).
- MIME:
MimeTypes.VIDEO_DOLBY_VISION = "video/dolby-vision".MediaCodecUtil.getCodecMimeTypeучитывает вендорские MIME:OMX.MS.HEVCDV.Decoder→video/hevcdv, а Realtek (OMX.RTK.video.decoder,OMX.realtek.video.decoder.tunneled) →video/dv_hevc. MediaCodecVideoRenderer.onConfigure: для DV вMediaFormatявно пишетсяKEY_PROFILE, потому что некоторые устройства этого требуют.
1.2 Как выбирается декодер (MediaCodecVideoRenderer.getDecoderInfos)
if (SDK_INT >= 26 && format is DV && !Api26.doesDisplaySupportDolbyVision(context)) {
alt = MediaCodecUtil.getAlternativeDecoderInfos(...) // HEVC/AVC/AV1 по таблице выше
if (!alt.isEmpty()) return alt;
}
return MediaCodecUtil.getDecoderInfosSoftMatch(...) // DV-декодеры + альтернативные
doesDisplaySupportDolbyVisionпроверяетdisplay.isHdr()и наличиеHDR_TYPE_DOLBY_VISIONвDisplay.getHdrCapabilities().getSupportedHdrTypes()дляDEFAULT_DISPLAY.- Если DV-дисплея нет,
supportsFormatвозвращаетDECODER_SUPPORT_FALLBACK_MIMETYPE. getDecoderInfosSoftMatchдаёт полный список: декодеры для основного MIME плюс для альтернативного (см. javadoc). Отсюда следствие: если на устройстве есть DV-декодер, а дисплей DV не поддерживает, для P5 и P7 будет использован DV-декодер. Что он выведет на не-DV экран (тон-маппинг или фиолетовая картинка), зависит от SoC (не проверено).- Если ни DV-декодера, ни фолбэка нет (например, P7 на Chromecast/Google TV Streamer без DV), трек получает статус unsupported и видео не играет. Решения: (а) DeviceProfile, чтобы сервер срезал DV или транскодировал; (б) на клиенте подменить
sampleMimeTypeнаvideo/hevcчерез собственныйMediaCodecSelectorили обёртку экстрактора (так делают сторонние плееры (не проверено)). - Из release notes:
- 1.9.x: «Fix issue where some playbacks of Dolby Vision files fail when attempting to use a fallback AVC or HEVC codec».
- Ранее: «Disable codec reuse for Dolby-Vision content with different profiles» и «Support CEA-608 in DV content».
- 1.11.0: трек с распознанным, но неподдерживаемым профилем/уровнем теперь
NO_EXCEEDS_CAPABILITIES, а неYES. - 1.10.1: #3135 перенёс HDR-предпочтения в quality preferences.
- Открыт PR #3461 «Select HDR video tracks by display HDR capabilities».
1.3 Профиль 7 (FEL/MEL)
- В MKV (MakeMKV) BL+EL+RPU объединены в один трек с NAL type 62/63, codec
dvhe.07.06. - media3 не делает ни композиции EL, ни конвертации P7 в P8. Feature request #1865 («DV Profile 7 + FEL on Android TV») открыт с 2024-11, без ответа мейнтейнеров.
- Фолбэк P7 → HEVC (BL HDR10/HDR10+) появится только после PR #3160 (open, approve от контрибьютора 2026-08-14).
- Устройства: Nvidia Shield 2019 фактически играет P7 (MEL и FEL как BL+RPU), но не сообщает об этом в
MediaCodecList. jellyfin-androidtv держит это вKnownDefects.unreportedDoviProfile7Support(Build.MODEL == "SHIELD Android TV"). - Ugoos/CoreELEC с
dovi.koи полноценным FEL недоступны обычному Android-приложению. - Свежий баг jellyfin-androidtv: #5794, «TrueHD passthrough and DV P7 fallback broken on Shield 2019». В 0.20 beta частично исправлен; на не-DV Samsung P7 играет в SDR вместо HDR10.
1.4 DV в MKV и MP4
- MKV:
MatroskaExtractorпонимаетID_BLOCK_ADDITION_MAPPING (0x41E4),ID_BLOCK_ADD_ID_TYPE (0x41E7),ID_BLOCK_ADD_ID_EXTRA_DATA (0x41ED). ПриBlockAddIDType == 0x64766343 ('dvcC')или0x64767643 ('dvvC')байты конфигурации парсятся черезDolbyVisionConfig.parse(), после чегоmimeType = video/dolby-vision,codecs = dvhe.PP.LL.dvwCдля MKV не обрабатывается, для MP4 обрабатывается.BlockAdditionalс ID 4 (ITU-T T.35) берётся только для VP9, остальные block additions пропускаются. - MP4/fMP4: боксы
dvcC,dvvC,dvwC, sample entriesdvhe,dvh1,dav1,dvav/dva1(Mp4Box.java). - TS/HLS: DV определяется через
SUPPLEMENTAL-CODECSв HLS. В «сыром» TS с DV-дескриптором (Blu-ray m2ts) нет (не проверено); M2TS всё равно не открывается, см. §7.
1.5 Известные открытые проблемы DV
- #1895: DV P8.1 + HDR10+ (гибридные релизы) на Fire TV 4K Max и других MediaTek. Симптомы: зависание, чёрный или пурпурный экран. Фикс PR #3085 («Skip HDR10+ metadata processing when DV Profile 8 is detected»), open, результаты смешанные. jellyfin-androidtv обходит это через
KnownDefects.hevcDoviHdr10PlusBugдля моделейAFTKRT,AFTKA,AFTKM,AFTMM. - #5184 (jellyfin-androidtv): чёрное мигание на DoVi при смене сцен.
- #1635: DASH, нельзя выбрать DV AdaptationSet. Для нас неактуально.
2. HDR10 / HDR10+ / HLG и tunneling
2.1 Метаданные
- Статические (MaxCLL/MaxFALL/mastering display): из MKV
Colour/MasteringMetadata, MP4mdcv/clliили VUI/SEI попадают вColorInfo.hdrStaticInfo, затемMediaFormatUtil.maybeSetColorInfoзаписываетMediaFormat.KEY_HDR_STATIC_INFO,KEY_COLOR_TRANSFER,KEY_COLOR_STANDARD,KEY_COLOR_RANGE. В 1.11.0 добавлено «Matroska: Use codec bitstream metadata to populate accurate ColorInfo», так что неверные или пустые Colour-элементы MKV больше не ломают HDR. - HDR10+ (динамические):
- HEVC и AV1: метаданные лежат в SEI/OBU потока и уходят в декодер вместе с кадром. Для MKV ничего специального не нужно, декодер должен поддерживать
HEVCProfileMain10HDR10Plus. - VP9 в MKV/WebM: BlockAdditional ID 4 →
supplementalData→MediaCodec.setParameters(PARAMETER_KEY_HDR10_PLUS_INFO)(API 29+,setHdr10PlusInfoV29).
- HEVC и AV1: метаданные лежат в SEI/OBU потока и уходят в декодер вместе с кадром. Для MKV ничего специального не нужно, декодер должен поддерживать
- HLG: определяется по
COLOR_TRANSFER_HLG, отдельной обработки нет. - Новое: в 1.11.0 добавлена экспериментальная поддержка HAGC (SMPTE ST 2094-50) для MP4/MKV, работает на API 37+. С 1.11.1 отключается через
ExtractorsFactory.setParseHagcMetadata(false). - Открыт PR #3463: «Map 10-bit AV1 to AV1ProfileMain10HDR10 only for PQ».
2.2 Tunneled playback
- API:
DefaultTrackSelector.Parameters.Builder.setTunnelingEnabled(true). Проверка:ExoPlayer.isTunnelingEnabled(). Работает, только если и видео-, и аудиорендерер поддерживают tunneling. Для видео нужен декодер сFEATURE_TunneledPlayback, поддерживающий формат и seamless adaptation (MediaCodecVideoRenderer.supportsFormat). Нужен audio session id: AV-sync идёт по аудио (HW sync). - Плюсы: кадры идут от декодера сразу в HW-композитор, минуя app/SurfaceFlinger. Меньше дропов для 4K60/HDR на слабых SoC (Amlogic/MediaTek-стики), точнее A/V sync. В Just Player опция «Tunneled playback» описана как «can improve playback of 4K/HDR content on Android TV». Подробнее: AOSP multimedia tunneling.
- Минусы и что ломается:
- Без аудиотрека не работает (#2541). Пауза делается через AudioTrack.
- Seek в конец может зависнуть, не доходя до
STATE_ENDED(#1574). - Гонка с audio session id: crash
IllegalStateException(исправлен в 1.10.1, #3099). - DTS:X P2 в HLS-fMP4 теряет первые 2 секунды в tunnel (#1245).
- Нельзя применять video effects или GL-обработку к кадрам. Не работают
VideoFrameMetadataListenerи покадровый контроль, render-first-frame на паузе после seek ненадёжен (частично не проверено). - Нет гарантии нескольких одновременных tunneled-декодеров.
- Часть HDR-метаданных на некоторых SoC в tunnel теряется (не проверено).
- Субтитры: text-рендерер не зависит от tunneling, cues приходят как обычно,
SubtitleViewили ASS-overlay (AssRenderType.OVERLAY_*) рисуются поверх SurfaceView. Не будут работать только режимы, встраивающие субтитры в видеокадр (AssRenderType.EFFECTS_*, deprecated) (логический вывод, не проверено на устройстве). - Рекомендация: опция «Tunneling: Авто/Вкл/Выкл», по умолчанию выкл. Если для конкретной модели есть подтверждённые проблемы с плавностью, включать через device-list. jellyfin-androidtv tunneling не использует, а только репортит поддержку (
ExoPlayerPlaySupportReport.tunnelingFromFlags).
3. Режим дисплея: частота и HDR
3.1 Что умеет media3
ExoPlayer.setVideoChangeFrameRateStrategy(int)принимает толькоC.VIDEO_CHANGE_FRAME_RATE_STRATEGY_ONLY_IF_SEAMLESS(по умолчанию) или..._OFF. На API 30+VideoFrameReleaseHelper.Api30.setSurfaceFrameRateвызываетSurface.setFrameRate(rate, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)(или 0/DEFAULT на паузе). ВариантCHANGE_FRAME_RATE_ALWAYSв media3 не реализован.- По гайду Android:
- для фильмов использовать
Surface.setFrameRate(fps, FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS)(API 31), но только если пользователь выбралDisplayManager.getMatchContentFrameRateUserPreference() == MATCH_CONTENT_FRAMERATE_ALWAYS; - дождаться
DisplayListener.onDisplayChanged, затем выдержать паузу около 2 с и запускать воспроизведение; - на Android TV «тяжёлую» смену режима (HDMI re-negotiation)
setFrameRateможет не вызвать, и гайд прямо советует для этогоpreferredDisplayModeId.
- для фильмов использовать
- Связанные баги:
3.2 Как делают другие
- jellyfin-androidtv (
ui/playback/PlaybackController.java,findBestDisplayMode/setRefreshRate): перебираетDisplay.getSupportedModes()и пропускает режимы ниже 1280×720 и ниже разрешения видео. Подходят частоты== fps,== 2×fps,== 2.5×fps(в сотых герца). Вес:100000 − rateDiff + 100000 − resolutionDiff. РежимыSCALE_ON_DEVICE/SCALE_ON_TV(RefreshRateSwitchingBehavior). Применяетwindow.attributes.preferredDisplayModeId, по выходу сбрасывает в 0 (CustomPlaybackOverlayFragment). - Just Player (
Utils.handleFrameRate): fps берётся изMediaExtractor. Ищется режим того же разрешения с частотой, кратной fps; если такого нет, берётся максимальная. СтавитсяpreferredDisplayModeId, воспроизведение стартует вDisplayManager.DisplayListener.onDisplayChanged. Комментарий в коде: «ExoPlayer already uses Surface.setFrameRate() on Android 11+». - Kodi (
XBMCApp.cpp::SetDisplayMode/SetDisplayModeCallback): тожеpreferredDisplayModeId. HDR-возможности берёт изDisplay.getHdrCapabilities().getSupportedHdrTypes()(AndroidUtils.cpp). - Nova Video Player:
preferredDisplayModeIdс похожей логикой (не проверено по коду). - Рекомендация: свой
DisplayModeManager:- До
prepare()взять fps из JellyfinMediaStream.RealFrameRate/AverageFrameRate. - Если режим «Всегда», выбрать
Display.Modeи выставитьpreferredDisplayModeId(на API 31+ можно ещёSurface.setFrameRate(..., CHANGE_FRAME_RATE_ALWAYS)). - Дождаться
onDisplayChangedс таймаутом. - Включить
setAllowInvalidateSelectionsOnRendererCapabilitiesChange(true), потому что после переподключения HDMI меняются AudioCapabilities. - Учесть 23.976 против 24.000 (сравнивать с точностью около 0.01 Гц) и фильтровать режимы с меньшим разрешением.
- До
3.3 Переключение HDR
- У обычного приложения нет публичного API «включить HDR на HDMI». Платформа или прошивка переключают выход, когда в
SurfaceViewпоступают кадры с HDR-dataspace. Это зависит от настроек «Match content dynamic range» (Shield, Google TV, Fire TV) (не проверено для каждой прошивки). На API 34+ естьDisplayManager.setHdrConversionMode/HdrConversionMode, но это system API (не проверено). - Проверка возможностей:
Display.isHdr()иDisplay.getHdrCapabilities().supportedHdrTypes(HDR_TYPE_DOLBY_VISION,HDR_TYPE_HDR10,HDR_TYPE_HLGс API 24,HDR_TYPE_HDR10_PLUSс API 29). На API 34+ такжеDisplay.Mode.getSupportedHdrTypes()(не проверено). - Обязателен
SurfaceView:TextureViewHDR не передаёт. В Compose этоPlayerSurface(surfaceType = SURFACE_TYPE_SURFACE_VIEW)изmedia3-ui-composeилиAndroidExternalSurface. - Открытый баг #3406: после остановки HDR-видео дисплей остаётся в HDR (Mi Box S, Android TV 11/14). Kodi этим не страдает. Гипотеза: нужно освобождать или отсоединять Surface, сбрасывать
preferredDisplayModeIdи закрывать активити плеера (не проверено).
4. Аудио: passthrough и FFmpeg
4.1 Passthrough (DefaultAudioSink, AudioCapabilities)
- Таблица
AudioCapabilities.ALL_SURROUND_ENCODINGS_AND_MAX_CHANNELS:AC36,AC46,DTS6,DTS_UHD_P210,E_AC3_JOC6,E_AC38,DTS_HD8,DOLBY_TRUEHD8. - Встроенные фолбэки: E-AC3-JOC → E-AC3 (Atmos-метаданные при этом остаются внутри потока); DTS-HD и DTS-UHD-P2 → DTS core, если HD не поддерживается.
- На API 33+ для TV используется
AudioManager.getDirectProfilesForAttributes. На API 29+ для TV учитываютсяexternal_surround_sound_enabled(Fire TV) иforce_external_surround_sound. Для Amazon и Xiaomi есть отдельная ветка. - Atmos: E-AC3-JOC (DD+ Atmos) битстримится как
ENCODING_E_AC3_JOC(API 28+). TrueHD Atmos битстримится какENCODING_DOLBY_TRUEHD(MAT); Atmos в TrueHD передаётся прозрачно. - DTS-HD MA / DTS:X (Blu-ray): идут как
ENCODING_DTS_HD, DTS:X прозрачно внутри. КорректныйENCODING_DTS_HD_MA(API 34) появится с PR #3234 (open), связанный issue #2487. Детект DTS-HD в MKV и MP4 доработан в 1.11 (PR #3226, #3229), в TS склеиваются core и ext (#3147). - TrueHD:
- MKV:
TrueHdSampleRechunkerгруппирует sync-фреймы для passthrough. - fMP4: TrueHD не поддерживается (#1519), PR #3411 открыт. Для нас это значит: не просить сервер ремуксить TrueHD в HLS-fMP4.
- Программный декод 2.0 TrueHD сломан в FFmpeg-расширении с media3 1.2.1 (FFmpeg 6.0, #1843, open). 5.1/7.1 работают.
- #3329: сложности учёта данных в
DefaultAudioSinkпри TrueHD → MAT.
- MKV:
- Реальность устройств: TrueHD/DTS-HD битстримит Shield. Chromecast/Google TV Streamer и большинство стиков — только DD/DD+ (часто «Dolby Atmos via DD+»). Сводки нет (не проверено).
- Наблюдение jellyfin-androidtv PR #5822: если объявить TrueHD как поддерживаемый, а устройство не может его битстримить, сервер отдаст оригинал, клиент декодирует его в PCM 7.1, а TV в цепочке может свести это в стерео. Лучше просить сервер перекодировать в AC3 5.1. Контрпример: Chromecast напрямую в ресивер. Нужна пользовательская настройка.
4.2 Расширение FFmpeg
- Официальный модуль
libraries/decoder_ffmpeg. ВнутриFfmpegAudioRendererиExperimentalFfmpegVideoRenderer. Последний в 1.11.1 явно нерабочий:supportsFormatвозвращаетFORMAT_UNSUPPORTED_TYPE,// TODO: Merge actual implementation in PR #1591,b/470357054.DefaultRenderersFactoryподхватывает его рефлексией, но толку нет. - Маппинг MIME → FFmpeg (
FfmpegLibrary.getCodecName): aac, mp3, ac3, eac3 (+JOC), truehd, dca (DTS / Express / HD), vorbis, opus, amrnb/wb, flac, alac, pcm_mulaw/alaw, h264, hevc. - В Maven Google модуль не публикуется (ExoPlayer #2781). Сборка: NDK r26b, FFmpeg
release/6.0,build_ffmpeg.shс--disable-everything --enable-decoder=..., без--enable-gpl, то есть получается LGPL-2.1+. - Готовый AAR от Jellyfin:
org.jellyfin.media3:media3-ffmpeg-decoder(Maven Central, последняя1.9.0+1, 2025-12-29). Репозиторий jellyfin/jellyfin-androidx-media: submodulesandroidx/media(ветка release) и FFmpegrelease/6.0.ENABLED_DECODERS=(flac alac pcm_mulaw pcm_alaw mp3 aac ac3 eac3 dca mlp truehd), ABI 23, только аудио. Лицензия репозитория и POM: GPL-3.0; сам libffmpeg собран без--enable-gpl(LGPL). jellyfin-androidtv использует1.9.0+1вместе с media31.11.1: бинарно совместимо на практике, но это риск при обновлениях. РежимEXTENSION_RENDERER_MODE_ONили_PREFER(опция «prefer ffmpeg»). - Лицензия для нас: при GPL-клиенте (клиенты Jellyfin под GPL-2.0/3.0) конфликта нет. LGPL требует возможности перелинковки, у открытого проекта это выполнено. Патентные вопросы по AC3/E-AC3/DTS/TrueHD декодерам в FFmpeg лежат на дистрибьюторе (юридически не проверено). Для Google Play многие плееры (Just Player, Next Player, jellyfin-androidtv) так и поставляются.
5. Программное декодирование видео
| Источник | Кодеки | Статус / артефакт | HDR / 10-bit |
|---|---|---|---|
decoder_vp9 (LibvpxVideoRenderer) |
VP9 (+VP8?) | собирать самим, в Maven нет | 10-bit выводится через GL YUV-рендер (не проверено) |
decoder_av1 (Libdav1dVideoRenderer) |
AV1 | с 1.9.0 на dav1d вместо libgav1; собирать самим (NDK r27, meson) | (не проверено) |
ExperimentalFfmpegVideoRenderer (официальный) |
— | заглушка | — |
| PR #1591 «Implement an FfmpegVideoRenderer» (rabbitknight) | VP8/9, MPEG-1/2, VC-1, ProRes, AV1, H.264 Hi10 | open с 2024-08, без ревью мейнтейнеров; адаптирован к 1.11.1 (2026-09); есть баги (green screen при zero stride, pixelWidthHeightRatio=1) |
10-bit приводится к 8-bit |
NextLib anilbeesetti/nextlib, io.github.anilbeesetti:nextlib-media3ext:1.11.1-0.16.0 (2026-09-13) |
видео: h264, hevc, mpeg2video, mpegvideo, vp8, vp9, libdav1d; аудио: как у Jellyfin + opus, vorbis, amr | Maven Central, синхронизирован с media3 1.11.1, используется Next Player; FFmpeg 9.0.1, --enable-version3 + mbedTLS (LGPLv3), репозиторий GPL-3.0 |
вывод WINDOW_FORMAT_RGBA_8888 / YUV420P через sws_scale, то есть HDR и 10-bit теряются |
Что остаётся невозможным без libVLC/libmpv (или без серверной транскодировки):
- VC-1 / WMV3 / WMV9: нет ASF-экстрактора. VC-1 в MKV распознаётся (FourCC
WVC1→video/wvc1) и играет только при HW-декодере. Он есть на Shield и части Amlogic, на Chromecast/Google TV, по-видимому, нет (не проверено). Программного VC-1 нет ни в одной готовой сборке (можно пересобрать NextLib сvc1/wmv3). - MPEG-2 без HW: NextLib (программный mpeg2video). HW MPEG-2 есть почти везде, но на свежих SoC иногда выкинут (не проверено).
- H.264 Hi10P (High 10): HW-поддержки практически нет. jellyfin-androidtv проверяет
AVCProfileHigh10. Программно через NextLib, но с 8-bit выводом и большой нагрузкой на CPU (1080p ещё реально, 4K нет). - MPEG-4 ASP / DivX / Xvid:
AviExtractorи MKV (V_MPEG4/ISO/ASP, FourCC DIVX/XVID/DX50/FMP4) отдаютvideo/mp4v-es. Работает только при HW-декодере; программныйc2.android.mpeg4.decoderнадёжен лишь для Simple Profile (B-frames, QPEL, GMC ASP не тянет) (не проверено). MS MPEG-4 v2/v3 (MP42/MP43) распознаются, но декодеров нет. - HEVC 4:2:2 / 4:4:4, 12-bit, ProRes, RealVideo, Theora, WMV, H.263+ экзотика, MPEG-1 (без HW): только FFmpeg-движок.
- Любой HDR при программном декоде через media3-экстеншены: только SDR-вывод.
6. Субтитры
6.1 Встроенная поддержка
- Парсеры (
DefaultSubtitleParserFactory): SSA/ASS (упрощённо: стили, позиционирование, без анимаций и шрифтов), WebVTT, MP4VTT, SubRip, TX3G, PGS, VobSub, DVB, TTML. - MKV codec IDs:
S_TEXT/UTF8,S_TEXT/ASS,S_TEXT/SSA,S_TEXT/WEBVTT,S_VOBSUB,S_HDMV/PGS,S_DVBSUB. - TS: DVB-subs (
0x59); PGS в TS (0x90) не поддерживается. - MP4: VobSub (1.9), tx3g, wvtt.
- Последние исправления: VobSub SPU START/STOP (1.10), PGS RLE color index 0, пропуски субтитров VobSub.
- Проблема #2667: все image-based дорожки (PGS/VobSub) парсятся в Bitmap, даже невыбранные. При десятках PGS-дорожек это бьёт по памяти и CPU. Обход: пересоздавать или отключать треки, или просить сервер отдавать без лишних дорожек (не проверено).
- Внешние (side-loaded):
MediaItem.SubtitleConfiguration(uri).setMimeType(...).setLanguage(...).setSelectionFlags(...)→SubtitleExtractor(один файл = один парсер). Надёжно: SRT, ASS/SSA, VTT, TTML. Внешние.sup(PGS) и.idx/.sub(VobSub) так подключить нельзя или ненадёжно:.supсодержит PTS в заголовках сегментов, которыеPgsParserне читает, а VobSub требует idx как initialization data и MPEG-PS для .sub (не проверено). jellyfin-androidtv для DVBSUB/DVDSUB/IDX/PGS объявляет толькоEMBED+ENCODE(burn-in), внешних нет.
6.2 ASS/SSA через libass: io.github.peerless2012:ass-media
- Репозиторий peerless2012/libass-android, MIT, последний коммит 2026-10-09. Версии:
0.5.1(2026-08), до этого 0.5.0 и 0.4.0. Это 0.x, API менялся (EFFECTS_*→ deprecated). - Модули:
lib_ass(prefab, libass + freetype/harfbuzz/fribidi/fontconfig),lib_ass_kt(JNI-обёртка),lib_ass_media(интеграция с media3). Внутренне собран против media3 1.8.0. - Классы:
AssHandler(AssRenderType),AssHandlerConfig(glyphSize, cacheSize, maxRenderPixels)(ограничение разрешения рендера, полезно для 4K),AssRenderersFactory,AssSubtitleParserFactory,AssMatroskaExtractor(наследникMatroskaExtractor, достаёт вложенные шрифты из MKV Attachments →AssHandler.addFont),AssSubtitleView. AssRenderType:CUES(черезSubtitleView, bitmap-cues),OVERLAY_CANVAS,OVERLAY_OPEN_GL, deprecatedEFFECTS_CANVAS/EFFECTS_OPEN_GL.- Пользователи: Jellyfin Android TV, Fladder, NuvioTV, AfuseKt.
- Интеграция в jellyfin-androidtv (
playback/media3/exoplayer/src/main/kotlin/ExoPlayerBackend.kt):Включается пользовательской настройкойval assHandler = AssHandler(AssRenderType.OVERLAY_OPEN_GL) val parserFactory = AssSubtitleParserFactory(assHandler) val extractors = DefaultExtractorsFactory().withAssMkvSupport(parserFactory, assHandler) DefaultMediaSourceFactory(dataSourceFactory, extractors).setSubtitleParserFactory(parserFactory) val renderers = AssRenderersFactory(assHandler, DefaultRenderersFactory(context).apply { setEnableDecoderFallback(true); setExtensionRendererMode(ON / PREFER) }) // после build(): assHandler.init(player) // в SubtitleView: addView(AssSubtitleView(context, assHandler))assDirectPlay(ключlibass_enabled, по умолчаниюfalse). Без неё ASS/SSA в DeviceProfile получаетENCODE(burn-in на сервере). - Риски:
AssMatroskaExtractorнаследует внутренностиMatroskaExtractor(protected-методы), поэтому может ломаться при обновлениях media3. Производительность тяжёлых typesetting-субтитров на слабых ТВ-SoC не измерена.
7. Контейнеры и экстракторы
| Формат | Статус в media3 1.11.1 |
|---|---|
| MKV/WebM | Основной. Поддерживаются lacing (Xiph/EBML/fixed), header stripping (ContentCompAlgo = 3). zlib/bzlib/lzo (ContentCompAlgo 0/1/2) приводят к ParserException "ContentCompAlgo N not supported" (#2361, open). Шифрование только AES (WebM). Главы парсятся в Chapter metadata (1.11.0), но ordered chapters, linked segments (SegmentUID) и editions не реализованы (воспроизводится сегмент как есть). Треки после кластеров исправлены в 1.11.1 (#3377), треки в последнем кластере в #3250. ALAC, float64 PCM. V_MS/VFW/FOURCC распознаёт только DIVX, H263 и WVC1. Theora и RealVideo не поддерживаются. |
| MP4/MOV/fMP4 | Полный (DV, HDR10+, главы с 1.11, mfra-seek). TrueHD в fMP4 нет (#1519). |
| AVI | AviExtractor: H.264, MPEG-4 ASP (DivX/Xvid), MJPEG, MP42/MP43; аудио PCM, MP3, AAC, AC3, DTS. Исправлена потеря аудио без keyframe-флагов (1.11.0). Индексы и OpenDML у больших AVI могут работать с ограничениями (не проверено). |
| MPEG-TS (188 байт) | H.262/H.264/H.265, AAC/AC3/E-AC3/AC4/DTS/DTS-HD/DTS-UHD, DVB-subs. Нет TrueHD (0x83), LPCM BD (0x80), PGS (0x90), VC-1 (0xEA). |
| M2TS/MTS (192 байта, BDAV) | Не поддерживается: sniff требует sync-байт через каждые 188 байт. #608 закрыт как дубль ExoPlayer #1488: «isn't supported», PR приветствуется. |
| MPEG-PS/VOB | PsExtractor (MPEG-2 + AC3/MPA/LPCM?) работает для простых случаев; навигации DVD нет. |
| ISO, VIDEO_TS, BDMV | Не поддерживаются: нет UDF/ISO, IFO/MPLS, меню. Только через сервер: Jellyfin сам разбирает BDMV/VIDEO_TS и отдаёт поток (remux/transcode) (поведение 12.2 не проверено). |
| ASF/WMV | Экстрактора нет. Мейнтейнер в #3249 (2026-06): «WMV ... isn't supported as per CDD». Сторонние ASF-экстракторы есть в форках (mytv-android, FongMi), качество не оценено. |
| FLV, OGG, WAV, FLAC, MP3, AMR, AC3/AC4/ADTS, HEIF/AVIF/JPEG/PNG | есть. Ogg с несколькими треками не поддерживается (#2876). |
Вывод: в DeviceProfile для media3 не объявлять как direct play: asf, wmv, m2ts, iso, vob (кроме простого MPEG-PS), MKV с zlib-сжатием (сервер это не видит, нужен фолбэк по ошибке), а также кодеки без HW-декодера. При этом jellyfin-androidtv объявляет ASF, WMV, VOB, XVID, OGM в directPlayProfile, что выглядит как источник ошибок. Для нашего гибрида такие контейнеры правильнее маршрутизировать в libVLC/libmpv.
8. DeviceProfile в jellyfin-androidtv: что переиспользовать
Файлы: app/src/main/java/org/jellyfin/androidtv/util/profile/: deviceProfile.kt, MediaCodecCapabilitiesTest.kt, KnownDefects.kt, deviceProfileReport.kt, codec/{MediaCodecQuery, AvcCodecCapabilities, HevcCodecCapabilities, Av1CodecCapabilities, audioPassthrough}.kt. Сборка через org.jellyfin.sdk.model.deviceprofile.buildDeviceProfile { } из SDK (DSL: transcodingProfile, directPlayProfile, codecProfile { conditions; applyConditions }, subtitleProfile).
8.1 Опрос MediaCodec (MediaCodecQuery)
MediaCodecList(REGULAR_CODECS), только декодеры. ОпцияsoftwareCodecsEnabled: на API 29+ отбрасываютсяisSoftwareOnly.hasCodecForMime,hasDecoder(mime, profile, level)(поcapabilities.profileLevels, с особым случаем H.263 Level45),getDecoderLevel(максимальный level для профиля),getMaxResolution(videoCapabilities.supportedWidths/Heights.upper),supportsMultiInstance(maxSupportedInstances > 1).- AVC:
AVCProfileHigh10 @ Level4даёт поддержку «high 10». Уровни переводятся в ffprobe-шкалу (×10, 1b = 9) черезLEVEL_MAP. - HEVC:
Main10 @ MainTierLevel4;- HDR10 =
HEVCProfileMain10HDR10, HDR10+ =HEVCProfileMain10HDR10Plus(API 29+); - DV = есть хоть какой-то декодер
video/dolby-vision(API 24+); - DV с EL =
DolbyVisionProfileDvheDtb @ LevelHd24и multi-instance HEVC (для двухдекодерной схемы). - Уровни ×30 (4.1 → 123).
- AV1: Main10 @ Level5,
AV1ProfileMain10HDR10/HDR10Plus, DV P10 =DolbyVisionProfileDvav110. Для Fire OS, где AV1 есть ниже API Q, значения констант захардкожены. - VC-1:
hasCodecForMime(video/wvc1). - Аудио passthrough (
audioPassthrough.kt):AudioCapabilities.getCapabilities(context, AudioAttributes(USAGE_MEDIA, MOVIE), null, [STEREO, 5.1])→isPassthroughPlaybackSupported(Format(mime, 2ch)). РежимыBitstreamAudioMode.AUTO,ENABLE,DISABLEдля AC3, EAC3, DTS и TrueHD по отдельности.
8.2 Логика профиля
- Direct play:
- контейнеры asf, hls, m4v, mkv, mov, mp4, ogm, ogv, ts, vob, webm, wmv, xvid;
- видео av1, h264, hevc, mpeg, mpeg2video, vc1, vp8, vp9;
- аудио: список с фильтром по bitstream-настройкам; при «downmix to stereo» только aac, mp2, mp3.
- Transcoding:
- HLS-TS (hevc если есть, иначе h264; аудио aac/ac3/eac3/mp3);
- HLS-fMP4 (+alac/flac/opus/dts);
copyTimestamps=false,enableSubtitlesInManifest=true.
- CodecProfiles:
- H.264: допустимые профили; level ≤ max (отдельно для High10); ref frames ≤ 12 при ширине ≥ 1200 и ≤ 4 при ширине ≥ 1900. Это эвристика для слабых декодеров.
- HEVC: main и, если есть, main 10, плюс level.
- AV1:
notEquals "main 10", если нет Main10. - VC-1:
profile == "none", если нет декодера. - Max width/height для каждого кодека.
AUDIO_CHANNELS ≤ 8(или 2).
- HDR через
VideoRangeType: трюк сconditions { VIDEO_RANGE_TYPE notEquals "A|B|C" }плюсapplyConditions { VIDEO_RANGE_TYPE inCollection [...] }. Профиль применяется только к медиа с неподдерживаемым range type, а сервер берёт список из notEquals для StreamBuilder: может срезать DV и оставить HDR10 BL вместо полной транскодировки (механизм на сервере не проверен). Множества:- всегда
DOVI_INVALID; - без DV-EL (и не Shield-исключение) →
DOVI_WITH_EL(+DOVI_WITH_ELHDR10_PLUS); - без DV →
DOVI;DOVI_WITH_HDR10только если и HDR10 нет; - без HDR10+ →
HDR10_PLUS(+HDR10, если нет и его); - на Fire TV 4K (
AFTKRT,AFTKA,AFTKM,AFTMM) всегда исключатьDOVI_WITH_HDR10_PLUS/DOVI_WITH_ELHDR10_PLUS. - Пользовательские оверрайды по форматам (
HdrFormat: HDR10, HDR10+, DV P5, P7, P8) с режимами ENABLE/DISABLE/AUTO.
- всегда
- Субтитры:
- VTT/WebVTT: embed, external, hls;
- SRT/SubRip/TTML: embed, external;
- DVBSUB/DVDSUB/IDX: embed + encode;
- PGS: embed (если
pgsDirectPlay) + encode; - ASS/SSA: embed/external при libass, иначе encode.
- KnownDefects: всего 2 списка (Fire TV DV+HDR10+, Shield P7). Отдельной базы «плохих» устройств нет.
- Чего там нет, но нам нужно:
- Проверки, что дисплей поддерживает DV/HDR10/HDR10+ (
HdrCapabilities). Сейчас смотрят только декодер. media3 при этом сам уйдёт в фолбэк, а профиль может ошибочно разрешить P5 на не-DV ТВ. - Учёта P5 (без фолбэка).
- Маршрутизации на второй движок (VLC/mpv) для VC-1/WMV/M2TS/Hi10P и т.п.
- HDR10+ на VP9.
HLGиDOVIWithHLGне рассматриваются явно.
- Проверки, что дисплей поддерживает DV/HDR10/HDR10+ (
8.3 Прочее из ExoPlayerBackend jellyfin-androidtv
DefaultRenderersFactory.setEnableDecoderFallback(true).DefaultExtractorsFactory.setConstantBitrateSeekingEnabled(true)+AlwaysEnabled.TsExtractortimestamp search bytesTS_PACKET_SIZE * 1800на low-RAM устройствах.AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED,setAllowInvalidateSelectionsOnRendererCapabilitiesChange(true).PlaySupportReportизRendererCapabilities(формат, adaptive, tunneling, HW-accel, decoder support) дляsupportsStream(). Это готовый паттерн для выбора движка: если media3 не может, идём во второй движок.
9. Риски и рекомендации для проекта
- DV P7 (самые популярные remux): на всём, кроме Shield, P7 не даст DV. Без DV-декодера он не даст и картинки, пока не смержен PR #3160. Варианты: (а) свой
MediaCodecSelector, отдающий HEVC-декодеры дляvideo/dolby-visionс профилем DvheDtb, когда нет DV-дисплея или декодера; (б) DeviceProfile сDOVI_WITH_ELв unsupported, чтобы сервер срезал DV. - Гибриды DV + HDR10+ на MediaTek (Fire TV, Google TV Streamer): держать KnownDefects-список и опцию «предпочитать DV / HDR10+» (так сделано в Wholphin, судя по ссылке в #1895).
- FFmpeg-видео в media3 официально не готово. NextLib жизнеспособен, но даёт SDR 8-bit и CPU-нагрузку. Для HDR или экзотики при программном декоде нужен второй движок; HDR-вывод libmpv/libVLC на Android отдельно не исследован здесь (не проверено).
- Совместимость бинарных расширений:
org.jellyfin.media3:media3-ffmpeg-decoder:1.9.0+1иass-media(собран под 1.8.0) работают с 1.11.1, но это не гарантировано. Лучше собирать FFmpeg-аудио самим в CI под точную версию media3 или взять NextLib, у которого версия привязана к media3 (1.11.1-0.16.0). - Смена частоты и HDR: делать самим через
preferredDisplayModeId+DisplayListener, с паузой. Явно сбрасывать режим и освобождать Surface при выходе (см. #3406). - TrueHD: не просить HLS-fMP4 с TrueHD. Для 2.0 TrueHD программный декод FFmpeg сломан (#1843). Без passthrough лучше транскод в AC3/E-AC3 на сервере.
- Tunneling: опционально и по умолчанию выкл. Тестировать seek, паузу, субтитры и HDR на целевых устройствах.
- Контейнеры: M2TS, ASF/WMV, ISO, VIDEO_TS и MKV-zlib сразу отправлять во второй движок или на серверный remux.
Источники (проверено)
- androidx/media main @
8c6678b(1.11.1):MatroskaExtractor.java,MediaCodecUtil.java,MediaCodecVideoRenderer.java,VideoFrameReleaseHelper.java,AudioCapabilities.java,DefaultRenderersFactory.java,decoder_ffmpeg/*,decoder_av1/*,TsExtractor.java,avi/StreamFormatChunk.java,DefaultSubtitleParserFactory.java,RELEASENOTES.md. https://github.com/androidx/media - jellyfin-androidtv master @
0032f4a: https://github.com/jellyfin/jellyfin-androidtv/tree/master/app/src/main/java/org/jellyfin/androidtv/util/profile,playback/media3/exoplayer/src/main/kotlin/ExoPlayerBackend.kt,ui/playback/PlaybackController.java - https://github.com/jellyfin/jellyfin-androidx-media (
build.sh,.gitmodules); Maven: https://repo1.maven.org/maven2/org/jellyfin/media3/media3-ffmpeg-decoder/ - https://github.com/peerless2012/libass-android; Maven: https://repo1.maven.org/maven2/io/github/peerless2012/ass-media/
- https://github.com/anilbeesetti/nextlib; Maven: https://repo1.maven.org/maven2/io/github/anilbeesetti/nextlib-media3ext/
- https://github.com/moneytoo/Player (Just Player,
Utils.java,PlayerActivity.java) - Kodi: https://github.com/xbmc/xbmc/blob/master/xbmc/platform/android/activity/XBMCApp.cpp,
xbmc/windowing/android/AndroidUtils.cpp - https://developer.android.com/media/optimize/performance/frame-rate
- https://source.android.com/devices/tv/multimedia-tunneling
- Issues/PR: androidx/media #1865, #3160, #1895, #3085, #3406, #3461, #3463, #2541, #1574, #3099, #1245, #2258, #3120, #2941, #3234, #2487, #3229, #3147, #1519, #3411, #1843, #3329, #1591, #3249, #608, #2361, #2667, #2876, #2830, #3153, #3135; jellyfin-androidtv #5794, #5822, #5184.