> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://fastpix.com/docs/upload-videos/set-up-resumable-uploads-for-ios/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://fastpix.com/_mcp/server. # Set up resumable uploads for iOS The [FastPix iOS Upload SDK](https://github.com/FastPix/iOS-Uploads) helps you to upload videos from your iOS app to the FastPix platform. It’s built to handle large files, manage network interruptions, and give you full control over the upload process all with minimal setup. \ ## Why use the FastPix iOS upload SDK? The FastPix iOS SDK is built for reliability, flexibility, and performance when uploading large video files from your app. Key features include: * **Chunked uploads**: Automatically breaks large videos into smaller chunks for smoother transfers. You can customize the chunkSize to fit your app’s needs. * **Resumable uploads**: If the network drops or the app closes, uploads resume from where they left off no need to start over. * **Pause and resume support**: Give users control by allowing uploads to be paused mid-way and resumed later without data loss. \ \ ## Step 1: Install the SDK ### Prerequisites * An existing Xcode project. * Swift Package Manager (SPM) installed on your development machine. \ ### Installing using Swift Package Manager (SPM) 1. Open your Xcode project. 2. Navigate to the "File" menu and select "**Swift Packages**" > "**Add Package Dependency...**" 3. In the search bar, enter the URL for the FastPixUploadSDK repository: `https://github.com/FastPix/iOS-Uploads` 4. Locate the desired version of the SDK. By default, the latest version will be displayed. 5. Click "**Add Package**" to integrate the FastPixUploadSDK into your project. \ ### Importing the SDK package Once the package dependency is added, you can import the FastPixUploadSDK module: **`import`** ```text import import fp_swift_upload_sdk ``` \ \ ## Step 2: Create an upload URL To fully integrate the FastPixUploadSDK into your iOS application, follow these steps: \ ### Generating a signed direct upload URL * Implement server-side logic to interact with FastPix's API to create a signed URL. * You will need an access token from FastPix (**Token ID** and **Secret Key**), generated in your FastPix dashboard. * After making the request, FastPix returns a signed URL used for secure uploads. \ ```swift func createDirectUpload() async throws -> URL { let parameters: [String: Any] = [ "corsOrigin": "*", "newAssetSettings": [ "accessPolicy": "public", "generateSubtitles": true, "normalizeAudio": true, "maxResolution": "1080p", "mediaQuality": "standard" ] ] guard let jsonData = try? JSONSerialization.data(withJSONObject: parameters) else { throw NSError(domain: "com.example.app", code: 500, userInfo: [ NSLocalizedDescriptionKey: "Failed to serialize parameters" ]) } let request = try { var req = try URLRequest(url: fullURL(forEndpoint: "upload")) req.httpBody = jsonData req.httpMethod = "POST" req.addValue("application/json", forHTTPHeaderField: "Content-Type") req.addValue("application/json", forHTTPHeaderField: "Accept") let credentials = "\(FASTPIX_ACCESS_TOKEN_ID):\(FASTPIX_SECRET_KEY)" let basicAuthCredential = Data(credentials.utf8).base64EncodedString() req.addValue("Basic \(basicAuthCredential)", forHTTPHeaderField: "Authorization") return req }() let (data, response) = try await urlSession.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { throw CreateUploadError(message: "Invalid HTTP response") } if (200...299).contains(httpResponse.statusCode) { do { if let jsonDictionary = try JSONSerialization.jsonObject(with: data) as? [String: Any], let dataDic = jsonDictionary["data"] as? [String: Any], let urlString = dataDic["url"] as? String, let uploadUrl = URL(string: urlString) { return uploadUrl } else { print("Failed to parse upload URL from response.") return URL(string: "")! } } catch { print("Error decoding response: \(error.localizedDescription)") return URL(string: "")! } } else { let errorMessage = String(decoding: data, as: UTF8.self) throw CreateUploadError(message: "Upload POST failed: HTTP \(httpResponse.statusCode):\n\(errorMessage)") } } /// Generates a full URL for a given endpoint in the FastPix Video public API private func fullURL(forEndpoint endpoint: String) throws -> URL { let fullPath = "https://api.fastpix.com/v1/on-demand/\(endpoint)" guard let url = URL(string: fullPath) else { throw CreateUploadError(message: "Bad endpoint: \(endpoint)") } return url } ``` \ \ \ ## Step 3: Start your upload ```swift import fp_swift_upload_sdk // Initialize the uploader var uploader = Uploads() Task { do { // Get the upload URL from your backend let createUploadURL = try await self.myServerBackend.createDirectUpload() // Start the upload uploader.uploadFile( file: file!, endpoint: createUploadURL.absoluteString, chunkSizeKB: Int(chunkSize.text ?? "") ?? 0 ) } catch { print("Failed to create upload URL: \(error.localizedDescription)") } } ``` \ * **Uploader initialization**: `var uploader = Uploads()` prepares for a new session. * **Async upload trigger**: Runs inside `Task` for async handling. * **Signed URL retrieval**: Calls `createDirectUpload()` from your backend. * **File upload start**: Uses `uploadFile()` to initiate upload. Add this code in your upload functionality module within your iOS app project. It could be in a service layer or directly within a controller or view model where the upload operation is triggered, ensuring it is properly integrated with your app's workflow. \ \ ## Step 4: Track progress with progressHandler You can show real-time progress updates during the upload by using the `progressHandler` callback provided by the SDK. This allows you to update progress bars, display percentage completion, and notify users when the upload finishes. \ ```swift import fp_swift_upload_sdk var uploader = Uploads() uploader.progressHandler = { [weak self] progress in // Ensure UI updates are done on the main thread DispatchQueue.main.async { guard let self = self else { return } // Show and style progress UI self.progressBar.layer.cornerRadius = 10 self.progressBar.isHidden = false self.label_progress.isHidden = false // Update progress bar and label self.progressBar.progress = progress self.label_progress.text = String(format: "%.0f%%", progress * 100) // If upload is complete if progress == 1.0 { self.button_pause.isHidden = true self.button_abort.isHidden = true self.button_upload.isHidden = false // Show success alert let alertController = UIAlertController( title: "Success", message: "The video file uploaded successfully.", preferredStyle: .alert ) let okAction = UIAlertAction(title: "OK", style: .default, handler: nil) alertController.addAction(okAction) self.present(alertController, animated: true, completion: nil) } } } ``` \ \ ## Step 5 : Pause and resume uploads Pause and resumable uploads allow you to temporarily pause a file upload and then pick it up later from where it left off. This is especially useful for large video files that may be interrupted due to network issues or app crashes. \ * **Create an uploader object** ```swift var uploader = Uploads() ``` \ * **Call pause() to temporarily stop the upload** ```swift uploader.pause() ``` \ * **Call resume() to continue the upload from where it stopped** ```swift uploader.resume() ``` \ Below is an **example implementation** of the iOS Upload SDK. You can customize the UI and components based on your app’s design. \ ```swift import UIKit import PhotosUI import fp_swift_upload_sdk import Foundation import Network class SelectUploadVideoViewController: UIViewController, PHPickerViewControllerDelegate, UITextFieldDelegate, UploadProgressDelegate, UploadSDKErrorDelegate, UIDocumentPickerDelegate { @IBOutlet weak var thumbNailImage: UIImageView! @IBOutlet weak var button_upload: UIButton! @IBOutlet weak var progressBar: UIProgressView! @IBOutlet weak var label_progress: UILabel! @IBOutlet weak var button_selectVideo: UIButton! @IBOutlet weak var errorLabel: UILabel! @IBOutlet weak var buttonsStack: UIStackView! @IBOutlet weak var button_pause: UIButton! @IBOutlet weak var chunkSize: UITextField! @IBOutlet weak var uploadedChunk: UILabel! @IBOutlet var button_abort: UIButton! var isOfflineModeEnabled = false var uploadIsPaused = false var file : URL? var pauseBtnClicked = false var uploader = Uploads() var picje = VideoPickerConfiguration() var thumGenerater = ThumbnailImageGenerater() var authenticatedURL: URL? = nil var videoInputURL: URL? = nil let myServerBackend = UploadURLGenerater(urlSession: URLSession(configuration: URLSessionConfiguration.default)) private var prepareTask: Task? = nil public var isPaused = false private var assetRequestId: PHImageRequestID? = nil let session = URLSession.shared var requestData = [String: Any]() var chunks : Int = 0 var resData = [String]() var urlIndex = 0 var chunksList: [String] = [] var chunksArray: [Data] = [] var signedURL: URL? var objectName: String? var uploadId: String? let monitor = NWPathMonitor() var isInternetAvailable = false var chunkAttempt = 0 override func viewDidLoad() { super.viewDidLoad() chunkSize.delegate = self uploader.progressDelegate = self uploader.errorDelegate = self button_abort.isHidden = true pickerConfig() title = "Create New Upload" button_upload.isHidden = false button_pause.isHidden = true PHPhotoLibrary.authorizationStatus(for: .readWrite) button_upload.isEnabled = false button_upload.backgroundColor = .lightGray // Set the progress handler progressBar.isHidden = true label_progress.isHidden = true uploader.progressHandler = { [weak self] progress in // Update UI with the progress value DispatchQueue.main.async { self?.progressBar.layer.cornerRadius = 10 self?.progressBar.isHidden = false self?.label_progress.isHidden = false // Update progress bar or any other UI element self?.progressBar.progress = progress self?.label_progress.text = String(format: "%.0f%%", progress * 100) if progress == 1.0 { self?.button_pause.isHidden = true self?.button_abort.isHidden = true self?.button_upload.isHidden = false let alertController = UIAlertController(title: "success", message: "The video file uploaded successfully.", preferredStyle: .alert) let okAction = UIAlertAction(title: "OK", style: .default, handler: nil) alertController.addAction(okAction) self?.present(alertController, animated: true, completion: nil) } } } startMonitoring() NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) chunkSize.attributedPlaceholder = NSAttributedString( string: "Enter chunk size", attributes: [ .foregroundColor: UIColor.white // Change to your desired color ] ) } func didUpdateProgressText(_ text: String) { DispatchQueue.main.async { self.uploadedChunk.text = text } } func uploadSDKDidFail(with error: String) { DispatchQueue.main.async { self.displayErrorAlert(error: error) } } func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() // Dismisses the keyboard return true } func startMonitoring() { monitor.pathUpdateHandler = { path in DispatchQueue.main.async { [self] in self.isInternetAvailable = path.status == .satisfied if self.isInternetAvailable { if uploadIsPaused { if self.chunkAttempt < chunksArray.count { } } } else { } } } let queue = DispatchQueue(label: "NetworkMonitor") monitor.start(queue: queue) } @objc func appDidBecomeActive() { startMonitoring() } func pickerConfig() { let alert = UIAlertController(title: "Select Media Type", message: nil, preferredStyle: .actionSheet) // Option to pick Video alert.addAction(UIAlertAction(title: "Pick Video", style: .default, handler: { _ in let config = self.picje.pickerConfig() // video-only config let picker = PHPickerViewController(configuration: config) picker.delegate = self picker.modalPresentationStyle = .overCurrentContext self.present(picker, animated: true) })) // Option to pick Audio alert.addAction(UIAlertAction(title: "Pick Audio", style: .default, handler: { _ in let audioPicker = UIDocumentPickerViewController(forOpeningContentTypes: [UTType.audio]) audioPicker.allowsMultipleSelection = false audioPicker.delegate = self self.present(audioPicker, animated: true) })) alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) self.present(alert, animated: true) } @IBAction func button_pauseVideo(_ sender: UIButton) { sender.isEnabled = false if sender.isSelected { sender.setTitle("Pause", for: .normal) uploadIsPaused = false pauseBtnClicked = true uploader.resume() sender.backgroundColor = .systemGreen } else { sender.setTitle("Resume", for: .normal) uploadIsPaused = true pauseBtnClicked = false startMonitoring() uploader.pause() sender.backgroundColor = .systemRed } sender.isSelected.toggle() DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { sender.isEnabled = true } } @IBAction func button_uploadVideo(_ sender: UIButton) { button_upload.isHidden = true button_pause.isHidden = false button_abort.isHidden = false self.progressBar.isHidden = false self.label_progress.isHidden = false self.label_progress.text = String(format: "%.0f%%", 0.0000000 * 100) Task { let createUploadURL = try await self.myServerBackend.createDirectUpload() uploader.uploadFile(file: file!, endpoint: createUploadURL.absoluteString, chunkSizeKB: Int(chunkSize.text ?? "") ?? 0) } } @IBAction func button_abort(_ sender: UIButton) { button_pause.isHidden = true button_abort.isHidden = true button_upload.isHidden = false uploader.abort() sender.backgroundColor = .red } @IBAction func button_selectVideo(_ sender: UIButton) { pickerConfig() } func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { dismiss(animated: true, completion: nil) guard let result = results.first else { return } if let assetRequestId = self.assetRequestId { PHImageManager.default().cancelImageRequest(assetRequestId) } let tempDir = FileManager.default.temporaryDirectory let tempFile = URL(string: "upload-\(Date().timeIntervalSince1970).mp4", relativeTo: tempDir)! guard let assetIdentitfier = result.assetIdentifier else { print("No Asset ID for chosen asset") return } guard let assetIdentitfier = result.assetIdentifier else { print("No Asset ID for chosen asset") return } let options = PHFetchOptions() options.includeAssetSourceTypes = [.typeUserLibrary, .typeCloudShared] let phAssetResult = PHAsset.fetchAssets(withLocalIdentifiers: [assetIdentitfier], options: options) guard let phAsset = phAssetResult.firstObject else { return } let exportOptions = PHVideoRequestOptions() exportOptions.isNetworkAccessAllowed = true exportOptions.deliveryMode = .highQualityFormat assetRequestId = PHImageManager.default().requestExportSession(forVideo: phAsset, options: exportOptions, exportPreset: AVAssetExportPresetHighestQuality, resultHandler: {(exportSession, info) -> Void in DispatchQueue.main.async { [weak self] in guard let exportSession = exportSession else { return } self?.thumGenerater.extractThumbnailAsync(exportSession.asset) { thumbnailImage in guard let thumbImage = thumbnailImage else { return } DispatchQueue.main.async { self?.thumbNailImage.image = UIImage(cgImage: thumbImage) self?.thumbNailImage.isHidden = false self?.button_selectVideo.isUserInteractionEnabled = false } } } }) result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier , completionHandler: { [ weak self] url, error in guard let fileURL = url else { return } let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first guard let targetURL = documentsDirectory?.appendingPathComponent(fileURL.lastPathComponent) else { return } self?.file = (documentsDirectory?.appendingPathComponent(fileURL.lastPathComponent))! do { if FileManager.default.fileExists(atPath: targetURL.path) { try FileManager.default.removeItem(at: targetURL) } else { // print("file path not exists") } try FileManager.default.copyItem(at: fileURL, to: targetURL) self?.videoInputURL = targetURL DispatchQueue.main.async { self?.button_upload.isEnabled = true self?.button_upload.backgroundColor = .green } } catch { self?.displayErrorAlert(error: error.localizedDescription) } }) } func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let fileURL = urls.first else { return } // Start accessing security-scoped resource let didStartAccessing = fileURL.startAccessingSecurityScopedResource() defer { if didStartAccessing { fileURL.stopAccessingSecurityScopedResource() } } // Confirm access granted guard didStartAccessing else { print("Failed to access security-scoped resource") self.displayErrorAlert(error: "Unable to access selected file due to security permissions.") return } // Now safe to use fileURL print("Picked audio file: \(fileURL.lastPathComponent)") // Copy to local directory let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first guard let targetURL = documentsDirectory?.appendingPathComponent(fileURL.lastPathComponent) else { return } self.file = documentsDirectory?.appendingPathComponent(fileURL.lastPathComponent) do { if FileManager.default.fileExists(atPath: targetURL.path) { try FileManager.default.removeItem(at: targetURL) } try FileManager.default.copyItem(at: fileURL, to: targetURL) self.videoInputURL = targetURL // Or self.audioInputURL DispatchQueue.main.async { self.button_upload.isEnabled = true self.button_upload.backgroundColor = .green } } catch { self.displayErrorAlert(error: error.localizedDescription) } } func pickerDidCancel(_ picker: PHPickerViewController) { dismiss(animated: true, completion: nil) } // Display an error message func displayErrorAlert(error: String) { let alertController = UIAlertController(title: "Error", message: error, preferredStyle: .alert) let okAction = UIAlertAction(title: "OK", style: .default) { [weak self] _ in DispatchQueue.main.async { self?.thumbNailImage.isHidden = false self?.button_selectVideo.isUserInteractionEnabled = true if error == "The current upload was aborted. Please try uploading a new video" { self?.navigationController?.popViewController(animated: true) } } } alertController.addAction(okAction) present(alertController, animated: true, completion: nil) } deinit { monitor.cancel() NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil) } } class UploadURLGenerater { public func presentErrorAlert(on viewController: UIViewController, withTitle title: String, message: String) { let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) let okAction = UIAlertAction(title: "OK", style: .default, handler: nil) alertController.addAction(okAction) viewController.present(alertController, animated: true, completion: nil) } func createDirectUpload() async throws -> URL { let parameters: [String: Any] = [ "corsOrigin": "*", "pushMediaSettings": [ "accessPolicy": "public", "generateSubtitles": true, "normalizeAudio": true, "maxResolution": "1080p" ] ] guard let jsonData = try? JSONSerialization.data(withJSONObject: parameters) else { throw NSError(domain: "com.example.app", code: 500, userInfo: [NSLocalizedDescriptionKey: "Failed to serialize parameters"]) } let request = try { var req = try URLRequest(url:fullURL(forEndpoint: "upload")) req.httpBody = jsonData req.httpMethod = "POST" req.addValue("application/json", forHTTPHeaderField: "Content-Type") req.addValue("application/json", forHTTPHeaderField: "accept") let basicAuthCredential = "\(FASTPIX_ACCESS_TOKEN_ID):\(FASTPIX_SECRET_KEY)".data(using: .utf8)!.base64EncodedString() req.addValue("Basic \(basicAuthCredential)", forHTTPHeaderField: "Authorization") return req }() let (data, response) = try await urlSession.data(for: request) let httpResponse = response as! HTTPURLResponse print("Response",httpResponse.statusCode) var uploadUrl: URL? if (200...299).contains(httpResponse.statusCode) { do { // Assuming responseData is the Data object you want to convert to a dictionary if let jsonDictionary = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { var dataDic = jsonDictionary["data"] as? NSDictionary uploadUrl = URL(string: dataDic?.value(forKey: "url") as? String ?? "") } else { print("Failed to convert data to dictionary") } } catch { print("Error converting data to dictionary: \(error.localizedDescription)") } guard let uploadUrl = uploadUrl else { return URL(string: "")!} return uploadUrl } else { throw CreateUploadError(message: "Upload POST failed: HTTP \(httpResponse.statusCode):\n\(String(decoding: data, as: UTF8.self))") } } /// Generates a full URL for a given endpoint in the FastPix Video public API private func fullURL(forEndpoint: String) throws -> URL { guard let url = URL(string: "https://venus-v1.fastpix.dev/on-demand/\(forEndpoint)") else { throw CreateUploadError(message: "bad endpoint") } return url } // https://dev-api.fastpix.com let urlSession: URLSession let jsonEncoder: JSONEncoder let jsonDecoder: JSONDecoder //Gowtham Venus let FASTPIX_ACCESS_TOKEN_ID = "ACCESS TOKEN" let FASTPIX_SECRET_KEY = "SECRET KEY" init(urlSession: URLSession) { self.urlSession = urlSession self.jsonEncoder = JSONEncoder() self.jsonEncoder.keyEncodingStrategy = JSONEncoder.KeyEncodingStrategy.convertToSnakeCase self.jsonDecoder = JSONDecoder() self.jsonDecoder.keyDecodingStrategy = JSONDecoder.KeyDecodingStrategy.convertFromSnakeCase } convenience init() { self.init(urlSession: URLSession(configuration: URLSessionConfiguration.default)) } } struct CreateUploadError : Error { let message: String } fileprivate struct CreateUploadPost: Codable { var newAssetSettings: NewAssetSettings = NewAssetSettings() var corsOrigin: String = "*" } fileprivate struct NewAssetSettings: Codable { var playbackPolicy: [String] = ["public"] var passthrough: String = "Extra video data. This can be any data and it's for your use" var mp4Support: String = "standard" var normalizeAudio: Bool = true var test: Bool = false } fileprivate struct CreateUploadResponse: Decodable { var url: String var id: String var timeout: Int64 var status: String } fileprivate struct CreateUploadResponseContainer: Decodable { var data: CreateUploadResponse } ``` \ ## Handling network throttling Network throttling is the intentional slowing down of internet speeds by an Internet Service Provider (ISP) to manage network congestion. FastPixUploadSDK is designed to handle network throttling efficiently, ensuring smooth video uploads even when network conditions are less than ideal. It automatically detects and adapts to bandwidth throttling, optimizing video upload speeds within the allowed data limits to ensure that the upload process continues without disruption. \ ## Error handling Proper error handling ensures the SDK gracefully handles failures and provides meaningful feedback. \ ### Common error scenarios: * Network failures * Timeout errors * Invalid data or server responses * Permission issues \ ### Implementation example: ```swift import fp_swift_upload_sdk class ViewController: UIViewController, UploadSDKErrorDelegate { var uploader = Uploads() override func viewDidLoad() { super.viewDidLoad() uploader.errorDelegate = self } } ``` \ ## Changelog All notable changes to this project will be documented in this file. ### \[1.0.3] #### Changed * Code standardization updates applied across the SDK to align with best practices and strengthen overall stability. ### \[1.0.2] #### Compatibility * The SDK is now fully compatible with both old and new versions of Xcode (before and after Xcode 26) and Swift (Swift 5.x and Swift 6.x including Swift 6.3.2). * No functionality has been changed. All fixes are only compatability ones, updates to ensure the SDK builds and runs correctly across all supported Xcode and Swift versions. #### Fixed * Fixed invalid `try (tuple)` syntax in `uploadFile` that caused a Swift 6.3.2 compiler crash (SILGen segfault) under Xcode 26. * Fixed `customizedChunkSize` incorrectly storing `0` when `chunkSizeKB: 0` was passed. Both `nil` and `0` now correctly fall back to the SDK default (`16384` KB). * Fixed chunk size validation thresholds that were written in MB (`< 5`, `> 500`) but compared against a value stored in KB, causing valid chunk sizes and the SDK default to always fail validation. * Fixed `FileHandle` on iOS 13.0–13.3 silently returning empty `Data` due to a missing availability check. Legacy `seek(toFileOffset:)` and `readData(ofLength:)` are now used as a fallback on iOS \< 13.4. * Fixed missing `fileHandle.closeFile()` fallback in `deinit` for iOS \< 13.0. * Fixed force-unwrap crash on `VideoChunkProcessor(fileURL:)` returning `nil` when the file could not be opened. * Fixed force-unwrap crash on `currentChunk` in `requestChunk`. * Fixed `NotificationCenter.addObserver` being called off the main thread when `uploadFile` is invoked from a background thread. #### Changed * Replaced duplicated chunk-success logic in `submitHttpRequest` with a shared `handleChunkSuccess()` method. * All closures (`emit`, upload task completion, `asyncAfter`) now capture `self` weakly via `[weak self]` to prevent retain cycles. * `UploadEvent` associated error values updated from bare `Error` to `any Error` for Swift 5.7+ and Swift 6 compatibility. #### Added * `Sendable` conformance added to `UploadEvent`, `UploadsDelegate`, `UploadProgressDelegate`, and `UploadSDKErrorDelegate` for Swift 6 strict concurrency compliance. #### Removed * Removed unstable `() async throws -> String` existential type from `validateUserInput` endpoint check, which is unsupported across Swift versions. * Removed unused variable `var response = 429` and duplicate `if let httpResponse` rebinding inside `submitHttpRequest`. ### \[1.0.1] #### Changed * **Domain migration: `fastpix.io` → `fastpix.com`** * Direct Upload API endpoint updated from `https://api.fastpix.io/v1/on-demand/upload` to `https://api.fastpix.com/v1/on-demand/upload`. * README references for dashboard, documentation, and API links updated to use `.com` domains. > **Note:** Existing `.io` domains will continue functioning temporarily, but migration to `.com` endpoints is strongly recommended to avoid future disruptions. ### \[1.0.0] #### Added * **Chunking**: Files are automatically split into chunks (configurable, default size is 16MB/chunk). * **Pause and Resume**: Allows temporarily pausing the upload and resuming after a while. * **Upload Lifecycle Callbacks**: Track the entire upload process using callback functions to monitor upload lifecycle. * **Retry**: Individual chunks are retried up to 5 times with exponential backoff to recover from temporary network failures. * **Error Handling**: Comprehensive error management to notify users of issues during uploads. * **Customizability**: Options to customize chunk size and retry attempts. * **Swift Package Manager Support**: SDK is installable via SPM using the repo URL. * Implemented support for Google Cloud Storage resumable uploads and chunked client uploads. * Added retry mechanism with exponential backoff for GCS upload failures based on retryable status codes. * Enabled support for user-provided signed URLs for resumable uploads with externally generated session URIs. * Updated the API endpoint from `https://v1.fastpix.io/on-demand/uploads` to `https://api.fastpix.io/v1/on-demand/upload`. \ \ > The single API stack for video: upload, encode, stream, secure, and analyze video at any scale. Explore guides, API references, and SDKs.