Executando verificação de segurança...
29

🎥 Estudei APIs nativas do browser e FFMPEG para criar meu próprio OBS

Fala pessoal, tudo certo?

Pra hoje venho compartilhar aqui meu aplicativo gratuito de gravações: Kronos. O app que grava seus videos, organiza em pastas customizadas por você e (se você quiser), automaticamente faz o upload para o Youtube, criando uma playlist com o seu workspace escolhido.

Como ele nasceu e qual dor resolve?

Sempre usei o OBS para gravar reuniões, bugs, demonstração de features, etc. Contudo, a UI não me agradava 100%, então pensei em uma UI/UX do zero.

E eu gostava de organizar em pastas as minhas gravações, pra eu não me perder nos arquivos ou poder sempre consultar quando necessário. Problema disso? Era bem massante e muitas vezes eu apenas desistia de organizar, deixando tudo bagunçado na minha pasta local de videos.

Com isso em mente, me surgiu uma ideia de criar o meu próprio OBS. Pesquisei várias coisas, começando, claro, pelo FFMPEG!

Porém, pasmem, não estou usando ele pra gravar diretamente os vídeos, apenas para pegar informações dos arquivos gravados. E por qual motivo? Bom, vamos la:

APIs nativas do Browser e do Electron

Usei diversas APIs para gravação de vídeo neste projeto, algumas delas, mas não limitadas a, são:

  • mediaDevices
  • AudioContext
  • MediaStream
  • MediaRecorder
  • desktopCapturer

Essas interfaces me ajudam a chegar no meu objetivo principal: Mostrar preview do monitor/janela que a pessoa quiser e gravar video, áudio e microfone. E isso foi uma aventura! Algumas coisas (principalmente de áudio), estavam complicadas de entenderem, mas vou exemplificar aqui com foi feito, bora lá

Como a mágica da gravação acontece? (Com código!)

Pense nisso como montar um set de filmagem com peças de LEGO.

Passo 1: Apontando a Câmera para a Tela

Primeiro, eu peço ao Electron uma lista de telas e janelas. Quando você escolhe uma (screenSourceId), eu uso a API getUserMedia para capturá-la.

// Pede ao navegador para criar um "stream" (fluxo de dados)
// da tela que o usuário escolheu.
const desktopStream = await navigator.mediaDevices.getUserMedia({
  audio: {
    mandatory: {
      // Pede para capturar o áudio que sai dos alto-falantes
      chromeMediaSource: 'desktop'
    }
  },
  video: {
    mandatory: {
      chromeMediaSource: 'desktop',     // Diz que queremos uma fonte de vídeo do desktop
      chromeMediaSourceId: screenSourceId // O ID específico da tela ou janela
    }
  }
});

Resultado: Temos um objeto desktopStream com a faixa de vídeo e a faixa de áudio do seu computador.

Passo 2: Ligando o Microfone

Agora, capturamos a sua voz em um stream separado.

// Pede ao navegador para criar um stream de áudio do microfone
const micStream = await navigator.mediaDevices.getUserMedia({
    audio: true, // Só queremos áudio
    video: false // ...e nada de vídeo desta vez
});

Resultado: Temos um micStream separado, contendo apenas o som da sua voz.

Passo 3: A Mesa de Som Virtual com AudioContext

Aqui vem a parte mais legal: juntar o som da tela com o som do microfone. Faço isso em tempo real.

// Primeiro, separamos a faixa de vídeo original, pois ela não vai mudar
const [videoTrack] = desktopStream.getVideoTracks();

// Agora, a mágica do áudio começa criando um "contexto" de áudio
const audioContext = new AudioContext();

// Criamos um "destino" para o nosso áudio já mixado
const destinationNode = audioContext.createMediaStreamDestination();

// Adicionamos o áudio do desktop como uma fonte no nosso "mixer"
// (se ele existir, claro)
if (desktopStream.getAudioTracks().length > 0) {
    const desktopSource = audioContext.createMediaStreamSource(desktopStream);
    desktopSource.connect(destinationNode);
}

// E também adicionamos o áudio do microfone como outra fonte
const micSource = audioContext.createMediaStreamSource(micStream);
micSource.connect(destinationNode);

// Pegamos a faixa de áudio já mixada do nosso nó de destino
const [mixedAudioTrack] = destinationNode.stream.getAudioTracks();

// Por fim, criamos um novo stream final, combinando:
// a faixa de vídeo original + a nova faixa de áudio mixada
const finalStream = new MediaStream([videoTrack, mixedAudioTrack]);

Resultado: Tenho um finalStream perfeito, com o vídeo da tela e o áudio combinado do sistema e do microfone.

Passo 4: O Botão de Gravar (REC) com MediaRecorder

Com tudo pronto e mixado, entrego o finalStream para o MediaRecorder.

// Cria uma nova instância do gravador com o stream final
const mediaRecorder = new MediaRecorder(finalStream, {
    mimeType: 'video/webm; codecs=vp9,opus'
});

// Uma lista para guardar os "pedaços" do vídeo que serão gravados
const recordedChunks = [];

// Diz ao gravador o que fazer quando ele tiver um novo pedaço de vídeo
mediaRecorder.ondataavailable = (event) => {
  if (event.data.size > 0) {
    // Adiciona o novo pedaço à nossa lista
    recordedChunks.push(event.data);
  }
};

// Aperta o "REC"! A gravação começa agora.
mediaRecorder.start();

Resultado: O MediaRecorder começa a gerar dados de vídeo, que eu vou guardando na lista recordedChunks.

Passo 5: Juntando os Pedaços e Salvando

Quando você para a gravação, eu executo o código final dentro do evento onstop do gravador.

// Para o gravador de forma segura. Quando ele parar, o `onstop` será chamado.
mediaRecorder.stop();

// Define o que acontece assim que a gravação for finalizada
mediaRecorder.onstop = async () => {
    // Junta todos os pedaços de vídeo em um único arquivo em memória (Blob)
    const videoBlob = new Blob(recordedChunks, {
        type: 'video/webm'
    });

    // Converte o Blob para um formato que o Electron consegue manipular
    const videoBuffer = await videoBlob.arrayBuffer();
    
    // Envia o buffer do vídeo para o processo principal do Electron
    // para que ele possa ser salvo no disco do usuário
    await window.video.saveFile(videoBuffer, /* ... infos do vídeo ... */);

    console.log('Vídeo salvo com sucesso!');
};

Resultado Final: Um arquivo .webm é salvo no seu computador. Missão cumprida!

E é isso! Unindo essas peças de código, o Kronos consegue fazer todo o trabalho pesado para você.


Por que não usei o FFMPEG para gravar?

Muitos podem se perguntar: "Se o FFMPEG é tão poderoso, por que não usá-lo para a gravação em si?". A resposta é simples: as APIs nativas que eu escolhi tornaram o processo mais fácil, leve e direto ao ponto.

Aqui estão os principais motivos:

  1. Melhor Experiência de Desenvolvimento (DX): Trabalhar com as APIs mediaDevices e MediaRecorder é como usar blocos de LEGO. Elas são bem documentadas, fáceis de entender e se integram perfeitamente com o JavaScript. Fazer a mesma coisa com FFMPEG exigiria lidar com processos complexos no terminal, linhas de comando enormes e uma curva de aprendizado muito maior.
  2. Compatibilidade por Padrão: A maior vantagem é que essas APIs são parte da web moderna. Isso significa que meu código funciona em Windows, macOS e Linux sem nenhuma alteração. O Electron simplesmente "expõe" essas APIs para o meu aplicativo. Com FFMPEG, eu teria que me preocupar em empacotar binários diferentes para cada sistema operacional, o que é um grande trabalho extra.
  3. Menor Consumo de Recursos: Iniciar uma gravação com as APIs nativas é quase instantâneo e consome menos CPU e memória. O FFMPEG, sendo um processo externo, precisaria ser iniciado, configurado e gerenciado, o que pode ser mais lento e pesado para o sistema, especialmente em computadores menos potentes.
  4. Integração Perfeita com a Interface: Como tudo é JavaScript, eu consigo conectar facilmente a lógica de gravação com os botões, pré-visualizações de vídeo e controles de volume na tela. Mostrar um preview da tela, por exemplo, é trivial com um MediaStream. Com FFMPEG, isso seria um pesadelo para implementar.
  5. Facilidade na Manipulação de Áudio: Como mostrei, a AudioContext me deu um controle incrível sobre as fontes de áudio, permitindo mixar o som do sistema e do microfone em tempo real. Fazer essa mixagem com FFMPEG "ao vivo" durante a gravação é possível, mas infinitamente mais complexo.

Em resumo, enquanto o FFMPEG é uma ferramenta fantástica e indispensável para processar vídeos depois que eles são criados (como para converter formatos ou extrair informações), para a tarefa de capturar e gravar, as APIs nativas foram a minha escolha da vez!

Kronos tem muitas outras features atuais e futuras que eu não falei em extensão aqui (talvez em posts posteriores), mas por hoje, é isso!

Abraços!

Baixem o Kronos em: https://kronosrec.com

Carregando publicação patrocinada...
6

Que publicação sensacional!!! Muito obrigado por trazer os detalhes técnicos aqui para dentro do TabNews.

A landing page está maravilhosa e a única coisa que senti falta foi de prints ou um rápido vídeo inline (pelo menos na versão mobile não apareceu).

2

Obrigado Deschamps!

Poisé, hoje foquei bastante no site desktop, porém mesmo assim ainda falta a demonstração do app em si antes da pessoa baixar, pra ver como funciona, valeu pelo feedback!

-1
4

Parabéns pelo projeto.

Há 2 anos fiz uma solução parecida. No final subia o video para o google drive da pessoa e gerava uma página para que um link fosse enviado para outra pessoa.

Foi aí que descobri que o iOS não roda webm gravado no windows.

Tentei converter no próprio browser para outro formato. Mas por uma restrição de segurança nao conseguia usar uma lib que precisava ativar shared buffer. A única forma de ligar o buffer era restringindo cross-origin para troca de conteúdo entre domínios, o que quebrava o envio ao drive.

Essa restrição existe por um bug nos processadores que é irreparável.

Enfim... Uma aventura!

2
3
1

Obrigado Mauricio!

Cara, gostei da sugestão, vou estudar a implementação. A ideia é ir incrementando esse produto ao longo do tempo e fazer algo grande mesmo, então obrigado pela dica!

1

Ótimo, vou acompanhar.

Utilizo atualmente o OBS Studio para enviar o desktop de uma máquina Virtual Windows para um servidor OvenMediaEngine, mas essa solução ficou bem pesada. Acho que esse seu produto pode aliviar um bocado!

2
2

Poisé, passei uns bons meses trabalhando nele. Entre trabalho, faculdade e outros projetos. Mas com todo esse conhecimento adquirido sei que vai adiantar pra alguém no futuro.

O plano é deixar open-source também, mas ainda quero organizar algumas coisas antes, mas tá sendo prioridade este ponto

2

O OBS é reconhecido pra vídeo ao vivo, embora de pra gravar.

Testei seu app, mas pra mim não rolou, ele crasha toda hora, nem funciona.
Vou passar meu setup:
OS: Linux Ubuntu 24.04.2 LTS
Hardware: Acer Nitro AN515-44, AMD Ryzen™ 7 4800H with Radeon™ Graphics × 16
Mas uso a GTX 1650 mobile para graficos

Seu post ta muito bom. Abraços

2

Cara, que bizarro. Tenho um setup praticamente igual, usando Linux também. Depois me passa algum contato seu pra eu investigar isso ai contigo se tiver um tempo. Ou mandar o problema pela aba de Contato no site, vlw!!

2
1
2
1

Bem objetivo, porém infelizmente esse tipo de software não tem apelo comercial em seu estado tão simples em face a concorrência tão elaborada dos que dominam o segmento.

1

Poisé, ainda está bem no inicio, nem é o objetivo ser comercial, é mais um projeto pessoal que eu gostei de fazer mesmo. Fora que é algo que eu estou usando todos os dias, assim como colegas do meu trabalho, que tem a mesma dor que eu descrevi no post.

Quem sabe no futuro, com mais features, mas hoje o OBS e gigantesco kk

0
1

Opa Julio, tudo certo? Cara, tenho um Mac aqui e consegui testar e rodar nele, mas era M3. Na teoria não deveria mudar nada no m1. Vou ver se consigo testar em um m1 também, valeu pelo feedback!

Se tiver mais informações pra compartilhar, se deu algum erro, ou se o app apenas não aparece, etc., ficaria muito grato!

1

Opá, obrigado pelo retorno Christofer, o que estava acontecendo é que o mac estava barrando por motivos de segurança, consegui rodar ele com o comando:
xattr -d com.apple.quarantine /Users/julio/Downloads/kronos.app

agora ele abriu, começou a gravar mas não consigo parar a gravação, eu clico no botão de STOP e não acontece nada, não da nenhum erro, será que pode ser permissão?

1

Estou investigando aqui, já fiz algumas changes porém não consigo testar 100%. Vou pegar um mac emprestado igual ao seu nos próximos dias.

Obrigado pelo feedback, Julio, vou priorizar esse hotfix!

1

Beleza, mas assim, fiz o teste em uma maquina windows e realmente a ferramenta ficou muito boa, se quiser posso fazer algumas sugestões de melhoria :D

1

Show de bola!

Se quiser montar uma listinha de sugestões sou todo ouvidos, de UI/UX, features, etc.

Tentei focar bem na parte de UX antes de lançar, estudando sobre na internet, é uma área bem legal, mas bem complexa de fazer certo kkkk