Skip to content

Fix: 旧版 libmpv(armeabi-v7a Android TV)播放含独立音频流的视频时黑屏无声音#71

Draft
TakChen wants to merge 7 commits intoStarfallan:mainfrom
TakChen:main
Draft

Fix: 旧版 libmpv(armeabi-v7a Android TV)播放含独立音频流的视频时黑屏无声音#71
TakChen wants to merge 7 commits intoStarfallan:mainfrom
TakChen:main

Conversation

@TakChen
Copy link
Copy Markdown

@TakChen TakChen commented Apr 10, 2026

关联 Issue

closes #70

问题背景

在搭载 Hi3751V660 芯片(armeabi-v7a)的 Android TV 设备上,使用设备内置旧版 libmpv 播放含独立音频流的视频时,视频画面正常但完全无声音。PGC 影视内容(音视频合并文件)不受影响。

根因

经过三轮调试,定位到三个叠加的兼容性问题:

根因 1:旧版 mpv 不支持 loadfile 的 options 参数

media_kitplayer.open(Media(url, extras: {...})) 在底层调用:

loadfile <url> replace 0 audio-files="https\://..."

旧版 libmpv 无法解析 loadfile 的第四个 options 字符串参数,整个命令失败,视频和音频均无法加载。

根因 2:loadfile replace 会清除预设的 audio-files 属性

即使跳过 extras,在 player.open() 前通过 change-list audio-files set 预设的值也会在 loadfile replace 执行时被清除,导致 pre-open 设置完全失效。

根因 3:change-list audio-files set <url> 中 URL 的 : 未转义

旧版 mpv 将选项值中的 : 解析为 key-value 分隔符,https://... 被错误分割,导致:

Cannot open file 'https': No such file or directory

修复方案

修改文件:lib/plugin/pl_player/controller.dart

1. Android 上跳过 extras,改用 change-list 命令

// 仅非 Android 平台通过 extras 传递 audio-files(Android 旧版 mpv 不支持 loadfile options)
if (!Platform.isAndroid) {
  extras['audio-files'] = '"$escapedAudio"';
}
// 所有平台均通过 change-list pre-open 设置(Android 主路径,非 Android 被 extras 覆盖)
await player.command(['change-list', 'audio-files', 'clr', '']);
await player.command(['change-list', 'audio-files', 'set', escapedAudio]);

2. Android 上 player.open() 之后补设 audio-files(post-open)

// 旧版 mpv 的 loadfile replace 会清除 audio-files,open() 后重新设置
if (Platform.isAndroid) {
  if (dataSource.audioSource case final audio? when (audio.isNotEmpty && !onlyPlayAudio.value)) {
    final ea = audio.replaceAll(':', r'\:');
    await player.command(['change-list', 'audio-files', 'clr', '']);
    await player.command(['change-list', 'audio-files', 'set', ea]);
  }
}

同样的 post-open 补设逻辑也应用于 refreshPlayer()

3. URL 中 : 转义为 \:

所有 change-list audio-files set 调用均使用 audio.replaceAll(':', r'\:') 处理 URL。

4. 恢复 lavfi-complex 的全平台无条件执行

移除之前为诊断目的误加的 kDebugMode || Platform.isAndroid 限制,lavfi-complex 恢复为所有平台正常处理。Android 上同样跳过 extras 传递。

平台兼容性

平台 audio-files 传递方式 说明
Android(旧版 mpv) change-list pre + post 不传 extras,避免 loadfile 失败;post-open 补设防清除
Android(新版 mpv) change-list pre + post 向后兼容,新版同样支持 change-list
iOS / macOS / Linux extras + change-list pre extrasloadfile 生效,行为不变
Windows extras; 转义)+ change-list pre Windows 下 ; 为列表分隔符,行为不变

测试

  • armeabi-v7a Android TV(Hi3751V660,Android 12):独立音频流视频播放有声音 ✅
  • 普通 Android 设备:视频正常播放,无回归 ✅
  • PGC 影视内容(合并音视频):播放正常,无回归 ✅

TakChen and others added 7 commits April 9, 2026 15:38
移除 kDebugMode/Platform.isAndroid 的条件限制,使音频标准化在所有平台生效<br>
精简旧版 mpv 兼容性错误处理,去除冗余的错误上报与调试日志<br>
更新注释以准确描述 change-list 规避方案
移除对旧版 libmpv 设备(如部分 Android TV armeabi-v7a)产生的兼容性错误日志的过滤代码,包括 loadfile options 参数解析错误及 invalid parameter 错误的特殊处理,统一交由错误上报流程处理
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 针对部分 Android TV(armeabi-v7a)设备内置旧版 libmpv 在播放“视频 + 独立音频流”场景下无声音的问题,调整了向 mpv 传递 audio-files/lavfi-complex 的方式以兼容旧版 loadfile 行为。

Changes:

  • Android 平台跳过通过 extras 传递 audio-files,改为使用 change-list,并在 open() 后再补设一次以对抗旧版 loadfile replace 清除属性的问题
  • change-list audio-files set 的 URL 冒号统一转义为 \:(针对旧版 mpv 的解析兼容)
  • lavfi-complex 恢复为不再受 kDebugMode || Platform.isAndroid 限制,并新增通过命令 set lavfi-complex 的设置路径

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +927 to +938
final escapedAudio = Platform.isWindows
? audio.replaceAll(';', r'\;')
: audio.replaceAll(':', r'\:');
// Android 上旧版 libmpv(如部分 Android TV armeabi-v7a 设备)不支持 loadfile 的 options 参数,
// 传入 extras 会导致整个 loadfile 命令失败(视频和音频都无法加载)。
// 因此 Android 上不使用 extras 传递 audio-files,改为通过 change-list 命令方式(pre-open + post-open 双重保障)。
// 注意:change-list 对 audio-files 属性值的 URL 中的冒号同样需要转义(旧版 mpv 会将冒号解析为 key:value 分隔符)
if (!Platform.isAndroid) {
extras['audio-files'] = '"$escapedAudio"';
}
await player.command(['change-list', 'audio-files', 'clr', '']);
await player.command(['change-list', 'audio-files', 'set', escapedAudio]);
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change-list audio-files is executed before player.open() on all platforms (including when a previous media is still loaded/paused). This mutates the player's current state and can have side effects (e.g., applying external audio to the currently loaded item) while it’s redundant on non-Android where extras['audio-files'] is still used. Consider limiting the pre-open change-list path to Android only, and/or moving the change-list calls to post-open() where you already re-apply them for Android.

Copilot uses AI. Check for mistakes.
Comment on lines +962 to 968
if (audioNormalization.isNotEmpty) {
// Android 上不使用 extras 传递 lavfi-complex,避免旧版 mpv 的 loadfile options 参数导致整体失败
if (!Platform.isAndroid) {
extras['lavfi-complex'] = '"[aid1] $audioNormalization [ao]"';
}
await player.command(['set', 'lavfi-complex', '[aid1] $audioNormalization [ao]']);
}
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lavfi-complex is now set via player.command(['set', ...]) before player.open(). This changes the player’s state for the currently loaded (paused) media and is also redundant on non-Android where the same option is still passed via extras. To avoid unintended cross-media effects, consider: (1) using extras only on non-Android (no pre-open set), and (2) applying lavfi-complex via command only on Android (ideally post-open() if loadfile replace resets it similarly to audio-files).

Copilot uses AI. Check for mistakes.
Comment on lines +983 to +988
if (Platform.isAndroid) {
if (dataSource.audioSource case final audio? when (audio.isNotEmpty && !onlyPlayAudio.value)) {
final ea = audio.replaceAll(':', r'\:');
await player.command(['change-list', 'audio-files', 'clr', '']);
await player.command(['change-list', 'audio-files', 'set', ea]);
}
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Android post-open() re-apply logic for audio-files is duplicated here and in refreshPlayer(). Consider extracting a small helper (e.g., _reapplyExternalAudioFilesIfNeeded(player)) to keep the escaping and conditions consistent and reduce the chance of future divergence.

Copilot uses AI. Check for mistakes.
@Starfallan
Copy link
Copy Markdown
Owner

这个问题可以在上游的PiliPlus复现吗?建议向上游提PR,让dom佬看看。。。(

@Starfallan
Copy link
Copy Markdown
Owner

Starfallan commented Apr 10, 2026

说实话,对于libmpv这块不是很熟。而且版本控制也会方便点,省的又要合并重复pr

@YujiaCheng1996
Copy link
Copy Markdown

YujiaCheng1996 commented Apr 11, 2026

这个问题可以在上游的PiliPlus复现吗?建议向上游提PR,让dom佬看看。。。(

cnctem#31 (comment)

@TakChen TakChen changed the title Fix: 旧版 libmpv(armeabi-v7a Android TV)播放含独立音频流的视频时无声音 Fix: 旧版 libmpv(armeabi-v7a Android TV)播放含独立音频流的视频时黑屏无声音 Apr 13, 2026
@YujiaCheng1996
Copy link
Copy Markdown

@TakChen 大佬合并下提交给上游提个PR吧

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.

[Bug] armeabi-v7a Android TV(旧版 libmpv)播放含独立音频流的视频时黑屏无声音

4 participants