SDK JavaScript ApsaraVideo VOD (aliyun-vod-upload-sdk) mengunggah file video dan gambar dari browser ke penyimpanan ApsaraVideo VOD. SDK ini menangani pengambilan kredensial, unggah multi-bagian, unggah yang dapat dilanjutkan, serta pelacakan progres.
Persyaratan browser
| Browser | Versi minimum |
| Chrome | 60+ |
| Microsoft Edge | 79+ (Chromium) |
| Firefox | 55+ |
| Safari | 11+ |
| Browser default Android | 60+ |
| Browser default iOS | 11+ |
Mulai cepat
Anda dapat mengunduh kode sumber Demo untuk contoh lengkap yang berfungsi.
Instalasi
Instal SDK menggunakan npm:
npm install aliyun-vod-upload-sdkSDK menyediakan build ESM, CJS, dan UMD serta mendukung semua bundler modern (Vite, webpack, Rollup, dan esbuild).
Impor melalui CDN
Untuk menggunakan SDK tanpa bundler, impor dari CDN:
<script src="https://g.alicdn.com/apsara-media-box/imp-web-vod-upload/2.0.0/vod-upload.umd.js"></script>
<script>
const { createUploader, UploadError } = window.VodUpload;
</script>Persyaratan API backend
SDK tidak memanggil langsung Alibaba Cloud OpenAPI. Sebaliknya, SDK mendelegasikan ke backend Anda melalui callback getAuth untuk memperoleh kredensial unggah. Backend Anda harus mengimplementasikan API berikut, yang sesuai dengan operasi OpenAPI VOD:
| API Backend | Operasi OpenAPI yang sesuai | Bidang respons |
| Buat kredensial unggah video | CreateUploadVideo | { UploadAuth, UploadAddress, VideoId } |
| Segarkan kredensial unggah video | RefreshUploadVideo | { UploadAuth, UploadAddress, VideoId } |
| Buat kredensial unggah gambar | CreateUploadImage | { UploadAuth, UploadAddress, ImageId } |
Respons JSON backend diteruskan apa adanya ke SDK. SDK secara otomatis menangani decoding Base64 dan pemetaan bidang.
Unggah file dalam lima baris kode
Contoh berikut menunjukkan kode minimal untuk mengunggah file:
import { createUploader } from 'aliyun-vod-upload-sdk';
const uploader = createUploader({
getAuth: () => fetch('/api/vod/auth').then(r => r.json()),
});
// file adalah objek File, misalnya dari elemen <input type="file">
const { videoId } = await uploader.upload(file);Fitur dasar
Unggah video
Contoh berikut membuat uploader dengan callback getAuth yang menangani pembuatan kredensial awal dan penyegaran kredensial:
import { createUploader } from 'aliyun-vod-upload-sdk';
const uploader = createUploader({
getAuth: async (ctx) => {
switch (ctx.kind) {
case 'create-video':
return fetch('/api/vod/create-auth', {
method: 'POST',
body: JSON.stringify({ fileName: ctx.file.name, ...ctx.meta }),
}).then(r => r.json());
case 'refresh-video':
return fetch(`/api/vod/refresh-auth?videoId=${ctx.videoId}`)
.then(r => r.json());
}
},
});
const result = await uploader.upload(file, {
meta: { title: 'My Video', cateId: 1000, tags: 'tutorial,example' },
});
console.log('Unggah berhasil, videoId:', result.videoId);Callback getAuth menerima parameter ctx. SDK secara otomatis menentukan ctx.kind berdasarkan skenario:
create-video— Unggahan pertama kali. Backend harus memanggil CreateUploadVideo.refresh-video— Penyegaran kredensial selama unggah yang dapat dilanjutkan. Backend harus memanggil RefreshUploadVideo.create-image— Unggahan gambar. Backend harus memanggil CreateUploadImage.
Unggah gambar
Unggah gambar menggunakan metode upload() yang sama. SDK secara otomatis mendeteksi jenis file berdasarkan file.type (jenis image/* menggunakan jalur unggah gambar):
const uploader = createUploader({
getAuth: async (ctx) => {
if (ctx.kind === 'create-image') {
return fetch('/api/vod/image-auth', {
method: 'POST',
body: JSON.stringify({ imageType: 'default' }),
}).then(r => r.json());
}
},
});
const result = await uploader.upload(imageFile);
console.log('Unggah gambar berhasil, imageId:', result.imageId);Lacak progres unggah
Gunakan callback onProgress untuk melacak progres unggah:
const result = await uploader.upload(file, {
onProgress: (percent, loaded, total) => {
// percent: 0..1 (misalnya 0,5 berarti 50%)
// loaded: byte yang diunggah
// total: ukuran file total dalam byte
progressBar.value = percent;
console.log(`${(percent * 100).toFixed(1)}% - ${loaded} / ${total}`);
},
});SDK menjamin bahwa percent bernilai 1 saat unggah selesai.
Batalkan unggah
Anda dapat membatalkan unggah dengan menggunakan metode task.abort() atau AbortSignal.
Metode 1: task.abort() — Pendekatan paling sederhana
const task = uploader.upload(file);
cancelBtn.onclick = () => task.abort();
try {
const result = await task;
} catch (e) {
if (e.name === 'AbortError') {
console.log('Unggah dibatalkan oleh pengguna');
}
}Metode 2: AbortSignal — Cocok untuk siklus hidup komponen React/Vue
const ctrl = new AbortController();
cancelBtn.onclick = () => ctrl.abort();
try {
const result = await uploader.upload(file, { signal: ctrl.signal });
} catch (e) {
if (e.name === 'AbortError') {
console.log('Unggah dibatalkan');
}
}Setelah pembatalan, checkpoint dipertahankan. Pemanggilan berikutnya ke upload(file) secara otomatis melanjutkan dari titik henti.
Unggah yang dapat dilanjutkan
Unggah yang dapat dilanjutkan diaktifkan secara default dan tidak memerlukan konfigurasi tambahan. Fitur ini hanya didukung untuk unggah video; unggah gambar tidak menggunakan unggah yang dapat dilanjutkan. SDK secara otomatis:
Menyimpan progres bagian yang telah selesai di
localStorage.Melanjutkan dari tempat unggah terputus ketika
upload(file)dipanggil lagi setelah refresh halaman.Mencocokkan checkpoint berdasarkan aturan: file.name + file.size + file.lastModified yang sama.
// Unggah pertama (pengguna menutup halaman pada progres 40%)
await uploader.upload(file);
// Pengguna membuka kembali halaman dan mengunggah file yang sama lagi
// SDK secara otomatis melanjutkan dari 40% tanpa mengunggah ulang
const result = await uploader.upload(file);Untuk menonaktifkan unggah yang dapat dilanjutkan:
const uploader = createUploader({
getAuth: myGetAuth,
checkpoint: false, // Nonaktifkan unggah yang dapat dilanjutkan
});Fitur lanjutan
Unggah batch
SDK menyediakan API upload() untuk satu file. Untuk unggah batch, gunakan primitif async JavaScript standar.
Unggah serial (paling sederhana)
for (const file of files) {
const result = await uploader.upload(file, {
onProgress: p => updateProgress(file.name, p),
});
console.log(`${file.name} selesai, videoId: ${result.videoId}`);
}Unggah konkuren (bandwidth tinggi dan file kecil)
const results = await Promise.all(
files.map(f => uploader.upload(f)),
);(Direkomendasikan) Unggah dengan batas konkurensi (file besar)
import pLimit from 'p-limit';
const limit = pLimit(2); // Unggah maksimal 2 file secara bersamaan
const results = await Promise.all(
files.map(f => limit(() => uploader.upload(f, {
onProgress: p => updateItemProgress(f, p),
}))),
);Jeda dan lanjutkan
SDK tidak memiliki API eksplisit untuk jeda. Capai efek jeda melalui batalkan + lanjutkan:
// Jeda: batalkan unggah saat ini
task.abort();
// Lanjutkan: unggah ulang file yang sama, secara otomatis dilanjutkan dari checkpoint
const result = await uploader.upload(file);Timeout dan pembatalan multi-sumber
Gunakan AbortSignal.timeout() dan AbortSignal.any() untuk mengimplementasikan pembatalan berbasis timeout dan gabungan:
// Batalkan otomatis setelah 60 detik
await uploader.upload(file, {
signal: AbortSignal.timeout(60_000),
});
// Pembatalan multi-sumber: pembatalan manual pengguna ATAU timeout 60 detik
const userCtrl = new AbortController();
await uploader.upload(file, {
signal: AbortSignal.any([
userCtrl.signal,
AbortSignal.timeout(60_000),
]),
});Integrasi React dan Vue
React
Contoh React Hook
import { useEffect, useState, useRef } from 'react';
import { createUploader, UploadError } from 'aliyun-vod-upload-sdk';
function useUploader(getAuth) {
const uploaderRef = useRef(createUploader({ getAuth }));
useEffect(() => {
return () => uploaderRef.current.dispose(); // Lepaskan saat unmount
}, []);
return uploaderRef.current;
}
function UploadButton({ file }) {
const uploader = useUploader(myGetAuth);
const [progress, setProgress] = useState(0);
const handleUpload = () => {
const ctrl = new AbortController();
uploader.upload(file, {
signal: ctrl.signal,
onProgress: p => setProgress(p),
}).then(result => {
console.log('Berhasil', result.videoId);
}).catch(e => {
if (e.name !== 'AbortError') {
console.error('Gagal', e);
}
});
};
return <button onClick={handleUpload}>Upload ({(progress * 100).toFixed(0)}%)</button>;
}Vue 3
Contoh Vue 3 Composable
import { onUnmounted, ref } from 'vue';
import { createUploader } from 'aliyun-vod-upload-sdk';
export function useUploader(getAuth) {
const uploader = createUploader({ getAuth });
const progress = ref(0);
onUnmounted(() => uploader.dispose());
async function upload(file) {
return uploader.upload(file, {
onProgress: p => { progress.value = p; },
});
}
return { upload, progress };
}Referensi API
createUploader(config)
Membuat instans Uploader.
import { createUploader } from 'aliyun-vod-upload-sdk';
const uploader = createUploader(config);UploaderConfig
| Parameter | Type | Wajib | Default | Deskripsi |
| getAuth | GetAuth | Ya | — | Callback pengambilan kredensial. Untuk informasi lebih lanjut, lihat callback getAuth. |
| retry | RetryPolicy | Tidak | { count: 3 } | Kebijakan retry otomatis untuk kegagalan unggah multi-bagian OSS. |
| checkpoint | false | { store?: CheckpointStore } | Tidak | localStorage | Konfigurasi unggah yang dapat dilanjutkan. Atur ke false untuk menonaktifkan. Atur ke { store } untuk menyuntikkan store kustom. |
| parallel | number | Tidak | 4 | Jumlah bagian OSS konkuren. |
| partSize | number | Tidak | 1048576 (1 MB) | Ukuran bagian OSS dalam byte. |
| timeout | number | Tidak | 60000 | Timeout permintaan jaringan dalam milidetik. |
| cname | string | Tidak | — | Nama domain OSS kustom. |
| refreshSTSTokenInterval | number | Tidak | 300000 (5 menit) | Interval pemeriksaan penyegaran kredensial STS dalam milidetik. |
RetryPolicy
| Bidang | Tipe | Default | Deskripsi |
| count | number | 3 | Jumlah maksimum percobaan ulang. |
uploader.upload(file, options?)
Mengunggah satu file. Mengembalikan UploadTask, yang merupakan Promise<UploadResult> sekaligus objek dengan metode .abort().
const task = uploader.upload(file, options);UploadOptions
| Parameter | Tipe | Deskripsi |
| meta | VodVideoMeta | VodImageMeta | Metadata media (judul, kategori, tag, dan lainnya). |
| signal | AbortSignal | Sinyal pembatalan standar web. |
| onProgress | (percent, loaded, total) => void | Callback progres. Nilai percent berkisar dari 0 hingga 1. |
| partSize | number | Mengganti partSize global. |
| parallel | number | Mengganti parallel global. |
VodVideoMeta (metadata video)
| Bidang | Tipe | Deskripsi |
| title | string | Judul video. |
| description | string | Deskripsi video. |
| cateId | number | ID kategori. |
| tags | string | Tag, dipisahkan koma. |
| templateGroupId | string | ID grup templat transkoding. |
| storageLocation | string | Alamat penyimpanan. |
| coverUrl | string | URL gambar sampul. |
| workflowId | string | ID alur kerja. |
| appId | string | ID aplikasi. |
| userData | Record | Data kustom. |
VodImageMeta (metadata gambar)
| Bidang | Tipe | Deskripsi |
| title | string | Judul gambar. |
| description | string | Deskripsi gambar. |
| imageType | 'default' | 'cover' | 'watermark' | Jenis gambar. |
| imageExt | string | Ekstensi file gambar. |
| tags | string | Tag. |
| cateId | number | ID kategori. |
| storageLocation | string | Alamat penyimpanan. |
UploadResult
| Bidang | Tipe | Deskripsi |
| videoId | string? | ID video. Dikembalikan untuk unggah video. |
| imageId | string? | ID gambar. Dikembalikan untuk unggah gambar. |
| etag | string | ETag OSS. |
| requestId | string | ID permintaan OSS. |
| durationMs | number | Durasi unggah dalam milidetik. |
| uploadTaskId | string | ID tugas unggah unik. Dapat digunakan untuk troubleshooting bersama tim dukungan teknis. |
UploadTask
Objek yang dikembalikan oleh upload() merupakan Promise<UploadResult> sekaligus objek dengan metode .abort().
type UploadTask = Promise<UploadResult> & {
abort(): void;
};Metode .abort() hilang setelah chaining. task.then(fn) mengembalikan Promise biasa. Untuk mempertahankan kemampuan membatalkan setelah chaining, gunakan options.signal.
uploader.dispose()
Melepaskan sumber daya: membatalkan semua unggah yang sedang berlangsung dan membersihkan status internal.
uploader.dispose();Setelah pemanggilan ini, instans uploader tidak dapat digunakan lagi. Panggil metode ini saat komponen React atau Vue di-unmount untuk mencegah kebocoran memori.
Callback getAuth
type GetAuth = (ctx: AuthContext) => Promise<AuthResult>;AuthContext
SDK meneruskan nilai ctx yang berbeda berdasarkan skenario unggah:
| ctx.kind | Skenario pemicu | Bidang ctx tambahan |
| 'create-video' | Unggah video pertama kali | file, meta? |
| 'refresh-video' | Penyegaran kredensial selama unggah yang dapat dilanjutkan | file, videoId |
| 'create-image' | Unggah gambar | file, meta? |
AuthResult
JSON mentah yang dikembalikan oleh backend dari respons OpenAPI. SDK secara otomatis mendecodenya:
| Bidang | Tipe | Wajib | Deskripsi |
| UploadAuth | string | Ya | Kredensial STS yang dienkripsi Base64. |
| UploadAddress | string | Ya | Alamat unggah OSS yang dienkripsi Base64. |
| VideoId | string | Wajib untuk create-video | ID video. |
| ImageId | string | Wajib untuk create-image | ID gambar. |
| ImageURL | string | Tidak | URL gambar (dilewatkan). |
Implementasi getAuth paling sederhana:
// Rute backend terpadu
createUploader({
getAuth: ctx => fetch('/api/vod/auth', {
method: 'POST',
body: JSON.stringify(ctx),
}).then(r => r.json()),
});Fallback otomatis saat kegagalan refresh-video: Saat penyegaran kredensial selama unggah yang dapat dilanjutkan gagal, SDK secara otomatis fallback ke create-video untuk mengunggah ulang file. Ini sepenuhnya transparan bagi pemanggil.
Penanganan error
Struktur UploadError
Semua error unggah adalah instans UploadError (memperluas Error):
import { UploadError } from 'aliyun-vod-upload-sdk';
class UploadError extends Error {
readonly code: string; // Kode error terstruktur, misalnya 'UPLOAD.OSS.ACCESS_DENIED'
readonly message: string; // Deskripsi error
readonly suggestion: string; // Saran perbaikan
readonly cause?: unknown; // Error asli yang mendasari
readonly uploadTaskId?: string; // ID tugas unggah (untuk troubleshooting)
}Pembatalan unggah yang diprakarsai pengguna bukanlah UploadError. Ini adalah DOMException standar (name === 'AbortError').
Kode error
Format kode error: UPLOAD.{layer}.{type}
| Kode error | Deskripsi | Saran perbaikan |
| UPLOAD.AUTH.GET_AUTH_FAILED | Callback getAuth melempar error atau mengembalikan struktur yang tidak valid. | Periksa apakah callback getAuth mengembalikan JSON yang benar berisi bidang UploadAuth dan UploadAddress. Pastikan layanan backend tersedia dan format respons benar. |
| UPLOAD.OSS.ACCESS_DENIED | Error izin OSS (403). | Periksa apakah kredensial unggah (Token STS) valid dan belum kedaluwarsa, serta apakah kebijakan RAM memberikan izin tulis OSS. |
| UPLOAD.OSS.NO_SUCH_BUCKET | Bucket tidak ada. | Periksa apakah nama bucket dalam alamat unggah benar, dan pastikan bucket telah dibuat di wilayah yang sesuai. |
| UPLOAD.OSS.NO_SUCH_UPLOAD | Sesi unggah multi-bagian telah kedaluwarsa. | Sesi unggah multi-bagian telah kedaluwarsa (mungkin setelah lebih dari 24 jam). SDK secara otomatis mengunggah ulang file. Tidak diperlukan tindakan manual. |
| UPLOAD.OSS.UNKNOWN | Error OSS tidak dikenal. | Periksa konsol browser untuk informasi error detail, atau hubungi dukungan teknis. |
| UPLOAD.NETWORK.TIMEOUT | Permintaan timeout. | Buka panel Network di developer tools browser untuk melihat detail permintaan yang gagal. Periksa apakah koneksi jaringan stabil, atau coba tingkatkan nilai konfigurasi timeout. |
| UPLOAD.NETWORK.ERROR | Error koneksi jaringan. | Buka panel Network di developer tools browser untuk melihat kode status dan respons permintaan yang gagal. Pastikan browser dapat mengakses alamat layanan OSS. Periksa apakah proxy atau firewall memblokir permintaan. |
| UPLOAD.NETWORK.OFFLINE | Browser sedang offline. | Periksa status jaringan dan coba unggah lagi setelah terhubung kembali. |
| UPLOAD.FILE.EMPTY | Ukuran file 0. | Ukuran file 0. File kosong tidak dapat diunggah. Periksa apakah file yang benar dipilih. |
| UPLOAD.INTERNAL.DISPOSED | Instans Uploader telah di-dispose. | Panggil createUploader() lagi untuk membuat instans baru. |
Gunakan konstanta kode error
(Direkomendasikan) Gunakan konstanta ErrorCode daripada literal string:
import { ErrorCode, UploadError } from 'aliyun-vod-upload-sdk';
try {
await uploader.upload(file);
} catch (e) {
if (e instanceof UploadError) {
switch (e.code) {
case ErrorCode.AUTH_GET_AUTH_FAILED:
showToast('Gagal memperoleh kredensial. Refresh halaman dan coba lagi.');
break;
case ErrorCode.NETWORK_TIMEOUT:
case ErrorCode.NETWORK_ERROR:
showToast('Error jaringan. Periksa koneksi Anda dan coba lagi.');
break;
case ErrorCode.OSS_ACCESS_DENIED:
showToast('Izin unggah tidak mencukupi. Hubungi administrator.');
break;
default:
showToast(`Unggah gagal: ${e.suggestion || e.message}`);
}
}
}Troubleshooting
Setiap UploadError berisi uploadTaskId, yaitu ID jejak global untuk seluruh proses unggah. Anda dapat memberikan ID ini kepada tim dukungan teknis Alibaba Cloud untuk identifikasi masalah cepat:
catch (e) {
if (e instanceof UploadError) {
// Kirim ke tim dukungan teknis Anda
const diagnostic = {
code: e.code,
message: e.message,
uploadTaskId: e.uploadTaskId,
suggestion: e.suggestion,
};
reportToSupport(diagnostic);
}
}UploadResult.uploadTaskId juga dapat digunakan untuk korelasi log setelah unggah berhasil:
const result = await uploader.upload(file);
myLogger.info('Unggah berhasil', { videoId: result.videoId, uploadTaskId: result.uploadTaskId });