阿里云首页 智能媒体生产

普通剪辑 WebSDK 接入指南

aliyun-video-editing-websdk 是一个快速搭建云智能剪辑 web 端界面的 SDK,本文将介绍如何接入这个 SDK

体验WebSDK

欢迎通过智能媒体生产控制台,创建“云智能剪辑”-“普通剪辑工程”进行WebSDK结构及功能体验,具体操作请参见普通剪辑工程快速入门

接入WebSDK前需要完成的设计

在体验WebSDK环节中,您一定大致了解了整体WebSDK的结构和功能。接下来要介绍需要您提前进行的设计。

一、设计媒资导入界面

为了便于您更灵活的封装媒资管理,实现对不同用户的资源隔离,我们开放了媒资导入界面的自由定制。

您需要设计一个导入界面,如弹窗。

用户点击“导入素材”后:

1、WebSDK会调用您传入config里的SearchMedia,打开您的导入素材界面。参考本文档“正式接入WebSDK”-“三、Config参数说明”中的SearchMedia;

2、用户在素材界面选中媒资后,调用服务端接口,将媒资与当前工程注册关联,接口请参见新增剪辑工程关联素材

3、注册关联成功后,参考本文档“正式接入WebSDK0”-“三、Config参数说明”-“1 获取工程媒资”, 将自动将媒资列表填充至界面左上角的资源库。

参考智能媒体生产控制台体验版的界面如下:

导入素材界面

二、设计合成界面

为了便于您在合成时能精简填写项,我们开放了合成界面的自由定制。

您需要设计一个合成界面,如弹窗。

用户点击“导出视频”后:

1、WebSDK会调用您传入config里的produceEditingProjectVideo函数,打开您的合界面。参考本文档“正式接入WebSDK”-“三、Config参数说明”中的produceEditingProjectVideo;

2、用户填写合成表单信息并确认提交后,您需要将用户填写的内容、您固定的一些参数以及 WebSDK 提供的 timeline(timeline获取请参考本文档“正式接入WebSDK”-“三、Config参数说明”中的getEditingProject),整体作为参数调用服务端接口提交合成。 接口请参见提交剪辑合成作业

3、如接口返回合成任务创建成功,则返回 resolved 状态的 Promise 给 WebSDK,否则返回 rejected 状态的 Promise。

参考智能媒体生产控制台体验版的界面如下:

合成界面

正式接入WebSDK

一、准备工作

在 html 的 head 标签中引入 SDK 的 CSS 文件,如下所示

<head>
  <link rel="stylesheet" href="https://g.alicdn.com/thor-server/video-editing-websdk/x.x.x/index.css">
</head>

其中 x.x.x 为版本号,当前最新版本为 3.9.0

然后在 html 的 body 标签中创建一个用以挂载剪辑界面的节点,在 body 标签的尾部引入 SDK 的 JS 文件,再引用一个 script 用以调用 WebSDK,如下所示:

<body>
  <div id="aliyun-video-editor" style="height:700px"></div> // 您可以根据需要改变 container 高度
  <script src="https://g.alicdn.com/thor-server/video-editing-websdk/x.x.x/index.js"></script>
  <script>
    // 调用 SDK 的代码放在这里
  </script>
</body>

二、初始化

WebSDK 目前只提供单独 JS 引入的方式,后续会考虑提供 npm 包。该单独 JS 会在 window 上挂载 AliyunVideoEditor 对象。

WebSDK 调用方式如下:

window.AliyunVideoEditor.init(config);

config 是一个对象,对象的 key-value 说明见下一节

三、config 参数说明

我们先通过 WebSDK 生成的界面介绍一下常用的一些 config 参数是如何被调用的

界面介绍各部分介绍如下:

  1. 当前工程绑定的素材,页面初始化时 SDK 会调用 getEditingProjectMaterials 参数获取当前工程已绑定的素材并展示在对应的 tab(根据素材的 mediaType 属性决定展示在哪个 tab)

  2. 当用户点击这个按钮后,SDK 会调用 searchMedia 参数,调用方此时应展示一个选择素材的界面(比如一个弹窗),并且在用户选择并确认之后,将用户选中的素材返回给 SDK,SDK 会将这些素材添加在对应的 tab 中

  3. 当用户的鼠标悬浮在素材上时,会展示一个删除按钮,用户如果点击这个删除按钮,SDK 会调用 deleteEditingProjectMaterials 参数通知调用方该素材已经从工程中删除,调用方应发起请求将这个素材与这个工程解绑

  4. 当前工程的时间线,页面初始化时 SDK 会调用 getEditingProject 参数获取当前工程的时间线并展示,调用方应发起请求从服务端拿到当前的时间线等数据传给 SDK

  5. 当用户点击保存按钮时,SDK 会调用 updateEditingProject 参数,将工程的时间线等数据传给调用方,调用方应将这些数据发送给服务端进行保存

  6. 当用户点击生成按钮时,SDK 会调用 produceEditingProjectVideo 参数,将工程的时间线等数据传给调用方,一般而言,这些数据是不够的,调用方应展示一个弹窗让用户填写更多的参数,比如希望合成的视频分辨率等,在用户点击提交的时候,调用方将这些用户填写的数据连同 SDK 传出的数据提交给服务端进行合成。

贴纸界面

7. 当用户首次点击贴纸 tab 时,SDK 会调用 getStickerCategories 参数获取贴纸的分类并展示在 8 的位置,同时调用 getStickers 参数获取第一个贴纸分类下的贴纸

8. 当用户点击一个未点击过的贴纸分类时,SDK 会调用 getStickers 参数获取该分类的贴纸

config 详细的参数项如下所示:

参数名

参数说明

是否必选

参数类型

参数类型说明

引入版本

locale

界面语言

'zh-CN' | 'en-US'

语言代码,默认展示中文

3.0.0

container

WebSDK 生成界面挂载的 DOM 节点

HTMLElement

DOM 节点

3.0.0

defaultAspectRatio

默认的预览比例

PlayerAspectRatio

预览比例枚举值,详见第四节,默认 16:9

3.4.0

defaultSubtitleText

字幕的默认文本内容

string

如不填,则为“阿里云剪辑”(中文)或"Online Editing"(英文)。如填写,不能超过 20个字符

3.6.0

useDynamicSrc

是否动态获取资源的 src

boolean

3.0.0

getDynamicSrc

如果 useDynamicSrc 为 true,则需要传此项,用以动态获取资源的 src

(mediaId: string, mediaType: 'video' | 'audio' | 'image') => Promise<string>

一个函数,参数为资源的 mediaId 和 mediaType。返回一个 Promise,该 Promise 应该 resolve 资源新的 src

3.0.0

getEditingProjectMaterials

获取工程关联的素材

() => Promise<Media[]>

一个函数,返回一个 Promise,该 Promise 应该 resolve 所有素材类型的素材数组

3.0.0

searchMedia

从媒资库导入素材

(mediaType: 'video' | 'audio' | 'image') => Promise<Media[]>

一个函数,参数指定添加素材的类型。返回一个 Promise,该 Promise 应该 resolve 新增的素材数组。注意,调用方必须得自己将新增的素材与工程关联起来

3.0.0

deleteEditingProjectMaterials

将素材与工程解绑

(mediaId: string, mediaType: 'video' | 'audio' | 'image') => Promise<void>

一个函数,传入参数为被删除素材的 mediaId 和 mediaType,返回一个 Promise,调用方保存成功后 resolve,否则 reject

3.0.0

submitASRJob

提交智能识别字幕任务(m3u8 格式的素材暂不支持该功能)

(mediaId: string, startTime: string, duration: string) => Promise<ASRResult[]>

一个函数,传入参数为要识别字幕的素材 id,该素材片段的开始时间及时长,返回一个 Promise,该 Promise 应该 resolve 识别的结果 ASRResult 数组

3.1.0

submitAudioProduceJob

提交文字转语音任务

(text:string,voice:AudioProduceVoice)=>Promise<Media>

一个函数,传入参数为字幕的内容和用户选择的声音(详见第四节),返回一个 Promise,该 Promise 应该 resolve 生成语音的数据

3.5.0

getStickerCategories

获取贴纸分类,如不传,则不分类

() => Promise<StickerCategory[]>

一个函数,返回一个 Promise,该 Promise 应该 resolve 贴纸的分类数组

3.0.0

getStickers

获取贴纸

(config: {categoryId?: string; page: number; size: number}) => Promise<StickerResponse>

一个函数,返回一个 Promise,该 Promise 应该 resolve 贴纸的总量和贴纸数组。如果贴纸没有分类,则 categoryId 为空

3.0.0

getEditingProject

获取工程的时间线

() => Promise<{timeline?: Timeline; projectId?: string; modifiedTime?: string}>

一个函数,返回一个 Promise,该 Promise 应该 resolve 这个工程的 Timeline 数据、项目 id、最后修改时间

3.0.0

updateEditingProject

保存工程的时间线

(data: {coverUrl: string; duration: number; timeline: Timeline; isAuto: boolean}) => Promise<{projectId: string}>

一个函数,传入参数为工程的封面图地址、时长(单位为秒)、Timeline 数据和是否自动保存(当前每分钟自动保存 1 次),返回一个 Promise,调用方保存成功后 resolve,否则 reject

3.0.0

produceEditingProjectVideo

生成视频

(data:{ coverUrl: string; duration: number; aspectRatio: PlayerAspectRatio; timeline: Timeline; recommend: IProduceRecommend; }) => Promise<void>

一个函数,传入参数为工程的封面图地址、时长(单位为秒)、 Timeline 数据 和 recommend (视频合成的分辨率、码率的推荐数据),返回一个 Promise,调用方生成成功后 resolve,否则 reject

3.0.0(aspectRatio 是 3.4.0 新增的,recommend 是 3.8.0新增的)

customTexts

自定义部分文案

{
importButton?:string;
updateButton?:string;
produceButton?:string;
}

一个对象,importButton 对应导入按钮的文案,updateButton 对应更新按钮的文案,produceButton 对应生成按钮的文案

3.7.0

四、数据结构补充说明

Timeline

该数据为 SDK 生成的时间线数据,用以发送给服务端进行视频的合成,具体结构定义,请参见 Timeline 配置说明

PlayerAspectRatio

枚举值:'16:9', '9:16'

AudioProduceVoice

枚举值:'xiaoyun', 'xiaogang', 'sitong', 'aitong'

(分别对应标准女声、标准男声、可爱男童声、软萌女童声)

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; // 当 useDynamicUrl 为 true 时,src 可以不传
    snapshots: string[];
    sprites: string[];
    spriteConfig: {
      num: string; // 雪碧图中有多少个小图
      lines: string; // 行数
      cols: string; // 列数
      cellWidth?: string; // 单个小图的宽度,可不传
      cellHeight?: string; // 单个小图的高度,可不传
    };
  };
  audio?: { // materialType 为'audio'时须传入此项
    title: string;
    duration: number;
    src?: string; // 当 useDynamicUrl 为 true 时,src 可以不传
    coverUrl?: string; // 音频封面
  };
  image?: { // materialType 为'image'时须传入此项
    title: string;
    src?: string; // 当 useDynamicUrl 为 true 时,src 可以不传
    coverUrl?: string; // 图片预览图,在列表及轨道区会优先展示该字段,如果没有,则展示 src 字段,如果两者都没有则无法在轨道区预览
    width?: number; // 图片的宽度,用于合成的推荐分辨率,不传入则不会有推荐的分辨率
    height?: number; // 图片的高度,用于合成的推荐分辨率,不传入则不会有推荐的分辨率
  };
}

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 智能推荐数据

export interface IProduceRecommend {
  width?: number;
  height?: number;
  bitrate?: number;
}

五、init 方法示例代码

WebSDK 本身只负责界面和交互,不会发起请求,您需要将请求的逻辑提供给 WebSDK 调用。请求本身应该先发送给您自己的服务端,您自己的服务端再根据自己的 AccessKeyId, AccessKeySecret 转发给相关的阿里云服务端。

// 注意,WebSDK 本身并不提供 request 这个方法,这里仅作为示例,您可以使用您喜欢的网络请求库,如 axios 等

window.AliyunVideoEditor.init({
  container: document.getElementById('aliyun-video-editor'),
  locale: 'zh-CN',
  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 由调用方自己保存
      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
      };
    }
    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];
  }
}

如果您之前已经接入早于 3.7.0 版本的 WebSDK,当您升级到 3.7.0 及以上版本的 WebSDK 时,请您参照以上示例代码修改 updateEditingProject 参数(区别在于之前是透传 FEExtend 字段给服务端,现在是透传 timeline 字段给服务端)

智能生成字幕 submitASRJob

这是一个可选的功能,如果 init 方法没有传 submitASRJob 参数,则视频的属性面板,不会展示“智能生成字幕”按钮,submitASRJob 的示例如下:

const submitASRJob = async (mediaId, startTime, duration) => {
  const res = await request('SubmitASRJob', { // https://help.aliyun.com/document_detail/203425.html
    inputFile: mediaId,
    startTime,
    duration
  });

  if (res.code === '200') {
    const jobId = res.data.JobId;
    
    const interval = 10000; // 轮询的时间间隔,接入方可以自定义
    const totalTimes = 10; // 轮询次数,接入方可以自定义
    let result = {};
    for (let i = 0; i < totalTimes; i++) {
      await new Promise(resolve => {
        window.setTimeout(resolve, interval);
      });
      
      // 获取智能任务结果
      result = await request('GetSmartHandleJob', { // https://help.aliyun.com/document_detail/203429.html
        JobId: jobId
      });
      if (result.code !== '200') break; // 任务失败了,结束轮询
      const state = res.data.State;
      if (state !== 'Creating' && state !== 'Executing') break;
    }
    
    if (result.code === '200' && result.data.State === 'Finished') {
      return JSON.parse(result.data.Output);
    } else {
      throw new Error('智能识别字幕失败')
    }
  } else {
    throw new Error(res.message);
  }
};

智能生成配音 submitAudioProduceJob

这是一项可选的功能,如果 init 方法没有传 submitAudioProduceJob 参数,则字幕的属性面板,不会展示“智能配音”tab,submitAudioProduceJob 的示例如下:

const submitAudioProduceJob = async (text, voice) => {
  // 智能生成配音会生成一个音频文件存放到接入方的 OSS 上,这里 bucket, path 和 filename 是一种命名的示例,接入方可以自定义
  const bucket = 'MyBucket';
  const path = 'autoProducedAudios/';
  const filename = `${text.slice(0, 10)}${Date.now()}`;

  const res1 = await request('SubmitAudioProduceJob', { // https://help.aliyun.com/document_detail/212273.html
    IsAsync: 0,
    EditingConfig: JSON.stringify({
      voice,
      format: 'mp3'
    }),
    InputConfig: text,
    OutputConfig: {
      bucket,
      object: `${path}${filename}`
    }
  });

  if (res1.code !== '200') {
    throw new Error('抱歉,暂未识别当前文字内容');
  }
  const res2 = await request('GetSmartHandleJob', { // https://help.aliyun.com/document_detail/203429.html
    JobId: res1.data.JobId
  });
  if (res2.code === '200' && res2.data.State === 'Finished') {
    const mediaId = res2.data.Output;
    
    const interval = 10000; // 轮询的时间间隔,接入方可以自定义
    const totalTimes = 10; // 轮询次数,接入方可以自定义
    
    let result = {};

    // 轮询媒资是否已经注册完毕
    for (let i = 0; i < totalTimes; i++) {
      await new Promise(resolve => {
        window.setTimeout(resolve, interval);
      });
      
      result = await request('GetMediaInfo', {
        MediaId: mediaId
      });
      
      if (result.code !== '200') break; // 注册失败,结束轮询
      if (result.data?.MediaInfo?.MediaBasicInfo?.Status === 'Normal') break; // 注册成功,结束轮询
    }
      
    if (result.code === '200') {
      result = transMediaList([result.data.MediaInfo]); // transMediaList 同前文中的定义
      const newAudio = result[0];
      
      // 将新的音频素材与工程进行绑定
      await request('AddEditingProjectMaterials', {
        ProjectId: 'xxxxx',
        MaterialMaps: {
          audio: newAudio.mediaId
        }
      });
      
      return newAudio;
    } else {
      throw new Error('抱歉,暂未识别当前文字内容');
    }
  } else {
    throw new Error(res2.data.ErrorMsg || '抱歉,暂未识别当前文字内容');
  }
};

六、其他顶层接口

以上所叙述的都是 window.AliyunVideoEditor 对象中 init 方法的使用,这个对象还包含以下属性或方法:

  1. version

用以获取当前版本的版本号

七、FAQ

1. WebSDK 支持预览哪些格式的媒资?

视频:mp4, webm, mkv, mov, m3u8

音频:mp3

图片:jpg, jpeg, png, webp, gif

注:上述格式之外的媒资,不一定不能预览,只是未经过验证是否可以预览。且即使不能预览,也能添加进轨道区进行编排并提交服务端合成

2. 为什么我的媒资是上述支持预览的格式,但是在添加进轨道区时,会提示“您的浏览器暂不支持当前格式预览,您的编辑在合成时可以生效”?

WebSDK 目前是根据媒资的 title 字段的后缀名来判断是否支持的,如果判断不支持,则会在加入轨道区时弹窗提示。请您检查 getEditingProjectMaterials 和 searchMedia 这两个函数参数的返回值中,媒资的 title 字段是否包含了媒资的后缀名

3. 为什么示例代码我无法直接运行?

这个示例代码仅作为参考,目前是无法直接运行的,有以下几部分是需要接入方提供具体实现后方能完整运行的:

    1. 接入方需要提供多个服务端的接口,这些接口加上鉴权信息后转发前端的请求给阿里云的接口

    2. 前端需要实现发起网络请求的函数,即示例代码的 request 函数,可以使用成熟的开源库如 axios,也可以是接入方项目中已存在的请求工具函数

    3. 点击导入素材之后展示的弹窗,在这个弹窗里,接入方可以实现本地上传功能,也可以展示媒资库中已有的媒资供用户选择

    4. 点击导出视频之后展示的弹窗,在这个弹窗里,接入方可以提供表单供用户填写一些合成的参数,提交表单后携带这些合成参数给服务端

首页 智能媒体生产 WebSDK接入手册 普通剪辑 WebSDK 接入指南