UNPKG

tav-media

Version:

Cross platform media editing framework

507 lines (506 loc) 19.3 kB
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 };