当前位置: 首页 > news >正文

flutter开发实战-实现获取视频的缩略图封面video_thumbnail

flutter开发实战-实现获取视频的缩略图封面video_thumbnail

在很多时候,我们查看视频的时候,视频没有播放时候,会显示一张封面,可能封面没有配置图片,这时候就需要通过获取视频的缩略图来显示封面了。这里使用了video_thumbnail来实现获取视频的缩略图。

一、引入video_thumbnail

在工程的pubspec.yaml中引入插件

  # 视频缩略图video_thumbnail: ^0.5.3

VideoThumbnail的属性如下

static Future<String?> thumbnailFile({required String video,Map<String, String>? 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; // 缩略图本地fileint? width; // 缩略图的widthint? height; // 缩略图的heightVideoThumbInfo({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_names
VideoThumbManager get VideoThumb => VideoThumbManager.instance;class VideoThumbManager {static VideoThumbManager get instance {return _singleton;}//保存单例static VideoThumbManager _singleton = VideoThumbManager._internal();//工厂构造函数factory VideoThumbManager() => _singleton;//私有构造函数VideoThumbManager._internal();// 保存url对应的本地缩略图filefinal _thumbMap = Map<String, File>();// url对应本地缩略图的信息final _thumbInfoMap = Map<String, VideoThumbInfo>();Future<void> setThumb(String url, File file) async {if (url.isEmpty) {return;}bool exist = await file.exists();if (exist == false) {return;}_thumbMap[url] = file;}Future<File?> 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; // 缩略图本地fileint? width; // 缩略图的widthint? height; // 缩略图的heightVideoThumbInfo({required this.url,});
}

三、显示视频缩略图的Widget

用于显示视频缩略图的Widget

/// 用于显示视频缩略图的Widget
class VideoThumbImage extends StatefulWidget {const VideoThumbImage({super.key, required this.url, this.maxWidth, this.maxHeight});final String url;final double? maxWidth;final double? maxHeight;@overrideState<VideoThumbImage> createState() => _VideoThumbImageState();
}class _VideoThumbImageState extends State<VideoThumbImage> {VideoThumbInfo? _videoThumbInfo;@overridevoid initState() {// TODO: implement initStatesuper.initState();VideoThumb.getVideoThumbInfo(widget.url,onVideoThumbInfoListener: (VideoThumbInfo? thumbInfo) {if (mounted) {setState(() {_videoThumbInfo = thumbInfo;});}});}@overridevoid dispose() {// TODO: implement disposesuper.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();}@overrideWidget build(BuildContext context) {return ConstrainedBox(constraints: BoxConstraints(maxWidth: widget.maxWidth ?? double.infinity,maxHeight: widget.maxHeight ?? double.infinity,),child: buildVideoThumb(context),);}
}

效果图如下:

在这里插入图片描述

四、小结

flutter开发实战-实现获取视频的缩略图封面video_thumbnail

学习记录,每天不停进步。

相关文章:

flutter开发实战-实现获取视频的缩略图封面video_thumbnail

flutter开发实战-实现获取视频的缩略图封面video_thumbnail 在很多时候&#xff0c;我们查看视频的时候&#xff0c;视频没有播放时候&#xff0c;会显示一张封面&#xff0c;可能封面没有配置图片&#xff0c;这时候就需要通过获取视频的缩略图来显示封面了。这里使用了video…...

Prompt Toolkit探索:打造交互式CLI应用

简介&#xff1a;prompt_toolkit 是一个 Python 的库&#xff0c;它提供了一系列功能丰富的用户界面元素&#xff0c;比如自动完成、语法高亮、多行编辑、提示等等&#xff0c;让你可以轻松地构建出功能强大的命令行工具。而且&#xff0c;这个库还被 IPython 和 pgcli 这样的知…...

【已解决】AttributeError: module ‘gradio‘ has no attribute ‘outputs‘

问题描述 AttributeError: module gradio has no attribute outputs 不知道作者用的是哪个gradio版本&#xff0c;最新的版本报错AttributeError: module gradio has no attribute outputs &#xff0c; 换一个老一点的版本会报错AttributeError: module gradio has no attribu…...

WPF Mvvm模式下面如何将事件映射到ViewModel层

前言 平常用惯了Command绑定,都快忘记传统的基于事件编程模式了,但是Commond模式里面有个明显的问题,就是你无法获取到事件源的参数。很多大聪明肯定会说,这还不简单,通过自己写控件,给控件加个自定义属性不就行了,想要啥事件就写啥事件进去,完全自主可控。但是对于写…...

C# WPF上位机开发(计算器界面设计)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 c# wpf最大的优势就是开发业务软件比较快、效率比较高。一般来说&#xff0c;它的界面和逻辑部分可以同时开发。界面的部分用xaml编写即可&#xf…...

[c]比较月亮大小

本题的难点就是分情况讨论 #include<stdio.h> int main() {int n;scanf("%d",&n);int arr2[n];int p;for(int m0;m<n-1;m){scanf("%d",&arr2[m]);//输入n个数保存到数组}if(n1)//当输入一个数据时&#xff0c;输入0&#xff0c;可以判断…...

【Java 基础】16 泛型

文章目录 什么是泛型&#xff1f;泛型的声明泛型的使用泛型方法通配符和泛型上下界1&#xff09;通配符2&#xff09;泛型上下界 泛型的好处注意事项 泛型提供了一种在编写代码时更好地 支持类型安全的机制。通过泛型&#xff0c;我们可以编写更加 通用、 灵活、 可读性高的…...

Android framework定制1-->用户无操作一段时间,自动播放客户提供的视频,用户操作后退出播放

在PowerManagerService.java中监听用户操作&#xff0c;10秒无操作则打开预置的apk播放视频&#xff0c;直接上代码&#xff1a; --- a/frameworks/base/services/core/java/com/android/server/power/PowerManagerService.javab/frameworks/base/services/core/java/com/andr…...

Vmware17虚拟机安装windows10系统

不要去什么系统之家之类的下载镜像&#xff0c;会不好安装&#xff0c;镜像被魔改过了&#xff0c;适合真实物理机上的系统在PE里安装系统&#xff0c;建议下载原版系统ISO文件 安装vmware17pro 下载地址https://dwangshuo.jb51.net/202211/tools/VMwareplayer17_855676.rar 解…...

Golang实践录:读取yaml配置文件

本文对 yaml 文件进行解析。 下载 yaml执行 go get github.com/spf13/viper 安装。 golang 有很多库可以解释 yaml 文件。本文选用 viper 进行解析&#xff0c;执行 go get github.com/spf13/viper 安装。 yaml语法规则 yaml对大小写敏感。yaml的层级关系只能使用空格缩进&a…...

oracle sql相关语法

SQL*PLUS 在SQL*PLUS执行&#xff0c;会在执行后显示查询的执行计划和统计信息 SET AUTOTRACE ON;SELECT * FROM your_table WHERE column_name value;SET AUTOTRACE OFF;PLSQL PLSQL查询sql界面&#xff0c;鼠标右键&#xff0c;点击执行计划&#xff0c;会出现sql的执行计…...

el-table,列表合并,根据名称列名称相同的品名将其它列值相同的进行合并

el-table,列表合并,根据名称列名称相同的品名将其它列值相同的进行合并,并且不能跨品名合并 如图 用到el-table合并行的方法合并 tableSpanMethod({ row, column, rowIndex, columnIndex }) {if (column.property "materielName") {//合并商品名const _row this…...

微信小程序显示二维码?

wxml <canvas style"width: 100%;height: 100%;margin-left: 20%;" id"Canvase" type"2d"></canvas> js // pages/code/code.js Page({/*** 页面的初始数据*/data: {code: ,},/*** 生命周期函数--监听页面加载*/onLoad(options) {…...

JavaWeb开发全流程笔记

JavaWeb 前端Web开发javaScript1.JS引入2.JS基础语法3.JS函数4.JS对象 BOMDOM文档对象模型JS事件监听VueVue常用指令Vue的生命周期 AjaxAxios 前端工程化环境准备NodeJS安装和Vue-cli安装vue项目Vue组件库Element组件的使用 Vue路由Nginx打包部署 后端Web开发MavenSpringBootHT…...

LLM;超越记忆《第 2 部分 》

一、说明 在这篇博客中&#xff0c;我深入研究了将大型语言模型&#xff08;LLM&#xff09;提升到基本记忆之上的数学框架。我们探索了动态上下文学习、连续空间插值及其生成能力&#xff0c;揭示了 LLM 如何理解、适应和创新超越传统机器学习模型。 LLM代表了人工智能的重大飞…...

Python中的加法测试题实现

随机生成5道10以内的加法测试题&#xff0c;用户在10秒内使用键盘输入答案。完成全部5道答题之后&#xff0c;计算机生成答题记录报告&#xff0c;并对答题情况进行分析&#xff0c;显示“答对了”&#xff0c;或“答错了”、并显示正确答案。如果未能按时完成&#xff0c;则显…...

使用gcloud SDK 管理和部署 Cloud run service

查看cloud run 上的service 列表&#xff1a; gcloud run services list > gcloud run services listSERVICE REGION URL LAST DEPLOYED BY LAST DEPL…...

JS逆向-mytoken之code参数

前言 本文是该专栏的第60篇,后面会持续分享python爬虫干货知识,记得关注。 本文以mytoken为例,通过js逆向获取其code参数的生成规律。具体的“逆向”思路逻辑,笔者将会详细介绍每个步骤,并且将在正文结合“完整代码”进行详细说明。 接下来,跟着笔者直接往下看正文详细…...

第九节HarmonyOS 常用基础组件4-Button

一、Button Button组件主要用来响应点击操作&#xff0c;可以包含子组件。 示例代码&#xff1a; Entry Component struct Index {build() {Row() {Column() {Button(确定, { type: ButtonType.Capsule, stateEffect: true }).width(90%).height(40).fontSize(16).fontWeigh…...

常用数据预处理方法 python

常用数据预处理方法 数据清洗缺失值处理示例删除缺失值插值法填充缺失值 异常值处理示例删除异常值替换异常值 数据类型转换示例数据类型转换在数据清洗过程中非常常见 重复值处理示例处理重复值是数据清洗的重要步骤 数据转换示例 数据集成示例数据集成是将多个数据源合并为一…...

独立开发者如何借助Taotoken多模型能力打造全能AI助手应用

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 独立开发者如何借助Taotoken多模型能力打造全能AI助手应用 对于独立开发者或小型工作室而言&#xff0c;构建一个功能全面的AI助手…...

个人自动化技能库构建指南:从Python脚本到Cron定时任务

1. 项目概述&#xff1a;一个为“摸鱼”场景设计的自动化技能库最近在GitHub上看到一个挺有意思的项目&#xff0c;叫my-copaw-skill。光看这个名字&#xff0c;就透着一股子“打工人”的幽默感——“copaw”这个词&#xff0c;我琢磨着应该是“copilot”&#xff08;副驾驶/助…...

Taotoken用量看板如何帮助个人开发者管理月度预算

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 Taotoken用量看板如何帮助个人开发者管理月度预算 对于独立工作的个人开发者而言&#xff0c;项目预算往往是决定技术选型与使用策…...

5个场景深度解析:如何用bili2text将B站视频变成你的私人知识库

5个场景深度解析&#xff1a;如何用bili2text将B站视频变成你的私人知识库 【免费下载链接】bili2text Bilibili视频转文字&#xff0c;一步到位&#xff0c;输入链接即可使用 项目地址: https://gitcode.com/gh_mirrors/bi/bili2text 凌晨两点&#xff0c;小林还在为明…...

JVM调优实战:让你的服务性能提升50%

一、背景 线上一个核心订单服务&#xff0c;QPS 3000左右&#xff0c;经常出现接口超时告警。监控显示&#xff1a; 平均RT: 180ms&#xff08;要求<100ms&#xff09;Full GC频率: 每天20次&#xff0c;每次STW 1.5sCPU使用率: 峰值85%服务规格: 8C16G&#xff0c;堆内存…...

NCM格式转换实战指南:ncmdumpGUI全面解析

NCM格式转换实战指南&#xff1a;ncmdumpGUI全面解析 【免费下载链接】ncmdumpGUI C#版本网易云音乐ncm文件格式转换&#xff0c;Windows图形界面版本 项目地址: https://gitcode.com/gh_mirrors/nc/ncmdumpGUI 你是否曾为网易云音乐下载的NCM格式音乐无法在其他设备播…...

终极指南:3步掌握yfinance金融数据获取与智能修复实战

终极指南&#xff1a;3步掌握yfinance金融数据获取与智能修复实战 【免费下载链接】yfinance Download market data from Yahoo! Finances API 项目地址: https://gitcode.com/GitHub_Trending/yf/yfinance yfinance是一个强大的Python库&#xff0c;能够从Yahoo! Finan…...

别再拷贝exe到NXBIN了!用批处理文件搞定NX二次开发外部exe的环境变量(附VS2015/NX12配置)

告别手动拷贝&#xff1a;用批处理智能管理NX二次开发环境变量 每次修改完NX二次开发的外部exe程序&#xff0c;都要手动拷贝到NXBIN目录&#xff1f;这种重复劳动不仅低效&#xff0c;还容易导致版本混乱。其实只需一个简单的批处理脚本&#xff0c;就能彻底解决环境变量配置问…...

未来之窗昭和仙君(九十三)用户指引自助教学源码—东方仙盟

代码<!DOCTYPE html> <html lang"zh-CN"> <head> <meta charset"UTF-8"> <meta http-equiv"X-UA-Compatible" content"IEedge,chrome1"> <title>你的导师-未来之窗</title> <style>*…...

量子误差缓解:Bhattacharyya距离与保形预测的应用

1. 量子噪声与误差缓解的核心挑战在当前的NISQ&#xff08;Noisy Intermediate-Scale Quantum&#xff09;时代&#xff0c;量子计算机面临的最大障碍就是噪声和误差问题。这些噪声主要来源于量子比特与环境之间的相互作用、门操作的不完美性以及测量误差等。以一个典型的超导量…...