You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 20:14:53