All Products
Search
Document Center

ApsaraVideo VOD:Upload files using the iOS SDK

Last Updated:Jul 13, 2026

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:

  1. Your app calls the SDK to start an upload. The SDK triggers the getAuth callback.

  2. 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.

  3. Your app passes the OpenAPI response to the SDK. The SDK extracts UploadAuth and UploadAddress from the response and uploads the file directly to OSS.

  4. If credentials expire during a large upload, the SDK triggers the getAuth callback again with a refresh kind to obtain new credentials.

Prerequisites

ApsaraVideo VOD Quick Start

ItemRequirement
Minimum iOS versioniOS 12.0
Development languageObjective-C (Swift projects use a Bridging Header for bridging)
Alibaba Cloud accountApsaraVideo VOD is activated
Authorization serviceA 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'
end

Run the following commands:

pod repo update
pod install

The 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.tbd

The 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];
Note

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:

KindTriggerOpenAPI to call
VODAuthKindCreateVideoFirst upload of a video fileCreateUploadVideo
VODAuthKindRefreshVideoResumable upload / credential refreshRefreshUploadVideo
VODAuthKindCreateImageImage uploadCreateUploadImage

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):

KindRequired fields
VODAuthKindCreateVideo / VODAuthKindRefreshVideoUploadAuth, UploadAddress, VideoId
VODAuthKindCreateImageUploadAuth, 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: 2

timeout 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.

Important

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:

ScenarioSDK behavior
[task cancel] is called during uploadThe resumable upload record is retained. The next uploadFile: call with the same file automatically resumes the upload.
App crashes or is terminated in the backgroundThe resumable upload record is retained. The next cold start uploadFile: call with the same file automatically resumes the upload.
Any change in the file fingerprintThe file is treated as a new file and is uploaded from scratch.
OSS uploadId expiresThe 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-shanghai

For 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: YES

Error handling

VODUploadError inherits from NSError. Error codes use the UPLOAD.{LAYER}.{TYPE} three-segment format:

LAYERError codeDescription
AUTHUPLOAD.AUTH.GET_AUTH_FAILEDThe getAuth callback failed.
AUTHUPLOAD.AUTH.DECODE_FAILEDFailed to decode UploadAuth.
AUTHUPLOAD.AUTH.EXPIREDThe credential has expired.
OSSUPLOAD.OSS.ACCESS_DENIEDOSS access denied (permission/signature issue).
OSSUPLOAD.OSS.NO_SUCH_BUCKETThe bucket does not exist.
OSSUPLOAD.OSS.NO_SUCH_UPLOADThe uploadId has expired. The SDK automatically falls back.
OSSUPLOAD.OSS.UPLOAD_FAILEDOSS upload failed.
OSSUPLOAD.OSS.MERGE_FAILEDOSS multipart merge failed.
NETWORKUPLOAD.NETWORK.TIMEOUTNetwork timeout.
NETWORKUPLOAD.NETWORK.UNREACHABLENetwork unreachable.
FILEUPLOAD.FILE.NOT_FOUNDThe file does not exist.
FILEUPLOAD.FILE.EMPTYThe file is empty.
CANCELUPLOAD.CANCEL.USER_CANCELLEDThe user cancelled the upload.
INTERNALUPLOAD.INTERNAL.DISPOSEDThe instance has been disposed.
INTERNALUPLOAD.INTERNAL.INVALID_CONFIGThe configuration is invalid.

Public properties:

  • NSString *errorCode — Error code

  • NSString *errorMessage — Error message

  • NSError *cause — Underlying OSS exception

  • NSString *uploadTaskId — Upload task ID

  • NSString *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.checkpoint is set to YES.

  • Check whether the uploadId has expired. When the NoSuchUpload response 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.