Играемся с вебкамерой:
от старта потока до дополненной реальности

Зарема Халилова, Uploadcare

Играемся с вебкамерой

от старта потока до дополненной реальности

Зарема Халилова

SPB Frontend
Uploadcare
Browser support of getUserMedia from caniuse

Safari 11 ✔

Media Capture and Stream API

webcam mic

Поток

Получаем поток с устройства

			const constraints = {video: true, audio: true}
			 
			navigator.mediaDevices.getUserMedia(constraints)
				.then(stream => {...})
				.catch(error => {...})
		

Транслируем в video элемент

			stream => {
				const $video = document.querySelector('video')
				 
				$video.srcObject = stream
				$video.onloadedmetadata = () => $video.play()
			}
		

Демо: Поток в video элемент

Выберите этот слайд, чтобы увидеть демо

Поток в canvas

    stream => {
      const $video = document.createElement('video')
      
      $video.srcObject = stream
      $video.onloadedmetadata = () => {
        $video.play()
        ...
  

Поток в canvas

        ...
        const $canvas = document.querySelector('canvas')
        
        $canvas.width = $video.videoWidth
        $canvas.height = $video.videoHeight
        ...
  

Поток в canvas

        ...
        const context = $canvas.getContext('2d')
        
        const draw = () => {
          context.drawImage($video, 0, 0)
          requestAnimationFrame(draw)
        }
        ...
  

Поток в canvas

        ...
        requestAnimationFrame(draw)
      }
    }
  

Демо: Поток в canvas

Выберите этот слайд, чтобы увидеть демо

Демо: Поток в canvas

Выберите этот слайд, чтобы увидеть демо

Демо: Поток в canvas

Выберите этот слайд, чтобы увидеть демо

Поток в canvas

Проблемы с производительностью

  1. частота вызовов drawImage выше, чем frame rate у видео

1280px

Поток в canvas

Решение проблемы №1

    let frameDrawed = true
    const runDraw = () => {
      if (!frameDrawed) return
      frameDrawed = false
      requestAnimationFrame(draw)
    }
  

Поток в canvas

Решение проблемы №1

    const streamTrack = stream.getVideoTracks()[0]
    const {frameRate} = streamTrack.getSettings()
    const delay = 1000 / frameRate
      
    let frameInterval = setInterval(runDraw, delay)
  

Поток в canvas

Решение проблемы №1

    const draw = () => {
      ...
      requestAnimationFrame(draw)
      frameDrawed = true
    }
  

1280px

requestAnimationFrame ❤ setInterval

Ограничения

Ограничения

    const constraints = {video: true, audio: true}
     
    navigator.mediaDevices.getUserMedia(constraints)
    	.then(stream => {...})
    	.catch(error => {...})
  

Поддерживаемые браузером ограничения

    const supportedConstraints =
      navigator.mediaDevices.getSupportedConstraints()
  

Демо: Поддерживаемые браузером ограничения

Ограничения видео

    const constraints = {
      video: {
        height: 200,
        aspectRatio: 1,
      },
    }
     
    navigator.mediaDevices.getUserMedia(constraints)
  

Демо: Ограничения видео

Выберите этот слайд, чтобы увидеть демо

Возможности устройства

Возможности устройства

    const videoTrack = stream.getVideoTracks()[0]
    const capabilities = videoTrack.getCapabilities()
      
    if (Object.keys(capabilities).length) {
      // You can configure stream
    }
  

Возможности: фонарик

    if (capabilities.torch) {
      const $torch = document.querySelector('button.torch')
      $torch.onclick = () => {
        const torch = (videoTrack.getSettings()).torch
        videoTrack.applyConstraints({
          advanced: [{torch: !currentTorch}]
        })
  

Возможности: зум

    if (capabilities.zoom) {
      const $zoom = document.querySelector('input[type=range]')
      
      $zoom.min = capabilities.zoom.min
      $zoom.max = capabilities.zoom.max
      $zoom.step = capabilities.zoom.step
      ...
  

Возможности: зум

      ...
      $zoom.value = (videoTrack.getSettings()).zoom
      
      $zoom.onchange = (e) => videoTrack.applyConstraints({
        advanced: [{zoom: e.target.value}]
      })
  

Демо: Возможности

Выберите этот слайд, чтобы увидеть демо

Фото

Совет: картинка из blob

    const blobToImage = blob => {
      const $img = document.createElement('img')
      
      $img.src = URL.createObjectURL(blob)
      $img.onload = () => URL.revokeObjectURL($img.src)
      
      document.querySelector('.output').append($img)
    }
  

Делаем фото

Способ №1: canvas и drawImage

Сохраняет текущий видео-фрейм

Делаем фото: drawImage

    stream => {
      ... // Поток в video элемент
      ... // Создаем canvas и context
      context.drawImage($video, 0, 0)
      ...
  

Делаем фото: drawImage

      ...
      $canvas.toBlob(blob => blobToImage(blob))
    }
  

Делаем фото

Способ №2: ImageCapture и grabFrame

Сохраняет текущий фрейм с потока

Делаем фото: grabFrame

    stream => {
      const streamTrack = stream.getVideoTracks()[0]
      const imageCapture = new ImageCapture(streamTrack)
      ...
  

Делаем фото: grabFrame

      ...
      imageCapture.grabFrame()
        .then(imageBitmap => {
          ... // Создаем canvas и context
          context.drawImage(imageBitmap, 0, 0)
          $canvas.toBlob(blob => blobToImage(blob))
        }
    }
  

Делаем фото

Способ №3: ImageCapture и takePhoto

Сохраняет с потока фрейм максимального разрешения

Делаем фото: takePhoto

      ...
      imageCapture.takePhoto()
      .then(blob => blobToImage(blob))
    }
  

Демо: Делаем фото

Выберите этот слайд, чтобы увидеть демо

Запись

Демо: Запись видео

Выберите этот слайд, чтобы увидеть демо

Запись видео

    stream => {
      ... // Поток в video элемент
      let chunks = []
      const mediaRecorder = new MediaRecorder(stream)
      ...
  

Запись видео

      ...
      $startButton.onclick = () => mediaRecorder.start()
      $stopButton.onclick = () => mediaRecorder.stop()
      ...
  

Запись видео

      ...
      mediaRecorder.ondataavailable = (e) => chunks.push(e.data)
      
      mediaRecorder.onstop = () =>
        blobToVideo(new Blob(chunks, {type: 'video/webm'}))
    }
  

Let's fun

Фильтры

Демо: Фильтры

Выберите этот слайд, чтобы увидеть демо

Фильтры

Используйте CSS Filters для video элемента

    video[data-filter=grayscale] {
      filter: grayscale(100%);
    }
  

Фильтры

Совет: как сохранить фото с фильтром

    context.filter = window
      .getComputedStyle($video, null)
      .getPropertyValue('filter')
      
    context.drawImage($video, 0, 0)
  

ASCII

Маска

Демо: Маска

Выберите этот слайд, чтобы увидеть демо

Маска

Дополненная реальность

Дополненная реальность

    <script src=".../aframe.min.js"></script>
    <script src=".../aframe-ar.js"></script>
  

Дополненная реальность

    <body style='margin : 0px; overflow: hidden;'>
    <a-scene embedded artoolkit='sourceType: webcam;'>
      <a-box position='0 0 0.5' material='opacity: 0.7;'></a-box>
      <a-marker-camera preset='hiro'></a-marker-camera>
    </a-scene>
    </body>
  

Что ещё?

Можно сделать

  1. видео-конференции с помощью WebRTC

Коллекция ссылок

Спасибо!

https://goo.gl/15RG2L


@Zmoki