Skip to content

Commit 8457ea3

Browse files
authored
Releases/v1.0.0 (#111)
* build: remove corrupt reference in project file (#106) * feat: expose 4K resolution cap when requesting to standardize inputs (#105) * fix: inspect estimated per track bitrate (#107) * docs: wording improvement and formatting pass (#110) * fix: upload and source asset state handling (#109) * fix: upload cancelled while standardizing shouldn't proceed to the transport stage * chore: SDK example improvements (#108) * chore: remove deprecated API (#112)
1 parent 50d5215 commit 8457ea3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1132
-1212
lines changed

.github/workflows/run-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
jobs:
77
unit-tests:
88
name: Run Unit Tests
9-
runs-on: macos-13
9+
runs-on: macos-14
1010
steps:
1111
- name: Install xcbeautify
1212
run: brew install xcbeautify

Example/SwiftUploadSDKExample/SwiftUploadSDKExample.xcodeproj/project.pbxproj

Lines changed: 38 additions & 168 deletions
Large diffs are not rendered by default.

Example/SwiftUploadSDKExample/SwiftUploadSDKExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
"kind" : "remoteSourceControl",
66
"location" : "https://github.com/apple/swift-docc-plugin",
77
"state" : {
8-
"revision" : "9b1258905c21fc1b97bf03d1b4ca12c4ec4e5fda",
9-
"version" : "1.2.0"
8+
"revision" : "26ac5758409154cc448d7ab82389c520fa8a8247",
9+
"version" : "1.3.0"
1010
}
1111
},
1212
{

Example/SwiftUploadSDKExample/SwiftUploadSDKExample.xcodeproj/xcshareddata/xcschemes/SwiftUploadSDKExample.xcscheme

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,6 @@
4040
ReferencedContainer = "container:SwiftUploadSDKExample.xcodeproj">
4141
</BuildableReference>
4242
</TestableReference>
43-
<TestableReference
44-
skipped = "NO"
45-
parallelizable = "YES">
46-
<BuildableReference
47-
BuildableIdentifier = "primary"
48-
BlueprintIdentifier = "F38876C62B86DBEB00B82A86"
49-
BuildableName = "SwiftUploadSDKExampleUnitTests.xctest"
50-
BlueprintName = "SwiftUploadSDKExampleUnitTests"
51-
ReferencedContainer = "container:SwiftUploadSDKExample.xcodeproj">
52-
</BuildableReference>
53-
</TestableReference>
5443
</Testables>
5544
</TestAction>
5645
<LaunchAction

Example/SwiftUploadSDKExample/SwiftUploadSDKExample/ContentView.swift

Lines changed: 0 additions & 43 deletions
This file was deleted.

Example/SwiftUploadSDKExample/SwiftUploadSDKExample/Model/ThumbnailModel.swift

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,7 @@ import MuxUploadSDK
1212
class ThumbnailModel: ObservableObject {
1313

1414
func startExtractingThumbnail() {
15-
guard thumbnailGenerator == nil else {
16-
return
17-
}
18-
19-
thumbnailGenerator = AVAssetImageGenerator(asset: asset)
20-
thumbnailGenerator?.generateCGImagesAsynchronously(forTimes: [NSValue(time: CMTime.zero)]) {
15+
thumbnailGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: CMTime.zero)]) {
2116
requestedTime,
2217
image,
2318
actualTime,
@@ -41,23 +36,29 @@ class ThumbnailModel: ObservableObject {
4136
}
4237
}
4338
@unknown default:
44-
fatalError()
39+
SwiftUploadSDKExample.logger.error("Failed to extract thumnail with invalid result")
4540
}
4641
}
4742

4843
}
4944

50-
private let asset: AVAsset
51-
private let upload: DirectUpload
52-
private var thumbnailGenerator: AVAssetImageGenerator?
53-
45+
let upload: DirectUpload
46+
var asset: AVAsset {
47+
upload.inputAsset
48+
}
49+
50+
let thumbnailGenerator: AVAssetImageGenerator
51+
5452
@Published var thumbnail: CGImage?
5553
@Published var uploadProgress: DirectUpload.TransportStatus?
5654

57-
init(asset: AVAsset, upload: DirectUpload) {
58-
self.asset = asset
55+
init(upload: DirectUpload) {
5956
self.upload = upload
60-
57+
self.thumbnailGenerator = AVAssetImageGenerator(
58+
asset: upload.inputAsset
59+
)
60+
self.thumbnailGenerator.appliesPreferredTrackTransform = true
61+
6162
upload.progressHandler = { state in
6263
SwiftUploadSDKExample.logger.info("Upload progressing from ViewModel: \(state.progress)")
6364
self.uploadProgress = state

Example/SwiftUploadSDKExample/SwiftUploadSDKExample/Model/UploadCreationModel.swift

Lines changed: 99 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@
88
import Foundation
99
import PhotosUI
1010
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+
}
1125

1226
class 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

218235
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)
220240
}
221241

222242
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)
224246
}
225247

226248
extension PHAuthorizationStatus {

Example/SwiftUploadSDKExample/SwiftUploadSDKExample/Model/UploadListModel.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,15 @@ import AVFoundation
1010
import MuxUploadSDK
1111

1212
class UploadListModel : ObservableObject {
13-
14-
init() {
15-
DirectUploadManager.shared.addDelegate(
13+
14+
@Published var lastKnownUploads: [DirectUpload]
15+
16+
init(
17+
directUploadManager: DirectUploadManager = .shared
18+
) {
19+
20+
self.lastKnownUploads = directUploadManager.allManagedDirectUploads()
21+
directUploadManager.addDelegate(
1622
Delegate(
1723
handler: { uploads in
1824

@@ -39,8 +45,6 @@ class UploadListModel : ObservableObject {
3945
)
4046
)
4147
}
42-
43-
@Published var lastKnownUploads: [DirectUpload] = Array()
4448
}
4549

4650
fileprivate class Delegate: DirectUploadManagerDelegate {

0 commit comments

Comments
 (0)