tav-media
Version:
Cross platform media editing framework
507 lines (506 loc) • 19.3 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var PAGView_1;
import { EventManager } from './utils/event-manager';
import { PAGSurface } from './pag-surface';
import { destroyVerify } from './utils/decorators';
import { isOffscreenCanvas } from './utils/canvas';
import { BackendContext } from './core/backend-context';
import { PAGModule } from './pag-module';
import { RenderCanvas } from './core/render-canvas';
import { Clock } from './utils/clock';
let PAGView = PAGView_1 = class PAGView {
constructor(pagPlayer, canvasElement, file) {
/**
* The repeat count of player.
*/
this.repeatCount = 0;
/**
* Indicates whether or not this pag view is playing.
*/
this.isPlaying = false;
/**
* Indicates whether or not this pag view is destroyed.
*/
this.isDestroyed = false;
this.pagViewOptions = {
useScale: true,
useCanvas2D: false,
firstFrame: true,
};
this.renderCanvas = null;
this.pagGlContext = null;
this.frameRate = 0;
this.pagSurface = null;
this.currentFrame = -1;
this.timer = null;
this.flushingNextFrame = false;
this.playTime = 0;
this.startTime = 0;
this.repeatedTimes = 0;
this.eventManager = new EventManager();
this.rawWidth = 0;
this.rawHeight = 0;
this.debugData = {
FPS: 0,
flushTime: 0,
};
this.fpsBuffer = [];
this.player = pagPlayer;
this.canvasElement = canvasElement;
this.file = file;
}
/**
* Create pag view.
* @param file pag file.
* @param canvas target render canvas.
* @param initOptions pag view options
* @returns
*/
static init(file, canvas, initOptions = {}) {
return __awaiter(this, void 0, void 0, function* () {
let canvasElement = null;
if (typeof canvas === 'string') {
canvasElement = document.getElementById(canvas.substr(1));
}
else if (canvas instanceof HTMLCanvasElement) {
canvasElement = canvas;
}
else if (isOffscreenCanvas(canvas)) {
canvasElement = canvas;
}
if (!canvasElement)
throw new Error('Canvas is not found!');
const pagPlayer = PAGModule.PAGPlayer.create();
const pagView = new PAGView_1(pagPlayer, canvasElement, file);
pagView.pagViewOptions = Object.assign(Object.assign({}, pagView.pagViewOptions), initOptions);
if (pagView.pagViewOptions.useCanvas2D) {
PAGModule.globalCanvas.retain();
pagView.pagGlContext = BackendContext.from(PAGModule.globalCanvas.glContext);
}
else {
pagView.renderCanvas = RenderCanvas.from(canvasElement);
pagView.renderCanvas.retain();
pagView.pagGlContext = BackendContext.from(pagView.renderCanvas.glContext);
}
pagView.resetSize(pagView.pagViewOptions.useScale);
pagView.frameRate = file.frameRate();
pagView.pagSurface = this.makePAGSurface(pagView.pagGlContext, pagView.rawWidth, pagView.rawHeight);
pagView.player.setSurface(pagView.pagSurface);
pagView.player.setComposition(file);
pagView.setProgress(0);
if (pagView.pagViewOptions.firstFrame) {
yield pagView.flush();
pagView.currentFrame = 0;
}
return pagView;
});
}
static makePAGSurface(pagGlContext, width, height) {
if (!pagGlContext.makeCurrent())
throw new Error('Make context current fail!');
const pagSurface = PAGSurface.fromRenderTarget(0, width, height, true);
pagGlContext.clearCurrent();
return pagSurface;
}
/**
* The duration of current composition in microseconds.
*/
duration() {
return this.file.duration();
}
/**
* Adds a listener to the set of listeners that are sent events through the life of an animation,
* such as start, repeat, and end.
*/
addListener(eventName, listener) {
return this.eventManager.on(eventName, listener);
}
/**
* Removes a listener from the set listening to this animation.
*/
removeListener(eventName, listener) {
return this.eventManager.off(eventName, listener);
}
/**
* Start the animation.
*/
play() {
return __awaiter(this, void 0, void 0, function* () {
if (this.isPlaying)
return;
if (this.playTime === 0) {
this.eventManager.emit("onAnimationStart" /* PAGViewListenerEvent.onAnimationStart */, this);
}
this.eventManager.emit("onAnimationPlay" /* PAGViewListenerEvent.onAnimationPlay */, this);
if (this.currentFrame === 0) {
this.eventManager.emit("onAnimationUpdate" /* PAGViewListenerEvent.onAnimationUpdate */, this);
}
this.isPlaying = true;
this.startTime = this.getNowTime() * 1000 - this.playTime;
yield this.flushLoop();
});
}
/**
* Pause the animation.
*/
pause() {
if (!this.isPlaying)
return;
this.clearTimer();
this.isPlaying = false;
this.eventManager.emit("onAnimationPause" /* PAGViewListenerEvent.onAnimationPause */, this);
}
/**
* Stop the animation.
*/
stop(notification = true) {
return __awaiter(this, void 0, void 0, function* () {
this.clearTimer();
this.playTime = 0;
this.player.setProgress(0);
this.currentFrame = 0;
yield this.flush();
this.isPlaying = false;
if (notification) {
this.eventManager.emit("onAnimationCancel" /* PAGViewListenerEvent.onAnimationCancel */, this);
}
});
}
/**
* Set the number of times the animation will repeat. The default is 1, which means the animation
* will play only once. 0 means the animation will play infinity times.
*/
setRepeatCount(repeatCount) {
this.repeatCount = repeatCount < 0 ? 0 : repeatCount - 1;
}
/**
* Returns the current progress of play position, the value is from 0.0 to 1.0. It is applied only
* when the composition is not null.
*/
getProgress() {
return this.player.getProgress();
}
/**
* Set the progress of play position, the value is from 0.0 to 1.0.
*/
setProgress(progress) {
this.playTime = progress * this.duration();
this.startTime = this.getNowTime() * 1000 - this.playTime;
if (!this.isPlaying) {
this.player.setProgress(progress);
}
return progress;
}
/**
* Return the value of videoEnabled property.
*/
videoEnabled() {
return this.player.videoEnabled();
}
/**
* If set false, will skip video layer drawing.
*/
setVideoEnabled(enable) {
this.player.setVideoEnabled(enable);
}
/**
* If set to true, PAG renderer caches an internal bitmap representation of the static content for
* each layer. This caching can increase performance for layers that contain complex vector content.
* The execution speed can be significantly faster depending on the complexity of the content, but
* it requires extra graphics memory. The default value is true.
*/
cacheEnabled() {
return this.player.cacheEnabled();
}
/**
* Set the value of cacheEnabled property.
*/
setCacheEnabled(enable) {
this.player.setCacheEnabled(enable);
}
/**
* This value defines the scale factor for internal graphics caches, ranges from 0.0 to 1.0. The
* scale factors less than 1.0 may result in blurred output, but it can reduce the usage of graphics
* memory which leads to better performance. The default value is 1.0.
*/
cacheScale() {
return this.player.cacheScale();
}
/**
* Set the value of cacheScale property.
*/
setCacheScale(value) {
this.player.setCacheScale(value);
}
/**
* The maximum frame rate for rendering. If set to a value less than the actual frame rate from
* PAGFile, it drops frames but increases performance. Otherwise, it has no effect. The default
* value is 60.
*/
maxFrameRate() {
return this.player.maxFrameRate();
}
/**
* Set the maximum frame rate for rendering.
*/
setMaxFrameRate(value) {
this.player.setMaxFrameRate(value);
}
/**
* Returns the current scale mode.
*/
scaleMode() {
return this.player.scaleMode();
}
/**
* Specifies the rule of how to scale the pag content to fit the surface size. The matrix
* changes when this method is called.
*/
setScaleMode(value) {
this.player.setScaleMode(value);
}
/**
* Call this method to render current position immediately. If the play() method is already
* called, there is no need to call it. Returns true if the content has changed.
*/
flush() {
return __awaiter(this, void 0, void 0, function* () {
const clock = new Clock();
const res = yield this.player.flushInternal((res) => {
var _a, _b;
if (this.pagViewOptions.useCanvas2D && res && PAGModule.globalCanvas.canvas) {
if (!this.canvasContext)
this.canvasContext = (_a = this.canvasElement) === null || _a === void 0 ? void 0 : _a.getContext('2d');
const compositeOperation = this.canvasContext.globalCompositeOperation;
this.canvasContext.globalCompositeOperation = 'copy';
(_b = this.canvasContext) === null || _b === void 0 ? void 0 : _b.drawImage(PAGModule.globalCanvas.canvas, 0, PAGModule.globalCanvas.canvas.height - this.rawHeight, this.rawWidth, this.rawHeight, 0, 0, this.canvasContext.canvas.width, this.canvasContext.canvas.height);
this.canvasContext.globalCompositeOperation = compositeOperation;
}
clock.mark('flush');
this.setDebugData({ flushTime: clock.measure('', 'flush') });
this.updateFPS();
});
this.eventManager.emit("onAnimationUpdate" /* PAGViewListenerEvent.onAnimationUpdate */, this);
if (res) {
this.eventManager.emit("onAnimationFlushed" /* PAGViewListenerEvent.onAnimationFlushed */, this);
}
return res;
});
}
/**
* Free the cache created by the pag view immediately. Can be called to reduce memory pressure.
*/
freeCache() {
var _a;
(_a = this.pagSurface) === null || _a === void 0 ? void 0 : _a.freeCache();
}
/**
* Returns the current PAGComposition for PAGView to render as content.
*/
getComposition() {
return this.player.getComposition();
}
/**
* Sets a new PAGComposition for PAGView to render as content.
* Note: If the composition is already added to another PAGView, it will be removed from
* the previous PAGView.
*/
setComposition(pagComposition) {
this.player.setComposition(pagComposition);
}
/**
* Returns a copy of current matrix.
*/
matrix() {
return this.player.matrix();
}
/**
* Set the transformation which will be applied to the composition. The scaleMode property
* will be set to PAGScaleMode::None when this method is called.
*/
setMatrix(matrix) {
this.player.setMatrix(matrix);
}
getLayersUnderPoint(localX, localY) {
return this.player.getLayersUnderPoint(localX, localY);
}
/**
* Update size when changed canvas size.
*/
updateSize() {
var _a;
if (!this.canvasElement) {
throw new Error('Canvas element is not found!');
}
this.rawWidth = this.canvasElement.width;
this.rawHeight = this.canvasElement.height;
if (!this.pagGlContext)
return;
const pagSurface = PAGView_1.makePAGSurface(this.pagGlContext, this.rawWidth, this.rawHeight);
this.player.setSurface(pagSurface);
(_a = this.pagSurface) === null || _a === void 0 ? void 0 : _a.destroy();
this.pagSurface = pagSurface;
}
/**
* Prepares the player for the next flush() call. It collects all CPU tasks from the current
* progress of the composition and runs them asynchronously in parallel. It is usually used for
* speeding up the first frame rendering.
*/
prepare() {
return this.player.prepare();
}
destroy() {
var _a, _b, _c;
this.clearTimer();
this.player.destroy();
(_a = this.pagSurface) === null || _a === void 0 ? void 0 : _a.destroy();
if (this.pagViewOptions.useCanvas2D) {
PAGModule.globalCanvas.release();
}
else {
(_b = this.renderCanvas) === null || _b === void 0 ? void 0 : _b.release();
}
(_c = this.pagGlContext) === null || _c === void 0 ? void 0 : _c.destroy();
this.pagGlContext = null;
this.canvasContext = null;
this.canvasElement = null;
this.isDestroyed = true;
}
getDebugData() {
return this.debugData;
}
setDebugData(data) {
this.debugData = Object.assign(Object.assign({}, this.debugData), data);
}
flushLoop() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.isPlaying) {
return;
}
this.timer = window.requestAnimationFrame(() => __awaiter(this, void 0, void 0, function* () {
yield this.flushLoop();
}));
if (this.flushingNextFrame)
return;
yield this.flushNextFrame();
});
}
flushNextFrame() {
return __awaiter(this, void 0, void 0, function* () {
this.flushingNextFrame = true;
const duration = this.duration();
this.playTime = this.getNowTime() * 1000 - this.startTime;
const currentFrame = Math.floor((this.playTime / 1000000) * this.frameRate);
const count = Math.floor(this.playTime / duration);
if (this.repeatCount >= 0 && count > this.repeatCount) {
this.clearTimer();
this.player.setProgress(1);
yield this.flush();
this.playTime = 0;
this.isPlaying = false;
this.repeatedTimes = 0;
this.flushingNextFrame = false;
this.eventManager.emit("onAnimationEnd" /* PAGViewListenerEvent.onAnimationEnd */, this);
return true;
}
if (this.repeatedTimes === count && this.currentFrame === currentFrame) {
this.flushingNextFrame = false;
return false;
}
if (this.repeatedTimes < count) {
this.eventManager.emit("onAnimationRepeat" /* PAGViewListenerEvent.onAnimationRepeat */, this);
}
this.player.setProgress((this.playTime % duration) / duration);
const res = yield this.flush();
this.currentFrame = currentFrame;
this.repeatedTimes = count;
this.flushingNextFrame = false;
return res;
});
}
getNowTime() {
try {
return performance.now();
}
catch (e) {
return Date.now();
}
}
clearTimer() {
if (this.timer) {
window.cancelAnimationFrame(this.timer);
this.timer = null;
}
}
resetSize(useScale = true) {
if (!this.canvasElement) {
throw new Error('Canvas element is not found!');
}
if (!useScale || isOffscreenCanvas(this.canvasElement)) {
this.rawWidth = this.canvasElement.width;
this.rawHeight = this.canvasElement.height;
return;
}
let displaySize;
const canvas = this.canvasElement;
const styleDeclaration = window.getComputedStyle(canvas, null);
const computedSize = {
width: Number(styleDeclaration.width.replace('px', '')),
height: Number(styleDeclaration.height.replace('px', '')),
};
if (computedSize.width > 0 && computedSize.height > 0) {
displaySize = computedSize;
}
else {
const styleSize = {
width: Number(canvas.style.width.replace('px', '')),
height: Number(canvas.style.height.replace('px', '')),
};
if (styleSize.width > 0 && styleSize.height > 0) {
displaySize = styleSize;
}
else {
displaySize = {
width: canvas.width,
height: canvas.height,
};
}
}
canvas.style.width = `${displaySize.width}px`;
canvas.style.height = `${displaySize.height}px`;
this.rawWidth = displaySize.width * window.devicePixelRatio;
this.rawHeight = displaySize.height * window.devicePixelRatio;
canvas.width = this.rawWidth;
canvas.height = this.rawHeight;
}
updateFPS() {
let now;
try {
now = performance.now();
}
catch (e) {
now = Date.now();
}
this.fpsBuffer = this.fpsBuffer.filter((value) => now - value <= 1000);
this.fpsBuffer.push(now);
this.setDebugData({ FPS: this.fpsBuffer.length });
}
};
PAGView = PAGView_1 = __decorate([
destroyVerify
], PAGView);
export { PAGView };