Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,72 @@
import AVFoundation
import Foundation

protocol Standardizable { }

extension AVAsset: Standardizable { }

enum StandardizationResult {
case success(standardizedAsset: AVAsset)
case failure(error: Error)
}

enum StandardizationStrategy {
// Prefer using export session whenever possible
case exportSession
}

class UploadInputStandardizationWorker {

var sourceInput: AVAsset?

var standardizedInput: AVAsset?

func standardize(
sourceAsset: AVAsset,
maximumResolution: UploadOptions.InputStandardization.MaximumResolution,
outputURL: URL,
completion: @escaping (AVAsset, AVAsset?, URL?, Bool) -> ()
) {

let availableExportPresets = AVAssetExportSession.allExportPresets()

let exportPreset: String
if maximumResolution == .preset1280x720 {
exportPreset = AVAssetExportPreset1280x720
} else {
exportPreset = AVAssetExportPreset1920x1080
}

guard availableExportPresets.contains(where: {
$0 == exportPreset
}) else {
// TODO: Use VideoToolbox if export preset unavailable
completion(sourceAsset, nil, nil, false)
return
}

guard let exportSession = AVAssetExportSession(
asset: sourceAsset,
presetName: exportPreset
) else {
// TODO: Use VideoToolbox if export session fails to initialize
completion(sourceAsset, nil, nil, false)
return
}

exportSession.outputFileType = .mp4
exportSession.outputURL = outputURL

// TODO: Use Swift Concurrency
exportSession.exportAsynchronously {
if let exportError = exportSession.error {
completion(sourceAsset, nil, nil, false)
} else if let standardizedAssetURL = exportSession.outputURL {
let standardizedAsset = AVAsset(url: standardizedAssetURL)
completion(sourceAsset, standardizedAsset, outputURL, true)
} else {
completion(sourceAsset, nil, nil, false)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,32 @@ import AVFoundation
import Foundation

class UploadInputStandardizer {

var workers: [String: UploadInputStandardizationWorker] = [:]

func standardize(
id: String,
sourceAsset: AVAsset,
maximumResolution: UploadOptions.InputStandardization.MaximumResolution,
outputURL: URL,
completion: @escaping (AVAsset, AVAsset?, URL?, Bool) -> ()
) {
let worker = UploadInputStandardizationWorker()

worker.standardize(
sourceAsset: sourceAsset,
maximumResolution: maximumResolution,
outputURL: outputURL,
completion: completion
)
workers[id] = worker
}

// Storing the worker might not be necessary if an
// alternative reference is in place outside the
// stack frame
func acknowledgeCompletion(
id: String
) {
workers[id] = nil
}
}
84 changes: 82 additions & 2 deletions Sources/MuxUploadSDK/PublicAPI/MuxUpload.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,18 @@ public final class MuxUpload : Hashable, Equatable {
*/
public var inputStatusHandler: InputStatusHandler?

/**
Confirms upload if input standardization did not succeed
*/
public typealias NonStandardInputHandler = () -> Bool

/**
If set will be executed by the SDK when input standardization
hadn't succeeded, return <doc:true> to continue the upload
or return <doc:false> to cancel the upload
*/
public var nonStandardInputHandler: NonStandardInputHandler?

private let manageBySDK: Bool
var id: String {
uploadInfo.id
Expand Down Expand Up @@ -474,6 +486,28 @@ public final class MuxUpload : Hashable, Equatable {

switch inspectionResult {
case .inspectionFailure:
// TODO: Request upload confirmation
// before proceeding

guard let nonStandardInputHandler = self.nonStandardInputHandler else {
self.startNetworkTransport(
videoFile: videoFile
)
return
}

let shouldCancelUpload = nonStandardInputHandler()

if !shouldCancelUpload {
self.startNetworkTransport(
videoFile: videoFile
)
} else {
self.fileWorker?.cancel()
self.uploadManager.acknowledgeUpload(id: self.id)
self.input.processUploadCancellation()
}

self.startNetworkTransport(videoFile: videoFile)
case .standard:
self.startNetworkTransport(videoFile: videoFile)
Expand All @@ -488,8 +522,54 @@ public final class MuxUpload : Hashable, Equatable {
"""
)

// Skip format standardization
self.startNetworkTransport(videoFile: videoFile)
// TODO: inject Date() for testing purposes
let outputFileName = "upload-\(Date().timeIntervalSince1970)"

let outputDirectory = FileManager.default.temporaryDirectory
let outputURL = URL(
fileURLWithPath: outputFileName,
relativeTo: outputDirectory
)
let maximumResolution = self.input
.uploadInfo
.options
.inputStandardization
.maximumResolution

self.inputStandardizer.standardize(
id: self.id,
sourceAsset: AVAsset(url: videoFile),
maximumResolution: maximumResolution,
outputURL: outputURL
) { sourceAsset, standardizedAsset, outputURL, success in

if let outputURL, success {
self.startNetworkTransport(
videoFile: outputURL
)
} else {
guard let nonStandardInputHandler = self.nonStandardInputHandler else {
self.startNetworkTransport(
videoFile: videoFile
)
return
}

let shouldCancelUpload = nonStandardInputHandler()

if !shouldCancelUpload {
self.startNetworkTransport(
videoFile: videoFile
)
} else {
self.fileWorker?.cancel()
self.uploadManager.acknowledgeUpload(id: self.id)
self.input.processUploadCancellation()
}
}

self.inputStandardizer.acknowledgeCompletion(id: self.id)
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion apps/Test App/Test App/Model/UploadListModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class UploadListModel : ObservableObject {
self.lastKnownUploads = Array(uploadSet)
.sorted(
by: { lhs, rhs in
lhs.uploadStatus.startTime >= rhs.uploadStatus.startTime
(lhs.uploadStatus?.startTime ?? 0) >= (rhs.uploadStatus?.startTime ?? 0)
}
)
}
Expand Down
4 changes: 2 additions & 2 deletions apps/Test App/Test App/Screens/UploadListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,11 @@ fileprivate struct ListItem: View {
}

private func statusLine(status: MuxUpload.TransportStatus?) -> String {
guard let status = status, let progress = status.progress, status.startTime > 0 else {
guard let status = status, let progress = status.progress, let startTime = status.startTime, startTime > 0 else {
return "missing status"
}

let totalTimeSecs = status.updatedTime - status.startTime
let totalTimeSecs = status.updatedTime - (status.startTime ?? 0)
let totalTimeMs = Int64((totalTimeSecs) * 1000)
let kbytesPerSec = (progress.completedUnitCount) / totalTimeMs // bytes/milli = kb/sec
let fourSigs = NumberFormatter()
Expand Down