Flutter中视频缩略图渲染速度过慢的优化求助
本地视频缩略图加载缓慢问题
我用Flutter的VideoPlayer实现视频缩略图展示,代码如下:
class VideoPlayerWidget extends Stateful Widget { VideoPlayerWidget({ Key? key, required this.path, required this.sourceType, this.height, this.width, }) : super(key: key); final String path; final SourceType sourceType; final double? height; final double? width; @override State<VideoPlayerWidget> createState() => _VideoPlayerWidgetState(); } class _VideoPlayerWidgetState extends State<VideoPlayerWidget> { late VideoPlayerController _videoPlayerController; @override void initState() { switch (widget.sourceType) { case SourceType.asset: _videoPlayerController = VideoPlayerController.asset(widget.path); break; case SourceType.network: _videoPlayerController = VideoPlayerController.network(widget.path); break; case SourceType.file: _videoPlayerController = VideoPlayerController.file(File(widget.path)); break; case SourceType.contentUri: _videoPlayerController = VideoPlayerController.contentUri(Uri.parse(widget.path)); break; } _videoPlayerController.initialize().then((value) => setState((){})); super.initState(); } @override void dispose() { _videoPlayerController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { if ( !_videoPlayerController.value.isInitialized) { return Skeleton( height: widget.height ?? 0, width: widget.width ?? 0, ); } return GestureDetector( onTap: () { ChewiePlayingScreen(videoPlayerController: _videoPlayerController,).launch(context); }, child: Stack( children: [ SizedBox( height: widget.height, width: widget.width, child: _videoPlayerController.value.isInitialized ? VideoPlayer(_videoPlayerController) : Offstage(), ), Positioned.fill( child: Align( alignment: Alignment.center, child: Icon( Icons.play_circle_outline, color: SVAppWhite, size: 35, ), ), ) ], ), ); } } class ChewiePlayingScreen extends Stateful Widget { const ChewiePlayingScreen({Key? key, required this.videoPlayerController}) : super(key: key); final VideoPlayerController videoPlayerController; @override State<ChewiePlayingScreen> createState() => _ChewiePlayingScreenState(); } class _ChewiePlayingScreenState extends State<ChewiePlayingScreen> { late ChewieController chewieController; @override void initState() { chewieController = ChewieController( videoPlayerController: widget.videoPlayerController, aspectRatio: widget.videoPlayerController.value.aspectRatio, ); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( body: AspectRatio( aspectRatio: widget.videoPlayerController.value.aspectRatio, child: Chewie( controller: chewieController, ), ), ); } }
相机录制的视频缩略图加载正常,但通过ImagePicker获取的本地3MB视频,缩略图加载耗时长达20秒。
解决方案
1. 使用专门的缩略图提取包(推荐)
VideoPlayerController初始化会加载视频的完整元数据甚至部分流,对于非系统相机录制的视频(比如ImagePicker处理过的)效率极低。改用thumbnail包直接提取视频第一帧,速度提升明显。
步骤:
- 添加依赖到
pubspec.yaml:
dependencies: thumbnail: ^1.0.0
- 修改
_VideoPlayerWidgetState,替换原有的VideoPlayerController逻辑:
class _VideoPlayerWidgetState extends State<VideoPlayerWidget> { Uint8List? _thumbnailBytes; bool _isLoading = true; @override void initState() { _generateThumbnail(); super.initState(); } Future<void> _generateThumbnail() async { try { Uint8List bytes; switch (widget.sourceType) { case SourceType.asset: final assetPath = await DefaultAssetBundle.of(context).load(widget.path); bytes = await Thumbnail.image( image: assetPath.buffer.asUint8List(), height: widget.height?.toInt() ?? 200, width: widget.width?.toInt() ?? 200, ); break; case SourceType.file: bytes = await Thumbnail.file( file: File(widget.path), height: widget.height?.toInt() ?? 200, width: widget.width?.toInt() ?? 200, ); break; case SourceType.network: bytes = await Thumbnail.network( widget.path, height: widget.height?.toInt() ?? 200, width: widget.width?.toInt() ?? 200, ); break; case SourceType.contentUri: final file = await _contentUriToFile(Uri.parse(widget.path)); bytes = await Thumbnail.file( file: file, height: widget.height?.toInt() ?? 200, width: widget.width?.toInt() ?? 200, ); break; } setState(() { _thumbnailBytes = bytes; _isLoading = false; }); } catch (e) { setState(() => _isLoading = false); } } // 辅助方法:将contentUri转为File Future<File> _contentUriToFile(Uri uri) async { final file = File('${(await getTemporaryDirectory()).path}/${DateTime.now().millisecondsSinceEpoch}.tmp'); final inputStream = await context.contentResolver.openInputStream(uri); final outputStream = file.openWrite(); await inputStream?.pipe(outputStream); await inputStream?.close(); await outputStream.close(); return file; } @override Widget build(BuildContext context) { if (_isLoading) { return Skeleton( height: widget.height ?? 0, width: widget.width ?? 0, ); } return GestureDetector( onTap: () { // 点击后再初始化VideoPlayerController进入播放页面 late VideoPlayerController controller; switch (widget.sourceType) { case SourceType.asset: controller = VideoPlayerController.asset(widget.path); break; case SourceType.network: controller = VideoPlayerController.network(widget.path); break; case SourceType.file: controller = VideoPlayerController.file(File(widget.path)); break; case SourceType.contentUri: controller = VideoPlayerController.contentUri(Uri.parse(widget.path)); break; } controller.initialize().then((_) { ChewiePlayingScreen(videoPlayerController: controller).launch(context); }); }, child: Stack( children: [ if (_thumbnailBytes != null) SizedBox( height: widget.height, width: widget.width, child: Image.memory( _thumbnailBytes!, fit: BoxFit.cover, ), ), Positioned.fill( child: Align( alignment: Alignment.center, child: Icon( Icons.play_circle_outline, color: SVAppWhite, size: 35, ), ), ) ], ), ); } }
2. 优化VideoPlayerController初始化(备选)
如果不想引入第三方包,可以尝试限制VideoPlayerController只加载元数据,避免预加载视频流:
_videoPlayerController = VideoPlayerController.file(File(widget.path)) ..setLooping(false) ..setVolume(0) ..initialize().then((_) { setState(() {}); // 只需要第一帧,初始化后立即暂停 _videoPlayerController.pause(); });
但这种方法对ImagePicker获取的视频提升有限,不如专门的缩略图包效果显著。
内容的提问来源于stack exchange,提问作者mbakabilal
相关产品推荐
相关产品推荐

