diff --git a/Example/AppDownloadManager.swift b/Example/AppDownloadManager.swift new file mode 100644 index 0000000..129a818 --- /dev/null +++ b/Example/AppDownloadManager.swift @@ -0,0 +1,57 @@ +// +// AppDownloadManager.swift +// Example +// +// Created by Prithuvi on 25/01/24. +// + +import Foundation +import TPStreamsSDK + +class AppDownloadManager: TPStreamsDownloadDelegate, ObservableObject { + + @Published var offlineAssets: [OfflineAsset] = [] + + init() { + TPStreamsDownloadManager.shared.setTPStreamsDownloadDelegate(tpStreamsDownloadDelegate: self) + getOfflineAssets() + + NotificationCenter.default.addObserver(self, selector: #selector(handleOfflineAssetsUpdated), name: Notification.Name("OfflineAssetsUpdated"), object: nil) + } + + func getOfflineAssets() { + offlineAssets = TPStreamsDownloadManager.shared.getAllOfflineAssets() + } + + func onComplete(offlineAsset: OfflineAsset) { + getOfflineAssets() + print("Download Complete", offlineAsset) + } + + func onStart(offlineAsset: OfflineAsset) { + print("Download Start", offlineAsset.assetId) + } + + func onPause(offlineAsset: OfflineAsset) { + print("Download Pause", offlineAsset.assetId) + } + + func onResume(offlineAsset: OfflineAsset) { + print("Download Resume", offlineAsset.assetId) + } + + @objc func handleOfflineAssetsUpdated() { + getOfflineAssets() + print("Offline Assets Updated") + } + + func onStateChange(offlineAsset: OfflineAsset) { + print("Downloads State Change") + getOfflineAssets() + } + + deinit { + NotificationCenter.default.removeObserver(self, name: Notification.Name("OfflineAssetsUpdated"), object: nil) + } + +} diff --git a/Example/Views/DownloadListView.swift b/Example/Views/DownloadListView.swift index 48c7451..b6d1c78 100644 --- a/Example/Views/DownloadListView.swift +++ b/Example/Views/DownloadListView.swift @@ -9,18 +9,15 @@ import SwiftUI import TPStreamsSDK struct DownloadListView: View { - @State private var offlineAssets: [OfflineAsset] = [] - + @ObservedObject private var appDownloadManager = AppDownloadManager() + var body: some View { - List($offlineAssets, id: \.self) { offlineAsset in + List($appDownloadManager.offlineAssets, id: \.self) { offlineAsset in OfflineAssetRow(offlineAsset: offlineAsset) } - .onAppear { - offlineAssets = TPStreamsDownloadManager.shared.getAllOfflineAssets() - } .overlay( Group { - if offlineAssets.isEmpty { + if appDownloadManager.offlineAssets.isEmpty { Text("No downloads available") } } diff --git a/Example/Views/OfflineAssetRow.swift b/Example/Views/OfflineAssetRow.swift index f308853..1af4984 100644 --- a/Example/Views/OfflineAssetRow.swift +++ b/Example/Views/OfflineAssetRow.swift @@ -25,7 +25,7 @@ struct OfflineAssetRow: View { ProgressView(value: offlineAsset.percentageCompleted, total: 100) - Text("\(formatDate(date: offlineAsset.createdAt)) • \(formatDuration(seconds: offlineAsset.duration)) • \(String(format: "%.2f MB", offlineAsset.size / 8 / 1024 / 1024))") + Text("\(formatDate(date: offlineAsset.createdAt)) • \(formatDuration(seconds: offlineAsset.duration)) • \(String(format: "%.2f MB", offlineAsset.size / 8 / 1024 / 1024)) • \(offlineAsset.status)") .font(.caption) } } diff --git a/Source/Database/TPStreamsDownloadManager.swift b/Source/Database/TPStreamsDownloadManager.swift index 9f652af..bbc2533 100644 --- a/Source/Database/TPStreamsDownloadManager.swift +++ b/Source/Database/TPStreamsDownloadManager.swift @@ -14,6 +14,7 @@ public final class TPStreamsDownloadManager { static public let shared = TPStreamsDownloadManager() private var assetDownloadURLSession: AVAssetDownloadURLSession! private var assetDownloadDelegate: AssetDownloadDelegate! + private var tpStreamsDownloadDelegate: TPStreamsDownloadDelegate? = nil private init() { let backgroundConfiguration = URLSessionConfiguration.background(withIdentifier: "com.tpstreams.downloadSession") @@ -24,6 +25,11 @@ public final class TPStreamsDownloadManager { delegateQueue: OperationQueue.main ) } + + public func setTPStreamsDownloadDelegate(tpStreamsDownloadDelegate: TPStreamsDownloadDelegate) { + self.tpStreamsDownloadDelegate = tpStreamsDownloadDelegate + assetDownloadDelegate.tpStreamsDownloadDelegate = tpStreamsDownloadDelegate + } internal func startDownload(asset: Asset, videoQuality: VideoQuality) { @@ -50,12 +56,14 @@ public final class TPStreamsDownloadManager { OfflineAsset.manager.add(object: offlineAsset) assetDownloadDelegate.activeDownloadsMap[task] = offlineAsset task.resume() + tpStreamsDownloadDelegate?.onStart(offlineAsset: offlineAsset) } public func pauseDownload(_ offlineAsset: OfflineAsset) { if let task = assetDownloadDelegate.activeDownloadsMap.first(where: { $0.value == offlineAsset })?.key { task.suspend() OfflineAsset.manager.update(object: offlineAsset, with: ["status": Status.paused.rawValue]) + tpStreamsDownloadDelegate?.onPause(offlineAsset: offlineAsset) } } @@ -64,6 +72,7 @@ public final class TPStreamsDownloadManager { if task.state != .running { task.resume() OfflineAsset.manager.update(object: offlineAsset, with: ["status": Status.inProgress.rawValue]) + tpStreamsDownloadDelegate?.onResume(offlineAsset: offlineAsset) } } } @@ -77,17 +86,20 @@ public final class TPStreamsDownloadManager { internal class AssetDownloadDelegate: NSObject, AVAssetDownloadDelegate { var activeDownloadsMap = [AVAggregateAssetDownloadTask: OfflineAsset]() + var tpStreamsDownloadDelegate: TPStreamsDownloadDelegate? = nil func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { guard let assetDownloadTask = task as? AVAggregateAssetDownloadTask else { return } guard let offlineAsset = activeDownloadsMap[assetDownloadTask] else { return } updateDownloadCompleteStatus(error, offlineAsset) activeDownloadsMap.removeValue(forKey: assetDownloadTask) + tpStreamsDownloadDelegate?.onComplete(offlineAsset: offlineAsset) } func urlSession(_ session: URLSession, aggregateAssetDownloadTask: AVAggregateAssetDownloadTask, willDownloadTo location: URL) { guard let offlineAsset = activeDownloadsMap[aggregateAssetDownloadTask] else { return } OfflineAsset.manager.update(object: offlineAsset, with: ["downloadedPath": String(location.relativePath)]) + tpStreamsDownloadDelegate?.onStateChange(offlineAsset: offlineAsset) } func urlSession(_ session: URLSession, @@ -101,6 +113,7 @@ internal class AssetDownloadDelegate: NSObject, AVAssetDownloadDelegate { let percentageComplete = calculateDownloadPercentage(loadedTimeRanges, timeRangeExpectedToLoad) OfflineAsset.manager.update(object: offlineAsset, with: ["status": Status.inProgress.rawValue, "percentageCompleted": percentageComplete]) + tpStreamsDownloadDelegate?.onStateChange(offlineAsset: offlineAsset) } private func updateDownloadCompleteStatus(_ error: Error?,_ offlineAsset: OfflineAsset) { @@ -118,3 +131,11 @@ internal class AssetDownloadDelegate: NSObject, AVAssetDownloadDelegate { return percentageComplete * 100 } } + +public protocol TPStreamsDownloadDelegate { + func onComplete(offlineAsset: OfflineAsset) + func onStart(offlineAsset: OfflineAsset) + func onPause(offlineAsset: OfflineAsset) + func onResume(offlineAsset: OfflineAsset) + func onStateChange(offlineAsset: OfflineAsset) +} diff --git a/iOSPlayerSDK.xcodeproj/project.pbxproj b/iOSPlayerSDK.xcodeproj/project.pbxproj index 6fcdd34..9c96390 100644 --- a/iOSPlayerSDK.xcodeproj/project.pbxproj +++ b/iOSPlayerSDK.xcodeproj/project.pbxproj @@ -68,6 +68,7 @@ D9D6A3B12B62717B005390D7 /* OfflineAssetRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9D6A3B02B62717B005390D7 /* OfflineAssetRow.swift */; }; D9D6A3B32B6271BB005390D7 /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9D6A3B22B6271BB005390D7 /* PlayerView.swift */; }; D9D6A3B82B628B0F005390D7 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9D6A3B72B628B0F005390D7 /* Time.swift */; }; + D9D6A3B52B6274E4005390D7 /* AppDownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9D6A3B42B6274E4005390D7 /* AppDownloadManager.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -185,6 +186,7 @@ D9D6A3B02B62717B005390D7 /* OfflineAssetRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineAssetRow.swift; sourceTree = ""; }; D9D6A3B22B6271BB005390D7 /* PlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = ""; }; D9D6A3B72B628B0F005390D7 /* Time.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Time.swift; sourceTree = ""; }; + D9D6A3B42B6274E4005390D7 /* AppDownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDownloadManager.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -317,6 +319,7 @@ 8E6389C32A27277D00306FA4 /* ExampleApp.swift */, 8E6389C72A27278000306FA4 /* Assets.xcassets */, 8E6389C92A27278000306FA4 /* Preview Content */, + D9D6A3B42B6274E4005390D7 /* AppDownloadManager.swift */, ); path = Example; sourceTree = ""; @@ -657,6 +660,7 @@ 8E6389C62A27277D00306FA4 /* ContentView.swift in Sources */, 8E6389C42A27277D00306FA4 /* ExampleApp.swift in Sources */, D9D6A3AF2B627144005390D7 /* DownloadListView.swift in Sources */, + D9D6A3B52B6274E4005390D7 /* AppDownloadManager.swift in Sources */, D9D6A3B32B6271BB005390D7 /* PlayerView.swift in Sources */, D9D6A3B12B62717B005390D7 /* OfflineAssetRow.swift in Sources */, );