1/* eslint-env browser */
2import * as React from 'react';
3import * as Utils from './WebCameraUtils';
4import { FacingModeToCameraType } from './WebConstants';
5const VALID_SETTINGS_KEYS = [
6    'autoFocus',
7    'flashMode',
8    'exposureCompensation',
9    'colorTemperature',
10    'iso',
11    'brightness',
12    'contrast',
13    'saturation',
14    'sharpness',
15    'focusDistance',
16    'whiteBalance',
17    'zoom',
18];
19function useLoadedVideo(video, onLoaded) {
20    React.useEffect(() => {
21        if (video) {
22            video.addEventListener('loadedmetadata', () => {
23                // without this async block the constraints aren't properly applied to the camera,
24                // this means that if you were to turn on the torch and swap to the front camera,
25                // then swap back to the rear camera the torch setting wouldn't be applied.
26                requestAnimationFrame(() => {
27                    onLoaded();
28                });
29            });
30        }
31    }, [video]);
32}
33export function useWebCameraStream(video, preferredType, settings, { onCameraReady, onMountError, }) {
34    const isStartingCamera = React.useRef(false);
35    const activeStreams = React.useRef([]);
36    const capabilities = React.useRef({
37        autoFocus: 'continuous',
38        flashMode: 'off',
39        whiteBalance: 'continuous',
40        zoom: 1,
41    });
42    const [stream, setStream] = React.useState(null);
43    const mediaTrackSettings = React.useMemo(() => {
44        return stream ? stream.getTracks()[0].getSettings() : null;
45    }, [stream]);
46    // The actual camera type - this can be different from the incoming camera type.
47    const type = React.useMemo(() => {
48        if (!mediaTrackSettings) {
49            return null;
50        }
51        // On desktop no value will be returned, in this case we should assume the cameraType is 'front'
52        const { facingMode = 'user' } = mediaTrackSettings;
53        return FacingModeToCameraType[facingMode];
54    }, [mediaTrackSettings]);
55    const getStreamDeviceAsync = React.useCallback(async () => {
56        try {
57            return await Utils.getPreferredStreamDevice(preferredType);
58        }
59        catch (nativeEvent) {
60            if (__DEV__) {
61                console.warn(`Error requesting UserMedia for type "${preferredType}":`, nativeEvent);
62            }
63            if (onMountError) {
64                onMountError({ nativeEvent });
65            }
66            return null;
67        }
68    }, [preferredType, onMountError]);
69    const resumeAsync = React.useCallback(async () => {
70        const nextStream = await getStreamDeviceAsync();
71        if (Utils.compareStreams(nextStream, stream)) {
72            // Do nothing if the streams are the same.
73            // This happens when the device only supports one camera (i.e. desktop) and the mode was toggled between front/back while already active.
74            // Without this check there is a screen flash while the video switches.
75            return false;
76        }
77        // Save a history of all active streams (usually 2+) so we can close them later.
78        // Keeping them open makes swapping camera types much faster.
79        if (!activeStreams.current.some((value) => value.id === nextStream?.id)) {
80            activeStreams.current.push(nextStream);
81        }
82        // Set the new stream -> update the video, settings, and actual camera type.
83        setStream(nextStream);
84        if (onCameraReady) {
85            onCameraReady();
86        }
87        return false;
88    }, [getStreamDeviceAsync, setStream, onCameraReady, stream, activeStreams.current]);
89    React.useEffect(() => {
90        // Restart the camera and guard concurrent actions.
91        if (isStartingCamera.current) {
92            return;
93        }
94        isStartingCamera.current = true;
95        resumeAsync()
96            .then((isStarting) => {
97            isStartingCamera.current = isStarting;
98        })
99            .catch(() => {
100            // ensure the camera can be started again.
101            isStartingCamera.current = false;
102        });
103    }, [preferredType]);
104    // Update the native camera with any custom capabilities.
105    React.useEffect(() => {
106        const changes = {};
107        for (const key of Object.keys(settings)) {
108            if (!VALID_SETTINGS_KEYS.includes(key)) {
109                continue;
110            }
111            const nextValue = settings[key];
112            if (nextValue !== capabilities.current[key]) {
113                changes[key] = nextValue;
114            }
115        }
116        // Only update the native camera if changes were found
117        const hasChanges = !!Object.keys(changes).length;
118        const nextWebCameraSettings = { ...capabilities.current, ...changes };
119        if (hasChanges) {
120            Utils.syncTrackCapabilities(preferredType, stream, changes);
121        }
122        capabilities.current = nextWebCameraSettings;
123    }, [
124        settings.autoFocus,
125        settings.flashMode,
126        settings.exposureCompensation,
127        settings.colorTemperature,
128        settings.iso,
129        settings.brightness,
130        settings.contrast,
131        settings.saturation,
132        settings.sharpness,
133        settings.focusDistance,
134        settings.whiteBalance,
135        settings.zoom,
136    ]);
137    React.useEffect(() => {
138        // set or unset the video source.
139        if (!video.current) {
140            return;
141        }
142        Utils.setVideoSource(video.current, stream);
143    }, [video.current, stream]);
144    React.useEffect(() => {
145        return () => {
146            // Clean up on dismount, this is important for making sure the camera light goes off when the component is removed.
147            for (const stream of activeStreams.current) {
148                // Close all open streams.
149                Utils.stopMediaStream(stream);
150            }
151            if (video.current) {
152                // Invalidate the video source.
153                Utils.setVideoSource(video.current, stream);
154            }
155        };
156    }, []);
157    // Update props when the video loads.
158    useLoadedVideo(video.current, () => {
159        Utils.syncTrackCapabilities(preferredType, stream, capabilities.current);
160    });
161    return {
162        type,
163        mediaTrackSettings,
164    };
165}
166//# sourceMappingURL=useWebCameraStream.js.map