From 27c698e6df3de6e7a996bc8c12df153edf4bcd1b Mon Sep 17 00:00:00 2001 From: Michael Compton <46600603+mjamescompton@users.noreply.github.com> Date: Wed, 8 Nov 2023 14:13:37 +0100 Subject: [PATCH 1/2] console: Fix camera switching for ios, android and desktop --- pkg/webui/components/qr/input/video/index.js | 226 ++++++++++++------- 1 file changed, 142 insertions(+), 84 deletions(-) diff --git a/pkg/webui/components/qr/input/video/index.js b/pkg/webui/components/qr/input/video/index.js index 43a4b808df..d08e614ce7 100644 --- a/pkg/webui/components/qr/input/video/index.js +++ b/pkg/webui/components/qr/input/video/index.js @@ -1,4 +1,4 @@ -// Copyright © 2022 The Things Network Foundation, The Things Industries B.V. +// Copyright © 2023 The Things Network Foundation, The Things Industries B.V. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -28,43 +28,118 @@ const m = defineMessages({ switchCamera: 'Switch camera', }) -const Video = props => { +const Camera = props => { const { onRead, setError, setCapture } = props - const videoRef = createRef() - const [stream, setStream] = useState(undefined) - const [devices, setDevices] = useState([]) const [cameras, setCameras] = useState([]) - const [videoMode, setVideoMode] = useState({}) + const [deviceId, setDeviceId] = useState(undefined) + + const [hasFrontCamera, setHasFrontCamera] = useState(false) + const [hasBackCamera, setHasBackCamera] = useState(false) + + const handleSwitchCamera = useCallback(() => { + // The majority of mobile device will have labeled front and back cameras. + const currentCamera = cameras.find(device => device.deviceId === deviceId) + + if (hasFrontCamera && hasBackCamera) { + if (currentCamera.label.toLowerCase().includes('back')) { + const frontCamera = cameras.find(device => device.label.toLowerCase().includes('front')) + setDeviceId(frontCamera.deviceId) + } + + if (currentCamera.label.toLowerCase().includes('front')) { + const backCamera = cameras.find(device => device.label.toLowerCase().includes('back')) + setDeviceId(backCamera.deviceId) + } + } + }, [cameras, deviceId, hasBackCamera, hasFrontCamera]) + + const handleCameraCycle = useCallback(() => { + // If there are more than one camera then cycle through them, only if there are no camera clearly labeled and back and front + if (cameras.length > 1) { + const currentIndex = cameras.findIndex(device => device.deviceId === deviceId) + + const nextDevice = currentIndex !== -1 ? cameras[(currentIndex + 1) % cameras.length] : null + setDeviceId(nextDevice.deviceId) + } + }, [cameras, deviceId]) const getDevices = useCallback(async () => { - if (!devices.length) { - const enumerateDevices = await navigator.mediaDevices.enumerateDevices() - setDevices(enumerateDevices) + // Depending on your device you may have access to this list on initial load + // If you do not have access to this list then you will need to request a stream to get the list + const enumerateDevices = await navigator.mediaDevices.enumerateDevices() + const videoInputs = enumerateDevices.filter(device => device.kind === 'videoinput') + + if (videoInputs.length !== cameras.length) { + setCameras(videoInputs) } - }, [devices.length]) - - const getStream = useCallback(async () => { - if (devices.length && !stream) { - const ua = navigator.userAgent.toLowerCase() - const cameras = devices.filter(device => device.kind === 'videoinput') - setCameras(cameras) - let rearCamera = cameras.find(device => device.label.toLowerCase().includes('back')) - if (!rearCamera) { - rearCamera = cameras[1] ?? cameras[0] - } - const videoMode = - cameras.length > 1 - ? ua.indexOf('safari') !== -1 && ua.indexOf('chrome') === -1 - ? { facingMode: { exact: 'environment' } } - : { deviceId: rearCamera.deviceId } - : { facingMode: 'environment' } - - setVideoMode(videoMode) + }, [cameras]) + + const setDeviceIdFromStream = useCallback(userStream => { + const videoTracks = userStream.getVideoTracks() + + if (videoTracks.length > 0) { + const { deviceId } = videoTracks[0].getSettings() + setDeviceId(deviceId) + } + }, []) + + useEffect(() => { + setHasFrontCamera(cameras.some(device => device.label.toLowerCase().includes('front'))) + setHasBackCamera(cameras.some(device => device.label.toLowerCase().includes('back'))) + }, [cameras]) + + return ( + <> + {hasFrontCamera && hasBackCamera ? ( +