javascript

Tomar foto de cámara web con JavaScript y descargarla como imagen

En mi blog he hecho varios posts sobre tomar fotos con la cámara web desde JavaScript, para enviarlas a PHP; pero nunca he hecho un ejemplo sin usar PHP.

Es por ello que hoy voy a explicar cómo tomar una foto de la cámara web (igualmente de la cámara trasera o delantera en los dispositivos compatibles) y guardarla o descargarla directamente como archivo, sin usar código del servidor.

Tomar foto con JavaScript y descargarla como imagen PNG

El código para tomar la foto funciona en varios dispositivos con un navegador actualizado, desde teléfonos y tabletas Android, iPads y más.

Aplicación demostrativa y código fuente

Para probar la app accede a este enlace, y para ver el código fuente completo (ya que a través del post explicaré los detalles más importantes únicamente) mira el repositorio en Github.

Recuerda que para alojarla debes usar un servidor con https o localhost.

Código HTML

Me he esforzado en hacer el código HTML lo más corto posible. En resumen solamente necesitamos un elemento <video> para mostrar el stream y un canvas (que mantenemos oculto, y que podría ser creado dinámicamente).

<!DOCTYPE html>
<html lang="es">

<head>
 <!--
  Tomar una fotografía y descargarla
     @date 2019-04-23
     @author parzibyte
     @web parzibyte.me/blog
 -->
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 <title>Tomar foto con Javascript</title>
 <style>
  @media only screen and (max-width: 700px) {
   video {
    max-width: 100%;
   }
  }
 </style>
</head>

<body>
 <h1>Tomar foto con JavaScript</h1>
 <p>
  Programado por Luis Cabrera Benito a.k.a. <a target="_blank" href="https://www.parzibyte.me/blog">parzibyte</a>
 </p>
 <h1>Selecciona un dispositivo</h1>
 <div>
  <select name="listaDeDispositivos" id="listaDeDispositivos"></select>
  <button id="boton">Tomar foto</button>
 </div>
 <br>
 <video muted="muted" id="video"></video>
 <canvas id="canvas" style="display: none;"></canvas>
</body>
<script src="script.js"></script>

</html>

Eso es todo el código de la vista, es momento de ver el código JavaScript.

Pedir permiso para acceder a la cámara y obtener stream

Para tomar la foto usamos la API de JavaScript llamada getUserMedia, la cual está disponible desde hace bastante tiempo y si bien no está estandarizada, varios navegadores ofrecen soporte para la misma.

Para pedir permisos llamamos a getUserMedia pasándole 3 argumentos:

  1. Un objeto con las propiedades de lo que queremos, por ejemplo, audio o vídeo
  2. Una función en caso de éxito, es decir, en caso de que se otorgue el permiso y haya cámaras
  3. Una función en caso de error en caso de que no se produzca un error técnico o el permiso se haya denegado
const tieneSoporteUserMedia = () =>
    !!(navigator.getUserMedia || (navigator.mozGetUserMedia || navigator.mediaDevices.getUserMedia) || navigator.webkitGetUserMedia || navigator.msGetUserMedia)
const _getUserMedia = (...arguments) =>
    (navigator.getUserMedia || (navigator.mozGetUserMedia || navigator.mediaDevices.getUserMedia) || navigator.webkitGetUserMedia || navigator.msGetUserMedia).apply(navigator, arguments);

const mostrarStream = idDeDispositivo => {
    _getUserMedia({
            video: {
                // Justo aquí indicamos cuál dispositivo usar
                deviceId: idDeDispositivo,
            }
        },
        (streamObtenido) => {
            // Aquí ya tenemos permisos, ahora sí llenamos el select,
            // pues si no, no nos daría el nombre de los dispositivos
            llenarSelectConDispositivosDisponibles();

            // Escuchar cuando seleccionen otra opción y entonces llamar a esta función
            $listaDeDispositivos.onchange = () => {
                // Detener el stream
                if (stream) {
                    stream.getTracks().forEach(function(track) {
                        track.stop();
                    });
                }
                // Mostrar el nuevo stream con el dispositivo seleccionado
                mostrarStream($listaDeDispositivos.value);
            }

            // Simple asignación
            stream = streamObtenido;

            // Mandamos el stream de la cámara al elemento de vídeo
            $video.srcObject = stream;
            $video.play();

            
        }, (error) => {
            console.log("Permiso denegado o error: ", error);
            $estado.innerHTML = "No se puede acceder a la cámara, o no diste permiso.";
        });
}

Dentro de la función de éxito será pasado un argumento que es el stream obtenido, el cual ponemos sobre un vídeo (sí, un elemento <video>) y reproducimos; de esta manera el usuario estará viendo un vídeo que tiene como fuente su cámara.

La lista de dispositivos para permitir el cambio de cámaras

Esta API también ofrece una manera de obtener la lista de dispositivos existentes, de esta manera podemos intercambiar entre la cámara frontal y trasera (o más cámaras en caso de que existan).

La lista tiene, entre otras cosas, un deviceId. Ese deviceId se pasa a getUserMedia y se devolverá el stream adecuado.

const obtenerDispositivos = () => navigator
    .mediaDevices
    .enumerateDevices();

Casi lo olvido: para obtener la lista se llama a la función navigator.mediaDevices.enumerateDevices.

Después de obtener la lista, los dispositivos se ponen en un elemento de tipo select. En el cambio del mismo se vuelve a obtener el stream (después de detener los demás) y se monta sobre el vídeo.

// La función que es llamada después de que ya se dieron los permisos
// Lo que hace es llenar el select con los dispositivos obtenidos
const llenarSelectConDispositivosDisponibles = () => {

    limpiarSelect();
    obtenerDispositivos()
        .then(dispositivos => {
            const dispositivosDeVideo = [];
            dispositivos.forEach(dispositivo => {
                const tipo = dispositivo.kind;
                if (tipo === "videoinput") {
                    dispositivosDeVideo.push(dispositivo);
                }
            });

            // Vemos si encontramos algún dispositivo, y en caso de que si, entonces llamamos a la función
            if (dispositivosDeVideo.length > 0) {
                // Llenar el select
                dispositivosDeVideo.forEach(dispositivo => {
                    const option = document.createElement('option');
                    option.value = dispositivo.deviceId;
                    option.text = dispositivo.label;
                    $listaDeDispositivos.appendChild(option);
                });
            }
        });
}

Tomar foto

Con lo que llevamos hasta ahora tenemos un select que permite intercambiar entre distintas cámaras, además de mostrar el vídeo en un elemento <video>; lo que sigue es usar un elemento <canvas> o lienzo para tomar la foto.

//Escuchar el click del botón para tomar la foto
$boton.addEventListener("click", function() {

    //Pausar reproducción
    $video.pause();

    //Obtener contexto del canvas y dibujar sobre él
    let contexto = $canvas.getContext("2d");
    $canvas.width = $video.videoWidth;
    $canvas.height = $video.videoHeight;
    contexto.drawImage($video, 0, 0, $canvas.width, $canvas.height);

    let foto = $canvas.toDataURL(); //Esta es la foto, en base 64

    let enlace = document.createElement('a'); // Crear un <a>
    enlace.download = "foto_parzibyte.me.png";
    enlace.href = foto;
    enlace.click();
    //Reanudar reproducción
    $video.play();
});

El proceso es sencillo. Se pausa el vídeo y se dibuja la imagen que tiene sobre el canvas; una vez hecho eso podemos obtener la imagen como base 64, y cuando la tenemos en ese formato solo resta descargarla creando un enlace.

Conclusión

En este ejemplo tomamos una foto con la cámara web y descargamos la imagen; si quieres ver cómo enviarla a PHP mira este ejemplo.

Los usos que puedes darle al consumo de estas APIs son muchísimos, una vez que ya tienes la foto puedes mostrarla en un elemento <img>, procesarla o cualquier cosa que tu app requiera.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Ver comentarios

  • Buenas!
    Cuando pruebo el mismo codigo html y codigo javascript desde mi sitio web local mi navegador no encuentra ninguno de los dispositivos de camara en navigator de javascript, que podria faltarme?

    • Pueden ser varios factores. Recuerde que se necesita estar en localhost o un sitio con https para acceder a los dispositivos, así como dar permiso de acceso a la cámara.
      Si tiene más dudas estoy para servirle en https://parzibyte.me/#contacto

  • Me marca el error
    Uncaught TypeError: navigator.mediaDevices is not a function
    at camara.js:64
    at camara.js:159

  • No logro hacer que la foto que se tomo se descargue en otra carpeta diferente a la de descargas, agradecería tu ayuda, gracias de antemano

  • Gracias por la web. Excelente!! Si quisiera ponerle un marco a la cámara antes de hacer la foto, se podría hacer? cómo?

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

3 horas hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

6 días hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

7 días hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

7 días hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

7 días hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

7 días hace

Esta web usa cookies.