@@ -34,6 +34,8 @@ export class VideoProcessor {
3434 private readonly FACE_DISTANCE_THRESHOLD = 0.15 ; // 15% of frame width threshold
3535 private faceBoxHistory : FaceBox [ ] = [ ] ;
3636 private readonly FACE_HISTORY_SIZE = 5 ;
37+ private isVideoFileSource : boolean = false ;
38+ private onVideoComplete : ( ( ) => void ) | null = null ;
3739
3840 constructor ( ) {
3941 // Initialize video element
@@ -42,6 +44,8 @@ export class VideoProcessor {
4244 this . videoElement . muted = true ;
4345 this . videoElement . autoplay = true ;
4446
47+ this . setupVideoEventListeners ( ) ;
48+
4549 // Initialize canvases with optimized settings
4650 this . croppedCanvas = this . createOptimizedCanvas ( 256 , 256 ) ;
4751
@@ -100,6 +104,60 @@ export class VideoProcessor {
100104 this . croppedCtx . imageSmoothingQuality = 'high' ;
101105 }
102106
107+ async loadVideoFile ( file : File ) : Promise < void > {
108+ // Stop any existing capture
109+ await this . stopCapture ( ) ;
110+
111+ // Set flag to indicate we're using a video file
112+ this . isVideoFileSource = true ;
113+
114+ // Create a URL for the file
115+ const videoURL = URL . createObjectURL ( file ) ;
116+
117+ // Reset buffers
118+ this . frameBuffer = [ ] ;
119+ this . newFramesBuffer = [ ] ;
120+ this . frameCount = 0 ;
121+
122+ // Set the video element source
123+ this . videoElement . src = videoURL ;
124+ this . videoElement . muted = true ;
125+
126+ // Wait for video metadata to load
127+ return new Promise < void > ( ( resolve , reject ) => {
128+ const timeout = setTimeout ( ( ) => {
129+ reject ( new Error ( 'Video loading timeout' ) ) ;
130+ } , 10000 ) ;
131+
132+ this . videoElement . onloadedmetadata = ( ) => {
133+ clearTimeout ( timeout ) ;
134+
135+ // Initialize face detector if needed
136+ if ( ! this . faceDetector . isInitialized ) {
137+ this . faceDetector . initialize ( ) . then ( ( ) => {
138+ // Load config and start processing
139+ this . loadConfigSettings ( ) . then ( ( ) => {
140+ this . startFrameProcessing ( ) ;
141+ resolve ( ) ;
142+ } ) . catch ( reject ) ;
143+ } ) . catch ( reject ) ;
144+ } else {
145+ // Just load config and start processing
146+ this . loadConfigSettings ( ) . then ( ( ) => {
147+ this . startFrameProcessing ( ) ;
148+ resolve ( ) ;
149+ } ) . catch ( reject ) ;
150+ }
151+ } ;
152+
153+ this . videoElement . onerror = ( ) => {
154+ clearTimeout ( timeout ) ;
155+ URL . revokeObjectURL ( videoURL ) ;
156+ reject ( new Error ( 'Failed to load video file' ) ) ;
157+ } ;
158+ } ) ;
159+ }
160+
103161 async startCapture ( ) : Promise < void > {
104162 try {
105163 // Clear frame buffer
@@ -113,6 +171,7 @@ export class VideoProcessor {
113171 this . onFrameProcessed = null ;
114172 }
115173
174+ this . isVideoFileSource = false ;
116175 // Load frame dimensions from config
117176 await this . loadConfigSettings ( ) ;
118177
@@ -292,34 +351,60 @@ export class VideoProcessor {
292351 }
293352 }
294353
354+ private setupVideoEventListeners ( ) : void {
355+ this . videoElement . addEventListener ( 'ended' , ( ) => {
356+ console . log ( '[VideoProcessor] Video playback complete' ) ;
357+ // Notify any listeners that processing is complete
358+ if ( this . onVideoComplete ) {
359+ this . onVideoComplete ( ) ;
360+ }
361+ } ) ;
362+ }
363+
295364 private startFrameProcessing ( ) : void {
296365 // Process frames at target FPS
297366 this . _isShuttingDown = false ; // Reset flag when starting
298367
368+ if ( this . isVideoFileSource ) {
369+ // Start playing the video
370+ this . videoElement . play ( ) . catch ( error => {
371+ console . error ( 'Failed to play video:' , error ) ;
372+ } ) ;
373+ }
374+
299375 const processFrame = async ( ) => {
300376 // First check if we're shutting down
301377 if ( this . _isShuttingDown ) {
302378 console . log ( '[VideoProcessor] Aborting frame processing loop - shutdown in progress' ) ;
303379 return ; // Don't request a new frame
304380 }
305381 const now = performance . now ( ) ;
306- if ( now - this . lastFrameTime >= this . frameInterval ) {
382+
383+ // For video files, we don't throttle by frame rate
384+ if ( this . isVideoFileSource || ( now - this . lastFrameTime >= this . frameInterval ) ) {
307385 // Update face detection if needed
308386 await this . updateFaceDetection ( ) ;
309387
310388 // Process frame
311389 this . processVideoFrame ( now ) ;
312390 this . lastFrameTime = now ;
313391 }
314- // Only request next frame if not shutting down
315- if ( ! this . _isShuttingDown ) {
392+
393+ // Only request next frame if not shutting down and (for video files) if video is still playing
394+ if ( ! this . _isShuttingDown && ( ! this . isVideoFileSource || ! this . videoElement . ended ) ) {
316395 this . processingFrameId = requestAnimationFrame ( processFrame ) ;
396+ } else if ( this . isVideoFileSource && this . videoElement . ended ) {
397+ console . log ( '[VideoProcessor] Video playback complete' ) ;
317398 }
318399 } ;
319400
320401 this . processingFrameId = requestAnimationFrame ( processFrame ) ;
321402 }
322403
404+ isVideoComplete ( ) : boolean {
405+ return this . isVideoFileSource && this . videoElement . ended ;
406+ }
407+
323408 private processVideoFrame ( timestamp : number ) : void {
324409 try {
325410 // Immediately exit if we're shutting down
@@ -494,6 +579,9 @@ export class VideoProcessor {
494579 return frames ;
495580 }
496581
582+ public setOnVideoComplete ( callback : ( ) => void ) : void {
583+ this . onVideoComplete = callback ;
584+ }
497585
498586 getCurrentFaceBox ( ) : FaceBox | null {
499587 return this . currentFaceBox ;
@@ -520,7 +608,13 @@ export class VideoProcessor {
520608 }
521609
522610 isCapturing ( ) : boolean {
523- // Return false if explicitly shutting down or media stream is inactive
611+ // Check if we're using a video file source
612+ if ( this . isVideoFileSource ) {
613+ // For video files, we're capturing if not shutting down and video isn't ended
614+ return ! this . _isShuttingDown && ! this . videoElement . ended && this . videoElement . readyState >= 2 ;
615+ }
616+
617+ // For camera capture, check media stream
524618 return ! this . _isShuttingDown && ! ! this . mediaStream ?. active ;
525619 }
526620}
0 commit comments