文档

接入视频剪辑Web SDK

更新时间:

智能生产制作提供专业在线的视频剪辑能力,针对自动化、智能化剪辑以及多人协作视频制作需求,您可以基于时间线进行云剪辑。通过阅读本文,您可以了解如何接入视频剪辑Web SDK。

使用说明

本文中引入的视频剪辑Web SDK的版本号5.0.1(仅供参考),从5.0.1开始,你需要申请License授权才能使用剪辑Web SDK。获取最新的版本及License信息,请参见视频剪辑工程——帮助信息

操作步骤

  1. 引入视频剪辑Web SDK。

    在项目前端页面文件中的<head>标签处引入视频剪辑Web SDK的CSS文件,如下所示:

    <head>
      <link rel="stylesheet" href="https://g.alicdn.com/thor-server/video-editing-websdk/5.0.0/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.0.0/index.js"></script>
      <script>
        // 调用 SDK 的代码放在这里
      </script>
    </body>
  2. 初始化视频剪辑Web SDK。

    window.AliyunVideoEditor.init(config);

config属性说明

config参数

参数

类型

必填

描述

引入版本

locale

string

界面语言,取值:

  • zh-CN(默认值):中文。

  • en-US:英文。

3.0.0

container

Element

Web SDK生成界面挂载的DOM节点。

3.0.0

defaultAspectRatio

PlayerAspectRatio

默认的视频预览比例,默认为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

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

AsrConfig

提交智能字幕任务的配置。

4.13.0

ttsConfig

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

PublicMaterialLibrary

公共素材库配置。

4.13.0

subtitleConfig

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

VoiceGroup[]

自定义语音选项。

4.3.5

getPreviewWaterMarks

() => Promise<Array<{ url?: string; mediaId?:string; width?: number; height?: number; x?: number; y?: number; xPlusWidth?: number; yPlusHeight?: number; opacity?: number; }>>;

预览区添加水印,防止截屏(合成时没有水印),参数如下所示:

  • url:水印图片地址,与水印媒资ID二选一。

  • mediaId:水印媒资ID,与水印图片地址二选一。

  • width:图片的宽缩放百分比,范围:(0,1],默认值为1,表示自适应预览区。

  • height:图片的高缩放百分比,范围:(0,1],默认值为1,表示自适应预览区。

  • x:图片相对预览区的x坐标值,范围:[0~1]。

  • y:图片相对预览区的y坐标值,范围:[0~1]。

  • xPlusWidth:图片定位的左右偏移参数,例如:图片左右居中,则x=0.5,xPlusWidth=-0.5。

  • yPlusHeight:图片定位的上下偏移参数,例如:图片左右居中,则y=0.5,yPlusHeight=-0.5。

  • opacity:图片透明度。

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

导入工程中的所有视频是否有代理音频(转码后的音频),取值:

  • true:所有导入工程中的视频素材都有代理音频,SDK对于视频分离音频轨操作不会限制视频素材原始时长。

    重要

    为了提升视频分离音频轨的性能,如果使用动态资源地址,则接口getDynamicSrc根据mediaType返回相应类型的资源信息;如果使用静态资源地址,则在Media.video.agentAudioSrc声明视频素材的代理音频地址。

  • false:视频分离音频轨操作仅对原始时长不超过30分钟的视频素材开放。

4.3.6

avatarConfig

AvatarConfig

数字人接入配置。

4.10.0

disableAutoAspectRatio

boolean

是否关闭根据素材分辨率切换比例弹窗。

4.12.2

数据结构说明

  • 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; // 批量导入字幕到编辑器
    }

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

相关参考

常见问题