本文介绍如何使用JavaScript SDK将媒体文件从本地上传至点播存储。
浏览器要求
浏览器 | 版本 |
Chrome | 66+ |
Microsoft Edge | 79+(Chromium内核) |
Firefox | 57+ |
Safari | 12.1+ |
Android系统默认浏览器 | 66+ |
iOS系统默认浏览器 | 12.2+ |
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.1.0/vod-upload.umd.js"></script>
<script>
const { createUploader, UploadError } = window.AliyunVodUpload;
</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);后端接口要求
在getAuth模式下,SDK不直接调用阿里云OpenAPI,而是通过getAuth回调委托你的后端获取上传凭证。后端需实现以下接口(对应VOD OpenAPI):
后端接口 | 对应 OpenAPI | 返回字段 |
创建视频上传凭证 | CreateUploadVideo |
|
刷新视频上传凭证 | RefreshUploadVideo |
|
创建图片上传凭证 | CreateUploadImage |
|
创建辅助媒资上传凭证 | CreateUploadAttachedMedia |
|
后端返回的JSON原样透传给SDK即可,SDK 内部自动完成base64解码和字段映射。
如果服务端仅提供STS临时凭证,可以使用STS直传模式getSTS:SDK 会在浏览器中直接签名调用上述VOD OpenAPI,无需实现这些接口。
基础功能
视频上传
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。create-attached-media—上传辅助媒资,需后端调用CreateUploadAttachedMedia。
图片上传
图片上传使用同一个 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);辅助媒资上传(水印 / 字幕 / 素材)
水印图、字幕文件(srt / ass / vtt)、素材(zip / 图片 / 视频片段)需调用CreateUploadAttachedMedia - 获取辅助媒资上传地址和凭证:在 meta里带上 businessType 即路由到该链路,其余用法与视频 / 图片完全一致(进度 / 取消 / 超时重试 / 埋点)。
// 字幕:.srt 不带 businessType 会被视频链路接走,生成一条类型错误的视频记录
const r1 = await uploader.upload(srtFile, { meta: { businessType: 'subtitle' } });
// 素材
const r2 = await uploader.upload(materialFile, { meta: { businessType: 'material' } });
// 水印图:走辅助媒资(产物与「图片链路 + imageType: 'watermark'」是两类不同资源)
const r3 = await uploader.upload(logoFile, {
meta: { businessType: 'watermark', mediaExt: 'png' },
});
console.log('mediaId:', r1.mediaId); // 三路统一字段:视频 = VideoId、图片 = ImageId、辅助媒资 = MediaIdbusinessType / mediaExt 等字段说明:
字段 | 说明 |
| 必填。取值原样透传给 VOD(文档值: |
| 可选。不传时 STS 模式从文件名扩展名派生( |
| 可选,与视频 |
两点行为差异:
不参与断点续传:视频点播没有该接口的Refresh 版本(凭证约 3000 秒有效,过期只能重新 Create 并拿到新
MediaId),所以 SDK 不为辅助媒资读写 checkpoint,中断后重传是全新一次上传。png 水印有两条官方路径:图片链路(
VodImageMeta.imageType = 'watermark')与辅助媒资链路(businessType: 'watermark')产物是两类不同资源,SDK不会自动决定;传businessType才走辅助媒资,不传就按文件类型走图片链路。
uploadAuth 模式下getAuth会收到ctx.kind === 'create-attached-media',把ctx.meta.businessType转给后端即可;STS模式(getSTS)下SDK自动完成签名调用,getSTS无需任何改动。
上传进度
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),
]),
});AbortSignal.timeout需要Chrome103+,AbortSignal.any需要Chrome116+。SDK本身兼容到Chrome60,若要支持更老的浏览器,请使用AbortController+setTimeout组合。
React / Vue 集成
React Hook示例
import { useEffect, useRef, useState } from 'react';
import { createUploader } from 'aliyun-vod-upload-sdk';
function useUploader(getAuth) {
const uploaderRef = useRef(null);
// 在 effect 里创建:避免每次渲染都 createUploader;
// StrictMode 下 effect 会「执行 → 清理 → 再执行」,清理时 dispose 的实例会被重新创建
useEffect(() => {
const uploader = createUploader({ getAuth });
uploaderRef.current = uploader;
return () => {
uploader.dispose(); // 组件卸载时释放,同时取消进行中的上传
uploaderRef.current = null;
};
}, []);
return uploaderRef;
}
function UploadButton({ file }) {
const uploaderRef = useUploader(myGetAuth);
const ctrlRef = useRef(null);
const [progress, setProgress] = useState(0);
const handleUpload = () => {
const uploader = uploaderRef.current;
if (!uploader) return;
const ctrl = new AbortController();
ctrlRef.current = ctrl;
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>
<button onClick={() => ctrlRef.current?.abort()}>取消</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。 |
|
| 与 | — | STS临时凭证回调(前端直传模式)。详见STS直传模式getSTS。 |
|
|
| — | VOD服务地域(如 |
|
| 否 |
| OSS分片上传失败时的自动重试策略。 |
|
| 否 | localStorage | 断点续传配置。 |
|
| 否 |
| OSS分片并发数。 |
|
| 否 |
| OSS分片大小(字节)。 |
|
| 否 |
| 网络请求超时(毫秒)。同时作用于OSS上传与STS模式的VOD OpenAPI调用。 |
|
| 否 | — | OSS自定义域名。 |
|
| 否 |
| 开启后每个分片计算并携带 |
|
| 否 |
| 两次刷新上传凭证之间的最小间隔(毫秒),仅视频上传生效(通过 |
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。 |
|
| 用户自定义数据。 |
|
| STS模式上传 |
|
| STS模式:转码模式(VOD |
VodImageMeta(图片元数据)
字段 | 类型 | 说明 |
|
| 图片标题 |
|
| 图片描述 |
|
| 图片类型 |
|
| 图片扩展名 |
|
| 标签 |
|
| 分类 ID |
|
| 存储地址 |
VodAttachedMediaMeta(辅助媒资元数据)
字段 | 类型 | 说明 |
|
| 必填。 |
|
| 扩展名;不传时STS模式从文件名派生,都拿不到则不发送该参数。 |
|
| 标题(辅助媒资可选,SDK不派生)。 |
|
| STS模式上传 |
|
| 描述。 |
|
| 标签(逗号分隔)。 |
|
| 分类ID,逗号分隔、最多 5 个(与视频的 |
|
| 存储地址。 |
|
| 应用ID。 |
|
| 用户自定义数据。 |
UploadResult(上传结果)
字段 | 类型 | 说明 |
|
| 视频ID(视频上传时返回)。 |
|
| 图片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。 |
|
| create-attached-media 必填 | 辅助媒资ID。 |
|
| 否 | 图片URL(透传)。 |
getAuth 最简实现:
// 后端统一路由。注意 ctx.file 是 File / Blob,JSON.stringify 会把它变成 {},
// 需要把后端用到的文件信息(如 fileName)单独取出来
createUploader({
getAuth: ({ file, ...rest }) => fetch('/api/vod/auth', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...rest, fileName: file.name, fileSize: file.size }),
}).then(r => r.json()),
});refresh-video 失败自动降级:当续传凭证刷新失败时,SDK 自动 fallback 到 create-video 重新上传,对调用方完全透明。
STS直传模式getSTS
getAuth模式需要由服务端调用VOD OpenAPI获取UploadAuth/UploadAddress。如果你的服务端仅提供STS 临时凭证,可以使用getSTS模式:SDK会在浏览器中使用该凭证签名调用VOD OpenAPI,获取上传凭证后再上传文件。
type GetSTS = () => Promise<STSCredential>;
interface STSCredential {
accessKeyId: string;
accessKeySecret: string;
securityToken: string;
}createUploader({
region: 'cn-shanghai', // getSTS 模式必填,决定 vod.<region>.aliyuncs.com 端点
getSTS: async () => {
const { AccessKeyId, AccessKeySecret, SecurityToken } = await fetch('/api/sts').then(r => r.json());
return { accessKeyId: AccessKeyId, accessKeySecret: AccessKeySecret, securityToken: SecurityToken };
},
});要点:
必须运行在HTTPS或localhost:STS模式的V3签名依赖Web Crypto
crypto.subtle,普通HTTP页面不可用;getAuth模式不受此限制。与
getAuth互斥:两者只能传一个。无参数、不含
expiration:SDK 不主动判断STS凭证是否过期;VOD返回InvalidSecurityToken.Expired时自动重新调一次getSTS()并重试一次。不做缓存、不做并发去重:多个并发
upload()会各自调getSTS(),需要缓存请自己实现。Title/FileName由SDK从文件派生:FileName=file.name(Blob时用meta.fileName),Title=meta.title ?? FileName;派生值截断到 128 字符,显式传的meta.title不截断。FileSize由SDK填:每次CreateUploadVideo无条件带上FileSize=file.size(v1要用户自己在userData里传,漏传VOD侧就缺大小)。VOD调用失败时错误码为
UPLOAD.VOD.CREATE_FAILED/UPLOAD.VOD.REFRESH_FAILED,suggestion里带VOD的排障链接;VOD原始Message不会进UploadError(可能含明文凭证),只会脱敏后上报到埋点。超时与重试复用
timeout/retry.count配置;4xx/5xx都不重试(只有上面那一处过期码例外)。
错误处理
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。确认后端服务可用且返回格式正确。 |
|
| STS模式下getSTS回调获取临时凭证失败。请检查getSTS 实现能否正常调用你的STS服务(网络连通性、鉴权、跨域配置),以及返回的 accessKeyId/ accessKeySecret/ securityToken是否完整。 |
| STS模式下 | STS模式下SDK调用VOD接口创建上传凭证失败。请检查 createUploader传入的region 是否与账号所属地域一致,getSTS返回的临时凭证是否具备VOD权限(CreateUploadVideo/ CreateUploadImage/ CreateUploadAttachedMedia),以及meta中的媒资参数是否合法。 |
| STS模式下 | STS模式下SDK调用VOD接口续期上传凭证失败。请检查 getSTS返回的临时凭证是否有效、是否具备 RefreshUploadVideo权限,以及videoId对应的媒资是否仍然存在。 |
| 配置非法:① | 配置无效。请确认 createUploader的getAuth与 getSTS只传其中一个;STS 模式(传getSTS)时必须提供非空的region,且与账号所属地域一致(如cn-shanghai)。上传辅助媒资时 meta.businessType必须是非空字符串(如watermark / subtitle/material)。 |
| OSS 权限错误(403) | OSS 访问被拒绝。请检查上传凭证(STS Token)是否有效、是否已过期,以及RAM 策略是否授权了OSS写入权限。 |
| Bucket不存在 | OSS Bucket不存在。请检查上传地址中的Bucket名称是否正确,确认Bucket已在对应区域创建。 |
| 分片上传会话已失效 | 分片上传会话已失效(可能已超过24小时)。SDK将自动重新上传,无需手动处理。 |
| OSS未知错误 | OSS上传遇到未知错误。请查看浏览器控制台获取详细错误信息。 |
| 请求超时 | 网络请求超时。请打开浏览器开发者工具的Network面板查看失败请求的详情,检查网络连接是否稳定,或尝试增大timeout配置值。 |
| 网络连接错误 | 网络连接错误。请打开浏览器开发者工具的Network面板查看失败请求的状态码和响应,确认浏览器可以访问OSS服务地址,排查是否有代理或防火墙拦截。 |
| 浏览器离线 | 浏览器检测到当前处于离线状态。请打开浏览器开发者工具的 Network 面板确认网络状态,连接网络后重试上传。 |
| 文件大小为0 | 文件大小为0,无法上传空文件。请检查文件是否正确选择。 |
| Uploader实例已销毁 | 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 });