行业知识
flutter开发实战-实现获取视频的缩略图封面video
2026-11-02 15:45:14
来源:互联网转载
flutter开发实���-实现获取视频的缩略图封面video_thumbnail
在很多时候,我们查看视频的时候,视频没有播放时候,会显示一张封面,可能封面没有配置图片,这时候就需要通过获取视频的缩略图来显示封面了。这里使用了video_thumbnail来实现获取视频的缩略图。
一、引入video_thumbnail
在工程的pubspec.yaml中引入插件
# 视频缩略图 video_thumbnail: ^0.5.3
VideoThumbnail的属性如下
static Future thumbnailFile( {required String video, Map? headers, String? thumbnailPath, ImageFormat imageFormat = ImageFormat.PNG, int maxHeight = 0, int maxWidth = 0, int timeMs = 0, int quality = 10}) - thumbnailPath为本地存储的文件目录
- imageFormat格式 jpg,png等
- video视频地址
- timeMs
二、获取视频的缩略图
使用video_thumbnail来获取视频缩略图
定义视频缩略图信息
class VideoThumbInfo { String url; // 原视频地址 File? thumbFile; // 缩略图本地file int? width; // 缩略图的width int? height; // 缩略图的height VideoThumbInfo({ required this.url, });}获取视频缩略图本地File
String path = (await getTemporaryDirectory()).path; String thumbnailPath = path + "/${DateTime.now().millisecond}.jpg"; final fileName = await VideoThumbnail.thumbnailFile( video: "https://vd2.bdstatic.com/mda-maif0tt1rirqp27q/540p/h264_cae/1611052585/mda-maif0tt1rirqp27q.mp4", thumbnailPath: thumbnailPath, imageFormat: imageFormat, quality: quality, maxWidth: maxWidth, maxHeight: maxHeight, timeMs: timeMs, ); File file = File(thumbnailPath);获取缩略图的宽高
Image image = Image.file(thumbFile!); image.image.resolve(const ImageConfiguration()).addListener( ImageStreamListener( (ImageInfo imageInfo, bool synchronousCall) { int imageWidth = imageInfo.image.width; int imageHeight = imageInfo.image.height; VideoThumbInfo videoThumbInfo = VideoThumbInfo(url: url); videoThumbInfo.thumbFile = thumbFile; videoThumbInfo.width = imageWidth; videoThumbInfo.height = imageHeight; VideoThumb.setThumbInfo(url, videoThumbInfo); onVideoThumbInfoListener(videoThumbInfo); }, onError: (exception, stackTrace) { print( "getVideoThumbInfoByFile imageStreamListener onError exception:${exception.toString()},stackTrace:${stackTrace}"); onVideoThumbInfoListener(null); }, ), );完整代码如下
import 'dart:io';import 'package:flutter/material.dart';import 'package:path_provider/path_provider.dart';import 'package:video_thumbnail/video_thumbnail.dart';// ignore: non_constant_identifier_namesVideoThumbManager get VideoThumb => VideoThumbManager.instance;class VideoThumbManager { static VideoThumbManager get instance { return _singleton; } //保存单例 static VideoThumbManager _singleton = VideoThumbManager._internal(); //工厂构造函数 factory VideoThumbManager() => _singleton; //私有构造函数 VideoThumbManager._internal(); // 保存url对应的本地缩略图file final _thumbMap = Map(); // url对应本地缩略图的信息 final _thumbInfoMap = Map(); Future setThumb(String url, File file) async { if (url.isEmpty) { return; } bool exist = await file.exists(); if (exist == false) { return; } _thumbMap[url] = file; } Future getThumb( String url, { ImageFormat imageFormat = ImageFormat.JPEG, int maxHeight = 0, int maxWidth = 0, int timeMs = 0, int quality = 100, }) async { File? thumbFile = _thumbMap[url]; if (thumbFile != null) { return thumbFile; } String path = (await getTemporaryDirectory()).path; String thumbnailPath = path + "/${DateTime.now().millisecond}.jpg"; final fileName = await VideoThumbnail.thumbnailFile( video: "https://vd2.bdstatic.com/mda-maif0tt1rirqp27q/540p/h264_cae/1611052585/mda-maif0tt1rirqp27q.mp4", thumbnailPath: thumbnailPath, imageFormat: imageFormat, quality: quality, maxWidth: maxWidth, maxHeight: maxHeight, timeMs: timeMs, ); File file = File(thumbnailPath); setThumb(url, file); return file; } // 获取缩略图的大小 void getVideoThumbInfo( String url, { ImageFormat imageFormat = ImageFormat.JPEG, int maxHeight = 0, int maxWidth = 0, int timeMs = 0, int quality = 100, required Function(VideoThumbInfo?) onVideoThumbInfoListener, }) async { try { VideoThumbInfo? thumbInfo = VideoThumb.getThumbInfo(url); if (thumbInfo != null) { onVideoThumbInfoListener(thumbInfo); return; } await VideoThumb.getThumb( url, imageFormat: imageFormat, maxWidth: maxWidth, maxHeight: maxHeight, timeMs: timeMs, quality: quality, ).then((value) { File? thumbFile = value; if (thumbFile != null) { VideoThumb.getVideoThumbInfoByFile( url: url, thumbFile: thumbFile, onVideoThumbInfoListener: onVideoThumbInfoListener, ); } else { onVideoThumbInfoListener(null); } }).onError((error, stackTrace) { print("getVideoThumbInfo error:${error.toString()}"); onVideoThumbInfoListener(null); }).whenComplete(() { print("getVideoThumbInfo whenComplete"); }); } catch (e) { print("getVideoThumbInfo catch error:${e.toString()}"); onVideoThumbInfoListener(null); } } /// 根据file获取缩略图信息 void getVideoThumbInfoByFile({ required String url, required File thumbFile, required Function(VideoThumbInfo?) onVideoThumbInfoListener, }) async { try { VideoThumbInfo? thumbInfo = VideoThumb.getThumbInfo(url); if (thumbInfo != null) { onVideoThumbInfoListener(thumbInfo); return; } Image image = Image.file(thumbFile!); image.image.resolve(const ImageConfiguration()).addListener( ImageStreamListener( (ImageInfo imageInfo, bool synchronousCall) { int imageWidth = imageInfo.image.width; int imageHeight = imageInfo.image.height; VideoThumbInfo videoThumbInfo = VideoThumbInfo(url: url); videoThumbInfo.thumbFile = thumbFile; videoThumbInfo.width = imageWidth; videoThumbInfo.height = imageHeight; VideoThumb.setThumbInfo(url, videoThumbInfo); onVideoThumbInfoListener(videoThumbInfo); }, onError: (exception, stackTrace) { print( "getVideoThumbInfoByFile imageStreamListener onError exception:${exception.toString()},stackTrace:${stackTrace}"); onVideoThumbInfoListener(null); }, ), ); } catch (e) { print("getVideoThumbInfoByFile catch error:${e.toString()}"); onVideoThumbInfoListener(null); } } void removeThumb(String url) { if (url.isEmpty) { return; } _thumbMap.remove(url); } /// 获取存储缩略图信息 VideoThumbInfo? getThumbInfo(String url) { if (url.isEmpty) { return null; } VideoThumbInfo? thumbInfo = _thumbInfoMap[url]; return thumbInfo; } /// 存储缩略图信息 void setThumbInfo(String url, VideoThumbInfo videoThumbInfo) async { if (url.isEmpty) { return; } _thumbInfoMap[url] = videoThumbInfo; } void removeThumbInfo(String url) { if (url.isEmpty) { return; } _thumbInfoMap.remove(url); } void clear() { _thumbMap.clear(); _thumbInfoMap.clear(); }}class VideoThumbInfo { String url; // 原视频地址 File? thumbFile; // 缩略图本地file int? width; // 缩略图的width int? height; // 缩略图的height VideoThumbInfo({ required this.url, });}三、显示视频缩略图的Widget
用于显示视频缩略图的Widget
/// 用于显示视频缩略图的Widgetclass VideoThumbImage extends StatefulWidget { const VideoThumbImage( {super.key, required this.url, this.maxWidth, this.maxHeight}); final String url; final double? maxWidth; final double? maxHeight; @override State createState() => _VideoThumbImageState();}class _VideoThumbImageState extends State { VideoThumbInfo? _videoThumbInfo; @override void initState() { // TODO: implement initState super.initState(); VideoThumb.getVideoThumbInfo(widget.url, onVideoThumbInfoListener: (VideoThumbInfo? thumbInfo) { if (mounted) { setState(() { _videoThumbInfo = thumbInfo; }); } }); } @override void dispose() { // TODO: implement dispose super.dispose(); } // 根据VideoThumb来显示图片 Widget buildVideoThumb(BuildContext context) { if (_videoThumbInfo != null && _videoThumbInfo!.thumbFile != null) { double? imageWidth; double? imageHeight; if (_videoThumbInfo!.width != null && _videoThumbInfo!.height != null) { imageWidth = _videoThumbInfo!.width!.toDouble(); imageWidth = _videoThumbInfo!.height!.toDouble(); } return Container( width: imageWidth, height: imageHeight, clipBehavior: Clip.hardEdge, decoration: const BoxDecoration( color: Colors.transparent, ), child: Image.file( _videoThumbInfo!.thumbFile!, width: imageWidth, height: imageHeight, ), ); } return Container(); } @override Widget build(BuildContext context) { return ConstrainedBox( constraints: BoxConstraints( maxWidth: widget.maxWidth ?? double.infinity, maxHeight: widget.maxHeight ?? double.infinity, ), child: buildVideoThumb(context), ); }}效果图如下:
四、小结
flutter开发实战-实现获取视频的缩略图封面video_thumbnail
学习记录,每天不停进步。