flutter下载m3u8视频

m3u8是苹果公司推出的视频播放标准,是m3u的一种,只是编码格式采用的是UTF-8。
存储的信息类似于:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:6
#EXTINF:5,
abc_0123.ts
#EXTINF:5,
abc_0124.ts
#EXTINF:5,
abc_0125.ts
#EXT-X-ENDLIST

m3u8准确来说是一种索引文件,使用m3u8文件实际上是通过它来解析对应的放在服务器上的视频网络地址,从而实现在线播放。
在flutter中,要下载m3u8的视频,实际上是要将m3u8中列出的abc_0123.ts视频文件全部下载下来,再合成一个mp4文件。
需要用到几个插件

flutter_hls_parser: ^2.0.0  // 解析m3u8文件
ffmpeg_kit_flutter: 4.5.1-LTS  // 合成mp4文件
image_gallery_saver: ^1.7.1  // 保存到手机相册

具体代码如下

import "dart:io";

import "package:bot_toast/bot_toast.dart";
import "package:dio/dio.dart";
import "package:ffmpeg_kit_flutter/ffmpeg_kit.dart";
import "package:ffmpeg_kit_flutter/ffmpeg_session.dart";
import "package:ffmpeg_kit_flutter/log.dart";
import "package:ffmpeg_kit_flutter/statistics.dart";
import "package:flutter/material.dart";
import "package:flutter_hls_parser/flutter_hls_parser.dart";
import "package:image_gallery_saver/image_gallery_saver.dart";
import "package:path_provider/path_provider.dart";

class DownloadM3U8Util {
  DownloadM3U8Util._();

  static final DownloadM3U8Util instance = DownloadM3U8Util._();
  List<String> downLoadUrl = [];

  // 传入m3u8下载地址
  Future<void> downloadM3u8(String url) async {
    debugPrint("DownloadUtil, _url=$url");
    if (downLoadUrl.contains(url)) {
      print("已进入下载队列");
      return;
    }
    final String savePath = await _getSavePath();  // 存储路径
   
    downLoadUrl.add(url);
    final String host = url.substring(0, url.lastIndexOf("/"));
    final String m3u8FileName = url.substring(url.lastIndexOf("/") + 1); // m3u8文件名带后缀.  xxx.m3u8
    final String m3u8Path = "$savePath/$m3u8FileName"; // m3u8保存绝对路径  /abc/../xxx.m3u8
    final String m3u8Name = m3u8FileName.split(".").first; // m3u8文件名不带后缀  xxx

    final Response response = await Dio().download(url, m3u8Path);
    if (response.statusCode != 200) {
      print("下载 m3u8 失败 _url=$url");
      downLoadUrl.remove(url);
      return;
    }
    // video文件夹不存在则创建
    Directory directory = Directory("$savePath/video/");
    if (!directory.existsSync()) {
      directory.createSync();
    }

    HlsPlaylist playList;
    try {
      playList = await HlsPlaylistParser.create()
          .parse(Uri.parse(url), await File(m3u8Path).readAsLines());
    } on ParserException catch (e) {
      print(e);
    }

    if (playList is HlsMediaPlaylist) {
      // 读取m3u8文件的URL信息
      final mediaPlaylistUrls = playList.segments.map((it) => it.url);
      for (var value in mediaPlaylistUrls) {
        // value  oux02488.ts
        String tsUrl = "$host/$value";
        File file = File("$savePath/$value");
        if (!file.existsSync()) {
          file.create();
        }
        print("下载ts地址 url=$tsUrl, file.path=${file.path}");
        Response _response = await Dio().download(tsUrl, file.path);
        if (_response.statusCode != 200) {
          downLoadUrl.remove(url);
          return;
        }
      }
      print("已下载完所有ts视频");
      String cmd = '-allowed_extensions ALL -i $m3u8Path  "$savePath/video/$m3u8Name.mp4"';

      ///合并ts为mp4
      FFmpegKit.executeAsync(cmd, (FFmpegSession session) {
        BotToast.showText(text: "下载成功");
        downLoadUrl.remove(url);
        // 保存到相册
        ImageGallerySaver.saveFile("$savePath/video/$m3u8Name.mp4").then((value) {
          // 删除临时路径的文件
          File file = File("$savePath/video/$m3u8Name.mp4");
          file.deleteSync();
        });
      });
    }
  }

  /// 获取存储路径
  static Future<String> _getSavePath() async {
    final directory = Platform.isAndroid
        ? await getExternalStorageDirectory()
        : await getApplicationDocumentsDirectory();
    return directory.path;
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容