本文介绍如何使用JavaScript SDK将媒体文件从本地上传至点播存储。
浏览器要求
浏览器 | 版本 |
Chrome | 60+ |
Microsoft Edge | 79+(Chromium内核) |
Firefox | 55+ |
Safari | 11+ |
Android系统默认浏览器 | 60+ |
iOS系统默认浏览器 | 11+ |
Demo下载地址
下载地址:Demo源码
快速集成
安装
npm install aliyun-vod-upload-sdkSDK 提供 ESM、CJS、UMD 三种构建产物,支持所有现代打包工具(Vite / webpack / Rollup / esbuild)。
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>5 行代码上传
import { createUploader } from 'aliyun-vod-upload-sdk';
const uploader = createUploader({
getAuth: () => fetch('/api/vod/auth').then(r => r.json()),
});
const { videoId } = await uploader.upload(file);后端接口要求
SDK 不直接调用阿里云 OpenAPI,而是通过 getAuth 回调委托你的后端获取上传凭证。后端需实现以下接口(对应 VOD OpenAPI):
后端接口 | 对应 OpenAPI | 返回字段 |
创建视频上传凭证 | CreateUploadVideo |
|
刷新视频上传凭证 | RefreshUploadVideo |
|
创建图片上传凭证 | CreateUploadImage |
|
后端返回的 JSON 原样透传给 SDK 即可,SDK 内部自动完成 base64 解码和字段映射。
基础功能
视频上传
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: '我的视频', cateId: 1000, tags: '教程,示例' },
});
console.log('上传成功,videoId:', result.videoId);getAuth 回调接收一个 ctx 参数,SDK 会根据场景自动决定 ctx.kind:
create-video— 首次上传,需后端调用 CreateUploadVideo。refresh-video— 续传或凭证过期时刷新,需后端调用 RefreshUploadVideo。create-image— 上传图片,需后端调用 CreateUploadImage。
图片上传
图片上传使用同一个 upload() 方法,SDK 根据 file.type 自动识别(image/* 类型走图片链路):
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('图片上传成功,imageId:', result.imageId);上传进度
const result = await uploader.upload(file, {
onProgress: (percent, loaded, total) => {
// percent: 0..1 (如 0.5 表示 50%)
// loaded: 已上传字节数
// total: 文件总字节数
progressBar.value = percent;
console.log(`${(percent * 100).toFixed(1)}% - ${loaded} / ${total}`);
},
});SDK 保证上传完成时 percent 最终一定等于 1。
取消上传
方式一:task.abort() — 最简单
const task = uploader.upload(file); cancelBtn.onclick = () => task.abort(); try { const result = await task; } catch (e) { if (e.name === 'AbortError') { console.log('用户取消了上传'); } }方式二:AbortSignal — 适合 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('已取消'); } }
取消后 checkpoint 会保留,下次再 upload(file) 会自动从断点续传。
断点续传
断点续传是默认开启的,无需任何额外配置(仅视频上传支持,图片上传不走断点续传)。SDK 自动:
将已完成的分片进度存入
localStorage。页面刷新后再次
upload(file),自动从上次中断处继续。匹配规则:相同 file.name + file.size + file.lastModified → 命中 checkpoint。
// 第一次上传(上传到 40% 时用户关闭页面)
await uploader.upload(file);
// 用户重新打开页面后再次上传同一个文件
// SDK 自动从 40% 继续,不会重复上传
const result = await uploader.upload(file);关闭断点续传:
const uploader = createUploader({
getAuth: myGetAuth,
checkpoint: false, // 关闭
});高级功能
批量上传
SDK 提供单文件 upload() API,批量上传使用标准 JavaScript 异步原语组合:
// 串行上传(最简单)
for (const file of files) {
const result = await uploader.upload(file, {
onProgress: p => updateProgress(file.name, p),
});
console.log(`${file.name} 完成,videoId: ${result.videoId}`);
}// 并发上传(大带宽 + 小文件)
const results = await Promise.all(
files.map(f => uploader.upload(f)),
);// 限并发(推荐,大文件场景)
import pLimit from 'p-limit';
const limit = pLimit(2); // 最多同时上传 2 个文件
const results = await Promise.all(
files.map(f => limit(() => uploader.upload(f, {
onProgress: p => updateItemProgress(f, p),
}))),
);暂停与恢复
SDK 没有显式的暂停API。暂停效果通过 取消 + 续传 实现:
// 暂停:取消当前上传
task.abort();
// 恢复:重新上传同一文件,自动从 checkpoint 续传
const result = await uploader.upload(file);超时取消与多源取消
// 60 秒超时自动取消
await uploader.upload(file, {
signal: AbortSignal.timeout(60_000),
});
// 多源取消:用户手动取消 或 60 秒超时
const userCtrl = new AbortController();
await uploader.upload(file, {
signal: AbortSignal.any([
userCtrl.signal,
AbortSignal.timeout(60_000),
]),
});React / Vue 集成
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(); // 组件卸载时释放
}, []);
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('成功', result.videoId);
}).catch(e => {
if (e.name !== 'AbortError') {
console.error('失败', e);
}
});
};
return <button onClick={handleUpload}>上传 ({(progress * 100).toFixed(0)}%)</button>;
}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 };
}API文档
createUploader(config)
创建 Uploader 实例。
import { createUploader } from 'aliyun-vod-upload-sdk';
const uploader = createUploader(config);UploaderConfig
参数 | 类型 | 必填 | 默认值 | 说明 |
|
| 是 | — | 凭证获取回调,详见凭证回调 getAuth |
|
| 否 |
| OSS 分片上传失败时的自动重试策略 |
|
| 否 | localStorage | 断点续传配置。 |
|
| 否 |
| OSS 分片并发数 |
|
| 否 |
| OSS 分片大小(字节) |
|
| 否 |
| 网络请求超时(毫秒) |
|
| 否 | — | OSS 自定义域名 |
|
| 否 |
| STS 凭证刷新检查间隔(毫秒) |
RetryPolicy
字段 | 类型 | 默认值 | 说明 |
|
|
| 最大重试次数 |
uploader.upload(file, options?)
上传单个文件。返回 UploadTask:既是 Promise<UploadResult>,也带 .abort() 方法。
const task = uploader.upload(file, options);UploadOptions
参数 | 类型 | 说明 |
|
| 媒资元数据(标题、分类、标签等)。 |
|
| Web 标准取消信号。 |
|
| 进度回调, |
|
| 覆盖全局 partSize。 |
|
| 覆盖全局 parallel。 |
VodVideoMeta(视频元数据)
字段 | 类型 | 说明 |
|
| 视频标题 |
|
| 视频描述 |
|
| 分类 ID |
|
| 标签(逗号分隔) |
|
| 转码模板组 ID |
|
| 存储地址 |
|
| 封面 URL |
|
| 工作流 ID |
|
| 应用 ID |
|
| 用户自定义数据 |
VodImageMeta(图片元数据)
字段 | 类型 | 说明 |
|
| 图片标题 |
|
| 图片描述 |
|
| 图片类型 |
|
| 图片扩展名 |
|
| 标签 |
|
| 分类 ID |
|
| 存储地址 |
UploadResult(上传结果)
字段 | 类型 | 说明 |
|
| 视频 ID(视频上传时返回) |
|
| 图片 ID(图片上传时返回) |
|
| OSS ETag |
|
| OSS 请求 ID |
|
| 上传耗时(毫秒) |
|
| 上传任务唯一 ID(可用于售后排障) |
UploadTask
upload() 返回的对象同时是 Promise<UploadResult> 和带 .abort() 方法的对象。
type UploadTask = Promise<UploadResult> & {
abort(): void;
};链式调用后 .abort() 方法会丢失。task.then(fn) 返回的是普通 Promise。如需链式后仍可取消,请用 options.signal。
uploader.dispose()
释放资源:取消所有进行中的上传并清除内部状态。
uploader.dispose();调用后该 uploader 实例不可再使用。在 React/Vue 组件卸载时务必调用,避免内存泄漏。
凭证回调 getAuth
type GetAuth = (ctx: AuthContext) => Promise<AuthResult>;AuthContext
SDK 根据上传场景传入不同的 ctx:
ctx.kind | 触发场景 | ctx 额外字段 |
| 首次视频上传 |
|
| 续传时刷新凭证 |
|
| 图片上传 |
|
AuthResult
后端返回的 OpenAPI 原始 JSON,SDK 内部自动解码:
字段 | 类型 | 必填 | 说明 |
|
| 是 | base64 编码的 STS 凭证 |
|
| 是 | base64 编码的 OSS 上传地址 |
|
| create-video 必填 | 视频 ID |
|
| create-image 必填 | 图片 ID |
|
| 否 | 图片 URL(透传) |
getAuth 最简实现:
// 后端统一路由
createUploader({
getAuth: ctx => fetch('/api/vod/auth', {
method: 'POST',
body: JSON.stringify(ctx),
}).then(r => r.json()),
});refresh-video 失败自动降级:当续传凭证刷新失败时,SDK 自动 fallback 到 create-video 重新上传,对调用方完全透明。
错误处理
UploadError 结构
所有上传错误都是 UploadError 实例(继承自 Error):
import { UploadError } from 'aliyun-vod-upload-sdk';
class UploadError extends Error {
readonly code: string; // 结构化错误码,如 'UPLOAD.OSS.ACCESS_DENIED'
readonly message: string; // 错误描述
readonly suggestion: string; // 中文修复建议
readonly cause?: unknown; // 底层原始错误
readonly uploadTaskId?: string; // 上传任务 ID(可用于售后排障)
}用户取消上传不是 UploadError,而是标准的 DOMException(name === 'AbortError')。
错误码清单
错误码格式:UPLOAD.{层级}.{类型}
错误码 | 含义 | 修复建议 |
|
| 请检查 getAuth 回调是否正确返回了包含 UploadAuth、UploadAddress 的 JSON。确认后端服务可用且返回格式正确。 |
| OSS 权限错误(403) | 请检查上传凭证(STS Token)是否有效、是否已过期,以及 RAM 策略是否授权了 OSS 写入权限。 |
| Bucket 不存在 | 请检查上传地址中的 Bucket 名称是否正确,确认 Bucket 已在对应区域创建。 |
| 分片上传会话已失效 | 分片上传会话已失效(可能已超过 24 小时)。SDK 将自动重新上传,无需手动处理。 |
| OSS 未知错误 | 请查看浏览器控制台获取详细错误信息,或联系技术支持。 |
| 请求超时 | 请打开浏览器开发者工具的 Network 面板查看失败请求的详情,检查网络连接是否稳定,或尝试增大 timeout 配置值。 |
| 网络连接错误 | 请打开浏览器开发者工具的 Network 面板查看失败请求的状态码和响应,确认浏览器可以访问 OSS 服务地址,排查是否有代理或防火墙拦截。 |
| 浏览器离线 | 请确认网络状态,连接网络后重试上传。 |
| 文件大小为 0 | 文件大小为 0,无法上传空文件。请检查文件是否正确选择。 |
| Uploader 实例已销毁 | 请重新调用 createUploader() 创建新实例。 |
使用错误码常量
推荐使用 ErrorCode 常量而非字符串字面量:
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('凭证获取失败,请刷新页面重试');
break;
case ErrorCode.NETWORK_TIMEOUT:
case ErrorCode.NETWORK_ERROR:
showToast('网络异常,请检查网络后重试');
break;
case ErrorCode.OSS_ACCESS_DENIED:
showToast('上传权限不足,请联系管理员');
break;
default:
showToast(`上传失败:${e.suggestion || e.message}`);
}
}
}排障指引
每个 UploadError 都携带 uploadTaskId,这是整次上传的全局串联 ID。提交给阿里云售后时可快速定位问题:
catch (e) {
if (e instanceof UploadError) {
// 发给你的技术支持团队
const diagnostic = {
code: e.code,
message: e.message,
uploadTaskId: e.uploadTaskId,
suggestion: e.suggestion,
};
reportToSupport(diagnostic);
}
}UploadResult.uploadTaskId 同样可用于成功上传后的日志关联:
const result = await uploader.upload(file);
myLogger.info('上传成功', { videoId: result.videoId, uploadTaskId: result.uploadTaskId });