本文介紹如何使用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 |
|
| 儲存地址 |
|
| 封面 URL |
|
| 工作流程 ID |
|
| 應用 ID |
|
| 使用者自訂資料 |
VodImageMeta(圖片中繼資料)
欄位 | 類型 | 說明 |
|
| 圖片標題 |
|
| 圖片描述 |
|
| 圖片類型 |
|
| 圖片副檔名 |
|
| 標籤 |
|
| 分類別識別碼 |
|
| 儲存地址 |
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 });