octopus-svga
Version:
高性能SVGA动效播放器,支持Web/微信小程序/支付宝小程序/抖音小程序,设计目标是 解析速度更快、体积更小、性能更高、兼容性更高、功能更丰富。
860 lines (845 loc) • 22.7 kB
TypeScript
import { RawImage, PlatformImage, PlatformCanvas, PlatformOffscreenCanvas, Bitmap, OctopusPlatform, OctopusPlatformPlugins, OctopusPlatformPluginOptions } from 'octopus-platform';
type PlatformRenderingContext2D = OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;
interface RawImages {
[key: string]: RawImage;
}
interface PlatformImages {
[key: string]: PlatformImage | ImageBitmap;
}
interface PainterActionModel {
type: "C" | "O";
clear: "CL" | "RE";
}
type PainterMode = "poster" | "animation";
interface CanvasSize {
width: number;
height: number;
}
interface TransformScale {
scaleX: number;
scaleY: number;
translateX: number;
translateY: number;
}
declare const enum PLAYER_FILL_MODE {
/**
* 播放完成后停在首帧
*/
FORWARDS = "forwards",
/**
* 播放完成后停在尾帧
*/
BACKWARDS = "backwards",
/**
* 播放完成后清空画布
*/
NONE = "none"
}
declare const enum PLAYER_PLAY_MODE {
/**
* 顺序播放
*/
FORWARDS = "forwards",
/**
* 倒序播放
*/
FALLBACKS = "fallbacks"
}
declare const enum PLAYER_CONTENT_MODE {
/**
* 缩放图片填满 Canvas,图片可能出现变形
*/
FILL = "fill",
/**
* 等比例缩放至整张图片填满 Canvas,不足部分留白
*/
ASPECT_FIT = "aspect-fit",
/**
* 等比例缩放至图片填满 Canvas,超出部分不展示
*/
ASPECT_FILL = "aspect-fill",
/**
* 图片对齐 Canvas 中心,超出部分不展示
*/
CENTER = "center"
}
type PlayerEventCallback = () => void;
type PlayerProcessEventCallback = (percent: number, frame: number) => void;
type PosterEventCallback = () => void;
interface PlayerConfig {
/**
* 循环次数,默认值 0(无限循环)
*/
loop: number;
/**
* 最后停留的目标模式,类似于 animation-fill-mode,默认值 forwards。
*/
fillMode: PLAYER_FILL_MODE;
/**
* 播放模式,默认值 forwards
*/
playMode: PLAYER_PLAY_MODE;
/**
* 填充模式,类似于 content-mode。
*/
contentMode: PLAYER_CONTENT_MODE;
/**
* 开始播放的帧数,默认值 0
*/
startFrame: number;
/**
* 结束播放的帧数,默认值 0
*/
endFrame: number;
/**
* 循环播放的开始帧,默认值 0
*/
loopStartFrame: number;
}
type PlayerConfigOptions = Partial<PlayerConfig> & {
/**
* 主屏,播放动画的 Canvas 元素
*/
container: string;
/**
* 副屏,播放动画的 Canvas 元素
*/
secondary?: string;
};
interface PosterConfig {
/**
* 主屏,绘制海报的 Canvas 元素
*/
container: string;
/**
* 填充模式,类似于 content-mode。
*/
contentMode: PLAYER_CONTENT_MODE;
/**
* 绘制成海报的帧,默认是0。
*/
frame: number;
}
type PosterConfigOptions = Partial<PosterConfig>;
declare namespace PlatformVideo {
interface VideoSize {
width: number;
height: number;
}
interface Rect {
x: number;
y: number;
width: number;
height: number;
}
interface Transform {
a: number;
b: number;
c: number;
d: number;
tx: number;
ty: number;
}
const enum LINE_CAP_CODE {
BUTT = 0,
ROUND = 1,
SQUARE = 2
}
const enum LINE_CAP {
BUTT = "butt",
ROUND = "round",
SQUARE = "square"
}
const enum LINE_JOIN_CODE {
MITER = 0,
ROUND = 1,
BEVEL = 2
}
const enum LINE_JOIN {
MITER = "miter",
ROUND = "round",
BEVEL = "bevel"
}
type RGBA<R extends number, G extends number, B extends number, A extends number> = `rgba(${R}, ${G}, ${B}, ${A})`;
const enum SHAPE_TYPE_CODE {
SHAPE = 0,
RECT = 1,
ELLIPSE = 2,
KEEP = 3
}
const enum SHAPE_TYPE {
SHAPE = "shape",
RECT = "rect",
ELLIPSE = "ellipse"
}
interface VideoStyles {
fill: RGBA<number, number, number, number> | null;
stroke: RGBA<number, number, number, number> | null;
strokeWidth: number | null;
lineCap: CanvasLineCap | null;
lineJoin: CanvasLineJoin | null;
miterLimit: number | null;
lineDash: number[] | null;
}
interface ShapePath {
d: string;
}
interface RectPath {
x: number;
y: number;
width: number;
height: number;
cornerRadius: number;
}
interface EllipsePath {
x: number;
y: number;
radiusX: number;
radiusY: number;
}
interface VideoShapeShape {
type: SHAPE_TYPE.SHAPE;
path: ShapePath;
styles: VideoStyles;
transform: Transform;
}
interface VideoShapeRect {
type: SHAPE_TYPE.RECT;
path: RectPath;
styles: VideoStyles;
transform: Transform;
}
interface VideoShapeEllipse {
type: SHAPE_TYPE.ELLIPSE;
path: EllipsePath;
styles: VideoStyles;
transform: Transform;
}
type VideoFrameShape = VideoShapeShape | VideoShapeRect | VideoShapeEllipse;
interface HiddenVideoFrame {
alpha: 0;
}
interface VideoFrame {
alpha: number;
transform: Transform | null;
layout: Rect;
shapes: VideoFrameShape[];
}
interface VideoSprite {
imageKey: string;
frames: Array<VideoFrame | HiddenVideoFrame>;
}
interface VideoParams {
viewBoxWidth: number;
viewBoxHeight: number;
fps: number;
frames: number;
}
interface Video {
/**
* svga 版本号
*/
version: string;
/**
* svga 文件名
*/
filename: string;
/**
* svga 尺寸
*/
size: VideoSize;
/**
* svga 帧率
*/
fps: number;
/**
* 是否可以编辑svga文件
*/
locked: boolean;
/**
* svga 帧数
*/
frames: number;
/**
* svga 二进制图片合集
*/
images: RawImages;
/**
* svga 动态元素
*/
dynamicElements: PlatformImages;
/**
* svga 关键帧信息
*/
sprites: VideoSprite[];
}
}
/**
* SVGA 下载解析器
*/
declare class Parser {
/**
* 解压视频源文件
* @param data
* @returns
*/
static decompress(data: ArrayBufferLike): ArrayBufferLike;
/**
* 解析视频实体
* @param data 视频二进制数据
* @param url 视频地址
* @param needDecompress 是否解压
* @returns
*/
static parseVideo(data: ArrayBufferLike, url: string, needDecompress?: boolean): PlatformVideo.Video;
/**
* 读取文件资源
* @param url 文件资源地址
* @returns
*/
static download(url: string): Promise<ArrayBuffer>;
/**
* 通过 url 下载并解析 SVGA 文件
* @param url SVGA 文件的下载链接
* @returns Promise<SVGA 数据源
*/
static load(url: string): Promise<PlatformVideo.Video>;
}
declare class Painter {
private readonly mode;
/**
* 主屏的 Canvas 元素
* Front Screen
*/
F: PlatformCanvas | PlatformOffscreenCanvas | null;
/**
* 主屏的 Context 对象
* Front Context
*/
FC: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null;
/**
* 副屏的 Canvas 元素
* Background Screen
*/
B: PlatformCanvas | PlatformOffscreenCanvas | null;
/**
* 副屏的 Context 对象
* Background Context
*/
BC: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null;
/**
* 画布的宽度
*/
W: number;
/**
* 画布的高度
*/
H: number;
/**
* 粉刷模式
*/
private model;
/**
* 渲染器实例
*/
private renderer;
/**
*
* @param mode
* - poster: 海报模式
* - animation: 动画模式
* - 默认为 animation
* @param W 海报模式必须传入
* @param H 海报模式必须传入
*/
constructor(mode?: PainterMode, width?: number, height?: number);
/**
* 设置 Canvas 的处理模式
* - C:代表 Canvas
* - O:代表 OffscreenCanvas
*/
private setActionModel;
/**
* 注册画笔,根据环境判断生成最优的绘制方式
* @param selector
* @param ofsSelector
* @param component
*/
register(selector: string, ofsSelector?: string, component?: any): Promise<void>;
clearContainer: () => void;
clearSecondary: () => void;
resize: (contentMode: PLAYER_CONTENT_MODE, videoSize: PlatformVideo.VideoSize) => void;
draw: (videoEntity: PlatformVideo.Video, materials: Map<string, Bitmap>, dynamicMaterials: Map<string, Bitmap>, currentFrame: number, head: number, tail: number) => void;
stick: () => void;
/**
* 销毁画笔
*/
destroy(): void;
}
declare class ResourceManager {
private readonly painter;
/**
* 判断是否是 ImageBitmap
* @param img
* @returns
*/
private static isBitmap;
/**
* 释放内存资源(图片)
* @param img
*/
private static releaseOne;
private caches;
/**
* 动态素材
*/
readonly dynamicMaterials: Map<string, Bitmap>;
/**
* 素材
*/
readonly materials: Map<string, Bitmap>;
/**
* 已清理Image对象的坐标
*/
private point;
constructor(painter: Painter);
/**
* 创建图片标签
* @returns
*/
private createImage;
/**
* 将 ImageBitmap 插入到 caches
* @param img
*/
private inertBitmapIntoCaches;
/**
* 加载额外的图片资源
* @param source 资源内容/地址
* @param filename 文件名称
* @returns
*/
loadExtImage(source: string | Uint8Array, filename: string): Promise<PlatformImage | ImageBitmap>;
/**
* 加载图片集
* @param images 图片数据
* @param filename 文件名称
* @returns
*/
loadImagesWithRecord(images: RawImages, filename: string, type?: "normal" | "dynamic"): Promise<void>;
/**
* 释放图片资源
*/
release(): void;
/**
* 整理图片资源,将重复的图片资源移除
*/
private tidyUp;
/**
* 清理图片资源
*/
cleanup(): void;
}
/**
* SVGA 播放器
*/
declare class Player {
/**
* SVGA 元数据
* Video Entity
*/
private entity;
/**
* 当前配置项
*/
private readonly config;
/**
* 资源管理器
*/
resource: ResourceManager | null;
/**
* 刷头实例
*/
readonly painter: Painter;
/**
* 动画实例
*/
private readonly animator;
/**
* 设置配置项
* @param options 可配置项
* @property container 主屏,播放动画的 Canvas 元素
* @property secondary 副屏,播放动画的 Canvas 元素
* @property loop 循环次数,默认值 0(无限循环)
* @property fillMode 最后停留的目标模式,类似于 animation-fill-mode,接受值 forwards 和 fallbacks,默认值 forwards。
* @property playMode 播放模式,接受值 forwards 和 fallbacks ,默认值 forwards。
* @property startFrame 单个循环周期内开始播放的帧数,默认值 0
* @property endFrame 单个循环周期内结束播放的帧数,默认值 0
* @property loopStartFrame 循环播放的开始帧,仅影响第一个周期的开始帧,默认值 0
* @property enableInObserver 是否启用 IntersectionObserver 监听容器是否处于浏览器视窗内,默认值 false
*/
setConfig(options: string | PlayerConfigOptions, component?: any): Promise<void>;
/**
* 更新配置
* @param key
* @param value
*/
setItem<T extends keyof PlayerConfig>(key: T, value: PlayerConfig[T]): void;
/**
* 装载 SVGA 数据元
* @param videoEntity SVGA 数据源
* @returns Promise<void>
*/
mount(videoEntity: PlatformVideo.Video): Promise<void>;
/**
* 开始播放事件回调
* @param frame
*/
onStart?: PlayerEventCallback;
/**
* 重新播放事件回调
* @param frame
*/
onResume?: PlayerEventCallback;
/**
* 暂停播放事件回调
* @param frame
*/
onPause?: PlayerEventCallback;
/**
* 停止播放事件回调
* @param frame
*/
onStop?: PlayerEventCallback;
/**
* 播放中事件回调
* @param percent
* @param frame
* @param frames
*/
onProcess?: PlayerProcessEventCallback;
/**
* 结束播放事件回调
* @param frame
*/
onEnd?: PlayerEventCallback;
/**
* 开始播放
*/
start(): void;
/**
* 重新播放
*/
resume(): void;
/**
* 暂停播放
*/
pause(): void;
/**
* 停止播放
*/
stop(): void;
/**
* 销毁实例
*/
destroy(): void;
/**
* 跳转到指定帧
* @param frame 目标帧
* @param andPlay 是否立即播放
*/
stepToFrame(frame: number, andPlay?: boolean): void;
/**
* 跳转到指定百分比
* @param percent 目标百分比
* @param andPlay 是否立即播放
*/
stepToPercentage(percent: number, andPlay?: boolean): void;
/**
* 开始绘制动画
*/
private startAnimation;
}
declare class Poster {
/**
* SVGA 元数据
* Video Entity
*/
private entity;
/**
* 海报配置项
*/
private readonly config;
/**
* 是否配置完成
*/
private isConfigured;
/**
* 刷头实例
*/
readonly painter: Painter;
/**
* 资源管理器
*/
resource: ResourceManager | null;
constructor(width: number, height: number);
/**
* 注册 SVGA 海报
* @param selector 容器选择器
* @param component 组件
*/
private register;
/**
* 设置配置项
* @param options 可配置项
*/
setConfig(options?: string | PosterConfigOptions, component?: any): Promise<void>;
/**
* 修改内容模式
* @param contentMode
*/
setContentMode(contentMode: PLAYER_CONTENT_MODE): void;
/**
* 设置当前帧
* @param frame
*/
setFrame(frame: number): void;
/**
* 装载 SVGA 数据元
* @param videoEntity SVGA 数据源
* @param currFrame
* @returns
*/
mount(videoEntity: PlatformVideo.Video): Promise<void>;
/**
* 绘制海报
*/
draw(): void;
/**
* 获取海报的 ImageData 数据
*/
toImageData(): ImageData;
/**
* 销毁海报
*/
destroy(): void;
}
type PlatformProperties = "now" | "path" | "remote" | "local" | "decode" | "image" | "rAF" | "getSelector" | "getCanvas" | "getOfsCanvas";
declare class EnhancedPlatform extends OctopusPlatform<PlatformProperties> {
now: OctopusPlatformPlugins["now"];
path: OctopusPlatformPlugins["path"];
remote: OctopusPlatformPlugins["remote"];
local: OctopusPlatformPlugins["local"];
decode: OctopusPlatformPlugins["decode"];
image: OctopusPlatformPlugins["image"];
rAF: OctopusPlatformPlugins["rAF"];
getSelector: OctopusPlatformPlugins["getSelector"];
getCanvas: OctopusPlatformPlugins["getCanvas"];
getOfsCanvas: OctopusPlatformPlugins["getOfsCanvas"];
constructor();
installPlugin(plugin: OctopusPlatformPluginOptions<PlatformProperties>): void;
}
declare const platform: EnhancedPlatform;
interface IQrCodeImgOptions {
/**
* 二维码内容
*/
code: string;
/**
* 二维码的大小
*/
size: number;
/**
* 二维码的码元 二维码横向有多少个小点(1 - 40)
*/
typeNumber?: number;
/**
* 二维码的纠错等级
* L: 7%(错误字码在 7% 以内可被修正, 容错率较低不建议使用)
* M: 15%(错误字码在 15% 以内可被修正, 容错率较低不建议使用)
* Q: 25%(错误字码在 25% 以内可被修正)
* H: 30%(错误字码在 30% 以内可被修正)
*/
correctLevel?: "L" | "M" | "Q" | "H";
/**
* 二维码颜色,仅支持 六位的十六进制颜色值,暂不支持透明色 (仅对二维码生效)
*/
codeColor?: string;
/**
* 二维码背景颜色,仅支持 六位的十六进制 颜色值。暂不支持透明色 (仅对二维码生效)
*/
backgroundColor?: string;
}
declare function generateImageBufferFromCode(options: IQrCodeImgOptions): any;
declare function generateImageFromCode(options: IQrCodeImgOptions): string;
declare function createBufferOfImageData(imageData: ImageData): Uint8Array<ArrayBufferLike>;
/**
* @deprecated 请使用 createBufferOfImageData 代替,此方法可能在后续版本中移除
*/
declare const getBufferFromImageData: typeof createBufferOfImageData;
declare function createImageDataUrl(imageData: ImageData): string;
/**
* @deprecated 请使用 createImageDataUrl 代替,此方法可能在后续版本中移除
*/
declare const getDataURLFromImageData: typeof createImageDataUrl;
/**
* 检查数据是否为zlib压缩格式
* @param data 待检查的二进制数据
* @returns 是否为zlib压缩格式
*/
declare function isZlibCompressed(data: Uint8Array): boolean;
interface Bucket {
origin: string;
local: string;
entity: PlatformVideo.Video | null;
promise: Promise<ArrayBufferLike> | null;
}
interface NeedUpdatePoint {
action: "remove" | "add";
start: number;
end: number;
}
type LoadMode = "fast" | "whole";
interface VideoManagerOptions {
preprocess: (bucket: Bucket) => Promise<ArrayBufferLike>;
postprocess: (bucket: Bucket, buff: ArrayBufferLike) => Promise<PlatformVideo.Video> | PlatformVideo.Video;
cleanup: (buckets: Bucket[]) => Promise<void> | void;
}
declare class VideoManager {
/**
* 视频池的当前指针位置
*/
private point;
/**
* 视频的最大留存数量,其他视频将放在磁盘上缓存
*/
private maxRemain;
/**
* 留存视频的开始指针位置
*/
private remainStart;
/**
* 留存视频的结束指针位置
*/
private remainEnd;
/**
* 视频加载模式
* - 快速加载模式:可保证当前视频加载完成后,尽快播放;其他请求将使用Promise的方式保存在bucket中,以供后续使用
* - 完整加载模式:可保证所有视频加载完成,确保播放切换的流畅性
*/
private loadMode;
/**
* 视频池的所有数据
*/
private buckets;
private readonly options;
/**
* 获取视频池大小
*/
get size(): number;
constructor(loadMode: LoadMode, options?: Partial<VideoManagerOptions>);
/**
* 更新留存指针位置
*/
private updateRemainRange;
/**
* 指针是否在留存空间内
* @param point
* @returns
*/
private includeRemainRange;
private downloadAndParseVideo;
/**
* 创建bucket
* @param url 远程地址
* @param point 指针位置
* @param needDownloadAndParse 是否需要下载并解析
* @returns
*/
private createBucket;
/**
* 预加载视频到本地磁盘中
* @param urls 视频远程地址
* @param point 当前指针位置
* @param maxRemain 最大留存数量
*/
prepare(urls: string[], point?: number, maxRemain?: number): Promise<void>;
/**
* 获取当前帧的bucket
* @returns
*/
get(): Promise<Bucket>;
/**
* 获取当前的指针位置
* @returns
*/
getPoint(): number;
/**
* 获取指定位置的bucket
* @param pos
* @returns
*/
go(point: number): Promise<Bucket>;
/**
* 清理所有的bucket
* @returns
*/
clear(needRemoveFiles?: boolean): Promise<void>;
}
interface VideoEditorOptions {
mode?: "R" | "A";
}
declare class VideoEditor {
private readonly painter;
private readonly resource;
private readonly entity;
constructor(painter: Painter, resource: ResourceManager, entity: PlatformVideo.Video);
private set;
/**
* 获取自定义编辑器
* @returns
*/
getContext(): OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D | null;
/**
* 是否是有效的Key
* @param key
* @returns
*/
hasValidKey(key: string): boolean;
/**
* 加载并缓存图片
* @param source
* @param url
* @returns
*/
loadImage(source: Uint8Array | string, url: string): Promise<PlatformImage | ImageBitmap>;
/**
* 创建画布图片
* @param key
* @param context
* @param options
* @returns
*/
setCanvas(key: string, context: PlatformRenderingContext2D, options?: VideoEditorOptions & {
width?: number;
height?: number;
}): Promise<void>;
/**
* 创建二进制图片
* @param key
* @param buff
* @param options
* @returns
*/
setImage(key: string, url: string, options?: VideoEditorOptions): Promise<void>;
/**
* 创建二维码图片
* @param key
* @param code
* @param options
* @returns
*/
setQRCode(key: string, code: string, options: VideoEditorOptions & Omit<IQrCodeImgOptions, "code">): Promise<void>;
}
export { EnhancedPlatform, PLAYER_CONTENT_MODE, PLAYER_FILL_MODE, PLAYER_PLAY_MODE, Painter, Parser, PlatformVideo, Player, Poster, VideoEditor, VideoManager, createBufferOfImageData, createImageDataUrl, generateImageBufferFromCode, generateImageFromCode, getBufferFromImageData, getDataURLFromImageData, isZlibCompressed, platform };
export type { Bucket, CanvasSize, IQrCodeImgOptions, LoadMode, NeedUpdatePoint, PainterActionModel, PainterMode, PlatformImages, PlatformProperties, PlatformRenderingContext2D, PlayerConfig, PlayerConfigOptions, PlayerEventCallback, PlayerProcessEventCallback, PosterConfig, PosterConfigOptions, PosterEventCallback, RawImages, TransformScale, VideoManagerOptions };