export interface DecodedVideoInfo { width: number; height: number; duration: number; // in seconds frameRate: number; codec: string; } /** * Simple video decoder for WebM files using native VideoDecoder API. * For export, we'll use a different approach - directly rendering from the HTML video element. */ export class VideoFileDecoder { private decoder: VideoDecoder | null = null; private info: DecodedVideoInfo | null = null; private videoElement: HTMLVideoElement | null = null; async loadVideo(videoUrl: string): Promise { // Create a video element to get video info this.videoElement = document.createElement('video'); this.videoElement.src = videoUrl; this.videoElement.preload = 'metadata'; return new Promise((resolve, reject) => { this.videoElement!.addEventListener('loadedmetadata', () => { const video = this.videoElement!; this.info = { width: video.videoWidth, height: video.videoHeight, duration: video.duration, frameRate: 60, // 60fps for smooth playback codec: 'avc1.640033', // H.264 High Profile Level 5.1 }; resolve(this.info); }); this.videoElement!.addEventListener('error', (e) => { reject(new Error(`Failed to load video: ${e}`)); }); }); } /** * Get video element for seeking */ getVideoElement(): HTMLVideoElement | null { return this.videoElement; } /** * Seek to a specific time and wait for the frame to be ready */ async seekToTime(timeInSeconds: number): Promise { if (!this.videoElement) { throw new Error('Video not loaded'); } return new Promise((resolve) => { const video = this.videoElement!; const onSeeked = () => { video.removeEventListener('seeked', onSeeked); resolve(); }; video.addEventListener('seeked', onSeeked); video.currentTime = timeInSeconds; }); } getInfo(): DecodedVideoInfo | null { return this.info; } destroy(): void { if (this.videoElement) { this.videoElement.pause(); this.videoElement.src = ''; this.videoElement = null; } if (this.decoder) { if (this.decoder.state !== 'closed') { this.decoder.close(); } this.decoder = null; } } }