libpag
Version:
Portable Animated Graphics
141 lines (127 loc) • 5.87 kB
text/typescript
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.
*/
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<VideoReaderManager> {
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<number> = [];
/** Flag indicating whether this instance has been destroyed */
public isDestroyed = false;
/** Map storing VideoReader instances indexed by video ID */
private videoReaderMap: Map<number, VideoReader> = new Map<number, VideoReader>();
/**
* 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<number>;
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;
}
}