Skip to content

Commit be70b3d

Browse files
daytime-emandrewjl-muxrefactornatorweb-flow
authored
Releases/v0.3.0 (#26)
# API Changes * `MuxUpload`'s initializer no longer requires a MIME type or Retry Time. These are calculated internally * Added methods for querying the `UploadManager` for the list of currenty-active uploads, and listening for changes to the list ## Improvements * NFC: call into upload manager reference (#14) * api: Remove extraneous MIME type and retry time config fields, add opt-out for event tracking (#16) * doc: Add a much-improved example app (#15) ## Fixes * Fix: Uploads continue without reporting progress after being resumed (#24) * Fix: handlers not retained unless callers retain the `MuxUpload` (#25) Co-authored-by: AJ Lauer Barinov <102617203+andrewjl-mux@users.noreply.github.com> Co-authored-by: Liam Lindner <liam@mux.com> Co-authored-by: Emily Dixon <edixon@mux.com> Co-authored-by: GitHub <noreply@github.com>
1 parent 835e5f4 commit be70b3d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2008
-470
lines changed

Mux-Upload-SDK.podspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Pod::Spec.new do |s|
22
s.name = 'Mux-Upload-SDK'
33
s.module_name = 'MuxUploadSDK'
4-
s.version = '0.2.1'
4+
s.version = '0.3.0'
55
s.summary = 'Upload video to Mux.'
66
s.description = 'A library for uploading video to Mux. Similar to UpChunk, but for iOS.'
77

Sources/MuxUploadSDK/Internal Utilities/ChunkedFile.swift Sources/MuxUploadSDK/InternalUtilities/ChunkedFile.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class ChunkedFile {
2828

2929
/// Reads the next chunk from the file, advancing the file for the next read
3030
/// This method does synchronous I/O, so call it in the background
31-
public func readNextChunk() -> Result<FileChunk, Error> {
31+
func readNextChunk() -> Result<FileChunk, Error> {
3232
MuxUploadSDK.logger?.info("--readNextChunk(): called")
3333
do {
3434
guard fileHandle != nil else {
@@ -84,8 +84,8 @@ class ChunkedFile {
8484
}
8585
let data = try fileHandle.read(upToCount: chunkSize)
8686
guard let data = data else {
87-
// No more data to read, we reached EOF. The caller shouldn't call us like this, but let's be safe
88-
return FileChunk(startByte: 0, endByte: 0, totalFileSize: 0, chunkData: Data(capacity: 0))
87+
// Called while already at the end of the file. We read zero bytes, "ending" at the end of the file
88+
return FileChunk(startByte: fileSize, endByte: fileSize, totalFileSize: fileSize, chunkData: Data(capacity: 0))
8989
}
9090

9191
let nsData = NSData(data: data)

Sources/MuxUploadSDK/Internal Utilities/UploadEvent.swift Sources/MuxUploadSDK/InternalUtilities/UploadEvent.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ struct UploadEvent: Codable {
4343

4444
self.deviceModel = device.model
4545

46-
self.appName = Bundle.main.appName
46+
self.appName = Bundle.main.bundleIdentifier
4747
self.appVersion = Bundle.main.appVersion
4848

4949
if #available(iOS 16, *) {

Sources/MuxUploadSDK/Public API/MuxErrorCase.swift

-16
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// MuxErrorCode.swift
3+
//
4+
//
5+
// Created by Emily Dixon on 2/27/23.
6+
//
7+
8+
import Foundation
9+
10+
/// Represents the possible error cases from a ``MuxUpload``
11+
public enum MuxErrorCase : Int {
12+
/// The cause of the error is not known
13+
case unknown = -1
14+
/// The upload was cancelled
15+
case cancelled = 0
16+
/// The input file could not be read or processed
17+
case file = 1
18+
/// The upload could not be completed due to an HTTP error
19+
case http = 2
20+
/// The upload could not be completed due to a connection error
21+
case connection = 3
22+
}

Sources/MuxUploadSDK/Public API/MuxUpload.swift Sources/MuxUploadSDK/PublicAPI/MuxUpload.swift

+88-24
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,45 @@
77

88
import Foundation
99

10-
/**
11-
Uploads a video file to a previously-created Direct Upload. Create instances of this object using ``Builder``
10+
///
11+
/// Uploads a video file to a previously-created Mux Video Direct Upload.
12+
///
13+
/// This class is part of a full-stack workflow for uploading video files to Mux Video. In order to use this object you must first have
14+
/// created a [Direct Upload](https://docs.mux.com/guides/video/upload-files-directly) on your server backend.
15+
/// Then, use the PUT URL created there to upload your video file.
16+
///
17+
/// For example:
18+
/// ```swift
19+
/// let upload = MuxUpload(
20+
/// uploadURL: myMuxUploadURL,
21+
/// videoFileURL: myVideoFileURL,
22+
/// )
23+
///
24+
/// upload.progressHandler = { state in
25+
/// self.uploadScreenState = .uploading(state)
26+
/// }
27+
///
28+
/// upload.resultHandler = { result in
29+
/// switch result {
30+
/// case .success(let success):
31+
/// self.uploadScreenState = .done(success)
32+
/// self.upload = nil
33+
/// NSLog("Upload Success!")
34+
/// case .failure(let error):
35+
/// self.uploadScreenState = .failure(error)
36+
/// NSLog("!! Upload error: \(error.localizedDescription)")
37+
/// }
38+
/// }
39+
///
40+
/// self.upload = upload
41+
/// upload.start()
42+
/// ```
43+
///
44+
/// Uploads created by this SDK are globally managed by default, and can be resumed after failures or even after process death. For more information on
45+
/// this topic, see ``UploadManager``
46+
///
47+
public final class MuxUpload : Hashable, Equatable {
1248

13-
TODO: usage here
14-
*/
15-
public final class MuxUpload {
16-
1749
private let uploadInfo: UploadInfo
1850
private let manageBySDK: Bool
1951
private let id: Int
@@ -26,7 +58,7 @@ public final class MuxUpload {
2658
/**
2759
Represents the state of an upload in progress.
2860
*/
29-
public struct Status : Sendable {
61+
public struct Status : Sendable, Hashable {
3062
public let progress: Progress?
3163
public let updatedTime: TimeInterval
3264
public let startTime: TimeInterval
@@ -50,21 +82,25 @@ public final class MuxUpload {
5082

5183
}
5284

85+
/// Creates a new `MuxUpload` with the given confguration
86+
/// - Parameters
87+
/// - uploadURL: the PUT URL for your direct upload
88+
/// - videoFileURL: A URL to the input video file
89+
/// - retriesPerChunk: The number of times each chunk of the file upload can be retried before failure is declared
90+
/// - optOutOfEventTracking: This SDK collects performance and reliability data that helps make the Upload SDK the best it can be. If you do not wish to share this information with Mux, you may pass `true` for this parameter
5391
public convenience init(
5492
uploadURL: URL,
5593
videoFileURL: URL,
56-
videoMIMEType: String = "video/*", // TODO: We can guess this, so make it optional,
57-
chunkSize: Int = 8 * 1024 * 1024, // Google recommends *at least* 8M,
94+
chunkSize: Int = 8 * 1024 * 1024, // Google recommends at least 8M
5895
retriesPerChunk: Int = 3,
59-
retryBaseTimeInterval: TimeInterval = 0.5
96+
optOutOfEventTracking: Bool = false
6097
) {
6198
let uploadInfo = UploadInfo(
6299
uploadURL: uploadURL,
63100
videoFile: videoFileURL,
64-
videoMIMEType: videoMIMEType,
65101
chunkSize: chunkSize,
66102
retriesPerChunk: retriesPerChunk,
67-
retryBaseTime: retryBaseTimeInterval
103+
optOutOfEventTracking: optOutOfEventTracking
68104
)
69105

70106
self.init(
@@ -84,7 +120,7 @@ public final class MuxUpload {
84120
*/
85121
public var progressHandler: StateHandler?
86122

87-
public struct Success : Sendable {
123+
public struct Success : Sendable, Hashable {
88124
public let finalState: Status
89125
}
90126

@@ -107,7 +143,12 @@ public final class MuxUpload {
107143
/**
108144
True if this upload is currently in progress and not paused
109145
*/
110-
var inProgress: Bool { get { fileWorker != nil } }
146+
public var inProgress: Bool { get { fileWorker != nil && !complete } }
147+
148+
/**
149+
True if this upload was completed
150+
*/
151+
public var complete: Bool { get { lastSeenStatus.progress?.completedUnitCount ?? 0 > 0 && lastSeenStatus.progress?.fractionCompleted ?? 0 >= 1.0 } }
111152

112153
/**
113154
URL to the file that will be uploaded
@@ -118,30 +159,38 @@ public final class MuxUpload {
118159
The remote endpoint that this object uploads to
119160
*/
120161
public var uploadURL: URL { get { return uploadInfo.uploadURL } }
121-
// TODO: Computed Properties for some other UploadInfo properties
122162

123163
/**
124164
Begins the upload. You can control what happens when the upload is already started. If `forceRestart` is true, the upload will be restarted. Otherwise, nothing will happen. The default is not to restart
125165
*/
126166
public func start(forceRestart: Bool = false) {
127-
if self.manageBySDK {
167+
// Use an existing globally-managed upload if desired & one exists
168+
if self.manageBySDK && fileWorker == nil {
128169
// See if there's anything in progress already
129-
fileWorker = UploadManager.shared.findUploaderFor(videoFile: videoFile)
170+
fileWorker = uploadManager.findUploaderFor(videoFile: videoFile)
130171
}
131-
guard !forceRestart && fileWorker == nil else {
132-
MuxUploadSDK.logger?.warning("start() called but upload is in progress")
172+
if fileWorker != nil && !forceRestart {
173+
MuxUploadSDK.logger?.warning("start() called but upload is already in progress")
174+
fileWorker?.addDelegate(
175+
withToken: id,
176+
InternalUploaderDelegate { [weak self] state in self?.handleStateUpdate(state) }
177+
)
178+
fileWorker?.start()
133179
return
134180
}
181+
182+
// Start a new upload
135183
if forceRestart {
136-
fileWorker?.cancel()
184+
cancel()
137185
}
138186
let completedUnitCount = UInt64({ self.lastSeenStatus.progress?.completedUnitCount ?? 0 }())
139187
let fileWorker = ChunkedFileUploader(uploadInfo: uploadInfo, startingAtByte: completedUnitCount)
140188
fileWorker.addDelegate(
141189
withToken: id,
142-
InternalUploaderDelegate { [weak self] state in self?.handleStateUpdate(state) }
190+
InternalUploaderDelegate { [self] state in handleStateUpdate(state) }
143191
)
144192
fileWorker.start()
193+
uploadManager.registerUploader(fileWorker, withId: id)
145194
self.fileWorker = fileWorker
146195
}
147196

@@ -156,11 +205,11 @@ public final class MuxUpload {
156205
}
157206

158207
/**
159-
Cancels an ongoing download. Temp files will be deleted asynchronously. State and Delegates will be cleared. Your delegates will recieve no further calls
208+
Cancels an ongoing download. State and Delegates will be cleared. Your delegates will recieve no further calls
160209
*/
161210
public func cancel() {
162211
fileWorker?.cancel()
163-
UploadManager.shared.acknowledgeUpload(ofFile: videoFile)
212+
uploadManager.acknowledgeUpload(ofFile: videoFile)
164213

165214
lastSeenStatus = Status(progress: nil, updatedTime: 0, startTime: 0, isPaused: true)
166215
progressHandler = nil
@@ -174,6 +223,7 @@ public final class MuxUpload {
174223
let finalStatus = Status(progress: result.finalProgress, updatedTime: result.finishTime, startTime: result.startTime, isPaused: false)
175224
notifySuccess(Result<Success, UploadError>.success(Success(finalState: finalStatus)))
176225
}
226+
fileWorker?.removeDelegate(withToken: id)
177227
fileWorker = nil
178228
}
179229
case .failure(let error): do {
@@ -192,6 +242,7 @@ public final class MuxUpload {
192242
notifyFailure(Result.failure(parsedError))
193243
}
194244
}
245+
fileWorker?.removeDelegate(withToken: id)
195246
fileWorker = nil
196247
}
197248
case .uploading(let update): do {
@@ -209,6 +260,19 @@ public final class MuxUpload {
209260
}
210261
}
211262

263+
/// Two`MuxUpload`s with the same ``MuxUpload/videoFile`` and ``MuxUpload/uploadURL`` are considered equivalent
264+
public static func == (lhs: MuxUpload, rhs: MuxUpload) -> Bool {
265+
lhs.videoFile == rhs.videoFile
266+
&& lhs.uploadURL == rhs.uploadURL
267+
}
268+
269+
/// This object's hash is computed based on its ``MuxUpload/videoFile`` and its ``MuxUpload/uploadURL``
270+
public func hash(into hasher: inout Hasher) {
271+
hasher.combine(videoFile)
272+
hasher.combine(uploadURL)
273+
}
274+
275+
212276
private init (uploadInfo: UploadInfo, manage: Bool = true, uploadManager: UploadManager) {
213277
self.uploadInfo = uploadInfo
214278
self.manageBySDK = manage
@@ -222,7 +286,7 @@ public final class MuxUpload {
222286

223287
handleStateUpdate(uploader.currentState)
224288
uploader.addDelegate(
225-
withToken: id,
289+
withToken: self.id,
226290
InternalUploaderDelegate { [weak self] state in self?.handleStateUpdate(state) }
227291
)
228292
}

Sources/MuxUploadSDK/Public API/SDK.swift Sources/MuxUploadSDK/PublicAPI/MuxUploadSDK.swift

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,24 @@ import Foundation
99
import OSLog
1010

1111
///
12-
/// Exposes SDK metadata. Has some extensions that hide global data
12+
/// Metadata and logging for this SDK
1313
///
1414
public enum MuxUploadSDK {
1515
}
1616

1717
public extension MuxUploadSDK {
18+
19+
/// The `Logger` being used to log events from this SDK
1820
static var logger: Logger? = nil
1921

22+
/// Enables logging by adding a `Logger` with `subsystem: "Mux"` and `category: "Upload"`
2023
static func enableDefaultLogging() {
2124
logger = Logger(subsystem: "Mux", category: "MuxUpload")
2225
}
2326

27+
/// Uses the specified `Logger` to log events from this SDK
2428
static func useLogger(logger: Logger) {
2529
self.logger = logger
2630
}
31+
2732
}

0 commit comments

Comments
 (0)