Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to switch cameras in PWA app built with reactjs?

I have a code which records or uploads the videos. The app is built using create-react-app and is a PWA. I have used facingMode constraint but it still doesnt switch cameras on mobile phone (Samung fold 2) even in motorola phone it doesnt have the same affect. Here is the code:

import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';

import config from '../../config';


const MediaRecorderCapture = props => {
  const [mediaRecorder, setMediaRecorder] = useState({});
  const [isRecording, setIsRecording] = useState(false);
  const [showRear, setShowRear] = useState(false);
  const recorderRef = useRef();
  const playerRef = useRef();

  /**
   * Initialize the MediaRecorder on component mount
   */
  useEffect(() => {
    console.log("reinitializing..")
    initializeMediaRecorder();
  }, [showRear])

  /**
   * Upon MedaRecorder being set, monitor the following events
   */
  useEffect(() => {
    let chunks = [];

    mediaRecorder.ondataavailable = e => chunks.push(e.data)

    mediaRecorder.onstop = e => {
      let blob = new Blob(chunks, { type: 'video/mp4' });
      chunks = [];

      let url = (window.URL || window.webkitURL).createObjectURL(blob);
      handleVideoRecorder(blob);
      // uploadVideo(blob);
      // setPlaybackPreview(url);
      // createDownloadLink(url);
    }

    mediaRecorder.onerror = e => {
      console.log('Error recording stream');
      console.log(e.error);
    }

    console.log('MediaRecorder ready');
  }, [mediaRecorder]);



  // const createDownloadLink = url => {
  //   const link = <a href={url} download="Recording.mp4">Download</a>;
  //   const linkContainer = document.getElementById('download-link');
  //   ReactDOM.render(link, linkContainer);
  // }

  /**
   * Helper function to console out change in state of the MediaRecorder
   */
  useEffect(() => {
    console.log(`MediaRecorder state: ${mediaRecorder.state}`)
  }, [mediaRecorder.state])

  /**
   * Start recording the stream
   */
  const start = async () => {
    if (mediaRecorder.state === 'recording') return;
    mediaRecorder.start();
    setIsRecording(true);
  }

  /**
   * Stop recording the stream
   */
  const stop = async () => {
    if (mediaRecorder.state === 'inactive') return;
    mediaRecorder.stop();
    setIsRecording(false);
    await initializeMediaRecorder();
  }

  /**
   * Set the playback player's source to the url of the newly recorderd stream
   * @param {string} url 
   */
  const setPlaybackPreview = url => {
    if (!playerRef.current) return;
    console.log(`Playback URL: ${url}`);
    playerRef.current.src = url;
  }

  /**
   * Get a media device stream (webcam)
   */
  const getStream = () => {
    return new Promise(async (resolve, reject) => {
      try {
        const stream = await navigator.mediaDevices.getUserMedia({
          audio: true,
          video: {
            facingMode: showRear ? 'environment' : 'user'
          }
        });
        console.log('Stream fetched and rear camera? ', showRear);
        resolve(stream);
      }
      catch (err) {
        console.log('Error in fetching stream')
        reject(err);
      }
    })
  }

  /**
   * Set the live stream retrieved from the media device
   * to the designated player to preview
   * @param {object} stream 
   */
  const setRecordingStreamPreview = stream => {
    if (!recorderRef.current) return;
    recorderRef.current.srcObject = stream;
    console.log("recordref ", recorderRef.current.srcObject)

  }

  /**
   * Create MediaRecorder object from a given stream
   * @param {object} stream 
   */
  const createMediaRecorder = stream => {
    return new Promise((resolve, reject) => {
      try {
        const mediaRecorder = new MediaRecorder(stream);
        console.log('New MediaRecorder created');
        resolve(mediaRecorder);
      }
      catch (err) {
        console.log('Error in creating new MediaRecorder');
        reject(err);
      }
    })
  }

  /**
   * Initialize MediaRecorder
   */
  const initializeMediaRecorder = async () => {
    return new Promise(async (resolve, reject) => {
      try {
        const stream = await getStream();
        console.log(stream);
        setRecordingStreamPreview(stream);
        const mediaRecorder = await createMediaRecorder(stream);
        setMediaRecorder(mediaRecorder);
        resolve(mediaRecorder);
      }
      catch (err) {
        console.log('Error in initializing MediaRecorder of fetching media devices stream')
        reject(err);
      }
    })
  }

  const handleFileChange = evt => {
    props.handleFileChange(evt);
  }

  const handleVideoRecorder = blob => {
    props.handleVideoRecorder(blob);
  }

  const handleRearCamera = _ => {
    setShowRear(!showRear);
  }

  return (
    <>
      <video
        className="container is-widescreen"
        ref={recorderRef}
        autoPlay
        playsInline
        muted
      />
      <div className="level is-mobile">
        <div className="level-item has-text-centered">
          <div className="player-uttons">
            <button className="button is-rounded is-danger" onClick={isRecording ? stop : start}>
              <span className="icon">
                {isRecording ?
                  <i className="fa fa-stop"></i>
                  :
                  <i className="fa fa-video-camera"></i>
                }
              </span>
            </button>
            <button className="button is-rounded is-warning ml-2" onClick={handleRearCamera}>
              <span className="icon">
                <i className="fa fa-refresh"></i>
                {/* {isRecording ?
                  <i className="fa fa-stop"></i>
                  :
                  <i className="fa fa-video-camera"></i>
                } */}
              </span>
            </button>
          </div>
        </div>
      </div>
      <div className="level is-mobile">
        <div className="level-item has-text-centered">
          <div className="file is-primary">
            <label className="file-label">
              <input onChange={handleFileChange} className="file-input" type="file" name="video" accept="video/mp4,video/x-m4v,video/*" />
              <span className="file-cta">
                <span className="file-icon">
                  <i className="fa fa-cloud-upload"></i>
                </span>
                <span className="file-label">
                  Upload Video
                </span>
              </span>
            </label>
          </div>
        </div>
      </div>
    </>
  )
}

export default MediaRecorderCapture;

I am initializing the media once the flag is set as well. Any idea why this is not working please? Guys, I am really stuck with this. Thanks

like image 506
Maverick Avatar asked Jun 19 '21 14:06

Maverick


People also ask

Can I use PWA With React?

With your PWA app running using React, you've successfully built your first PWA React application! From here, you can test many PWA features, such as installation, offline viewing, and Lighthouse audit testing. If you hit the '+' symbol on the right side of the URL ba,r you can install your PWA, just as done here.

How to access the camera of a mobile device using React PWA?

How to access the camera of a mobile device using React Progressive Web App (PWA) 1 Create a new react app with CRA#N#npx create-react-app sample-pwa#N#cd sample-pwa 2 Register the service worker#N#What is the purpose of the serviceWorker.js file? since we don’t require to know the... 3 Add options to access the mobile device camera More ...

How do I create a PWA with typescript in react?

The easiest way to get started creating a new PWA is to use one of the custom templates shipped with Create React App. To create a PWA with TypeScript support using Create React App, run the npx command below in the terminal: This builds you a React web app built with TypeScript with support for PWA out of the box.

Can I access the camera and camera roll from a PWA?

It's possible to access some, but not all, of the native device features from a PWA. One that we can access, is the camera and camera roll. We'll use two different methods to allow users to upload images to our application.

Does create react app support code splitting?

Create React App is built upon webpack. There is excellent support for code splitting in webpack, and thus, Create React App supports it by default. Let’s apply it to our app.


Video Answer


1 Answers

edit: to make the camera list enumerate on page load, you need to ask for permissions right away:


window.onload  = function(){ 
        navigator.getUserMedia({audio:true,video:true}, function(stream) {
            stream.getTracks().forEach(x=>x.stop());
            getCamAndMics();
          }, err=>console.log(err));
<rest of app>
}

I have built an app that enumerates through the cameras available, and then lets the user choose which camera to record with:

function getCamAndMics(){
     // List cameras and microphones. in the menu
       
     navigator.mediaDevices.enumerateDevices()
     .then(function(devices) {
         devices.forEach(function(device) {
             console.log(device.kind + ": " + device.label +" id = " + device.deviceId);
             var audioSelect = document.getElementById("audioPicker-select");
             var cameraSelect = document.getElementById("cameraPicker-select");
             if(device.kind=="audioinput"){
                 //add a select to the audio dropdown list
                 var option = document.createElement("option");
                 option.value = device.deviceId;
                 option.text = device.label;
                 audioSelect.appendChild(option);
             }else if(device.kind =="videoinput"){
                 //add a select to the camera dropdown list
                 var option = document.createElement("option");
                 option.value = device.deviceId;
                 option.text = device.label;
                 cameraSelect.appendChild(option);

             }
         });
     })
     .catch(function(err) {
         console.log(err.name + ": " + err.message);
     });


}

Even while the recording is underway - if the user changes the value, the camera will switch. This is not only great on mobile (front & back cameras), but also for desktops that might have a few media devices connected.

I send the cameraId in the configuration to the getUserMedia:

  var videoOptions = {
            deviceId: cameraId,
            width: { min: 100, ideal: cameraW, max: 1920 },
            height: { min: 100, ideal: cameraH, max: 1080 },
            frameRate: {ideal: cameraFR}
        };
    
        cameraMediaOptions = {
            audio: false,
            video: videoOptions
        };
        cameraStream = await navigator.mediaDevices.getUserMedia(cameraMediaOptions);

Code is on Github:https://github.com/dougsillars/recordavideo/blob/main/public/index.js

Demo is live at https://record.a.video.

With this setup, I can livestream or record and upload the video to share on demand. (The backend is https://api.video for VOD and live streaming)

like image 143
Doug Sillars Avatar answered Oct 17 '22 19:10

Doug Sillars