使用JavaScript和React访问网络摄像头

我不喜欢垃圾(恶意软件)的网络摄像头软件。

2020年秋季时,我购买了一个新Microsoft LifeCam,当我得知必须下载他们的官方软件才能使用时,立即就产生了反感。我也尝试了其他软件,但事实让人非常无语,我对自己说:“用JavaScript连接到我的网络摄像头有多难?”

实际上,这的确很容易。你可以创建自己的浏览器应用程序来拍照、录视频甚至创建滤镜。这完全取决于你的想象力和创造力。

在本文中,我们将讨论这一点。

第一步

首先,我们需要创建HTML部分。我想拍照,于是添加了一个按钮。我们将在一分钟内为所有这些样式进行设置和修改。

import React from "react";


const App = () => {
  return (
      <div>
          <button>Take a photo</button>
          <video/>
      </div>
  );
};

export default App;

分解上面的代码; button 是用来给自己拍照, video 用于我们的网络摄像头流,而canvas是用于从视频中拍摄快照并将其显示出来。

下一步是访问我们的网络摄像头。为此,我们需要使用Hooks的useRef功能。具体步骤如下,我们需要添加:

ref={videoRef}

到所需的HTML元素,然后从我们的组件顶部使用

const videoRef = useRef(null);

useRef是使用React访问DOM元素的方法。它的作用是创建一个普通的JavaScript对象,该对象包含一些属性(如current),这对于内部保留任何可变值时很有用。与纯对象的不同之处在于,它在每个渲染上都为你提供相同的ref对象。

接下来要做的就是创建一种获取视频并将其流式传输的方法,

const App = () => {
  const videoRef = useRef(null);

  const getVideo = () => {
    navigator.mediaDevices
      .getUserMedia({ video: { width: 300 } })
      .then(stream => {
        let video = videoRef.current;
        video.srcObject = stream;
        video.play();
      })
      .catch(err => {
        console.error("error:", err);
      });
  };

  return (
    <div>
        <button>Take a photo</button>
        <video ref={videoRef}/>
        <canvas />
      </div>
    </div>
  );
};

export default App;

兴趣点是关键字: navigator

直接从MDN读取:

Navigator 界面表示用户代理的状态和身份。它允许脚本查询并自己注册以进行一些活动。

Navigator 对象可以使用只读 window.navigator 属性

这是不言自明的。但是最好自己看看,只需要打开开发工具并在控制台上键入navigator即可。

仔细观察,里面 mediaDevices 属性。如果对其进行扩展,则可以看到已定义的原型即 getUserMedia, 该原型返回一个Promise。

我们将使用它来访问我们的网络摄像头。再次直接从MDN复制,

1_JUNY3uZjwMhGanNSCJszxQ

navigator.mediaDevices():

返回到 MediaDevices 对象的引用,然后该对象可用于获取有关媒体设备( MediaDevices.enumerateDevices() )的信息,并使用 MediaDevices.getUserMedia() 来请求对媒体的访问。

在兑现此承诺的部分,我们可以将网络摄像头中的视频分配给我们使用useRef访问的视频标签。

let video = videoRef.current;        
video.srcObject = stream;        
video.play();

这是我们访问WebCam所需的全部操作。能获得多少乐趣完全取决于你自己!创建滤镜、添加效果、拍照和处理图像数据……有着无限可能!

现在,你的代码应如下所示:

import React, { useEffect, useRef } from "react";

const App = () => {
  const videoRef = useRef(null);

  useEffect(() => {
    getVideo();
  }, [videoRef]);

  const getVideo = () => {
    navigator.mediaDevices
      .getUserMedia({ video: { width: 300 } })
      .then(stream => {
        let video = videoRef.current;
        video.srcObject = stream;
        video.play();
      })
      .catch(err => {
        console.error("error:", err);
      });
  };

  return (
    <div>
      <div>
        <button>Take a photo</button>
        <video ref={videoRef} />
      </div>
    </div>
  );
};

export default App;

第二步

从我们刚刚访问的网络摄像头拍摄快照也很容易。我们所需要做的只是创建一个画布并将数据写入该画布中。这里最重要的是从WebCam获取快照并将其作为2D上下文进行渲染。

然后,以每200毫秒将该2D上下文写入到Canvas中。

  const paintToCanvas = () => {
    let video = videoRef.current;
    let photo = photoRef.current;
    let ctx = photo.getContext("2d");

    const width = 320;
    const height = 240;
    photo.width = width;
    photo.height = height;

    return setInterval(() => {
      ctx.drawImage(video, 0, 0, width, height);
    }, 200);
  };

要停止绘制,我们需要将视频源设置为 null, 并在流中的轨道上使用stop()。这就足够了:

const stop = (e) => {
  const stream = video.srcObject;
  const tracks = stream.getTracks();

  for (let i = 0; i < tracks.length; i++) {
    let track = tracks[i];
    track.stop();
  }

  video.srcObject = null;
}

此时,你应该设置网络摄像头在一侧播放,并且画布会随时间间隔进行更新。

react-webcam-step2 - StackBlitz

Starter project for React apps that exports to the create-react-app CLI.

stackblitz.com

.toDataURL()

要将摄像头快照中的照片拍摄到画布上,我们需要使用 toDataURL

用这个方法返回数据URI,其中包含由 type 参数指定的格式表示的图像。默认情况下,它是PNG。Chrome也支持该 image/webp 类型。

canvas.toDataURL('image/jpeg', 1.0);

逗号后的部分用于指定照片的质量(从0到1)。

之后,我们仅需要创建一个图像元素并添加我们拍摄的图片作为源。我还添加了一个属性供我们下载。

link.setAttribute(“event-name”, “name-for-the-image”);
  const takePhoto = () => {
    let photo = photoRef.current;
    let strip = stripRef.current;

    const data = photo.toDataURL("image/jpeg");

    const link = document.createElement("a");
    link.href = data;
    link.setAttribute("download", "myWebcam");
    link.innerHTML = `<img src='${data}' alt='thumbnail'/>`;
    strip.insertBefore(link, strip.firstChild);
  };
<button onClick={() => takePhoto()}>Take a photo</button>

在进行下一步之前,以下是代码的外观:

react-webcam-step3 - StackBlitz

导出到create-react-app CLI的React应用的Starter项目.
stackblitz.com

有趣的部分

针对本文,我将添加一项功能来拍照,并使用网络摄像头中的1个像素给我创建的小场景添加效果。该场景是由Cassie Evans启发的

https://codepen.io/cassie-codes/embed/ZjErdL?default-tab=&theme-id=

我们将从快照中获取一个像素并操纵我创建的场景。天空将随着该像素发生相应的变化。

%E6%9C%AA%E5%91%BD%E5%90%8D_%E5%89%AF%E6%9C%AC

场景

为了创建场景,我检查了freepik并找到了一张有山脉的图像 可以在 这里 找到

现在,在 paintToCanvas 函数内部,我们将添加更多行。但是在此之前,我们会将场景添加到我们的HTML部分中。

<div ref={colorRef} className="scene">
 <img
  className="mountains"
    src="https://i.ibb.co/RjYk1Ps/2817290-eps-1.png"
 />
</div>

你看到 ref 用法了吗?同样,借助 useRef ,我们将在稍后为该元素添加一些样式。

要添加这些样式,我将从快照及其颜色值中获取第一个像素。

  const paintToCanvas = () => {
    let video = videoRef.current;
    let photo = photoRef.current;
    let ctx = photo.getContext("2d");

    const width = 320;
    const height = 240;
    photo.width = width;
    photo.height = height;

    return setInterval(() => {
      let color = colorRef.current;

      ctx.drawImage(video, 0, 0, width, height);
      let pixels = ctx.getImageData(0, 0, width, height);

      color.style.backgroundColor = `rgb(${pixels.data[0]},${pixels.data[1]},${
        pixels.data[2]
      })`;
      color.style.borderColor = `rgb(${pixels.data[0]},${pixels.data[1]},${
        pixels.data[2]
      })`;
    }, 200);
  };

这是最终版本:

https://itnext.io/accessing-the-webcam-with-javascript-and-react-33cbe92f49cb

感谢阅读!特别感谢Semih Onay的审核以及CassieEvans让我使用她的Codepen。快去关注她!

原文作者 Gökhan İpek
原文链接https://itnext.io/accessing-the-webcam-with-javascript-and-react-33cbe92f49cb

推荐阅读
作者信息
AgoraTechnicalTeam
TA 暂未填写个人简介
文章
131
相关专栏
本专栏仅用于分享音视频相关的技术文章,与其他开发者和 Agora 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。