Skip to content

Commit 4671747

Browse files
authored
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 f4796ef commit 4671747

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
@@ -474,6 +486,28 @@ public final class MuxUpload : Hashable, Equatable {
474486

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

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

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)