88import Foundation
99import PhotosUI
1010import MuxUploadSDK
11+ import SwiftUI
12+
13+ struct UploadInput : Transferable {
14+ let file : URL
15+ static var transferRepresentation : some TransferRepresentation {
16+ FileRepresentation (
17+ contentType: . mpeg4Movie
18+ ) { transferable in
19+ SentTransferredFile ( transferable. file)
20+ } importing: { receivedTransferedFile in
21+ UploadInput ( file: receivedTransferedFile. file)
22+ }
23+ }
24+ }
1125
1226class UploadCreationModel : ObservableObject {
1327
@@ -32,19 +46,33 @@ class UploadCreationModel : ObservableObject {
3246 var localizedDescription : String
3347
3448 }
35-
36- func requestPhotosAccess( ) {
37- switch photosAuthStatus {
38- case . cant_auth( _) : logger. critical ( " This application can't ask for permission to access photos. Check your Info.plist for NSPhotoLibraryAddUsageDescription, and make sure to use a physical device with this app " )
39- case . authorized( _) : logger. warning ( " requestPhotosAccess called but we already had access. ignoring " )
40- case . can_auth( _) : doRequestPhotosPermission ( )
49+
50+ private var assetRequestId : PHImageRequestID ? = nil
51+ private var prepareTask : Task < Void , Never > ? = nil
52+ private var thumbnailGenerator : AVAssetImageGenerator ? = nil
53+
54+ private let logger = SwiftUploadSDKExample . logger
55+ private let myServerBackend = FakeBackend ( urlSession: URLSession ( configuration: URLSessionConfiguration . default) )
56+
57+ @Published var photosAuthStatus : PhotosAuthState
58+ @Published var exportState : ExportState = . not_started
59+ @Published var pickedItem : [ PhotosPickerItem ] = [ ] {
60+ didSet {
61+ tryToPrepare ( from: pickedItem. first!)
4162 }
4263 }
64+
65+ init ( ) {
66+ let innerAuthStatus = PHPhotoLibrary . authorizationStatus ( for: . readWrite)
67+ self . photosAuthStatus = innerAuthStatus. asAppAuthState ( )
68+ self . exportState = . not_started
69+ }
4370
4471 @discardableResult func startUpload( preparedMedia: PreparedUpload , forceRestart: Bool ) -> DirectUpload {
4572 let upload = DirectUpload (
4673 uploadURL: preparedMedia. remoteURL,
47- videoFileURL: preparedMedia. localVideoFile
74+ inputAsset: AVAsset ( url: preparedMedia. localVideoFile) ,
75+ options: . default
4876 )
4977 upload. progressHandler = { progress in
5078 self . logger. info ( " Uploading \( progress. progress? . completedUnitCount ?? 0 ) / \( progress. progress? . totalUnitCount ?? 0 ) " )
@@ -55,59 +83,67 @@ class UploadCreationModel : ObservableObject {
5583 }
5684
5785 /// Prepares a Photos Asset for upload by exporting it to a local temp file
58- func tryToPrepare( from pickerResult: PHPickerResult ) {
59- if case ExportState . preparing = exportState {
60- return
61- }
62-
63- // Cancel anything that was already happening
64- if let assetRequestId = assetRequestId {
65- PHImageManager . default ( ) . cancelImageRequest ( assetRequestId)
66- }
67- if let prepareTask = prepareTask {
68- prepareTask. cancel ( )
69- }
70- if let thumbnailGenerator = thumbnailGenerator {
71- thumbnailGenerator. cancelAllCGImageGeneration ( )
72- }
73-
74- // TODO: This is a very common workflow. Should the SDK be able to do this workflow with Photos?
86+ func tryToPrepare( from pickerItem: PhotosPickerItem ) {
7587 exportState = . preparing
7688
7789 let tempDir = FileManager . default. temporaryDirectory
78- let tempFile = URL ( string: " upload- \( Date ( ) . timeIntervalSince1970) .mp4 " , relativeTo: tempDir) !
79-
80- guard let assetIdentitfier = pickerResult. assetIdentifier else {
81- NSLog ( " !! No Asset ID for chosen asset " )
82- exportState = . failure( UploadCreationModel . PickerError. assetExportSessionFailed)
83- return
84- }
85- let options = PHFetchOptions ( )
86- options. includeAssetSourceTypes = [ . typeUserLibrary, . typeCloudShared]
87- let phAssetResult = PHAsset . fetchAssets ( withLocalIdentifiers: [ assetIdentitfier] , options: options)
88- guard let phAsset = phAssetResult. firstObject else {
89- self . logger. error ( " !! No Asset fetched " )
90+ let tempFile = URL (
91+ string: " upload- \( Date ( ) . timeIntervalSince1970) .mp4 " ,
92+ relativeTo: tempDir
93+ ) !
94+
95+ guard let itemIdentifier = pickerItem. itemIdentifier else {
96+ self . logger. error ( " No item identifier for chosen video " )
9097 Task . detached {
9198 await MainActor . run {
92- self . exportState = . failure( PickerError . missingAssetIdentifier)
99+ self . exportState = . failure(
100+ PickerError . assetExportSessionFailed
101+ )
93102 }
94103 }
95104 return
96105 }
97-
98- let exportOptions = PHVideoRequestOptions ( )
99- exportOptions. isNetworkAccessAllowed = true
100- exportOptions. deliveryMode = . highQualityFormat
101- assetRequestId = PHImageManager . default ( ) . requestExportSession ( forVideo: phAsset, options: exportOptions, exportPreset: AVAssetExportPresetHighestQuality, resultHandler: { ( exportSession, info) -> Void in
102- DispatchQueue . main. async {
103- guard let exportSession = exportSession else {
104- self . logger. error ( " !! No Export session " )
105- self . exportState = . failure( UploadCreationModel . PickerError. assetExportSessionFailed)
106- return
106+
107+ doRequestPhotosPermission { authorizationStatus in
108+ Task . detached {
109+ await MainActor . run {
110+ self . photosAuthStatus = authorizationStatus. asAppAuthState ( )
111+
112+ let options = PHFetchOptions ( )
113+ options. includeAssetSourceTypes = [ . typeUserLibrary, . typeCloudShared]
114+ let fetchAssetResult = PHAsset . fetchAssets ( withLocalIdentifiers: [ itemIdentifier] , options: options)
115+ guard let fetchedAsset = fetchAssetResult. firstObject else {
116+ self . logger. error ( " No Asset fetched " )
117+ Task . detached {
118+ await MainActor . run {
119+ self . exportState = . failure(
120+ PickerError . missingAssetIdentifier
121+ )
122+ }
123+ }
124+ return
125+ }
126+
127+ let exportOptions = PHVideoRequestOptions ( )
128+ exportOptions. isNetworkAccessAllowed = true
129+ exportOptions. deliveryMode = . highQualityFormat
130+ self . assetRequestId = PHImageManager . default ( ) . requestExportSession (
131+ forVideo: fetchedAsset,
132+ options: exportOptions,
133+ exportPreset: AVAssetExportPresetPassthrough,
134+ resultHandler: { ( exportSession, info) -> Void in
135+ DispatchQueue . main. async {
136+ guard let exportSession = exportSession else {
137+ self . logger. error ( " !! No Export session " )
138+ self . exportState = . failure( UploadCreationModel . PickerError. assetExportSessionFailed)
139+ return
140+ }
141+ self . exportToOutFile ( session: exportSession, outFile: tempFile)
142+ }
143+ } )
107144 }
108- self . exportToOutFile ( session: exportSession, outFile: tempFile)
109145 }
110- } )
146+ }
111147 }
112148
113149 private func exportToOutFile( session: AVAssetExportSession , outFile: URL ) {
@@ -180,32 +216,13 @@ class UploadCreationModel : ObservableObject {
180216
181217 }
182218
183- private func doRequestPhotosPermission( ) {
184- PHPhotoLibrary . requestAuthorization ( for: . readWrite) { status in
185- Task . detached {
186- await MainActor . run {
187- self . photosAuthStatus = status. asAppAuthState ( )
188- }
189- }
190- }
191- }
192-
193- private var assetRequestId : PHImageRequestID ? = nil
194- private var prepareTask : Task < Void , Never > ? = nil
195- private var thumbnailGenerator : AVAssetImageGenerator ? = nil
196-
197- private let logger = SwiftUploadSDKExample . logger
198- private let myServerBackend = FakeBackend ( urlSession: URLSession ( configuration: URLSessionConfiguration . default) )
199-
200- @Published
201- var photosAuthStatus : PhotosAuthState
202- @Published
203- var exportState : ExportState = . not_started
204-
205- init ( ) {
206- let innerAuthStatus = PHPhotoLibrary . authorizationStatus ( for: . readWrite)
207- self . photosAuthStatus = innerAuthStatus. asAppAuthState ( )
208- self . exportState = . not_started
219+ private func doRequestPhotosPermission(
220+ handler: @escaping ( PHAuthorizationStatus ) -> Void
221+ ) {
222+ PHPhotoLibrary . requestAuthorization (
223+ for: . readWrite,
224+ handler: handler
225+ )
209226 }
210227}
211228
@@ -216,11 +233,16 @@ struct PreparedUpload {
216233}
217234
218235enum ExportState {
219- case not_started, preparing, failure( UploadCreationModel . PickerError ) , ready( PreparedUpload )
236+ case not_started
237+ case preparing
238+ case failure( UploadCreationModel . PickerError )
239+ case ready( PreparedUpload )
220240}
221241
222242enum PhotosAuthState {
223- case cant_auth( PHAuthorizationStatus ) , can_auth( PHAuthorizationStatus ) , authorized( PHAuthorizationStatus )
243+ case cant_auth( PHAuthorizationStatus )
244+ case can_auth( PHAuthorizationStatus )
245+ case authorized( PHAuthorizationStatus )
224246}
225247
226248extension PHAuthorizationStatus {
0 commit comments