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.0dart 3.2.0win 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-feature
    android:name="android.hardware.telephony"
    android:required="false" />
<!--    相机权限-->
<uses-feature
    android: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.xmlapplication中添加如下配置

<provider
    android:name="vn.hunghd.flutterdownloader.DownloadedFileProvider"
    android:authorities="${applicationId}.flutter_downloader.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android: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%,2
      print("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.
    );
  }
}

效果图
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/210362.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于springboot 图书馆管理系统

qq&#xff08;2829419543&#xff09;获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;springboot 前端&#xff1a;采用vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xf…

网络编程之套接字

端口 && IP 在学习套接字编程之前&#xff0c;我们必须了解一下前缀知识。首先是IP和端口的作用。 在这之前&#xff0c;我们要明白一件事。那就是把数据从一台主机发送到另一台主机&#xff0c;是目的吗&#xff1f;&#xff1f;&#xff1f;当然不是&#xff01;&a…

Hdoop学习笔记(HDP)-Part.20 安装Flume

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

java学习part27线程死锁

基本就是操作系统的内容 138-多线程-线程安全的懒汉式_死锁_ReentrantLock的使用_哔哩哔哩_bilibili

基于SpringBoot+Vue的前后端分离的房屋租赁系统2

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 开发过程中&#xff0…

基于Django框架搭建的协同过滤算法电影推荐网站-爬取的豆瓣电影数据

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介概述技术栈实现流程 二、功能三、系统四. 总结 一项目简介 # 电影推荐网站介绍 概述 该电影推荐网站是基于Django框架搭建的&#xff0c;旨在为用户提供个…

前端文本省略号后面添加复制文字

前端文本省略号后面添加复制文字 1、效果图 2、代码展示 <div class"link-content-wrap" click"copyLinkText"><div class"link-content">{{ shareResult.url || }} </div><span class"show-ellipsis" click&…

Linux MIPI 调试中常见的问题

一、概述 做嵌入式工作的小伙伴知道&#xff0c;有时候程序编写没有调试过程中费时&#xff0c;之间笔记里有 MIPI 摄像头驱动开发的过程&#xff0c;有需要的小伙伴可以参考&#xff1a;Linux RN6752 驱动编写。而我也是第一次琢磨 MIPI 协议&#xff0c;其中有很多不明白的地…

本科毕业生个人简历23篇

刚毕业的本科生如何制作一份令招聘方印象深刻的简历&#xff1f;可以参考以下这23篇精选的本科毕业生应聘简历案例&#xff01;无论您的专业是什么&#xff0c;都能从中汲取灵感&#xff0c;提升简历质量&#xff0c;轻松斩获心仪职位&#xff01;小伙伴们快来看看吧&#xff0…

LeetCode刷题---合并两个有序链表

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏&#xff1a;http://t.csdnimg.cn/ZxuNL http://t.csdnimg.cn/c9twt 前言&#xff1a;这个专栏主要讲述递归递归、搜索与回溯算法&#xff0c;所以下面题目主要也是这些算法做的 我讲述…

3D模型材质编辑

在线工具推荐&#xff1a; 三维数字孪生场景工具 - GLTF/GLB在线编辑器 - Three.js AI自动纹理化开发 - YOLO 虚幻合成数据生成器 - 3D模型在线转换 - 3D模型预览图生成服务 如今&#xff0c;3D 纹理、打印和建模都非常流行。使用可用的高级工具&#xff0c;创建 3D 模型…

24双非硕的秋招总结

24 双非硕的秋招总结 结果&#xff1a; 运气捡漏去了腾讯 想想自己整个研究生学习过程&#xff0c;还是挺坎坷的&#xff0c;记录一下&#xff0c;也给未来的同学提供一些参考。 研一 我是研一上开始学前端的&#xff0c;应该是21年10月份左右&#xff0c;我们实验室是专门…

Nacos多数据源插件

Nacos从2.2.0版本开始,可通过SPI机制注入多数据源实现插件,并在引入对应数据源实现后,便可在Nacos启动时通过读取application.properties配置文件中spring.datasource.platform配置项选择加载对应多数据源插件.本文档详细介绍一个多数据源插件如何实现以及如何使其生效。 注意:…

DOM 事件的传播机制

前端面试大全DOM 事件的传播机制 &#x1f31f;经典真题 &#x1f31f;事件与事件流 事件流 事件冒泡流 事件捕获流 标准 DOM 事件流 &#x1f31f;事件委托 &#x1f31f;真题解答 &#x1f31f;总结 &#x1f31f;经典真题 谈一谈事件委托以及冒泡原理 &#x1f3…

134. 加油站(贪心算法)

根据题解 这道题使用贪心算法&#xff0c;找到当前可解决问题的状态即可 「贪心算法」的问题需要满足的条件&#xff1a; 最优子结构&#xff1a;规模较大的问题的解由规模较小的子问题的解组成&#xff0c;规模较大的问题的解只由其中一个规模较小的子问题的解决定&#xff…

蓝桥第一期模拟总结

文章目录 1.动态的 Tab 栏2.地球漫游3.迷惑的this4.燃烧卡路里5.魔法失灵了6.年龄统计 所有题目链接 1.动态的 Tab 栏 本题要实现一个tab栏固定效果&#xff0c;看见题目就想到css中的 position: fixed; 尝试了很久都没能实现效果&#xff0c;后来又想到了粘性定位 position: …

【深度学习】深度学习框架的环境配置

目录 1. 配置cuda环境 1.1. 安装cuda和cudnn 1.1.1. 显卡驱动配置 1.1.2. 下载安装cuda 1.1.3. 下载cudnn&#xff0c;将解压后文件复制到cuda目录下 1.2. 验证是否安装成功 2. 配置conda环境 2.1. 安装anaconda 2.2. conda换源 2.3. 创建conda环境 2.4. pip换源 3…

封装员工头像组件

创建image-upload组件 <template><el-uploadclass"avatar-uploader"action"":show-file-list"false":before-upload"beforeAvatarUpload"><!-- (自动上传)action是上传地址 --><img v-if"value" :s…

第二次量子化

专栏目录: 高质量文章导航-持续更新中 前置复盘: 玻色子和费米子: 首先,我们希望把描述单粒子态的量子力学推广到全同多粒子体系。我们的做法是从单粒子态的希尔伯特空间(Hilbert Space)出发,构造全同多粒子态的态空间——福克空间(Fock Space),它实际上就是无穷个…

20231202 VCSA 7.0 日志清理及空间扩容

日志清理 ssh 到 VCSA 上&#xff0c;输入 shell 进入控制台 df -lh 查看空间大小&#xff0c;发现 /storage/archive 占据空间比较大&#xff0c;这里应该是备份日志的地方&#xff0c;里边文件不大但很多&#xff0c;查找并删除 30 天以前的日志 cd /storage/archive/vpostgr…