diff --git a/Sora/Utlis & Misc/DownloadUtils/DownloadManager.swift b/Sora/Utlis & Misc/DownloadUtils/DownloadManager.swift index 2adab56..e3eabd5 100644 --- a/Sora/Utlis & Misc/DownloadUtils/DownloadManager.swift +++ b/Sora/Utlis & Misc/DownloadUtils/DownloadManager.swift @@ -5,16 +5,19 @@ // Created by Francesco on 29/04/25. // -import SwiftUI import AVKit +import SwiftUI import AVFoundation class DownloadManager: NSObject, ObservableObject { @Published var activeDownloads: [(URL, Double)] = [] @Published var localPlaybackURL: URL? + @Published var subtitleURL: URL? private var assetDownloadURLSession: AVAssetDownloadURLSession! private var activeDownloadTasks: [URLSessionTask: URL] = [:] + private var activeDownloadHeaders: [URL: [String: String]] = [:] + private var activeSubtitleTasks: [URLSessionDownloadTask: URL] = [:] override init() { super.init() @@ -23,21 +26,34 @@ class DownloadManager: NSObject, ObservableObject { } private func initializeDownloadSession() { - #if targetEnvironment(simulator) - Logger.shared.log("Download Sessions are not available on Simulator", type: "Error") - #else - let configuration = URLSessionConfiguration.background(withIdentifier: "hls-downloader") - - assetDownloadURLSession = AVAssetDownloadURLSession( - configuration: configuration, - assetDownloadDelegate: self, - delegateQueue: .main - ) - #endif +#if targetEnvironment(simulator) + Logger.shared.log("Download Sessions are not available on Simulator", type: "Error") +#else + let configuration = URLSessionConfiguration.background(withIdentifier: "hls-downloader") + + assetDownloadURLSession = AVAssetDownloadURLSession( + configuration: configuration, + assetDownloadDelegate: self, + delegateQueue: .main + ) +#endif } - func downloadAsset(from url: URL) { - let asset = AVURLAsset(url: url) + func downloadAsset(from url: URL, headers: [String: String]? = nil, subtitleURL: URL? = nil) { + if let headers = headers { + activeDownloadHeaders[url] = headers + } + + let asset: AVURLAsset + if let headers = headers { + let options = [ + "AVURLAssetHTTPHeaderFieldsKey": headers + ] + asset = AVURLAsset(url: url, options: options) + } else { + asset = AVURLAsset(url: url) + } + let task = assetDownloadURLSession.makeAssetDownloadTask( asset: asset, assetTitle: "Offline Video", @@ -47,6 +63,45 @@ class DownloadManager: NSObject, ObservableObject { task?.resume() activeDownloadTasks[task!] = url + + if let subtitleURL = subtitleURL { + downloadSubtitle(from: subtitleURL, headers: headers) + } + } + + private func downloadSubtitle(from url: URL, headers: [String: String]? = nil) { + var request = URLRequest(url: url) + headers?.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) } + + let task = URLSession.shared.downloadTask(with: request) { [weak self] location, response, error in + guard let self = self, + let location = location, + error == nil else { + Logger.shared.log("Subtitle download failed: \(error?.localizedDescription ?? "Unknown error")", type: "Error") + return + } + + do { + let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + let fileName = url.lastPathComponent + let savedURL = documents.appendingPathComponent(fileName) + + if FileManager.default.fileExists(atPath: savedURL.path) { + try FileManager.default.removeItem(at: savedURL) + } + + try FileManager.default.moveItem(at: location, to: savedURL) + + DispatchQueue.main.async { + self.subtitleURL = savedURL + } + } catch { + Logger.shared.log("Failed to save subtitle file: \(error.localizedDescription)", type: "Error") + } + } + + activeSubtitleTasks[task] = url + task.resume() } private func loadLocalContent() { @@ -70,21 +125,32 @@ class DownloadManager: NSObject, ObservableObject { extension DownloadManager: AVAssetDownloadDelegate { func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) { - activeDownloadTasks.removeValue(forKey: assetDownloadTask) + if let originalURL = activeDownloadTasks[assetDownloadTask] { + activeDownloadTasks.removeValue(forKey: assetDownloadTask) + activeDownloadHeaders.removeValue(forKey: originalURL) + } localPlaybackURL = location + Logger.shared.log("Asset download completed successfully", type: "Debug") } func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { - guard let error = error else { return } - Logger.shared.log("Download failed: \(error.localizedDescription)", type: "Error") - activeDownloadTasks.removeValue(forKey: task) + if let error = error { + Logger.shared.log("Download failed: \(error.localizedDescription)", type: "Error") + + if let originalURL = activeDownloadTasks[task] { + activeDownloadHeaders.removeValue(forKey: originalURL) + } + activeDownloadTasks.removeValue(forKey: task) + + if let downloadTask = task as? URLSessionDownloadTask { + activeSubtitleTasks.removeValue(forKey: downloadTask) + } + } else { + Logger.shared.log("Download completed successfully", type: "Debug") + } } - func urlSession(_ session: URLSession, - assetDownloadTask: AVAssetDownloadTask, - didLoad timeRange: CMTimeRange, - totalTimeRangesLoaded loadedTimeRanges: [NSValue], - timeRangeExpectedToLoad: CMTimeRange) { + func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didLoad timeRange: CMTimeRange, totalTimeRangesLoaded loadedTimeRanges: [NSValue], timeRangeExpectedToLoad: CMTimeRange) { guard let url = activeDownloadTasks[assetDownloadTask] else { return } let progress = loadedTimeRanges