Spaces:
Running
Running
| /** | |
| * 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<MediaStream | null>(null); | |
| const [isStreaming, setIsStreaming] = useState(false); | |
| const [availableCameras, setAvailableCameras] = useState<MediaDeviceInfo[]>([]); | |
| 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; | |
| } | |