You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
being able to set a max size of pictures taken with capacitor camera
= don't upload pictures of 200Mo on recent phones, making app crash
Platforms
iOS
Android
Web
Request or proposed solution
When some users use he capacitor camera (phones, tablets, laptops), the browser sometimes crashes due to very high resolution (due to modern high resolution cameras).
We are now handling the pictures compression / resize with javascript, but browser hardly manipulate pictures of 200Mo in a mobile webview, causing app crashes.
Camera plugin has correctOrientation option to have non rotated pictures that works on Android and iOS.
Also has width and height options for the pixel size, the best would also be to define maximum file size (weight) in Mo.
Alternatives
Javascript alternatives are not good, because mobile web browsers most of the time don't have enough RAM to handle this kind of pictures.
Example of code using capacitor camera plugin today (it should me muuuuuch simpler):
importtype{Photo}from"@capacitor/camera";import{Camera,CameraDirection,CameraResultType,CameraSource,}from"@capacitor/camera";import{Capacitor}from"@capacitor/core";import{compressImage}from"~/config/browser-image-compression";enumTakePhotoError{PERMISSION_DENIED="CameraPermissionDenied",USER_CANCELED="UserCanceledPhoto",UNEXPECTED="UnexpectedError",}typeTakePhotoReturnType=|{result: {blob: Blob; image: Photo}}|{ result: null;error?: {type: TakePhotoError;data?: any}};constsleep=(milliseconds: number)=>{returnnewPromise((resolve)=>setTimeout(resolve,milliseconds));};// Take a picture on native devices, using only HTML standard// We need this on iOS because capacitor provide uri that we can't// fetch because of Safari CORS policies.// An alternative would be to use capacitor/camera base64 image// but this lead to out of memory error when the picture taken is too big// (which is often the case with iPhone camera quality)// The underlying issue with Capacitor camera plugin is that there is no way// for us to directly retrieve "Blob" data from the native camera API.// Thanksfully HTML native input with some options allows us to do just that// even if it's not a perfect way to do this, this is the best we found as today
async functiontakeNativePicture(): Promise<File|null>{// We create our input which we will programatically activate to take our picture// Cf: https://developer.mozilla.org/fr/docs/Web/HTML/Attributes/captureconst input =document.createElement("input");letpictureFile: File|null=null;input.type="file";// This options will select the rear camera of the deviceinput.capture="environment";// Behavior of accept property is not uniform on all devices (for example on IOs it opens a// select to choose between camera and gallery, but nos on Android), it should be fixed in the// time, cf https://github.com/apache/cordova-android/issues/816#issuecomment-1214221026input.accept="image/*";// This is here due to a lack of browser implementation of the latest html standard// As today, there is no straightforward way to know if a "file input" pop-up (such as photo)// has be closed / canceled / aborted, a way to do so has been added to HTML standard recently but// not implemented in any browser yet// cf: https://github.com/whatwg/html/issues/6376 and https://github.com/whatwg/html/pull/6735// So, as today, we must use dirty tricks to know if our user is still inside the "file input" modal or not// Here, I decided to go with the "documentvisibilty" as a singleton to do so. When the photo model is opened// on mobile, the document visibility become "hidden", when the modal is closed, it become visible againletdocumentVisibility=null;constvisibilityChangeHandler=()=>{documentVisibility=document.visibilityState;};constinputChangeHandler=(ev: React.FormEvent<HTMLInputElement>)=>{if(ev.currentTarget.files&&ev.currentTarget.files.length>0){pictureFile=ev.currentTarget.files[0];}};document.addEventListener("visibilitychange",visibilityChangeHandler);// We must listen on both events to handle the case when a user re-take a photoinput.addEventListener("input",inputChangeHandlerasunknownasEventListener);input.addEventListener("change",inputChangeHandlerasunknownasEventListener);// This will trigger the "photo modal opening" on mobile devicesinput.click();// While our user is inside our modal, we want to wait until he either: dismiss it, or take a picture// note that we have to check for both status: "null" AND "hidden", indeed afer the input.click, it can// take between 300 to 500ms for the phone to open the "photo modal", once he does, the "event listened" will// set our "documentVisibility" to hidden accordingly, and once the modal is closed, it'll go to "visible"// so we have 3 states:// - documentVisibility is null (our photo modal hasn't been opened yet, wait for it)// - documentVisibility hidden (photo modal is currently displayed to the user)// - documentVisibility visible (modal has been open, and is now closed, we can continue)// Also if a file is uploaded in non-mobile/full page context (eg: on desktop), we break our loop if the file input// is populated at some point.while((documentVisibility===null||documentVisibility==="hidden")&&pictureFile===null){awaitsleep(100);}// We cleanup our event listener on document visibility as we won't need it anymoredocument.removeEventListener("visibilitychange",visibilityChangeHandler);// Sometimes the "visiblitychange" event pop-up before the "input" change has been triggered// so if the input is empty, add a last wait to leave the time to the browser to process the input change callbackif(!pictureFile){awaitsleep(100);pictureFile=input.files?.[0]??null;}// If our user took a picture, we will have it into the files of the inputreturnpictureFile;}
async functiontakePhoto(): Promise<TakePhotoReturnType>{let cameraPermission ={camera: "granted"};// On web, Camera.requestPermissions is not implemented, we do it only if we are on a native platformif(Capacitor.isNativePlatform()&&Capacitor.isPluginAvailable("Camera")){cameraPermission=awaitCamera.requestPermissions({permissions: ["camera"],});}if(cameraPermission.camera!=="denied"){// This is a dirty trick to avoid the "pwa-camera-modal" pop-up to be blocked by "radix-dialog"// modal dialogs. We don't control where the pwa modal is put in the dom// So we have to remove the "pointer-events: none" from body if he exist (radix-dialog is open)// to allow interactions with the "pwa-camera-modal", then we restore it as it was before the camera// was opened, to avoid side effects with our "radix-dialog" logic// TODO: find a less dirty way to handle thatconstoldBodyPointerEvents=document.body.style.getPropertyValue("pointer-events");// Here we restore our pointer-events style on body as it was before taking photo,// This MUST be called whatever the result of getPhoto (sucess || error)constrestoreBodyPointerEvents=()=>{if(oldBodyPointerEvents){document.body.style.setProperty("pointer-events",oldBodyPointerEvents);}};try{if(Capacitor.isNativePlatform()){constfile=awaittakeNativePicture();if(file){// We compress the photo because it can cause canvas crash on iOS when annotatingconstcompressedFile=awaitcompressImage(file);return{result: {blob: compressedFile,image: {format: compressedFile.type.replace("image/",""),saved: false,webPath: `${Date.now()}${compressedFile.name}`,},},};}// If we have no file is empty, it means the user canceled the photo popupreturn{error: {type: TakePhotoError.USER_CANCELED},result: null};}else{document.body.style.setProperty("pointer-events","auto");constimage=awaitCamera.getPhoto({allowEditing: true,correctOrientation: false,direction: CameraDirection.Rear,quality: 90,resultType: CameraResultType.Uri,source: CameraSource.Camera,});restoreBodyPointerEvents();if(image.webPath){constresp=awaitfetch(image.webPath);constblob=awaitresp.blob();return{result: { blob, image }};}}// This is dirty as fuck but capcitor throw an error// for whatever happens in the api other than a full complete "picture taken and validated" logic}catch(e: unknown){restoreBodyPointerEvents();if(einstanceofError){if(e?.message?.startsWith?.("User cancelled")){return{error: {type: TakePhotoError.USER_CANCELED},result: null,};}}}return{error: {type: TakePhotoError.UNEXPECTED},result: null};}return{error: {type: TakePhotoError.PERMISSION_DENIED},result: null};}export{takePhoto,TakePhotoError};
Aarbel
changed the title
[Feature]: native camera resolution, rotation and max size settings
[Feature]: native camera m, rotation and max size settings
Apr 15, 2024
Aarbel
changed the title
[Feature]: native camera m, rotation and max size settings
[Feature]: native camera max size settings
Apr 15, 2024
Aarbel
changed the title
[Feature]: native camera max size settings
[Feature]: native camera max weight settings
Apr 15, 2024
Description
Need: to natively include settings to
= don't upload pictures of 200Mo on recent phones, making app crash
Platforms
Request or proposed solution
When some users use he capacitor camera (phones, tablets, laptops), the browser sometimes crashes due to very high resolution (due to modern high resolution cameras).
We are now handling the pictures compression / resize with javascript, but browser hardly manipulate pictures of 200Mo in a mobile webview, causing app crashes.
Camera plugin has correctOrientation option to have non rotated pictures that works on Android and iOS.
Also has width and height options for the pixel size, the best would also be to define maximum file size (weight) in Mo.
Alternatives
Javascript alternatives are not good, because mobile web browsers most of the time don't have enough RAM to handle this kind of pictures.
Example of code using capacitor camera plugin today (it should me muuuuuch simpler):
Cf ionic-team/capacitor#7401
The text was updated successfully, but these errors were encountered: