使用JavaScript SDK上传文件

更新时间:
复制 MD 格式

本文介绍如何使用JavaScript SDK将媒体文件从本地上传至点播存储。

浏览器要求

浏览器

版本

Chrome

60+

Microsoft Edge

79+(Chromium内核)

Firefox

55+

Safari

11+

Android系统默认浏览器

60+

iOS系统默认浏览器

11+

Demo下载地址

快速集成

安装

npm install aliyun-vod-upload-sdk

SDK 提供 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

{ UploadAuth, UploadAddress, VideoId }

刷新视频上传凭证

RefreshUploadVideo

{ UploadAuth, UploadAddress, VideoId }

创建图片上传凭证

CreateUploadImage

{ UploadAuth, UploadAddress, ImageId }

后端返回的 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 自动:

  1. 将已完成的分片进度存入 localStorage

  2. 页面刷新后再次 upload(file),自动从上次中断处继续。

  3. 匹配规则:相同 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

GetAuth

凭证获取回调,详见凭证回调 getAuth

retry

RetryPolicy

{ count: 3 }

OSS 分片上传失败时的自动重试策略

checkpoint

false | { store?: CheckpointStore }

localStorage

断点续传配置。false 关闭;{ store } 注入自定义 store

parallel

number

4

OSS 分片并发数

partSize

number

1048576(1MB)

OSS 分片大小(字节)

timeout

number

60000

网络请求超时(毫秒)

cname

string

OSS 自定义域名

refreshSTSTokenInterval

number

300000(5min)

STS 凭证刷新检查间隔(毫秒)

RetryPolicy

字段

类型

默认值

说明

count

number

3

最大重试次数

uploader.upload(file, options?)

上传单个文件。返回 UploadTask:既是 Promise<UploadResult>,也带 .abort() 方法。

const task = uploader.upload(file, options);

UploadOptions

参数

类型

说明

meta

VodVideoMeta | VodImageMeta

媒资元数据(标题、分类、标签等)。

signal

AbortSignal

Web 标准取消信号。

onProgress

(percent, loaded, total) => void

进度回调,percent 范围 0..1。

partSize

number

覆盖全局 partSize。

parallel

number

覆盖全局 parallel。

VodVideoMeta(视频元数据)

字段

类型

说明

title

string

视频标题

description

string

视频描述

cateId

number

分类 ID

tags

string

标签(逗号分隔)

templateGroupId

string

转码模板组 ID

storageLocation

string

存储地址

coverUrl

string

封面 URL

workflowId

string

工作流 ID

appId

string

应用 ID

userData

Record<string, unknown>

用户自定义数据

VodImageMeta(图片元数据)

字段

类型

说明

title

string

图片标题

description

string

图片描述

imageType

'default' | 'cover' | 'watermark'

图片类型

imageExt

string

图片扩展名

tags

string

标签

cateId

number

分类 ID

storageLocation

string

存储地址

UploadResult(上传结果)

字段

类型

说明

videoId

string?

视频 ID(视频上传时返回)

imageId

string?

图片 ID(图片上传时返回)

etag

string

OSS ETag

requestId

string

OSS 请求 ID

durationMs

number

上传耗时(毫秒)

uploadTaskId

string

上传任务唯一 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 额外字段

'create-video'

首次视频上传

filemeta?

'refresh-video'

续传时刷新凭证

filevideoId

'create-image'

图片上传

filemeta?

AuthResult

后端返回的 OpenAPI 原始 JSON,SDK 内部自动解码:

字段

类型

必填

说明

UploadAuth

string

base64 编码的 STS 凭证

UploadAddress

string

base64 编码的 OSS 上传地址

VideoId

string

create-video 必填

视频 ID

ImageId

string

create-image 必填

图片 ID

ImageURL

string

图片 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,而是标准的 DOMExceptionname === 'AbortError')。

错误码清单

错误码格式:UPLOAD.{层级}.{类型}

错误码

含义

修复建议

UPLOAD.AUTH.GET_AUTH_FAILED

getAuth 回调抛错或返回结构非法

请检查 getAuth 回调是否正确返回了包含 UploadAuth、UploadAddress 的 JSON。确认后端服务可用且返回格式正确。

UPLOAD.OSS.ACCESS_DENIED

OSS 权限错误(403)

请检查上传凭证(STS Token)是否有效、是否已过期,以及 RAM 策略是否授权了 OSS 写入权限。

UPLOAD.OSS.NO_SUCH_BUCKET

Bucket 不存在

请检查上传地址中的 Bucket 名称是否正确,确认 Bucket 已在对应区域创建。

UPLOAD.OSS.NO_SUCH_UPLOAD

分片上传会话已失效

分片上传会话已失效(可能已超过 24 小时)。SDK 将自动重新上传,无需手动处理。

UPLOAD.OSS.UNKNOWN

OSS 未知错误

请查看浏览器控制台获取详细错误信息,或联系技术支持。

UPLOAD.NETWORK.TIMEOUT

请求超时

请打开浏览器开发者工具的 Network 面板查看失败请求的详情,检查网络连接是否稳定,或尝试增大 timeout 配置值。

UPLOAD.NETWORK.ERROR

网络连接错误

请打开浏览器开发者工具的 Network 面板查看失败请求的状态码和响应,确认浏览器可以访问 OSS 服务地址,排查是否有代理或防火墙拦截。

UPLOAD.NETWORK.OFFLINE

浏览器离线

请确认网络状态,连接网络后重试上传。

UPLOAD.FILE.EMPTY

文件大小为 0

文件大小为 0,无法上传空文件。请检查文件是否正确选择。

UPLOAD.INTERNAL.DISPOSED

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 });