The ApsaraVideo VOD iOS upload SDK enables your app to upload video and image files directly to VOD storage. Your app obtains upload credentials from a backend authorization service, and the SDK uses those credentials to upload files to OSS without routing media through your server.
How it works
The upload process involves three parties: your iOS app, your backend service, and ApsaraVideo VOD (which stores files in OSS). The workflow is as follows:
Your app calls the SDK to start an upload. The SDK triggers the
getAuthcallback.In the callback, your app requests upload credentials from your backend service, which calls a VOD OpenAPI operation (such as
CreateUploadVideo) and returns the response.Your app passes the OpenAPI response to the SDK. The SDK extracts
UploadAuthandUploadAddressfrom the response and uploads the file directly to OSS.If credentials expire during a large upload, the SDK triggers the
getAuthcallback again with a refresh kind to obtain new credentials.
Prerequisites
| Item | Requirement |
| Minimum iOS version | iOS 12.0 |
| Development language | Objective-C (Swift projects use a Bridging Header for bridging) |
| Alibaba Cloud account | ApsaraVideo VOD is activated |
| Authorization service | A backend service that can issue upload credentials (UploadAuth) is prepared |
Integrate the SDK
Integration method
Add the following content to your Podfile:
platform :ios, '12.0'
target 'YourApp' do
pod 'VODUpload', '~> 2.0'
endRun the following commands:
pod repo update
pod installThe SDK automatically pulls the underlying AliyunOSSiOS dependency.
Project configuration
The podspec declares the following system libraries, which are automatically linked when you use CocoaPods for integration:
SystemConfiguration.framework
MobileCoreServices.framework
CoreMedia.framework
AVFoundation.framework
CoreTelephony.framework
libresolv.tbdThe SDK includes a PrivacyInfo.xcprivacy privacy manifest that is automatically injected to VODUpload.bundle/PrivacyInfo.xcprivacy during pod integration. No additional configuration is required.
Import the required header files:
#import <VODUpload/VODUploadV2Client.h>
#import <VODUpload/VODUploadConfig.h>
#import <VODUpload/VODAuthContext.h>
#import <VODUpload/VODGetAuthCallback.h>
#import <VODUpload/VODUploadOptions.h>
#import <VODUpload/VODUploadResult.h>
#import <VODUpload/VODUploadError.h>
#import <VODUpload/VODVideoMeta.h>
#import <VODUpload/VODImageMeta.h>
#import <VODUpload/VODUploadTask.h>Upload workflow
Initialize an upload instance
VODUploadConfig uses properties for direct configuration. Call the +uploaderWithConfig: factory method to create an instance:
VODUploadConfig *config = [[VODUploadConfig alloc] init];
config.getAuth = getAuth; // Required: authorization callback. For more information, see "Handle authorization callbacks".
VODUploadV2Client *uploader = [VODUploadV2Client uploaderWithConfig:config];The uploader instance must be retained as a property. If it is a local variable, ARC releases it, which causes callback loss. You can reuse the same instance to upload multiple files concurrently. Call [uploader dispose] to release resources when the lifecycle ends.
Upload files
Call uploadFile:options:callback: to start an upload. The method returns a VODUploadTask handle that you can use for cancellation:
VODVideoMeta *meta = [[VODVideoMeta alloc] init];
meta.title = @"My Video";
meta.tags = @"demo";
meta.cateId = @(1000);
VODUploadOptions *options = [[VODUploadOptions alloc] init];
options.videoMeta = meta;
options.onProgress = ^(float percent, int64_t uploaded, int64_t total) {
NSLog(@"%.1f%%", percent * 100);
};
VODUploadTask *task = [uploader uploadFile:filePath
options:options
callback:^(VODUploadResult *r, VODUploadError *e) {
if (e) {
NSLog(@"%@: %@", e.errorCode, e.errorMessage);
return;
}
NSLog(@"videoId=%@ uploadTaskId=%@ etag=%@ requestId=%@ durationMs=%lld",
r.videoId, r.uploadTaskId, r.etag, r.requestId, r.durationMs);
}];
// Cancel during upload. Resumable upload is enabled by default. The next uploadFile call with the same file automatically resumes the upload.
[task cancel];
// task.uploadTaskId can be used to correlate logs and integrate with tracking.Image upload: The SDK automatically uses the image upload process based on the file extension (jpg / jpeg / png / gif / bmp / webp / heic) or if options.imageMeta != nil is explicitly set. In the success callback, imageId and imageUrl have values:
VODImageMeta *imgMeta = [[VODImageMeta alloc] init];
imgMeta.imageType = @"cover";
imgMeta.title = @"Cover";
VODUploadOptions *opts = [[VODUploadOptions alloc] init];
opts.imageMeta = imgMeta;
[uploader uploadFile:imagePath options:opts callback:cb];The description field of VODVideoMeta is desc (not description, to avoid conflicts with the Objective-C reserved method).
Handle authorization callbacks
In v2, the VODGetAuthCallback block requests upload credentials from your application. The SDK calls getAuth at specific points and uses VODAuthContext.kind to indicate the required credential type:
| Kind | Trigger | OpenAPI to call |
| VODAuthKindCreateVideo | First upload of a video file | CreateUploadVideo |
| VODAuthKindRefreshVideo | Resumable upload / credential refresh | RefreshUploadVideo |
| VODAuthKindCreateImage | Image upload | CreateUploadImage |
Your application passes the raw JSON response from the backend OpenAPI call directly to completion. The SDK reads the following keys from the OpenAPI response by field name (case-sensitive):
| Kind | Required fields |
| VODAuthKindCreateVideo / VODAuthKindRefreshVideo | UploadAuth, UploadAddress, VideoId |
| VODAuthKindCreateImage | UploadAuth, UploadAddress, ImageId, ImageURL (passed through to result.imageUrl) |
Field names are case-sensitive. Pass through the OpenAPI response directly. Manually renaming fields to videoId, imageUrl, or image_url causes the SDK to parse them as nil.
VODGetAuthCallback getAuth = ^(VODAuthContext *ctx,
void (^completion)(NSDictionary *result, NSError *error)) {
switch (ctx.kind) {
case VODAuthKindCreateVideo:
[YourBackend createUploadVideo:ctx.fileName
fileSize:ctx.fileSize
completion:^(NSDictionary *json, NSError *err) {
completion(json, err);
}];
break;
case VODAuthKindRefreshVideo:
[YourBackend refreshUploadVideo:ctx.videoId
completion:^(NSDictionary *json, NSError *err) {
completion(json, err);
}];
break;
case VODAuthKindCreateImage:
[YourBackend createUploadImage:ctx.fileName
completion:^(NSDictionary *json, NSError *err) {
completion(json, err);
}];
break;
}
};Advanced settings
Upload acceleration
Set VODVideoMeta.userData to the following JSON. The VOD server automatically returns the OSS global transfer acceleration endpoint (oss-accelerate.aliyuncs.com) when issuing the UploadAddress. The SDK then directly uploads to the acceleration endpoint:
VODVideoMeta *meta = [[VODVideoMeta alloc] init];
meta.title = @"My Video";
meta.userData = @"{\"Type\":\"oss\",\"Domain\":\"oss-accelerate.aliyuncs.com\"}";Upload acceleration relies on OSS global transfer acceleration. You must enable transfer acceleration for the bucket in the OSS console. For more information, see Access OSS using transfer acceleration.
Upload and transcode
Specify a transcoding template group or workflow by using VODVideoMeta:
VODVideoMeta *meta = [[VODVideoMeta alloc] init];
meta.title = @"My Video";
meta.desc = @"Video description"; // Note: use desc, not description
meta.coverUrl = @"https://example.com/cover.jpg";
meta.cateId = @(1000);
meta.tags = @"tag1,tag2";
meta.storageLocation = @"<Optional: custom storage region>";
meta.templateGroupId = @"<Your template group ID>"; // Transcoding template group
meta.workflowId = @"<Your workflow ID>"; // Workflow (optional)
meta.appId = @"<Optional: application ID>";Supported fields of VODVideoMeta: title, desc, coverUrl, cateId, tags, userData, storageLocation, templateGroupId, workflowId, and appId. All fields are optional and are passed through to CreateUploadVideo.
After the upload is complete, the VOD server transcodes the source video based on the template group. If you do not want to trigger transcoding, pass a "no transcoding" template group when calling CreateUploadVideo from your backend.
Timeout and retry
VODUploadConfig *config = [[VODUploadConfig alloc] init];
config.getAuth = getAuth;
config.timeout = 60; // Unit: seconds. Default value: 60
config.maxRetryCount = 2; // Default value: 2timeout controls the connection and read/write timeout (NSTimeInterval, in seconds) for a single OSS request. maxRetryCount controls the number of retries for OSS requests upon network errors.
The timeout unit is milliseconds on Android and seconds on iOS. Pay attention to this difference when integrating across platforms.
Signature version
config.signature = @"v4"; // Default value: @"v4". Valid values: @"v1"OSS V4 signature is the recommended version. Starting from September 1, 2025, V4 signature is mandatory for newly created buckets. Existing customers are also recommended to migrate as soon as possible. Set @"v1" explicitly only when the bucket used by your application still supports only V1 signatures.
Resumable upload
Resumable upload is enabled by default. The SDK uses the (lastModified, fileName, fileSize) triplet as the resumable upload key and automatically persists it to the local Caches directory:
| Scenario | SDK behavior |
[task cancel] is called during upload | The resumable upload record is retained. The next uploadFile: call with the same file automatically resumes the upload. |
| App crashes or is terminated in the background | The resumable upload record is retained. The next cold start uploadFile: call with the same file automatically resumes the upload. |
| Any change in the file fingerprint | The file is treated as a new file and is uploaded from scratch. |
| OSS uploadId expires | The SDK automatically clears the old record and starts a fresh upload through VODAuthKindCreateVideo. |
To disable resumable upload:
config.checkpoint = NO;Multipart upload
Use partSize to control the part size and parallel to control the number of concurrent parts:
// Global configuration
config.partSize = 1024 * 1024; // Default value: 1 MB
config.parallel = 4; // Default value: 4
// Per-task override
VODUploadOptions *options = [[VODUploadOptions alloc] init];
options.videoMeta = meta;
options.partSize = 2 * 1024 * 1024;
options.parallel = 6;Set the VOD service region
config.region = @"cn-shanghai"; // Default value: cn-shanghaiFor the list of supported regions, see ApsaraVideo VOD region IDs.
Data reporting
The SDK enables upload link tracking by default for product quality monitoring. Only runtime metrics of the upload process are collected, and file content is not included. To disable data reporting:
config.reportEnabled = NO; // Default value: YESError handling
VODUploadError inherits from NSError. Error codes use the UPLOAD.{LAYER}.{TYPE} three-segment format:
| LAYER | Error code | Description |
| AUTH | UPLOAD.AUTH.GET_AUTH_FAILED | The getAuth callback failed. |
| AUTH | UPLOAD.AUTH.DECODE_FAILED | Failed to decode UploadAuth. |
| AUTH | UPLOAD.AUTH.EXPIRED | The credential has expired. |
| OSS | UPLOAD.OSS.ACCESS_DENIED | OSS access denied (permission/signature issue). |
| OSS | UPLOAD.OSS.NO_SUCH_BUCKET | The bucket does not exist. |
| OSS | UPLOAD.OSS.NO_SUCH_UPLOAD | The uploadId has expired. The SDK automatically falls back. |
| OSS | UPLOAD.OSS.UPLOAD_FAILED | OSS upload failed. |
| OSS | UPLOAD.OSS.MERGE_FAILED | OSS multipart merge failed. |
| NETWORK | UPLOAD.NETWORK.TIMEOUT | Network timeout. |
| NETWORK | UPLOAD.NETWORK.UNREACHABLE | Network unreachable. |
| FILE | UPLOAD.FILE.NOT_FOUND | The file does not exist. |
| FILE | UPLOAD.FILE.EMPTY | The file is empty. |
| CANCEL | UPLOAD.CANCEL.USER_CANCELLED | The user cancelled the upload. |
| INTERNAL | UPLOAD.INTERNAL.DISPOSED | The instance has been disposed. |
| INTERNAL | UPLOAD.INTERNAL.INVALID_CONFIG | The configuration is invalid. |
Public properties:
NSString *errorCode— Error codeNSString *errorMessage— Error messageNSError *cause— Underlying OSS exceptionNSString *uploadTaskId— Upload task IDNSString *suggestion— Built-in fix suggestion from the SDK
if ([error.errorCode hasPrefix:@"UPLOAD.AUTH."]) {
// Prompt the user to check the backend authorization
} else if ([error.errorCode isEqualToString:VODErrorCodeOssUploadFailed]) {
// Retry or prompt the user to check the network
}
NSLog(@"%@ : %@ (cause=%@, suggestion=%@)",
error.errorCode, error.errorMessage, error.cause, error.suggestion);FAQ
Can I initiate multiple uploadFile calls simultaneously?
Yes. Each task is independent. Control the number of concurrent uploads based on network conditions to avoid excessive upstream bandwidth pressure caused by the combination with parallel.
Resumable upload does not work after cancellation and re-upload
Troubleshoot the issue by performing the following steps:
Check whether the file path is the same.
Check whether the lastModified or size of the file has changed.
Check whether
config.checkpointis set toYES.Check whether the uploadId has expired. When the
NoSuchUploadresponse is received, the SDK automatically falls back to a fresh upload. This is expected behavior.
App Store Connect review rejected with "missing privacy manifest"
The SDK includes a PrivacyInfo.xcprivacy file that is automatically injected during pod integration. Make sure that your app also declares the Required Reason API that your app uses.
Callbacks are not triggered after creating the uploader
Make sure that uploader is not a local variable. The VODUploadV2Client instance must be retained as a property of self. Otherwise, ARC releases it prematurely.