Flutter视频缩略图生成插件video_thumbnail的使用
Flutter视频缩略图生成插件video_thumbnail的使用
插件简介
video_thumbnail
插件可以从视频文件或URL中生成缩略图。它可以将图片返回到内存中或者写入到文件中。它提供了丰富的选项来控制图像格式、分辨率和质量。支持iOS和Android平台。
方法介绍
function | parameter | description | return |
---|---|---|---|
thumbnailData | String [video] , optional Map<String, dynamic> [headers] , ImageFormat [imageFormat] (JPEG/PNG/WEBP), int [maxHeight] (0: for the original resolution of the video, or scaled by the source aspect ratio), [maxWidth](0: for the original resolution of the video, or scaled by the source aspect ratio), int [timeMs]generates the thumbnail from the frame around the specified millisecond, int [quality](0-100) | 从 [video]生成缩略图 | [Future<Uint8List>]` |
||
thumbnailFile | String [video] , optional Map<String, dynamic> [headers] , String [thumbnailPath] (folder or full path where to store the thumbnail file, null to save to same folder as the video file), ImageFormat [imageFormat] (JPEG/PNG/WEBP), int [maxHeight] (0: for the original resolution of the video, or scaled by the source aspect ratio), int [maxWidth] (0: for the original resolution of the video, or scaled by the source aspect ratio), int [timeMs] generates the thumbnail from the frame around the specified millisecond, int [quality] (0-100) |
从[video] 创建一个缩略图文件 |
[Future<String>] |
警告:
- 如果同时指定了
maxHeight
和maxWidth
,在Android平台上会有不同的结果,实际上会将缩略图缩放到指定的maxHeight和maxWidth。 - 要从网络资源生成缩略图,
video
必须正确地进行URL编码。
使用方法
安装
在你的pubspec.yaml
文件中添加video_thumbnail
作为依赖项:
dependencies:
video_thumbnail: ^0.5.3
导入
import 'package:video_thumbnail/video_thumbnail.dart';
示例代码
从视频文件生成内存中的缩略图
final uint8list = await VideoThumbnail.thumbnailData(
video: videofile.path,
imageFormat: ImageFormat.JPEG,
maxWidth: 128, // 指定缩略图的宽度,高度自动按源宽高比缩放
quality: 25,
);
从视频URL生成缩略图文件
final fileName = await VideoThumbnail.thumbnailFile(
video: "https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4",
thumbnailPath: (await getTemporaryDirectory()).path,
imageFormat: ImageFormat.WEBP,
maxHeight: 64, // 指定缩略图的高度,宽度自动按源宽高比缩放
quality: 75,
);
从assets中的视频生成缩略图文件
final byteData = await rootBundle.load("assets/my_video.mp4");
Directory tempDir = await getTemporaryDirectory();
File tempVideo = File("${tempDir.path}/assets/my_video.mp4")
..createSync(recursive: true)
..writeAsBytesSync(byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes));
final fileName = await VideoThumbnail.thumbnailFile(
video: tempVideo.path,
thumbnailPath: (await getTemporaryDirectory()).path,
imageFormat: ImageFormat.PNG,
quality: 100,
);
完整示例Demo
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:video_thumbnail/video_thumbnail.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: DemoHome(),
);
}
}
class ThumbnailRequest {
final String video;
final String thumbnailPath;
final ImageFormat imageFormat;
final int maxHeight;
final int maxWidth;
final int timeMs;
final int quality;
const ThumbnailRequest({
this.video,
this.thumbnailPath,
this.imageFormat,
this.maxHeight,
this.maxWidth,
this.timeMs,
this.quality,
});
}
class ThumbnailResult {
final Image image;
final int dataSize;
final int height;
final int width;
const ThumbnailResult({this.image, this.dataSize, this.height, this.width});
}
Future<ThumbnailResult> genThumbnail(ThumbnailRequest r) async {
Uint8List bytes;
final Completer<ThumbnailResult> completer = Completer();
if (r.thumbnailPath != null) {
final thumbnailPath = await VideoThumbnail.thumbnailFile(
video: r.video,
headers: {
"USERHEADER1": "user defined header1",
"USERHEADER2": "user defined header2",
},
thumbnailPath: r.thumbnailPath,
imageFormat: r.imageFormat,
maxHeight: r.maxHeight,
maxWidth: r.maxWidth,
timeMs: r.timeMs,
quality: r.quality);
print("thumbnail file is located: $thumbnailPath");
final file = File(thumbnailPath);
bytes = file.readAsBytesSync();
} else {
bytes = await VideoThumbnail.thumbnailData(
video: r.video,
headers: {
"USERHEADER1": "user defined header1",
"USERHEADER2": "user defined header2",
},
imageFormat: r.imageFormat,
maxHeight: r.maxHeight,
maxWidth: r.maxWidth,
timeMs: r.timeMs,
quality: r.quality);
}
int _imageDataSize = bytes.length;
print("image size: $_imageDataSize");
final _image = Image.memory(bytes);
_image.image
.resolve(ImageConfiguration())
.addListener(ImageStreamListener((ImageInfo info, bool _) {
completer.complete(ThumbnailResult(
image: _image,
dataSize: _imageDataSize,
height: info.image.height,
width: info.image.width,
));
}));
return completer.future;
}
class GenThumbnailImage extends StatefulWidget {
final ThumbnailRequest thumbnailRequest;
const GenThumbnailImage({Key key, this.thumbnailRequest}) : super(key: key);
@override
_GenThumbnailImageState createState() => _GenThumbnailImageState();
}
class _GenThumbnailImageState extends State<GenThumbnailImage> {
@override
Widget build(BuildContext context) {
return FutureBuilder<ThumbnailResult>(
future: genThumbnail(widget.thumbnailRequest),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
final _image = snapshot.data.image;
final _width = snapshot.data.width;
final _height = snapshot.data.height;
final _dataSize = snapshot.data.dataSize;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Center(
child: Text(
"Image ${widget.thumbnailRequest.thumbnailPath == null ? 'data size' : 'file size'}: $_dataSize, width:$_width, height:$_height"),
),
Container(
color: Colors.grey,
height: 1.0,
),
_image,
],
);
} else if (snapshot.hasError) {
return Container(
padding: EdgeInsets.all(8.0),
color: Colors.red,
child: Text(
"Error:\n${snapshot.error.toString()}",
),
);
} else {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
"Generating the thumbnail for: ${widget.thumbnailRequest.video}..."),
SizedBox(
height: 10.0,
),
CircularProgressIndicator(),
]);
}
},
);
}
}
class DemoHome extends StatefulWidget {
@override
_DemoHomeState createState() => _DemoHomeState();
}
class _DemoHomeState extends State<DemoHome> {
final _editNode = FocusNode();
final _video = TextEditingController(
text:
"https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4");
ImageFormat _format = ImageFormat.JPEG;
int _quality = 50;
int _sizeH = 0;
int _sizeW = 0;
int _timeMs = 0;
GenThumbnailImage _futreImage;
String _tempDir;
@override
void initState() {
super.initState();
getTemporaryDirectory().then((d) => _tempDir = d.path);
}
@override
Widget build(BuildContext context) {
final _settings = <Widget>[
Slider(
value: _sizeH * 1.0,
onChanged: (v) => setState(() {
_editNode.unfocus();
_sizeH = v.toInt();
}),
max: 256.0,
divisions: 256,
label: "$_sizeH",
),
Center(
child: (_sizeH == 0)
? const Text(
"Original of the video's height or scaled by the source aspect ratio")
: Text("Max height: $_sizeH(px)"),
),
Slider(
value: _sizeW * 1.0,
onChanged: (v) => setState(() {
_editNode.unfocus();
_sizeW = v.toInt();
}),
max: 256.0,
divisions: 256,
label: "$_sizeW",
),
Center(
child: (_sizeW == 0)
? const Text(
"Original of the video's width or scaled by source aspect ratio")
: Text("Max width: $_sizeW(px)"),
),
Slider(
value: _timeMs * 1.0,
onChanged: (v) => setState(() {
_editNode.unfocus();
_timeMs = v.toInt();
}),
max: 10.0 * 1000,
divisions: 1000,
label: "$_timeMs",
),
Center(
child: (_timeMs == 0)
? const Text("The beginning of the video")
: Text("The closest frame at $_timeMs(ms) of the video"),
),
Slider(
value: _quality * 1.0,
onChanged: (v) => setState(() {
_editNode.unfocus();
_quality = v.toInt();
}),
max: 100.0,
divisions: 100,
label: "$_quality",
),
Center(child: Text("Quality: $_quality")),
Padding(
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 8.0),
child: InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(),
filled: true,
isDense: true,
labelText: "Thumbnail Format",
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Radio<ImageFormat>(
groupValue: _format,
value: ImageFormat.JPEG,
onChanged: (v) => setState(() {
_format = v;
_editNode.unfocus();
}),
),
const Text("JPEG"),
]),
Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Radio<ImageFormat>(
groupValue: _format,
value: ImageFormat.PNG,
onChanged: (v) => setState(() {
_format = v;
_editNode.unfocus();
}),
),
const Text("PNG"),
]),
Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Radio<ImageFormat>(
groupValue: _format,
value: ImageFormat.WEBP,
onChanged: (v) => setState(() {
_format = v;
_editNode.unfocus();
}),
),
const Text("WebP"),
]),
],
),
),
)
];
return Scaffold(
appBar: AppBar(
title: const Text('Thumbnail Plugin example'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 8.0),
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
filled: true,
isDense: true,
labelText: "Video URI",
),
maxLines: null,
controller: _video,
focusNode: _editNode,
keyboardType: TextInputType.url,
textInputAction: TextInputAction.done,
onEditingComplete: () {
_editNode.unfocus();
},
),
),
for (var i in _settings) i,
Expanded(
child: Container(
color: Colors.grey[300],
child: Scrollbar(
child: ListView(
shrinkWrap: true,
children: <Widget>[
(_futreImage != null) ? _futreImage : SizedBox(),
],
),
),
),
),
],
),
drawer: Drawer(
child: Column(
children: <Widget>[
AppBar(
title: const Text("Settings"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () => Navigator.pop(context),
)
],
),
for (var i in _settings) i,
],
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FloatingActionButton(
onPressed: () async {
File video =
await ImagePicker.pickVideo(source: ImageSource.camera);
setState(() {
_video.text = video.path;
});
},
child: Icon(Icons.videocam),
tooltip: "Capture a video",
),
const SizedBox(
width: 5.0,
),
FloatingActionButton(
onPressed: () async {
File video =
await ImagePicker.pickVideo(source: ImageSource.gallery);
setState(() {
_video.text = video?.path;
});
},
child: Icon(Icons.local_movies),
tooltip: "Pick a video",
),
const SizedBox(
width: 20.0,
),
FloatingActionButton(
tooltip: "Generate a data of thumbnail",
onPressed: () async {
setState(() {
_futreImage = GenThumbnailImage(
thumbnailRequest: ThumbnailRequest(
video: _video.text,
thumbnailPath: null,
imageFormat: _format,
maxHeight: _sizeH,
maxWidth: _sizeW,
timeMs: _timeMs,
quality: _quality));
});
},
child: const Text("Data"),
),
const SizedBox(
width: 5.0,
),
FloatingActionButton(
tooltip: "Generate a file of thumbnail",
onPressed: () async {
setState(() {
_futreImage = GenThumbnailImage(
thumbnailRequest: ThumbnailRequest(
video: _video.text,
thumbnailPath: _tempDir,
imageFormat: _format,
maxHeight: _sizeH,
maxWidth: _sizeW,
timeMs: _timeMs,
quality: _quality));
});
},
child: const Text("File"),
),
],
));
}
}
这个完整的示例展示了如何使用video_thumbnail
插件从视频文件或URL生成缩略图,并提供了用户界面来选择视频源、设置缩略图参数以及展示生成的结果。
更多关于Flutter视频缩略图生成插件video_thumbnail的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
1 回复