8
8
import Foundation
9
9
import PhotosUI
10
10
import 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
+ }
11
25
12
26
class UploadCreationModel : ObservableObject {
13
27
@@ -32,19 +46,33 @@ class UploadCreationModel : ObservableObject {
32
46
var localizedDescription : String
33
47
34
48
}
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!)
41
62
}
42
63
}
64
+
65
+ init ( ) {
66
+ let innerAuthStatus = PHPhotoLibrary . authorizationStatus ( for: . readWrite)
67
+ self . photosAuthStatus = innerAuthStatus. asAppAuthState ( )
68
+ self . exportState = . not_started
69
+ }
43
70
44
71
@discardableResult func startUpload( preparedMedia: PreparedUpload , forceRestart: Bool ) -> DirectUpload {
45
72
let upload = DirectUpload (
46
73
uploadURL: preparedMedia. remoteURL,
47
- videoFileURL: preparedMedia. localVideoFile
74
+ inputAsset: AVAsset ( url: preparedMedia. localVideoFile) ,
75
+ options: . default
48
76
)
49
77
upload. progressHandler = { progress in
50
78
self . logger. info ( " Uploading \( progress. progress? . completedUnitCount ?? 0 ) / \( progress. progress? . totalUnitCount ?? 0 ) " )
@@ -55,59 +83,67 @@ class UploadCreationModel : ObservableObject {
55
83
}
56
84
57
85
/// 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 ) {
75
87
exportState = . preparing
76
88
77
89
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 " )
90
97
Task . detached {
91
98
await MainActor . run {
92
- self . exportState = . failure( PickerError . missingAssetIdentifier)
99
+ self . exportState = . failure(
100
+ PickerError . assetExportSessionFailed
101
+ )
93
102
}
94
103
}
95
104
return
96
105
}
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
+ } )
107
144
}
108
- self . exportToOutFile ( session: exportSession, outFile: tempFile)
109
145
}
110
- } )
146
+ }
111
147
}
112
148
113
149
private func exportToOutFile( session: AVAssetExportSession , outFile: URL ) {
@@ -180,32 +216,13 @@ class UploadCreationModel : ObservableObject {
180
216
181
217
}
182
218
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
+ )
209
226
}
210
227
}
211
228
@@ -216,11 +233,16 @@ struct PreparedUpload {
216
233
}
217
234
218
235
enum 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 )
220
240
}
221
241
222
242
enum 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 )
224
246
}
225
247
226
248
extension PHAuthorizationStatus {
0 commit comments