智能生产制作提供专业在线的视频剪辑能力,针对自动化、智能化剪辑以及多人协作视频制作需求,您可以基于时间线进行云剪辑。通过阅读本文,您可以了解如何接入视频剪辑Web SDK。
使用说明
本文中引入的视频剪辑Web SDK的版本号5.2.2(仅供参考),从5.0.0开始,你需要申请License授权才能使用剪辑Web SDK。获取最新的版本信息,请参见视频剪辑工程——帮助信息。
License授权方式有2种:
方式1(推荐):购买企业标准版(售价3万元,支持进行官网价5万元的IMS任意功能消费抵扣)及以上订阅资源包,会赠送webSDK License,购买说明请参见:计费概述;
方式2:其他订阅方式,您可以单独购买webSDK license,定价2万/年,可工单联系官方获取购买链接;
操作步骤
引入视频剪辑Web SDK。
在项目前端页面文件中的
<head>
标签处引入视频剪辑Web SDK的CSS文件,如下所示:<head> <link rel="stylesheet" href="https://g.alicdn.com/thor-server/video-editing-websdk/5.2.2/index.css"> </head>
在
<body>
标签处添加一个用以挂载剪辑界面的<div>
节点,并在<body>
标签末尾添加引入Web SDK的JS文件,同时添加一个用以调用Web SDK的<script>
节点。<body> <div id="aliyun-video-editor" style="height:700px"></div> // 您可以根据需要改变 container 高度 <script src="https://g.alicdn.com/thor-server/video-editing-websdk/5.2.2/index.js"></script> <script> // 调用 SDK 的代码放在这里 </script> </body>
初始化视频剪辑Web SDK。
window.AliyunVideoEditor.init(config);
参数
config
为对象,对象中的属性说明请参见config属性说明。初始化函数
init()
示例请参见init()示例代码。
config属性说明
config参数
参数 | 类型 | 必填 | 描述 | 引入版本 |
locale | string | 否 | 界面语言,取值:
| 3.0.0 |
container | Element | 是 | Web SDK生成界面挂载的DOM节点。 | 3.0.0 |
defaultAspectRatio | 否 | 默认的视频预览比例,默认为16∶9。 | 3.4.0 | |
defaultSubtitleText | string | 否 | 默认的字幕内容,不超过20个字符,默认为“阿里云剪辑”。 | 3.6.0 |
useDynamicSrc | boolean | 否 | 是否动态获取资源信息。 | 3.0.0 |
getDynamicSrc | (mediaId: string, mediaType: 'video' | 'audio' | 'image' | 'font', mediaOrigin?:'private' | 'public', inputUrl?: string) => Promise<string>; | 否 | 动态获取资源信息,必填与否与参数useDynamicSrc一致。返回的Promise对象需要resolve资源新的信息。 | 3.10.0 |
getEditingProjectMaterials | () => Promise<InputMedia[]>; | 是 | 获取工程关联的素材。返回的Promise对象需要resolve所有素材类型的数组。 | 3.0.0 |
searchMedia | (mediaType: 'video' | 'audio' | 'image') => Promise<InputMedia[]>; | 是 | 资源库导入素材按钮相应函数。单击导入素材后会搜索媒资信息,将媒资库媒资导入到资源库中。返回的Promise对象需要resolve新增素材的数组。 重要 您需要调用AddEditingProjectMaterials将新增的素材与工程关联起来。 | 3.0.0 |
deleteEditingProjectMaterials | (mediaId: string, mediaType: 'video' | 'audio' | 'image') => Promise<void>; | 是 | 解绑工程与素材。返回的Promise对象需要resolve。 | 3.0.0 |
submitASRJob | (mediaId: string, startTime: string, duration: string) => Promise<ASRResult[]>; | 否 | 提交智能识别字幕任务。返回的Promise对象需要resolve识别的结果ASRResult数组。 | 3.1.0 推荐使用AsrConfig,使用AsrConfig会覆盖submitASRJob方法。 |
submitAudioProduceJob | (text: string, voice: string, voiceConfig?: VoiceConfig) => Promise<InputMedia>; | 否 | 提交文字转语音任务,参数依次为:字幕内容、语音效果voice值和语音配置。返回的Promise对象需要resolve生成语音的数据。 | 4.3.5 推荐使用TTSConfig,使用TTSConfig会覆盖submitAudioProduceJob方法。 |
licenseConfig | 是 | WebSDK的使用需要进行license配置。只有配置了license,才能在线上环境中使用WebSDK,如果没有配置license,只能在localhost域名下使用,在没有配置license的情况下,在localhost使用会出现水印,而在在线上环境中进行预览时会出现黑屏情况。 | 5.0.1 | |
dynamicSrcQps | number | 否 | 限制dynamicSrc的请求频率。 | 4.13.0 |
getTimelineMaterials | (params: TimelineMaterial[]) => Promise<InputMedia[]> | 否 | 获取timeline中的素材媒资信息,以用于获取在getEditingProjectMaterials中未注册的媒资,例如:第三方媒资。 | 4.13.4 |
asrConfig | 否 | 提交智能字幕任务的配置。 | 4.13.0 | |
ttsConfig | 否 | 提交智能配音任务的配置。 | 5.0.1 | |
disableAutoJobModal | boolean | 否 | 关闭项目出现AI任务时自动打开的弹窗。 | 5.0.1 |
disableGreenMatting | boolean | 否 | 关闭绿幕抠图入口。 | 4.13.0 |
disableRealMatting | boolean | 否 | 关闭实景抠图入口。 | 4.13.0 |
disableDenoise | boolean | 否 | 关闭降噪入口。 | 4.13.0 |
audioWaveRenderDisabled | boolean | 否 | 关闭波形图渲染。 | 4.13.0 |
publicMaterials | 否 | 公共素材库配置。 | 4.13.0 | |
subtitleConfig | 否 | 字幕背景渐变等配置。 | 4.13.0 | |
getStickerCategories | () => Promise<StickerCategory[]>; | 否 | 获取贴纸分类,如果不传,则不分类。返回的Promise对象需要resolve贴纸的分类数组。 | 3.0.0 |
getStickers | (config: {categoryId?: string; page: number; size: number}) => Promise<StickerResponse>; | 否 | 获取贴纸,如果贴纸没有分类,则categoryId 为空。返回的Promise对象需要resolve贴纸的总量和贴纸数组。 | 3.0.0 |
getEditingProject | () => Promise<{timeline?: Timeline; projectId?: string; modifiedTime?: string}>; | 是 | 获取工程的时间线。返回的Promise对象需要resolve时间线Timeline数据、项目ID和最后修改时间。 | 3.0.0 |
updateEditingProject | (data: {coverUrl: string; duration: number; timeline: Timeline; isAuto: boolean}) => Promise<{projectId: string}>; | 是 | 保存工程的时间线,参数依次为:工程的封面图地址、时长(单位:秒)、Timeline数据和是否自动保存(当前每分钟自动保存1次)。返回的Promise对象需要resolve项目ID。 | 3.0.0 |
produceEditingProjectVideo | (data:{ coverUrl: string; duration: number; aspectRatio: PlayerAspectRatio; mediaMarks: MediaMark[]; timeline: Timeline; recommend: IProduceRecommend; }) => Promise<void>; | 是 | 生成视频,参数依次为:工程的封面图地址、时长(单位:秒)、视频比例、媒资标记、Timeline数据和recommend (视频合成的分辨率、码率的推荐数据)。返回的Promise对象需要resolve。 | 4.4.0 |
customTexts | {importButton?:string;updateButton?:string;produceButton?:string;backButton?:string;logoUrl?:string;} | 否 | 自定义部分文案,参数依次对应视频剪辑界面的导入素材、保存、导出视频、返回按钮的文案和左上角Logo。 | 3.7.0 |
customFontList | Array<string | CustomFontItem>; | 否 | 自定义字体类型。 | 3.10.0 |
customVoiceGroups | 否 | 自定义语音选项。 | 4.3.5 | |
getPreviewWaterMarks | () => Promise<Array<{ url?: string; mediaId?:string; width?: number; height?: number; x?: number; y?: number; xPlusWidth?: number; yPlusHeight?: number; opacity?: number; }>>; | 否 | 预览区添加水印,防止截屏(合成时没有水印),参数如下所示:
| 4.3.5 |
exportVideoClipsSplit | (data: Array<{ coverUrl: string; duration: number; aspectRatio: PlayerAspectRatio; mediaMarks: MediaMark[]; timeline: Timeline; recommend?: IProduceRecommend; }>) => Promise<void>; | 否 | 将选中Timeline的多个独立片段拆分为不同Timeline并导出,参数依次为:默认封面图、导出Timeline的时长、导出比例、媒资标记、导出的Timeline片段、视频合成的分辨率或码率的推荐数据。 | 4.4.0 |
exportFromMediaMarks | (data: Array<{coverUrl: string; duration: number; aspectRatio: PlayerAspectRatio; mediaMarks: MediaMark[]; timeline:Timeline; recommend?: IProduceRecommend;}>,) => Promise<void>; | 否 | 将选中Timeline的多个标记片段拆分为不同Timeline并导出,参数依次为:默认封面图、导出Timeline的时长、导出比例、媒资标记、导出的Timeline片段、视频合成的分辨率或码率的推荐数据。 | 4.4.5 |
exportVideoClipsMerge | (data: { coverUrl: string; duration: number; aspectRatio: PlayerAspectRatio; mediaMarks: MediaMark[]; timeline: Timeline; recommend?:IProduceRecommend; }) => Promise<void>; | 否 | 将选中Timeline同一轨道的多个独立片段合成为一个Timeline并导出,参数依次为:默认封面图、导出Timeline的时长、导出比例、媒资标记、导出的Timeline片段、视频合成的分辨率或码率的推荐数据。 | 4.4.0 |
getAudioByMediaId | (mediaId: string) =>Promise<string>; | 否 | 获取音频地址,用于提升视频的音频波形图绘制速度。传入的参数为视频素材ID,初始化时传入该参数,SDK会优先使用其返回的音频地址解析出视频的音频波形。返回的Promise对象需要resolve音频地址URL。 | 4.3.5 |
hasTranscodedAudio | boolean | 否 | 导入工程中的所有视频是否有代理音频(转码后的音频),取值:
| 4.3.6 |
avatarConfig | 否 | 数字人接入配置。 | 4.10.0 | |
disableAutoAspectRatio | boolean | 否 | 是否关闭根据素材分辨率切换比例弹窗。 | 4.12.2 |
videoTranslation | VideoTranslation | 否 | 视频翻译相关配置 | 5.1.0 |
数据结构说明
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; // 语调,取值范围:-500~500,默认值:0 format?: string; // 输出文件格式,支持:PCM/WAV/MP3 }
InputMedia
type InputMedia = (InputVideo | InputAudio | InputImage) interface InputSource { sourceState?: 'ready' | 'loading' | 'fail'; } type MediaIdType = 'mediaId' | 'mediaURL'; interface SpriteConfig { num: string; lines: string; cols: string; cellWidth?: string; cellHeight?: string; } interface MediaMark { startTime: number; endTime: number; content: string; } interface InputVideo extends InputSource { mediaId: string; mediaIdType?: MediaIdType; mediaType: 'video'; video: { title: string; coverUrl?: string; duration: number; format?: string; src?: string; snapshots?: string[]; sprites?: string[]; spriteConfig?: SpriteConfig; width?: number; height?: number; rotate?: number; bitrate?: number; fps?: number; hasTranscodedAudio?: true; agentAudioSrc?: string; marks?: MediaMark[]; codec?: string; }; } interface InputAudio extends InputSource { mediaId: string; mediaIdType?: MediaIdType; mediaType: 'audio'; audio: { title: string; duration: number; coverUrl?: string; src?: string; marks?: MediaMark[]; formatNames?: string[]; }; } interface InputImage extends InputSource { mediaId: string; mediaIdType?: MediaIdType; mediaType: 'image'; image: { title: string; coverUrl?: string; src?: string; width?: number; height?: number; rotate?: number; }; } type TimelineMaterial = { mediaIdType: MediaIdType; mediaId: string; mediaType: MediaType };
MediaMark
interface MediaMark { startTime: number; endTime: number; content: string; }
ASRResult
interface ASRResult { content: string; // 字幕的内容 from: number; // 字幕的开始相对于识别素材的开始的时间偏移量 to: number; // 字幕的结束相对于识别素材的开始的时间偏移量 }
StickerCategory
interface StickerCategory { id: string; // 分类的 id name: string; // 分类的名称,调用者自行切换语言 }
StickerResponse
interface Sticker { mediaId: string; src: string; } interface StickerResponse { total: number; stickers: Sticker[]; }
IProduceRecommend
interface IProduceRecommend { width?: number; height?: number; bitrate?: number; }
CustomFontItem
interface CustomFontItem { key: string; // 字体唯一标识 name?: string; // 展示的名字,没有用key url: string; // 字体地址 // 用于前、后端字体渲染保持一致,页面文字渲染大小是您设置的字体大小乘以这个倍数 fontServerScale?: { // 普通字幕字体倍数 common: number; // 花字字体倍数 decorated: number; }; }
VoiceGroup
export interface VoiceGroup { type: string; // 分类 category:string; // 主分类 voiceList?: Voice[]; emptyContent?: { description: string; linkText: string; link: string; }; getVoiceList?: (page: number, pageSize: number) => Promise<{ items: Voice[]; total: number }>; getVoice?: (voiceId: string) => Promise<Voice | null>; getDemo?: (mediaId: string) => Promise<{ src: string }>; }
Voice
export interface Voice { voiceUrl?: string; // 示例音频地址 demoMediaId?: string; // 示例音频的播放地址 voiceType: VoiceType; // 类型 voice: string; // 人声 key name: string; // “人名” desc: string; // 简介 tag?: string; // 标签 remark?: string; // 备注支持的语言等信息 custom?: boolean; // 是否专属人声 }
VoiceType
enum VoiceType { Male = 'Male', // 男声 Female = 'Female', // 女声 Boy = 'Boy', // 男孩童声 Girl = 'Girl', // 女孩童声 }
AvatarConfig
// 数字人配置说明 interface AvatarConfig { // 数字人列表 getAvatarList: () => DigitalHumanList[]; // 提交数字人任务 submitAvatarVideoJob: <T extends keyof DigitalHumanJobParamTypes>( job: DigitalHumanJob<T>, ) => Promise<DigitalHumanJobInfo>; // 获取数字人任务结果 getAvatarVideoJob: (jobId: string) => Promise<DigitalHumanJobResult>; // 任务轮询时间 refreshInterval: number; // 数字人输出视频配置 outputConfigs: Array<{ width: number; height: number; bitrates: number[]; }>; filterOutputConfig?: ( item: DigitalHuman, config: Array<{ width: number; height: number; bitrates: number[]; }>, ) => Array<{ width: number; height: number; bitrates: number[]; }>; } // 数字人详细类型说明 // 数字人参数 interface DigitalHuman { avatarId: string; // 数字人ID avatarName: string; // 数字人名称 coverUrl: string; // 数字人封面 videoUrl?: string; // 数字人视频demo地址 outputMask?: boolean; // 是否输出遮罩 transparent?: boolean; // 是否背景透明 } // 数字人列表 interface DigitalHumanList { default: boolean; id: string; name: string; getItems: (pageNo: number, pageSize: number) => Promise<{ total: number; items: DigitalHuman[] }>; } // 数字人提交任务返回的信息 interface DigitalHumanJobInfo { jobId: string; mediaId: string; } // 数字人任务参数类型 type DigitalHumanJobParamTypes = { text: {//文字驱动 text?: string; params?: DigitalHumanTextParams; output?: DigitalHumanOutputParams; }; audio: {//音频文件驱动 mediaId?: string; params?: DigitalHumanAudioParams; output?: DigitalHumanOutputParams; }; }; // text|audio type DigitalHumanJobType = keyof DigitalHumanJobParamTypes; // 数字人文字驱动任务参数 type DigitalHumanTextParams = { voice: string; volume: number; speechRate: number; pitchRate: number; autoASRJob?: boolean; }; // 数字人音频文件驱动任务参数 type DigitalHumanAudioParams = { title: string; autoASRJob?: boolean; }; // 数字人输出视频其他参数 type DigitalHumanOutputParams = { bitrate: number; width: number; height: number; }; // 生成数字人字幕切片类型 type SubtitleClip = { from: number; to: number; content: string }; // 数字人任务运行轮询结果 interface DigitalHumanJobResult { jobId: string; mediaId: string; done: boolean; errorMessage?: string; job?: DigitalHumanJob<any>; video?: InputVideo; subtitleClips?: SubtitleClip[]; } // 数字人任务 type DigitalHumanJob<T extends DigitalHumanJobType> = { type: T; title: string; avatar: DigitalHuman; data: DigitalHumanJobParamTypes[T]; }; // 数字人生成视频 interface InputVideo { mediaId: string; mediaType: 'video'; video: { title: string; coverUrl?: string; duration: number; src?: string; // 当 useDynamicUrl 为 true 时,src 可以不传 snapshots?: string[]; sprites?: string[]; spriteConfig?: SpriteConfig;//精灵图 width?: number; // 视频源的宽度 height?: number; // 视频源的高度 rotate?: number; // 视频源的旋转角度 bitrate?: number; // 视频源的码率 fps?: number; // 视频源的帧率 hasTranscodedAudio?: true; // 是否含有转码后的音频流 agentAudioSrc?: string; // 代理的音频地址(用于分离音频轨),当 useDynamicUrl 为 true 时,agentAudioSrc 可以不传 marks?: MediaMark[];// 视频标记 }; }
LicenseConfig
// license的配置 type LicenseConfig = { rootDomain?: string; // license使用的根域名,例如使用的域名是 editor.abc.com,这里填的值就是abc.com licenseKey?: string; // 申请的licenseKey,参考顶部使用说明在控制台中申请 }
AsrConfig
// 智能生成字幕的配置 type AsrConfig = { interval?: number; // 轮询时长,单位:毫秒 defaultText?: string; // 默认的文案 maxPlaceHolderLength?: number; // 默认的文案最大长度 submitASRJob: (mediaId: string, startTime: string, duration: string) => Promise<ASRJobInfo>; getASRJobResult?: (jobId: string) => Promise<ASRJobInfo>; } interface ASRJobInfo { jobId?: string; jobDone: boolean; jobError?: string; result?: ASRResult[]; }
TTSConfig
// 智能配音任务的配置 type TTSConfig = { interval?: number; // 轮询时长,单位:毫秒 submitAudioProduceJob: (text: string, voice: string, voiceConfig?: VoiceConfig) => Promise<TTSJobInfo>; getAudioJobResult?: (jobId: string) => Promise<TTSJobInfo>; } interface VoiceConfig { volume: number; speech_rate: number; pitch_rate: number; format?: string; custom?: boolean; } interface TTSJobInfo { jobId?: string; jobDone: boolean; jobError?: string; asr?: AudioASRResult[]; result?: InputAudio | null; } interface AudioASRResult { begin_time?: string; end_time?: string; text?: string; content?: string; from?: number; to?: number; }
PublicMaterialLibrary
// 公共媒资库的配置 type PublicMaterialLibrary = { getLists: () => Promise<MaterialList[]>; name?: string; pageSize?: number; // 单页展示数量 }; type MaterialList = { name?: string; key: string; tag?: string; mediaType: 'video' | 'audio' | 'image'; styleType?: 'video' | 'audio' | 'image' | 'background'; getItems: ( pageIndex: number, pageSize: number, ) => Promise<{ items: InputMedia[]; end: boolean; }>; };
SubtitleConfig
type SubtitleConfig = { // 自定义纹理列表 customTextures?: { list: () => Promise< Array<{ key: string; url: string; }> >; // 添加自定义纹理 onAddTexture: () => Promise<{ key: string; url: string; }>; // 删除自定义纹理 onDeleteTexture: (key: string) => Promise<void>; }; }
AliyunVideoEditor
// AliyunVideoEditor 实例方法 type AliyunVideoEditor = { init: (config: IConfig) => void; // 初始化编辑器 destroy: (keepState?: boolean) => boolean; // 销毁编辑器 version: string | undefined; // 获取编辑器版本 setCurrentTime: (currentTime: number) => void; // 设置编辑器预览时间 getCurrentTime: () => number; // 获取编辑器预览时间 getDuration: () => number; // 获取编辑器时长 addProjectMaterials: (materials: InputMedia[]) => void; // 添加项目素材到编辑器 setProjectMaterials: (materials: InputMedia[]) => void; // 设置项目素材到编辑器 updateProjectMaterials: (update: (materials: InputMedia[]) => InputMedia[]) => void; // 更新编辑器当前项目素材 deleteProjectMaterial: (mediaId: string) => void; // 删除编辑器项目素材 setProjectTimeline: ({ VideoTracks, AudioTracks, AspectRatio }: CustomTimeline) => Promise<void>; // 设置编辑器的timeline getProjectTimeline: () => any; // 获取编辑器的timeline getEvents: (eventType?: 'ui' | 'player' | 'error' | 'websdk' | 'timeline') => IObservable<EventData<any>>; // 获取编辑器的事件 importSubtitles: (type: 'ass' | 'srt' | 'clip' | 'asr', config: string) => void; // 批量导入字幕到编辑器 }
VideoTranslation
type VideoTranslation = { language?: { // 源语言 source: Array<{ value: string; label: string; }>; // 目标语言 target: Array<{ value: string; label: string; }>; }; // 视频翻译 translation?: { interval?: number; submitVideoTranslationJob: (params: TranslationJobParams) => Promise<TranslationJobInfo>; getVideoTranslationJob: (jobId: string) => Promise<TranslationJobInfo>; }; // 字幕擦除 detext?: { interval?: number; submitDetextJob: (param: DetextJobParams) => Promise<DetextJobInfo>; getDetextJob: (jobId: string) => Promise<DetextJobInfo>; }; // 字幕提取 captionExtraction?: { interval?: number; submitCaptionExtractionJob: (param: CaptionExtractionJobParams) => Promise<CaptionExtractionJobInfo>; getCaptionExtractionJob: (jobId: string) => Promise<CaptionExtractionJobInfo>; }; }; interface TranslationJobParams { type: 'Video' | 'Text' | 'TextArray'; mediaId?: string; mediaIdType?: MediaIdType; text?: string; textArray?: string[]; editingConfig: { SourceLanguage: string; TargetLanguage: string; DetextArea?: string; SupportEditing?: boolean; SubtitleTranslate?: { TextSource: 'OCR' | 'SubtitleFile'; OcrArea?: string; SubtitleConfig?: string; }; }; } interface TranslationJobInfo { jobId?: string; jobDone: boolean; jobError?: string; result?: { video?: InputVideo; timeline?: string; text?: string; textArray?: Array<{ Target: string; Source: string; }>; }; } interface DetextJobParams { mediaId: string; mediaIdType: MediaIdType; box?: 'auto' | Array<[number, number, number, number]>; } interface DetextJobInfo { jobId?: string; jobDone: boolean; jobError?: string; result?: { video?: InputVideo; }; } interface CaptionExtractionJobParams { mediaId: string; mediaIdType: MediaIdType; box?: 'auto' | Array<[number, number, number, number]>; } interface CaptionExtractionJobInfo { jobId?: string; jobDone: boolean; jobError?: string; result?: { srtContent?: string; }; }
init()示例代码
Web SDK只负责界面交互,不会发起请求,您需要通过Web SDK调用请求逻辑。请求本身应该先发送给您自己的服务端,您自己的服务端再根据AccessKey信息(AccessKey ID和AccessKey Secret)转发给相关的阿里云OpenAPI。
// 注意,WebSDK 本身并不提供 request 这个方法,这里仅作为示例,您可以使用您喜欢的网络请求库,如 axios 等
window.AliyunVideoEditor.init({
container: document.getElementById('aliyun-video-editor'),
locale: 'zh-CN',
licenseConfig: {
rootDomain: "", // license使用的根域名,例如abc.com
licenseKey: "", // 申请的licenseKey,没有配置licenseKey,在预览时会出现水印,没有配置license的情况下,只能在localhost的域名下预览
},
useDynamicSrc: true, // 媒资库默认情况下播放地址会过期,所以需要动态获取
getDynamicSrc: (mediaId, mediaType) => new Promise((resolve, reject) => {
request('GetMediaInfo', { // https://help.aliyun.com/document_detail/197842.html
MediaId: mediaId
}).then((res) => {
if (res.code === '200') {
// 注意,这里仅作为示例,实际中建议做好错误处理,避免如 FileInfoList 为空数组时报错等异常情况
resolve(res.data.MediaInfo.FileInfoList[0].FileBasicInfo.FileUrl);
} else {
reject();
}
});
}),
getEditingProjectMaterials: () => {
if (projectId) { // 如果没有 projectId,需要先在智能媒体服务控制台创建剪辑工程,获取projectId。
return request('GetEditingProjectMaterials', { // https://help.aliyun.com/document_detail/209068.html
ProjectId: projectId
}).then((res) => {
const data = res.data.MediaInfos;
return transMediaList(data); // 需要做一些数据变换,具体参考后文
});
}
return Promise.resolve([]);
},
searchMedia: (mediaType) => { // mediaType 为用户当前所在的素材 tab,可能为 video | audio | image,您可以根据这个参数对应地展示同类型的可添加素材
return new Promise((resolve) => {
// 调用方需要自己实现展示媒资、选择媒资添加的界面,这里的 callDialog 只是一种示例,WebSDK 本身并不提供
// 关于展示媒资,请参考:https://help.aliyun.com/document_detail/197964.html
callDialog({
onSubmit: async (materials) => {
if (!projectId) { // 如果没有 projectId,需要先创建工程,如果能确保有 projectId,则不需要该步
const addRes = await request('CreateEditingProject', { // https://help.aliyun.com/document_detail/197834.html
Title: 'xxxx',
});
projectId = addRes.data.Project.ProjectId;
}
// 组装数据
const valueObj = {};
materials.forEach(({ mediaType, mediaId }) => {
if (!valueObj[mediaType]) {
valueObj[mediaType] = mediaId;
} else {
valueObj[mediaType] += mediaId;
}
})
const res = await request('AddEditingProjectMaterials', { // https://help.aliyun.com/document_detail/209069.html
ProjectId: projectId,
MaterialMaps: valueObj,
});
if (res.code === '200') {
return resolve(transMediaList(res.data.MediaInfos));
}
resolve([]);
}
});
});
},
deleteEditingProjectMaterials: async (mediaId, mediaType) => {
const res = await request('DeleteEditingProjectMaterials', { // https://help.aliyun.com/document_detail/209067.html
ProjectId: projectId,
MaterialType: mediaType,
MaterialIds: mediaId
});
if (res.code === '200') return Promise.resolve();
return Promise.reject();
},
getStickerCategories: async () => {
const res = await request('ListAllPublicMediaTags', { // https://help.aliyun.com/document_detail/207796.html
BusinessType: 'sticker',
WebSdkVersion: window.AliyunVideoEditor.version
});
const stickerCategories = res.data.MediaTagList.map(item => ({
id: item.MediaTagId,
name: myLocale === 'zh-CN' ? item.MediaTagNameChinese : item.MediaTagNameEnglish // myLocale 是您期望的语言
}));
return stickerCategories;
},
getStickers: async ({ categoryId, page, size }) => {
const params = {
PageNo: page,
PageSize: size,
IncludeFileBasicInfo: true,
MediaTagId: categoryId
};
const res = await request('ListPublicMediaBasicInfos', params); // https://help.aliyun.com/document_detail/207797.html
const fileList = res.data.MediaInfos.map(item => ({
mediaId: item.MediaId,
src: item.FileInfoList[0].FileBasicInfo.FileUrl
}));
return {
total: res.data.TotalCount,
stickers: fileList
};
},
getEditingProject: async () => {
if (projectId) {
const res = await request('GetEditingProject', { // https://help.aliyun.com/document_detail/197837.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', { // https://help.aliyun.com/document_detail/197835.html
ProjectId: projectId,
CoverURL: coverUrl,
Duration: duration,
Timeline: JSON.stringify(timeline)
}).then((res) => {
if (res.code === '200') {
// WebSDK 本身会进行自动保存,isAuto 则是告诉调用方这次保存是否自动保存,调用方可以控制只在手动保存时才展示保存成功的提示
!isAuto && Message.success('保存成功');
resolve();
} else {
reject();
}
});
}),
produceEditingProjectVideo: ({ coverUrl, duration = 0, aspectRatio, timeline, recommend }) => {
return new Promise((resolve) => {
callDialog({ // 调用方需要自己实现提交合成任务的界面,这里的 callDialog 只是一种示例
onSubmit: async ({ fileName, format, bitrate, description }) => { // 假设提交合成任务的界面让你获得了这些数据
// 先根据 fileName 和 format 拼接出存储的 mediaURL
const mediaURL = `http://bucketName.oss-cn-hangzhou.aliyuncs.com/${fileName}.${format}`;
// 根据 WebSDK 传入的预览比例来决定合成的宽高
const width = aspectRatio === '16:9' ? 640 : 360;
const height = aspectRatio === '16:9' ? 360 : 640;
// 若视频、图片素材传入的长宽、码率等数据,那么该函数返回的数据中的 recommend 就会包含了根据所使用的视频、图片计算得到的推荐的分辨率和码率
// recommend 数据结构可以查看 IProduceRecommend
// 你可以在提交界面上展示推荐数据或者直接使用在提交接口的参数里
const res = await request('SubmitMediaProducingJob', { // https://help.aliyun.com/document_detail/197853.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();
}
});
});
}
});
/**
* 将服务端的素材信息转换成 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),
// 源视频的宽高、码率等数据,用于推荐合成数据,不传入或是0时无推荐数据
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,
// 图片的宽高等数据,用于推荐合成数据,不传入或是0时无推荐数据
width: Number(fileBasicInfo.Width) || 0,
height: Number(fileBasicInfo.Height) || 0,
}
}
return result;
});
} else {
return [data];
}
}