diff --git a/.gitignore b/.gitignore index a05ceee..78b562b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ .DS_Store node_modules -dist .rpt2_cache diff --git a/dist/components/Camera/Camera.d.ts b/dist/components/Camera/Camera.d.ts new file mode 100644 index 0000000..0e606c1 --- /dev/null +++ b/dist/components/Camera/Camera.d.ts @@ -0,0 +1,3 @@ +import React from 'react'; +import { CameraProps } from './types'; +export declare const Camera: React.ForwardRefExoticComponent>; diff --git a/dist/components/Camera/styles.d.ts b/dist/components/Camera/styles.d.ts new file mode 100644 index 0000000..3f15820 --- /dev/null +++ b/dist/components/Camera/styles.d.ts @@ -0,0 +1,10 @@ +import { AspectRatio } from './types'; +export declare const Wrapper: import("styled-components").StyledComponent<"div", any, {}, never>; +export declare const Container: import("styled-components").StyledComponent<"div", any, { + aspectRatio: AspectRatio; +}, never>; +export declare const ErrorMsg: import("styled-components").StyledComponent<"div", any, {}, never>; +export declare const Cam: import("styled-components").StyledComponent<"video", any, { + mirrored: boolean; +}, never>; +export declare const Canvas: import("styled-components").StyledComponent<"canvas", any, {}, never>; diff --git a/dist/components/Camera/types.d.ts b/dist/components/Camera/types.d.ts new file mode 100644 index 0000000..975bb80 --- /dev/null +++ b/dist/components/Camera/types.d.ts @@ -0,0 +1,26 @@ +/// +export declare type FacingMode = 'user' | 'environment'; +export declare type AspectRatio = 'cover' | number; +export declare type Stream = MediaStream | null; +export declare type SetStream = React.Dispatch>; +export declare type SetNumberOfCameras = React.Dispatch>; +export declare type SetNotSupported = React.Dispatch>; +export declare type SetPermissionDenied = React.Dispatch>; +export interface CameraProps { + facingMode?: FacingMode; + aspectRatio?: AspectRatio; + numberOfCamerasCallback?(numberOfCameras: number): void; + videoSourceDeviceId?: string | undefined; + errorMessages: { + noCameraAccessible?: string; + permissionDenied?: string; + switchCamera?: string; + canvas?: string; + }; + videoReadyCallback?(): void; +} +export declare type CameraType = React.ForwardRefExoticComponent> & { + takePhoto(): string; + switchCamera(): FacingMode; + getNumberOfCameras(): number; +}; diff --git a/dist/index.cjs.js b/dist/index.cjs.js new file mode 100644 index 0000000..f38882e --- /dev/null +++ b/dist/index.cjs.js @@ -0,0 +1,206 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } + +var React = require('react'); +var React__default = _interopDefault(React); +var styled = _interopDefault(require('styled-components')); + +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ + +function __makeTemplateObject(cooked, raw) { + if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } + return cooked; +} + +var Wrapper = styled.div(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n"], ["\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n"]))); +var Container = styled.div(templateObject_2 || (templateObject_2 = __makeTemplateObject(["\n width: 100%;\n ", "\n"], ["\n width: 100%;\n ", + "\n"])), function (_a) { + var aspectRatio = _a.aspectRatio; + return aspectRatio === 'cover' + ? "\n position: absolute;\n bottom: 0;\n top: 0;\n left: 0;\n right: 0;" + : "\n position: relative;\n padding-bottom: " + 100 / aspectRatio + "%;"; +}); +var ErrorMsg = styled.div(templateObject_3 || (templateObject_3 = __makeTemplateObject(["\n padding: 40px;\n"], ["\n padding: 40px;\n"]))); +var Cam = styled.video(templateObject_4 || (templateObject_4 = __makeTemplateObject(["\n width: 100%;\n height: 100%;\n object-fit: cover;\n z-index: 0;\n transform: rotateY(", ");\n"], ["\n width: 100%;\n height: 100%;\n object-fit: cover;\n z-index: 0;\n transform: rotateY(", ");\n"])), function (_a) { + var mirrored = _a.mirrored; + return (mirrored ? '180deg' : '0deg'); +}); +var Canvas = styled.canvas(templateObject_5 || (templateObject_5 = __makeTemplateObject(["\n display: none;\n"], ["\n display: none;\n"]))); +var templateObject_1, templateObject_2, templateObject_3, templateObject_4, templateObject_5; + +var Camera = React__default.forwardRef(function (_a, ref) { + var _b = _a.facingMode, facingMode = _b === void 0 ? 'user' : _b, _c = _a.aspectRatio, aspectRatio = _c === void 0 ? 'cover' : _c, _d = _a.numberOfCamerasCallback, numberOfCamerasCallback = _d === void 0 ? function () { return null; } : _d, _e = _a.videoSourceDeviceId, videoSourceDeviceId = _e === void 0 ? undefined : _e, _f = _a.errorMessages, errorMessages = _f === void 0 ? { + 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.', + } : _f, _g = _a.videoReadyCallback, videoReadyCallback = _g === void 0 ? function () { return null; } : _g; + var player = React.useRef(null); + var canvas = React.useRef(null); + var container = React.useRef(null); + var _h = React.useState(0), numberOfCameras = _h[0], setNumberOfCameras = _h[1]; + var _j = React.useState(null), stream = _j[0], setStream = _j[1]; + var _k = React.useState(facingMode), currentFacingMode = _k[0], setFacingMode = _k[1]; + var _l = React.useState(false), notSupported = _l[0], setNotSupported = _l[1]; + var _m = React.useState(false), permissionDenied = _m[0], setPermissionDenied = _m[1]; + React.useEffect(function () { + numberOfCamerasCallback(numberOfCameras); + }, [numberOfCameras]); + React.useImperativeHandle(ref, function () { return ({ + takePhoto: function () { + var _a, _b, _c, _d; + if (numberOfCameras < 1) { + throw new Error(errorMessages.noCameraAccessible); + } + if (canvas === null || canvas === void 0 ? void 0 : canvas.current) { + var playerWidth = ((_a = player === null || player === void 0 ? void 0 : player.current) === null || _a === void 0 ? void 0 : _a.videoWidth) || 1280; + var playerHeight = ((_b = player === null || player === void 0 ? void 0 : player.current) === null || _b === void 0 ? void 0 : _b.videoHeight) || 720; + var playerAR = playerWidth / playerHeight; + var canvasWidth = ((_c = container === null || container === void 0 ? void 0 : container.current) === null || _c === void 0 ? void 0 : _c.offsetWidth) || 1280; + var canvasHeight = ((_d = container === null || container === void 0 ? void 0 : container.current) === null || _d === void 0 ? void 0 : _d.offsetHeight) || 1280; + var canvasAR = canvasWidth / canvasHeight; + var sX = void 0, sY = void 0, sW = void 0, sH = void 0; + 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; + var context = canvas.current.getContext('2d'); + if (context && (player === null || player === void 0 ? void 0 : player.current)) { + context.drawImage(player.current, sX, sY, sW, sH, 0, 0, sW, sH); + } + var imgData = canvas.current.toDataURL('image/jpeg'); + return imgData; + } + else { + throw new Error(errorMessages.canvas); + } + }, + switchCamera: function () { + 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 + } + var newFacingMode = currentFacingMode === 'user' ? 'environment' : 'user'; + setFacingMode(newFacingMode); + return newFacingMode; + }, + getNumberOfCameras: function () { + return numberOfCameras; + }, + }); }); + React.useEffect(function () { + initCameraStream(stream, setStream, currentFacingMode, videoSourceDeviceId, setNumberOfCameras, setNotSupported, setPermissionDenied); + }, [currentFacingMode, videoSourceDeviceId]); + React.useEffect(function () { + if (stream && player && player.current) { + player.current.srcObject = stream; + } + return function () { + if (stream) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + } + }; + }, [stream]); + return (React__default.createElement(Container, { ref: container, aspectRatio: aspectRatio }, + React__default.createElement(Wrapper, null, + notSupported ? React__default.createElement(ErrorMsg, null, errorMessages.noCameraAccessible) : null, + permissionDenied ? React__default.createElement(ErrorMsg, null, errorMessages.permissionDenied) : null, + React__default.createElement(Cam, { ref: player, id: "video", muted: true, autoPlay: true, playsInline: true, mirrored: currentFacingMode === 'user' ? true : false, onLoadedData: function () { + videoReadyCallback(); + } }), + React__default.createElement(Canvas, { ref: canvas })))); +}); +Camera.displayName = 'Camera'; +var initCameraStream = function (stream, setStream, currentFacingMode, videoSourceDeviceId, setNumberOfCameras, setNotSupported, setPermissionDenied) { + var _a; + // stop any active streams in the window + if (stream) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + } + var constraints = { + audio: false, + video: { + deviceId: videoSourceDeviceId ? { exact: videoSourceDeviceId } : undefined, + facingMode: currentFacingMode, + width: { ideal: 1920 }, + height: { ideal: 1920 }, + }, + }; + if ((_a = navigator === null || navigator === void 0 ? void 0 : navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia) { + navigator.mediaDevices + .getUserMedia(constraints) + .then(function (stream) { + setStream(handleSuccess(stream, setNumberOfCameras)); + }) + .catch(function (err) { + handleError(err, setNotSupported, setPermissionDenied); + }); + } + else { + var getWebcam = navigator.getUserMedia || + navigator.webkitGetUserMedia || + navigator.mozGetUserMedia || + navigator.mozGetUserMedia || + navigator.msGetUserMedia; + if (getWebcam) { + getWebcam(constraints, function (stream) { + setStream(handleSuccess(stream, setNumberOfCameras)); + }, function (err) { + handleError(err, setNotSupported, setPermissionDenied); + }); + } + else { + setNotSupported(true); + } + } +}; +var handleSuccess = function (stream, setNumberOfCameras) { + navigator.mediaDevices + .enumerateDevices() + .then(function (r) { return setNumberOfCameras(r.filter(function (i) { return i.kind === 'videoinput'; }).length); }); + return stream; +}; +var handleError = function (error, setNotSupported, setPermissionDenied) { + console.error(error); + //https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia + if (error.name === 'PermissionDeniedError') { + setPermissionDenied(true); + } + else { + setNotSupported(true); + } +}; + +exports.Camera = Camera; diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..913a793 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,3 @@ +import { Camera } from './components/Camera/Camera'; +import { CameraType, CameraProps } from './components/Camera/types'; +export { Camera, CameraType, CameraProps }; diff --git a/dist/index.esm.js b/dist/index.esm.js new file mode 100644 index 0000000..8459a52 --- /dev/null +++ b/dist/index.esm.js @@ -0,0 +1,199 @@ +import React, { useRef, useState, useEffect, useImperativeHandle } from 'react'; +import styled from 'styled-components'; + +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ + +function __makeTemplateObject(cooked, raw) { + if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } + return cooked; +} + +var Wrapper = styled.div(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n"], ["\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n"]))); +var Container = styled.div(templateObject_2 || (templateObject_2 = __makeTemplateObject(["\n width: 100%;\n ", "\n"], ["\n width: 100%;\n ", + "\n"])), function (_a) { + var aspectRatio = _a.aspectRatio; + return aspectRatio === 'cover' + ? "\n position: absolute;\n bottom: 0;\n top: 0;\n left: 0;\n right: 0;" + : "\n position: relative;\n padding-bottom: " + 100 / aspectRatio + "%;"; +}); +var ErrorMsg = styled.div(templateObject_3 || (templateObject_3 = __makeTemplateObject(["\n padding: 40px;\n"], ["\n padding: 40px;\n"]))); +var Cam = styled.video(templateObject_4 || (templateObject_4 = __makeTemplateObject(["\n width: 100%;\n height: 100%;\n object-fit: cover;\n z-index: 0;\n transform: rotateY(", ");\n"], ["\n width: 100%;\n height: 100%;\n object-fit: cover;\n z-index: 0;\n transform: rotateY(", ");\n"])), function (_a) { + var mirrored = _a.mirrored; + return (mirrored ? '180deg' : '0deg'); +}); +var Canvas = styled.canvas(templateObject_5 || (templateObject_5 = __makeTemplateObject(["\n display: none;\n"], ["\n display: none;\n"]))); +var templateObject_1, templateObject_2, templateObject_3, templateObject_4, templateObject_5; + +var Camera = React.forwardRef(function (_a, ref) { + var _b = _a.facingMode, facingMode = _b === void 0 ? 'user' : _b, _c = _a.aspectRatio, aspectRatio = _c === void 0 ? 'cover' : _c, _d = _a.numberOfCamerasCallback, numberOfCamerasCallback = _d === void 0 ? function () { return null; } : _d, _e = _a.videoSourceDeviceId, videoSourceDeviceId = _e === void 0 ? undefined : _e, _f = _a.errorMessages, errorMessages = _f === void 0 ? { + 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.', + } : _f, _g = _a.videoReadyCallback, videoReadyCallback = _g === void 0 ? function () { return null; } : _g; + var player = useRef(null); + var canvas = useRef(null); + var container = useRef(null); + var _h = useState(0), numberOfCameras = _h[0], setNumberOfCameras = _h[1]; + var _j = useState(null), stream = _j[0], setStream = _j[1]; + var _k = useState(facingMode), currentFacingMode = _k[0], setFacingMode = _k[1]; + var _l = useState(false), notSupported = _l[0], setNotSupported = _l[1]; + var _m = useState(false), permissionDenied = _m[0], setPermissionDenied = _m[1]; + useEffect(function () { + numberOfCamerasCallback(numberOfCameras); + }, [numberOfCameras]); + useImperativeHandle(ref, function () { return ({ + takePhoto: function () { + var _a, _b, _c, _d; + if (numberOfCameras < 1) { + throw new Error(errorMessages.noCameraAccessible); + } + if (canvas === null || canvas === void 0 ? void 0 : canvas.current) { + var playerWidth = ((_a = player === null || player === void 0 ? void 0 : player.current) === null || _a === void 0 ? void 0 : _a.videoWidth) || 1280; + var playerHeight = ((_b = player === null || player === void 0 ? void 0 : player.current) === null || _b === void 0 ? void 0 : _b.videoHeight) || 720; + var playerAR = playerWidth / playerHeight; + var canvasWidth = ((_c = container === null || container === void 0 ? void 0 : container.current) === null || _c === void 0 ? void 0 : _c.offsetWidth) || 1280; + var canvasHeight = ((_d = container === null || container === void 0 ? void 0 : container.current) === null || _d === void 0 ? void 0 : _d.offsetHeight) || 1280; + var canvasAR = canvasWidth / canvasHeight; + var sX = void 0, sY = void 0, sW = void 0, sH = void 0; + 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; + var context = canvas.current.getContext('2d'); + if (context && (player === null || player === void 0 ? void 0 : player.current)) { + context.drawImage(player.current, sX, sY, sW, sH, 0, 0, sW, sH); + } + var imgData = canvas.current.toDataURL('image/jpeg'); + return imgData; + } + else { + throw new Error(errorMessages.canvas); + } + }, + switchCamera: function () { + 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 + } + var newFacingMode = currentFacingMode === 'user' ? 'environment' : 'user'; + setFacingMode(newFacingMode); + return newFacingMode; + }, + getNumberOfCameras: function () { + return numberOfCameras; + }, + }); }); + useEffect(function () { + initCameraStream(stream, setStream, currentFacingMode, videoSourceDeviceId, setNumberOfCameras, setNotSupported, setPermissionDenied); + }, [currentFacingMode, videoSourceDeviceId]); + useEffect(function () { + if (stream && player && player.current) { + player.current.srcObject = stream; + } + return function () { + if (stream) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + } + }; + }, [stream]); + return (React.createElement(Container, { ref: container, aspectRatio: aspectRatio }, + React.createElement(Wrapper, null, + notSupported ? React.createElement(ErrorMsg, null, errorMessages.noCameraAccessible) : null, + permissionDenied ? React.createElement(ErrorMsg, null, errorMessages.permissionDenied) : null, + React.createElement(Cam, { ref: player, id: "video", muted: true, autoPlay: true, playsInline: true, mirrored: currentFacingMode === 'user' ? true : false, onLoadedData: function () { + videoReadyCallback(); + } }), + React.createElement(Canvas, { ref: canvas })))); +}); +Camera.displayName = 'Camera'; +var initCameraStream = function (stream, setStream, currentFacingMode, videoSourceDeviceId, setNumberOfCameras, setNotSupported, setPermissionDenied) { + var _a; + // stop any active streams in the window + if (stream) { + stream.getTracks().forEach(function (track) { + track.stop(); + }); + } + var constraints = { + audio: false, + video: { + deviceId: videoSourceDeviceId ? { exact: videoSourceDeviceId } : undefined, + facingMode: currentFacingMode, + width: { ideal: 1920 }, + height: { ideal: 1920 }, + }, + }; + if ((_a = navigator === null || navigator === void 0 ? void 0 : navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia) { + navigator.mediaDevices + .getUserMedia(constraints) + .then(function (stream) { + setStream(handleSuccess(stream, setNumberOfCameras)); + }) + .catch(function (err) { + handleError(err, setNotSupported, setPermissionDenied); + }); + } + else { + var getWebcam = navigator.getUserMedia || + navigator.webkitGetUserMedia || + navigator.mozGetUserMedia || + navigator.mozGetUserMedia || + navigator.msGetUserMedia; + if (getWebcam) { + getWebcam(constraints, function (stream) { + setStream(handleSuccess(stream, setNumberOfCameras)); + }, function (err) { + handleError(err, setNotSupported, setPermissionDenied); + }); + } + else { + setNotSupported(true); + } + } +}; +var handleSuccess = function (stream, setNumberOfCameras) { + navigator.mediaDevices + .enumerateDevices() + .then(function (r) { return setNumberOfCameras(r.filter(function (i) { return i.kind === 'videoinput'; }).length); }); + return stream; +}; +var handleError = function (error, setNotSupported, setPermissionDenied) { + console.error(error); + //https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia + if (error.name === 'PermissionDeniedError') { + setPermissionDenied(true); + } + else { + setNotSupported(true); + } +}; + +export { Camera }; diff --git a/package-lock.json b/package-lock.json index f673072..291ca03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,19 @@ "node": ">=6.0.0" } }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.24.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.6.tgz",