Flutter视频循环APK在安卓平板异常:播放与循环故障求助
问题概述
我用Flutter开发了一款含4个视频循环播放的APK,在3台Android 13设备上表现不一致:
- 模拟器(Google Nexus 7、Pixel 3):运行完全正常
- 三星Galaxy S20:运行完全正常
- 三星Galaxy Tab A:4个视频可正常播放,但无法循环
- 联想Tab M10 Plus:仅能播放2个视频,且无法循环
视频播放组件代码
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; class SlideShowApp extends StatefulWidget { const SlideShowApp({Key? key, required this.mediaUrls, required widget.onSlideshowEnd}) : super(key: key); final List<String> mediaUrls; final VoidCallback onSlideshowEnd; @override _SlideShowAppState createState() => _SlideShowAppState(); } class _SlideShowAppState extends State<SlideShowApp> { late dynamic _currentMedia; late VideoPlayerController _videoPlayerController; late Future<void> _initializeVideoPlayerFuture; int currentIndex = 0; Timer? _imageTimer; @override void initState() { super.initState(); _currentMedia = _initializeMedia(widget.mediaUrls[currentIndex]); } dynamic _initializeMedia(String url) { String fileType = getFileTypeFromUrl(url); print('filetype: $fileType'); if (fileType == 'video') { print('Video is in: $url'); _videoPlayerController = VideoPlayerController.networkUrl(Uri.parse(url)) ..initialize().then((_) { _videoPlayerController.play(); setState(() {}); }) ..addListener(() { int duration = _videoPlayerController.value.duration.inSeconds ~/ 4; print('duration: $duration'); // Set up the timer here for videos _imageTimer?.cancel(); _imageTimer = Timer(Duration(seconds: duration), () { _showNextMedia(); }); }); return AspectRatio( aspectRatio: _videoPlayerController.value.aspectRatio, child: VideoPlayer(_videoPlayerController), ); } else if (fileType == 'image') { print('Image is in: $url'); _imageTimer?.cancel(); _imageTimer = Timer(const Duration(seconds: 5), () { print('In Timer'); _showNextMedia(); }); // Display image directly return Image.network( url, width: double.infinity, height: double.infinity, fit: BoxFit.cover, ); } else { return const SizedBox.shrink(); // Unknown file type, display nothing } } void _showNextMedia() { // Dispose the current media controller if (_currentMedia is VideoPlayerController) { (_currentMedia as VideoPlayerController).dispose(); } // Move to the next media currentIndex = currentIndex + 1; print('currentIndex: $currentIndex'); // Check if the end of the list is reached if (currentIndex < widget.mediaUrls.length) { // Cancel the image timer if it exists _imageTimer?.cancel(); // Initialize and show the next media setState(() { _currentMedia = _initializeMedia(widget.mediaUrls[currentIndex]); }); } else { // Slideshow has reached the end, you can handle this as needed widget.onSlideshowEnd(); Navigator.pop(context); print('Slideshow reached the end.'); } } @override void dispose() { if (_currentMedia is VideoPlayerController) { (_currentMedia as VideoPlayerController).dispose(); } _imageTimer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return FutureBuilder( future: _currentMedia is VideoPlayerController ? _initializeVideoPlayerFuture : Future.value(null), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { return _currentMedia; } else { return const Center( child: CircularProgressIndicator(), ); } }, ); } } String getFileTypeFromUrl(String url) { String extension = url.split('.').last.toLowerCase(); if (extension == 'mp4') { return 'video'; } else if (extension == 'jpg' || extension == 'jpeg') { return 'image'; } return 'unknown'; }
Logcat错误信息
[OMX.qcom.video.decoder.avc] configureCodec returning error -12 signalError(omxError 0x80001001, internalError -12) Codec reported err 0xfffffff4/NO_MEMORY, actionCode 0, while in state 0/UNINITIALIZED Video codec error c0.z$b: Decoder init failed: OMX.qcom.video.decoder.avc, Format(1, null, null, video/avc, avc1.640028, -1, null, [1080, 1920, 30.0], [-1, -1]) Caused by: android.media.MediaCodec$CodecException: Error 0xfffffff4 Playback error l.t: MediaCodecVideoRenderer error, index=0, format=Format(1, null, null, video/avc, avc1.640028, -1, null, [1080, 1920, 30.0], [-1, -1]), format_supported=YES Caused by: c0.z$b: Decoder init failed: OMX.qcom.video.decoder.avc, Format(1, null, null, video/avc, avc1.640028, -1, null, [1080, 1920, 30.0], [-1, -1]) [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(VideoError, Video player had error l.t: MediaCodecVideoRenderer error, index=0, format=Format(1, null, null, video/avc, avc1.640028, -1, null, [1080, 1920, 30.0], [-1, -1]), format_supported=YES, null, null)
排查与修复建议
1. 内存泄漏问题(对应联想Tab M10 Plus的NO_MEMORY错误)
Logcat中的NO_MEMORY错误表明设备解码器内存耗尽,核心问题是视频控制器释放逻辑错误:
- 当前代码中
_showNextMedia里判断_currentMedia is VideoPlayerController完全错误,因为_currentMedia实际是AspectRatio组件,并非控制器实例,导致旧的_videoPlayerController从未被正确释放,多次播放后耗尽解码器内存。 - 修复:新增
VideoPlayerController? _activeVideoController变量,初始化视频时将控制器赋值给该变量,切换视频时先调用_activeVideoController?.dispose()释放资源,再创建新控制器并更新该变量。
2. 视频循环与切换逻辑错误(对应三星Tab A的无法循环)
- 现有代码在
currentIndex等于列表长度时直接结束播放,未重置索引实现循环,需修改索引更新逻辑:
移除原逻辑中调用currentIndex = (currentIndex + 1) % widget.mediaUrls.length;onSlideshowEnd和Navigator.pop的结束代码,或在currentIndex >= widget.mediaUrls.length时将索引重置为0。 - 视频切换依赖
duration ~/4的Timer逻辑错误,应监听视频播放状态,当_videoPlayerController.value.position >= _videoPlayerController.value.duration时自动切换,而非用固定比例的Timer,避免提前切换导致循环失效。
3. 控制器初始化与FutureBuilder不匹配
代码中_initializeVideoPlayerFuture从未被赋值,导致FutureBuilder逻辑失效,可能引发UI渲染异常。需在初始化视频控制器时,将_initializeVideoPlayerFuture赋值为_videoPlayerController.initialize()。
4. 设备兼容性优化
- 联想Tab M10 Plus和三星Tab A硬件解码器性能较弱,建议降低视频分辨率(如从1080P降至720P)或调整视频编码参数,确保设备解码器能稳定处理。
- 增加错误捕获逻辑,当视频初始化失败时自动跳过该视频或重试,避免应用崩溃。
内容的提问来源于stack exchange,提问作者PLWO
相关产品推荐
相关产品推荐

