Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Releases/v1.0.0 #111

Merged
merged 12 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading