/** * Copyright 2024 Google LLC * * 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 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { useState, useEffect } from "react"; import { UseMediaStreamResult } from "./use-media-stream-mux"; export function useWebcam(): UseMediaStreamResult { const [stream, setStream] = useState(null); const [isStreaming, setIsStreaming] = useState(false); const [availableCameras, setAvailableCameras] = useState([]); const [currentCameraIndex, setCurrentCameraIndex] = useState(-1); // Get list of available cameras on mount useEffect(() => { async function getCameras() { try { // First request permission to ensure we can enumerate video devices await navigator.mediaDevices.getUserMedia({ video: true }) .then(stream => { // Stop the stream immediately, we just needed permission stream.getTracks().forEach(track => track.stop()); }); const devices = await navigator.mediaDevices.enumerateDevices(); const videoDevices = devices.filter(device => device.kind === 'videoinput'); setAvailableCameras(videoDevices); console.log('Available cameras:', videoDevices); } catch (err) { console.error('Error getting cameras:', err); } } getCameras(); }, []); useEffect(() => { const handleStreamEnded = () => { setIsStreaming(false); setStream(null); }; if (stream) { stream .getTracks() .forEach((track) => track.addEventListener("ended", handleStreamEnded)); return () => { stream .getTracks() .forEach((track) => track.removeEventListener("ended", handleStreamEnded), ); }; } }, [stream]); const start = async () => { // If we're already streaming, cycle to next camera if (isStreaming) { const nextIndex = (currentCameraIndex + 1) % (availableCameras.length); setCurrentCameraIndex(nextIndex); // Stop current stream if (stream) { stream.getTracks().forEach((track) => track.stop()); } // If we've cycled through all cameras, stop streaming if (nextIndex === 0) { setStream(null); setIsStreaming(false); return null; } const deviceId = availableCameras[nextIndex].deviceId; const mediaStream = await navigator.mediaDevices.getUserMedia({ video: { deviceId: { exact: deviceId } } }); setStream(mediaStream); setIsStreaming(true); return mediaStream; } else { // Start with first camera setCurrentCameraIndex(0); const deviceId = availableCameras[0]?.deviceId; const mediaStream = await navigator.mediaDevices.getUserMedia({ video: deviceId ? { deviceId: { exact: deviceId } } : true }); setStream(mediaStream); setIsStreaming(true); return mediaStream; } }; const stop = () => { if (stream) { stream.getTracks().forEach((track) => track.stop()); setStream(null); setIsStreaming(false); setCurrentCameraIndex(-1); } }; const result: UseMediaStreamResult = { type: "webcam", start, stop, isStreaming, stream, }; return result; }