Skip to content

Commit e70b5b3

Browse files
committed
Reporting updates (#49)
* Only mark upload as started if its ready * Reporting updates Add upload failed and input standardization events Add standardization failure event handler to reporter Add standardization success event handler to reporter Rename method for upload success Rename upload event to upload succeeded Support dispatching multiple events at the same time Naive non-thread safe implementation reporter networking Change event type casing, individual files for events Fix reporter test Actually report upload failure Read off file size Use Date in events and serialize to ISO8601 string Finangle duration and start time Set correct version in correct place Keep a session UUID Route every request through one chokepoint Upload failed test Shared test encoder Input standardization failed test Input standardization succeeded test Better organize expected json strings Use a shared instance Remove dupe file * Thread asset duration through inspection and standardization * Pass export error back * Include non standard input reasons * Workaround duration load pause * Add early return in delegate callback * Call correct method * Overloaded methods * Avoid mutating while iterating * tests * Remove unnecessary check
1 parent a7775e2 commit e70b5b3

19 files changed

+913
-185
lines changed

Sources/MuxUploadSDK/InputInspection/UploadInputFormatInspectionResult.swift

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ enum UploadInputFormatInspectionResult {
2121
case unsupportedPixelFormat
2222
}
2323

24-
case inspectionFailure
25-
case standard
26-
case nonstandard([NonstandardInputReason])
24+
case inspectionFailure(duration: CMTime)
25+
case standard(duration: CMTime)
26+
case nonstandard(
27+
reasons: [NonstandardInputReason],
28+
duration: CMTime
29+
)
2730

2831
var isStandard: Bool {
2932
if case Self.standard = self {
@@ -33,12 +36,52 @@ enum UploadInputFormatInspectionResult {
3336
}
3437
}
3538

39+
var sourceInputDuration: CMTime {
40+
switch self {
41+
case .inspectionFailure(duration: let duration):
42+
return duration
43+
case .standard(duration: let duration):
44+
return duration
45+
case .nonstandard(_, duration: let duration):
46+
return duration
47+
}
48+
}
49+
3650
var nonstandardInputReasons: [NonstandardInputReason]? {
37-
if case Self.nonstandard(let nonstandardInputReasons) = self {
51+
if case Self.nonstandard(let nonstandardInputReasons, _) = self {
3852
return nonstandardInputReasons
3953
} else {
4054
return nil
4155
}
4256
}
4357

4458
}
59+
60+
extension UploadInputFormatInspectionResult.NonstandardInputReason: CustomStringConvertible {
61+
var description: String {
62+
switch self {
63+
case .audioCodec:
64+
return "audio_codec"
65+
case .audioEditList:
66+
return "audio_edit_list"
67+
case .pixelAspectRatio:
68+
return "pixel_aspect_ratio"
69+
case .videoBitrate:
70+
return "video_bitrate"
71+
case .videoCodec:
72+
return "video_codec"
73+
case .videoEditList:
74+
return "video_edit_list"
75+
case .videoFrameRate:
76+
return "video_frame_rate"
77+
case .videoGOPSize:
78+
return "video_gop_size"
79+
case .videoResolution:
80+
return "video_resolution"
81+
case .unexpectedMediaFileParameters:
82+
return "unexpected_media_file_parameters"
83+
case .unsupportedPixelFormat:
84+
return "unsupported_pixel_format"
85+
}
86+
}
87+
}

Sources/MuxUploadSDK/InputInspection/UploadInputInspector.swift

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,50 +20,83 @@ class AVFoundationUploadInputInspector: UploadInputInspector {
2020
func performInspection(
2121
sourceInput: AVAsset,
2222
completionHandler: @escaping (UploadInputFormatInspectionResult) -> ()
23+
) {
24+
sourceInput.loadValuesAsynchronously(
25+
forKeys: [
26+
"duration"
27+
]
28+
) {
29+
// FIXME: Trying to avoid the callback pyramid of doom
30+
// here, newer AVAsset APIs use Concurrency
31+
// but Concurrency itself has very primitive
32+
// task sequencing. Replace with async AVAsset
33+
// methods.
34+
let sourceInputDuration = sourceInput.duration
35+
self.performInspection(
36+
sourceInput: sourceInput,
37+
sourceInputDuration: sourceInputDuration,
38+
completionHandler: completionHandler
39+
)
40+
}
41+
}
42+
43+
func performInspection(
44+
sourceInput: AVAsset,
45+
sourceInputDuration: CMTime,
46+
completionHandler: @escaping (UploadInputFormatInspectionResult) -> ()
2347
) {
2448
// TODO: Eventually load audio tracks too
2549
if #available(iOS 15, *) {
2650
sourceInput.loadTracks(
2751
withMediaType: .video
2852
) { tracks, error in
2953
if error != nil {
30-
completionHandler(.inspectionFailure)
54+
completionHandler(
55+
.inspectionFailure(duration: sourceInputDuration)
56+
)
3157
return
3258
}
3359

3460
if let tracks {
3561
self.inspect(
62+
sourceInputDuration: sourceInputDuration,
3663
tracks: tracks,
3764
completionHandler: completionHandler
3865
)
3966
}
4067
}
4168
} else {
4269
sourceInput.loadValuesAsynchronously(
43-
forKeys: ["tracks"]
70+
forKeys: [
71+
"tracks"
72+
]
4473
) {
4574
// Non-blocking if "tracks" is already loaded
4675
let tracks = sourceInput.tracks(
4776
withMediaType: .video
4877
)
78+
4979
self.inspect(
80+
sourceInputDuration: sourceInputDuration,
5081
tracks: tracks,
5182
completionHandler: completionHandler
5283
)
5384
}
5485
}
55-
5686
}
5787

5888
func inspect(
89+
sourceInputDuration: CMTime,
5990
tracks: [AVAssetTrack],
6091
completionHandler: @escaping (UploadInputFormatInspectionResult) -> ()
6192
) {
6293
switch tracks.count {
6394
case 0:
6495
// Nothing to inspect, therefore nothing to standardize
6596
// declare as already standard
66-
completionHandler(.standard)
97+
completionHandler(
98+
.standard(duration: sourceInputDuration)
99+
)
67100
case 1:
68101
if let track = tracks.first {
69102
track.loadValuesAsynchronously(
@@ -73,12 +106,18 @@ class AVFoundationUploadInputInspector: UploadInputInspector {
73106
]
74107
) {
75108
guard let formatDescriptions = track.formatDescriptions as? [CMFormatDescription] else {
76-
completionHandler(.inspectionFailure)
109+
completionHandler(
110+
.inspectionFailure(
111+
duration: sourceInputDuration
112+
)
113+
)
77114
return
78115
}
79116

80117
guard let formatDescription = formatDescriptions.first else {
81-
completionHandler(.inspectionFailure)
118+
completionHandler(
119+
.inspectionFailure(duration: sourceInputDuration)
120+
)
82121
return
83122
}
84123

@@ -106,17 +145,21 @@ class AVFoundationUploadInputInspector: UploadInputInspector {
106145
}
107146

108147
if nonStandardReasons.isEmpty {
109-
completionHandler(.standard)
148+
completionHandler(
149+
.standard(duration: sourceInputDuration)
150+
)
110151
} else {
111-
completionHandler(.nonstandard(nonStandardReasons))
152+
completionHandler(.nonstandard(reasons: nonStandardReasons, duration: sourceInputDuration))
112153
}
113154

114155
}
115156
}
116157
default:
117158
// Inspection fails for multi-video track inputs
118159
// for the time being
119-
completionHandler(.inspectionFailure)
160+
completionHandler(
161+
.inspectionFailure(duration: sourceInputDuration)
162+
)
120163
}
121164
}
122165
}

Sources/MuxUploadSDK/InputStandardization/UploadInputStandardizationWorker.swift

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,22 @@ enum StandardizationStrategy {
1919
case exportSession
2020
}
2121

22+
struct StandardizationError: Error {
23+
var localizedDescription: String
24+
25+
static var missingExportPreset = StandardizationError(
26+
localizedDescription: "Missing export session preset"
27+
)
28+
29+
static var exportSessionInitializationFailure = StandardizationError(
30+
localizedDescription: "Export session failed to initialize"
31+
)
32+
33+
static var standardizedAssetExportFailure = StandardizationError(
34+
localizedDescription: "Failed to export standardized asset"
35+
)
36+
}
37+
2238
class UploadInputStandardizationWorker {
2339

2440
var sourceInput: AVAsset?
@@ -29,7 +45,7 @@ class UploadInputStandardizationWorker {
2945
sourceAsset: AVAsset,
3046
maximumResolution: UploadOptions.InputStandardization.MaximumResolution,
3147
outputURL: URL,
32-
completion: @escaping (AVAsset, AVAsset?, URL?, Bool) -> ()
48+
completion: @escaping (AVAsset, AVAsset?, Error?) -> ()
3349
) {
3450

3551
let availableExportPresets = AVAssetExportSession.allExportPresets()
@@ -45,7 +61,7 @@ class UploadInputStandardizationWorker {
4561
$0 == exportPreset
4662
}) else {
4763
// TODO: Use VideoToolbox if export preset unavailable
48-
completion(sourceAsset, nil, nil, false)
64+
completion(sourceAsset, nil, StandardizationError.missingExportPreset)
4965
return
5066
}
5167

@@ -54,7 +70,7 @@ class UploadInputStandardizationWorker {
5470
presetName: exportPreset
5571
) else {
5672
// TODO: Use VideoToolbox if export session fails to initialize
57-
completion(sourceAsset, nil, nil, false)
73+
completion(sourceAsset, nil, StandardizationError.exportSessionInitializationFailure)
5874
return
5975
}
6076

@@ -64,12 +80,12 @@ class UploadInputStandardizationWorker {
6480
// TODO: Use Swift Concurrency
6581
exportSession.exportAsynchronously {
6682
if let exportError = exportSession.error {
67-
completion(sourceAsset, nil, nil, false)
83+
completion(sourceAsset, nil, exportError)
6884
} else if let standardizedAssetURL = exportSession.outputURL {
6985
let standardizedAsset = AVAsset(url: standardizedAssetURL)
70-
completion(sourceAsset, standardizedAsset, outputURL, true)
86+
completion(sourceAsset, standardizedAsset, nil)
7187
} else {
72-
completion(sourceAsset, nil, nil, false)
88+
completion(sourceAsset, nil, StandardizationError.standardizedAssetExportFailure)
7389
}
7490
}
7591
}

Sources/MuxUploadSDK/InputStandardization/UploadInputStandardizer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class UploadInputStandardizer {
1313
sourceAsset: AVAsset,
1414
maximumResolution: UploadOptions.InputStandardization.MaximumResolution,
1515
outputURL: URL,
16-
completion: @escaping (AVAsset, AVAsset?, URL?, Bool) -> ()
16+
completion: @escaping (AVAsset, AVAsset?, Error?) -> ()
1717
) {
1818
let worker = UploadInputStandardizationWorker()
1919

Sources/MuxUploadSDK/InternalUtilities/ChunkedFile.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class ChunkedFile {
9696
let fileSize = try fileManager.fileSizeOfItem(
9797
atPath: fileURL.path
9898
)
99-
99+
100100
guard let data = data else {
101101
// Called while already at the end of the file. We read zero bytes, "ending" at the end of the file
102102
return FileChunk(startByte: fileSize, endByte: fileSize, totalFileSize: fileSize, chunkData: Data(capacity: 0))
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// InputStandardizationFailedEvent.swift
3+
//
4+
5+
import Foundation
6+
7+
struct InputStandardizationFailedEvent: Codable {
8+
var type: String = "upload_input_standardization_failed"
9+
var sessionID: String
10+
var version: String = "1"
11+
var data: Data
12+
13+
struct Data: Codable {
14+
var appName: String?
15+
var appVersion: String?
16+
var deviceModel: String
17+
var errorDescription: String
18+
var inputDuration: Double
19+
var inputSize: UInt64
20+
var maximumResolution: String
21+
var nonStandardInputReasons: [String]
22+
var platformName: String
23+
var platformVersion: String
24+
var regionCode: String?
25+
var sdkVersion: String
26+
var standardizationStartTime: Date
27+
var standardizationEndTime: Date
28+
var uploadCanceled: Bool
29+
var uploadURL: URL
30+
}
31+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// InputStandardizationSucceededEvent.swift
3+
//
4+
5+
import Foundation
6+
7+
struct InputStandardizationSucceededEvent: Codable {
8+
var type: String = "upload_input_standardization_succeeded"
9+
var sessionID: String
10+
var version: String = "1"
11+
var data: Data
12+
13+
struct Data: Codable {
14+
var appName: String?
15+
var appVersion: String?
16+
var deviceModel: String
17+
var inputDuration: Double
18+
var inputSize: UInt64
19+
var maximumResolution: String
20+
var nonStandardInputReasons: [String]
21+
var platformName: String
22+
var platformVersion: String
23+
var regionCode: String?
24+
var sdkVersion: String
25+
var standardizationStartTime: Date
26+
var standardizationEndTime: Date
27+
var uploadURL: URL
28+
}
29+
}

0 commit comments

Comments
 (0)