Como gravar vídeos usando HTML Javascript e CSSAprenda como implementar uma câmera e gravar pequenos videos usando html, javascript e css de forma simples16 de setembro de 2024 - 10 min de leitura

Introdução

Anteriormente fizemos uma câmera para poder tirar fotos usando o navegador, vamos agora evoluir a solução e gravar pequenos vídeos usando a mesma estrutura mais adicionando um novo recurso.

Antes de começar!

Iremos utilizar como base o projeto de câmera feita no post anterior, ele é bem simples e caso você não tenha visto é só acessar aqui: Como criar uma câmera utilizando Javascript HTML5 e CSS3

Vimos no post anterior que a câmera é acessada pela api do navegador acessada pela função navigator.mediaDevices.getUserMedia e que ela só funciona em um contexto seguro, seja ele localhost ou usando https (documentação oficial) se atente a isso caso a sua câmera não esteja funcionando.

Vamos ao trabalho

Passo 1: Ajustando o layout

Basicamente a estrutura é a mesma, fiz apenas algumas alterações e adicionei alguns controles que uma câmera de vídeo precisa. A câmera aberta antes de gravar ficou assim: ⁣

layout da camera de video usando html

E quando está gravando:

camera html enquanto grava o video

o código HTML:

1 <div id="dialog-camera" class="absolute w-screen h-screen bg-stone-900 z-10 hidden"> 2 3 <div class="w-full h-full" id="video-preview-container"> 4 <video class="w-full h-full" autoplay id="video-preview"></video> 5 6 <div id="timer" class="absolute bottom-20 w-fit mx-auto left-0 right-0 text-white z-10 hidden">00:00</div> 7 <div 8 class="absolute bottom-3 text-center z-10 grid grid-cols-3 p-2 justify-center place-items-center items-center w-full 9 landscape:grid-rows-3 landscape:grid-cols-1 landscape:right-3 landscape:w-fit landscape:top-0 landscape:bottom-0"> 10 <i id="btn-close-dialog" class="fa-solid fa-xmark w-12 text-white text-3xl"></i> 11 <i id="btn-recording" 12 class="fa-solid fa-circle-dot text-[48px] w-12 rounded-full text-white bg-red-500 text-4xl "></i> 13 <i id="btn-stop-recording" 14 class="fa-solid fa-circle-stop text-[48px] w-12 rounded-full text-white bg-red-500 text-4xl hidden"></i> 15 <i id="btn-toggle-camera" class="fa-solid fa-camera-rotate w-12 text-white text-3xl"></i> 16 </div> 17 </div> 18 19 <div id="photo-preview-container" class="w-full h-full items-center justify-center hidden"> 20 <video class="w-full landscape:w-fit landscape:h-[75%]" controls id="video-preview-recorded"></video> 21 <div id="actions-preview" class="absolute bottom-3 z-10 flex p-2 justify-around items-center w-full 22 landscape:flex-col-reverse landscape:right-3 landscape:w-fit landscape:top-0 landscape:bottom-0"> 23 <button id="btn-repeat" class="text-white px-3 py-2 ">Repetir</button> 24 <button id="btn-ok" class="text-white px-3 py-2 ">OK</button> 25 </div> 26 </div> 27 28 </div>

Observação: Estou mostrando somente o código alterado, caso queira acessar o código completo acesse aqui

Passo 2: Recuperando elementos HTML e declaração das variáveis de controle

No início da minha tag <script> declarei as variáveis que serão utilizadas para recuperar os elementos referentes a câmera, os controles mais genéricos como botões de abrir/fechar a câmera e logo depois as minhas variáveis de controle que me ajudarão a manter o estado da câmera.

1 // recuperando elementos da camera de video 2 const videoPreview = document.querySelector('#video-preview'); 3 const btnCamera = document.querySelector('#btn-recording'); 4 const btnStopCamera = document.querySelector('#btn-stop-recording'); 5 const videoPreviewContainer = document.querySelector('#video-preview-container'); 6 const photoPreviewContainer = document.querySelector('#photo-preview-container'); 7 const previewRecorded = document.querySelector('#video-preview-recorded'); 8 const timer = document.querySelector('#timer'); 9 10 // recuperando elementos de controle genericos 11 const dialogCamera = document.querySelector('#dialog-camera'); 12 const btnToggleCamera = document.querySelector('#btn-toggle-camera'); 13 const btnOpenCamera = document.querySelector('#btn-open-camera'); 14 const btnCloseDialog = document.querySelector('#btn-close-dialog'); 15 const btnRepeat = document.querySelector('#btn-repeat'); 16 const btnOk = document.querySelector('#btn-ok'); 17 18 // declarando variaveis de controle 19 let currentFacingMode = 'environment'; 20 let mediaRecorder; 21 let streamCamera; 22 let secondsElapsed = 0; 23 let intervalId;

Passo 3: Iniciar/Pausar a câmera

Iniciar a câmera é exatamente igual para quando vamos fazer uma câmera apenas para tirar uma foto. Primeiro declaramos uma função que será responsável por chamar a api getUserMedia do navegador que caso obtenha sucesso irá retornar um stream no qual vamos direcionar para um objeto de vídeo e também iremos salvar este stream nas nossas variáveis de controle, pois iremos usar tanto para a gravação quanto para parar a câmera quando não estivermos usando.

1 const startCamera = (facingMode = 'environment') => { 2 stopCamera(); 3 navigator.mediaDevices.getUserMedia({ 4 video: { 5 facingMode, 6 width: { 7 max: 1980, 8 ideal: 1024 9 }, 10 height: { 11 max: 1080, 12 ideal: 768 13 } 14 } 15 }).then((stream) => { 16 videoPreview.srcObject = stream; 17 streamCamera = stream; 18 }) 19 } 20

Linha 17: Note que estamos usando a variável streamCamera anteriormente declarada para armazenar o stream.

Para parar a câmera usaremos o stream do vídeo e percorremos cada track chamando o método stop de cada uma.

1 const stopCamera = () => { 2 if (videoPreview.srcObject) { 3 const stream = videoPreview.srcObject; 4 const tracks = stream.getTracks().forEach((track) => track.stop()); 5 } 6 }

Usamos o método startCamera sempre que usuário abre a câmera pela primeira vez ou quando ele precisa trocar o modo câmera frontal ou traseira. Como mostra as funções a seguir:

1 btnOpenCamera.addEventListener('click', () => { 2 dialogCamera.classList.toggle('hidden'); 3 photoPreviewContainer.classList.add('hidden'); 4 videoPreviewContainer.classList.remove('hidden'); 5 6 startCamera(currentFacingMode); 7 }); 8 9 btnToggleCamera.addEventListener('click', () => { 10 if (currentFacingMode == 'environment') { 11 currentFacingMode = 'user'; 12 } else { 13 currentFacingMode = 'environment' 14 } 15 16 startCamera(currentFacingMode); 17 })

Passo 4: Gravar um vídeo

Para gravar um vídeo vamos precisar de um objeto chamado MediaRecorder ele será o responsável por obter através do stream gerado da câmera pequenos pedaços que chamamos de chunks que iremos armazenar na memória e quando a gravação terminar juntaremos os chunks em um arquivo único no formato Blob.

Para isso acontecer de forma organizada vamos criar algumas funções para separar cada responsabilidade deste processo.

1const startRecording = () => { 2 btnCamera.classList.toggle('hidden'); 3 btnStopCamera.classList.toggle('hidden'); 4 btnToggleCamera.classList.toggle('hidden'); 5 timer.classList.toggle('hidden'); 6 secondsElapsed = 0; 7 8 mediaRecorder = new MediaRecorder(streamCamera, { 9 mimeType: 'video/webm;codecs=vp8' 10 }); 11 12 const chunks = []; 13 mediaRecorder.ondataavailable = (event) => { 14 chunks.push(event.data); 15 } 16 17 mediaRecorder.onstop = () => { 18 const blob = new Blob(chunks, { type: 'video/mp4' }); 19 const urlPreview = URL.createObjectURL(blob); 20 previewRecorded.src = urlPreview; 21 22 photoPreviewContainer.classList.replace('hidden', 'flex'); 23 videoPreviewContainer.classList.toggle('hidden'); 24 25 mediaRecorder = null; 26 stopTimer(); 27 } 28 29 const CHUNK_SIZE = 1000; // 1 seg 30 mediaRecorder.start(CHUNK_SIZE); 31 startTimer(); 32 }

A função principal é a startRecording vamos observar cada parte para entender o que está acontecendo.

Linhas 2-5: Estamos removendo da tela o botão de gravação e de troca de câmera (já que durante a gravação não é permitido trocar entre as câmeras)

Linha 6: Estamos zerando o contador do vídeo.

Linhas 8-10: Instanciamos um novo objeto MediaRecorder passando o streamCamera (nosso stream que está inicializado) e o parâmetro de gravação mimeType para que ele saiba que é um vídeo e qual codec de gravação.

Linhas 12-15: Declaramos um array para armazenar os pedaços do video (chunks) e na função logo após estamos adicionando ao array conforme recebemos os pedaços pelo callback ondataavailable do objeto mediaRecorder.

Linhas 17-27: Aqui estamos tratando quando a gravação for parada pelo usuário que é escutada a partir do callback onstop.

Na linha 18 pegamos os chunks e passamos para uma nova instância do objeto Blob com o parâmetro de video/mp4 que será o formato de saída do vídeo, já na linha 19 geramos uma URL local para podermos mostrar na pré-visualização do vídeo setado na linha 20.

Em sequência estamos:

- Habilitando o preview do vídeo gravado (linha 22);

- Escondendo a câmera (linha 23);

- Zerando o objeto mediaRecorder (linha 25);

- Parando o timer (linha 26).

Linhas 29-31: Definimos o tamanho do chunk em 1 segundo, iniciamos a gravação juntamente com o timer.

A função que chama o startRecording é essa:

1 btnCamera.addEventListener('click', () => { 2 startRecording(); 3 });

Para parar de gravar o vídeo iremos usar a função:

1const stopRecording = () => { 2 streamCamera.getTracks().forEach((track) => track.stop()); 3 mediaRecorder.stop(); 4 }

E iremos chamar a função no clique do botão de stop:

1 btnStopCamera.addEventListener('click', () => { 2 stopRecording(); 3 })

Funções que manipulam o timer (iniciar, formatar e parar)

Observação: Estas são apenas funções genéricas geradas pelo chatGPT para formatar os segundos passados no formato 00:00 e atualizar o texto da div do timer.

1 const formatTime = (seconds) => { 2 const minutes = Math.floor((seconds % 3600) / 60).toString().padStart(2, '0'); 3 const secs = (seconds % 60).toString().padStart(2, '0'); 4 return `${minutes}:${secs}`; 5 } 6 7 const startTimer = () => { 8 intervalId = setInterval(() => { 9 secondsElapsed++; 10 timer.textContent = formatTime(secondsElapsed); 11 }, 1000); 12 } 13 14 const stopTimer = () => { 15 timer.textContent = '00:00'; 16 clearInterval(intervalId); 17 }

Após gravar se deu tudo certo, a tela ficará assim:

Pré-visualização do video gravado

Funções utilizadas para manipular a tela e resetar os controles

1 resetControls = () => { 2 btnCamera.classList.remove('hidden'); 3 btnStopCamera.classList.add('hidden'); 4 btnToggleCamera.classList.remove('hidden'); 5 6 timer.classList.add('hidden'); 7 secondsElapsed = 0; 8 stopTimer(); 9 } 10 11 btnCloseDialog.addEventListener('click', () => { 12 dialogCamera.classList.toggle('hidden'); 13 14 resetControls(); 15 stopCamera(); 16 }); 17 18 btnRepeat.addEventListener('click', () => { 19 previewRecorded.src = ''; 20 21 photoPreviewContainer.classList.replace('flex', 'hidden'); 22 videoPreviewContainer.classList.toggle('hidden'); 23 24 resetControls(); 25 startCamera(); 26 }); 27 28 btnOk.addEventListener('click', () => { 29 dialogCamera.classList.toggle('hidden'); 30 photoPreviewContainer.classList.replace('flex', 'hidden'); 31 videoPreviewContainer.classList.toggle('hidden'); 32 33 resetControls(); 34 stopCamera(); 35 });

DICA: É muito importante que você pare a utilização da câmera quando não estiver utilizando para evitar que sua aplicação use muita memória ou tenha problemas futuros ao tentar abrir uma câmera que ficou aberta por engano. Por isso chamamos o stopCamera nas linhas 15 e 34

Código-fonte

Todo código-fonte tanto deste exemplo quanto do tutorial anterior se encontra neste repositório github

Conclusão

Antes de finalizar, gostaria de agradecer à Michelle Corrêa pelo contato e pela dúvida, que inspirou a criação deste post. Espero que o conteúdo tenha sido útil e ajudado a entender melhor como funciona a gravação de vídeos utilizando os recursos do navegador. Muito obrigado a todos!

Caso tenham dúvidas podem deixar nos comentários aqui em baixo ou me mandar em um dos meus contatos deixados na página sobre mim.

Faça login para comentar

Comentários

Ainda não existem comentários aqui 😥
@2024 | Gustavo Oliveira - Todos direitos reservados