import {PAGModule} from "./pag-module"; import type {VideoReader} from "./interfaces"; import {transferToArrayBuffer} from "./utils/common"; import type {VideoReaderManager as VideoReaderManageInterfaces} from './interfaces'; import {destroyVerify} from "./utils/decorators"; /** * VideoReaderManager is responsible for managing multiple VideoReader instances. * It handles video decoder initialization, frame preparation, and lifecycle management. */ @destroyVerify export class VideoReaderManager { /** * Checks whether the given PAG composition contains any video content. * @param wasmIns - The WebAssembly instance to check * @returns True if the composition contains video, false otherwise */ public static HasVideo(wasmIns: any){ return PAGModule._videoInfoManager._HasVideo(wasmIns) as boolean; } /** * Factory method to create and initialize a VideoReaderManager instance. * @param wasmIns - The WebAssembly instance containing video information * @returns A promise that resolves to a fully initialized VideoReaderManager instance */ public static async make(wasmIns: any): Promise { const videoManage = new VideoReaderManager(wasmIns); await videoManage.createVideoReader(); return videoManage; } /** WebAssembly instance for video information management */ public wasmIns: any; /** Array of video IDs managed by this instance */ public videoIDs: Array = []; /** Flag indicating whether this instance has been destroyed */ public isDestroyed = false; /** Map storing VideoReader instances indexed by video ID */ private videoReaderMap: Map = new Map(); /** * Constructor for VideoReaderManager. * Initializes the WASM instance and retrieves all video IDs. * @param wasmIns - The WebAssembly instance * @throws Error if VideoReaderManager creation fails */ public constructor( wasmIns: any ) { this.wasmIns = PAGModule._videoInfoManager._Make(wasmIns); if (!this.wasmIns) { throw new Error('create VideoReaderManager fail!'); } this.videoIDs = this.wasmIns._getVideoIDs() as Array; if (this.wasmIns._hasTimeRangeOverlap() as boolean) { console.error("The current file contains multiple layers referencing the same video with overlapping timelines. " + "This scenario is not supported, and the rendered result may not match expectations. It is recommended to offset the timelines of these layers."); } } /** * Creates VideoReader instances for all videos in the PAG file. * For each video ID, retrieves MP4 data and initializes the corresponding VideoReader. * Also prepares the first frame with the initial playback rate. */ public async createVideoReader() { for (const id of this.videoIDs) { // Retrieve MP4 data for the current video ID const mp4Data = this.wasmIns._getMp4DataByID(id); if (mp4Data !== null) { // Create VideoReader with video metadata (width, height, frame rate, static time ranges) this.videoReaderMap.set(id, await PAGModule.VideoReader.create(mp4Data, this.wasmIns._getWidthByID(id), this.wasmIns._getHeightByID(id), this.wasmIns._getFrameRateByID(id), this.wasmIns._getStaticTimeRangesByID(id))); // Prepare the first frame (frame 0) with the current playback rate await this.videoReaderMap.get(id)?.prepare(0, this.wasmIns._getPlaybackRateByID(id)); } } } /** * Retrieves a VideoReader instance by video ID. * Marks the video as using hardware decoding (software decode disabled). * @param id - The video ID * @returns The VideoReader instance or undefined if not found * @throws Error if VideoReader is not found for the given ID */ public getVideoReaderByID(id: number): VideoReader | undefined { if (this.videoReaderMap.get(id) === undefined) { console.error(`get VideoReader fail!,id:${id}`); return undefined; } return this.videoReaderMap.get(id); } /** * Prepares target frames for all videos based on current playback position. * This method is called during rendering to ensure the correct frames are decoded. * It handles both hardware and software decoding modes. */ public async prepareTargetFrame() { for (const id of this.videoIDs) { if (this.isDestroyed) return; // Get the target frame index from WASM const targetFrame = this.wasmIns._getTargetFrameByID(id) as number; if (targetFrame < 0) { continue; } if (this.videoReaderMap.get(id) !== undefined) { // Prepare the target frame with current playback rate. await this.videoReaderMap.get(id)?.prepare(targetFrame, this.wasmIns._getPlaybackRateByID(id)); } else { console.error("videoReader is undefined,id:", id); } } } /** * Destroys the VideoReaderManager instance and cleans up all resources. * This includes destroying the WASM instance, all VideoReader instances, * and clearing internal maps. */ public destroy(): void { // Delete the WASM instance this.wasmIns.delete(); // Destroy all VideoReader instances for (const key of this.videoReaderMap.keys()) { this.videoReaderMap.get(key)?.onDestroy(); } // Clear all internal maps this.videoReaderMap.clear(); // Mark as destroyed this.isDestroyed = true; } }