diff --git a/public/locale/en.json b/public/locale/en.json index ac3ffddcba0..e49c5f2a068 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -515,7 +515,7 @@ "by_name": "by {{by}}", "camera": "Camera", "camera_bed_link_success": "Camera linked to bed successfully.", - "camera_permission_denied": "Camera Permission denied", + "camera_permission_denied": "Camera Permission Denied", "camera_was_linked_to_bed": "This camera was linked to this bed", "cancel": "Cancel", "cancel_appointment": "Cancel Appointment", diff --git a/src/Utils/useCamera.ts b/src/Utils/useCamera.ts new file mode 100644 index 00000000000..ff5723e14d5 --- /dev/null +++ b/src/Utils/useCamera.ts @@ -0,0 +1,37 @@ +import { useCallback, useRef } from "react"; +import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; + +export const useCamera = () => { + const toastShownRef = useRef(false); + const { t } = useTranslation(); + + const requestPermission = useCallback( + async (cameraFacingMode: string = "user") => { + try { + toastShownRef.current = false; + const constraints: MediaStreamConstraints = { + video: { facingMode: cameraFacingMode }, + }; + + const mediaStream = + await navigator.mediaDevices.getUserMedia(constraints); + + if (mediaStream == null) { + return { hasPermission: false, mediaStream: null }; + } + + return { hasPermission: true, mediaStream: mediaStream }; + } catch (_error) { + if (!toastShownRef.current) { + toastShownRef.current = true; + toast.warning(t("camera_permission_denied")); + } + return { hasPermission: false, mediaStream: null }; + } + }, + [t], + ); + + return { requestPermission }; +}; diff --git a/src/components/Common/AvatarEditModal.tsx b/src/components/Common/AvatarEditModal.tsx index dd4d6284e57..b5f5b5e6beb 100644 --- a/src/components/Common/AvatarEditModal.tsx +++ b/src/components/Common/AvatarEditModal.tsx @@ -23,6 +23,8 @@ import { import useDragAndDrop from "@/hooks/useDragAndDrop"; +import { useCamera } from "@/Utils/useCamera"; + interface Props { title: string; open: boolean; @@ -77,6 +79,7 @@ const AvatarEditModal = ({ ); const { t } = useTranslation(); const [isDragging, setIsDragging] = useState(false); + const { requestPermission } = useCamera(); const handleSwitchCamera = useCallback(() => { setConstraint( @@ -391,9 +394,11 @@ const AvatarEditModal = ({ width={1280} ref={webRef} videoConstraints={constraint} - onUserMediaError={(_e) => { - setIsCameraOpen(false); - toast.warning(t("camera_permission_denied")); + onUserMediaError={async () => { + const hasPermission = await requestPermission("user"); + if (!hasPermission) { + setIsCameraOpen(false); + } }} /> diff --git a/src/components/Files/CameraCaptureDialog.tsx b/src/components/Files/CameraCaptureDialog.tsx index 8ee66a313d6..9faccd865c0 100644 --- a/src/components/Files/CameraCaptureDialog.tsx +++ b/src/components/Files/CameraCaptureDialog.tsx @@ -15,6 +15,8 @@ import { import useBreakpoints from "@/hooks/useBreakpoints"; +import { useCamera } from "@/Utils/useCamera"; + export interface CameraCaptureDialogProps { open: boolean; onOpenChange: (open: boolean) => void; @@ -26,6 +28,8 @@ export interface CameraCaptureDialogProps { export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { const { open, onOpenChange, onCapture, onResetCapture, setPreview } = props; const isLaptopScreen = useBreakpoints({ lg: true, default: false }); + const { requestPermission } = useCamera(); + const [stream, setStream] = useState(null); const [cameraFacingMode, setCameraFacingMode] = useState( isLaptopScreen ? "user" : "environment", @@ -41,17 +45,23 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { useEffect(() => { if (!open) return; - let stream: MediaStream | null = null; - navigator.mediaDevices - .getUserMedia({ video: { facingMode: cameraFacingMode } }) - .then((mediaStream) => { - stream = mediaStream; - }) - .catch(() => { - toast.warning(t("camera_permission_denied")); + const getCameraStream = async () => { + const hasPermission = await requestPermission(cameraFacingMode); + if (!hasPermission.hasPermission) { onOpenChange(false); - }); + return; + } + + try { + const mediaStream = hasPermission.mediaStream; + setStream(mediaStream); + } catch (error) { + console.error("Error accessing camera:", error); + } + }; + + getCameraStream(); return () => { if (stream) {