@@ -497,11 +497,27 @@ export default class Camera extends OverlayPlugin {
497497 const sourceWidth = isImage ? mediaElement . width : mediaElement . videoWidth ;
498498 const sourceHeight = isImage ? mediaElement . height : mediaElement . videoHeight ;
499499
500+ // Fixed target resolution (16:9) to ensure stability and standard aspect ratio
501+ const targetWidth = 1280 ;
502+ const targetHeight = 720 ;
503+
500504 const canvas = document . createElement ( 'canvas' ) ;
501- canvas . width = sourceWidth || 1280 ;
502- canvas . height = sourceHeight || 720 ;
505+ canvas . width = targetWidth ;
506+ canvas . height = targetHeight ;
503507 const ctx = canvas . getContext ( '2d' ) ;
504508
509+ // Fill black background for letterboxing
510+ ctx . fillRect ( 0 , 0 , targetWidth , targetHeight ) ;
511+
512+ // Calculate scaling to fit (contain) while preserving aspect ratio
513+ const ratio = Math . min ( targetWidth / sourceWidth , targetHeight / sourceHeight ) ;
514+ const drawWidth = Math . floor ( sourceWidth * ratio ) ;
515+ const drawHeight = Math . floor ( sourceHeight * ratio ) ;
516+
517+ // Center the video in the canvas
518+ const startX = ( targetWidth - drawWidth ) / 2 ;
519+ const startY = ( targetHeight - drawHeight ) / 2 ;
520+
505521 let audioTrack = null ;
506522 /*
507523 * If the instance supports microphone and we are playing a video,
@@ -582,13 +598,14 @@ export default class Camera extends OverlayPlugin {
582598
583599 if ( isImage ) {
584600 // Optimization: Draw image once, no loop needed
585- if ( sourceWidth && sourceHeight ) {
586- ctx . drawImage ( mediaElement , 0 , 0 , sourceWidth , sourceHeight ) ;
601+ if ( drawWidth && drawHeight ) {
602+ ctx . drawImage ( mediaElement , startX , startY , drawWidth , drawHeight ) ;
587603 }
588604 } else {
589605 // Optimization: Video draw loop, use requestVideoFrameCallback if available for efficient sync, otherwise fallback to throttled rAF
590606 this [ type + 'UseVideoFrameCallback' ] = 'requestVideoFrameCallback' in mediaElement ;
591607 let lastTime = 0 ;
608+
592609 // Target 30fps for the fallback throttle
593610 const throttleInterval = 1000 / 30 ;
594611
@@ -599,16 +616,16 @@ export default class Camera extends OverlayPlugin {
599616 }
600617
601618 if ( this [ type + 'UseVideoFrameCallback' ] ) {
602- if ( sourceWidth && sourceHeight ) {
603- ctx . drawImage ( mediaElement , 0 , 0 , sourceWidth , sourceHeight ) ;
619+ if ( drawWidth && drawHeight ) {
620+ ctx . drawImage ( mediaElement , startX , startY , drawWidth , drawHeight ) ;
604621 }
605622 this [ type + 'AnimationId' ] = mediaElement . requestVideoFrameCallback ( drawLoop ) ;
606623 } else {
607624 // Throttling logic for rAF
608625 if ( ! lastTime || now - lastTime >= throttleInterval ) {
609626 lastTime = now ;
610- if ( sourceWidth && sourceHeight ) {
611- ctx . drawImage ( mediaElement , 0 , 0 , sourceWidth , sourceHeight ) ;
627+ if ( drawWidth && drawHeight ) {
628+ ctx . drawImage ( mediaElement , startX , startY , drawWidth , drawHeight ) ;
612629 }
613630 }
614631 this [ type + 'AnimationId' ] = requestAnimationFrame ( drawLoop ) ;
@@ -702,11 +719,11 @@ export default class Camera extends OverlayPlugin {
702719 if ( typeof mediaElement . pause === 'function' ) {
703720 mediaElement . pause ( ) ;
704721 }
722+
723+ mediaElement . removeAttribute ( 'src' ) ;
724+
705725 if ( typeof mediaElement . load === 'function' ) {
706- mediaElement . src = '' ;
707726 mediaElement . load ( ) ;
708- } else {
709- mediaElement . src = '' ;
710727 }
711728
712729 if ( mediaElement . parentNode ) {
0 commit comments