Skip to content

fix(jellyfin): 选中外挂 ASS 字幕导致播放失败 - #3301

Open
GrahamZen wants to merge 2 commits into
open-ani:mainfrom
GrahamZen:fix/jellyfin-subtitle-mime-types
Open

fix(jellyfin): 选中外挂 ASS 字幕导致播放失败#3301
GrahamZen wants to merge 2 commits into
open-ani:mainfrom
GrahamZen:fix/jellyfin-subtitle-mime-types

Conversation

@GrahamZen

@GrahamZen GrahamZen commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

问题

选中 Jellyfin 的外挂 ASS 字幕(MediaStreamsIsExternal == true 的流)会让播放直接失败:

androidx.media3.exoplayer.ExoPlaybackException: Unexpected runtime error
Caused by: java.lang.IllegalStateException: Legacy decoding is disabled,
    can't handle text/x-ass samples (expected application/x-media3-cues).
  at androidx.media3.exoplayer.text.TextRenderer.assertLegacyDecodingEnabledIfRequired
  at androidx.media3.exoplayer.text.TextRenderer.onStreamChanged

外挂字幕经由 MediaExtraFiles.subtitles 交给播放器,Android 端把 Subtitle.mimeType 原样喂给 ExoPlayer 的 MediaItem.SubtitleConfiguration.setMimeTypeLibassExoPlayerMediampPlayer.createLibassMediaSource)。而 BaseJellyfinMediaSource.getSubtitles 把 ASS 映射成了 "text/x-ass" —— 这不是 media3 认识的类型,两条解析路径都拒绝它:

  • DefaultSubtitleParserFactory.supportsFormat 的白名单里 ASS/SSA 对应的是 TEXT_SSA = "text/x-ssa",没有 text/x-assass-mediaAssSubtitleParserFactory.supportsFormat 直接委托给它,create 里也是 format.sampleMimeType == MimeTypes.TEXT_SSA 才走 libass。
  • 于是退回旧的 decoder 路径,TextRendereronStreamChanged 里抛出上面那个 IllegalStateException(media3 的源码注释写明这是故意让 App "aware via the playback failure")。

srt / vtt 则直接落进 application/octet-stream

内封字幕不受影响:它们由 MatroskaExtractor 按 codec id 填 Format.sampleMimeType,不经过这段代码 —— 所以这个问题只在媒体库里存在外挂字幕文件时才会暴露。

顺带两个小问题:

  • getSubtitleUri 用原始 Codec 拼字幕 URL 的扩展名(Stream.$codec),而 Jellyfin 是按这个扩展名决定输出格式的,服务器返回大写(如 "ASS")时就不可靠。
  • label = stream.Title,但 Title 是可空的。Jellyfin 对只有语言、没有标题的外挂字幕就不给 Title,此时字幕列表里显示的是 ExoPlayer 的 TrackGroup id(实测显示成 1:0)。

改动

  • ass/ssa -> text/x-ssasrt/subrip -> application/x-subripvtt/webvtt -> text/vtt。带上 subrip/webvtt 是因为 Jellyfin 的 Codec 字段透传的是 ffprobe 的 codec name,SRT 报的是 subrip 而不是 srt
  • Codec 先转小写再拼进字幕 URL。
  • label 在没有 Title 时依次退到 Language 和占位符。

验证

新增 JellyfinSubtitleStreamTest,3 个测试分别守 MIME 映射、URL 扩展名小写、label 兜底。响应字段照着仓库里的真实响应样本 datasource/jellyfin/sample-item.json 写,并额外放了一条 PGSSUB 图形字幕和一条音轨,确认它们仍被 IsTextSubtitleStream / Type 过滤掉。

做了对照实验(同一套测试,只换生产代码):

生产代码 结果
本 PR 的改动 13 passed
当前 main 原样(git checkout main -- BaseJellyfinMediaSource.kt 3 个新测试全部失败,原有 10 个仍通过

测试执行:./gradlew :datasource:jellyfin:desktopTest --rerun-tasks,13 个测试全过。

实机验证(Android TV + 真实 Jellyfin):给一集 mkv 旁边放一个外挂 .ass(Jellyfin 识别为 IsExternal: true / Codec: "ass",内容是几条带 \pos 定位、颜色、\frz 旋转和 \move 动画的特效行),在字幕列表里选中这条外挂轨:

构建 结果
上游正式包 播放立刻失败,logcat 就是开头那段 can't handle text/x-ass samples;该轨在列表里显示成 1:0
带本修复的构建(ass -> text/x-ssalabel 兜底) 正常播放,外挂字幕的定位、颜色与动画均正常渲染,该轨显示成 zho

未覆盖:srt / vtt 的映射没有实机验证(手上的服务器只有 ass 外挂字幕),是按 media3 MimeTypes 的常量对齐补的;桌面端未实机运行。

🤖 Generated with Claude Code

@GrahamZen GrahamZen changed the title fix(jellyfin): 外挂字幕的 MIME 类型不是播放器认识的值 fix(jellyfin): 选中外挂 ASS 字幕导致播放失败 Aug 13, 2026
外挂字幕经 MediaExtraFiles.subtitles 交给播放器, Android 端把 Subtitle.mimeType 原样
喂给 ExoPlayer 的 MediaItem.SubtitleConfiguration.setMimeType. 而 getSubtitles 把 ASS
映射成了 "text/x-ass", 这不是 media3 认识的类型: DefaultSubtitleParserFactory 的白名单里
ASS/SSA 对应 TEXT_SSA ("text/x-ssa"), ass-media 的 AssSubtitleParserFactory 也是判
sampleMimeType == TEXT_SSA 才走 libass. 于是退回旧 decoder 路径, TextRenderer 在
onStreamChanged 里抛 "Legacy decoding is disabled, can't handle text/x-ass samples"
—— 不是字幕不显示, 是整个播放挂掉. srt/vtt 则直接落进 application/octet-stream.

内封字幕不受影响 (由 MatroskaExtractor 按 codec id 填 sampleMimeType), 所以这个问题只在
媒体库里存在外挂字幕文件时才会暴露.

- ass/ssa -> text/x-ssa, srt/subrip -> application/x-subrip, vtt/webvtt -> text/vtt.
  Jellyfin 的 Codec 字段透传 ffprobe 的 codec name, 所以 SRT 报的是 "subrip" 而不是 "srt".
- Codec 先转小写再拼进字幕 URL 的扩展名.
- label 在没有 Title 时依次退到 Language 和占位符 —— Jellyfin 对只有语言、没有标题的外挂
  字幕不给 Title, 此时字幕列表里显示的是 ExoPlayer 的 TrackGroup id (实测显示成 "1:0").

三个回归测试分别守: MIME 映射、URL 扩展名小写、label 兜底.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@GrahamZen
GrahamZen force-pushed the fix/jellyfin-subtitle-mime-types branch from a205551 to 3070ef2 Compare August 13, 2026 21:04
@StageGuard

Copy link
Copy Markdown
Member

其他播放器呢,最少要再测试 mpv

@IT-BillDeng

Copy link
Copy Markdown
Contributor

已测试桌面端 mpv,主视频播放正常、未发现回归。由于当前 mpv 后端尚未消费 MediaExtraFiles.subtitles,本 PR 对 mpv 没有可观察的行为影响。
简单来说,桌面端 mpv 目前还不会读取这部分外挂字幕信息,所以这次改动不会改变它现有的播放行为。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants