@monkvision/camera-web 4.5.6 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -172,11 +172,26 @@ Main component exported by this package, displays a Camera preview and the given
172
172
  Object passed to Camera HUD components that is used to control the camera
173
173
 
174
174
  ### Properties
175
- | Prop | Type | Description |
176
- |-------------------|-----------------------------|----------------------------------------------------------------------------------------------------------|
177
- | takePicture | () => Promise<MonkPicture> | A function that you can call to ask the camera to take a picture. |
178
- | error | UserMediaError &#124; null | The error details if there has been an error when fetching the camera stream. |
179
- | isLoading | boolean | Boolean indicating if the camera preview is loading. |
180
- | retry | () => void | A function to retry the camera stream fetching in case of error. |
181
- | dimensions | PixelDimensions &#124; null | The Camera stream dimensions (`null` if there is no stream). |
182
- | previewDimensions | PixelDimensions &#124; null | The effective video dimensions of the Camera stream on the client screen (`null` if there is no stream). |
175
+ | Prop | Type | Description |
176
+ |-------------------|--------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
177
+ | takePicture | () => Promise<MonkPicture> | A function that you can call to ask the camera to take a picture. |
178
+ | getImageData | () => ImageData | Function used to take a raw screenshot of the camera stream for manual image processing. Performance tracking is disabled for this method (only available using the `takePicture` method). |
179
+ | compressImage | (image: ImageData) => Promise<MonkPicture> | Function used to compress raw image data into a MonkPicture object. Performance tracking is disabled for this method (only available using the `takePicture` method). |
180
+ | error | UserMediaError &#124; null | The error details if there has been an error when fetching the camera stream. |
181
+ | isLoading | boolean | Boolean indicating if the camera preview is loading. |
182
+ | retry | () => void | A function to retry the camera stream fetching in case of error. |
183
+ | dimensions | PixelDimensions &#124; null | The Camera stream dimensions (`null` if there is no stream). |
184
+ | previewDimensions | PixelDimensions &#124; null | The effective video dimensions of the Camera stream on the client screen (`null` if there is no stream). |
185
+
186
+
187
+ ## Hooks
188
+ ### useCameraPermission
189
+ ```tsx
190
+ import { useCameraPermission } from '@monkvision/camera-web';
191
+
192
+ function TestComponent() {
193
+ const { requestCameraPermission } = useCameraPermission();
194
+ return <button onClick={useCameraPermission}>Request Camera Permissions</button>;
195
+ }
196
+ ```
197
+ Custom hook that can be used to request the camera permissions on the current device.
@@ -56,8 +56,12 @@ function Camera(_a) {
56
56
  }), takePicture = _h.takePicture, isTakePictureLoading = _h.isLoading;
57
57
  var isLoading = isPreviewLoading || isTakePictureLoading;
58
58
  var cameraPreview = (0, react_1.useMemo)(function () { return ((0, jsx_runtime_1.jsxs)("div", __assign({ style: Camera_styles_1.styles['container'] }, { children: [(0, jsx_runtime_1.jsx)("video", { style: Camera_styles_1.styles['cameraPreview'], ref: videoRef, autoPlay: true, playsInline: true, controls: false, muted: true, "data-testid": 'camera-video-preview' }), (0, jsx_runtime_1.jsx)("canvas", { ref: canvasRef, style: Camera_styles_1.styles['cameraCanvas'], "data-testid": 'camera-canvas' })] }))); }, []);
59
+ var getImageData = (0, react_1.useCallback)(function () { return takeScreenshot(); }, [takeScreenshot]);
60
+ var compressImage = (0, react_1.useCallback)(function (image) { return compress(image); }, [compress]);
59
61
  return HUDComponent ? ((0, jsx_runtime_1.jsx)(HUDComponent, __assign({ handle: {
60
62
  takePicture: takePicture,
63
+ getImageData: getImageData,
64
+ compressImage: compressImage,
61
65
  error: error,
62
66
  retry: retry,
63
67
  isLoading: isLoading,
@@ -9,6 +9,25 @@ export interface CameraHandle {
9
9
  * A function that you can call to ask the camera to take a picture.
10
10
  */
11
11
  takePicture: () => Promise<MonkPicture>;
12
+ /**
13
+ * A function that you can call to get the current raw image data displayed on the camera stream. You can use this
14
+ * function if you need to apply a custom procesing to the image pixels and don't want the automatic compression logic
15
+ * of the Camera component. You can use the `handle.compressImage` method to compress the raw image data using the
16
+ * Camera component's compression configuration. If you just want to take a picture normally, use the
17
+ * `handle.takePicture` method.
18
+ *
19
+ * Note: This method does NOT use any monitoring tracking. The only way to enable monitoring is by taking pictures via
20
+ * the `handle.takePicture` method.
21
+ */
22
+ getImageData: () => ImageData;
23
+ /**
24
+ * A function that you can call to compress a raw ImageData (taken using the `handle.compressImage` function) into a
25
+ * MonkPicture object. This function will use the compression options passed as parameters to the Camera component.
26
+ *
27
+ * Note: This method does NOT use any monitoring tracking. The only way to enable monitoring is by taking pictures via
28
+ * the `handle.takePicture` method.
29
+ */
30
+ compressImage: (image: ImageData) => Promise<MonkPicture>;
12
31
  /**
13
32
  * The error details if there has been an error when fetching the camera stream.
14
33
  */
@@ -23,7 +23,7 @@ export interface CameraScreenshotConfig {
23
23
  *
24
24
  * @return A ImageData object that contains the raw pixel's data.
25
25
  */
26
- export type TakeScreenshotFunction = (monitoring: InternalCameraMonitoringConfig) => ImageData;
26
+ export type TakeScreenshotFunction = (monitoring?: InternalCameraMonitoringConfig) => ImageData;
27
27
  /**
28
28
  * Custom hook used to take screenshots of a video element. This hook is aimed to be used in pair with a `<canvas>`
29
29
  * element referenced by the returned ref.
@@ -16,10 +16,10 @@ var react_1 = require("react");
16
16
  var monitoring_1 = require("@monkvision/monitoring");
17
17
  var monitoring_2 = require("../monitoring");
18
18
  var utils_1 = require("./utils");
19
- function startScreenshotMeasurement(monitoring, dimensions) {
19
+ function startScreenshotMeasurement(dimensions, monitoring) {
20
20
  var _a;
21
21
  var _b, _c;
22
- (_b = monitoring.transaction) === null || _b === void 0 ? void 0 : _b.startMeasurement(monitoring_2.ScreenshotMeasurement.operation, {
22
+ (_b = monitoring === null || monitoring === void 0 ? void 0 : monitoring.transaction) === null || _b === void 0 ? void 0 : _b.startMeasurement(monitoring_2.ScreenshotMeasurement.operation, {
23
23
  data: monitoring.data,
24
24
  tags: __assign((_a = {}, _a[monitoring_2.ScreenshotMeasurement.outputResolutionTagName] = dimensions
25
25
  ? "".concat(dimensions.width, "x").concat(dimensions.height)
@@ -27,14 +27,14 @@ function startScreenshotMeasurement(monitoring, dimensions) {
27
27
  description: monitoring_2.ScreenshotMeasurement.description,
28
28
  });
29
29
  }
30
- function stopScreenshotMeasurement(monitoringConfig, status) {
30
+ function stopScreenshotMeasurement(status, monitoring) {
31
31
  var _a;
32
- (_a = monitoringConfig.transaction) === null || _a === void 0 ? void 0 : _a.stopMeasurement(monitoring_2.ScreenshotMeasurement.operation, status);
32
+ (_a = monitoring === null || monitoring === void 0 ? void 0 : monitoring.transaction) === null || _a === void 0 ? void 0 : _a.stopMeasurement(monitoring_2.ScreenshotMeasurement.operation, status);
33
33
  }
34
- function setScreeshotSizeMeasurement(monitoring, image) {
34
+ function setScreeshotSizeMeasurement(image, monitoring) {
35
35
  var _a;
36
36
  var imageSizeBytes = image.data.length;
37
- (_a = monitoring.transaction) === null || _a === void 0 ? void 0 : _a.setMeasurement(monitoring_2.ScreenshotSizeMeasurement.name, imageSizeBytes, 'byte');
37
+ (_a = monitoring === null || monitoring === void 0 ? void 0 : monitoring.transaction) === null || _a === void 0 ? void 0 : _a.setMeasurement(monitoring_2.ScreenshotSizeMeasurement.name, imageSizeBytes, 'byte');
38
38
  }
39
39
  /**
40
40
  * Custom hook used to take screenshots of a video element. This hook is aimed to be used in pair with a `<canvas>`
@@ -43,22 +43,22 @@ function setScreeshotSizeMeasurement(monitoring, image) {
43
43
  function useCameraScreenshot(_a) {
44
44
  var videoRef = _a.videoRef, canvasRef = _a.canvasRef, dimensions = _a.dimensions;
45
45
  return (0, react_1.useCallback)(function (monitoring) {
46
- startScreenshotMeasurement(monitoring, dimensions);
46
+ startScreenshotMeasurement(dimensions, monitoring);
47
47
  var context = (0, utils_1.getCanvasHandle)(canvasRef, function () {
48
- return stopScreenshotMeasurement(monitoring, monitoring_1.TransactionStatus.UNKNOWN_ERROR);
48
+ return stopScreenshotMeasurement(monitoring_1.TransactionStatus.UNKNOWN_ERROR, monitoring);
49
49
  }).context;
50
50
  if (!dimensions) {
51
- stopScreenshotMeasurement(monitoring, monitoring_1.TransactionStatus.UNKNOWN_ERROR);
51
+ stopScreenshotMeasurement(monitoring_1.TransactionStatus.UNKNOWN_ERROR, monitoring);
52
52
  throw new Error('Unable to take a picture because the video stream has no dimension.');
53
53
  }
54
54
  if (!videoRef.current) {
55
- stopScreenshotMeasurement(monitoring, monitoring_1.TransactionStatus.UNKNOWN_ERROR);
55
+ stopScreenshotMeasurement(monitoring_1.TransactionStatus.UNKNOWN_ERROR, monitoring);
56
56
  throw new Error('Unable to take a picture because the video element is null.');
57
57
  }
58
58
  context.drawImage(videoRef.current, 0, 0, dimensions.width, dimensions.height);
59
59
  var imageData = context.getImageData(0, 0, dimensions.width, dimensions.height);
60
- setScreeshotSizeMeasurement(monitoring, imageData);
61
- stopScreenshotMeasurement(monitoring, monitoring_1.TransactionStatus.OK);
60
+ setScreeshotSizeMeasurement(imageData, monitoring);
61
+ stopScreenshotMeasurement(monitoring_1.TransactionStatus.OK, monitoring);
62
62
  return imageData;
63
63
  }, [dimensions]);
64
64
  }
@@ -17,7 +17,7 @@ export interface UseCompressionParams {
17
17
  /**
18
18
  * Function used to compress images and create DataURI objects.
19
19
  */
20
- export type CompressFunction = (image: ImageData, monitoring: InternalCameraMonitoringConfig) => Promise<MonkPicture>;
20
+ export type CompressFunction = (image: ImageData, monitoring?: InternalCameraMonitoringConfig) => Promise<MonkPicture>;
21
21
  /**
22
22
  * Custom hook used to manage the camera <canvas> element used to take video screenshots and encode images.
23
23
  */
@@ -52,25 +52,25 @@ var monitoring_1 = require("@monkvision/monitoring");
52
52
  var react_1 = require("react");
53
53
  var monitoring_2 = require("../monitoring");
54
54
  var utils_1 = require("./utils");
55
- function startCompressionMeasurement(monitoring, options, image) {
55
+ function startCompressionMeasurement(options, image, monitoring) {
56
56
  var _a;
57
57
  var _b, _c;
58
- (_b = monitoring.transaction) === null || _b === void 0 ? void 0 : _b.startMeasurement(monitoring_2.CompressionMeasurement.operation, {
59
- data: monitoring.data,
60
- tags: __assign((_a = {}, _a[monitoring_2.CompressionMeasurement.formatTagName] = options.format, _a[monitoring_2.CompressionMeasurement.qualityTagName] = options.quality, _a[monitoring_2.CompressionMeasurement.dimensionsTagName] = "".concat(image.width, "x").concat(image.height), _a), ((_c = monitoring.tags) !== null && _c !== void 0 ? _c : {})),
58
+ (_b = monitoring === null || monitoring === void 0 ? void 0 : monitoring.transaction) === null || _b === void 0 ? void 0 : _b.startMeasurement(monitoring_2.CompressionMeasurement.operation, {
59
+ data: monitoring === null || monitoring === void 0 ? void 0 : monitoring.data,
60
+ tags: __assign((_a = {}, _a[monitoring_2.CompressionMeasurement.formatTagName] = options.format, _a[monitoring_2.CompressionMeasurement.qualityTagName] = options.quality, _a[monitoring_2.CompressionMeasurement.dimensionsTagName] = "".concat(image.width, "x").concat(image.height), _a), ((_c = monitoring === null || monitoring === void 0 ? void 0 : monitoring.tags) !== null && _c !== void 0 ? _c : {})),
61
61
  description: monitoring_2.CompressionMeasurement.description,
62
62
  });
63
63
  }
64
- function stopCompressionMeasurement(monitoring, status) {
64
+ function stopCompressionMeasurement(status, monitoring) {
65
65
  var _a;
66
- (_a = monitoring.transaction) === null || _a === void 0 ? void 0 : _a.stopMeasurement(monitoring_2.CompressionMeasurement.operation, status);
66
+ (_a = monitoring === null || monitoring === void 0 ? void 0 : monitoring.transaction) === null || _a === void 0 ? void 0 : _a.stopMeasurement(monitoring_2.CompressionMeasurement.operation, status);
67
67
  }
68
- function setCustomMeasurements(monitoring, image, picture) {
68
+ function setCustomMeasurements(image, picture, monitoring) {
69
69
  var _a, _b;
70
70
  var imageSizeBytes = image.data.length;
71
71
  var pictureSizeBytes = picture.blob.size;
72
- (_a = monitoring.transaction) === null || _a === void 0 ? void 0 : _a.setMeasurement(monitoring_2.CompressionSizeRatioMeasurement.name, pictureSizeBytes / imageSizeBytes, 'ratio');
73
- (_b = monitoring.transaction) === null || _b === void 0 ? void 0 : _b.setMeasurement(monitoring_2.PictureSizeMeasurement.name, pictureSizeBytes, 'byte');
72
+ (_a = monitoring === null || monitoring === void 0 ? void 0 : monitoring.transaction) === null || _a === void 0 ? void 0 : _a.setMeasurement(monitoring_2.CompressionSizeRatioMeasurement.name, pictureSizeBytes / imageSizeBytes, 'ratio');
73
+ (_b = monitoring === null || monitoring === void 0 ? void 0 : monitoring.transaction) === null || _b === void 0 ? void 0 : _b.setMeasurement(monitoring_2.PictureSizeMeasurement.name, pictureSizeBytes, 'byte');
74
74
  }
75
75
  function compressUsingBrowser(image, canvasRef, options) {
76
76
  var _a = (0, utils_1.getCanvasHandle)(canvasRef), canvas = _a.canvas, context = _a.context;
@@ -103,19 +103,19 @@ function useCompression(_a) {
103
103
  return __generator(this, function (_a) {
104
104
  switch (_a.label) {
105
105
  case 0:
106
- startCompressionMeasurement(monitoring, options, image);
106
+ startCompressionMeasurement(options, image, monitoring);
107
107
  _a.label = 1;
108
108
  case 1:
109
109
  _a.trys.push([1, 3, , 4]);
110
110
  return [4 /*yield*/, compressUsingBrowser(image, canvasRef, options)];
111
111
  case 2:
112
112
  picture = _a.sent();
113
- setCustomMeasurements(monitoring, image, picture);
114
- stopCompressionMeasurement(monitoring, monitoring_1.TransactionStatus.OK);
113
+ setCustomMeasurements(image, picture, monitoring);
114
+ stopCompressionMeasurement(monitoring_1.TransactionStatus.OK, monitoring);
115
115
  return [2 /*return*/, picture];
116
116
  case 3:
117
117
  err_1 = _a.sent();
118
- stopCompressionMeasurement(monitoring, monitoring_1.TransactionStatus.UNKNOWN_ERROR);
118
+ stopCompressionMeasurement(monitoring_1.TransactionStatus.UNKNOWN_ERROR, monitoring);
119
119
  throw err_1;
120
120
  case 4: return [2 /*return*/];
121
121
  }
@@ -82,6 +82,10 @@ export interface UserMediaError {
82
82
  * @see useUserMedia
83
83
  */
84
84
  export interface UserMediaResult {
85
+ /**
86
+ * The getUserMedia function that can be used to fetch the stream data manually if no videoRef is passed.
87
+ */
88
+ getUserMedia: () => Promise<MediaStream>;
85
89
  /**
86
90
  * The resulting video stream. The stream can be null when not initialized or in case of an error.
87
91
  */
@@ -117,17 +121,19 @@ export interface UserMediaResult {
117
121
  /**
118
122
  * React hook that wraps the `navigator.mediaDevices.getUserMedia` browser function in order to add React logic layers
119
123
  * and utility tools :
120
- * - Creates an effect for `getUserMedia` that will be run everytime some state parameters are updated.
124
+ * - Creates an effect for `getUserMedia` that will be run everytime some state parameters are updated (the effect is
125
+ * run only if the videoRef is passed, if not, the `getUserMedia` function must be called manually).
121
126
  * - Will call `track.applyConstraints` when the video contstraints are updated in order to update the video stream.
122
127
  * - Makes sure that the `getUserMedia` is only called when it needs to be using memoized state.
123
128
  * - Provides various utilities such as error catching, loading information and a retry on failure feature.
124
129
  *
125
130
  * @param constraints The same media constraints you would pass to the `getUserMedia` function. Note that this hook has
126
131
  * been designed for video only, so audio constraints could provoke unexpected behaviour.
127
- * @param videoRef The ref to the video element displaying the camera preview stream.
132
+ * @param videoRef The ref to the video element displaying the camera preview stream. If the ref is not passed, the
133
+ * effect will not automatically be called.
128
134
  * @return The result of this hook contains the resulting video stream, an error object if there has been an error, a
129
135
  * loading indicator and a retry function that tries to get a camera stream again. See the `UserMediaResult` interface
130
136
  * for more information.
131
137
  * @see UserMediaResult
132
138
  */
133
- export declare function useUserMedia(constraints: MediaStreamConstraints, videoRef: RefObject<HTMLVideoElement>): UserMediaResult;
139
+ export declare function useUserMedia(constraints: MediaStreamConstraints, videoRef: RefObject<HTMLVideoElement> | null): UserMediaResult;
@@ -179,14 +179,16 @@ function getStreamDimensions(stream, checkOrientation) {
179
179
  /**
180
180
  * React hook that wraps the `navigator.mediaDevices.getUserMedia` browser function in order to add React logic layers
181
181
  * and utility tools :
182
- * - Creates an effect for `getUserMedia` that will be run everytime some state parameters are updated.
182
+ * - Creates an effect for `getUserMedia` that will be run everytime some state parameters are updated (the effect is
183
+ * run only if the videoRef is passed, if not, the `getUserMedia` function must be called manually).
183
184
  * - Will call `track.applyConstraints` when the video contstraints are updated in order to update the video stream.
184
185
  * - Makes sure that the `getUserMedia` is only called when it needs to be using memoized state.
185
186
  * - Provides various utilities such as error catching, loading information and a retry on failure feature.
186
187
  *
187
188
  * @param constraints The same media constraints you would pass to the `getUserMedia` function. Note that this hook has
188
189
  * been designed for video only, so audio constraints could provoke unexpected behaviour.
189
- * @param videoRef The ref to the video element displaying the camera preview stream.
190
+ * @param videoRef The ref to the video element displaying the camera preview stream. If the ref is not passed, the
191
+ * effect will not automatically be called.
190
192
  * @return The result of this hook contains the resulting video stream, an error object if there has been an error, a
191
193
  * loading indicator and a retry function that tries to get a camera stream again. See the `UserMediaResult` interface
192
194
  * for more information.
@@ -202,17 +204,11 @@ function useUserMedia(constraints, videoRef) {
202
204
  var _f = (0, react_1.useState)(null), selectedCameraDeviceId = _f[0], setSelectedCameraDeviceId = _f[1];
203
205
  var _g = (0, react_1.useState)(null), lastConstraintsApplied = _g[0], setLastConstraintsApplied = _g[1];
204
206
  var handleError = (0, monitoring_1.useMonitoring)().handleError;
205
- var isActive = (0, react_1.useRef)(true);
206
- var cameraPermissionState = null;
207
- (0, react_1.useEffect)(function () {
208
- return function () {
209
- isActive.current = false;
210
- };
211
- }, []);
212
- var handleGetUserMediaError = function (err) {
207
+ var isMounted = (0, common_1.useIsMounted)();
208
+ var handleGetUserMediaError = function (err, permissionState) {
213
209
  var type = UserMediaErrorType.OTHER;
214
210
  if (err instanceof Error && err.name === 'NotAllowedError') {
215
- switch (cameraPermissionState) {
211
+ switch (permissionState) {
216
212
  case 'denied':
217
213
  type = UserMediaErrorType.WEBPAGE_NOT_ALLOWED;
218
214
  break;
@@ -232,11 +228,13 @@ function useUserMedia(constraints, videoRef) {
232
228
  setIsLoading(false);
233
229
  };
234
230
  var onStreamInactive = function () {
235
- setError({
236
- type: UserMediaErrorType.STREAM_INACTIVE,
237
- nativeError: new Error('The camera stream was closed.'),
238
- });
239
- setIsLoading(false);
231
+ if (isMounted()) {
232
+ setError({
233
+ type: UserMediaErrorType.STREAM_INACTIVE,
234
+ nativeError: new Error('The camera stream was closed.'),
235
+ });
236
+ setIsLoading(false);
237
+ }
240
238
  };
241
239
  var retry = (0, react_1.useCallback)(function () {
242
240
  if (error && !isLoading) {
@@ -246,89 +244,98 @@ function useUserMedia(constraints, videoRef) {
246
244
  setLastConstraintsApplied(null);
247
245
  }
248
246
  }, [error, isLoading]);
249
- (0, react_1.useEffect)(function () {
250
- if (error || isLoading || (0, fast_deep_equal_1.default)(lastConstraintsApplied, constraints)) {
251
- return;
252
- }
253
- setLastConstraintsApplied(constraints);
254
- var getUserMedia = function () { return __awaiter(_this, void 0, void 0, function () {
255
- var deviceDetails, updatedConstraints, str;
256
- return __generator(this, function (_a) {
257
- switch (_a.label) {
258
- case 0:
259
- setIsLoading(true);
260
- if (stream) {
261
- stream.removeEventListener('inactive', onStreamInactive);
262
- stream.getTracks().forEach(function (track) { return track.stop(); });
263
- }
264
- return [4 /*yield*/, (0, utils_1.analyzeCameraDevices)(constraints)];
265
- case 1:
266
- deviceDetails = _a.sent();
267
- updatedConstraints = __assign(__assign({}, constraints), { video: __assign(__assign({}, (constraints ? constraints.video : {})), { deviceId: { exact: deviceDetails.validDeviceIds } }) });
268
- return [4 /*yield*/, navigator.mediaDevices.getUserMedia(updatedConstraints)];
269
- case 2:
270
- str = _a.sent();
271
- str === null || str === void 0 ? void 0 : str.addEventListener('inactive', onStreamInactive);
272
- if (isActive.current) {
273
- setStream(str);
274
- setDimensions(getStreamDimensions(str, true));
275
- setIsLoading(false);
276
- setAvailableCameraDevices(deviceDetails.availableDevices);
277
- setSelectedCameraDeviceId(getStreamDeviceId(str));
278
- }
279
- return [2 /*return*/];
280
- }
281
- });
282
- }); };
283
- var getCameraPermissionState = function () { return __awaiter(_this, void 0, void 0, function () {
284
- var err_1;
285
- return __generator(this, function (_a) {
286
- switch (_a.label) {
287
- case 0:
288
- _a.trys.push([0, 2, , 3]);
289
- return [4 /*yield*/, navigator.permissions.query({
290
- name: 'camera',
291
- })];
292
- case 1: return [2 /*return*/, _a.sent()];
293
- case 2:
294
- err_1 = _a.sent();
295
- return [2 /*return*/, null];
296
- case 3: return [2 /*return*/];
297
- }
298
- });
299
- }); };
300
- getUserMedia()
301
- .catch(function (err) {
302
- return Promise.all([err, getCameraPermissionState()]);
303
- })
304
- .then(function (result) {
305
- if (!result) {
306
- return Promise.all([null, getCameraPermissionState()]);
247
+ var getUserMedia = (0, react_1.useCallback)(function () { return __awaiter(_this, void 0, void 0, function () {
248
+ var deviceDetails, updatedConstraints, str;
249
+ return __generator(this, function (_a) {
250
+ switch (_a.label) {
251
+ case 0:
252
+ setIsLoading(true);
253
+ if (stream) {
254
+ stream.removeEventListener('inactive', onStreamInactive);
255
+ stream.getTracks().forEach(function (track) { return track.stop(); });
256
+ }
257
+ return [4 /*yield*/, (0, utils_1.analyzeCameraDevices)(constraints)];
258
+ case 1:
259
+ deviceDetails = _a.sent();
260
+ updatedConstraints = __assign(__assign({}, constraints), { video: __assign(__assign({}, (constraints ? constraints.video : {})), { deviceId: { exact: deviceDetails.validDeviceIds } }) });
261
+ return [4 /*yield*/, navigator.mediaDevices.getUserMedia(updatedConstraints)];
262
+ case 2:
263
+ str = _a.sent();
264
+ str === null || str === void 0 ? void 0 : str.addEventListener('inactive', onStreamInactive);
265
+ if (isMounted()) {
266
+ setStream(str);
267
+ setDimensions(getStreamDimensions(str, true));
268
+ setIsLoading(false);
269
+ setAvailableCameraDevices(deviceDetails.availableDevices);
270
+ setSelectedCameraDeviceId(getStreamDeviceId(str));
271
+ }
272
+ return [2 /*return*/, str];
273
+ }
274
+ });
275
+ }); }, [stream, constraints]);
276
+ var getCameraPermissionState = function () { return __awaiter(_this, void 0, void 0, function () {
277
+ var err_1;
278
+ return __generator(this, function (_a) {
279
+ switch (_a.label) {
280
+ case 0:
281
+ _a.trys.push([0, 2, , 3]);
282
+ return [4 /*yield*/, navigator.permissions.query({
283
+ name: 'camera',
284
+ })];
285
+ case 1: return [2 /*return*/, _a.sent()];
286
+ case 2:
287
+ err_1 = _a.sent();
288
+ return [2 /*return*/, null];
289
+ case 3: return [2 /*return*/];
307
290
  }
308
- return result;
309
- })
310
- .then(function (_a) {
311
- var _b;
312
- var err = _a[0], cameraPermission = _a[1];
313
- cameraPermissionState = (_b = cameraPermission === null || cameraPermission === void 0 ? void 0 : cameraPermission.state) !== null && _b !== void 0 ? _b : null;
314
- if (err && isActive.current) {
315
- handleGetUserMediaError(err);
316
- throw err;
291
+ });
292
+ }); };
293
+ (0, react_1.useEffect)(function () {
294
+ if (videoRef) {
295
+ if (error || isLoading || (0, fast_deep_equal_1.default)(lastConstraintsApplied, constraints)) {
296
+ return;
317
297
  }
318
- })
319
- .catch(handleError);
320
- }, [constraints, stream, error, isLoading, lastConstraintsApplied]);
298
+ setLastConstraintsApplied(constraints);
299
+ var effect = function () { return __awaiter(_this, void 0, void 0, function () {
300
+ var err_2, permissionState;
301
+ var _a, _b;
302
+ return __generator(this, function (_c) {
303
+ switch (_c.label) {
304
+ case 0:
305
+ _c.trys.push([0, 2, , 4]);
306
+ return [4 /*yield*/, getUserMedia()];
307
+ case 1:
308
+ _c.sent();
309
+ return [3 /*break*/, 4];
310
+ case 2:
311
+ err_2 = _c.sent();
312
+ return [4 /*yield*/, getCameraPermissionState()];
313
+ case 3:
314
+ permissionState = (_b = (_a = (_c.sent())) === null || _a === void 0 ? void 0 : _a.state) !== null && _b !== void 0 ? _b : null;
315
+ if (err_2 && isMounted()) {
316
+ handleGetUserMediaError(err_2, permissionState);
317
+ throw err_2;
318
+ }
319
+ return [3 /*break*/, 4];
320
+ case 4: return [2 /*return*/];
321
+ }
322
+ });
323
+ }); };
324
+ effect().catch(handleError);
325
+ }
326
+ }, [constraints, stream, error, isLoading, lastConstraintsApplied, getUserMedia, videoRef]);
321
327
  (0, react_1.useEffect)(function () {
322
- if (stream && videoRef.current) {
328
+ if (stream && videoRef && videoRef.current) {
323
329
  // eslint-disable-next-line no-param-reassign
324
330
  videoRef.current.onresize = function () {
325
- if (isActive.current) {
331
+ if (isMounted()) {
326
332
  setDimensions(getStreamDimensions(stream, false));
327
333
  }
328
334
  };
329
335
  }
330
- }, [stream]);
336
+ }, [stream, videoRef]);
331
337
  return (0, common_1.useObjectMemo)({
338
+ getUserMedia: getUserMedia,
332
339
  stream: stream,
333
340
  dimensions: dimensions,
334
341
  error: error,
@@ -0,0 +1 @@
1
+ export * from './useCameraPermission';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./useCameraPermission"), exports);
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Handle used to request camera permission on the user device.
3
+ */
4
+ export interface CameraPermissionHandle {
5
+ /**
6
+ * Callback that can be used to request the camera permission on the current device.
7
+ */
8
+ requestCameraPermission: () => Promise<void>;
9
+ }
10
+ /**
11
+ * Custom hook that can be used to request the camera permissions on the current device.
12
+ */
13
+ export declare function useCameraPermission(): CameraPermissionHandle;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.useCameraPermission = void 0;
40
+ var react_1 = require("react");
41
+ var common_1 = require("@monkvision/common");
42
+ var types_1 = require("@monkvision/types");
43
+ var Camera_1 = require("../Camera");
44
+ var utils_1 = require("../Camera/hooks/utils");
45
+ var hooks_1 = require("../Camera/hooks");
46
+ /**
47
+ * Custom hook that can be used to request the camera permissions on the current device.
48
+ */
49
+ function useCameraPermission() {
50
+ var _this = this;
51
+ var contraints = (0, react_1.useMemo)(function () {
52
+ return (0, utils_1.getMediaConstraints)({
53
+ resolution: (0, common_1.isMobileDevice)() ? types_1.CameraResolution.UHD_4K : types_1.CameraResolution.FHD_1080P,
54
+ facingMode: Camera_1.CameraFacingMode.ENVIRONMENT,
55
+ });
56
+ }, []);
57
+ var getUserMedia = (0, hooks_1.useUserMedia)(contraints, null).getUserMedia;
58
+ var requestCameraPermission = (0, react_1.useCallback)(function () { return __awaiter(_this, void 0, void 0, function () {
59
+ var stream;
60
+ return __generator(this, function (_a) {
61
+ switch (_a.label) {
62
+ case 0: return [4 /*yield*/, getUserMedia()];
63
+ case 1:
64
+ stream = _a.sent();
65
+ stream.getTracks().forEach(function (track) { return track.stop(); });
66
+ return [2 /*return*/];
67
+ }
68
+ });
69
+ }); }, [getUserMedia]);
70
+ return (0, common_1.useObjectMemo)({ requestCameraPermission: requestCameraPermission });
71
+ }
72
+ exports.useCameraPermission = useCameraPermission;
package/lib/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './Camera';
2
2
  export * from './SimpleCameraHUD';
3
+ export * from './hooks';
3
4
  export * from './utils';
4
5
  export * from './i18n';
package/lib/index.js CHANGED
@@ -16,5 +16,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./Camera"), exports);
18
18
  __exportStar(require("./SimpleCameraHUD"), exports);
19
+ __exportStar(require("./hooks"), exports);
19
20
  __exportStar(require("./utils"), exports);
20
21
  __exportStar(require("./i18n"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monkvision/camera-web",
3
- "version": "4.5.6",
3
+ "version": "5.0.0",
4
4
  "license": "BSD-3-Clause-Clear",
5
5
  "packageManager": "yarn@3.2.4",
6
6
  "description": "MonkJs camera package for React (web) used to display a camera preview and take pictures",
@@ -28,9 +28,9 @@
28
28
  "lint:fix": "yarn run prettier:fix && yarn run eslint:fix"
29
29
  },
30
30
  "dependencies": {
31
- "@monkvision/common": "4.5.6",
32
- "@monkvision/common-ui-web": "4.5.6",
33
- "@monkvision/monitoring": "4.5.6",
31
+ "@monkvision/common": "5.0.0",
32
+ "@monkvision/common-ui-web": "5.0.0",
33
+ "@monkvision/monitoring": "5.0.0",
34
34
  "fast-deep-equal": "^3.1.3",
35
35
  "i18next": "^23.4.5",
36
36
  "react-i18next": "^13.2.0"
@@ -42,13 +42,13 @@
42
42
  "react-router-dom": "^6.22.3"
43
43
  },
44
44
  "devDependencies": {
45
- "@monkvision/eslint-config-base": "4.5.6",
46
- "@monkvision/eslint-config-typescript": "4.5.6",
47
- "@monkvision/eslint-config-typescript-react": "4.5.6",
48
- "@monkvision/jest-config": "4.5.6",
49
- "@monkvision/prettier-config": "4.5.6",
50
- "@monkvision/test-utils": "4.5.6",
51
- "@monkvision/typescript-config": "4.5.6",
45
+ "@monkvision/eslint-config-base": "5.0.0",
46
+ "@monkvision/eslint-config-typescript": "5.0.0",
47
+ "@monkvision/eslint-config-typescript-react": "5.0.0",
48
+ "@monkvision/jest-config": "5.0.0",
49
+ "@monkvision/prettier-config": "5.0.0",
50
+ "@monkvision/test-utils": "5.0.0",
51
+ "@monkvision/typescript-config": "5.0.0",
52
52
  "@testing-library/react": "^12.1.5",
53
53
  "@testing-library/react-hooks": "^8.0.1",
54
54
  "@types/fscreen": "^1.0.1",
@@ -90,5 +90,5 @@
90
90
  "url": "https://github.com/monkvision/monkjs/issues"
91
91
  },
92
92
  "homepage": "https://github.com/monkvision/monkjs",
93
- "gitHead": "c12e283e4abf1f810fdbc924a2d199652b92db30"
93
+ "gitHead": "6426efb1b64fde310666b5556fc76aeb7c8f2584"
94
94
  }