{"id":874,"date":"2024-05-12T14:09:30","date_gmt":"2024-05-12T19:09:30","guid":{"rendered":"https:\/\/80bits.blog\/?p=874"},"modified":"2024-05-12T14:09:31","modified_gmt":"2024-05-12T19:09:31","slug":"video-javascript-capturar-video-desde-una-web","status":"publish","type":"post","link":"https:\/\/80bits.blog\/index.php\/2024\/05\/12\/video-javascript-capturar-video-desde-una-web\/","title":{"rendered":"Video &#8211; JavaScript &#8211; Capturar video desde una web"},"content":{"rendered":"\n<p>Para probar un concepto, quise crear una grabadora de video muy simple y multiplataforma, as\u00ed que eleg\u00ed la web porque funciona en cualquier sistema operativo, incluso en m\u00f3viles.<\/p>\n\n\n\n<p>He creado una web sencilla para experimentar con el manejo de dispositivos de audio y video desde la web. No tiene mucha complejidad ni Frameworks, solo un poco de Html, Css y Javascript.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"reproducir-video\">Reproducir video<\/h2>\n\n\n\n<p>Solo se necesita usar el elemento <code>video<\/code> de html para mostrar la se\u00f1al de un dispositivo que tenga el sistema, y con eso basta por la parte del front end.<\/p>\n\n\n\n<p><code>&lt;video id=\"video\" autoplay&gt;&lt;\/video&gt;<\/code><\/p>\n\n\n\n<p>Para la parte de Javascript se requiere algo m\u00e1s que una l\u00ednea de c\u00f3digo, lo primero que hay que hacer es obtener la lista de dispositivos de la siguiente forma.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const devices = await navigator.mediaDevices.enumerateDevices();&nbsp;\n\nconst cameras = devices.filter(dispositivo =&gt; dispositivo.kind === 'videoinput');\n<\/code><\/pre>\n\n\n\n<p>El primer paso es obtener los dispositivos, que suelen ser de dos tipos: <code>audiooutput<\/code> y <code>videoinput<\/code>. En este ejemplo se seleccionan los dispositivos de imagen, porque son los que queremos mostrar en el elemento video de html.\nLo que se obtiene con el c\u00f3digo anterior es un array de objetos similar a este:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n&nbsp;&nbsp;&nbsp; \"deviceId\": \"781f4b551c88a09fc960dafd9639edcdc257b9ecd9d0cfcfe387554be58b08c8\",&nbsp;\n\n&nbsp;&nbsp;&nbsp; \"kind\": \"videoinput\",&nbsp;\n\n&nbsp;&nbsp;&nbsp; \"label\": \"Camera 1\",&nbsp;\n\n&nbsp;&nbsp;&nbsp; \"groupId\": \"dda24e81ab426e6442f4b0db46a43c6df90e16fb1a02a67250ab6b9c9002fd08\"&nbsp;\n}\n<\/code><\/pre>\n\n\n\n<p>La propiedad <code>deviceId<\/code>, que identifica al dispositivo del sistema, es la que nos interesa para reproducir el video en el elemento html de video.\nDespu\u00e9s de esto, el siguiente paso es crear un objeto del tipo <code>MediaStreamConstraints<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const constraints = {\n\nvideo: {\n\ndeviceId: 123,\n\nWidth: 1920,\n\nheight: 1080,\n\nframeRate: { ideal: 30, max: 30 }\n\n}\n\n};\n<\/code><\/pre>\n\n\n\n<p>En la propiedad video de este objeto, se incluyen algunos valores como los del ejemplo. El m\u00e1s importante es el <code>deviceId<\/code>, que es el dispositivo de video que queremos usar. Las otras propiedades son la resoluci\u00f3n y el <code>frameRate<\/code>, que son importantes para la grabaci\u00f3n. Sin embargo, hay que verificar qu\u00e9 resoluciones y cuadros por segundo admite nuestro dispositivo.\nUna vez tenemos el objeto debemos pasarlo de esta manera para empezar a obtener el stream de video.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const stream = await navigator.mediaDevices.getUserMedia(constraints);\n<\/code><\/pre>\n\n\n\n<p>Ahora lo unico que debemos hacer es pasar ese stream de datos hacia nuestro elemento video en html de esta forma.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const videoElement = document.getElementById('video');\nvideoElement.srcObject = stream;\n<\/code><\/pre>\n\n\n\n<p>Y eso ser\u00eda todo lo que hay que hacer para que el video aparezca en la pantalla sobre elemento video.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"grabar\">Grabar<\/h2>\n\n\n\n<p>Para grabar el stream de video en vivo, necesitamos crear un objeto de tipo <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/MediaRecorder\">MediaRecorder<\/a> que reciba como par\u00e1metros el stream y algunas opciones b\u00e1sicas como el mimeType y el bitRate.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const mediaRecorder = new MediaRecorder(stream, { mimeType: 'video\/webm;codecs=h264', videoBitsPerSecond: bitRate });\n<\/code><\/pre>\n\n\n\n<p>El tema de los codecs es algo distinto, ya que no todos los navegadores tienen soporte para los mismos codecs ni contenedores. Por ejemplo, prob\u00e9 en el navegador Edge y este no tiene soporte para el contenedor mp4, por eso us\u00e9 webm y el codec h264, pero muchos solo admiten los codecs vp8 y vp9. Safari s\u00ed que admite el contenedor mp4 y codecs como h264.<\/p>\n\n\n\n<p>Una vez construido este objeto lo que se tiene que hacer es ejecutar el evento start para comenzar a grabar<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mediaRecorder.start();\n<\/code><\/pre>\n\n\n\n<p>El m\u00e9todo anterior ejecutara el evento onstart, que podemos personalizar tal vez para ejecutar alguna acci\u00f3n como en mi caso un contador de segundos.<\/p>\n\n\n\n<p>Una vez que necesitemos detener la grabaci\u00f3n hay que ejecutar el m\u00e9todo stop.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mediaRecorder.stop();\n<\/code><\/pre>\n\n\n\n<p>Este tambi\u00e9n ejecutara un evento llamado onstop, el cual tambi\u00e9n podemos extender.<\/p>\n\n\n\n<p>El m\u00e9todo stop, al ejecutarse, permite acceder a los datos cuando se encuentren disponibles. Estos se pueden obtener en un evento llamado ondataavailable, que entrega un objeto de tipo <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/BlobEvent\" target=\"_blank\" rel=\"noopener\" title=\"\">BlobEvent<\/a>. Este objeto tiene una propiedad llamada data, de tipo <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/BlobEvent\/data\" target=\"_blank\" rel=\"noopener\" title=\"\">Blob<\/a>, que contiene el &#8220;video&#8221;.<\/p>\n\n\n\n<p>Estos datos de video se guardan en la memoria interna y temporal del navegador, no se guardan directamente en el sistema de archivos si no que tenemos que \u201cdescargarlos\u201d como cualquier otro archivo de una web.<\/p>\n\n\n\n<p>Para obtener el video debemos crear una url para acceder al archivo de esta manera:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mediaRecorder.ondataavailable = function (event) {\nconst { data } = event;\nif (data &amp;&amp; data.size &gt; 0) {\nconst url = URL.createObjectURL(data);\nconst downloadLink = document.getElementById('downloadLink');\ndownloadLink.href = url;\ndownloadLink.download = 'recorded_video.webm';\ndownloadLink.style.display = 'block';\n}\n};\n<\/code><\/pre>\n\n\n\n<p>El c\u00f3digo anterior se encarga de obtener los datos de video y, si existen, generar la url que dirige al archivo y mostrarla en el navegador con el elemento de url: <code>Descargar Video<\/code><\/p>\n\n\n\n<p>Con ello obtendremos un link de descarga para el archivo y as\u00ed obtenemos el video.<\/p>\n\n\n\n<p>Y eso ser\u00eda todo, dejo el enlace a Github para descargar el proyecto que hice a modo de demo.<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/genitalico\/recordVideoFromHtml\">Proyecto en Github<\/a><\/p>\n\n\n\n<p><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/MediaRecorder#browser_compatibility\" target=\"_blank\" rel=\"noopener\" title=\"\">Compatibilidad de MediaRecorder en los navegadores.<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Captura del proyecto<\/h2>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img decoding=\"async\" width=\"1024\" height=\"1024\" src=\"https:\/\/80bits.blog\/wp-content\/uploads\/2024\/05\/htmlrecordvideo1-1024x1024.png\" alt=\"Video Screen Shoot\" class=\"wp-image-922\" style=\"width:727px;height:auto\" srcset=\"https:\/\/80bits.blog\/wp-content\/uploads\/2024\/05\/htmlrecordvideo1-1024x1024.png 1024w, https:\/\/80bits.blog\/wp-content\/uploads\/2024\/05\/htmlrecordvideo1-300x300.png 300w, https:\/\/80bits.blog\/wp-content\/uploads\/2024\/05\/htmlrecordvideo1-150x150.png 150w, https:\/\/80bits.blog\/wp-content\/uploads\/2024\/05\/htmlrecordvideo1-768x768.png 768w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"notas\">Notas<\/h2>\n\n\n\n<p>Es necesario tener en cuenta varios aspectos, el primero y tras hacer algunas pruebas con navegadores como Mozilla, Safari, Chrome y Edge (estos dos \u00faltimos se podr\u00edan considerar lo mismo), es el asunto de los codecs y contenedores, como dije solo Safari admite mp4, adem\u00e1s los otros al usar un contenedor webm no almacenan metadatos del video, algo tan simple como la duraci\u00f3n del video no se almacena por lo que al reproducir el archivo en un reproductor no sabr\u00e1s la duraci\u00f3n solo se reproducir\u00e1 hasta el final, cosa que si hace Safari.<\/p>\n\n\n\n<p>En la parte inicial del proyecto tengo un loop para ir verificando los codecs soportados por el navegador, pero solo a modo de referencia, no los uso realmente.<\/p>\n\n\n\n<p>Agregar la duraci\u00f3n de video a los metadatos requerir\u00eda modificar el Blob a nivel binario y guardar la duraci\u00f3n aparte, lo que va m\u00e1s all\u00e1 del alcance de este blog.&nbsp;<\/p>\n\n\n\n<p>Lo que si hice fue crear un temporizador para mostrar el tiempo que va grabando, pero a nivel web, con esta informaci\u00f3n se podr\u00eda meter a los metadatos.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Para probar un concepto, quise crear una grabadora de video muy simple y multiplataforma, as\u00ed que eleg\u00ed la web porque funciona en cualquier sistema operativo, incluso en m\u00f3viles. He creado una web sencilla para experimentar con el manejo de dispositivos de audio y video desde la web. No tiene mucha complejidad ni Frameworks, solo un [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":877,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[95,28,66,49,65,67],"tags":[96,34,35,98],"class_list":["post-874","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-html","category-js","category-linux","category-macos","category-sistemas-operativos","category-windows","tag-html","tag-javascript","tag-js","tag-web"],"jetpack_featured_media_url":"https:\/\/80bits.blog\/wp-content\/uploads\/2024\/05\/webcamcolors1.jpeg","jetpack-related-posts":[],"jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/874","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/comments?post=874"}],"version-history":[{"count":41,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/874\/revisions"}],"predecessor-version":[{"id":929,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/874\/revisions\/929"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/media\/877"}],"wp:attachment":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/media?parent=874"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/categories?post=874"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/tags?post=874"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}