如何在使用 reactjs 构建的 PWA 应用中切换摄像头?

Posted

技术标签:

【中文标题】如何在使用 reactjs 构建的 PWA 应用中切换摄像头?【英文标题】:How to switch cameras in PWA app built with reactjs? 【发布时间】:2021-09-03 22:12:43 【问题描述】:

我有一个记录或上传视频的代码。该应用程序是使用 create-react-app 构建的,是一个 PWA。 我使用了 facesMode 约束,但它仍然不会在手机(Samung fold 2)上切换摄像头,即使在摩托罗拉手机中它也没有相同的影响。 代码如下:

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;

一旦设置了标志,我也会初始化媒体。 知道为什么这不起作用吗? 伙计们,我真的很坚持这一点。 谢谢

【问题讨论】:

我用你的代码创建了一个最小的复制品,它在我的像素 2 设备和摩托罗拉 moto X genymotion 模拟设备上工作,你能提供摩托罗拉手机的型号吗?也许我可以找到一种方法来模仿它 检查这是否解决了您的问题:***.com/a/61191764/8656738 【参考方案1】:

编辑:要在页面加载时枚举相机列表,您需要立即请求权限:


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>


我已经构建了一个应用程序,它可以枚举可用的摄像头,然后让用户选择使用哪个摄像头进行录制:

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);
     );



即使在录制过程中 - 如果用户更改值,相机也会切换。这不仅适用于移动设备(前置和后置摄像头),而且适用于可能连接了一些媒体设备的台式机。

我将配置中的cameraId发送给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);

代码在 Github:https://github.com/dougsillars/recordavideo/blob/main/public/index.js

演示在https://record.a.video直播。

通过此设置,我可以直播或录制视频并上传视频以按需分享。 (后台是https://api.video点播和直播)

【讨论】:

感谢 Doug 的回答..但我不知道为什么...这个演示无法在我的桌面浏览器上运行,即 chrome、brave..firefox..all 都是最新版本...也不在移动浏览器上。 点击开始录制...这是网站首先要求访问摄像头/麦克风的地方。我希望它对你有用。如果没有 - 请告诉我。 :) 在最新的 iOS 上尝试过,使用最新的 Chrome,它不要求权限,因此没有可用的选项:/ 好的..我在移动 chrome 浏览器上尝试了隐身模式...但它不会翻转..一旦您选择了它所使用的相机设备。 认为这是一个移动的东西。一旦浏览器选择了一个摄像头,另一个摄像头就会关闭。我实际上尝试将前 流式传输(因为那会很酷),但它冻结了另一个摄像头。

以上是关于如何在使用 reactjs 构建的 PWA 应用中切换摄像头?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 PWA Android 应用中隐藏 URL 栏?

如何使用 React PWA 像原生 iOS 应用一样进行导航转换?

如何通过构建现有状态来更新 reactjs 应用程序的状态?

如何在 tomcat 服务器上部署 reactJS 应用程序?

如何用Angular5创建一个PWA项目

在 Kubernetes 上的 Jenkins 管道中使用 npm 构建 ReactJs 应用程序