diff --git a/MeowBili/PersonalCenter/DownloadsView.swift b/MeowBili/PersonalCenter/DownloadsView.swift index 5f8ac7869..c89d3c585 100644 --- a/MeowBili/PersonalCenter/DownloadsView.swift +++ b/MeowBili/PersonalCenter/DownloadsView.swift @@ -25,6 +25,11 @@ import MarqueeText import AVFoundation import SDWebImageSwiftUI +var downloadingProgressDataList = [(pts: PassthroughSubject, isFinished: Bool)]() +var failedDownloadTasks = [Int]() +var downloadResumeDatas = [Int: [String: String]]() +var videoDownloadRequests = [DownloadRequest]() + struct DownloadsView: View { public static var willPlayVideoPath = "" @State var metadatas = [[String: String]]() @@ -140,15 +145,15 @@ struct DownloadingListView: View { @State var downloadProgresses = [Double]() @State var downloadedSizes = [Int64]() @State var totalSizes = [Int64]() - @State var localFailedDownloadTasks = Set() + @State var videoDetails = [[String: String]]() + @State var localFailedDownloadTasks = [Int]() @State var localVariableUpdateTimer: Timer? - @State var isRefreshing = false var body: some View { List { - if !videoDownloadRequests.isEmpty && !isRefreshing { - ForEach(0..() -var videoDownloadRequests = [URLSessionTask]() -var videoDownloadDetails = [Int: [String: String]]() -let videoDownloadSession = { - let configuration = URLSessionConfiguration.background(withIdentifier: "com.darock.DarockBili.Video-Download.background") - configuration.isDiscretionary = false - configuration.sessionSendsLaunchEvents = true - let session = URLSession(configuration: configuration, delegate: VideoDownloadSessionDelegate.shared, delegateQueue: nil) - return session -}() - struct VideoDownloadView: View { var bvid: String var videoDetails: [String: String] @@ -85,11 +74,11 @@ struct VideoDownloadView: View { AF.request("https://api.bilibili.com/x/web-interface/view?bvid=\(bvid)").response { response in let cid = Int64((String(data: response.data!, encoding: .utf8)?.components(separatedBy: "\"pages\":[{\"cid\":")[1].components(separatedBy: ",")[0])!)! AF.request("https://api.bilibili.com/x/player/playurl?platform=html5&bvid=\(bvid)&cid=\(cid)", headers: headers).response { response in - let headers = [ + let headers: HTTPHeaders = [ "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "accept-encoding": "gzip, deflate, br", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", - "cookie": "SESSDATA=\(sessdata)", + "cookie": "", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.30 Safari/537.36 Edg/84.0.522.11", "sec-fetch-dest": "document", "sec-fetch-mode": "navigate", @@ -99,68 +88,45 @@ struct VideoDownloadView: View { "referer": "https://www.bilibili.com/" ] let downloadCID = String(VideoDownloadView.downloadCID ?? 0) - isLoading = false - var request = URLRequest(url: URL(string: VideoDownloadView.downloadLink ?? (String(data: response.data!, encoding: .utf8)?.components(separatedBy: ",\"url\":\"")[1].components(separatedBy: "\",")[0])!.replacingOccurrences(of: "\\u0026", with: "&"))!) - request.allHTTPHeaderFields = headers - let task = videoDownloadSession.downloadTask(with: request) - task.resume() - var videoDetails = videoDetails - if isPaged { - videoDetails.updateValue(downloadCID, forKey: "DownloadCID") + let destination: DownloadRequest.Destination = { _, _ in + let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + let fileURL = documentsURL.appendingPathComponent("dlds/\(bvid)\(isPaged ? downloadCID : "").mp4") + + return (fileURL, [.removePreviousFile, .createIntermediateDirectories]) } - videoDownloadDetails.updateValue(videoDetails, forKey: task.taskIdentifier) - videoDownloadRequests.append(task) + isLoading = false + downloadingProgressDataList.append((.init(), false)) + let currentDownloadingIndex = downloadingProgressDataList.count - 1 + videoDownloadRequests.append( + AF.download((VideoDownloadView.downloadLink ?? (String(data: response.data!, encoding: .utf8)?.components(separatedBy: ",\"url\":\"")[1].components(separatedBy: "\",")[0])!.replacingOccurrences(of: "\\u0026", with: "&")), headers: headers, to: destination) + .downloadProgress { p in + downloadingProgressDataList[currentDownloadingIndex].pts.send(.init(data: .init(progress: p.fractionCompleted, currentSize: p.completedUnitCount, totalSize: p.totalUnitCount), videoDetails: videoDetails)) + } + .response { r in + if r.error == nil, let filePath = r.fileURL?.path { + debugPrint(filePath) + debugPrint(bvid) + var detTmp = videoDetails + detTmp.updateValue(filePath, forKey: "Path") + detTmp.updateValue(String(Date.now.timeIntervalSince1970), forKey: "Time") + UserDefaults.standard.set(detTmp, forKey: "\(bvid)\(isPaged ? downloadCID : "")") + downloadingProgressDataList[currentDownloadingIndex].isFinished = true + } else { + if FileManager.default.fileExists(atPath: NSHomeDirectory() + "/tmp/VideoDownloadResumeData\(currentDownloadingIndex).drkdatav") { + try? FileManager.default.removeItem(atPath: NSHomeDirectory() + "/tmp/VideoDownloadResumeData\(currentDownloadingIndex).drkdatav") + } + try? r.resumeData?.write(to: URL(filePath: NSHomeDirectory() + "/tmp/VideoDownloadResumeData\(currentDownloadingIndex).drkdatav")) + var detTmp = videoDetails + detTmp.updateValue("\(bvid)\(isPaged ? String(VideoDownloadView.downloadCID!) : "")", forKey: "SetKey") + downloadResumeDatas.updateValue(detTmp, forKey: currentDownloadingIndex) + failedDownloadTasks.append(currentDownloadingIndex) + debugPrint(r.error as Any) + } + } + ) VideoDownloadView.downloadLink = nil } } } } } - -private class VideoDownloadSessionDelegate: NSObject, URLSessionDelegate, URLSessionDownloadDelegate { - static let shared = VideoDownloadSessionDelegate() - - func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { - debugPrint("Finish Background Events") - } - - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { - debugPrint(location) - downloadTask.progress.completedUnitCount = downloadTask.progress.totalUnitCount - if var details = videoDownloadDetails[downloadTask.taskIdentifier] { - let destination = URL(filePath: NSHomeDirectory() + "/Documents/dlds/\(details["BV"]!)\(details["DownloadCID"] ?? "").mp4") - do { - try FileManager.default.moveItem(at: location, to: destination) - details.updateValue(destination.path, forKey: "Path") - details.updateValue(String(Date.now.timeIntervalSince1970), forKey: "Time") - UserDefaults.standard.set(details, forKey: "\(details["BV"]!)\(details["DownloadCID"] ?? "")") - } catch { - print(error) - } - } - } - - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { - DispatchQueue.main.async { - downloadTask.progress.completedUnitCount = totalBytesWritten - downloadTask.progress.totalUnitCount = totalBytesExpectedToWrite - } - } - - func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: (any Error)?) { - guard let error else { return } - let userInfo = (error as NSError).userInfo - if let resumeData = userInfo[NSURLSessionDownloadTaskResumeData] as? Data { - do { - let resumeFilePath = NSHomeDirectory() + "/tmp/VideoDownloadResumeData\(task.taskIdentifier).drkdatav" - if FileManager.default.fileExists(atPath: resumeFilePath) { - try FileManager.default.removeItem(atPath: resumeFilePath) - } - try resumeData.write(to: URL(filePath: resumeFilePath)) - videoDownloadNeedsResumeIdentifiers.insert(task.taskIdentifier) - } catch { - print(error) - } - } - } -} diff --git a/MeowBili/Video/VideoPlayerView.swift b/MeowBili/Video/VideoPlayerView.swift index 6970ca984..4d05c07d5 100644 --- a/MeowBili/Video/VideoPlayerView.swift +++ b/MeowBili/Video/VideoPlayerView.swift @@ -157,17 +157,6 @@ struct VideoPlayerView: View { playerScaledOffset = .zero } }) - .frame( - width: isFullScreen ? WKInterfaceDevice.current().screenBounds.height : nil, - height: isFullScreen ? WKInterfaceDevice.current().screenBounds.width : nil - ) - .offset(y: isFullScreen ? 20 : 0) - .scaleEffect(playerScale) - .ignoresSafeArea() - .offset(playerScaledOffset) - .animation(.smooth, value: cachedPlayerTimeControlStatus) - .animation(.smooth, value: playerScale) - ._statusBarHidden(true) .overlay { ZStack { if isDanmakuEnabled { @@ -199,6 +188,17 @@ struct VideoPlayerView: View { } } .rotationEffect(.degrees(isFullScreen ? 90 : 0)) + .frame( + width: isFullScreen ? WKInterfaceDevice.current().screenBounds.height : nil, + height: isFullScreen ? WKInterfaceDevice.current().screenBounds.width : nil + ) + .offset(y: isFullScreen ? 19 : 0) + .scaleEffect(playerScale) + .ignoresSafeArea() + .offset(playerScaledOffset) + .animation(.smooth, value: cachedPlayerTimeControlStatus) + .animation(.smooth, value: playerScale) + ._statusBarHidden(true) .tag(1) List { Section { @@ -603,7 +603,11 @@ private struct ZoomableRepresent: _UIViewControllerRepresentable where T: Vie func handlePinchGesture(_ pinchGesture: NSObject) { let pinchGesture = Dynamic(pinchGesture) if pinchGesture.state == 1 || pinchGesture.state == 2 { + #if !targetEnvironment(simulator) let scale = CGFloat(pinchGesture.scale.asFloat!) + #else + let scale = CGFloat(pinchGesture.scale.asDouble!) + #endif if let onScaleChange { onScaleChange(scale) } else {