UNPKG

use-simple-camera

Version:

A simple and easy to use react hook, it can help you capture videos, images and get media devices streams in an easy to use way.

302 lines (292 loc) 13.4 kB
'use strict'; var react = require('react'); /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; const useSimpleCamera = () => { const [permissionAcquired, setPermissionAcquired] = react.useState(false); const [isCameraActive, setIsCameraActive] = react.useState(false); const [mediaStream, setMediaStream] = react.useState(null); const [videoDevicesIDs, setVideoDevicesID] = react.useState([]); const [audioDevicesIDs, setAudioDevicesID] = react.useState([]); const [videoRecodingInProgress, setVideoRecodingInProgress] = react.useState(false); const [activeMediaRecorder, setActiveMediaRecorder] = react.useState(null); const [videos, setVideos] = react.useState([]); const [videoProcessingStatus, setVideoProcessingStatus] = react.useState([]); react.useEffect(() => { const checkPermissions = () => __awaiter(void 0, void 0, void 0, function* () { try { if (!navigator.permissions) { console.warn("Permissions API not supported. Falling back to media device check."); const stream = yield navigator.mediaDevices.getUserMedia({ video: true, audio: true, }); stream.getTracks().forEach((track) => track.stop()); setPermissionAcquired(true); return; } const permissions = ["camera", "microphone"]; const results = yield Promise.all(permissions.map((name) => __awaiter(void 0, void 0, void 0, function* () { const status = yield navigator.permissions.query({ name: name, }); return status.state === "granted"; }))); setPermissionAcquired(results.every(Boolean)); } catch (_a) { setPermissionAcquired(false); } }); checkPermissions(); }, []); react.useEffect(() => { const updateVideoProcessingStatus = () => { setVideoProcessingStatus(videos.map(item => ({ id: item.id, status: item.processed ? "ready" : "processing" }))); }; updateVideoProcessingStatus(); }, [videos]); const acquirePermissions = () => __awaiter(void 0, void 0, void 0, function* () { try { if (permissionAcquired) return; const stream = yield navigator.mediaDevices.getUserMedia({ video: true, audio: true, }); stream.getTracks().forEach((track) => track.stop()); setPermissionAcquired(true); } catch (error) { console.error("Error acquiring permissions:", error); setPermissionAcquired(false); throw new Error("Failed to acquire permissions."); } }); const startCamera = (config) => __awaiter(void 0, void 0, void 0, function* () { try { if (!acquirePermissions) throw new Error("Failed to acquire permission for media device usage."); const mediaStreamConstraints = config ? config : { video: true, audio: true, }; const mediaStream = yield navigator.mediaDevices.getUserMedia(mediaStreamConstraints); setVideoDevicesID(mediaStream.getVideoTracks()); setAudioDevicesID(mediaStream.getAudioTracks()); setMediaStream(mediaStream); setIsCameraActive(true); } catch (error) { if (error.name == "NotAllowedError") { setPermissionAcquired(false); throw new Error("Failed to acquire permission for media device usage."); } throw error; } }); const stopCamera = () => __awaiter(void 0, void 0, void 0, function* () { mediaStream === null || mediaStream === void 0 ? void 0 : mediaStream.getTracks().forEach((item) => item.stop()); setAudioDevicesID([]); setVideoDevicesID([]); setIsCameraActive(false); }); const captureImage = (videoTrackID) => __awaiter(void 0, void 0, void 0, function* () { try { if (!permissionAcquired) { setPermissionAcquired(false); throw new Error("Failed to acquire permission for media device usage."); } if (!mediaStream) throw new Error("Camera not active, start camera."); const videoStreamTrack = videoTrackID ? mediaStream.getTracks().find((item) => item.id === videoTrackID) : mediaStream.getVideoTracks()[0]; if (!videoStreamTrack) throw new Error("No video track available."); if (window.ImageCapture) { const imageCapture = new window.ImageCapture(videoStreamTrack); const blob = yield imageCapture.takePhoto(); const imageURL = URL.createObjectURL(blob); return imageURL; } const videoElement = document.createElement("video"); videoElement.srcObject = new MediaStream([videoStreamTrack]); yield new Promise((resolve, reject) => { videoElement.onloadedmetadata = () => { videoElement.play().then(resolve).catch(reject); }; }); const canvas = document.createElement("canvas"); canvas.width = videoElement.videoWidth; canvas.height = videoElement.videoHeight; const context = canvas.getContext("2d"); if (!context) throw new Error("Failed to create canvas context."); context.drawImage(videoElement, 0, 0, canvas.width, canvas.height); const imageURL = canvas.toDataURL("image/png"); videoElement.pause(); if (videoElement.srcObject) videoElement.srcObject = null; canvas.remove(); videoElement.remove(); return imageURL; } catch (error) { throw new Error(`Failed to capture image. Original error: ${error.message || error}`); } }); const recordVideo = (id, config) => __awaiter(void 0, void 0, void 0, function* () { if (videoRecodingInProgress) throw new Error("Video recording is already in progress."); if (!mediaStream) throw new Error("Media stream is not available."); setVideos((previousVideos) => [ ...previousVideos.filter((item) => item.id != id), { id, data: new Blob(), processed: false, }, ]); let customMediaStream = mediaStream; if (config) { const customVideoStreamTrack = mediaStream.getTrackById(config.videoStreamID); const customAudioStreamTrack = mediaStream.getTrackById(config.audioStreamID); if (customVideoStreamTrack && customAudioStreamTrack) { customMediaStream = new MediaStream([ customVideoStreamTrack, customAudioStreamTrack, ]); } } const mediaRecorder = new MediaRecorder(customMediaStream, { mimeType: (config === null || config === void 0 ? void 0 : config.customMimeType) ? config.customMimeType : "video/webm", }); setActiveMediaRecorder(mediaRecorder); const videoBlobsRecorded = []; mediaRecorder.addEventListener("dataavailable", (e) => videoBlobsRecorded.push(e.data)); mediaRecorder.addEventListener("stop", (_e) => { setVideos((previousVideos) => [ ...previousVideos.filter((item) => item.id != id), { id, data: new Blob(videoBlobsRecorded, { type: (config === null || config === void 0 ? void 0 : config.customMimeType) ? config.customMimeType : "video/webm", }), processed: true, }, ]); setActiveMediaRecorder(null); setVideoRecodingInProgress(false); }); setVideoRecodingInProgress(true); mediaRecorder.start(1000); }); const stopVideoRecording = () => __awaiter(void 0, void 0, void 0, function* () { if (!videoRecodingInProgress || !activeMediaRecorder) throw new Error("Video recording is not in progress."); activeMediaRecorder.stop(); }); const getRecordedVideoURL = (videoID) => { const existingVideo = videos.find((item) => item.id === videoID); if (!existingVideo) throw new Error(`No video with ${videoID} exists.`); if (!existingVideo.processed) throw new Error(`Video ${videoID} still processing.`); return URL.createObjectURL(existingVideo.data); }; const getRecordedVideoBlob = (videoID) => { const existingVideo = videos.find((item) => item.id === videoID); if (!existingVideo) throw new Error(`No video with ${videoID} exists.`); if (!existingVideo.processed) throw new Error(`Video ${videoID} still processing.`); return existingVideo.data; }; const downloadRecordedVideo = (videoID, filename) => { const existingVideo = videos.find((item) => item.id === videoID); if (!existingVideo) throw new Error(`No video with ${videoID} exists.`); if (!existingVideo.processed) throw new Error(`Video ${videoID} still processing.`); const tempDownload = document.createElement("a"); const blobURL = URL.createObjectURL(existingVideo.data); tempDownload.href = blobURL; tempDownload.download = filename || `${videoID}.webm`; tempDownload.click(); URL.revokeObjectURL(blobURL); }; const getMediaStream = (config) => __awaiter(void 0, void 0, void 0, function* () { if (!mediaStream) throw new Error("Failed to initialize media stream."); const tracks = []; if (config.videoID !== "none") { console.log(config); const videoTrack = config.videoID === "default" ? mediaStream.getVideoTracks()[0] : mediaStream .getTracks() .find((track) => track.id === config.videoID); if (!videoTrack) throw new Error("Invalid video source ID."); tracks.push(videoTrack); } if (config.audioID !== "none") { const audioTrack = config.audioID === "default" ? mediaStream.getAudioTracks()[0] : mediaStream .getTracks() .find((track) => track.id === config.audioID); if (!audioTrack) throw new Error("Invalid audio source ID."); tracks.push(audioTrack); } return new MediaStream(tracks); }); return { permissionAcquired, isCameraActive, videoDevicesIDs, audioDevicesIDs, videoProcessingStatus, videoRecodingInProgress, acquirePermissions, startCamera, stopCamera, getMediaStream, captureImage, recordVideo, stopVideoRecording, getRecordedVideoURL, downloadRecordedVideo, getRecordedVideoBlob, }; }; exports.useSimpleCamera = useSimpleCamera;