From 32c892c38002526a0bb48b5ef6a1c92741e53597 Mon Sep 17 00:00:00 2001 From: croire <1432593898@qq.com> Date: Wed, 11 May 2022 21:47:46 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9B=E5=BB=BA=E5=86=85=E5=BB=BA=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DownKyi/DownKyi.csproj | 1 + .../Services/Download/AriaDownloadService.cs | 708 +--------------- .../Download/BuiltinDownloadService.cs | 181 +++++ DownKyi/Services/Download/DownloadService.cs | 756 +++++++++++++++++- 4 files changed, 955 insertions(+), 691 deletions(-) create mode 100644 DownKyi/Services/Download/BuiltinDownloadService.cs diff --git a/DownKyi/DownKyi.csproj b/DownKyi/DownKyi.csproj index 789105a..328074d 100644 --- a/DownKyi/DownKyi.csproj +++ b/DownKyi/DownKyi.csproj @@ -117,6 +117,7 @@ + diff --git a/DownKyi/Services/Download/AriaDownloadService.cs b/DownKyi/Services/Download/AriaDownloadService.cs index 0bee235..3edd9f9 100644 --- a/DownKyi/Services/Download/AriaDownloadService.cs +++ b/DownKyi/Services/Download/AriaDownloadService.cs @@ -3,15 +3,10 @@ using DownKyi.Core.Aria2cNet.Client; using DownKyi.Core.Aria2cNet.Client.Entity; using DownKyi.Core.Aria2cNet.Server; using DownKyi.Core.BiliApi.Login; -using DownKyi.Core.BiliApi.VideoStream; using DownKyi.Core.BiliApi.VideoStream.Models; -using DownKyi.Core.Danmaku2Ass; -using DownKyi.Core.FFmpeg; using DownKyi.Core.Logging; using DownKyi.Core.Settings; -using DownKyi.Core.Storage; using DownKyi.Core.Utils; -using DownKyi.Images; using DownKyi.Models; using DownKyi.Utils; using DownKyi.ViewModels.DownloadManager; @@ -20,7 +15,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; namespace DownKyi.Services.Download @@ -30,14 +24,6 @@ namespace DownKyi.Services.Download /// public class AriaDownloadService : DownloadService, IDownloadService { - private Task workTask; - private CancellationTokenSource tokenSource; - private CancellationToken cancellationToken; - private List downloadingTasks = new List(); - - private readonly int retry = 5; - private readonly string nullMark = ""; - public AriaDownloadService(ObservableCollection downloadingList, ObservableCollection downloadedList) : base(downloadingList, downloadedList) { Tag = "AriaDownloadService"; @@ -50,40 +36,9 @@ namespace DownKyi.Services.Download /// /// /// - public string DownloadAudio(DownloadingItem downloading) + public override string DownloadAudio(DownloadingItem downloading) { - // 更新状态显示 - downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); - downloading.DownloadContent = DictionaryResource.GetString("DownloadingAudio"); - - // 如果没有Dash,返回null - if (downloading.PlayUrl == null || downloading.PlayUrl.Dash == null) { return null; } - - // 如果audio列表没有内容,则返回null - if (downloading.PlayUrl.Dash.Audio == null) { return null; } - else if (downloading.PlayUrl.Dash.Audio.Count == 0) { return null; } - - // 根据音频id匹配 - PlayUrlDashVideo downloadAudio = null; - foreach (PlayUrlDashVideo audio in downloading.PlayUrl.Dash.Audio) - { - if (audio.Id == downloading.AudioCodec.Id) - { - downloadAudio = audio; - break; - } - } - - // 避免Dolby==null及其它未知情况,直接使用异常捕获 - try - { - // Dolby Atmos - if (downloading.AudioCodec.Id == 30250) - { - downloadAudio = downloading.PlayUrl.Dash.Dolby.Audio[0]; - } - } - catch (Exception) { } + PlayUrlDashVideo downloadAudio = BaseDownloadAudio(downloading); return DownloadVideo(downloading, downloadAudio); } @@ -93,29 +48,9 @@ namespace DownKyi.Services.Download /// /// /// - public string DownloadVideo(DownloadingItem downloading) + public override string DownloadVideo(DownloadingItem downloading) { - // 更新状态显示 - downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); - downloading.DownloadContent = DictionaryResource.GetString("DownloadingVideo"); - - // 如果没有Dash,返回null - if (downloading.PlayUrl == null || downloading.PlayUrl.Dash == null) { return null; } - - // 如果Video列表没有内容,则返回null - if (downloading.PlayUrl.Dash.Video == null) { return null; } - else if (downloading.PlayUrl.Dash.Video.Count == 0) { return null; } - - // 根据视频编码匹配 - PlayUrlDashVideo downloadVideo = null; - foreach (PlayUrlDashVideo video in downloading.PlayUrl.Dash.Video) - { - if (video.Id == downloading.Resolution.Id && Utils.GetVideoCodecName(video.Codecs) == downloading.VideoCodecName) - { - downloadVideo = video; - break; - } - } + PlayUrlDashVideo downloadVideo = BaseDownloadVideo(downloading); return DownloadVideo(downloading, downloadVideo); } @@ -199,150 +134,27 @@ namespace DownKyi.Services.Download /// 下载封面 /// /// - public string DownloadCover(DownloadingItem downloading, string coverUrl, string fileName) + public override string DownloadCover(DownloadingItem downloading, string coverUrl, string fileName) { - // 更新状态显示 - downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); - downloading.DownloadContent = DictionaryResource.GetString("DownloadingCover"); - // 下载大小 - downloading.DownloadingFileSize = string.Empty; - // 下载速度 - downloading.SpeedDisplay = string.Empty; - - // 查询、保存封面 - StorageCover storageCover = new StorageCover(); - string cover = storageCover.GetCover(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid, coverUrl); - if (cover == null) - { - return null; - } - - // 复制图片到指定位置 - try - { - File.Copy(cover, fileName, true); - - // 记录本次下载的文件 - if (!downloading.Downloading.DownloadFiles.ContainsKey(coverUrl)) - { - downloading.Downloading.DownloadFiles.Add(coverUrl, fileName); - } - - return fileName; - } - catch (Exception e) - { - Core.Utils.Debugging.Console.PrintLine(e); - LogManager.Error(Tag, e); - } - - return null; + return BaseDownloadCover(downloading, coverUrl, fileName); } /// /// 下载弹幕 /// /// - public string DownloadDanmaku(DownloadingItem downloading) + public override string DownloadDanmaku(DownloadingItem downloading) { - // 更新状态显示 - downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); - downloading.DownloadContent = DictionaryResource.GetString("DownloadingDanmaku"); - // 下载大小 - downloading.DownloadingFileSize = string.Empty; - // 下载速度 - downloading.SpeedDisplay = string.Empty; - - string title = $"{downloading.Name}"; - string assFile = $"{downloading.DownloadBase.FilePath}.ass"; - - // 记录本次下载的文件 - if (!downloading.Downloading.DownloadFiles.ContainsKey("danmaku")) - { - downloading.Downloading.DownloadFiles.Add("danmaku", assFile); - } - - int screenWidth = SettingsManager.GetInstance().GetDanmakuScreenWidth(); - int screenHeight = SettingsManager.GetInstance().GetDanmakuScreenHeight(); - //if (SettingsManager.GetInstance().IsCustomDanmakuResolution() != AllowStatus.YES) - //{ - // if (downloadingEntity.Width > 0 && downloadingEntity.Height > 0) - // { - // screenWidth = downloadingEntity.Width; - // screenHeight = downloadingEntity.Height; - // } - //} - - // 字幕配置 - Config subtitleConfig = new Config - { - Title = title, - ScreenWidth = screenWidth, - ScreenHeight = screenHeight, - FontName = SettingsManager.GetInstance().GetDanmakuFontName(), - BaseFontSize = SettingsManager.GetInstance().GetDanmakuFontSize(), - LineCount = SettingsManager.GetInstance().GetDanmakuLineCount(), - LayoutAlgorithm = SettingsManager.GetInstance().GetDanmakuLayoutAlgorithm().ToString("G").ToLower(), // async/sync - TuneDuration = 0, - DropOffset = 0, - BottomMargin = 0, - CustomOffset = 0 - }; - - Core.Danmaku2Ass.Bilibili.GetInstance() - .SetTopFilter(SettingsManager.GetInstance().GetDanmakuTopFilter() == AllowStatus.YES) - .SetBottomFilter(SettingsManager.GetInstance().GetDanmakuBottomFilter() == AllowStatus.YES) - .SetScrollFilter(SettingsManager.GetInstance().GetDanmakuScrollFilter() == AllowStatus.YES) - .Create(downloading.DownloadBase.Avid, downloading.DownloadBase.Cid, subtitleConfig, assFile); - - return assFile; + return BaseDownloadDanmaku(downloading); } /// /// 下载字幕 /// /// - public List DownloadSubtitle(DownloadingItem downloading) + public override List DownloadSubtitle(DownloadingItem downloading) { - // 更新状态显示 - downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); - downloading.DownloadContent = DictionaryResource.GetString("DownloadingSubtitle"); - // 下载大小 - downloading.DownloadingFileSize = string.Empty; - // 下载速度 - downloading.SpeedDisplay = string.Empty; - - List srtFiles = new List(); - - var subRipTexts = VideoStream.GetSubtitle(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid); - if (subRipTexts == null) - { - return null; - } - - foreach (var subRip in subRipTexts) - { - string srtFile = $"{downloading.DownloadBase.FilePath}_{subRip.LanDoc}.srt"; - try - { - File.WriteAllText(srtFile, subRip.SrtString); - - // 记录本次下载的文件 - if (!downloading.Downloading.DownloadFiles.ContainsKey("subtitle")) - { - downloading.Downloading.DownloadFiles.Add("subtitle", srtFile); - } - - srtFiles.Add(srtFile); - } - catch (Exception e) - { - Core.Utils.Debugging.Console.PrintLine("DownloadSubtitle()发生异常: {0}", e); - LogManager.Error("DownloadSubtitle()", e); - } - } - - return srtFiles; + return BaseDownloadSubtitle(downloading); } /// @@ -352,84 +164,22 @@ namespace DownKyi.Services.Download /// /// /// - public string MixedFlow(DownloadingItem downloading, string audioUid, string videoUid) + public override string MixedFlow(DownloadingItem downloading, string audioUid, string videoUid) { - // 更新状态显示 - downloading.DownloadStatusTitle = DictionaryResource.GetString("MixedFlow"); - downloading.DownloadContent = DictionaryResource.GetString("DownloadingVideo"); - // 下载大小 - downloading.DownloadingFileSize = string.Empty; - // 下载速度 - downloading.SpeedDisplay = string.Empty; - if (videoUid == nullMark) { return null; } - - string finalFile = $"{downloading.DownloadBase.FilePath}.mp4"; - if (videoUid == null) - { - finalFile = $"{downloading.DownloadBase.FilePath}.aac"; - } - - // 合并音视频 - FFmpegHelper.MergeVideo(audioUid, videoUid, finalFile); - - // 获取文件大小 - if (File.Exists(finalFile)) - { - FileInfo info = new FileInfo(finalFile); - downloading.FileSize = Format.FormatFileSize(info.Length); - } - else - { - downloading.FileSize = Format.FormatFileSize(0); - } - - return finalFile; + return BaseMixedFlow(downloading, audioUid, videoUid); } /// /// 解析视频流的下载链接 /// /// - public void Parse(DownloadingItem downloading) + public override void Parse(DownloadingItem downloading) { - // 更新状态显示 - downloading.DownloadStatusTitle = DictionaryResource.GetString("Parsing"); - downloading.DownloadContent = string.Empty; - // 下载大小 - downloading.DownloadingFileSize = string.Empty; - // 下载速度 - downloading.SpeedDisplay = string.Empty; - - if (downloading.PlayUrl != null && downloading.Downloading.DownloadStatus == DownloadStatus.NOT_STARTED) - { - // 设置下载状态 - downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; - - return; - } - - // 设置下载状态 - downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; - - // 解析 - switch (downloading.Downloading.PlayStreamType) - { - case PlayStreamType.VIDEO: - downloading.PlayUrl = VideoStream.GetVideoPlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid); - break; - case PlayStreamType.BANGUMI: - downloading.PlayUrl = VideoStream.GetBangumiPlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid); - break; - case PlayStreamType.CHEESE: - downloading.PlayUrl = VideoStream.GetCheesePlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid, downloading.DownloadBase.EpisodeId); - break; - default: - break; - } + BaseParse(downloading); } /// @@ -437,48 +187,8 @@ namespace DownKyi.Services.Download /// private async Task EndTask() { - // 结束任务 - tokenSource.Cancel(); - - await workTask; - - //先简单等待一下 - - // 下载数据存储服务 - DownloadStorageService downloadStorageService = new DownloadStorageService(); - // 保存数据 - foreach (DownloadingItem item in downloadingList) - { - switch (item.Downloading.DownloadStatus) - { - case DownloadStatus.NOT_STARTED: - break; - case DownloadStatus.WAIT_FOR_DOWNLOAD: - break; - case DownloadStatus.PAUSE_STARTED: - break; - case DownloadStatus.PAUSE: - break; - case DownloadStatus.DOWNLOADING: - // TODO 添加设置让用户选择重启后是否自动开始下载 - item.Downloading.DownloadStatus = DownloadStatus.WAIT_FOR_DOWNLOAD; - //item.Downloading.DownloadStatus = DownloadStatus.PAUSE; - break; - case DownloadStatus.DOWNLOAD_SUCCEED: - case DownloadStatus.DOWNLOAD_FAILED: - break; - default: - break; - } - - item.Progress = 0; - - downloadStorageService.UpdateDownloading(item); - } - foreach (DownloadedItem item in downloadedList) - { - downloadStorageService.UpdateDownloaded(item); - } + // 停止基本任务 + await BaseEndTask(); // 关闭Aria服务器 await CloseAriaServer(); @@ -500,395 +210,15 @@ namespace DownKyi.Services.Download // 启动Aria服务器 StartAriaServer(); - tokenSource = new CancellationTokenSource(); - cancellationToken = tokenSource.Token; - workTask = Task.Run(DoWork); - } - - /// - /// 执行任务 - /// - private async Task DoWork() - { - // 上次循环时正在下载的数量 - int lastDownloadingCount = 0; - - while (true) - { - int maxDownloading = SettingsManager.GetInstance().GetAriaMaxConcurrentDownloads(); - int downloadingCount = 0; - - try - { - downloadingTasks.RemoveAll((m) => m.IsCompleted); - foreach (DownloadingItem downloading in downloadingList) - { - if (downloading.Downloading.DownloadStatus == DownloadStatus.DOWNLOADING) - { - downloadingCount++; - } - } - - foreach (DownloadingItem downloading in downloadingList) - { - if (downloadingCount >= maxDownloading) - { - break; - } - - // 开始下载 - if (downloading.Downloading.DownloadStatus == DownloadStatus.NOT_STARTED || downloading.Downloading.DownloadStatus == DownloadStatus.WAIT_FOR_DOWNLOAD) - { - //这里需要立刻设置状态,否则如果SingleDownload没有及时执行,会重复创建任务 - downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; - downloadingTasks.Add(SingleDownload(downloading)); - downloadingCount++; - } - } - } - catch (InvalidOperationException e) - { - Core.Utils.Debugging.Console.PrintLine("Start DoWork()发生InvalidOperationException异常: {0}", e); - LogManager.Error("Start DoWork() InvalidOperationException", e); - } - catch (Exception e) - { - Core.Utils.Debugging.Console.PrintLine("Start DoWork()发生异常: {0}", e); - LogManager.Error("Start DoWork()", e); - } - - // 判断是否该结束线程,若为true,跳出while循环 - if (cancellationToken.IsCancellationRequested) - { - Core.Utils.Debugging.Console.PrintLine("AriaDownloadService: 下载服务结束,跳出while循环"); - LogManager.Debug(Tag, "下载服务结束"); - break; - } - - // 判断下载列表中的视频是否全部下载完成 - if (lastDownloadingCount > 0 && downloadingList.Count == 0 && downloadedList.Count > 0) - { - AfterDownload(); - } - lastDownloadingCount = downloadingList.Count; - - // 降低CPU占用 - await Task.Delay(500); - } - - await Task.WhenAny(Task.WhenAll(downloadingTasks), Task.Delay(30000)); - foreach (Task tsk in downloadingTasks.FindAll((m) => !m.IsCompleted)) - { - Core.Utils.Debugging.Console.PrintLine("AriaDownloadService: 任务结束超时"); - LogManager.Debug(Tag, "任务结束超时"); - } - } - - /// - /// 下载一个视频 - /// - /// - /// - private async Task SingleDownload(DownloadingItem downloading) - { - // 路径 - downloading.DownloadBase.FilePath = downloading.DownloadBase.FilePath.Replace("\\", "/"); - string[] temp = downloading.DownloadBase.FilePath.Split('/'); - //string path = downloading.DownloadBase.FilePath.Replace(temp[temp.Length - 1], ""); - string path = downloading.DownloadBase.FilePath.TrimEnd(temp[temp.Length - 1].ToCharArray()); - - // 路径不存在则创建 - if (!Directory.Exists(path)) - { - Directory.CreateDirectory(path); - } - - try - { - await Task.Run(new Action(() => - { - // 初始化 - downloading.DownloadStatusTitle = string.Empty; - downloading.DownloadContent = string.Empty; - //downloading.Downloading.DownloadFiles.Clear(); - - // 解析并依次下载音频、视频、弹幕、字幕、封面等内容 - Parse(downloading); - - // 暂停 - Pause(downloading); - - string audioUid = null; - // 如果需要下载音频 - if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"]) - { - //audioUid = DownloadAudio(downloading); - for (int i = 0; i < retry; i++) - { - audioUid = DownloadAudio(downloading); - if (audioUid != null && audioUid != nullMark) - { - break; - } - } - } - if (audioUid == nullMark) - { - DownloadFailed(downloading); - return; - } - - // 暂停 - Pause(downloading); - - string videoUid = null; - // 如果需要下载视频 - if (downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) - { - //videoUid = DownloadVideo(downloading); - for (int i = 0; i < retry; i++) - { - videoUid = DownloadVideo(downloading); - if (videoUid != null && videoUid != nullMark) - { - break; - } - } - } - if (videoUid == nullMark) - { - DownloadFailed(downloading); - return; - } - - // 暂停 - Pause(downloading); - - string outputDanmaku = null; - // 如果需要下载弹幕 - if (downloading.DownloadBase.NeedDownloadContent["downloadDanmaku"]) - { - outputDanmaku = DownloadDanmaku(downloading); - } - - // 暂停 - Pause(downloading); - - List outputSubtitles = null; - // 如果需要下载字幕 - if (downloading.DownloadBase.NeedDownloadContent["downloadSubtitle"]) - { - outputSubtitles = DownloadSubtitle(downloading); - } - - // 暂停 - Pause(downloading); - - string outputCover = null; - string outputPageCover = null; - // 如果需要下载封面 - if (downloading.DownloadBase.NeedDownloadContent["downloadCover"]) - { - string fileName = $"{downloading.DownloadBase.FilePath}.{GetImageExtension(downloading.DownloadBase.PageCoverUrl)}"; - - // page的封面 - outputPageCover = DownloadCover(downloading, downloading.DownloadBase.PageCoverUrl, fileName); - // 封面 - outputCover = DownloadCover(downloading, downloading.DownloadBase.CoverUrl, $"{path}/Cover.{GetImageExtension(downloading.DownloadBase.CoverUrl)}"); - } - - // 暂停 - Pause(downloading); - - // 混流 - string outputMedia = string.Empty; - if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"] || downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) - { - outputMedia = MixedFlow(downloading, audioUid, videoUid); - } - - // 这里本来只有IsExist,没有pause,不知道怎么处理 - // 是否存在 - //isExist = IsExist(downloading); - //if (!isExist.Result) - //{ - // return; - //} - - // 检测音频、视频是否下载成功 - bool isMediaSuccess = true; - if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"] || downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) - { - // 只有下载音频不下载视频时才输出aac - // 只要下载视频就输出mp4 - if (File.Exists(outputMedia)) - { - // 成功 - isMediaSuccess = true; - } - else - { - isMediaSuccess = false; - } - } - - // 检测弹幕是否下载成功 - bool isDanmakuSuccess = true; - if (downloading.DownloadBase.NeedDownloadContent["downloadDanmaku"]) - { - if (File.Exists(outputDanmaku)) - { - // 成功 - isDanmakuSuccess = true; - } - else - { - isDanmakuSuccess = false; - } - } - - // 检测字幕是否下载成功 - bool isSubtitleSuccess = true; - if (downloading.DownloadBase.NeedDownloadContent["downloadSubtitle"]) - { - if (outputSubtitles == null) - { - // 为null时表示不存在字幕 - } - else - { - foreach (string subtitle in outputSubtitles) - { - if (!File.Exists(subtitle)) - { - // 如果有一个不存在则失败 - isSubtitleSuccess = false; - } - } - } - } - - // 检测封面是否下载成功 - bool isCover = true; - if (downloading.DownloadBase.NeedDownloadContent["downloadCover"]) - { - if (File.Exists(outputCover) || File.Exists(outputPageCover)) - { - // 成功 - isCover = true; - } - else - { - isCover = false; - } - } - - if (!isMediaSuccess || !isDanmakuSuccess || !isSubtitleSuccess || !isCover) - { - DownloadFailed(downloading); - return; - } - - // 下载完成后处理 - Downloaded downloaded = new Downloaded - { - MaxSpeedDisplay = Format.FormatSpeed(downloading.Downloading.MaxSpeed), - }; - // 设置完成时间 - downloaded.SetFinishedTimestamp(new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds()); - - DownloadedItem downloadedItem = new DownloadedItem - { - DownloadBase = downloading.DownloadBase, - Downloaded = downloaded - }; - - App.PropertyChangeAsync(new Action(() => - { - // 加入到下载完成list中,并从下载中list去除 - downloadedList.Add(downloadedItem); - downloadingList.Remove(downloading); - - // 下载完成列表排序 - DownloadFinishedSort finishedSort = SettingsManager.GetInstance().GetDownloadFinishedSort(); - App.SortDownloadedList(finishedSort); - })); - })); - } - catch (OperationCanceledException) - { - } - } - - /// - /// 下载失败后的处理 - /// - /// - private void DownloadFailed(DownloadingItem downloading) - { - downloading.DownloadStatusTitle = DictionaryResource.GetString("DownloadFailed"); - downloading.DownloadContent = string.Empty; - downloading.DownloadingFileSize = string.Empty; - downloading.SpeedDisplay = string.Empty; - - downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOAD_FAILED; - downloading.StartOrPause = ButtonIcon.Instance().Retry; - downloading.StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); - } - - /// - /// 下载完成后的操作 - /// - private void AfterDownload() - { - AfterDownloadOperation operation = SettingsManager.GetInstance().GetAfterDownloadOperation(); - switch (operation) - { - case AfterDownloadOperation.NONE: - // 没有操作 - break; - case AfterDownloadOperation.OPEN_FOLDER: - // 打开文件夹 - break; - case AfterDownloadOperation.CLOSE_APP: - // 关闭程序 - App.PropertyChangeAsync(() => - { - System.Windows.Application.Current.Shutdown(); - }); - break; - case AfterDownloadOperation.CLOSE_SYSTEM: - // 关机 - System.Diagnostics.Process.Start("shutdown.exe", "-s"); - break; - default: - break; - } - } - - /// - /// 获取图片的扩展名 - /// - /// - /// - private string GetImageExtension(string coverUrl) - { - if (coverUrl == null) - { - return string.Empty; - } - - // 图片的扩展名 - string[] temp = coverUrl.Split('.'); - string fileExtension = temp[temp.Length - 1]; - return fileExtension; + // 启动基本服务 + BaseStart(); } /// /// 强制暂停 /// /// - private void Pause(DownloadingItem downloading) + protected override void Pause(DownloadingItem downloading) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/DownKyi/Services/Download/BuiltinDownloadService.cs b/DownKyi/Services/Download/BuiltinDownloadService.cs new file mode 100644 index 0000000..824deb7 --- /dev/null +++ b/DownKyi/Services/Download/BuiltinDownloadService.cs @@ -0,0 +1,181 @@ +using DownKyi.Core.BiliApi.VideoStream.Models; +using DownKyi.Models; +using DownKyi.Utils; +using DownKyi.ViewModels.DownloadManager; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; + +namespace DownKyi.Services.Download +{ + public class BuiltinDownloadService : DownloadService, IDownloadService + { + public BuiltinDownloadService(ObservableCollection downloadingList, ObservableCollection downloadedList) : base(downloadingList, downloadedList) + { + Tag = "BuiltinDownloadService"; + } + + #region 音视频 + + /// + /// 下载音频,返回下载文件路径 + /// + /// + /// + public override string DownloadAudio(DownloadingItem downloading) + { + PlayUrlDashVideo downloadAudio = BaseDownloadAudio(downloading); + + return DownloadVideo(downloading, downloadAudio); + } + + /// + /// 下载视频,返回下载文件路径 + /// + /// + /// + public override string DownloadVideo(DownloadingItem downloading) + { + PlayUrlDashVideo downloadVideo = BaseDownloadVideo(downloading); + + return DownloadVideo(downloading, downloadVideo); + } + + /// + /// 将下载音频和视频的函数中相同代码抽象出来 + /// + /// + /// + /// + private string DownloadVideo(DownloadingItem downloading, PlayUrlDashVideo downloadVideo) + { + + return null; + } + + #endregion + + /// + /// 下载封面 + /// + /// + /// + /// + /// + public override string DownloadCover(DownloadingItem downloading, string coverUrl, string fileName) + { + return BaseDownloadCover(downloading, coverUrl, fileName); + } + + /// + /// 下载弹幕 + /// + /// + /// + public override string DownloadDanmaku(DownloadingItem downloading) + { + return BaseDownloadDanmaku(downloading); + } + + /// + /// 下载字幕 + /// + /// + /// + public override List DownloadSubtitle(DownloadingItem downloading) + { + return BaseDownloadSubtitle(downloading); + } + + /// + /// 混流音频和视频 + /// + /// + /// + /// + /// + public override string MixedFlow(DownloadingItem downloading, string audioUid, string videoUid) + { + return BaseMixedFlow(downloading, audioUid, videoUid); + } + + /// + /// 解析视频流的下载链接 + /// + /// + public override void Parse(DownloadingItem downloading) + { + BaseParse(downloading); + } + + /// + /// 停止下载服务(转换await和Task.Wait两种调用形式) + /// + private async Task EndTask() + { + // 停止基本任务 + await BaseEndTask(); + } + + /// + /// 停止下载服务 + /// + public void End() + { + Task.Run(EndTask).Wait(); + } + + public void Start() + { + // 启动基本服务 + BaseStart(); + } + + /// + /// 强制暂停 + /// + /// + protected override void Pause(DownloadingItem downloading) + { + cancellationToken.ThrowIfCancellationRequested(); + + downloading.DownloadStatusTitle = DictionaryResource.GetString("Pausing"); + if (downloading.Downloading.DownloadStatus == DownloadStatus.PAUSE) + { + throw new OperationCanceledException("Stop thread by pause"); + } + // 是否存在 + var isExist = IsExist(downloading); + if (!isExist) + { + throw new OperationCanceledException("Task is deleted"); + } + } + + /// + /// 是否存在于下载列表中 + /// + /// + /// + private bool IsExist(DownloadingItem downloading) + { + bool isExist = downloadingList.Contains(downloading); + if (isExist) + { + return true; + } + else + { + return false; + } + } + + #region 内建下载器 + + + + #endregion + + } +} diff --git a/DownKyi/Services/Download/DownloadService.cs b/DownKyi/Services/Download/DownloadService.cs index f8612a8..0107e35 100644 --- a/DownKyi/Services/Download/DownloadService.cs +++ b/DownKyi/Services/Download/DownloadService.cs @@ -1,15 +1,39 @@ -using DownKyi.ViewModels.DownloadManager; +using DownKyi.Core.BiliApi.VideoStream; +using DownKyi.Core.BiliApi.VideoStream.Models; +using DownKyi.Core.Danmaku2Ass; +using DownKyi.Core.FFmpeg; +using DownKyi.Core.Logging; +using DownKyi.Core.Settings; +using DownKyi.Core.Storage; +using DownKyi.Core.Utils; +using DownKyi.Images; +using DownKyi.Models; +using DownKyi.Utils; +using DownKyi.ViewModels.DownloadManager; +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; +using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace DownKyi.Services.Download { - public class DownloadService + public abstract class DownloadService { protected string Tag = "DownloadService"; protected ObservableCollection downloadingList; protected ObservableCollection downloadedList; + protected Task workTask; + protected CancellationTokenSource tokenSource; + protected CancellationToken cancellationToken; + protected List downloadingTasks = new List(); + + protected readonly int retry = 5; + protected readonly string nullMark = ""; + /// /// 初始化 /// @@ -21,5 +45,733 @@ namespace DownKyi.Services.Download this.downloadedList = downloadedList; } + protected PlayUrlDashVideo BaseDownloadAudio(DownloadingItem downloading) + { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingAudio"); + + // 如果没有Dash,返回null + if (downloading.PlayUrl == null || downloading.PlayUrl.Dash == null) { return null; } + + // 如果audio列表没有内容,则返回null + if (downloading.PlayUrl.Dash.Audio == null) { return null; } + else if (downloading.PlayUrl.Dash.Audio.Count == 0) { return null; } + + // 根据音频id匹配 + PlayUrlDashVideo downloadAudio = null; + foreach (PlayUrlDashVideo audio in downloading.PlayUrl.Dash.Audio) + { + if (audio.Id == downloading.AudioCodec.Id) + { + downloadAudio = audio; + break; + } + } + + // 避免Dolby==null及其它未知情况,直接使用异常捕获 + try + { + // Dolby Atmos + if (downloading.AudioCodec.Id == 30250) + { + downloadAudio = downloading.PlayUrl.Dash.Dolby.Audio[0]; + } + } + catch (Exception) { } + + return downloadAudio; + } + + protected PlayUrlDashVideo BaseDownloadVideo(DownloadingItem downloading) + { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingVideo"); + + // 如果没有Dash,返回null + if (downloading.PlayUrl == null || downloading.PlayUrl.Dash == null) { return null; } + + // 如果Video列表没有内容,则返回null + if (downloading.PlayUrl.Dash.Video == null) { return null; } + else if (downloading.PlayUrl.Dash.Video.Count == 0) { return null; } + + // 根据视频编码匹配 + PlayUrlDashVideo downloadVideo = null; + foreach (PlayUrlDashVideo video in downloading.PlayUrl.Dash.Video) + { + if (video.Id == downloading.Resolution.Id && Utils.GetVideoCodecName(video.Codecs) == downloading.VideoCodecName) + { + downloadVideo = video; + break; + } + } + + return downloadVideo; + } + + protected string BaseDownloadCover(DownloadingItem downloading, string coverUrl, string fileName) + { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingCover"); + // 下载大小 + downloading.DownloadingFileSize = string.Empty; + // 下载速度 + downloading.SpeedDisplay = string.Empty; + + // 查询、保存封面 + StorageCover storageCover = new StorageCover(); + string cover = storageCover.GetCover(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid, coverUrl); + if (cover == null) + { + return null; + } + + // 复制图片到指定位置 + try + { + File.Copy(cover, fileName, true); + + // 记录本次下载的文件 + if (!downloading.Downloading.DownloadFiles.ContainsKey(coverUrl)) + { + downloading.Downloading.DownloadFiles.Add(coverUrl, fileName); + } + + return fileName; + } + catch (Exception e) + { + Core.Utils.Debugging.Console.PrintLine(e); + LogManager.Error(Tag, e); + } + + return null; + } + + protected string BaseDownloadDanmaku(DownloadingItem downloading) + { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingDanmaku"); + // 下载大小 + downloading.DownloadingFileSize = string.Empty; + // 下载速度 + downloading.SpeedDisplay = string.Empty; + + string title = $"{downloading.Name}"; + string assFile = $"{downloading.DownloadBase.FilePath}.ass"; + + // 记录本次下载的文件 + if (!downloading.Downloading.DownloadFiles.ContainsKey("danmaku")) + { + downloading.Downloading.DownloadFiles.Add("danmaku", assFile); + } + + int screenWidth = SettingsManager.GetInstance().GetDanmakuScreenWidth(); + int screenHeight = SettingsManager.GetInstance().GetDanmakuScreenHeight(); + //if (SettingsManager.GetInstance().IsCustomDanmakuResolution() != AllowStatus.YES) + //{ + // if (downloadingEntity.Width > 0 && downloadingEntity.Height > 0) + // { + // screenWidth = downloadingEntity.Width; + // screenHeight = downloadingEntity.Height; + // } + //} + + // 字幕配置 + Config subtitleConfig = new Config + { + Title = title, + ScreenWidth = screenWidth, + ScreenHeight = screenHeight, + FontName = SettingsManager.GetInstance().GetDanmakuFontName(), + BaseFontSize = SettingsManager.GetInstance().GetDanmakuFontSize(), + LineCount = SettingsManager.GetInstance().GetDanmakuLineCount(), + LayoutAlgorithm = SettingsManager.GetInstance().GetDanmakuLayoutAlgorithm().ToString("G").ToLower(), // async/sync + TuneDuration = 0, + DropOffset = 0, + BottomMargin = 0, + CustomOffset = 0 + }; + + Core.Danmaku2Ass.Bilibili.GetInstance() + .SetTopFilter(SettingsManager.GetInstance().GetDanmakuTopFilter() == AllowStatus.YES) + .SetBottomFilter(SettingsManager.GetInstance().GetDanmakuBottomFilter() == AllowStatus.YES) + .SetScrollFilter(SettingsManager.GetInstance().GetDanmakuScrollFilter() == AllowStatus.YES) + .Create(downloading.DownloadBase.Avid, downloading.DownloadBase.Cid, subtitleConfig, assFile); + + return assFile; + } + + protected List BaseDownloadSubtitle(DownloadingItem downloading) + { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingSubtitle"); + // 下载大小 + downloading.DownloadingFileSize = string.Empty; + // 下载速度 + downloading.SpeedDisplay = string.Empty; + + List srtFiles = new List(); + + var subRipTexts = VideoStream.GetSubtitle(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid); + if (subRipTexts == null) + { + return null; + } + + foreach (var subRip in subRipTexts) + { + string srtFile = $"{downloading.DownloadBase.FilePath}_{subRip.LanDoc}.srt"; + try + { + File.WriteAllText(srtFile, subRip.SrtString); + + // 记录本次下载的文件 + if (!downloading.Downloading.DownloadFiles.ContainsKey("subtitle")) + { + downloading.Downloading.DownloadFiles.Add("subtitle", srtFile); + } + + srtFiles.Add(srtFile); + } + catch (Exception e) + { + Core.Utils.Debugging.Console.PrintLine($"{Tag}.DownloadSubtitle()发生异常: {0}", e); + LogManager.Error($"{Tag}.DownloadSubtitle()", e); + } + } + + return srtFiles; + } + + protected string BaseMixedFlow(DownloadingItem downloading, string audioUid, string videoUid) + { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("MixedFlow"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingVideo"); + // 下载大小 + downloading.DownloadingFileSize = string.Empty; + // 下载速度 + downloading.SpeedDisplay = string.Empty; + + //if (videoUid == nullMark) + //{ + // return null; + //} + + string finalFile = $"{downloading.DownloadBase.FilePath}.mp4"; + if (videoUid == null) + { + finalFile = $"{downloading.DownloadBase.FilePath}.aac"; + } + + // 合并音视频 + FFmpegHelper.MergeVideo(audioUid, videoUid, finalFile); + + // 获取文件大小 + if (File.Exists(finalFile)) + { + FileInfo info = new FileInfo(finalFile); + downloading.FileSize = Format.FormatFileSize(info.Length); + } + else + { + downloading.FileSize = Format.FormatFileSize(0); + } + + return finalFile; + } + + protected void BaseParse(DownloadingItem downloading) + { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("Parsing"); + downloading.DownloadContent = string.Empty; + // 下载大小 + downloading.DownloadingFileSize = string.Empty; + // 下载速度 + downloading.SpeedDisplay = string.Empty; + + if (downloading.PlayUrl != null && downloading.Downloading.DownloadStatus == DownloadStatus.NOT_STARTED) + { + // 设置下载状态 + downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; + + return; + } + + // 设置下载状态 + downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; + + // 解析 + switch (downloading.Downloading.PlayStreamType) + { + case PlayStreamType.VIDEO: + downloading.PlayUrl = VideoStream.GetVideoPlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid); + break; + case PlayStreamType.BANGUMI: + downloading.PlayUrl = VideoStream.GetBangumiPlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid); + break; + case PlayStreamType.CHEESE: + downloading.PlayUrl = VideoStream.GetCheesePlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid, downloading.DownloadBase.EpisodeId); + break; + default: + break; + } + } + + /// + /// 执行任务 + /// + protected async Task DoWork() + { + // 上次循环时正在下载的数量 + int lastDownloadingCount = 0; + + while (true) + { + int maxDownloading = SettingsManager.GetInstance().GetAriaMaxConcurrentDownloads(); + int downloadingCount = 0; + + try + { + downloadingTasks.RemoveAll((m) => m.IsCompleted); + foreach (DownloadingItem downloading in downloadingList) + { + if (downloading.Downloading.DownloadStatus == DownloadStatus.DOWNLOADING) + { + downloadingCount++; + } + } + + foreach (DownloadingItem downloading in downloadingList) + { + if (downloadingCount >= maxDownloading) + { + break; + } + + // 开始下载 + if (downloading.Downloading.DownloadStatus == DownloadStatus.NOT_STARTED || downloading.Downloading.DownloadStatus == DownloadStatus.WAIT_FOR_DOWNLOAD) + { + //这里需要立刻设置状态,否则如果SingleDownload没有及时执行,会重复创建任务 + downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; + downloadingTasks.Add(SingleDownload(downloading)); + downloadingCount++; + } + } + } + catch (InvalidOperationException e) + { + Core.Utils.Debugging.Console.PrintLine("Start DoWork()发生InvalidOperationException异常: {0}", e); + LogManager.Error("Start DoWork() InvalidOperationException", e); + } + catch (Exception e) + { + Core.Utils.Debugging.Console.PrintLine("Start DoWork()发生异常: {0}", e); + LogManager.Error("Start DoWork()", e); + } + + // 判断是否该结束线程,若为true,跳出while循环 + if (cancellationToken.IsCancellationRequested) + { + Core.Utils.Debugging.Console.PrintLine("AriaDownloadService: 下载服务结束,跳出while循环"); + LogManager.Debug(Tag, "下载服务结束"); + break; + } + + // 判断下载列表中的视频是否全部下载完成 + if (lastDownloadingCount > 0 && downloadingList.Count == 0 && downloadedList.Count > 0) + { + AfterDownload(); + } + lastDownloadingCount = downloadingList.Count; + + // 降低CPU占用 + await Task.Delay(500); + } + + await Task.WhenAny(Task.WhenAll(downloadingTasks), Task.Delay(30000)); + foreach (Task tsk in downloadingTasks.FindAll((m) => !m.IsCompleted)) + { + Core.Utils.Debugging.Console.PrintLine("AriaDownloadService: 任务结束超时"); + LogManager.Debug(Tag, "任务结束超时"); + } + } + + /// + /// 下载一个视频 + /// + /// + /// + private async Task SingleDownload(DownloadingItem downloading) + { + // 路径 + downloading.DownloadBase.FilePath = downloading.DownloadBase.FilePath.Replace("\\", "/"); + string[] temp = downloading.DownloadBase.FilePath.Split('/'); + //string path = downloading.DownloadBase.FilePath.Replace(temp[temp.Length - 1], ""); + string path = downloading.DownloadBase.FilePath.TrimEnd(temp[temp.Length - 1].ToCharArray()); + + // 路径不存在则创建 + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + try + { + await Task.Run(new Action(() => + { + // 初始化 + downloading.DownloadStatusTitle = string.Empty; + downloading.DownloadContent = string.Empty; + //downloading.Downloading.DownloadFiles.Clear(); + + // 解析并依次下载音频、视频、弹幕、字幕、封面等内容 + Parse(downloading); + + // 暂停 + Pause(downloading); + + string audioUid = null; + // 如果需要下载音频 + if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"]) + { + //audioUid = DownloadAudio(downloading); + for (int i = 0; i < retry; i++) + { + audioUid = DownloadAudio(downloading); + if (audioUid != null && audioUid != nullMark) + { + break; + } + } + } + if (audioUid == nullMark) + { + DownloadFailed(downloading); + return; + } + + // 暂停 + Pause(downloading); + + string videoUid = null; + // 如果需要下载视频 + if (downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) + { + //videoUid = DownloadVideo(downloading); + for (int i = 0; i < retry; i++) + { + videoUid = DownloadVideo(downloading); + if (videoUid != null && videoUid != nullMark) + { + break; + } + } + } + if (videoUid == nullMark) + { + DownloadFailed(downloading); + return; + } + + // 暂停 + Pause(downloading); + + string outputDanmaku = null; + // 如果需要下载弹幕 + if (downloading.DownloadBase.NeedDownloadContent["downloadDanmaku"]) + { + outputDanmaku = DownloadDanmaku(downloading); + } + + // 暂停 + Pause(downloading); + + List outputSubtitles = null; + // 如果需要下载字幕 + if (downloading.DownloadBase.NeedDownloadContent["downloadSubtitle"]) + { + outputSubtitles = DownloadSubtitle(downloading); + } + + // 暂停 + Pause(downloading); + + string outputCover = null; + string outputPageCover = null; + // 如果需要下载封面 + if (downloading.DownloadBase.NeedDownloadContent["downloadCover"]) + { + string fileName = $"{downloading.DownloadBase.FilePath}.{GetImageExtension(downloading.DownloadBase.PageCoverUrl)}"; + + // page的封面 + outputPageCover = DownloadCover(downloading, downloading.DownloadBase.PageCoverUrl, fileName); + // 封面 + outputCover = DownloadCover(downloading, downloading.DownloadBase.CoverUrl, $"{path}/Cover.{GetImageExtension(downloading.DownloadBase.CoverUrl)}"); + } + + // 暂停 + Pause(downloading); + + // 混流 + string outputMedia = string.Empty; + if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"] || downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) + { + outputMedia = MixedFlow(downloading, audioUid, videoUid); + } + + // 这里本来只有IsExist,没有pause,不知道怎么处理 + // 是否存在 + //isExist = IsExist(downloading); + //if (!isExist.Result) + //{ + // return; + //} + + // 检测音频、视频是否下载成功 + bool isMediaSuccess = true; + if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"] || downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) + { + // 只有下载音频不下载视频时才输出aac + // 只要下载视频就输出mp4 + if (File.Exists(outputMedia)) + { + // 成功 + isMediaSuccess = true; + } + else + { + isMediaSuccess = false; + } + } + + // 检测弹幕是否下载成功 + bool isDanmakuSuccess = true; + if (downloading.DownloadBase.NeedDownloadContent["downloadDanmaku"]) + { + if (File.Exists(outputDanmaku)) + { + // 成功 + isDanmakuSuccess = true; + } + else + { + isDanmakuSuccess = false; + } + } + + // 检测字幕是否下载成功 + bool isSubtitleSuccess = true; + if (downloading.DownloadBase.NeedDownloadContent["downloadSubtitle"]) + { + if (outputSubtitles == null) + { + // 为null时表示不存在字幕 + } + else + { + foreach (string subtitle in outputSubtitles) + { + if (!File.Exists(subtitle)) + { + // 如果有一个不存在则失败 + isSubtitleSuccess = false; + } + } + } + } + + // 检测封面是否下载成功 + bool isCover = true; + if (downloading.DownloadBase.NeedDownloadContent["downloadCover"]) + { + if (File.Exists(outputCover) || File.Exists(outputPageCover)) + { + // 成功 + isCover = true; + } + else + { + isCover = false; + } + } + + if (!isMediaSuccess || !isDanmakuSuccess || !isSubtitleSuccess || !isCover) + { + DownloadFailed(downloading); + return; + } + + // 下载完成后处理 + Downloaded downloaded = new Downloaded + { + MaxSpeedDisplay = Format.FormatSpeed(downloading.Downloading.MaxSpeed), + }; + // 设置完成时间 + downloaded.SetFinishedTimestamp(new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds()); + + DownloadedItem downloadedItem = new DownloadedItem + { + DownloadBase = downloading.DownloadBase, + Downloaded = downloaded + }; + + App.PropertyChangeAsync(new Action(() => + { + // 加入到下载完成list中,并从下载中list去除 + downloadedList.Add(downloadedItem); + downloadingList.Remove(downloading); + + // 下载完成列表排序 + DownloadFinishedSort finishedSort = SettingsManager.GetInstance().GetDownloadFinishedSort(); + App.SortDownloadedList(finishedSort); + })); + })); + } + catch (OperationCanceledException) + { + } + } + + /// + /// 下载失败后的处理 + /// + /// + protected void DownloadFailed(DownloadingItem downloading) + { + downloading.DownloadStatusTitle = DictionaryResource.GetString("DownloadFailed"); + downloading.DownloadContent = string.Empty; + downloading.DownloadingFileSize = string.Empty; + downloading.SpeedDisplay = string.Empty; + + downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOAD_FAILED; + downloading.StartOrPause = ButtonIcon.Instance().Retry; + downloading.StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); + } + + /// + /// 获取图片的扩展名 + /// + /// + /// + protected string GetImageExtension(string coverUrl) + { + if (coverUrl == null) + { + return string.Empty; + } + + // 图片的扩展名 + string[] temp = coverUrl.Split('.'); + string fileExtension = temp[temp.Length - 1]; + return fileExtension; + } + + /// + /// 下载完成后的操作 + /// + protected void AfterDownload() + { + AfterDownloadOperation operation = SettingsManager.GetInstance().GetAfterDownloadOperation(); + switch (operation) + { + case AfterDownloadOperation.NONE: + // 没有操作 + break; + case AfterDownloadOperation.OPEN_FOLDER: + // 打开文件夹 + break; + case AfterDownloadOperation.CLOSE_APP: + // 关闭程序 + App.PropertyChangeAsync(() => + { + System.Windows.Application.Current.Shutdown(); + }); + break; + case AfterDownloadOperation.CLOSE_SYSTEM: + // 关机 + System.Diagnostics.Process.Start("shutdown.exe", "-s"); + break; + default: + break; + } + } + + /// + /// 停止基本下载服务(转换await和Task.Wait两种调用形式) + /// + protected async Task BaseEndTask() + { + // 结束任务 + tokenSource.Cancel(); + + await workTask; + + //先简单等待一下 + + // 下载数据存储服务 + DownloadStorageService downloadStorageService = new DownloadStorageService(); + // 保存数据 + foreach (DownloadingItem item in downloadingList) + { + switch (item.Downloading.DownloadStatus) + { + case DownloadStatus.NOT_STARTED: + break; + case DownloadStatus.WAIT_FOR_DOWNLOAD: + break; + case DownloadStatus.PAUSE_STARTED: + break; + case DownloadStatus.PAUSE: + break; + case DownloadStatus.DOWNLOADING: + // TODO 添加设置让用户选择重启后是否自动开始下载 + item.Downloading.DownloadStatus = DownloadStatus.WAIT_FOR_DOWNLOAD; + //item.Downloading.DownloadStatus = DownloadStatus.PAUSE; + break; + case DownloadStatus.DOWNLOAD_SUCCEED: + case DownloadStatus.DOWNLOAD_FAILED: + break; + default: + break; + } + + item.Progress = 0; + + downloadStorageService.UpdateDownloading(item); + } + foreach (DownloadedItem item in downloadedList) + { + downloadStorageService.UpdateDownloaded(item); + } + } + + /// + /// 启动基本下载服务 + /// + protected void BaseStart() + { + tokenSource = new CancellationTokenSource(); + cancellationToken = tokenSource.Token; + workTask = Task.Run(DoWork); + } + + #region 抽象接口函数 + public abstract void Parse(DownloadingItem downloading); + public abstract string DownloadAudio(DownloadingItem downloading); + public abstract string DownloadVideo(DownloadingItem downloading); + public abstract string DownloadDanmaku(DownloadingItem downloading); + public abstract List DownloadSubtitle(DownloadingItem downloading); + public abstract string DownloadCover(DownloadingItem downloading, string coverUrl, string fileName); + public abstract string MixedFlow(DownloadingItem downloading, string audioUid, string videoUid); + + protected abstract void Pause(DownloadingItem downloading); + #endregion } }