166 lines
6.4 KiB
Dart
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[0].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!);
|
|
}
|
|
}
|