This commit is contained in:
Fredrik Burmester
2025-02-16 11:22:10 +01:00
parent df7144ede9
commit 696543d1b2
5 changed files with 220 additions and 117 deletions

View File

@@ -1,29 +1,36 @@
import ExpoModulesCore
import AVFoundation
import ExpoModulesCore
public class HlsDownloaderModule: Module {
var activeDownloads: [Int: (task: AVAssetDownloadTask, delegate: HLSDownloadDelegate, metadata: [String: Any])] = [:]
var activeDownloads:
[Int: (task: AVAssetDownloadTask, delegate: HLSDownloadDelegate, metadata: [String: Any])] = [:]
public func definition() -> ModuleDefinition {
Name("HlsDownloader")
Events("onProgress", "onError", "onComplete")
Function("downloadHLSAsset") { (providedId: String, url: String, assetTitle: String, metadata: [String: Any]?) -> Void in
print("Starting download - ID: \(providedId), URL: \(url), Title: \(assetTitle), Metadata: \(String(describing: metadata))")
Function("downloadHLSAsset") {
(providedId: String, url: String, assetTitle: String, metadata: [String: Any]?) -> Void in
print(
"Starting download - ID: \(providedId), URL: \(url), Title: \(assetTitle), Metadata: \(String(describing: metadata))"
)
guard let assetURL = URL(string: url) else {
self.sendEvent("onError", [
"id": providedId,
"error": "Invalid URL",
"state": "FAILED",
"metadata": metadata ?? [:]
])
self.sendEvent(
"onError",
[
"id": providedId,
"error": "Invalid URL",
"state": "FAILED",
"metadata": metadata ?? [:],
])
return
}
}
let asset = AVURLAsset(url: assetURL)
let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.hlsdownload")
let configuration = URLSessionConfiguration.background(
withIdentifier: "com.example.hlsdownload")
let delegate = HLSDownloadDelegate(module: self)
delegate.providedId = providedId
let downloadSession = AVAssetDownloadURLSession(
@@ -32,29 +39,35 @@ public class HlsDownloaderModule: Module {
delegateQueue: OperationQueue.main
)
guard let task = downloadSession.makeAssetDownloadTask(
asset: asset,
assetTitle: assetTitle,
assetArtworkData: nil,
options: nil
) else {
self.sendEvent("onError", [
guard
let task = downloadSession.makeAssetDownloadTask(
asset: asset,
assetTitle: assetTitle,
assetArtworkData: nil,
options: nil
)
else {
self.sendEvent(
"onError",
[
"id": providedId,
"error": "Failed to create download task",
"state": "FAILED",
"metadata": metadata ?? [:]
])
"error": "Failed to create download task",
"state": "FAILED",
"metadata": metadata ?? [:],
])
return
}
}
delegate.taskIdentifier = task.taskIdentifier
self.activeDownloads[task.taskIdentifier] = (task, delegate, metadata ?? [:])
self.sendEvent("onProgress", [
"id": providedId,
"progress": 0.0,
"state": "PENDING",
"metadata": metadata ?? [:]
])
self.sendEvent(
"onProgress",
[
"id": providedId,
"progress": 0.0,
"state": "PENDING",
"metadata": metadata ?? [:],
])
task.resume()
print("Download task started with identifier: \(task.taskIdentifier)")
@@ -76,20 +89,35 @@ public class HlsDownloaderModule: Module {
"bytesDownloaded": downloaded,
"bytesTotal": total,
"state": self.mappedState(for: task),
"metadata": metadata
])
}
"metadata": metadata,
])
}
return downloads
}
OnStartObserving { }
OnStopObserving { }
}
OnStartObserving {}
OnStopObserving {}
}
func removeDownload(with id: Int) {
activeDownloads.removeValue(forKey: id)
}
func persistDownloadedFolder(originalLocation: URL, folderName: String) throws -> URL {
let fileManager = FileManager.default
let documents = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let destinationDir = documents.appendingPathComponent("downloads", isDirectory: true)
if !fileManager.fileExists(atPath: destinationDir.path) {
try fileManager.createDirectory(at: destinationDir, withIntermediateDirectories: true)
}
let newLocation = destinationDir.appendingPathComponent(folderName, isDirectory: true)
if fileManager.fileExists(atPath: newLocation.path) {
try fileManager.removeItem(at: newLocation)
}
try fileManager.moveItem(at: originalLocation, to: newLocation)
return newLocation
}
func mappedState(for task: URLSessionTask, errorOccurred: Bool = false) -> String {
if errorOccurred { return "FAILED" }
switch task.state {
@@ -112,51 +140,79 @@ class HLSDownloadDelegate: NSObject, AVAssetDownloadDelegate {
self.module = module
}
func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didLoad timeRange: CMTimeRange, totalTimeRangesLoaded loadedTimeRanges: [NSValue], timeRangeExpectedToLoad: CMTimeRange) {
let downloaded = loadedTimeRanges.reduce(0.0) { total, value in
let timeRange = value.timeRangeValue
return total + CMTimeGetSeconds(timeRange.duration)
}
func urlSession(
_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didLoad timeRange: CMTimeRange,
totalTimeRangesLoaded loadedTimeRanges: [NSValue], timeRangeExpectedToLoad: CMTimeRange
) {
let downloaded = loadedTimeRanges.reduce(0.0) { total, value in
let timeRange = value.timeRangeValue
return total + CMTimeGetSeconds(timeRange.duration)
}
let total = CMTimeGetSeconds(timeRangeExpectedToLoad.duration)
let metadata = module?.activeDownloads[assetDownloadTask.taskIdentifier]?.metadata ?? [:]
let total = CMTimeGetSeconds(timeRangeExpectedToLoad.duration)
let metadata = module?.activeDownloads[assetDownloadTask.taskIdentifier]?.metadata ?? [:]
self.downloadedSeconds = downloaded
self.totalSeconds = total
self.downloadedSeconds = downloaded
self.totalSeconds = total
let progress = total > 0 ? downloaded / total : 0
let progress = total > 0 ? downloaded / total : 0
module?.sendEvent("onProgress", [
"id": providedId,
"progress": progress,
"bytesDownloaded": downloaded,
"bytesTotal": total,
"state": progress >= 1.0 ? "DONE" : "DOWNLOADING",
"metadata": metadata
module?.sendEvent(
"onProgress",
[
"id": providedId,
"progress": progress,
"bytesDownloaded": downloaded,
"bytesTotal": total,
"state": progress >= 1.0 ? "DONE" : "DOWNLOADING",
"metadata": metadata,
])
}
func urlSession(
_ session: URLSession, assetDownloadTask: AVAssetDownloadTask,
didFinishDownloadingTo location: URL
) {
let metadata = module?.activeDownloads[assetDownloadTask.taskIdentifier]?.metadata ?? [:]
let folderName = providedId // using providedId as the folder name
do {
guard let module = module else { return }
let newLocation = try module.persistDownloadedFolder(
originalLocation: location, folderName: folderName)
module.sendEvent(
"onComplete",
[
"id": providedId,
"location": newLocation.absoluteString,
"state": "DONE",
"metadata": metadata,
])
} catch {
module?.sendEvent(
"onError",
[
"id": providedId,
"error": error.localizedDescription,
"state": "FAILED",
"metadata": metadata,
])
}
module?.removeDownload(with: assetDownloadTask.taskIdentifier)
}
func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) {
let metadata = module?.activeDownloads[assetDownloadTask.taskIdentifier]?.metadata ?? [:]
module?.sendEvent("onComplete", [
"id": providedId,
"location": location.absoluteString,
"state": "DONE",
"metadata": metadata
])
module?.removeDownload(with: assetDownloadTask.taskIdentifier)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
let metadata = module?.activeDownloads[task.taskIdentifier]?.metadata ?? [:]
module?.sendEvent("onError", [
"id": providedId,
"error": error.localizedDescription,
"state": "FAILED",
"metadata": metadata
])
module?.removeDownload(with: taskIdentifier)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
let metadata = module?.activeDownloads[task.taskIdentifier]?.metadata ?? [:]
module?.sendEvent(
"onError",
[
"id": providedId,
"error": error.localizedDescription,
"state": "FAILED",
"metadata": metadata,
])
module?.removeDownload(with: taskIdentifier)
}
}
}

View File

@@ -2,7 +2,6 @@ import ExpoModulesCore
import VLCKit
import UIKit
public class VLCPlayerView: UIView {
func setupView(parent: UIView) {
self.backgroundColor = .black