All Products
Search
Document Center

ApsaraVideo VOD:Upload files using the Android SDK

Last Updated:Aug 24, 2026

Use the ApsaraVideo VOD Android upload SDK to upload media files from a local device to ApsaraVideo VOD storage. This topic describes how to integrate the SDK, configure uploads, and handle errors.

Prerequisites

ItemRequirement
Minimum Android versionAPI 14 (Android 4.0)
Compile versioncompileSdkVersion 30
Alibaba Cloud accountObtainedActivate ApsaraVideo VOD
Authorization serviceA backend service that can issue upload credentials (UploadAuth)

Usage limits

  • The Android SDK supports uploading audio, video, and images. Uploading auxiliary media assets is not supported.

Integrate the SDK

Add the SDK dependency

Add the Alibaba Cloud Maven repository to the project-level build.gradle:

allprojects {
    repositories {
        maven { url "https://maven.aliyun.com/nexus/content/repositories/releases" }
    }
}

Add the SDK dependency to the module-level build.gradle:

dependencies {
    implementation 'com.aliyun.video.android:upload:2.0.1'
}

The underlying OSS Android SDK is transitively included through the api dependency of VODUpload. You do not need to declare it again.

Project configuration

Declare the required permissions in AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Android 13+ granular media permissions, declare as needed -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

If you enable code obfuscation, add the following rules to proguard-rules.pro:

-keep class com.alibaba.sdk.android.vod.upload.v2.** { *; }
-keep interface com.alibaba.sdk.android.vod.upload.v2.** { *; }

The underlying OSS Android SDK includes built-in ProGuard rules. You do not need to declare them again.

Basic usage

Callback handling

The upload SDK (v2) uses a unified VODGetAuthCallback asynchronous callback to request upload credentials from the business layer. The SDK invokes getAuth in the following scenarios and uses VODAuthContext.getKind() to indicate the type of credential required:

KindTriggerOpenAPI operation to call
CREATE_VIDEOFirst upload of a video fileCreateUploadVideo
REFRESH_VIDEOResumable upload / credential refreshRefreshUploadVideo
CREATE_IMAGEImage uploadCreateUploadImage

The business layer only needs to pass the raw JSON response from the backend OpenAPI back to the SDK as-is. The SDK reads the following keys by their exact OpenAPI field names (case-sensitive):

KindRequired fields
CREATE_VIDEO / REFRESH_VIDEOUploadAuth, UploadAddress, VideoId
CREATE_IMAGEUploadAuth, UploadAddress, ImageId, ImageURL (passed through to result.getImageUrl())

Do not rename the fields. Renaming fields to videoId, imageUrl, or image_url causes the SDK to parse them as null.

The following example shows a getAuth implementation that handles each kind:

VODGetAuthCallback getAuth = (ctx, completion) -> {
    switch (ctx.getKind()) {
        case CREATE_VIDEO:
            yourBackend.createUploadVideo(ctx.getFileName(), ctx.getFileSize(),
                json -> completion.onSuccess(json),
                err -> completion.onFailure("BIZ.CREATE_VIDEO", err.getMessage()));
            break;
        case REFRESH_VIDEO:
            yourBackend.refreshUploadVideo(ctx.getVideoId(),
                json -> completion.onSuccess(json),
                err -> completion.onFailure("BIZ.REFRESH_VIDEO", err.getMessage()));
            break;
        case CREATE_IMAGE:
            yourBackend.createUploadImage(ctx.getFileName(),
                json -> completion.onSuccess(json),
                err -> completion.onFailure("BIZ.CREATE_IMAGE", err.getMessage()));
            break;
    }
};

Initialize the upload instance

Use VODUploadConfig.Builder to build the configuration and call VODUploadClient.create(...) to create an upload instance:

import com.alibaba.sdk.android.vod.upload.v2.*;

VODUploadConfig config = new VODUploadConfig.Builder()
        .setGetAuth(getAuth)               // Required: authentication callback. For more information, see "Callback handling".
        .build();

VODUploadClient uploader = VODUploadClient.create(context, config);

The uploader instance is reusable. You can use the same instance to upload multiple files concurrently. Call uploader.dispose() to release resources when the lifecycle ends.

Upload control

Call upload(...) to start an upload. The method returns a VODUploadTask handle that you can use to cancel the upload:

VODVideoMeta meta = new VODVideoMeta.Builder()
        .setTitle("My Video")
        .setTags("demo")
        .setCateId(1000)
        .build();

VODUploadOptions options = new VODUploadOptions.Builder()
        .setVideoMeta(meta)
        .setOnProgress((percent, uploaded, total) ->
                Log.d("Upload", String.format("%.1f%%", percent * 100)))
        .build();

VODUploadTask task = uploader.upload(filePath, options, new VODUploadResultCallback() {
    @Override public void onSuccess(VODUploadResult r) {
        Log.i("Upload",
                "videoId=" + r.getVideoId()
                + " uploadTaskId=" + r.getUploadTaskId()
                + " etag=" + r.getEtag()
                + " requestId=" + r.getRequestId()
                + " durationMs=" + r.getDurationMs());
    }
    @Override public void onFailure(VODUploadError e) {
        Log.e("Upload", e.getErrorCode() + " : " + e.getErrorMessage(), e.getCause());
    }
});

// Cancel the upload midway. Resumable upload is enabled by default. The next upload(...) call for the same file automatically resumes the upload.
task.cancel();
// task.getUploadTaskId() can be used to correlate logs or integrate with analytics.

upload(...) provides two overloads that support file paths and content:// URIs (compatible with Android 10+ Scoped Storage):

uploader.upload(String filePath, VODUploadOptions options, VODUploadResultCallback callback);
uploader.upload(Uri fileUri, VODUploadOptions options, VODUploadResultCallback callback);

Image upload: The SDK automatically uses the image upload path based on the file name extension (jpg / jpeg / png / gif / bmp / webp / heic) or when options.imageMeta != null. In the success callback, result.getImageId() and result.getImageUrl() contain valid values:

VODImageMeta imgMeta = new VODImageMeta.Builder()
        .setImageType("cover")
        .setTitle("Cover")
        .build();
uploader.upload("/sdcard/cover.jpg",
        new VODUploadOptions.Builder().setImageMeta(imgMeta).build(),
        callback);

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 uploads directly to the acceleration endpoint:

VODVideoMeta meta = new VODVideoMeta.Builder()
        .setTitle("My Video")
        .setUserData("{\"Type\":\"oss\",\"Domain\":\"oss-accelerate.aliyuncs.com\"}")
        .build();

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 through VODVideoMeta:

VODVideoMeta meta = new VODVideoMeta.Builder()
        .setTitle("My Video")
        .setDescription("Video description")
        .setCoverUrl("https://example.com/cover.jpg")
        .setCateId(1000)
        .setTags("tag1,tag2")
        .setStorageLocation("<Optional: custom storage region>")
        .setTemplateGroupId("<Your template group ID>")    // transcoding template group
        .setWorkflowId("<Your workflow ID>")          // Workflow (optional)
        .setAppId("<Optional: application ID>")
        .build();

VODVideoMeta supports the following fields: title / description / coverUrl / cateId / tags / userData / storageLocation / templateGroupId / workflowId / 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 transcoding template group. If you do not want to trigger transcoding, pass a "no transcoding" template group in the CreateUploadVideo call from your backend.

Timeout and retry

Configure the timeout and retry count for OSS requests:

VODUploadConfig config = new VODUploadConfig.Builder()
        .setGetAuth(getAuth)
        .setTimeout(60 * 1000)        // Unit: milliseconds. Default: 60s
        .setMaxRetryCount(2)          // Default: 2
        .build();

timeout controls the connection and read/write timeout for a single OSS request (in milliseconds). maxRetryCount controls the number of retries for an OSS request upon network exceptions.

Signature version

Specify the OSS signature version:

VODUploadConfig config = new VODUploadConfig.Builder()
        .setGetAuth(getAuth)
        .setSignature("v4")          // Default: "v4". Options: "v1"
        .build();

The OSS V4 signature is the recommended version. Starting September 1, 2025, newly created buckets must use the V4 signature. Existing customers are also advised to migrate as soon as possible. Set "v1" explicitly only if your bucket still supports only the V1 signature.

Resumable upload

By default, resumable upload is enabled. The SDK uses the (lastModified, fileName, fileSize) triplet as the resumable upload key and automatically persists it to SharedPreferences.

ScenarioSDK behavior
task.cancel() is called during uploadThe resumable upload record is retained. The next upload(...) call for the same file automatically resumes the upload.
App crash / process terminationThe resumable upload record is retained. The next cold start upload(...) call for the same file automatically resumes the upload.
Any change in the file fingerprintThe file is treated as a new file and uploaded from scratch.
OSS uploadId expirationThe SDK automatically clears the old record and initiates a new upload through CREATE_VIDEO.

To disable resumable upload:

new VODUploadConfig.Builder().setGetAuth(getAuth).setCheckpoint(false).build();

Multipart upload

Use partSize to control the part size and parallel to control the number of concurrent parts:

// Global configuration
VODUploadConfig config = new VODUploadConfig.Builder()
        .setGetAuth(getAuth)
        .setPartSize(1024 * 1024)     // Default: 1 MB
        .setParallel(4)               // Default: 4
        .build();

// Override for a single task
VODUploadOptions options = new VODUploadOptions.Builder()
        .setVideoMeta(meta)
        .setPartSize(2 * 1024 * 1024)
        .setParallel(6)
        .build();

Set the VOD service region

Specify the region of the ApsaraVideo VOD service:

new VODUploadConfig.Builder()
        .setGetAuth(getAuth)
        .setRegion("cn-shanghai")     // Default: cn-shanghai
        .build();

For a list of supported regions, see ApsaraVideo VOD region IDs.

Data reporting

By default, the SDK enables upload path instrumentation for product quality monitoring. Only runtime metrics of the upload process are collected. File content is not collected. To disable data reporting:

VODUploadConfig config = new VODUploadConfig.Builder()
        .setGetAuth(getAuth)
        .setReportEnabled(false)     // Default: true
        .build();

Error handling

VODUploadError extends java.lang.Exception. Error codes follow the three-segment format UPLOAD.{LAYER}.{TYPE}:

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 to a new upload.
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 upload was canceled by the user.
INTERNALUPLOAD.INTERNAL.DISPOSEDThe instance has been disposed.
INTERNALUPLOAD.INTERNAL.INVALID_CONFIGInvalid configuration.

Public methods of VODUploadError:

  • String getErrorCode() — Returns the error code.

  • String getErrorMessage() — Returns the error message.

  • String getUploadTaskId() — Returns the upload task ID.

  • String getSuggestion() — Returns the built-in fix suggestion from the SDK.

  • Throwable getCause() — Returns the underlying OSS exception (inherited from Exception).

The following example handles errors based on the error code:

@Override public void onFailure(VODUploadError e) {
    if (e.getErrorCode().startsWith("UPLOAD.AUTH.")) {
        // Prompt the user to check backend authentication
    } else if (VODUploadError.OSS_UPLOAD_FAILED.equals(e.getErrorCode())) {
        // Retry or prompt the user to check the network
    }
    Log.e(TAG, e.getErrorCode() + ": " + e.getErrorMessage()
            + " (suggestion=" + e.getSuggestion() + ")", e.getCause());
}

FAQ

Can I initiate multiple uploads simultaneously?

Yes. Each task is independent. As a best practice, control the number of concurrent uploads based on network conditions to avoid amplifying upstream bandwidth pressure when combined with parallel.

Resumable upload does not work after canceling and re-uploading

Check the following:

  • Verify that the file path is the same.

  • Verify that the file lastModified and size values have not changed.

  • Verify that config.checkpoint is set to true.

  • Verify whether the uploadId has expired. When the response is NoSuchUpload, the SDK automatically falls back to a new upload. This is expected behavior.

What are the fileName and fileSize values when uploading a content:// URI?

The SDK reads OpenableColumns.DISPLAY_NAME and OpenableColumns.SIZE through ContentResolver.query(...). If the size cannot be read, UPLOAD.FILE.EMPTY is returned.

What does getCause() return?

The return value inherits from Exception and retains the underlying OSS ServerException, ClientException, or IOException, which helps you troubleshoot in depth.