Introducción
Ya estamos aquí con un tercer post sobre tomar fotos con JavaScript. Este tutorial ofrece una nueva característica y es la de dar al usuario elegir cuál cámara usar para tomar la foto. En los posts anteriores únicamente tomaba de la cámara por defecto, y en algunos navegadores (Chrome, cof cof) no permite cambiar la cámara con la que se toma.
Afortunadamente eso no importa, porque podemos obtener la lista de dispositivos y cambiarlos como se nos dé la gana.
Por cierto, este post está más actualizado; usa constantes, querySelector y otras cosas que los anteriores no. Si bien esto no afecta el funcionamiento, nos da un código más bonito y entendible.
Pero basta de hablar, que hablar es de mal gusto; vamos a ver el código.
Versiones anteriores
En caso de que se me haya pasado explicar algo, puedes revisar las versiones anteriores de este post.
Tomar foto con cámara y JavaScript
Tomar foto con cámara y JavaScript v2
Compatibilidad
Compatible y probado con Edge, Firefox y Chrome en la fecha 22 de octubre del 2018. Puedes probarlo tú mismo y dejar en los comentarios si sigue funcionando (debería hacerlo por mucho tiempo).
En las siguientes imágenes he probado este código en el navegador, cubriendo obviamente mi cámara. Pruébalo tú si quieres y verás que funciona como un encanto.
Código fuente
Si lo deseas, puedes explorar el código de ejemplo en GitHub. Si más tarde hago actualizaciones podrás verlas directamente ahí. El repositorio está aquí.
Aquí está el código del script:
/*
Tomar una fotografía y guardarla en un archivo v3
@date 2018-10-22
@author parzibyte
@web parzibyte.me/blog
*/
function tieneSoporteUserMedia() {
return !!(navigator.getUserMedia || (navigator.mozGetUserMedia || navigator.mediaDevices.getUserMedia) || navigator.webkitGetUserMedia || navigator.msGetUserMedia)
}
function _getUserMedia() {
return (navigator.getUserMedia || (navigator.mozGetUserMedia || navigator.mediaDevices.getUserMedia) || navigator.webkitGetUserMedia || navigator.msGetUserMedia).apply(navigator, arguments);
}
// Declaramos elementos del DOM
const $video = document.querySelector("#video"),
$canvas = document.querySelector("#canvas"),
$boton = document.querySelector("#boton"),
$estado = document.querySelector("#estado"),
$listaDeDispositivos = document.querySelector("#listaDeDispositivos");
// 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 = () => {
navigator
.mediaDevices
.enumerateDevices()
.then(function (dispositivos) {
const dispositivosDeVideo = [];
dispositivos.forEach(function (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);
console.log("$listaDeDispositivos => ", $listaDeDispositivos)
});
}
});
}
(function () {
// Comenzamos viendo si tiene soporte, si no, nos detenemos
if (!tieneSoporteUserMedia()) {
alert("Lo siento. Tu navegador no soporta esta característica");
$estado.innerHTML = "Parece que tu navegador no soporta esta característica. Intenta actualizarlo.";
return;
}
//Aquí guardaremos el stream globalmente
let stream;
// Comenzamos pidiendo los dispositivos
navigator
.mediaDevices
.enumerateDevices()
.then(function (dispositivos) {
// Vamos a filtrarlos y guardar aquí los de vídeo
const dispositivosDeVideo = [];
// Recorrer y filtrar
dispositivos.forEach(function (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
// y le pasamos el id de dispositivo
if (dispositivosDeVideo.length > 0) {
// Mostrar stream con el ID del primer dispositivo, luego el usuario puede cambiar
mostrarStream(dispositivosDeVideo[0].deviceId);
}
});
const mostrarStream = idDeDispositivo => {
_getUserMedia(
{
video: {
// Justo aquí indicamos cuál dispositivo usar
deviceId: idDeDispositivo,
}
},
function (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();
//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
$estado.innerHTML = "Enviando foto. Por favor, espera...";
fetch("./guardar_foto.php", {
method: "POST",
body: encodeURIComponent(foto),
headers: {
"Content-type": "application/x-www-form-urlencoded",
}
})
.then(resultado => {
// A los datos los decodificamos como texto plano
return resultado.text()
})
.then(nombreDeLaFoto => {
// nombreDeLaFoto trae el nombre de la imagen que le dio PHP
console.log("La foto fue enviada correctamente");
$estado.innerHTML = `Foto guardada con éxito. Puedes verla <a target='_blank' href='./${nombreDeLaFoto}'> aquí</a>`;
})
//Reanudar reproducción
$video.play();
});
}, function (error) {
console.log("Permiso denegado o error: ", error);
$estado.innerHTML = "No se puede acceder a la cámara, o no diste permiso.";
});
}
})();
Es casi igual que el anterior, la lógica no cambia mucho.
Listar dispositivos y poder elegir cámara
Para poder listar los dispositivos llamamos a navigator.mediaDevices.enumerateDevices
, lo que devuelve una lista de los mismos, que pueden ser micrófonos y otras cosas.
Si te fijas, estamos llamando dos veces a ese método, ¿por qué? bueno, primero lo llamamos para ver si hay algún dispositivo para comenzar el stream, pero para ese tiempo el usuario no ha dado permisos, por lo que este método únicamente devuelve el id de los dispositivos, más no el nombre. Si no nos da el nombre, no podremos llenar el select de una buena manera.
Una vez que el usuario da permisos, volvemos a llamar a ese método para que nos dé los dispositivos. Como en este caso ya tenemos permisos, también devolverá el nombre de los mismos. Esto es más que nada por la seguridad.
Cuando hay más de un dispositivo, el select los muestra y al cambiarlos se usa esa cámara.
Fetch en lugar de XMLHttpRequest
XMLHttpRequest
es una API que ya tiene años. Claro, es el estándar y los frameworks que usamos para hacer peticiones AJAX simplemente son una capa que nos permite interactuar con esta API.
Sin embargo, nosotros no estamos usando ningún framework, así que teníamos que usar XMLHttpRequest
, pero ya no más, ahora usamos fetch
(pronto escribiré sobre ello), ya que estamos explotando las nuevas características de JS.
Silenciar vídeo
En el código HTML también se hicieron cambios. Se movieron algunos elementos pero lo más importante es que se agregó el atributo muted al elemento <video>
por las políticas del navegador Chrome (y tal vez todos lo hagan así). Esto no afecta en nada porque nosotros únicamente mostramos vídeo y no audio, pero igual se tiene que especificar.
El código queda así:
<!DOCTYPE html>
<html lang="es">
<head>
<!--
Tomar una fotografía y guardarla en un archivo v3
@date 2018-10-22
@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 y PHP v3.0</title>
<style>
@media only screen and (max-width: 700px) {
video {
max-width: 100%;
}
}
</style>
</head>
<body>
<h1>Tomar foto con JavaScript v3.0</h1>
<p>
Programado por Luis Cabrera Benito a.k.a. <a target="_blank" href="https://parzibyte.me/blog">parzibyte</a>
</p>
<h1>Selecciona un dispositivo</h1>
<div>
<select name="listaDeDispositivos" id="listaDeDispositivos"></select>
<button id="boton">Tomar foto</button>
<p id="estado"></p>
</div>
<br>
<video muted="muted" id="video"></video>
<canvas id="canvas" style="display: none;"></canvas>
</body>
<script src="script.js"></script>
</html>
Por cierto, puse unos estilos para que en móviles el vídeo ocupe el ancho de la pantalla pero no se pase más allá.
Código PHP
El código se queda intacto, pero aquí lo pego igualmente.
<?php
/*
Tomar una fotografía y guardarla en un archivo
@date @date 2018-10-22
@author parzibyte
@web parzibyte.me/blog
*/
$imagenCodificada = file_get_contents("php://input"); //Obtener la imagen
if(strlen($imagenCodificada) <= 0) exit("No se recibió ninguna imagen");
//La imagen traerá al inicio data:image/png;base64, cosa que debemos remover
$imagenCodificadaLimpia = str_replace("data:image/png;base64,", "", urldecode($imagenCodificada));
//Venía en base64 pero sólo la codificamos así para que viajara por la red, ahora la decodificamos y
//todo el contenido lo guardamos en un archivo
$imagenDecodificada = base64_decode($imagenCodificadaLimpia);
//Calcular un nombre único
$nombreImagenGuardada = "foto_" . uniqid() . ".png";
//Escribir el archivo
file_put_contents($nombreImagenGuardada, $imagenDecodificada);
//Terminar y regresar el nombre de la foto
exit($nombreImagenGuardada);
?>
Conclusión
El código se encuentra comentado para explicarse por sí mismo. Si tienes una duda, comentario o sugerencia puedes usar los comentarios.
Te invito a explorar el código en GitHub, recuerda que dejé el link arriba.
Excelente !!!!Muchas gracias mi estimado amigo, le agradezco por esta gran aporte en el desarrollo web!!!
Disculpa tengo una consulta
Como podría apagar el dispositivo WebCam mediante algún botón
Hola. Gracias por sus comentarios. Si tiene alguna consulta o duda, solicitud de creación de un programa o solicitud de cambio de software estoy para servirle en https://parzibyte.me/#contacto
Saludos!
Muy bueno, ya me funciona bien para capturar cámara y guardar, pero al usarlo en celular, a pesar que selecciona ambas cámaras, frontal y facing. No me funciona tomar foto con cámara frontal, solo funciona con cámara de selfies. Hay alguna linea de codigo para que funcionen ambas cámaras con celular?
Hola. Gracias por sus comentarios. Si tiene alguna consulta, solicitud de creación de un programa o solicitud de cambio de software con gusto lo atiendo en https://parzibyte.me/#contacto
Saludos!
hola tengo una consulta el escrp funciona muy bien pero cuando lo trabajo de forma local pero cuando lo subo y lo pruebo ya en linea me da un error que es el siguiente:
Uncaught TypeError: Cannot read property ‘getUserMedia’ of undefined
at tieneSoporteUserMedia (script.js:8)
at script.js:58
at script.js:163
pense que solo era en un navegador pero es en todos y en los dispositivos que puede ser me puedes ayudar.
que buen trabajo! y bien explicado, gracuas
Como puedo hacer para que me muestre o se recorte la imagen de la camara, para que se vea apaisado, como si fuera una foto para un id, la version 3 funciona a la perfeccion, pero me gustaria modificarlo para hacer fotos para id
por el canvas el tamaño del canvas seria el tamaño de la foto
Hola buen día, super bueno tu aporte, pero no me funciona al momento de ver la imagen tomada y al revisar en consola me sale un error 405 (Method Not Allowed), te pasó alguna vez? Saludos
Hola. Yo acabo de probar y funciona perfectamente
Acuérdate que debe estar https
Buenas tardes, esta muy bueno el codigo. lo descargue y lo estoy probando intacto como lo tienes en mi servidor. abre la pagina y apatece el boton tomar foto, pero el resto de cosas no aparece. me pide el permiso de usar la camara y se lo doy y no pasa nada. no sale el cuadro donde se ve la camara ni nada. que falto por hacer ?
Hola. El proyecto está completo, en la demostración funciona bien. Puede ser que sea problema de su servidor
Los mensajes son de éxito en la subida de la imagen pero no se guarda la imagen el server y me gustaría saber como guardar la imagen con un texto que provenga de una caja de texto.
Gracias por tu ayuda
Saludos
Ya lo pude subir al server, pero la otra pregunta es posible que me puedas ayudar por favor
Gracias
Hola. Con gusto lo ayudo en https://parzibyte.me#contacto
Saludos 🙂
Hola, seria posible que en lugar de escuchar el click del botón “Tomar foto” se guardara la foto si se hace click en la camara? (en caso de utilizar una camara conectada que disponga de boton para tomar la foto)
Hola. Por el momento no cuento con una cámara para probar, puede probar usted mismo y comentar su resultado
Saludos 🙂
Hola me gustaría saber como guardar la imagen con un texto que provenga de una caja de texto.
Agradezco de antemano tu ayuda.
Hola buena tarde, tú código está perfecto y me ayudo muchisimo pero tengo un problemilla con los navegadores en dispositivos iOS, hice mi propia pagina con tu código pero no funciona, pense que algo estaba haciendo mal pero después abrí tu página e igualmente no funciono la camara. Tú has tenido algún inconveniente con dispositivos iOS?
Gracias y Saludos
Así es, algo le pasa a ese sistema operativo que siempre presenta problemas. Intente instalando Google Chrome en esos dispositivos, tal vez así funcione
Saludos 🙂
en celulares iphone o sistema operativo iOS, no reconoce la camara, que se puede cambiar o configurar para que en iOS reconozca la camara?
Excelente, muchas gracias por compartir el conocimiento. Felicidades.
Muy buen aporte ya lo probe, pero tengo una duda por favor quisiera que me ayuden.
Logre que la imagen se inserte en la Base de Datos pero de una forma muy rara como muestra el siguiente codigo :
fetch_array();
$id_mesa=$fila[‘Maxid’];
$imagenCodificada = file_get_contents(“php://input”);
if(strlen($imagenCodificada) query($query);
exit($nombreImagenGuardada);
?>
Pero ahora la duda es la siguiente, cuando elimino una imagen y quiero volver a insertar otra imagen este codigo ya no me sirve para nada por que solo inserta imagen en el ultimo “ID”.
Por favor quisiera saber como enviar el “ID” y la imagen al mismo tiempo a este código o tal vez podrian realizar un ejemplo completo de como insertar la imagen (sacada con la cámara) en la base de datos.
Hola. Puedes crear un FormData y enviarlo en lugar de la foto; después sería cosa de obtener los campos separados en PHP y listo.
Un saludo.
Hola, lo probe y me funciono, el problema que tengo es que en mi celular no me abre el select de la camara, pero si lo abre si ejecuto la pagina en modo incognito, le he dado vueltas pero no encuentro la razon de la falla.
Me sale este error,
script.js:8 Uncaught TypeError: Cannot read property ‘getUserMedia’ of undefined
at tieneSoporteUserMedia (script.js:8)
at script.js:57
at script.js:161
favor de ayudarme
Si no especificas el navegador y el código que estás usando no puedo ayudarte. Lo que puedo inferir con lo que dices es que de alguna forma navigator no está definido; probablemente estás intentando en un navegador no soportado
mas detalles del error, en localhost funciona bien (chrome, mozilla) pero subi el codigo a mi servidor web con dominio y me sale esos errores:
Uncaught TypeError: Cannot read property ‘getUserMedia’ of undefined
at tieneSoporteUserMedia (control.js:1)
at control.js:39
at control.js:124
Tu dominio tiene https? recuerda que sin https no se puede acceder a la cámara / micrófono. Por otro lado, podrías colocar tu código en gist.github.com o pastebin para analizarlo de mejor manera?
Saludos
no tiene https
Podrías colocar el código que estás usando en un gist.github.com y poner el enlace aquí, para analizarlo?
era eso! tuve q poner mi servidor web con certificado!, ya funciona y muchas gracias….
Me agrada escuchar que ya funciona. No olvides compartir y suscribirte. Saludos
Heché a andar el código tal cual esta en el post y todo genial solo que en android no me pide permiso de camara
Hola, yo lo acabo de probar en Chrome y Firefox y va de maravilla. Intenta actualizar tu navegador, o darle permisos al navegador de igual forma, pues tal vez en una ocasión el navegador (Como app de Android) pidió permiso para acceder a la cámara y lo denegaste, por lo que ahora lo deniega en este código de JavaScript
Pingback: Grabar vídeo de cámara con JavaScript - Parzibyte's blog
Pingback: Tomar foto de cámara web con JavaScript y descargarla como imagen - Parzibyte's blog
tengo conectada una cámara FUL HD, pero aun así las fotos salen con poca calidad, como podemos agrandar el tamaño de la imagen para ver si se puede mejorar la calidad.
por cierto, batalle demasiado para que me guardara la imagen una ves tomada, el error me salia en los headers, y esto provocaba un error 403 en el js, la solución es la siguiente:
headers: {
“Content-Type”: ‘text/plain; charset=”utf-8″‘,
“Content-type”: “application/x-www-form-urlencoded”,
}
Saludos un fuerte abrazo
Hola. Para especificar una resolución puedes indicar las restricciones o constraints. Mira la documentación oficial: https://developer.mozilla.org/es/docs/Web/API/MediaDevices/getUserMedia#Par%C3%A1metros
Saludos y gracias por tus comentarios 🙂
Hola de nuevo, no veo el comentario que he hecho esta mañana…
Que debo hacer para que salga por defecto la cámara posterior ?
Un saludo y gracias por la información que nos facilitas.
Hola, siento la espera, pero como todo ser humano necesito dormir y como usted se ha de imaginar nadie me paga para mantener el blog, todo lo hago cuando tengo tiempo libre. Saludos 🙂
Pd: ya respondí la cuestión técnica en su anterior pregunta
Hola, el blog perfecto.
La versión 3 funcionar perfectamente.
Puedes indicarme que debo hacer para que por defecto utilizemos la cámara posterior del móvil.
Gracias
Hola. Los dispositivos están en un arreglo, normalmente el primero es el de la cámara frontal (arreglo[0]) y por lo tanto, en caso de existir, el segundo está en arreglo[1]; basta con tomar el segundo elemento del arreglo en lugar del primero al llamar por primera vez a getUserMedia
perfecto, he modificado la línea:
mostrarStream(dispositivosDeVideo[0].deviceId);
Al poner un [1] me ha mostrado la cámara trasera.
Muchas gracias
Un saludo
Hola, he probado el código y en versión PC no hay problema, en Android tampoco pero en IOS no va, usando Safari o el Chrome no deja seleccionar. Me he dado cuenta que es la detención de las cámaras, que no lo hace. Si se quita la opción de las cámaras si funciona. Es IOS 12, ¿ Alguna idea ?
Tendríamos que analizar el mensaje de error que aparece. Me parece raro que no funcione incluso con Chrome, pero como te digo, la única pista que podemos obtener es a través del mensaje de error que lance el navegador. ¿Has dado acceso a los navegadores para acceder a la cámara? (no sé cómo maneja la privacidad iOs por eso pregunto), por otro lado, tal vez sea el propio navegador con eso de los prefijos
La cuestión es que no da error, simplemente el cuadro de selección de la cámara aparece vacío y deja sin efecto el botón de “tomar foto”. En Safari y en chrome está habilitado el uso de la cámara y funciona con otros ejemplos pero sólo sin seleccionar cámara, coge la frontal. Además, he quitado navigator.webkitGetUserMedia y detecta y muestra que el navegador no es compatible. He probado con ipod, ipad y iphone y da lo mismo el navegador por lo que interpreto que es del sistema pero no se me ocurre el qué. Gracias
Amigo como le quitaste la opcion de las camaraS?
donde se quita la opcion de camaras ?, porque tengo el mismo problema que no funciona en IOS
hombre, tengo un problema, en pc esta funcionando perfectamente, pero si lo pasamos al móvil no carga y no puedo darle permisos al dispositivo para acceder a la cámara local mente por tu link de ejemplo se puede pero no en mi localhost, no se si tengo que poner certificado o algo quiero que me aclares que es lo que falta para que funciona
sino otro particular de agradecerte tu atención.
Hola, la página a la que accedes debe ser localhost o una página con HTTPS. Cuando accedes a localhost desde tu PC (la cual actúa como servidor), es localhost; pero cuando accedes desde tu móvil (incluso si es una LAN) accedes a otra IP (la de tu máquina) que no tiene HTTPS ni es localhost así que no tiene acceso. Por otro lado, si accedes al link que yo pongo, sí te lo permite porque tiene HTTPS.
En resumen, no puedes conectar a otro lugar que no tenga https o no sea “localhost”, puedes instalar un certificado SSL en localhost (cosa que a mi parecer es un poco compleja), rentar un dominio en internet con https o montar directamente la app en el móvil (suponiendo que es Android) como se ve aquí: https://parzibyte.me/blog/2018/11/14/aplicacion-web-php-mysql-sobre-android/
Saludos 🙂
pues sep hombre con un poco de esfuerzo pude lograr hacerlo andar en un servidor local con ssl autofirmados y funciona perfectamente
Me da gusto que haya funcionado 🙂
Hola tengo una duda si le incluyo dos input tipo text y quiero que el archivo se guarde con lo que ingresé en los cuadro de texto que método debo emplear?
Hola, crea tu formulario normalmente y añade en él un input de tipo hidden. En los eventos de la cámara, toma el base64 del canvas y ponlo como cadena en el input que mencioné anteriormente; en el lado del servidor simplemente decodifica esa cadena y listo.
Gracias por el aporte, consulta como puedo aumentar la resolución de la foto, por defecto lo deja en 640×480, me gustaria fueran unos 800×600 además de hacer un zoom y porque la camara es un “escanner con brazo”, que es realizada es un dispositivo de imagen modificado.
Gracias
Hola, no lo sé, pero la resolución depende de la cámara. Lo que podrías hacer sería acercar la imagen o agrandar el vídeo
Como seria para pasar la imagen tomada a un input de tipo file?
Hola, no lo sé, sería de investigar aunque no le encuentro utilidad. Si deseas que se envíe como si fuera un formulario usa la API de FormData en donde puedes añadir muchos valores y enviarlos por AJAX
Hola, no se puede asignar una imagen a un elemento input file…
No lo sé, pero si se desea que se envíe en el formulario, se podría codificar como base64 y luego asignarlo a un elemento de tipo hidden para más tarde decodificarlo del otro lado
Pingback: Tomar foto con Javascript y cámara para guardarla en servidor PHP - Parzibyte's blog
Pingback: Tomar foto de cámara web con Javascript, actualizado - Parzibyte's blog