Flutter SDK 更新后 video_player 在 Redmi 上黑屏或播放异常,如何排查?
现有日志还不足以确定根因,不能认定故障是 compileSdk 36 或 Flutter 3.27 的缺陷。可以保留现有 video_player 架构继续排查,也可以通过 Gradle 覆盖 Media3 的版本请求。建议先核对实际依赖版本,用单播放器排查资源占用和生命周期问题,再对比渲染路径和 Media3 版本。
日志能说明什么?
故障发生在更新 SDK 后,且仅出现在一台 Redmi 上。给出的日志主要记录了 Surface 的连接、断开和重新连接,以及 OMX.MTK.VIDEO.DECODER.AVC 的使用,没有直接说明缓冲区分配失败的原因。
日志涉及视频解码器,目前没有证据表明原问题中所说的 hardware encoder 存在缓冲区故障。Unsupported mime audio/x-adpcm-ms 等警告也不能单独证明当前视频使用了这些音频格式,或因此播放失败。
java.lang.IllegalArgumentException: start failed 需要结合完整堆栈判断。应保留首次失败前后的 Logcat,尤其是 PlaybackException、MediaCodec 错误及其 Caused by,不要只截取后续反复出现的 Surface 日志。
先确认到底更新了哪些依赖
compileSdk 决定编译时可用的 Android API;Media3 版本由依赖声明、传递依赖和 Gradle 解析规则决定。提高 compileSdk 本身不会自动选择更新的 Media3。Android 构建配置说明
video_player: ^2.11.1 也不是精确版本锁定。先记录 flutter --version,再检查 pubspec.lock 中实际使用的 video_player、video_player_android 和 video_player_platform_interface 版本。
在项目的 android 目录执行:
./gradlew :app:dependencyInsight --dependency androidx.media3 --configuration debugRuntimeClasspath
报告会显示最终选择的版本及其来源。如果项目使用 product flavors,应换成对应变体的 configuration;发布包还要检查 releaseRuntimeClasspath。Gradle 依赖诊断文档
尽量将当前结果与升级前的锁文件、构建配置和依赖报告比较。video_player_android 的更新记录包含独立的 Media3 版本升级,因此除了查看顶层 video_player 的声明,还要核对 Android 实现包。插件更新记录
用单播放器区分设备兼容与列表资源问题
当前实现将多个 VideoPlayerController 保存在列表中,可以先从这里查起,但这本身不能证明存在资源泄漏。
先建立一个最小页面,只创建一个 controller 播放同一个故障视频,不预加载其他视频。等待 initialize() 完成后再显示和播放,并记录初始化异常与播放期间的错误:
final controller = VideoPlayerController.networkUrl(videoUri);
void onPlayerChanged() {
final value = controller.value;
if (value.hasError) {
debugPrint('Video error: ${value.errorDescription}');
}
}
controller.addListener(onPlayerChanged);
try {
await controller.initialize();
await controller.play();
} catch (error, stackTrace) {
debugPrint('Video initialization failed: $error');
debugPrintStack(stackTrace: stackTrace);
}
示例假定 videoUri 是已有的 Uri,controller 由页面生命周期统一管理。页面退出时要移除监听并调用 dispose(),还要处理异步初始化完成前页面已经退出的情况。
如果单播放器正常而信息流异常,重点检查:
- 是否为所有列表项提前初始化 controller,导致同时持有过多解码器和 Surface。
- 是否在
build()中重复创建 controller,或重复初始化同一实例。 - 列表增删、排序或滚动后,widget 是否绑定到错误的 controller;可以使用稳定的业务
Key。 - 不再需要的 controller 是否执行了
dispose()。仅调用pause()不等于释放播放器资源。
如果单播放器仍然失败,可以将同一视频文件下载到设备,使用 VideoPlayerController.file 对比。这有助于区分网络传输与本地解码、渲染路径的问题,但仍需确认文件内容一致。
能否通过 controller 切换渲染路径?
video_player 2.11.1 的源码提供了 viewType 参数,默认使用 VideoViewType.textureView。可以创建新的 controller,测试另一条显示路径:
final controller = VideoPlayerController.networkUrl(
videoUri,
viewType: VideoViewType.platformView,
);
参数名是 viewType。2.11.1 版本源码
这个测试可用于对照诊断。如果在同一设备、同一视频、单 controller 的条件下,只有 Texture 路径失败,可以重点排查 Flutter 与插件的 Surface 交互。切换渲染路径不会自动切换为软件解码。
video_player_android 文档列出了 Android platform views 的已知问题,当前并不推荐直接使用该模式。即使测试有效,也应验证实际列表中的滚动、布局和页面切换表现,再决定是否将其作为设备兼容方案。Android 实现包说明
如何指定较旧的 Media3 版本?
可以覆盖 Gradle 的版本请求,建议先用于回归验证。优先选择升级前实际使用过的版本;没有适用于所有 Redmi 设备的通用旧版本。
以下 Groovy 代码放在根级 android/build.gradle,通过 Gradle 属性传入测试版本,并应用到各子项目:
def media3VersionForTest = findProperty('media3VersionForTest')
if (media3VersionForTest != null) {
subprojects {
configurations.configureEach {
resolutionStrategy.eachDependency { details ->
if (details.requested.group == 'androidx.media3') {
details.useVersion(media3VersionForTest.toString())
details.because('Compare Media3 versions during playback diagnosis')
}
}
}
}
}
在 android/gradle.properties 中设置 media3VersionForTest=旧构建实际使用的版本号,将中文说明替换为真实版本号后再构建。使用 .gradle.kts 的项目需要改写成 Kotlin DSL,不能直接粘贴该片段。
应将所用 Media3 模块对齐到兼容的同一版本。修改后重新运行 dependencyInsight,确认最终解析结果。Dart 的 dependency_overrides 不能直接锁定 Maven 中的 androidx.media3。Gradle 版本解析规则
旧版 Media3 可能缺少当前插件调用的 API,导致编译错误或 NoSuchMethodError。遇到这种情况,可以评估与该 Media3 版本配套的 video_player_android,或维护兼容的插件分支。保持 compileSdk 36 的同时测试较旧版本的 Media3 并不天然冲突,但所有依赖仍须满足各自的编译要求。
能否绕过故障硬件解码器?
针对题目给出的 video_player 2.11.1 接口,没有可直接传给 VideoPlayerController 的通用参数来“强制软件解码”或“排除某个 MediaCodec”。对应版本源码
如果完整堆栈确认是 decoder 初始化失败,可以在 Android 插件层评估 ExoPlayer 的 decoder fallback。例如,在创建 ExoPlayer 的位置接入:
val renderersFactory = DefaultRenderersFactory(context)
.setEnableDecoderFallback(true)
val player = ExoPlayer.Builder(context, renderersFactory)
.build()
这属于插件内部修改,需要接入现有播放器创建和释放流程。setEnableDecoderFallback(true) 的作用是在解码器初始化失败时尝试优先级较低的候选解码器;它不保证使用软件解码,也不保证修复播放开始后的花屏或 Surface 故障。DefaultRenderersFactory API
如果进一步确认某个设备上的特定 decoder 有问题,可以评估原生 MediaCodecSelector。使用时应依据设备、系统版本和实际 decoder 日志,不能仅凭 Redmi 品牌统一禁用硬件解码。
是否与 Flutter 3.27 有关?
Flutter 官方记录了 Android Surface 插件 API 的迁移,说明 Flutter 3.27 引入了相关回调,以及裁剪、旋转处理的变化。这条路径值得排查,但这些记录不能证明它导致了当前故障。Flutter Surface API 迁移说明
题目没有给出实际 Flutter 版本号,不能直接把“最新 stable”当作 3.27。验证时应固定视频文件和依赖,对比单 controller 与列表播放、Texture 与 platform view,以及新旧 Media3。若要对比 Flutter SDK,还需先确认同一组插件支持两个 SDK,避免同时改变多个变量。
最终修复需要根据实际对照结果确定,可能涉及 controller 生命周期、插件版本、Surface 路径或设备 decoder 兼容层。现阶段先取得首次失败的完整异常堆栈,以及升级前后的依赖差异。
备注:内容仅供参考。
