接入視頻剪輯Web SDK
視頻點播提供專業(yè)在線的視頻剪輯能力,針對自動化、智能化剪輯以及多人協(xié)作視頻制作需求,您可以基于時間線進行云剪輯。通過閱讀本文,您可以了解如何接入視頻剪輯Web SDK。
使用說明
本文以引入4.10.0版本的視頻剪輯Web SDK為例進行說明。
操作步驟
引入視頻剪輯Web SDK。
在項目前端頁面文件中的
<head>
標簽處引入視頻剪輯Web SDK的CSS文件,如下所示:<head> <link rel="stylesheet" > </head>
在
<body>
標簽處添加一個用以掛載剪輯界面的<div>
節(jié)點,并在<body>
標簽?zāi)┪蔡砑右隬eb SDK的JS文件,同時添加一個用以調(diào)用Web SDK的<script>
節(jié)點。<body> <div id="aliyun-video-editor" style="height:700px"></div> // 您可以根據(jù)需要改變 container 高度 <script src="https://g.alicdn.com/thor-server/video-editing-websdk/4.10.0/index.js"></script> <script> // 調(diào)用 SDK 的代碼放在這里 </script> </body>
初始化視頻剪輯Web SDK。
window.AliyunVideoEditor.init(config);
參數(shù)
config
為對象,對象中的屬性說明請參見config屬性說明。初始化函數(shù)
init()
示例請參見init()示例代碼。
config屬性說明
參數(shù) | 類型 | 必填 | 描述 | 引入版本 |
locale | string | 否 | 界面語言,取值:
| 3.0.0 |
container | Element | 是 | Web SDK生成界面掛載的DOM節(jié)點。 | 3.0.0 |
defaultAspectRatio | 否 | 默認的視頻預(yù)覽比例,默認為16∶9。 | 3.4.0 | |
defaultSubtitleText | string | 否 | 默認的字幕內(nèi)容,不超過20個字符,默認為“阿里云剪輯”。 | 3.6.0 |
useDynamicSrc | boolean | 否 | 是否動態(tài)獲取資源信息。 | 3.0.0 |
getDynamicSrc | (mediaId: string, mediaType: 'video' | 'audio' | 'image' | 'font', mediaOrigin?:'private' | 'public', inputUrl?: string) => Promise<string>; | 否 | 動態(tài)獲取資源信息,必填與否與參數(shù)useDynamicSrc一致。返回的Promise對象需要resolve資源新的信息。 | 3.10.0 |
getEditingProjectMaterials | () => Promise<Media[]>; | 是 | 獲取工程關(guān)聯(lián)的素材。返回的Promise對象需要resolve所有素材類型的數(shù)組。 | 3.0.0 |
searchMedia | (mediaType: 'video' | 'audio' | 'image') => Promise<Media[]>; | 是 | 資源庫導(dǎo)入素材按鈕相應(yīng)函數(shù)。單擊導(dǎo)入素材后會搜索媒資信息,將媒資庫媒資導(dǎo)入到資源庫中。返回的Promise對象需要resolve新增素材的數(shù)組。 重要 您需要調(diào)用AddEditingProjectMaterials接口將新增的素材與工程關(guān)聯(lián)起來。 | 3.0.0 |
deleteEditingProjectMaterials | (mediaId: string, mediaType: 'video' | 'audio' | 'image') => Promise<void>; | 是 | 解綁工程與素材。返回的Promise對象需要resolve。 | 3.0.0 |
getStickerCategories | () => Promise<StickerCategory[]>; | 否 | 獲取貼紙分類,如果不傳,則不分類。返回的Promise對象需要resolve貼紙的分類數(shù)組。 | 3.0.0 |
getStickers | (config: {categoryId?: string; page: number; size: number}) => Promise<StickerResponse>; | 否 | 獲取貼紙,如果貼紙沒有分類,則categoryId 為空。返回的Promise對象需要resolve貼紙的總量和貼紙數(shù)組。 | 3.0.0 |
getEditingProject | () => Promise<{timeline?: Timeline; projectId?: string; modifiedTime?: string}>; | 是 | 獲取工程的時間線。返回的Promise對象需要resolve時間線Timeline數(shù)據(jù)、項目ID和最后修改時間。 | 3.0.0 |
updateEditingProject | (data: {coverUrl: string; duration: number; timeline: Timeline; isAuto: boolean}) => Promise<{projectId: string}>; | 是 | 保存工程的時間線,參數(shù)依次為:工程的封面圖地址、時長(單位:秒)、Timeline數(shù)據(jù)和是否自動保存(當前每分鐘自動保存1次)。返回的Promise對象需要resolve項目ID。 | 3.0.0 |
produceEditingProjectVideo | (data:{ coverUrl: string; duration: number; aspectRatio: PlayerAspectRatio; timeline: Timeline; recommend: IProduceRecommend; }) => Promise<void>; | 是 | 生成視頻,參數(shù)依次為:工程的封面圖地址、時長(單位:秒)、視頻比例、Timeline數(shù)據(jù)和recommend (視頻合成的分辨率、碼率的推薦數(shù)據(jù))。返回的Promise對象需要resolve。 | 4.4.0 |
customTexts | {importButton?:string;updateButton?:string;produceButton?:string;backButton?:string;logoUrl?:string;} | 否 | 自定義部分文案,參數(shù)依次對應(yīng)視頻剪輯界面的導(dǎo)入素材、保存、導(dǎo)出視頻、返回按鈕的文案和左上角Logo。 | 3.7.0 |
getPreviewWaterMarks | () => Promise<Array<{ url?: string; mediaId?:string; width?: number; height?: number; x?: number; y?: number; xPlusWidth?: number; yPlusHeight?: number; opacity?: number; }>>; | 否 | 預(yù)覽區(qū)添加水印,防止截屏(合成時沒有水印),參數(shù)如下所示:
| 4.3.5 |
exportVideoClipsSplit | (data: Array<{ coverUrl: string; duration: number; aspectRatio: PlayerAspectRatio; timeline: Timeline; recommend?: IProduceRecommend; }>) => Promise<void>; | 否 | 將選中Timeline的多個獨立片段拆分為不同Timeline并導(dǎo)出,參數(shù)依次為:默認封面圖、導(dǎo)出Timeline的時長、導(dǎo)出比例、導(dǎo)出的Timeline片段、視頻合成的分辨率或碼率的推薦數(shù)據(jù)。 | 4.4.0 |
exportVideoClipsMerge | (data: { coverUrl: string; duration: number; aspectRatio: PlayerAspectRatio; timeline: Timeline; recommend?:IProduceRecommend; }) => Promise<void>; | 否 | 將選中Timeline同一軌道的多個獨立片段合成為一個Timeline并導(dǎo)出,參數(shù)依次為:默認封面圖、導(dǎo)出Timeline的時長、導(dǎo)出比例、導(dǎo)出的Timeline片段、視頻合成的分辨率或碼率的推薦數(shù)據(jù)。 | 4.4.0 |
disableMediaMarks | boolean | 否 | 當前VOD云剪輯暫時還不支持媒資標記,如果需要隱藏媒資標記相關(guān)功能,請設(shè)置 | 4.8.6 |
數(shù)據(jù)結(jié)構(gòu)說明:
PlayerAspectRatio
enum PlayerAspectRatio { w1h1 = '1:1', w2h1 = '2:1', w4h3 = '4:3', w3h4 = '3:4', w9h16 = '9:16', w16h9 = '16:9', w21h9 = '21:9', }
VoiceConfig
interface VoiceConfig { volume: number; // 音量,取值0~100,默認值50 speech_rate: number; // 語速,取值范圍:-500~500,默認值:0 pitch_rate: number; // 語調(diào),取值范圍:-500~500,默認值:0 format?: string; // 輸出文件格式,支持:PCM/WAV/MP3 }
Media
interface Media { mediaId: string; mediaType: 'video' | 'audio' | 'image'; video?: { // materialType 為'video'時須傳入此項 title: string; coverUrl: string; duration: number; width?: number; // 視頻源的寬度,用于合成的推薦分辨率,不傳入則不會有推薦的分辨率 height?: number; // 視頻源的高度,用于合成的推薦分辨率,不傳入則不會有推薦的分辨率 bitrate?: number; // 視頻源的碼率,用于合成的推薦碼率,不傳入則不會有推薦的碼率 src?: string; // 當 useDynamicSrc 為 true 時,src 可以不傳 snapshots: string[]; sprites: string[]; spriteConfig: { num: string; // 雪碧圖中有多少個小圖 lines: string; // 行數(shù) cols: string; // 列數(shù) cellWidth?: string; // 單個小圖的寬度,可不傳 cellHeight?: string; // 單個小圖的高度,可不傳 }; }; audio?: { // materialType 為'audio'時須傳入此項 title: string; duration: number; src?: string; // 當 useDynamicSrc 為 true 時,src 可以不傳 coverUrl?: string; // 音頻封面 }; image?: { // materialType 為'image'時須傳入此項 title: string; src?: string; // 當 useDynamicSrc 為 true 時,src 可以不傳 coverUrl?: string; // 圖片預(yù)覽圖,在列表及軌道區(qū)會優(yōu)先展示該字段,如果沒有,則展示 src 字段,如果兩者都沒有則無法在軌道區(qū)預(yù)覽 width?: number; // 圖片的寬度,用于合成的推薦分辨率,不傳入則不會有推薦的分辨率 height?: number; // 圖片的高度,用于合成的推薦分辨率,不傳入則不會有推薦的分辨率 }; }
StickerCategory
interface StickerCategory { id: string; // 分類的 id name: string; // 分類的名稱,調(diào)用者自行切換語言 }
StickerResponse
interface Sticker { mediaId: string; src: string; } interface StickerResponse { total: number; stickers: Sticker[]; }
IProduceRecommend
interface IProduceRecommend { width?: number; height?: number; bitrate?: number; }
init()示例代碼
Web SDK只負責界面交互,不會發(fā)起請求,您需要通過Web SDK調(diào)用請求邏輯。請求本身應(yīng)該先發(fā)送給您自己的服務(wù)端,您自己的服務(wù)端再根據(jù)AccessKey信息(AccessKey ID和AccessKey Secret)轉(zhuǎn)發(fā)給相關(guān)的阿里云OpenAPI。
// 注意,WebSDK 本身并不提供 request 這個方法,這里僅作為示例,您可以使用您喜歡的網(wǎng)絡(luò)請求庫,如 axios 等
window.AliyunVideoEditor.init({
container: document.getElementById('aliyun-video-editor'),
locale: 'zh-CN',
useDynamicSrc: true, // 媒資庫默認情況下播放地址會過期,所以需要動態(tài)獲取
getDynamicSrc: (mediaId, mediaType) => new Promise((resolve, reject) => {
request('GetPlayInfo', { // http://bestwisewords.com/document_detail/436555.html
MediaId: mediaId
}).then((res) => {
if (res.code === '200') {
// 注意,這里僅作為示例,實際中建議做好錯誤處理,避免如 FileInfoList 為空數(shù)組時報錯等異常情況
resolve(res.data.MediaInfo.FileInfoList[0].FileBasicInfo.FileUrl);
} else {
reject();
}
});
}),
getEditingProjectMaterials: () => {
if (projectId) { // projectId 由調(diào)用方自己保存
return request('GetEditingProjectMaterials', { // http://bestwisewords.com/document_detail/454953.html
ProjectId: projectId
}).then((res) => {
const data = res.data.MediaInfos;
return transMediaList(data); // 需要做一些數(shù)據(jù)變換,具體參考后文
});
}
return Promise.resolve([]);
},
searchMedia: (mediaType) => { // mediaType 為用戶當前所在的素材 tab,可能為 video | audio | image,您可以根據(jù)這個參數(shù)對應(yīng)地展示同類型的可添加素材
return new Promise((resolve) => {
// 調(diào)用方需要自己實現(xiàn)展示媒資、選擇媒資添加的界面,這里的 callDialog 只是一種示例,WebSDK 本身并不提供
// 關(guān)于展示媒資,請參考:http://bestwisewords.com/document_detail/436573.html
callDialog({
onSubmit: async (materials) => {
if (!projectId) { // 如果沒有 projectId,需要先創(chuàng)建工程,如果能確保有 projectId,則不需要該步
const addRes = await request('AddEditingProject', { // http://bestwisewords.com/document_detail/454948.html
Title: 'xxxx',
});
projectId = addRes.data.Project.ProjectId;
}
// 組裝數(shù)據(jù)
const valueObj = {};
materials.forEach(({ mediaType, mediaId }) => {
if (!valueObj[mediaType]) {
valueObj[mediaType] = mediaId;
} else {
valueObj[mediaType] += mediaId;
}
})
const res = await request('AddEditingProjectMaterials', { // http://bestwisewords.com/zh/vod/developer-reference/api-vod-2017-03-21-addeditingprojectmaterials?spm=a2c4g.11186623.0.i10
ProjectId: projectId,
MaterialMaps: valueObj,
});
if (res.code === '200') {
return resolve(transMediaList(res.data.MediaInfos));
}
resolve([]);
}
});
});
},
deleteEditingProjectMaterials: async (mediaId, mediaType) => {
const res = await request('DeleteEditingProjectMaterials', { // http://bestwisewords.com/zh/vod/developer-reference/api-vod-2017-03-21-deleteeditingprojectmaterials?spm=a2c4g.11186623.0.i18
ProjectId: projectId,
MaterialType: mediaType,
MaterialIds: mediaId
});
if (res.code === '200') return Promise.resolve();
return Promise.reject();
},
getEditingProject: async () => {
if (projectId) {
const res = await request('GetEditingProject', { // http://bestwisewords.com/document_detail/454952.html
ProjectId: projectId
});
const timelineString = res.data.Project.Timeline;
return {
projectId,
timeline: timelineString ? JSON.parse(timelineString) : undefined,
modifiedTime: res.data.Project.ModifiedTime,
title:res.data.Project.Title // 項目標題
};
}
return {};
},
updateEditingProject: ({ coverUrl, duration, timeline, isAuto }) => new Promise((resolve, reject) => {
request('UpdateEditingProject', { // http://bestwisewords.com/document_detail/454949.html
ProjectId: projectId,
CoverURL: coverUrl,
Duration: duration,
Timeline: JSON.stringify(timeline)
}).then((res) => {
if (res.code === '200') {
// WebSDK 本身會進行自動保存,isAuto 則是告訴調(diào)用方這次保存是否自動保存,調(diào)用方可以控制只在手動保存時才展示保存成功的提示
!isAuto && Message.success('保存成功');
resolve();
} else {
reject();
}
});
}),
produceEditingProjectVideo: ({ coverUrl, duration = 0, aspectRatio, timeline, recommend }) => {
return new Promise((resolve) => {
callDialog({ // 調(diào)用方需要自己實現(xiàn)提交合成任務(wù)的界面,這里的 callDialog 只是一種示例
onSubmit: async ({ fileName, format, bitrate, description }) => { // 假設(shè)提交合成任務(wù)的界面讓你獲得了這些數(shù)據(jù)
// 先根據(jù) fileName 和 format 拼接出存儲的 mediaURL
const mediaURL = `http://bucketName.oss-cn-hangzhou.aliyuncs.com/${fileName}.${format}`;
// 根據(jù) WebSDK 傳入的預(yù)覽比例來決定合成的寬高
const width = aspectRatio === '16:9' ? 640 : 360;
const height = aspectRatio === '16:9' ? 360 : 640;
// 若視頻、圖片素材傳入的長寬、碼率等數(shù)據(jù),那么該函數(shù)返回的數(shù)據(jù)中的 recommend 就會包含了根據(jù)所使用的視頻、圖片計算得到的推薦的分辨率和碼率
// recommend 數(shù)據(jù)結(jié)構(gòu)可以查看 IProduceRecommend
// 你可以在提交界面上展示推薦數(shù)據(jù)或者直接使用在提交接口的參數(shù)里
const res = await request('ProduceEditingProjectVideo', { // http://bestwisewords.com/document_detail/454947.html
OutputMediaConfig: JSON.stringify({
mediaURL,
bitrate: recommend.bitrate || bitrate,
width: recommend.width || width,
height: recommend.height || height
}),
OutputMediaTarget: 'oss-object',
ProjectMetadata: JSON.stringify({ Description: description }),
ProjectId: projectId,
Timeline: JSON.stringify(timeline)
});
if (res.code === '200') {
Message.success('生成視頻成功');
}
resolve();
}
});
});
}
});
/**
* 將服務(wù)端的素材信息轉(zhuǎn)換成 WebSDK 需要的格式
*/
function transMediaList(data) {
if (!data) return [];
if (Array.isArray(data)) {
return data.map((item) => {
const basicInfo = item.MediaBasicInfo;
const fileBasicInfo = item.FileInfoList[0].FileBasicInfo;
const mediaId = basicInfo.MediaId;
const result = {
mediaId
};
const mediaType = basicInfo.MediaType
result.mediaType = mediaType;
if (mediaType === 'video') {
result.video = {
title: fileBasicInfo.FileName,
duration: Number(fileBasicInfo.Duration),
// 源視頻的寬高、碼率等數(shù)據(jù),用于推薦合成數(shù)據(jù),不傳入或是0時無推薦數(shù)據(jù)
width: Number(fileBasicInfo.Width) || 0,
height: Number(fileBasicInfo.Height) || 0,
bitrate: Number(fileBasicInfo.Bitrate) || 0,
coverUrl: basicInfo.CoverURL
};
const spriteImages = basicInfo.SpriteImages
if (spriteImages) {
try {
const spriteArr = JSON.parse(spriteImages);
const sprite = spriteArr[0];
const config = JSON.parse(sprite.Config);
result.video.spriteConfig = {
num: config.Num,
lines: config.SpriteSnapshotConfig?.Lines,
cols: config.SpriteSnapshotConfig?.Columns,
cellWidth: config.SpriteSnapshotConfig?.CellWidth,
cellHeight: config.SpriteSnapshotConfig?.CellHeight
};
result.video.sprites = sprite.SnapshotUrlList;
} catch (e) {
console.log(e);
}
}
} else if (mediaType === 'audio') {
result.audio = {
title: fileBasicInfo.FileName,
duration: Number(fileBasicInfo.Duration),
coverURL: '' // 您可以給音頻文件一個默認的封面圖
}
} else if (mediaType === 'image') {
result.image = {
title: fileBasicInfo.FileName,
coverUrl: fileBasicInfo.FileUrl,
// 圖片的寬高等數(shù)據(jù),用于推薦合成數(shù)據(jù),不傳入或是0時無推薦數(shù)據(jù)
width: Number(fileBasicInfo.Width) || 0,
height: Number(fileBasicInfo.Height) || 0,
}
}
return result;
});
} else {
return [data];
}
}