Taking still photos with WebRTC

{{WebRTCSidebar}}

Šajā rakstā parādīsies, kā izmanto WebRTC, kā arī piekļūtu kameru datoriem vai WebRTC atbalsta un nefotografētu ar. Izmēģiniet šo paraugu, pēc tam izlasiet, lai uzzinātu, kā tas darbojas.

Uz WebRTC balstīta attēla uztveršanas lietotne - kreisajā pusē un bez tīmekļa kameras uzņemšanas video straumē un poga

Github ir piemērots, lai labi pārdomātu .

HTML marķējums

Mūsu HTML saskarnei un divām galvenajām darbības sadaļām: straumes un uztveršanas panelis un prezentācijas panelis. Lai atvieglotu stila veidošanos un vadību, katrs no tiem neradās līdzīgs {{HTMLElement ("div")}}}.

Pirmajā kreisajā pusē esošajā panelī un divos komponentos: {{HTMLElement ("video")}} elementi, kas saņems straumi no WebRTC, un {{HTMLElement ("poga")}}, ko lietotājs noklikšķina, lai uzņemtu video.

  <div class = "camera"> 
    <video id = "video"> Video straume nav pieejams. </video> 
    <button id = "startbutton"> Fotografēt </button> 
  </div>

Tas ir vienkārši, ja mēs redzam, kā tā sasaistās, kad mēs nokļūstam JavaScript kodā.

Tālāk mums ir {{HTMLElement ("audekls")}}} elementi, kurus var izmantot, lai saglabātu, kaut arī tos varētu mainīt pēc tam, kad pārveidot par izvades attēliem. Šis audekls tiek paslēpts, veidojot audekls ar {{cssxref ("display")}} :none, ļaujot izvairīties no pārblīvēšanas ekrāna - lietotājam šī zvaigznīte nav jāredz.

Mums ir arī {{HTMLElement ("img")}} elementi, kas satur ievilksim attēlu - tas ir pēdējais lietotājs parādījies uz displejiem.

  <audekls id = "audekls">
  </canvas>
  <div class = "output">
    <img id = "photo" alt = "Šajā lodziņā parādīsies ekrāna tveršana.">
  </div>

Tas ir viss attiecīgais HTML. Pārējais ir tikai dažas lapas izkārtojuma pūkas un mazliet teksta, kas piedāvā saiti uz šo lapu.

JavaScript kods

Tagad apskatīsim JavaScript kodu . Mēs to sadalīsim pāris koduma lieluma gabalos, lai būtu vieglāk izskaidrot.

Inicializēšana

Sākumā iesaiņojam visu skriptu anonīmā funkcijā, lai izvairītos no globāliem mainīgajiem, pēc tam iestatot dažādus mainīgos, kurus mēs izmantosim.

(funkcija () {
  var platums = 320; // Fotoattēla platumu pielīdzināsim šim
  var height = 0;     // This will be computed based on the input stream

  var streaming = false;

  var video = null;
  var canvas = null;
  var photo = null;
  var startbutton = null;

Those variables are:

width
Whatever size the incoming video is, we're going to scale the resulting image to be 320 pixels wide.
height
The output height of the image will be computed given the width and the aspect ratio of the stream.
streaming
Indicates whether or not there is currently an active stream of video running.
video
This will be a reference to the {{HTMLElement("video")}} element after the page is done loading.
canvas
This will be a reference to the {{HTMLElement("canvas")}} element after the page is done loading.
photo
This will be a reference to the {{HTMLElement("img")}} element after the page is done loading.
startbutton
This will be a reference to the {{HTMLElement("button")}} element that's used to trigger capture. We'll get that after the page is done loading.

The startup() function

The startup() function is run when the page has finished loading, courtesy of {{domxref("window.addEventListener()")}}. This function's job is to request access to the user's webcam, initialize the output {{HTMLElement("img")}} to a default state, and to establish the event listeners needed to receive each frame of video from the camera and react when the button is clicked to capture an image.

Getting element references

First, we grab references to the major elements we need to be able to access.

  function startup() {
    video = document.getElementById('video');
    canvas = document.getElementById('canvas');
    photo = document.getElementById('photo');
    startbutton = document.getElementById('startbutton');

Get the media stream

The next task is to get the media stream:

    navigator.mediaDevices.getUserMedia({ video: true, audio: false })
    .then(function(stream) {
        video.srcObject = stream;
        video.play();
    })
    .catch(function(err) {
        console.log("An error occurred: " + err);
    });

Here, we're calling {{domxref("MediaDevices.getUserMedia()")}} and requesting a video stream (without audio). It returns a promise which we attach success and failure callbacks to.

The success callback receives a stream object as input. It is the {{HTMLElement("video")}} element's source to our new stream.

Once the stream is linked to the <video> element, we start it playing by calling HTMLMediaElement.play().

The error callback is called if opening the stream doesn't work. This will happen for example if there's no compatible camera connected, or the user denied access.

Listen for the video to start playing

After calling HTMLMediaElement.play() on the {{HTMLElement("video")}}, there's a (hopefully brief) period of time that elapses before the stream of video begins to flow. To avoid blocking until that happens, we add an event listener to video for the {{event("canplay")}} event, which is delivered when the video playback actually begins. At that point, all the properties in the video object have been configured based on the stream's format.

    video.addEventListener('canplay', function(ev){
      if (!streaming) {
        height = video.videoHeight / (video.videoWidth/width);
      
        video.setAttribute('width', width);
        video.setAttribute('height', height);
        canvas.setAttribute('width', width);
        canvas.setAttribute('height', height);
        streaming = true;
      }
    }, false);

This callback does nothing unless it's the first time it's been called; this is tested by looking at the value of our streaming variable, which is false the first time this method is run.

If this is indeed the first run, we set the video's height based on the size difference between the video's actual size, video.videoWidth, and the width at which we're going to render it, width.

Finally, the width and height of both the video and the canvas are set to match each other by calling {{domxref("Element.setAttribute()")}} on each of the two properties on each element, and setting widths and heights as appropriate. Finally, we set the streaming variable to true to prevent us from inadvertently running this setup code again.

Handle clicks on the button

To capture a still photo each time the user clicks the startbutton, we need to add an event listener to the button, to be called when the {{event("click")}} event is issued:

    startbutton.addEventListener('click', function(ev){
      takepicture();
      ev.preventDefault();
    }, false);

This method is simple enough: it just calls our takepicture() function, defined below in the section {{anch("Capturing a frame from the stream")}}, then calls {{domxref("Event.preventDefault()")}} on the received event to prevent the click from being handled more than once.

Wrapping up the startup() method

There are only two more lines of code in the startup() method:

    clearphoto();
  }

This is where we call the clearphoto() method we'll describe below in the section {{anch("Clearing the photo box")}}.

Clearing the photo box

Clearing the photo box involves creating an image, then converting it into a format usable by the {{HTMLElement("img")}} element that displays the most recently captured frame. That code looks like this:

  function clearphoto() {
    var context = canvas.getContext('2d');
    context.fillStyle = "#AAA";
    context.fillRect(0, 0, canvas.width, canvas.height);

    var data = canvas.toDataURL('image/png');
    photo.setAttribute('src', data);
  }

We start by getting a reference to the hidden {{HTMLElement("canvas")}} element that we use for offscreen rendering.  Next we set the fillStyle to #AAA (a fairly light grey), and fill the entire canvas with that color by calling {{domxref("CanvasRenderingContext2D.fillRect()","fillRect()")}}.

Last in this function, we convert the canvas into a PNG image and call {{domxref("Element.setAttribute", "photo.setAttribute()")}} to make our captured still box display the image.

Capturing a frame from the stream

There's one last function to define, and it's the point to the entire exercise: the takepicture() function, whose job it is to capture the currently displayed video frame, convert it into a PNG file, and display it in the captured frame box. The code looks like this:

  function takepicture() {
    var context = canvas.getContext('2d');
    if (width && height) {
      canvas.width = width;
      canvas.height = height;
      context.drawImage(video, 0, 0, width, height);
    
      var data = canvas.toDataURL('image/png');
      photo.setAttribute('src', data);
    } else {
      clearphoto();
    }
  }

As is the case any time we need to work with the contents of a canvas, we start by getting the {{domxref("CanvasRenderingContext2D","2D drawing context")}} for the hidden canvas.

Then, if the width and height are both non-zero (meaning that there's at least potentially valid image data), we set the width and height of the canvas to match that of the captured frame, then call {{domxref("CanvasRenderingContext2D.drawImage()", "drawImage()")}} to draw the current frame of the video into the context, filling the entire canvas with the frame image.

Note: This takes advantage of the fact that the {{domxref("HTMLVideoElement")}} interface looks like an {{domxref("HTMLImageElement")}} to any API that accepts an HTMLImageElement as a parameter, with the video's current frame presented as the image's contents.

Once the canvas contains the captured image, we convert it to PNG format by calling {{domxref("HTMLCanvasElement.toDataURL()")}} on it; finally, we call {{domxref("Element.setAttribute", "photo.setAttribute()")}} to make our captured still box display the image.

If there isn't a valid image available (that is, the width and height are both 0), we clear the contents of the captured frame box by calling clearphoto().

Fun with filters

Since we're capturing images from the user's webcam by grabbing frames from a {{HTMLElement("video")}} element, we can very easily apply filters and fun effects to the video. As it turns out, any CSS filters you apply to the element using the {{cssxref("filter")}} property affect the captured photo. These filters can range from the simple (making the image black and white)  to the extreme (gaussian blurs and hue rotation).

You can play with this effect using, for example, the Firefox developer tools' style editor; see Edit CSS filters for details on how to do so.

Using specific devices

Vajadzības gadījumā varat ierobežot atļauto video avotu kopu ar noteiktu ierīci vai ierīču kopu. Lai to izdarītu, zvaniet {{domxref ("navigator.mediaDevices.enumerateDevices ()")}}. Kad solījums tiks izpildīts, izmantojot masīvu {{domxref ("MediaDeviceInfo")}}} objektus, kas apraksta pieejamās ierīces, atrodiet objektus, kurus vēlaties atļaut, un norādiet atbilstošos {{domxref ("MediaTrackConstraints.deviceId", "deviceId"). )}} vai deviceIds objektā {{domxref ("MediaTrackConstraints")}} tika nodots objektā {{domxref ("MediaDevices.getUserMedia", "getUserMedia ()")}}.

Skatīt arī