ToneSnap_FSX_Flutter/lib/global/download_manager.dart
2024-08-04 16:44:47 +08:00

166 lines
6.4 KiB
Dart

// Author: fengshengxiong
// Date: 2024/5/10
// Description: 下载管理
import 'package:background_downloader/background_downloader.dart';
import 'package:dio/dio.dart';
import 'package:get/get.dart';
import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/data/api/music_api.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/models/player_model.dart';
import 'package:tone_snap/data/storage/offline_box.dart';
import 'package:tone_snap/modules/sideb/offline/offline_controller.dart';
import 'package:tone_snap/modules/sideb/personal_music_library/personal_music_library_controller.dart';
import 'package:tone_snap/utils/date_util.dart';
import 'package:tone_snap/utils/local_path_util.dart';
import 'package:tone_snap/utils/log_util.dart';
import 'package:tone_snap/utils/obj_util.dart';
class DownloadManager {
static final DownloadManager _instance = DownloadManager._getInstance();
factory DownloadManager() => _instance;
static MemoryTaskQueue? tq;
List<Rx<MusicModel>> downloadList = [];
DownloadManager._getInstance() {
if (tq == null) {
tq ??= MemoryTaskQueue();
tq!.maxConcurrent = 3; // no more than 5 tasks active at any one time
tq!.maxConcurrentByHost = 3; // no more than two tasks talking to the same host at the same time
tq!.maxConcurrentByGroup = 3; // no more than three tasks from the same group active at the same time
FileDownloader().addTaskQueue(tq!); // 'connects' the TaskQueue to the FileDownloader
FileDownloader().updates.listen((update) async { // listen to updates as per usual
Rx<MusicModel>? musicModel = downloadList.firstWhereOrNull((e) => e.value.videoId == update.task.taskId);
if (musicModel == null) return;
if (update.runtimeType == TaskStatusUpdate) {
TaskStatus taskStatus = (update as TaskStatusUpdate).status;
LogUtil.d('${update.task.filename},任务状态: $taskStatus');
musicModel.update((fn) => fn?.taskStatus = taskStatus);
switch (taskStatus) {
case TaskStatus.enqueued:
break;
case TaskStatus.running:
break;
case TaskStatus.complete:
LogUtil.d('音乐下载路径:${await update.task.filePath()}');
musicModel.value.localPath = await update.task.filePath();
OfflineBox().add(musicModel.value.copyWith());
downloadList.remove(musicModel);
BaseEasyLoading.toast('Download completed');
if (Get.isRegistered<PersonalMusicLibraryController>()) {
PersonalMusicLibraryController.to.refreshOffline();
}
if (Get.isRegistered<OfflineController>()) {
OfflineController.to.getOfflineList();
}
break;
case TaskStatus.notFound:
BaseEasyLoading.toast('Download failed');
downloadList.remove(musicModel);
break;
case TaskStatus.failed:
BaseEasyLoading.toast('Download failed');
downloadList.remove(musicModel);
break;
case TaskStatus.canceled:
BaseEasyLoading.toast('Download cancelled');
downloadList.remove(musicModel);
break;
case TaskStatus.waitingToRetry:
break;
case TaskStatus.paused:
break;
}
}
if (update.runtimeType == TaskProgressUpdate) {
LogUtil.d('${update.task.filename},下载进度: $update');
musicModel.update((fn) => fn?.progress = (update as TaskProgressUpdate).progress);
}
});
}
}
/// 下载文件
void downloadMusic(Rx<MusicModel>? musicModel) {
if (musicModel == null) return;
musicModel.update((fn) {
fn?.taskStatus = TaskStatus.enqueued;
fn?.cancelToken = CancelToken();
});
_getMusicUrl(musicModel, (url, mimeType) async {
if (ObjUtil.isEmpty(url)) {
BaseEasyLoading.toast('Resource acquisition failed');
musicModel.update((fn) => fn?.taskStatus = TaskStatus.failed);
return;
}
String extension = mimeType ?? 'mp4';
if (ObjUtil.isNotEmpty(mimeType)) {
// 从 mimeType 中提取主类型和子类型
String type = mimeType!.split(';')[0].trim();
// 获取文件扩展名
extension = type.split('/')[1];
}
final filename = '${musicModel.value.title}_${DateUtil.getNowTimestamp()}.$extension';
final task = DownloadTask(
taskId: musicModel.value.videoId,
url: url!,
filename: filename,
directory: LocalPathUtil.getMusicDownloadDir(),
updates: Updates.statusAndProgress,
requiresWiFi: false,
retries: 0,
allowPause: false,
metaData: '',
);
tq?.add(task);
downloadList.add(musicModel);
});
}
Future<void> _getMusicUrl(Rx<MusicModel> musicModel, Function(String? url, String? mimeType) onTap) async {
PlayerModel? playerModel = await MusicApi.player(
videoId: musicModel.value.videoId,
cancelToken: musicModel.value.cancelToken,
fail: (baseError) {
if (baseError.code == DioExceptionType.cancel.index) {
musicModel.update((fn) {
fn?.taskStatus = TaskStatus.canceled;
fn?.cancelToken = null;
});
} else {
musicModel.update((fn) {
fn?.taskStatus = TaskStatus.failed;
fn?.cancelToken = null;
});
}
}
);
if (playerModel != null) {
if (ObjUtil.isEmpty(musicModel.value.coverUrl)) {
var thumbnails = playerModel.videoDetails?.thumbnail?.thumbnails;
if (thumbnails != null && thumbnails.isNotEmpty) {
musicModel.value.coverUrl = thumbnails.last.url;
}
}
if (ObjUtil.isEmpty(musicModel.value.musicType)) {
musicModel.value.musicType = playerModel.videoDetails?.musicVideoType;
}
var formats = playerModel.streamingData?.formats;
if (formats != null && formats.isNotEmpty) {
onTap(formats[0].url, formats[0].mimeType);
}
}
}
void cancelDownload(Rx<MusicModel>? musicModel) {
if (musicModel == null || musicModel.value.videoId == null) return;
musicModel.value.cancelToken?.cancel();
FileDownloader().cancelTaskWithId(musicModel.value.videoId!);
}
}