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
| Item | Requirement |
| Minimum Android version | API 14 (Android 4.0) |
| Compile version | compileSdkVersion 30 |
| Alibaba Cloud account | ObtainedActivate ApsaraVideo VOD |
| Authorization service | A 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:
| Kind | Trigger | OpenAPI operation to call |
CREATE_VIDEO | First upload of a video file | CreateUploadVideo |
REFRESH_VIDEO | Resumable upload / credential refresh | RefreshUploadVideo |
CREATE_IMAGE | Image upload | CreateUploadImage |
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):
| Kind | Required fields |
CREATE_VIDEO / REFRESH_VIDEO | UploadAuth, UploadAddress, VideoId |
CREATE_IMAGE | UploadAuth, 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.
| Scenario | SDK behavior |
task.cancel() is called during upload | The resumable upload record is retained. The next upload(...) call for the same file automatically resumes the upload. |
| App crash / process termination | The resumable upload record is retained. The next cold start upload(...) call for the same file automatically resumes the upload. |
| Any change in the file fingerprint | The file is treated as a new file and uploaded from scratch. |
| OSS uploadId expiration | The 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}:
| 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 to a new upload. |
| 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 upload was canceled by the user. |
| INTERNAL | UPLOAD.INTERNAL.DISPOSED | The instance has been disposed. |
| INTERNAL | UPLOAD.INTERNAL.INVALID_CONFIG | Invalid 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 fromException).
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
lastModifiedandsizevalues have not changed.Verify that
config.checkpointis set totrue.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.