Skip to content

Commit bf7d877

Browse files
committed
Small fixes on Camera layout
1 parent 537a564 commit bf7d877

File tree

10 files changed

+322
-261
lines changed

10 files changed

+322
-261
lines changed

packages/camera/src/components/Camera/index.js

Lines changed: 66 additions & 203 deletions
Original file line numberDiff line numberDiff line change
@@ -1,239 +1,104 @@
1-
import React, { forwardRef, useCallback, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from 'react';
1+
import React, { forwardRef, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from 'react';
22
import createElement from 'react-native-web/dist/exports/createElement';
3-
import adapter from 'webrtc-adapter';
43
import PropTypes from 'prop-types';
54

65
import { Text, useWindowDimensions, View } from 'react-native';
76
import { utils } from '@monkvision/toolkit';
87

8+
import { findDevices, findBestCandidate, setVideoSource } from './utils';
9+
import getOS from '../../utils/getOS';
910
import styles from './styles';
1011

1112
const { getSize } = utils.styles;
1213

13-
const Video = React.forwardRef((props, ref) => createElement('video', { ...props, ref }));
14-
15-
const tests = [
16-
// {
17-
// label: '4K(UHD) 4:3',
18-
// width: 3840,
19-
// height: 2880,
20-
// ratio: '4:3',
21-
// }, {
22-
// label: '4K(UHD) 16:9',
23-
// width: 3840,
24-
// height: 2160,
25-
// ratio: '16:9',
26-
// },
27-
{
28-
label: 'FHD 4:3',
29-
width: 1920,
30-
height: 1440,
31-
ratio: '4:3',
32-
}, {
33-
label: 'FHD 16:9',
34-
width: 1920,
35-
height: 1080,
36-
ratio: '16:9',
37-
}, {
38-
label: 'UXGA',
39-
width: 1600,
40-
height: 1200,
41-
ratio: '4:3',
42-
}, {
43-
label: 'HD(720p)',
44-
width: 1280,
45-
height: 720,
46-
ratio: '16:9',
47-
}, {
48-
label: 'SVGA',
49-
width: 800,
50-
height: 600,
51-
ratio: '4:3',
52-
}, {
53-
label: 'VGA',
54-
width: 640,
55-
height: 480,
56-
ratio: '4:3',
57-
}, {
58-
label: 'CIF',
59-
width: 352,
60-
height: 288,
61-
ratio: '4:3',
62-
}, {
63-
label: 'QVGA',
64-
width: 320,
65-
height: 240,
66-
ratio: '4:3',
67-
}, {
68-
label: 'QCIF',
69-
width: 176,
70-
height: 144,
71-
ratio: '4:3',
72-
}, {
73-
label: 'QQVGA',
74-
width: 160,
75-
height: 120,
76-
ratio: '4:3',
77-
}];
78-
79-
function getUserMedia(constraints) {
80-
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
81-
return navigator.mediaDevices.getUserMedia(constraints);
82-
}
83-
84-
// Some browsers partially implement mediaDevices. We can't just assign an object
85-
// with getUserMedia as it would overwrite existing properties.
86-
// Here, we will just add the getUserMedia property if it's missing.
87-
88-
// First get hold of the legacy getUserMedia, if present
89-
const get = navigator.getUserMedia
90-
|| navigator.webkitGetUserMedia
91-
|| navigator.mozGetUserMedia
92-
|| (() => {
93-
const error = new Error('Permission unimplemented');
94-
error.code = 0;
95-
error.name = 'NotAllowedError';
96-
throw error;
97-
});
98-
99-
return new Promise((resolve, reject) => {
100-
get.call(navigator, constraints, resolve, reject);
101-
});
102-
}
14+
const Video = React.forwardRef(
15+
(props, ref) => createElement(
16+
'video',
17+
{ ...props, ref },
18+
),
19+
);
10320

10421
const Camera = ({ children, containerStyle, onCameraReady, title }, ref) => {
22+
const videoRef = useRef();
23+
const expoCameraRef = useRef();
10524
const windowDimensions = useWindowDimensions();
106-
const videoEl = useRef();
107-
const canvasEl = useRef();
108-
const [candidate, setCandidate] = useState();
109-
const [loading, setLoading] = useState(false);
110-
111-
const { width: videoWidth, height: videoHeight } = useMemo(() => {
112-
if (!candidate) { return { width: 0, height: 0 }; }
113-
return getSize(candidate.test.ratio, windowDimensions, 'number');
114-
}, [candidate, windowDimensions]);
115-
116-
const gum = useCallback(async (test, device) => {
117-
if (window.stream) { window.stream.getTracks().forEach((track) => track.stop()); }
118-
119-
const OS = utils.getOS();
120-
const facingMode = ['iOS', 'Android'].includes(OS) ? { exact: 'environment' } : 'environment';
12125

122-
const constraints = {
123-
audio: false,
124-
video: {
125-
facingMode,
126-
deviceId: device.deviceId ? { exact: device.deviceId } : undefined,
127-
width: { exact: test.width },
128-
height: { exact: test.height },
129-
},
130-
};
131-
132-
let result;
133-
try {
134-
result = await getUserMedia(constraints);
135-
} catch (error) { result = { error }; }
136-
137-
return [test, result, constraints, device];
138-
}, []);
26+
const [camera, setCamera] = useState({
27+
stream: null,
28+
width: 0,
29+
height: 0,
30+
ratio: '4:3',
31+
pictureSize: '',
32+
});
13933

140-
const findBestCandidate = useCallback(async (devices) => {
141-
const testResults = devices
142-
.map((device) => tests.map(async (test) => gum(test, device)))
143-
.flat();
34+
const [loading, setLoading] = useState(false);
14435

145-
const [test, stream, constraint] = await testResults
146-
.reduce(async (resultA, resultB) => {
147-
const [testA, streamA] = await resultA;
148-
const [testB, streamB] = await resultB;
36+
const videoSize = useMemo(
37+
() => getSize(camera.ratio, windowDimensions, 'number'),
38+
[camera, windowDimensions],
39+
);
14940

150-
if (streamA.error) { return resultB; }
151-
if (streamB.error) { return resultA; }
41+
useImperativeHandle(ref, () => ({
42+
async takePicture() {
43+
if (!videoRef.current) { throw new Error('Camera is not ready!'); }
44+
const { width, height, stream } = camera;
15245

153-
return testA.width > testB.width ? resultA : resultB;
154-
});
46+
if (ImageCapture && getOS() !== 'ios') {
47+
const track = stream.getVideoTracks()[0];
48+
const imageCapture = new ImageCapture(track);
15549

156-
return {
157-
test,
158-
stream,
159-
constraint,
160-
ask: `${test.width}x${test.height}`,
161-
browserVer: `${adapter.browserDetails.browser} ${adapter.browserDetails.version}`,
162-
};
163-
}, [gum]);
50+
const blob = await imageCapture.takePhoto({
51+
imageWidth: width,
52+
imageHeight: height,
53+
});
16454

165-
const findDevices = useCallback(async () => {
166-
const constraints = { audio: false, video: { facingMode: 'environment' } };
167-
const mediaDevices = await navigator.mediaDevices.enumerateDevices();
168-
window.stream = await getUserMedia(constraints);
55+
const uri = URL.createObjectURL(blob);
16956

170-
return mediaDevices.filter(({ kind }) => kind === 'videoinput');
171-
}, []);
57+
return { uri };
58+
}
17259

173-
useImperativeHandle(
174-
ref,
175-
() => ({
176-
async takePicture() {
177-
if (!videoEl.current || videoEl.current?.readyState !== videoEl.current?.HAVE_ENOUGH_DATA) {
178-
throw new Error(
179-
'ERR_CAMERA_NOT_READY',
180-
'HTMLVideoElement does not have enough camera data to construct an image yet.',
181-
);
182-
}
60+
const canvas = document.createElement('canvas');
61+
canvas.width = width;
62+
canvas.height = height;
18363

184-
canvasEl.current
185-
.getContext('2d')
186-
.drawImage(videoEl.current, 0, 0, canvasEl.current.width, canvasEl.current.height);
64+
canvas.getContext('2d')
65+
.drawImage(videoRef.current, 0, 0, width, height);
18766

188-
const imageType = utils.getOS() === 'ios' ? 'image/png' : 'image/webp';
189-
const uri = canvasEl.current.toDataURL(imageType);
67+
const imageType = utils.getOS() === 'ios' ? 'image/png' : 'image/webp';
68+
const uri = canvas.toDataURL(imageType);
19069

191-
return { uri };
192-
},
193-
async resumePreview() {
194-
if (videoEl.current) {
195-
videoEl.current.play();
196-
}
197-
},
198-
async pausePreview() {
199-
if (videoEl.current) {
200-
videoEl.current.pause();
201-
}
202-
},
203-
}),
204-
[videoEl],
205-
);
70+
return { uri };
71+
},
72+
async resumePreview() {
73+
if (videoRef.current) {
74+
videoRef.current.play();
75+
}
76+
},
77+
async pausePreview() {
78+
if (videoRef.current) {
79+
videoRef.current.pause();
80+
}
81+
},
82+
}),
83+
[camera]);
20684

20785
useLayoutEffect(() => {
20886
(async () => {
209-
if (videoEl && !candidate && !loading) {
210-
const video = videoEl.current;
211-
87+
if (videoRef && !camera.stream && !loading) {
21288
setLoading(true);
21389

214-
const canvas = canvasEl.current;
21590
const devices = await findDevices();
21691
const bestCandidate = await findBestCandidate(devices);
21792

218-
setCandidate(bestCandidate);
219-
220-
if ('srcObject' in video) {
221-
video.srcObject = bestCandidate.stream;
222-
video.play();
223-
} else {
224-
video.src = window.URL.createObjectURL(bestCandidate.stream);
225-
}
226-
227-
canvas.width = bestCandidate.test.width;
228-
canvas.height = bestCandidate.test.height;
229-
230-
video.onloadedmetadata = () => { video.play(); };
93+
setVideoSource(videoRef.current, bestCandidate.stream);
23194

95+
setCamera(bestCandidate);
23296
setLoading(false);
97+
23398
onCameraReady();
23499
}
235100
})();
236-
}, [candidate, findBestCandidate, findDevices, loading, onCameraReady, videoEl]);
101+
}, [camera, loading, onCameraReady, videoRef]);
237102

238103
return (
239104
<View
@@ -245,13 +110,11 @@ const Camera = ({ children, containerStyle, onCameraReady, title }, ref) => {
245110
<Video
246111
autoPlay
247112
playsInline
248-
ref={videoEl}
249-
width={videoWidth}
250-
height={videoHeight}
113+
ref={videoRef}
114+
{...videoSize}
251115
/>
252-
<canvas ref={canvasEl} />
253116
{children}
254-
{(title !== '' && candidate) && <Text style={styles.title}>{title}</Text>}
117+
<Text style={styles.title}>{title}</Text>
255118
</View>
256119
);
257120
};

packages/camera/src/components/Camera/index.native.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import React, { forwardRef, useCallback } from 'react';
22
import PropTypes from 'prop-types';
33

4-
import { Text, View } from 'react-native';
4+
import { Text, View, useWindowDimensions } from 'react-native';
55
import { Camera as ExpoCamera } from 'expo-camera';
66

77
import { utils } from '@monkvision/toolkit';
88
import log from '../../utils/log';
99
import useAvailable from '../../hooks/useAvailable';
1010
import usePermissions from '../../hooks/usePermissions';
11-
import useWindowDimensions from '../../hooks/useWindowDimensions';
1211

1312
import styles from './styles';
1413

0 commit comments

Comments
 (0)