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文件實際上是通過它來解析對應(yīng)的放在服務(wù)器上的視頻網(wǎng)絡(luò)地址,從而實現(xiàn)在線播放滥玷。
在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文件夾不存在則創(chuàng)建
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;
}
}