Skip to content

Commit

Permalink
Releases/v1.0.0 (#111)
Browse files Browse the repository at this point in the history
* 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)
  • Loading branch information
andrewjl-mux authored Jun 12, 2024
1 parent 50d5215 commit 8457ea3
Show file tree
Hide file tree
Showing 41 changed files with 1,132 additions and 1,212 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
jobs:
unit-tests:
name: Run Unit Tests
runs-on: macos-13
runs-on: macos-14
steps:
- name: Install xcbeautify
run: brew install xcbeautify
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-plugin",
"state" : {
"revision" : "9b1258905c21fc1b97bf03d1b4ca12c4ec4e5fda",
"version" : "1.2.0"
"revision" : "26ac5758409154cc448d7ab82389c520fa8a8247",
"version" : "1.3.0"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,6 @@
ReferencedContainer = "container:SwiftUploadSDKExample.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F38876C62B86DBEB00B82A86"
BuildableName = "SwiftUploadSDKExampleUnitTests.xctest"
BlueprintName = "SwiftUploadSDKExampleUnitTests"
ReferencedContainer = "container:SwiftUploadSDKExample.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,7 @@ import MuxUploadSDK
class ThumbnailModel: ObservableObject {

func startExtractingThumbnail() {
guard thumbnailGenerator == nil else {
return
}

thumbnailGenerator = AVAssetImageGenerator(asset: asset)
thumbnailGenerator?.generateCGImagesAsynchronously(forTimes: [NSValue(time: CMTime.zero)]) {
thumbnailGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: CMTime.zero)]) {
requestedTime,
image,
actualTime,
Expand All @@ -41,23 +36,29 @@ class ThumbnailModel: ObservableObject {
}
}
@unknown default:
fatalError()
SwiftUploadSDKExample.logger.error("Failed to extract thumnail with invalid result")
}
}

}

private let asset: AVAsset
private let upload: DirectUpload
private var thumbnailGenerator: AVAssetImageGenerator?

let upload: DirectUpload
var asset: AVAsset {
upload.inputAsset
}

let thumbnailGenerator: AVAssetImageGenerator

@Published var thumbnail: CGImage?
@Published var uploadProgress: DirectUpload.TransportStatus?

init(asset: AVAsset, upload: DirectUpload) {
self.asset = asset
init(upload: DirectUpload) {
self.upload = upload

self.thumbnailGenerator = AVAssetImageGenerator(
asset: upload.inputAsset
)
self.thumbnailGenerator.appliesPreferredTrackTransform = true

upload.progressHandler = { state in
SwiftUploadSDKExample.logger.info("Upload progressing from ViewModel: \(state.progress)")
self.uploadProgress = state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@
import Foundation
import PhotosUI
import MuxUploadSDK
import SwiftUI

struct UploadInput: Transferable {
let file: URL
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(
contentType: .mpeg4Movie
) { transferable in
SentTransferredFile(transferable.file)
} importing: { receivedTransferedFile in
UploadInput(file: receivedTransferedFile.file)
}
}
}

class UploadCreationModel : ObservableObject {

Expand All @@ -32,19 +46,33 @@ class UploadCreationModel : ObservableObject {
var localizedDescription: String

}

func requestPhotosAccess() {
switch photosAuthStatus {
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")
case .authorized(_): logger.warning("requestPhotosAccess called but we already had access. ignoring")
case .can_auth(_): doRequestPhotosPermission()

private var assetRequestId: PHImageRequestID? = nil
private var prepareTask: Task<Void, Never>? = nil
private var thumbnailGenerator: AVAssetImageGenerator? = nil

private let logger = SwiftUploadSDKExample.logger
private let myServerBackend = FakeBackend(urlSession: URLSession(configuration: URLSessionConfiguration.default))

@Published var photosAuthStatus: PhotosAuthState
@Published var exportState: ExportState = .not_started
@Published var pickedItem: [PhotosPickerItem] = [] {
didSet {
tryToPrepare(from: pickedItem.first!)
}
}

init() {
let innerAuthStatus = PHPhotoLibrary.authorizationStatus(for: .readWrite)
self.photosAuthStatus = innerAuthStatus.asAppAuthState()
self.exportState = .not_started
}

@discardableResult func startUpload(preparedMedia: PreparedUpload, forceRestart: Bool) -> DirectUpload {
let upload = DirectUpload(
uploadURL: preparedMedia.remoteURL,
videoFileURL: preparedMedia.localVideoFile
inputAsset: AVAsset(url: preparedMedia.localVideoFile),
options: .default
)
upload.progressHandler = { progress in
self.logger.info("Uploading \(progress.progress?.completedUnitCount ?? 0)/\(progress.progress?.totalUnitCount ?? 0)")
Expand All @@ -55,59 +83,67 @@ class UploadCreationModel : ObservableObject {
}

/// Prepares a Photos Asset for upload by exporting it to a local temp file
func tryToPrepare(from pickerResult: PHPickerResult) {
if case ExportState.preparing = exportState {
return
}

// Cancel anything that was already happening
if let assetRequestId = assetRequestId {
PHImageManager.default().cancelImageRequest(assetRequestId)
}
if let prepareTask = prepareTask {
prepareTask.cancel()
}
if let thumbnailGenerator = thumbnailGenerator {
thumbnailGenerator.cancelAllCGImageGeneration()
}

// TODO: This is a very common workflow. Should the SDK be able to do this workflow with Photos?
func tryToPrepare(from pickerItem: PhotosPickerItem) {
exportState = .preparing

let tempDir = FileManager.default.temporaryDirectory
let tempFile = URL(string: "upload-\(Date().timeIntervalSince1970).mp4", relativeTo: tempDir)!

guard let assetIdentitfier = pickerResult.assetIdentifier else {
NSLog("!! No Asset ID for chosen asset")
exportState = .failure(UploadCreationModel.PickerError.assetExportSessionFailed)
return
}
let options = PHFetchOptions()
options.includeAssetSourceTypes = [.typeUserLibrary, .typeCloudShared]
let phAssetResult = PHAsset.fetchAssets(withLocalIdentifiers: [assetIdentitfier], options: options)
guard let phAsset = phAssetResult.firstObject else {
self.logger.error("!! No Asset fetched")
let tempFile = URL(
string: "upload-\(Date().timeIntervalSince1970).mp4",
relativeTo: tempDir
)!

guard let itemIdentifier = pickerItem.itemIdentifier else {
self.logger.error("No item identifier for chosen video")
Task.detached {
await MainActor.run {
self.exportState = .failure(PickerError.missingAssetIdentifier)
self.exportState = .failure(
PickerError.assetExportSessionFailed
)
}
}
return
}

let exportOptions = PHVideoRequestOptions()
exportOptions.isNetworkAccessAllowed = true
exportOptions.deliveryMode = .highQualityFormat
assetRequestId = PHImageManager.default().requestExportSession(forVideo: phAsset, options: exportOptions, exportPreset: AVAssetExportPresetHighestQuality, resultHandler: {(exportSession, info) -> Void in
DispatchQueue.main.async {
guard let exportSession = exportSession else {
self.logger.error("!! No Export session")
self.exportState = .failure(UploadCreationModel.PickerError.assetExportSessionFailed)
return

doRequestPhotosPermission { authorizationStatus in
Task.detached {
await MainActor.run {
self.photosAuthStatus = authorizationStatus.asAppAuthState()

let options = PHFetchOptions()
options.includeAssetSourceTypes = [.typeUserLibrary, .typeCloudShared]
let fetchAssetResult = PHAsset.fetchAssets(withLocalIdentifiers: [itemIdentifier], options: options)
guard let fetchedAsset = fetchAssetResult.firstObject else {
self.logger.error("No Asset fetched")
Task.detached {
await MainActor.run {
self.exportState = .failure(
PickerError.missingAssetIdentifier
)
}
}
return
}

let exportOptions = PHVideoRequestOptions()
exportOptions.isNetworkAccessAllowed = true
exportOptions.deliveryMode = .highQualityFormat
self.assetRequestId = PHImageManager.default().requestExportSession(
forVideo: fetchedAsset,
options: exportOptions,
exportPreset: AVAssetExportPresetPassthrough,
resultHandler: {(exportSession, info) -> Void in
DispatchQueue.main.async {
guard let exportSession = exportSession else {
self.logger.error("!! No Export session")
self.exportState = .failure(UploadCreationModel.PickerError.assetExportSessionFailed)
return
}
self.exportToOutFile(session: exportSession, outFile: tempFile)
}
})
}
self.exportToOutFile(session: exportSession, outFile: tempFile)
}
})
}
}

private func exportToOutFile(session: AVAssetExportSession, outFile: URL) {
Expand Down Expand Up @@ -180,32 +216,13 @@ class UploadCreationModel : ObservableObject {

}

private func doRequestPhotosPermission() {
PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
Task.detached {
await MainActor.run {
self.photosAuthStatus = status.asAppAuthState()
}
}
}
}

private var assetRequestId: PHImageRequestID? = nil
private var prepareTask: Task<Void, Never>? = nil
private var thumbnailGenerator: AVAssetImageGenerator? = nil

private let logger = SwiftUploadSDKExample.logger
private let myServerBackend = FakeBackend(urlSession: URLSession(configuration: URLSessionConfiguration.default))

@Published
var photosAuthStatus: PhotosAuthState
@Published
var exportState: ExportState = .not_started

init() {
let innerAuthStatus = PHPhotoLibrary.authorizationStatus(for: .readWrite)
self.photosAuthStatus = innerAuthStatus.asAppAuthState()
self.exportState = .not_started
private func doRequestPhotosPermission(
handler: @escaping (PHAuthorizationStatus) -> Void
) {
PHPhotoLibrary.requestAuthorization(
for: .readWrite,
handler: handler
)
}
}

Expand All @@ -216,11 +233,16 @@ struct PreparedUpload {
}

enum ExportState {
case not_started, preparing, failure(UploadCreationModel.PickerError), ready(PreparedUpload)
case not_started
case preparing
case failure(UploadCreationModel.PickerError)
case ready(PreparedUpload)
}

enum PhotosAuthState {
case cant_auth(PHAuthorizationStatus), can_auth(PHAuthorizationStatus), authorized(PHAuthorizationStatus)
case cant_auth(PHAuthorizationStatus)
case can_auth(PHAuthorizationStatus)
case authorized(PHAuthorizationStatus)
}

extension PHAuthorizationStatus {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@ import AVFoundation
import MuxUploadSDK

class UploadListModel : ObservableObject {

init() {
DirectUploadManager.shared.addDelegate(

@Published var lastKnownUploads: [DirectUpload]

init(
directUploadManager: DirectUploadManager = .shared
) {

self.lastKnownUploads = directUploadManager.allManagedDirectUploads()
directUploadManager.addDelegate(
Delegate(
handler: { uploads in

Expand All @@ -39,8 +45,6 @@ class UploadListModel : ObservableObject {
)
)
}

@Published var lastKnownUploads: [DirectUpload] = Array()
}

fileprivate class Delegate: DirectUploadManagerDelegate {
Expand Down
Loading

0 comments on commit 8457ea3

Please sign in to comment.