O ApsaraVideo VOD JavaScript SDK (aliyun-vod-upload-sdk) realiza o upload de arquivos de vídeo e imagem diretamente do navegador para o armazenamento do ApsaraVideo VOD. O SDK gerencia a obtenção de credenciais, o upload multipart, o upload retomável e o acompanhamento de progresso.
Requisitos do navegador
|
Navegador |
Versão mínima |
|
Chrome |
60+ |
|
Microsoft Edge |
79+ (Chromium) |
|
Firefox |
55+ |
|
Safari |
11+ |
|
Navegador padrão do Android |
60+ |
|
Navegador padrão do iOS |
11+ |
Início rápido
Baixe o código-fonte da demonstração para acessar um exemplo completo e funcional.
Instalação
Instale o SDK via npm:
npm install aliyun-vod-upload-sdk
O SDK oferece builds ESM, CJS e UMD, com suporte a todos os empacotadores modernos (Vite, webpack, Rollup e esbuild).
Importação via CDN
Para utilizar o SDK sem um empacotador, importe-o diretamente de uma 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>
Requisitos da API de backend
O SDK não chama diretamente a OpenAPI do Alibaba Cloud. Em vez disso, ele delega essa tarefa ao seu backend por meio do callback getAuth para obter as credenciais de upload. Seu backend deve implementar as seguintes APIs, correspondentes às operações da OpenAPI do VOD:
|
API de backend |
Operação OpenAPI correspondente |
Campos de resposta |
|
Criar credencial de upload de vídeo |
CreateUploadVideo |
{ UploadAuth, UploadAddress, VideoId } |
|
Atualizar credencial de upload de vídeo |
RefreshUploadVideo |
{ UploadAuth, UploadAddress, VideoId } |
|
Criar credencial de upload de imagem |
CreateUploadImage |
{ UploadAuth, UploadAddress, ImageId } |
O JSON de resposta do backend é repassado tal como está ao SDK. O próprio SDK cuida automaticamente da decodificação base64 e do mapeamento dos campos.
Faça o upload de um arquivo em cinco linhas de código
O exemplo abaixo apresenta o código mínimo necessário para enviar um arquivo:
import { createUploader } from 'aliyun-vod-upload-sdk';
const uploader = createUploader({
getAuth: () => fetch('/api/vod/auth').then(r => r.json()),
});
// file is a File object, for example from an <input type="file"> element
const { videoId } = await uploader.upload(file);
Recursos básicos
Upload de vídeos
Este exemplo cria um uploader com um callback getAuth que gerencia tanto a criação inicial quanto a atualização das credenciais:
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('Upload successful, videoId:', result.videoId);
O callback getAuth recebe um parâmetro ctx. O SDK determina automaticamente o valor de ctx.kind conforme o cenário:
**
create-video** — Primeiro upload. O backend precisa chamar CreateUploadVideo.**
refresh-video** — Atualização de credencial durante um upload retomável. O backend precisa chamar RefreshUploadVideo.**
create-image** — Upload de imagem. O backend precisa chamar CreateUploadImage.
Upload de imagens
O envio de imagens utiliza o mesmo método upload(). O SDK detecta automaticamente o tipo de arquivo com base em file.type (tipos image/* seguem o fluxo de upload de imagem):
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('Image upload successful, imageId:', result.imageId);
Acompanhamento do progresso de upload
Utilize o callback onProgress para monitorar o andamento do upload:
const result = await uploader.upload(file, {
onProgress: (percent, loaded, total) => {
// percent: 0..1 (e.g. 0.5 means 50%)
// loaded: bytes uploaded
// total: total file size in bytes
progressBar.value = percent;
console.log(`${(percent * 100).toFixed(1)}% - ${loaded} / ${total}`);
},
});
O SDK garante que percent seja igual a 1 quando o upload for concluído.
Cancelamento de um upload
É possível cancelar um upload utilizando o método task.abort() ou um AbortSignal.
Método 1: task.abort() — Abordagem mais simples
const task = uploader.upload(file);
cancelBtn.onclick = () => task.abort();
try {
const result = await task;
} catch (e) {
if (e.name === 'AbortError') {
console.log('Upload canceled by user');
}
}
Método 2: AbortSignal — Ideal para ciclos de vida de componentes 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('Upload canceled');
}
}
Após o cancelamento, o checkpoint é preservado. A próxima chamada a upload(file) retoma automaticamente o envio a partir do ponto de interrupção.
Upload retomável
O upload retomável vem ativado por padrão e não exige configurações adicionais. Esse recurso é suportado apenas para uploads de vídeo; uploads de imagem não utilizam essa funcionalidade. O SDK executa automaticamente as seguintes ações:
Armazena o progresso das partes concluídas no
localStorage.Retoma o envio do ponto onde foi interrompido quando
upload(file)é chamado novamente após uma atualização da página.Identifica os checkpoints usando a regra: mesmo file.name + file.size + file.lastModified.
// First upload (user closes the page at 40% progress)
await uploader.upload(file);
// User reopens the page and uploads the same file again
// The SDK automatically resumes from 40% without re-uploading
const result = await uploader.upload(file);
Para desativar o upload retomável:
const uploader = createUploader({
getAuth: myGetAuth,
checkpoint: false, // Disable resumable upload
});
Recursos avançados
Upload em lote
O SDK fornece uma API upload() para arquivo único. Para uploads em lote, utilize os primitivos assíncronos padrão do JavaScript.
Upload sequencial (mais simples)
for (const file of files) {
const result = await uploader.upload(file, {
onProgress: p => updateProgress(file.name, p),
});
console.log(`${file.name} complete, videoId: ${result.videoId}`);
}
Upload simultâneo (alta largura de banda e arquivos pequenos)
const results = await Promise.all(
files.map(f => uploader.upload(f)),
);
(Recomendado) Upload com limite de concorrência (arquivos grandes)
import pLimit from 'p-limit';
const limit = pLimit(2); // Upload at most 2 files simultaneously
const results = await Promise.all(
files.map(f => limit(() => uploader.upload(f, {
onProgress: p => updateItemProgress(f, p),
}))),
);
Pausar e retomar
O SDK não possui uma API explícita de pause. Obtenha o efeito de pausa combinando cancelamento + retomada:
// Pause: cancel the current upload
task.abort();
// Resume: re-upload the same file, automatically resumes from checkpoint
const result = await uploader.upload(file);
Timeout e cancelamento de múltiplas origens
Utilize AbortSignal.timeout() e AbortSignal.any() para implementar cancelamentos baseados em timeout ou combinados:
// Auto-cancel after 60 seconds
await uploader.upload(file, {
signal: AbortSignal.timeout(60_000),
});
// Multi-source cancellation: user manual cancel OR 60-second timeout
const userCtrl = new AbortController();
await uploader.upload(file, {
signal: AbortSignal.any([
userCtrl.signal,
AbortSignal.timeout(60_000),
]),
});
Integração com React e Vue
React
Exemplo de 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(); // Release on 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('Success', result.videoId);
}).catch(e => {
if (e.name !== 'AbortError') {
console.error('Failed', e);
}
});
};
return <button onClick={handleUpload}>Upload ({(progress * 100).toFixed(0)}%)</button>;
}
Vue 3
Exemplo de 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 };
}
Referência da API
createUploader(config)
Cria uma instância do Uploader.
import { createUploader } from 'aliyun-vod-upload-sdk';
const uploader = createUploader(config);
UploaderConfig
|
Parâmetro |
Tipo |
Obrigatório |
Padrão |
Descrição |
|
|
getAuth |
GetAuth |
Sim |
— |
Callback para obtenção de credenciais. Para mais informações, consulte callback getAuth. |
|
|
retry |
RetryPolicy |
Não |
{ count: 3 } |
Política de nova tentativa automática para falhas no upload multipart do OSS. |
|
|
checkpoint |
false |
{ store?: CheckpointStore } |
Não |
localStorage |
Configuração de upload retomável. Defina como |
|
parallel |
number |
Não |
4 |
Quantidade de partes do OSS enviadas simultaneamente. |
|
|
partSize |
number |
Não |
1048576 (1 MB) |
Tamanho da parte do OSS em bytes. |
|
|
timeout |
number |
Não |
60000 |
Timeout de requisição de rede em milissegundos. |
|
|
cname |
string |
Não |
— |
Nome de domínio personalizado do OSS. |
|
|
refreshSTSTokenInterval |
number |
Não |
300000 (5 min) |
Intervalo de verificação para atualização de credenciais STS em milissegundos. |
RetryPolicy
|
Campo |
Tipo |
Padrão |
Descrição |
|
count |
number |
3 |
Número máximo de novas tentativas. |
uploader.upload(file, options?)
Envia um único arquivo. Retorna um UploadTask, que funciona simultaneamente como uma Promise<UploadResult> e como um objeto com o método .abort().
const task = uploader.upload(file, options);
UploadOptions
|
Parâmetro |
Tipo |
Descrição |
|
|
meta |
VodVideoMeta |
VodImageMeta |
Metadados da mídia (título, categoria, tags, entre outros). |
|
signal |
AbortSignal |
Sinal de cancelamento padrão da Web. |
|
|
onProgress |
(percent, loaded, total) => void |
Callback de progresso. O valor de |
|
|
partSize |
number |
Substitui o partSize global. |
|
|
parallel |
number |
Substitui o parallel global. |
VodVideoMeta (metadados de vídeo)
|
Campo |
Tipo |
Descrição |
|
title |
string |
Título do vídeo. |
|
description |
string |
Descrição do vídeo. |
|
cateId |
number |
ID da categoria. |
|
tags |
string |
Tags separadas por vírgulas. |
|
templateGroupId |
string |
ID do grupo de modelos de transcodificação. |
|
storageLocation |
string |
Endereço de armazenamento. |
|
coverUrl |
string |
URL da imagem de capa. |
|
workflowId |
string |
ID do fluxo de trabalho. |
|
appId |
string |
ID da aplicação. |
|
userData |
Record |
Dados personalizados. |
VodImageMeta (metadados de imagem)
|
Campo |
Tipo |
Descrição |
||
|
title |
string |
Título da imagem. |
||
|
description |
string |
Descrição da imagem. |
||
|
imageType |
'default' |
'cover' |
'watermark' |
Tipo de imagem. |
|
imageExt |
string |
Extensão do arquivo de imagem. |
||
|
tags |
string |
Tags. |
||
|
cateId |
number |
ID da categoria. |
||
|
storageLocation |
string |
Endereço de armazenamento. |
UploadResult
|
Campo |
Tipo |
Descrição |
|
videoId |
string? |
ID do vídeo. Retornado em uploads de vídeo. |
|
imageId |
string? |
ID da imagem. Retornado em uploads de imagem. |
|
etag |
string |
ETag do OSS. |
|
requestId |
string |
ID da requisição do OSS. |
|
durationMs |
number |
Duração do upload em milissegundos. |
|
uploadTaskId |
string |
ID exclusivo da tarefa de upload. Pode ser usado para solução de problemas junto ao suporte técnico. |
UploadTask
O objeto retornado por upload() atua tanto como uma Promise<UploadResult> quanto como um objeto com o método .abort().
type UploadTask = Promise<UploadResult> & {
abort(): void;
};
O método .abort() se perde após o encadeamento. task.then(fn) retorna uma Promise comum. Para manter a capacidade de cancelamento após o encadeamento, utilize options.signal.
uploader.dispose()
Libera recursos: cancela todos os uploads em andamento e limpa o estado interno.
uploader.dispose();
Após essa chamada, a instância uploader não pode mais ser utilizada. Chame este método quando um componente React ou Vue for desmontado para evitar vazamentos de memória.
Callback getAuth
type GetAuth = (ctx: AuthContext) => Promise<AuthResult>;
AuthContext
O SDK passa diferentes valores de ctx dependendo do cenário de upload:
|
ctx.kind |
Cenário de acionamento |
Campos adicionais de ctx |
|
'create-video' |
Primeiro upload de vídeo |
file, meta? |
|
'refresh-video' |
Atualização de credencial durante upload retomável |
file, videoId |
|
'create-image' |
Upload de imagem |
file, meta? |
AuthResult
JSON bruto retornado pelo backend a partir da resposta da OpenAPI. O SDK faz a decodificação automaticamente:
|
Campo |
Tipo |
Obrigatório |
Descrição |
|
UploadAuth |
string |
Sim |
Credencial STS codificada em Base64. |
|
UploadAddress |
string |
Sim |
Endereço de upload do OSS codificado em Base64. |
|
VideoId |
string |
Obrigatório para create-video |
ID do vídeo. |
|
ImageId |
string |
Obrigatório para create-image |
ID da imagem. |
|
ImageURL |
string |
Não |
URL da imagem (repassada tal como está). |
Implementação mais simples de getAuth:
// Unified backend route
createUploader({
getAuth: ctx => fetch('/api/vod/auth', {
method: 'POST',
body: JSON.stringify(ctx),
}).then(r => r.json()),
});
Fallback automático em caso de falha no refresh-video: Quando a atualização de credencial durante um upload retomável falha, o SDK reverte automaticamente para create-video para reenviar o arquivo. Esse processo é totalmente transparente para quem faz a chamada.
Tratamento de erros
Estrutura do UploadError
Todos os erros de upload são instâncias de UploadError (estende Error):
import { UploadError } from 'aliyun-vod-upload-sdk';
class UploadError extends Error {
readonly code: string; // Structured error code, e.g. 'UPLOAD.OSS.ACCESS_DENIED'
readonly message: string; // Error description
readonly suggestion: string; // Fix suggestion
readonly cause?: unknown; // Original underlying error
readonly uploadTaskId?: string; // Upload task ID (for troubleshooting)
}
O cancelamento de upload iniciado pelo usuário não gera um UploadError. Trata-se de uma DOMException padrão (name === 'AbortError').
Códigos de erro
Formato do código de erro: UPLOAD.{layer}.{type}
|
Código de erro |
Descrição |
Correção sugerida |
|
UPLOAD.AUTH.GET_AUTH_FAILED |
O callback getAuth lançou um erro ou retornou uma estrutura inválida. |
Verifique se o callback getAuth retorna corretamente um JSON contendo os campos UploadAuth e UploadAddress. Certifique-se de que o service de backend está disponível e o formato da resposta está correto. |
|
UPLOAD.OSS.ACCESS_DENIED |
Erro de permissão do OSS (403). |
Valide se a credencial de upload (STS Token) é válida e não expirou, e se a política do RAM concede permissões de escrita no OSS. |
|
UPLOAD.OSS.NO_SUCH_BUCKET |
O bucket não existe. |
Confirme se o nome do bucket no endereço de upload está correto e garanta que o bucket foi criado na região correspondente. |
|
UPLOAD.OSS.NO_SUCH_UPLOAD |
A sessão de upload multipart expirou. |
A sessão de upload multipart expirou (possivelmente após mais de 24 horas). O SDK reenvia o arquivo automaticamente. Nenhuma ação manual é necessária. |
|
UPLOAD.OSS.UNKNOWN |
Erro desconhecido do OSS. |
Consulte o console do navegador para obter informações detalhadas sobre o erro ou entre em contato com o suporte técnico. |
|
UPLOAD.NETWORK.TIMEOUT |
Tempo limite da requisição esgotado. |
Abra o painel Network nas ferramentas de desenvolvedor do navegador para visualizar os detalhes da requisição com falha. Verifique se a conexão de rede está estável ou tente aumentar o valor de configuração do timeout. |
|
UPLOAD.NETWORK.ERROR |
Erro de conexão de rede. |
Abra o painel Network nas ferramentas de desenvolvedor do navegador para verificar o código de status e a resposta da requisição com falha. Garanta que o navegador consegue acessar o endereço do service OSS. Verifique se algum proxy ou firewall está bloqueando a requisição. |
|
UPLOAD.NETWORK.OFFLINE |
O navegador está offline. |
Verifique o status da rede e tente realizar o upload novamente após reconectar. |
|
UPLOAD.FILE.EMPTY |
O tamanho do arquivo é 0. |
O tamanho do arquivo é 0. Arquivos vazios não podem ser enviados. Verifique se o arquivo correto foi selecionado. |
|
UPLOAD.INTERNAL.DISPOSED |
A instância do Uploader foi descartada. |
Chame createUploader() novamente para criar uma nova instância. |
Uso de constantes de código de erro
(Recomendado) Utilize as constantes ErrorCode em vez de literais de 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('Failed to obtain credentials. Refresh the page and try again.');
break;
case ErrorCode.NETWORK_TIMEOUT:
case ErrorCode.NETWORK_ERROR:
showToast('Network error. Check your network and try again.');
break;
case ErrorCode.OSS_ACCESS_DENIED:
showToast('Insufficient upload permissions. Contact the administrator.');
break;
default:
showToast(`Upload failed: ${e.suggestion || e.message}`);
}
}
}
Solução de problemas
Cada UploadError contém um uploadTaskId, que serve como ID de rastreamento global para todo o upload. Forneça esse ID ao suporte técnico do Alibaba Cloud para agilizar a identificação do problema:
catch (e) {
if (e instanceof UploadError) {
// Send to your technical support team
const diagnostic = {
code: e.code,
message: e.message,
uploadTaskId: e.uploadTaskId,
suggestion: e.suggestion,
};
reportToSupport(diagnostic);
}
}
O campo UploadResult.uploadTaskId também pode ser usado para correlacionar logs após um upload bem-sucedido:
const result = await uploader.upload(file);
myLogger.info('Upload successful', { videoId: result.videoId, uploadTaskId: result.uploadTaskId });