Flutter:视频下载案例
前言
最近在研究视频下载,因此打算一边研究一边记录一下。方便以后使用时查看。
使用到的库有:
permission_handler 11.1.0 :权限请求
flutter_downloader 1.11.5:文件下载器
path_provider 2.1.1:路径处理
视频:
m3u8视频
:https://vip.lz-cdn5.com/20220402/3882_fcc71dbc/1800k/hls/mixed.m3u8
mp4视频
:https://www.runoob.com/try/demo_source/movie.mp4
开发配置
Android Studio 2022.3.1
版本 、flutter 3.16.0
、dart 3.2.0
、win 10
这里只考虑安卓不考虑其他系统
实现
permission_handler
单个权限
import 'package:permission_handler/permission_handler.dart';ElevatedButton(onPressed: () async {PermissionStatus status = await Permission.storage.request();// 除了下面3个常见情况还有:isLimited (表示权限被限制。这种状态适用于一些敏感权限,例如相机或麦克风权限。)、isProvisional(表示权限处于临时授予状态。这种状态适用于一些敏感权限,例如位置权限。)if (status.isGranted) {permissionState = '存储权限已被授予';} else if (status.isDenied) {permissionState = '存储权限被拒绝';} else if (status.isPermanentlyDenied) {permissionState = '存储权限被永久拒绝';}setState(() {});},child: const Text("获取存储权限")),
ElevatedButton(onPressed: () {openAppSettings();},child: const Text("手动去设置"))
多权限
import 'package:permission_handler/permission_handler.dart';void requestMultiplePermissions() async {Map<Permission, PermissionStatus> statuses = await [Permission.camera,Permission.microphone,Permission.location,].request();statuses.forEach((permission, status) {if (status.isGranted) {print('${permission.toString()} granted.');} else if (status.isDenied) {print('${permission.toString()} denied.');} else if (status.isPermanentlyDenied) {print('${permission.toString()} permanently denied.');}});
}
配置
项目/android/app/build.gradle
找到android
中的compileSdkVersion
设置为34
常用的权限
在项目/app/src/main/AndroidManifest.xml
添加对应的权限,主要是跟application
标签平级
常用权限如下:
<!-- 电话权限-->
<uses-featureandroid:name="android.hardware.telephony"android:required="false" />
<!-- 相机权限-->
<uses-featureandroid:name="android.hardware.camera"android:required="false" /><!-- 互联网权限-->
<uses-permission android:name="android.permission.INTERNET"/><!-- 联系人权限 group -->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/><!--存储权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- Android 12及更低版本的读取存储权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- Android 13及更新版本的细粒度媒体权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /><!-- 相机权限 -->
<uses-permission android:name="android.permission.CAMERA"/><!-- 短信权限组 -->
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH"/>
<uses-permission android:name="android.permission.RECEIVE_MMS"/><!-- 电话权限组 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.ADD_VOICEMAIL"/>
<uses-permission android:name="android.permission.USE_SIP"/>
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
<uses-permission android:name="android.permission.BIND_CALL_REDIRECTION_SERVICE"tools:ignore="ProtectedPermissions" /><!-- 日历权限组 -->
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" /><!-- 位置权限组 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /><!-- 麦克风、语音权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" /><!-- 传感器权限组 -->
<uses-permission android:name="android.permission.BODY_SENSORS" />
<uses-permission android:name="android.permission.BODY_SENSORS_BACKGROUND" /><!-- “访问媒体位置”组的权限选项 -->
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" /><!-- “活动识别”组的权限选项 -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /><!-- “忽略电池优化”组的权限选项 -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /><!--“附近设备”组的权限选项 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" /><!-- “管理外部存储”组的权限选项 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /><!-- “系统警报窗口”组的权限选项 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /><!-- “请求安装程序包”组的权限选项 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /><!-- “访问通知策略”组的权限选项 -->
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/><!-- “通知”组的权限选项 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/><!-- “报警”组的权限选项 -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
效果图
不同的安卓版本可能会有不同的效果
模拟器
真机,demo app
真机,夸克浏览器下载视频时请求的权限
path_provider
这个之前介绍过了,简单使用见:flutter:文件系统目录、文件读写
创建文件夹
ElevatedButton(onPressed: () async {// 获取应用程序目录Directory appDocumentDirectory =await getApplicationSupportDirectory();// 使用路径分隔符设置路径String path ="${appDocumentDirectory.path}${Platform.pathSeparator}myappVideoDownload";// 判断文件夹是否存在,不存在则创建var dir = Directory(path);print("路径:$path");if (!dir.existsSync()) {await dir.create(recursive: true);print("创建成功");}},child: const Text("下载视频"))
查看应用程序目录
下面是在Android studio中进行的操作。
1、点击Device Explorer
2、在展开的设备目录中,找到data文件夹下的data文件夹,然后找到应用程序的包名文件夹(例如:com.example.myapp)。
3、在应用程序的包名文件夹中,找到files文件夹,这就是应用程序的文档目录。
注: files目录:这个目录用于存储应用程序在运行时生成或下载的文件,例如用户生成的数据、缓存文件等。这些文件只能被应用程序本身访问,并且可以在运行时进行读取、写入和修改。这个目录的访问权限是私有的,其他应用程序无法直接访问。
flutter_downloader
默认最大并发下载数是2,如果要修改具体见官方文档。
配置
app/src/main/AndroidManifest.xml
的application
中添加如下配置
<providerandroid:name="vn.hunghd.flutterdownloader.DownloadedFileProvider"android:authorities="${applicationId}.flutter_downloader.provider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/provider_paths"/>
</provider>
在app/src/main/res/xml
目录下(没有的话自己手动创建),创建provider_paths.xml
文件,在文件里添加一下内容:
<?xml version="1.0" encoding="utf-8"?>
<paths><external-path name="external-path" path="."/><external-cache-path name="external-cache-path" path="."/><external-files-path name="external-files-path" path="."/><files-path name="files_path" path="."/><cache-path name="cache-path" path="."/><root-path name="root" path="."/>
</paths>
案例代码
import 'dart:io';
import 'dart:isolate';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';void main() async {// 初始化下载器WidgetsFlutterBinding.ensureInitialized();await FlutterDownloader.initialize(debug: true, ignoreSsl: false);runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});// This widget is the root of your application.Widget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),useMaterial3: true,),home: const MyHomePage(title: 'Flutter Demo Home Page'),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key, required this.title});final String title;State<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {// 文件下载列表List fileDownloadList = [];// 下载进度double downloadProgress = 0;// 用于线程通信final ReceivePort _port = ReceivePort();void initState() {super.initState();// 接收下载线程发来的消息IsolateNameServer.registerPortWithName(_port.sendPort, 'downloader_send_port');_port.listen((dynamic data) {var id = data[0];var status = data[1];int progress = data[2];// Download progress: 15%,2print("Download: $id,$status,$progress%");// 更新进度setState(() {downloadProgress = progress.toDouble();});});// 监听下载进度FlutterDownloader.registerCallback(downloadCallback);}dispose() {// 关闭通信IsolateNameServer.removePortNameMapping('downloader_send_port');super.dispose();}// 下载回调函数,这个函数必须是静态或者顶级函数,具体见官方文档说明static void downloadCallback(id, status, progress) {final SendPort? send =IsolateNameServer.lookupPortByName('downloader_send_port');send?.send([id, status, progress]);}// 文件下载方法Future<void> downloadFile(String url, [String? filename]) async {// 判断是否存在存储权限var hasStoragePermission = await Permission.storage.isGranted;if (!hasStoragePermission) {final status = await Permission.storage.request();hasStoragePermission = status.isGranted;}// 获取应用程序目录Directory appDocumentDirectory = await getApplicationSupportDirectory();// 使用路径分隔符设置路径String path ="${appDocumentDirectory.path}${Platform.pathSeparator}myappVideoDownload";// 判断文件夹是否存在,不存在则创建var dir = Directory(path);if (!dir.existsSync()) {await dir.create(recursive: true);print("创建成功,路径为:$path");}if (hasStoragePermission) {// 创建下载任务final taskId = await FlutterDownloader.enqueue(url: url,headers: {},// optional: header send with url (auth token etc)savedDir: path,saveInPublicStorage: true,fileName: filename);// 存储下载的任务fileDownloadList.add({'taskId':taskId,'filename':filename});print("下载任务ID是:$taskId");}}Widget build(BuildContext context) {return Scaffold(appBar: AppBar(backgroundColor: Theme.of(context).colorScheme.inversePrimary,title: Text(widget.title),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[ElevatedButton(onPressed: () async {await downloadFile("https://scpic.chinaz.net/files/default/imgs/2023-12-01/8b0607ced040f71b_s.jpg","aa.jpg");},child: const Text("下载图片")),Text("下载进度:$downloadProgress%"),LinearProgressIndicator(value: downloadProgress, //当前进度minHeight: 20, //最新高度color: Colors.red //当前进度的颜色),ElevatedButton(onPressed: () {final taskId = fileDownloadList[0]['taskId'];FlutterDownloader.open(taskId: taskId);},child: const Text("打开下载的文件"))],),), // This trailing comma makes auto-formatting nicer for build methods.);}
}
效果图
相关文章:

Flutter:视频下载案例
前言 最近在研究视频下载,因此打算一边研究一边记录一下。方便以后使用时查看。 使用到的库有: permission_handler 11.1.0 :权限请求 flutter_downloader 1.11.5:文件下载器 path_provider 2.1.1:路径处理 视频…...
要求CHATGPT高质量回答的艺术:提示工程技术的完整指南
要求CHATGPT高质量回答的艺术:提示工程技术的完整指南 第2章:指令提示技术 现在,让我们开始探索“指令提示技术”,以及如何使用它从ChatGPT生成高质量的文本。 指令提示技术是一种通过为模型提供特定指令来指导ChatGPT输出的方…...

JDK 历史版本下载以及指定版本应用
参考: 官网下载JAVA的JDK11版本(下载、安装、配置环境变量)_java11下载-CSDN博客 Gradle:执行命令时指定 JDK 版本 - 微酷网 下载 打开官网地址 Java Downloads | Oracle 当前版本在这里,但是我们要下载历史版本 选…...

Linux基础项目开发1:量产工具——UI系统(五)
前言: 前面我们已经把显示系统、输入系统、文字系统搭建好了,现在我们就要给它实现按钮操作了,也就是搭建UI系统,下面让我们一起实现UI系统的搭建吧 目录 一、按钮数据结构抽象 ui.h 二、按键编程 1.button.c 2.disp_manager…...

面试就是这么简单,offer拿到手软(四)—— 常见java152道基础面试题
面试就是这么简单,offer拿到手软(一)—— 常见非技术问题回答思路 面试就是这么简单,offer拿到手软(二)—— 常见65道非技术面试问题 面试就是这么简单,offer拿到手软(三ÿ…...
深入理解Redis分片策略:提升系统性能的关键一步
目录 引言 1. 一致性哈希算法 2. 范围分片 3. 哈希槽分片 实战经验分享 结论 引言 Redis作为一款高性能的键值存储系统,为了应对大规模数据和高并发的访问,引入了分片策略,使得数据能够分布存储在多个节点上,实现系统的横向…...

【数据结构(七)】查找算法
文章目录 查找算法介绍1. 线性查找算法2. 二分查找算法2.1. 思路分析2.2. 代码实现2.3. 功能拓展 3. 插值查找算法3.1. 前言3.2. 相关概念3.3. 实例应用 4. 斐波那契(黄金分割法)查找算法4.1. 斐波那契(黄金分割法)原理4.2. 实例应用 查找算法介绍 在 java 中,我们…...
Android画布Canvas绘制drawBitmap基于源Rect和目的Rect,Kotlin
Android画布Canvas绘制drawBitmap基于源Rect和目的Rect,Kotlin <?xml version"1.0" encoding"utf-8"?> <androidx.appcompat.widget.LinearLayoutCompat xmlns:android"http://schemas.android.com/apk/res/android"xmlns…...

深度优先搜索LeetCode979. 在二叉树中分配硬币
给你一个有 n 个结点的二叉树的根结点 root ,其中树中每个结点 node 都对应有 node.val 枚硬币。整棵树上一共有 n 枚硬币。 在一次移动中,我们可以选择两个相邻的结点,然后将一枚硬币从其中一个结点移动到另一个结点。移动可以是从父结点到…...

C++学习之路(十)C++ 用Qt5实现一个工具箱(增加一个时间戳转换功能)- 示例代码拆分讲解
上篇文章,我们用 Qt5 实现了在小工具箱中添加了《JSON数据格式化》功能,还是比较实用的。为了继续丰富我们的工具箱,今天我们就再增加一个平时经常用到的功能吧,就是「 时间戳转换 」功能,而且实现点击按钮后文字进行变…...
Linux 5.15安全特性之ARM64 PAC
ARM64 PAC(Pointer Authentication Code)机制是ARM架构中引入的一种安全特性,旨在提供指针的完整性和安全性保护。它通过在指针中插入一段额外的代码进行签名,以验证指针的完整性,从而抵御缓冲区溢出和代码注入等攻击。…...

同旺科技 分布式数字温度传感器
内附链接 1、数字温度传感器 主要特性有: ● 支持PT100 / PT1000 两种铂电阻; ● 支持 2线 / 3线 / 4线 制接线方式; ● 支持5V~17V DC电源供电; ● 支持电源反接保护; ● 支持通讯波特率1200bps、2…...
状态空间的定义
状态空间是描述一个系统所有可能状态的集合。在系统理论、控制论、计算机科学、强化学习等领域,状态空间是一种常见的概念。 状态空间框架是一种用于描述和分析系统的方法,它包括系统的状态、状态之间的转移关系以及与状态相关的行为。下面详细解释状态…...

数据挖掘实战-基于word2vec的短文本情感分析
🤵♂️ 个人主页:艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞Ǵ…...
大数据面试总结
1、冒泡排序、选择排序 2、二分查找 3、 hashmap和hashtable的区别?hashmap的底层实现原理? a、hashtable和hashmap的区别: 1、hashtable是线程安全的,会在每一个方法中都添加方法synchronize(同步机制)…...

利大于弊:物联网技术对电子商务渠道的影响
For Better or For Worse: Impacts of IoT Technology in e-Commerce Channel 物联网技术使用传感器和其他联网设备来手机和共享数据,并且被视为一种可以为供应链成员带来巨大的机会的突破性技术。本文的研究背景是:一个提供物联网基础设备的电子商务平…...
Python 元组详解(tuple)
文章目录 1 概述1.1 性质1.2 下标1.3 切片 2 常用方法2.1 访问:迭代、根据下标2.2 删除:del2.3 运算符:、*2.4 计算元组中元素个数:len()2.5 返回元组中元素最大值:max()2.6 返回元组中元素最小值:min()2.7…...

Redis部署-主从模式
目录 单点问题 主从模式 解析主从模式 配置redis主从模式 info replication命令查看复制相关的状态 断开复制关系 安全性 只读 传输延迟 拓扑结构 数据同步psync replicationid offset psync运行流程 全量复制流程 无硬盘模式 部分复制流程 积压缓冲区 实时复…...

全栈冲刺 之 一天速成MySQL
一、为什么使用数据库 数据储存在哪里? 硬盘、网盘、U盘、光盘、内存(临时存储) 数据持久化 使用文件来进行存储,数据库也是一种文件,像excel ,xml 这些都可以进行数据的存储,但大量数据操作…...
服务器运行train.py报错解决
在服务器配置完虚拟环境以及安装完各种所需库后,发现报错Traceback (most recent call last): File "/root/yolov5-master/yolov5-master/train.py", line 48, in <module> import val as validate # for end-of-epoch mAP File "/root/yolov5…...

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...

优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...

MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...