Core-SDK
Core-SDK 是画布渲染部分的 SDK,传入工程数据(JSON)可以将数据绘制到 Canvas 上。可基于该 SDK 进行二次开发,开发视频相关的小程序,H5,PC视频编辑器等基于 web 的在线工具。
使用方法如下:
import { VideoCoreSDK } from "@h5/videoCoreSDK";
const vc = new VideoCoreSDK({
data: MovieData, // 必填,工程数据
workerPath: "assets", // 选填,decode.worker.js的引用目录,默认是assets
registerId: "", // 必填,注册ID,和域名进行绑定的
movieId: "movieID", // 实例内部唯一标识,多个实例不能相同
EModuleEffectSourcePath: "/assets/effectcanvas/", // 特效资源模块加载路径
resourceHost: "https://cdn.h5ds.com", // 资源加载的host
scale: 1, // 选填,画布缩放比例,默认是1
env: "preview", // 必填,渲染模式:editor(编辑模式,可拖动编辑); preview(预览模式,只能预览,内存消耗更小)
currentTime: 0, // 选填,默认开始时间是0
plugins: [], // 选填,扩展插件
});
const core = await vc.init();
/**
* 假设工程数据是movieData,修改数据->更新视图
*/
// 1、获取工程数据中第一个元素
const [element] = movieData.elements;
// 2、修改第一个元素的坐标为[100, 200]
element.style.x = 100;
element.style.y = 200;
// 3、更新视图
core.update();可以通过new VideoCore(...)获取到vc实例,再通过vc.init()获取到core,core实例中包含了很多属性和方法,下面就挨个介绍这些属性和方法以及使用技巧。
// VideoCoreSDK 参数说明 typescript
export interface IMovieProps {
data: types.MovieData; // 工程项目数据
workerPath?: string; // worker地址
registerId?: string; // 注册ID
movieId?: string; // 实例内部唯一标识,多个实例不能相同
EModuleEffectSourcePath?: string; // 特效加载路径
scale?: number; // 画布等比缩小比例,默认是1
resourceHost?: string; // 资源加载的host
stopControl?: boolean; // 停止控制器,editor默认使用了控制器,可以通过这个参数在editor中禁用控制器
useRecordManager?: boolean; // 使用历史记录,默认是启用了历史记录
plugins?: types.PluginConfig[]; // 扩展插件
target?: HTMLElement | null; // canvas放入的DOM容器,默认是body
times?: [number, number]; // 如果传了该参数,资源只会加载该区间的,和 导出配合使用
env: types.Env; // 使用环境变量控制吧,env: 'editor' | 'preview' | 'export';
server?: boolean; // 服务端渲染设置为true,默认是false
// 导出MP4的时候必须预加载资源,在编辑器中是无需提前预加载资源的,所以这里有个参数控制是否需要预加载资源
fetchSourceBeforeRender?: boolean; // 是否需要在渲染之前缓存资源,默认是false
currentTime?: number; // 时间,editor, preivew 可以使用,export需要调用update方法
// 该方法接受一个改变currentTime的方法,触发currentTime改变
triggerCurrentTime?: (t: number) => void;
// 在编辑模式下选中多个元素
onSelectElements?: (elementIds: string[]) => void;
// 控制器变化后执行
onControlChangeEnd?: (elementIds: string[]) => void;
// 资源加载进度
onSourceProgress?: (p: number) => void;
// 单个资源加载进度
onEachSourceProgress?: (n: { src: string; p: number; total: number }) => void;
// 暂停事件
onPause?: () => void;
// 初始化成功后执行
initSuccess?: () => void;
// 渲染后执行
callback?: (c: Store) => void;
}属性说明
下面只列出了实例core的属性,:后面的表示属性的数据类型,私有属性都是_开头的
.target?: HTMLElement | null;
canvas 放入的 DOM 容器,默认是 body
.movieId: string
实例的唯一标识,可以是一个随机字符串,默认是'movieId'
.workerPath: string
worker 的地址,默认是/assets/worker/decode.worker.js,导出视频的时候需要使用这个 worker 去解码视频帧
.currentTime: number
当前显示的时间
.times: [number, number] | null
导出的时候可以传入 times 表示导出指定某一段时间的视频,单位是秒
.controlModeType: 'editElement' | 'editMask'
控制器的模式,分为元素编辑模式和遮罩编辑模式,在遮罩编辑模式下只可以编辑遮罩
.bodyContainer: PIXI.Container
body 容器,用于存放 PIXI 元素
.captionContainer: PIXI.Container
字幕容器,用于存放字幕元素
.data: types.MovieData
动画 JSON 数据
.events: events.EventsName
事件名称的实例,事件可以通过触发事件名称进行调用
// 更新视图
pubsub.publish(core.events.UPDATE_STAGE);
// 事件名称和说明:
events.UPDATE_STAGE; // 更新视图
events.VISIBLE_CONTROL; // 控制器显示隐藏
events.STOP_GROUP_DRAG; // 禁用组的框选
events.SORT_CONTAINER; // 重新排序container
events.UPDATE_CONTROL_TARGET; // target尺寸变化后更新控制器
events.UPDATE_CONTROL_LIST; // 控制器更新list
events.TRIGGER_CONTROL; // 触发控制器选中元素
events.TRIGGER_CONTROL_NO_CALLBACK; // 只触发控制器,不调用onSelected回调
events.UPDATE_MOVIE; // 更新Movie.tsx
events.CONTROL_CHANGE_END; // 控制器结束
events.CONTROL_CHANGE_START; // 控制器开始.record: RecordObject
历史记录方法集合,env 为 editor 才会有此参数
export interface RecordItem {
desc: string; // 描述信息
}
export interface RecordObject {
add: (item: RecordItem) => void; // 添加历史记录
debounceAdd: (item: RecordItem) => void; // 使用防抖函数添加历史记录
redo: () => void;
undo: () => void;
manager: RecordManager; // 历史记录的数据管理
}.resourceManage: ResourceManage
资源管理器实例,请参考类:packages\video-core\src\react-pixi\manage\resourceManage.ts
.movieSize: { width: number; height: number }
视频尺寸
.app: Application | undefined
pixijs 实例
.playing: boolean
是否在播放模式下,默认未在播放
.env: 'editor' | 'preview' | 'export'
当前的运行环境,editor 表示编辑器模式下,preview 表示预览模式下,export 表示导出模式下
.groups: types.BaseElement[][]
根据 trackIndex 对数据进行分组后的数据
.zIndexObjects: Record<string, number>
记录元素的 zIndex 数据,方便控制器元素使用
.cacheControlElementIds: string[]
控制器记录元素 ID 的数组
.usedMediaInfo: Record<string, types.UsedMediaItem>
缓存全部的媒体资源视频&音频,用于合成音频使用
.elementReadyMark: Record<string, 'start' | 'success' | 'error'>
记录图层准备成功,此处为了处理 react 中的异步问题,tring 可以是 elementId,或者其他参数
.renderAsyncMark: Record<string, 'start' | 'success' | 'error'>
参考格式:{elementId_time: 'start' | 'success' | 'error'}
绘制的过程是异步的,用于检测是否绘制成功了,绘制之前把元素的 id 和时间放入对象中,绘制成功后时间是 null,如果都绘制完成了标 记应该是{},1 秒都还没绘制出来就不做处理了,所以为了确保性能问题,每个画面的绘制时间必须要控制在 1 秒内
.controlElements: ControlChangeValues[]
记录控制器变化后的数据
export interface ControlChangeValues {
elementId: string; // 目标元素到ID
x: number;
y: number;
alpha?: number;
width: number;
height: number;
rotation: number;
}方法说明
get hideLockData(): number
隐藏和锁定是针对整个轨道而言,返回锁定,隐藏显示元素记录,在计算 group 的时候就要设置该参数
return Record<number, { hide: boolean; lock: boolean }>;getPixiContainerById(ids: string[]): PIXI.Container[]
通过 ids 获取 pixi container 对象
pixiContainerToImageById(id: string): Promise<HTMLImageElement>
通过 id 找到 pixi 的 container,然后再导出成图片
initHideLock(hideLock: Record<number, { hide: boolean; lock: boolean }>)
时间轴中的轨道的隐藏,锁定信息会记录到 movieData._hideLock ={} 中,需要对其进行初始化,_hideLock 中的 key 是 trackIndex
getHideLock(trackIndex: number): { hide: boolean;lock: boolean;}
获取轨道的 hide,lock 参数
setHideLock(params: { trackIndex: number; hide?: boolean; lock?: boolean }, updateMovie?: boolean)
设置指定轨道(trackIndex)的隐藏,锁定,第二个参数表示是否要更新 movie
changeControlMode(modeType: 'editElement' | 'editMask', id: string)
切换编辑模式,分别是元素编辑 和 遮罩编辑。
getFrameItem(elementData: types.BaseElement): types.FrameItem | null
获取当前帧数据,如果当前时间节点有数据将返回对应的帧数据(误差 0.1 秒)
asyncFrameAnimateStatus(elementData: types.BaseElement, status: types.FrameItem | null)
将帧动画的数据和元素的状态进行同步,可以理解为通过帧数据更新元素的显示状态
getFrameStatusByCurrentTime(elementData: types.BaseElement, currentTime?: number): types.FrameItem | null
获取指定时间位置的帧状态,如果第二个参数不传默认使用游标位置的时间
getFrameStatus(elementData: types.BaseElement, relativeTime: number | null): types.FrameItem | null
获取指定时间位置的帧状态
removeTrackMedias(trackIndex: number, resourceId: string)
移除媒体数据,删除元素的时候调用
checkSupportTransition(elementId: string): boolean
判断元素后是否支持插入转场数据,只有相邻的两个视频,图片元素之间才可以插入转场
removeInvalidTransition()
去掉无效的转场动画
play()
播放视频
pause()
暂停视频
sortZIndexNewData()
保存的时候重置 zindex,返回新的数据,之所以会重置 zindex 参数是因为插入新轨道的时候下标可能出现小数,重置会重新更新为整数
step(time: number)
设置视频时间
runAnimate()
执行 play 操作,fps 默认是 32 帧,requestAnimationFrame 的频率可能会在 60fps,这里需要通过 FPS 来播放,之所以不使用 setTimeout 来操 作,是因为 setTimeout 会导致误差很大而且没法纠正时间
updateControl(type: 'updateTarget' | 'updateList' | 'trigger' | 'triggerNoCallback' | 'visible' | 'stopGroupDrag', doms?: HTMLElement[] | SVGElement[] | string[] | { visible: boolean; elementId: string } | boolean)
更新控制器的方法
updateTarget: target 尺寸变化后更新控制器
updateList: targets 列表变化后更新控制器
trigger: 触发元素被选中
triggerNoCallback: 只触发控制器,不调用 onSelected 回调
visible: 显示隐藏控制器 { visible, elementId }
stopGroupDrag: 禁用&启用组的框选
// 触发控制器选中 elementId1
movie.updateControl("trigger", ["elementId1"]);updateSortTracks()
轨道排序更新,需要重新调用此方法去更新 pixi 中 z-index 的问题
getTotalTime()
获取视频的总时长
getElementDataByIds(ids: string[])
通过 ids 获取元素
getElementDataByTypes(types: types.ElementType[], data?: types.MovieData): types.BaseElement[]
通过类型获取元素
getCloneData()
获取 data 的 clone 数据,会重新计算 trackIndex,使其变成整数,从 1 开始
addResource(url: string, styleSize?: types.StyleSize): Promise<types.Resource>
添加资源,资源会同步添加到 resourceManage 缓存起来
export interface StyleSize {
width: number;
height: number;
}addElementByResource(resource: types.Resource, params: { elementType: types.ElementType; time: number; trackIndex?: number; duration?: number })
通过资源数据添加元素
core.addElementByResource(resource, {
elementType: "image", // 插入图片
time: 10, // 从10秒开始
trackIndex: 2, // 插入到第二个轨道
duration: 5, // 持续显示5秒
});addElementNoSource( info: Record<string | 'attrs', any>, params: { elementType: types.ElementType; time: number; trackIndex?: number; duration?: number })
添加那种没有 resource 的资源的元素,比如:text, effect, cutScene 等
update(time?: number)
更新 Movie 组件,比如修改了某个元素的_dirty 参数,可以调用此函数,修改了数据结构也可以调用此方法,export 环境中,使用 update 更新组件,因为在导出过程中,绘制是一个异步的过程,这里需要判断是否绘制成功
capture(): string
截取当前视图的画面,返回 base64 图片
controlChangeStart(elementIds: string[])
控制器选中的时候会触发该方法,不需要单独调用
controlChangeEnd(elementIds: string[])
控制器结束的回调函数,不需要单独调用
destroy()
销毁实例,内存回收
事件相关
onControlChangeEnd: ((elementIds: string[]) => void)
绑定控制器变化结束的事件
onControlChangeStart: ((elementIds: string[]) => void)
绑定控制器开始的事件
onEachSourceProgress: (n: { src: string; p: number; total: number }) => void
每个资源加载进度都会触发该事件
triggerCurrentTime: (t: number) => void
触发 currentTime 变化的事件
triggerRecordSelectElements: (ids: string[]) => void
触发元素选中
React DEMO
import { useEffect, useRef, useState } from 'react';
import './App.less';
import { VideoCoreSDK } from '@video/core/src/react-pixi/CoreSDK.tsx';
import { data } from './data';
import { Slider } from '@douyinfe/semi-ui';
function App() {
const videoCoreRef = useRef<any>(null);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
useEffect(() => {
// 确保只创建一次实例
if (!videoCoreRef.current) {
const vc = new VideoCoreSDK({
data: data, // 必填,工程数据
target: document.getElementById('video-container')!, // 必填,渲染目标
movieId: 'movieID', // 实例内部唯一标识,多个实例不能相同
env: 'preview', // 必填,渲染模式:editor(编辑模式,可拖动编辑); preview(预览模式,只能预览,内存消耗更小)
registerId: 'H5DS', // 必填,注册ID,和域名进行绑定的
resourceHost: 'https://cdn.h5ds.com',
EModuleEffectSourcePath: '/assets/effectcanvas/', // 特效资源模块加载路径
workerPath: 'assets', // 选填,decode.worker.js的引用目录,默认是assets
scale: 1, // 选填,画布缩放比例,默认是1
currentTime, // 选填,默认开始时间是0
plugins: [], // 选填,扩展插件
triggerCurrentTime: (currentTime: number) => {
setCurrentTime(currentTime);
},
});
vc.init().then((core: any) => {
videoCoreRef.current = core;
setDuration(core.getTotalTime());
});
}
// 清理函数,在组件卸载时销毁实例
return () => {
if (videoCoreRef.current) {
// videoCoreRef.current.destroy();
videoCoreRef.current = null;
}
};
}, []);
return (
<div
style={{
width: '960px',
}}
>
<div id="video-container"></div>
<div>
{!!duration && (
<Slider
value={currentTime}
max={duration}
step={0.01}
onChange={value => videoCoreRef.current?.triggerCurrentTime(value)}
/>
)}
</div>
<div className="list">
<a
onClick={() => {
videoCoreRef.current?.play();
}}
>
play
</a>
<a
onClick={() => {
videoCoreRef.current?.pause();
}}
>
pause
</a>
</div>
</div>
);
}
export default App;