From bd485c00aa5b68da3e7d90d0a24689a545cbe998 Mon Sep 17 00:00:00 2001 From: 99power <124181072+99power@users.noreply.github.com> Date: Fri, 1 Nov 2024 18:09:22 +0000 Subject: [PATCH 1/2] fix invalid props in DOM --- src/components/Camera/Camera.tsx | 564 +++++++++++++++---------------- src/components/Camera/styles.ts | 30 +- 2 files changed, 298 insertions(+), 296 deletions(-) diff --git a/src/components/Camera/Camera.tsx b/src/components/Camera/Camera.tsx index 3e79223..24f6056 100644 --- a/src/components/Camera/Camera.tsx +++ b/src/components/Camera/Camera.tsx @@ -1,312 +1,312 @@ import React, { useState, useEffect, useRef, useImperativeHandle } from 'react'; import { - CameraProps, - FacingMode, - Stream, - SetStream, - SetNumberOfCameras, - SetNotSupported, - SetPermissionDenied, + CameraProps, + FacingMode, + Stream, + SetStream, + SetNumberOfCameras, + SetNotSupported, + SetPermissionDenied, } from './types'; import { Container, Wrapper, Canvas, Cam, ErrorMsg } from './styles'; export const Camera = React.forwardRef( - ( - { - facingMode = 'user', - aspectRatio = 'cover', - numberOfCamerasCallback = () => null, - videoSourceDeviceId = undefined, - errorMessages = { - noCameraAccessible: 'No camera device accessible. Please connect your camera or try a different browser.', - permissionDenied: 'Permission denied. Please refresh and give camera permission.', - switchCamera: - 'It is not possible to switch camera to different one because there is only one video device accessible.', - canvas: 'Canvas is not supported.', - }, - videoReadyCallback = () => null, - }, - ref, - ) => { - const player = useRef(null); - const canvas = useRef(null); - const context = useRef(null); - const container = useRef(null); - const [numberOfCameras, setNumberOfCameras] = useState(0); - const [stream, setStream] = useState(null); - const [currentFacingMode, setFacingMode] = useState(facingMode); - const [notSupported, setNotSupported] = useState(false); - const [permissionDenied, setPermissionDenied] = useState(false); - const [torchSupported, setTorchSupported] = useState(false); - const [torch, setTorch] = useState(false); - const mounted = useRef(false); - - useEffect(() => { - mounted.current = true; - - return () => { - mounted.current = false; - }; - }, []); - - useEffect(() => { - numberOfCamerasCallback(numberOfCameras); - }, [numberOfCameras]); - - const switchTorch = async (on = false) => { - if (stream && navigator?.mediaDevices && !!mounted.current) { - const supportedConstraints = navigator.mediaDevices.getSupportedConstraints(); - const [track] = stream.getTracks(); - if (supportedConstraints && 'torch' in supportedConstraints && track) { - try { - await track.applyConstraints({ advanced: [{ torch: on }] } as MediaTrackConstraintSet); - return true; - } catch { - return false; - } - } - } - - return false; - }; - - useEffect(() => { - switchTorch(torch); - }, [torch]); - - useImperativeHandle(ref, () => ({ - takePhoto: (type?: 'base64url' | 'imgData') => { - if (numberOfCameras < 1) { - throw new Error(errorMessages.noCameraAccessible); - } + ( + { + facingMode = 'user', + aspectRatio = 'cover', + numberOfCamerasCallback = () => null, + videoSourceDeviceId = undefined, + errorMessages = { + noCameraAccessible: 'No camera device accessible. Please connect your camera or try a different browser.', + permissionDenied: 'Permission denied. Please refresh and give camera permission.', + switchCamera: + 'It is not possible to switch camera to different one because there is only one video device accessible.', + canvas: 'Canvas is not supported.', + }, + videoReadyCallback = () => null, + }, + ref, + ) => { + const player = useRef(null); + const canvas = useRef(null); + const context = useRef(null); + const container = useRef(null); + const [numberOfCameras, setNumberOfCameras] = useState(0); + const [stream, setStream] = useState(null); + const [currentFacingMode, setFacingMode] = useState(facingMode); + const [notSupported, setNotSupported] = useState(false); + const [permissionDenied, setPermissionDenied] = useState(false); + const [torchSupported, setTorchSupported] = useState(false); + const [torch, setTorch] = useState(false); + const mounted = useRef(false); + + useEffect(() => { + mounted.current = true; + + return () => { + mounted.current = false; + }; + }, []); + + useEffect(() => { + numberOfCamerasCallback(numberOfCameras); + }, [numberOfCameras]); + + const switchTorch = async (on = false) => { + if (stream && navigator?.mediaDevices && !!mounted.current) { + const supportedConstraints = navigator.mediaDevices.getSupportedConstraints(); + const [track] = stream.getTracks(); + if (supportedConstraints && 'torch' in supportedConstraints && track) { + try { + await track.applyConstraints({ advanced: [{ torch: on }] } as MediaTrackConstraintSet); + return true; + } catch { + return false; + } + } + } - if (canvas?.current) { - const playerWidth = player?.current?.videoWidth || 1280; - const playerHeight = player?.current?.videoHeight || 720; - const playerAR = playerWidth / playerHeight; - - const canvasWidth = container?.current?.offsetWidth || 1280; - const canvasHeight = container?.current?.offsetHeight || 1280; - const canvasAR = canvasWidth / canvasHeight; - - let sX, sY, sW, sH, imgData; - - if (playerAR > canvasAR) { - sH = playerHeight; - sW = playerHeight * canvasAR; - sX = (playerWidth - sW) / 2; - sY = 0; - } else { - sW = playerWidth; - sH = playerWidth / canvasAR; - sX = 0; - sY = (playerHeight - sH) / 2; - } - - canvas.current.width = sW; - canvas.current.height = sH; - - if (!context.current) { - context.current = canvas.current.getContext('2d', { willReadFrequently: true }); - } - - if (context.current && player?.current) { - context.current.drawImage(player.current, sX, sY, sW, sH, 0, 0, sW, sH); - } - - switch (type) { - case 'imgData': - imgData = context.current?.getImageData(0, 0, sW, sH); - break; - default: /* base64url */ - imgData = canvas.current.toDataURL('image/jpeg'); - break; - } - - return imgData; - } else { - throw new Error(errorMessages.canvas); - } - }, - switchCamera: () => { - if (numberOfCameras < 1) { - throw new Error(errorMessages.noCameraAccessible); - } else if (numberOfCameras < 2) { - console.error('Error: Unable to switch camera. Only one device is accessible.'); // console only - } - const newFacingMode = currentFacingMode === 'user' ? 'environment' : 'user'; - setFacingMode(newFacingMode); - return newFacingMode; - }, - getNumberOfCameras: () => { - return numberOfCameras; - }, - toggleTorch: () => { - const torchVal = !torch; - setTorch(torchVal); - return torchVal; - }, - torchSupported: torchSupported, - })); - - useEffect(() => { - initCameraStream( - stream, - setStream, - currentFacingMode, - videoSourceDeviceId, - setNumberOfCameras, - setNotSupported, - setPermissionDenied, - !!mounted.current, - ); - }, [currentFacingMode, videoSourceDeviceId]); - - useEffect(() => { - switchTorch(false).then((success) => setTorchSupported(success)); - if (stream && player && player.current) { - player.current.srcObject = stream; - } - return () => { - if (stream) { - stream.getTracks().forEach((track) => { - track.stop(); - }); - } - }; - }, [stream]); - - return ( - - - {notSupported ? {errorMessages.noCameraAccessible} : null} - {permissionDenied ? {errorMessages.permissionDenied} : null} - { - videoReadyCallback(); - }} - > - - - - ); - }, + return false; + }; + + useEffect(() => { + switchTorch(torch); + }, [torch]); + + useImperativeHandle(ref, () => ({ + takePhoto: (type?: 'base64url' | 'imgData') => { + if (numberOfCameras < 1) { + throw new Error(errorMessages.noCameraAccessible); + } + + if (canvas?.current) { + const playerWidth = player?.current?.videoWidth || 1280; + const playerHeight = player?.current?.videoHeight || 720; + const playerAR = playerWidth / playerHeight; + + const canvasWidth = container?.current?.offsetWidth || 1280; + const canvasHeight = container?.current?.offsetHeight || 1280; + const canvasAR = canvasWidth / canvasHeight; + + let sX, sY, sW, sH, imgData; + + if (playerAR > canvasAR) { + sH = playerHeight; + sW = playerHeight * canvasAR; + sX = (playerWidth - sW) / 2; + sY = 0; + } else { + sW = playerWidth; + sH = playerWidth / canvasAR; + sX = 0; + sY = (playerHeight - sH) / 2; + } + + canvas.current.width = sW; + canvas.current.height = sH; + + if (!context.current) { + context.current = canvas.current.getContext('2d', { willReadFrequently: true }); + } + + if (context.current && player?.current) { + context.current.drawImage(player.current, sX, sY, sW, sH, 0, 0, sW, sH); + } + + switch (type) { + case 'imgData': + imgData = context.current?.getImageData(0, 0, sW, sH); + break; + default: /* base64url */ + imgData = canvas.current.toDataURL('image/jpeg'); + break; + } + + return imgData; + } else { + throw new Error(errorMessages.canvas); + } + }, + switchCamera: () => { + if (numberOfCameras < 1) { + throw new Error(errorMessages.noCameraAccessible); + } else if (numberOfCameras < 2) { + console.error('Error: Unable to switch camera. Only one device is accessible.'); // console only + } + const newFacingMode = currentFacingMode === 'user' ? 'environment' : 'user'; + setFacingMode(newFacingMode); + return newFacingMode; + }, + getNumberOfCameras: () => { + return numberOfCameras; + }, + toggleTorch: () => { + const torchVal = !torch; + setTorch(torchVal); + return torchVal; + }, + torchSupported: torchSupported, + })); + + useEffect(() => { + initCameraStream( + stream, + setStream, + currentFacingMode, + videoSourceDeviceId, + setNumberOfCameras, + setNotSupported, + setPermissionDenied, + !!mounted.current, + ); + }, [currentFacingMode, videoSourceDeviceId]); + + useEffect(() => { + switchTorch(false).then((success) => setTorchSupported(success)); + if (stream && player && player.current) { + player.current.srcObject = stream; + } + return () => { + if (stream) { + stream.getTracks().forEach((track) => { + track.stop(); + }); + } + }; + }, [stream]); + + return ( + + + {notSupported ? {errorMessages.noCameraAccessible} : null} + {permissionDenied ? {errorMessages.permissionDenied} : null} + { + videoReadyCallback(); + }} + > + + + + ); + }, ); Camera.displayName = 'Camera'; const shouldSwitchToCamera = async (currentFacingMode: FacingMode): Promise => { - const cameras: string[] = []; - if (currentFacingMode === 'environment') { - await navigator.mediaDevices.enumerateDevices().then((devices) => { - const videoDevices = devices.filter((i) => i.kind == 'videoinput'); - videoDevices.forEach((device) => { - const capabilities = (device as InputDeviceInfo).getCapabilities(); - if (capabilities.facingMode && capabilities.facingMode.indexOf('environment') >= 0 && capabilities.deviceId) { - cameras.push(capabilities.deviceId); - } - }); - }); - } + const cameras: string[] = []; + if (currentFacingMode === 'environment') { + await navigator.mediaDevices.enumerateDevices().then((devices) => { + const videoDevices = devices.filter((i) => i.kind == 'videoinput'); + videoDevices.forEach((device) => { + const capabilities = (device as InputDeviceInfo).getCapabilities(); + if (capabilities.facingMode && capabilities.facingMode.indexOf('environment') >= 0 && capabilities.deviceId) { + cameras.push(capabilities.deviceId); + } + }); + }); + } - if (cameras.length > 1) { - return cameras.pop(); - } + if (cameras.length > 1) { + return cameras.pop(); + } - return undefined; + return undefined; }; const initCameraStream = async ( - stream: Stream, - setStream: SetStream, - currentFacingMode: FacingMode, - videoSourceDeviceId: string | undefined, - setNumberOfCameras: SetNumberOfCameras, - setNotSupported: SetNotSupported, - setPermissionDenied: SetPermissionDenied, - isMounted: boolean, + stream: Stream, + setStream: SetStream, + currentFacingMode: FacingMode, + videoSourceDeviceId: string | undefined, + setNumberOfCameras: SetNumberOfCameras, + setNotSupported: SetNotSupported, + setPermissionDenied: SetPermissionDenied, + isMounted: boolean, ) => { - // stop any active streams in the window - if (stream) { - stream.getTracks().forEach((track) => { - track.stop(); - }); - } - - let cameraDeviceId; - - const switchToCamera = await shouldSwitchToCamera(currentFacingMode); - if (switchToCamera) { - cameraDeviceId = switchToCamera; - } else { - cameraDeviceId = videoSourceDeviceId ? { exact: videoSourceDeviceId } : undefined; - } - - const constraints = { - audio: false, - video: { - deviceId: cameraDeviceId, - facingMode: currentFacingMode, - }, - }; + // stop any active streams in the window + if (stream) { + stream.getTracks().forEach((track) => { + track.stop(); + }); + } - if (navigator?.mediaDevices?.getUserMedia) { - navigator.mediaDevices - .getUserMedia(constraints) - .then((stream) => { - if (isMounted) { - setStream(handleSuccess(stream, setNumberOfCameras)); - } - }) - .catch((err) => { - handleError(err, setNotSupported, setPermissionDenied); - }); - } else { - const getWebcam = - navigator.getUserMedia || - navigator.webkitGetUserMedia || - navigator.mozGetUserMedia || - navigator.mozGetUserMedia || - navigator.msGetUserMedia; - if (getWebcam) { - getWebcam( - constraints, - async (stream) => { - if (isMounted) { - setStream(handleSuccess(stream, setNumberOfCameras)); - } - }, - (err) => { - handleError(err as Error, setNotSupported, setPermissionDenied); + let cameraDeviceId; + + const switchToCamera = await shouldSwitchToCamera(currentFacingMode); + if (switchToCamera) { + cameraDeviceId = switchToCamera; + } else { + cameraDeviceId = videoSourceDeviceId ? { exact: videoSourceDeviceId } : undefined; + } + + const constraints = { + audio: false, + video: { + deviceId: cameraDeviceId, + facingMode: currentFacingMode, }, - ); + }; + + if (navigator?.mediaDevices?.getUserMedia) { + navigator.mediaDevices + .getUserMedia(constraints) + .then((stream) => { + if (isMounted) { + setStream(handleSuccess(stream, setNumberOfCameras)); + } + }) + .catch((err) => { + handleError(err, setNotSupported, setPermissionDenied); + }); } else { - setNotSupported(true); + const getWebcam = + navigator.getUserMedia || + navigator.webkitGetUserMedia || + navigator.mozGetUserMedia || + navigator.mozGetUserMedia || + navigator.msGetUserMedia; + if (getWebcam) { + getWebcam( + constraints, + async (stream) => { + if (isMounted) { + setStream(handleSuccess(stream, setNumberOfCameras)); + } + }, + (err) => { + handleError(err as Error, setNotSupported, setPermissionDenied); + }, + ); + } else { + setNotSupported(true); + } } - } }; const handleSuccess = (stream: MediaStream, setNumberOfCameras: SetNumberOfCameras) => { - navigator.mediaDevices - .enumerateDevices() - .then((r) => setNumberOfCameras(r.filter((i) => i.kind === 'videoinput').length)); + navigator.mediaDevices + .enumerateDevices() + .then((r) => setNumberOfCameras(r.filter((i) => i.kind === 'videoinput').length)); - return stream; + return stream; }; const handleError = (error: Error, setNotSupported: SetNotSupported, setPermissionDenied: SetPermissionDenied) => { - console.error(error); - - //https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia - if (error.name === 'PermissionDeniedError') { - setPermissionDenied(true); - } else { - setNotSupported(true); - } + console.error(error); + + //https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia + if (error.name === 'PermissionDeniedError') { + setPermissionDenied(true); + } else { + setNotSupported(true); + } }; diff --git a/src/components/Camera/styles.ts b/src/components/Camera/styles.ts index 02f76d5..9e34974 100644 --- a/src/components/Camera/styles.ts +++ b/src/components/Camera/styles.ts @@ -9,31 +9,33 @@ export const Wrapper = styled.div` height: 100%; `; -export const Container = styled.div<{ aspectRatio: AspectRatio }>` +export const Container = styled.div<{ $aspectRatio: AspectRatio }>` width: 100%; - ${({ aspectRatio }) => - aspectRatio === 'cover' - ? ` - position: absolute; - bottom: 0; - top: 0; - left: 0; - right: 0;` - : ` - position: relative; - padding-bottom: ${100 / aspectRatio}%;`} + ${({ $aspectRatio }) => + $aspectRatio === 'cover' + ? ` + position: absolute; + bottom: 0; + top: 0; + left: 0; + right: 0; + ` + : ` + position: relative; + padding-bottom: ${100 / $aspectRatio}%; + `} `; export const ErrorMsg = styled.div` padding: 40px; `; -export const Cam = styled.video<{ mirrored: boolean }>` +export const Cam = styled.video<{ $mirrored: boolean }>` width: 100%; height: 100%; object-fit: cover; z-index: 0; - transform: rotateY(${({ mirrored }) => (mirrored ? '180deg' : '0deg')}); + transform: rotateY(${({ $mirrored }) => ($mirrored === true ? '180deg' : '0deg')}); `; export const Canvas = styled.canvas` From c11983fdc237fd6587ea8f8759a784fc833c8845 Mon Sep 17 00:00:00 2001 From: 99power <124181072+99power@users.noreply.github.com> Date: Fri, 1 Nov 2024 19:32:18 +0000 Subject: [PATCH 2/2] made getCapabilities call optional --- src/components/Camera/Camera.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Camera/Camera.tsx b/src/components/Camera/Camera.tsx index 24f6056..4fe4fb5 100644 --- a/src/components/Camera/Camera.tsx +++ b/src/components/Camera/Camera.tsx @@ -207,8 +207,9 @@ const shouldSwitchToCamera = async (currentFacingMode: FacingMode): Promise { const videoDevices = devices.filter((i) => i.kind == 'videoinput'); videoDevices.forEach((device) => { - const capabilities = (device as InputDeviceInfo).getCapabilities(); - if (capabilities.facingMode && capabilities.facingMode.indexOf('environment') >= 0 && capabilities.deviceId) { + const capabilities = (device as InputDeviceInfo).getCapabilities ? (device as InputDeviceInfo).getCapabilities() : null; + if (capabilities?.facingMode && capabilities.facingMode.indexOf('environment') >= 0 && capabilities.deviceId) { + cameras.push(capabilities.deviceId); } });