Skip to content

Commit 35583c8

Browse files
committed
Input standardization 3 (#46)
* Input standardization 3 Standardize via export session * Back out outputURL construction into MuxUpload * Expose hook for client to cancel upload if standardization failed * Call cancellation hook if inspection fails We're not sure if the input is standard or not so better to be safe and confirm * Export based on maximum resolution set by client * Fix test app build
1 parent 308d6dc commit 35583c8

File tree

5 files changed

+176
-6
lines changed

5 files changed

+176
-6
lines changed

Sources/MuxUploadSDK/InputStandardization/UploadInputStandardizationWorker.swift

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,72 @@
55
import AVFoundation
66
import Foundation
77

8+
protocol Standardizable { }
9+
10+
extension AVAsset: Standardizable { }
11+
12+
enum StandardizationResult {
13+
case success(standardizedAsset: AVAsset)
14+
case failure(error: Error)
15+
}
16+
17+
enum StandardizationStrategy {
18+
// Prefer using export session whenever possible
19+
case exportSession
20+
}
21+
822
class UploadInputStandardizationWorker {
923

1024
var sourceInput: AVAsset?
1125

1226
var standardizedInput: AVAsset?
27+
28+
func standardize(
29+
sourceAsset: AVAsset,
30+
maximumResolution: UploadOptions.InputStandardization.MaximumResolution,
31+
outputURL: URL,
32+
completion: @escaping (AVAsset, AVAsset?, URL?, Bool) -> ()
33+
) {
34+
35+
let availableExportPresets = AVAssetExportSession.allExportPresets()
36+
37+
let exportPreset: String
38+
if maximumResolution == .preset1280x720 {
39+
exportPreset = AVAssetExportPreset1280x720
40+
} else {
41+
exportPreset = AVAssetExportPreset1920x1080
42+
}
43+
44+
guard availableExportPresets.contains(where: {
45+
$0 == exportPreset
46+
}) else {
47+
// TODO: Use VideoToolbox if export preset unavailable
48+
completion(sourceAsset, nil, nil, false)
49+
return
50+
}
51+
52+
guard let exportSession = AVAssetExportSession(
53+
asset: sourceAsset,
54+
presetName: exportPreset
55+
) else {
56+
// TODO: Use VideoToolbox if export session fails to initialize
57+
completion(sourceAsset, nil, nil, false)
58+
return
59+
}
60+
61+
exportSession.outputFileType = .mp4
62+
exportSession.outputURL = outputURL
63+
64+
// TODO: Use Swift Concurrency
65+
exportSession.exportAsynchronously {
66+
if let exportError = exportSession.error {
67+
completion(sourceAsset, nil, nil, false)
68+
} else if let standardizedAssetURL = exportSession.outputURL {
69+
let standardizedAsset = AVAsset(url: standardizedAssetURL)
70+
completion(sourceAsset, standardizedAsset, outputURL, true)
71+
} else {
72+
completion(sourceAsset, nil, nil, false)
73+
}
74+
}
75+
}
1376
}

Sources/MuxUploadSDK/InputStandardization/UploadInputStandardizer.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,32 @@ import AVFoundation
66
import Foundation
77

88
class UploadInputStandardizer {
9-
9+
var workers: [String: UploadInputStandardizationWorker] = [:]
10+
11+
func standardize(
12+
id: String,
13+
sourceAsset: AVAsset,
14+
maximumResolution: UploadOptions.InputStandardization.MaximumResolution,
15+
outputURL: URL,
16+
completion: @escaping (AVAsset, AVAsset?, URL?, Bool) -> ()
17+
) {
18+
let worker = UploadInputStandardizationWorker()
19+
20+
worker.standardize(
21+
sourceAsset: sourceAsset,
22+
maximumResolution: maximumResolution,
23+
outputURL: outputURL,
24+
completion: completion
25+
)
26+
workers[id] = worker
27+
}
28+
29+
// Storing the worker might not be necessary if an
30+
// alternative reference is in place outside the
31+
// stack frame
32+
func acknowledgeCompletion(
33+
id: String
34+
) {
35+
workers[id] = nil
36+
}
1037
}

Sources/MuxUploadSDK/PublicAPI/MuxUpload.swift

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,18 @@ public final class MuxUpload : Hashable, Equatable {
149149
*/
150150
public var inputStatusHandler: InputStatusHandler?
151151

152+
/**
153+
Confirms upload if input standardization did not succeed
154+
*/
155+
public typealias NonStandardInputHandler = () -> Bool
156+
157+
/**
158+
If set will be executed by the SDK when input standardization
159+
hadn't succeeded, return <doc:true> to continue the upload
160+
or return <doc:false> to cancel the upload
161+
*/
162+
public var nonStandardInputHandler: NonStandardInputHandler?
163+
152164
private let manageBySDK: Bool
153165
var id: String {
154166
uploadInfo.id
@@ -472,6 +484,28 @@ public final class MuxUpload : Hashable, Equatable {
472484

473485
switch inspectionResult {
474486
case .inspectionFailure:
487+
// TODO: Request upload confirmation
488+
// before proceeding
489+
490+
guard let nonStandardInputHandler = self.nonStandardInputHandler else {
491+
self.startNetworkTransport(
492+
videoFile: videoFile
493+
)
494+
return
495+
}
496+
497+
let shouldCancelUpload = nonStandardInputHandler()
498+
499+
if !shouldCancelUpload {
500+
self.startNetworkTransport(
501+
videoFile: videoFile
502+
)
503+
} else {
504+
self.fileWorker?.cancel()
505+
self.uploadManager.acknowledgeUpload(id: self.id)
506+
self.input.processUploadCancellation()
507+
}
508+
475509
self.startNetworkTransport(videoFile: videoFile)
476510
case .standard:
477511
self.startNetworkTransport(videoFile: videoFile)
@@ -486,8 +520,54 @@ public final class MuxUpload : Hashable, Equatable {
486520
"""
487521
)
488522

489-
// Skip format standardization
490-
self.startNetworkTransport(videoFile: videoFile)
523+
// TODO: inject Date() for testing purposes
524+
let outputFileName = "upload-\(Date().timeIntervalSince1970)"
525+
526+
let outputDirectory = FileManager.default.temporaryDirectory
527+
let outputURL = URL(
528+
fileURLWithPath: outputFileName,
529+
relativeTo: outputDirectory
530+
)
531+
let maximumResolution = self.input
532+
.uploadInfo
533+
.options
534+
.inputStandardization
535+
.maximumResolution
536+
537+
self.inputStandardizer.standardize(
538+
id: self.id,
539+
sourceAsset: AVAsset(url: videoFile),
540+
maximumResolution: maximumResolution,
541+
outputURL: outputURL
542+
) { sourceAsset, standardizedAsset, outputURL, success in
543+
544+
if let outputURL, success {
545+
self.startNetworkTransport(
546+
videoFile: outputURL
547+
)
548+
} else {
549+
guard let nonStandardInputHandler = self.nonStandardInputHandler else {
550+
self.startNetworkTransport(
551+
videoFile: videoFile
552+
)
553+
return
554+
}
555+
556+
let shouldCancelUpload = nonStandardInputHandler()
557+
558+
if !shouldCancelUpload {
559+
self.startNetworkTransport(
560+
videoFile: videoFile
561+
)
562+
} else {
563+
self.fileWorker?.cancel()
564+
self.uploadManager.acknowledgeUpload(id: self.id)
565+
self.input.processUploadCancellation()
566+
}
567+
}
568+
569+
self.inputStandardizer.acknowledgeCompletion(id: self.id)
570+
}
491571
}
492572
}
493573
}

apps/Test App/Test App/Model/UploadListModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class UploadListModel : ObservableObject {
3535
self.lastKnownUploads = Array(uploadSet)
3636
.sorted(
3737
by: { lhs, rhs in
38-
lhs.uploadStatus.startTime >= rhs.uploadStatus.startTime
38+
(lhs.uploadStatus?.startTime ?? 0) >= (rhs.uploadStatus?.startTime ?? 0)
3939
}
4040
)
4141
}

apps/Test App/Test App/Screens/UploadListView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,11 @@ fileprivate struct ListItem: View {
125125
}
126126

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

132-
let totalTimeSecs = status.updatedTime - status.startTime
132+
let totalTimeSecs = status.updatedTime - (status.startTime ?? 0)
133133
let totalTimeMs = Int64((totalTimeSecs) * 1000)
134134
let kbytesPerSec = (progress.completedUnitCount) / totalTimeMs // bytes/milli = kb/sec
135135
let fourSigs = NumberFormatter()

0 commit comments

Comments
 (0)