前言

最近研究视频下载,因此打算一边研究一边记录一下。方便以后使用查看

使用到的库有:

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&gt; 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标签平级
在这里插入图片描述
常用权限如下

<!--    电话权限--&gt;
<uses-feature
    android:name="android.hardware.telephony"
    android:required="false" /&gt;
<!--    相机权限--&gt;
<uses-feature
    android:name="android.hardware.camera"
    android:required="false" /&gt;

<!--   互联网权限-->
<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.
    );
  }
}

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

原文地址:https://blog.csdn.net/weixin_41897680/article/details/134717664

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_45550.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注