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…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
