DEV Community

loading...
Cover image for Crie seu servidor de Live Streaming privado PART 2

Crie seu servidor de Live Streaming privado PART 2

Paulo Porto
senior software developer
・6 min read

Se você chegou aqui de paraquedas e ainda não leu a parte 1 clique aqui e aproveite.

No artigo anterior, nós criamos um servidor para Live Streaming utilizando apenas o Nginx com o módulo RTMP. Esta configuração é muito básica e, por isso precisamos de mais alguns passos.

Primeiro, utilizaremos a ferramenta FFmpeg para entregar múltiplas resoluções. Assim garantimos um vídeo contínuo e com qualidade ajustável de acordo com a banda de internet do cliente.

Segundo, para alcançar o mundo inteiro, precisamos da ajuda de um CDN(Content Delivery Network) para entregar a Live.

O que é ffmpeg?

É uma ferramenta open-source composta por diversas bibliotecas para conversão, compactação e edição. Saiba mais clicando aqui.

Porque precisamos de um CDN?

O CDN tem inicialmente o trabalho de reduzir a distância entre seu público e o seu conteúdo. Ele replica seus arquivos em diversos servidores localizados em pontos estratégicos pelo mundo. Então, quando o usuário solicita seu vídeo, ele não acessa o seu servidor e o sim um dos servidores do CDN que está mais próximo a ele.
Veja mais sobre aqui.

CDN

Vamos começar!

Download do código fonte aqui.

Como root execute os próximos comandos.

# Instale a dependência do H.264/MPEG-4 AVC encoder 
apt install -y libx264-dev

# Crie pastas para guardar os arquivos dos videos e scripts.
mkdir -p /var/nginx/www/hls/p360
mkdir -p /var/nginx/www/hls/p720
mkdir -p /var/nginx/scripts

# Baixe a biblioteca do codec H.264/MPEG 4
apt install -y libx264-dev
# Vá para a pasta tmp
cd /tmp

# Baixe o ffmpeg e o descompacte
wget http://ffmpeg.org/releases/ffmpeg-4.2.2.tar.gz
tar -zxvf ffmpeg-4.2.2.tar.gz

# Entre na pasta descompactada
cd ffmpeg-4.2.2

# Execute estes passos para instalar o ffmpeg
./configure --disable-x86asm --enable-gpl --enable-libx264
make && make install && make distclean
Enter fullscreen mode Exit fullscreen mode

O módulo RTMP pode ser configurado para executar um shell script no momento em que a transmissão é iniciada. Com esse gatilho iniciaremos o FFmpeg para começar a transcodificar.

Vá até a pasta /var/nginx/scripts.

Agora crie o arquivo live.sh

#!/bin/bash

on_die () {
# Mata o processo do FFmpeg para liberar o
# recurso para uma nova transmissão futura.
  pkill -KILL -P $$
# Executa um script adicional para apagar arquivos
# não mais necessários
  sh $SH_PATH/free.sh  $HLS_OUT_PATH/p360
  sh $SH_PATH/free.sh  $HLS_OUT_PATH/p720
}
# Cria um gatilho para executar o método on_die quando
# o script for encerrado
trap 'on_die' TERM

# Variáveis com o caminho das as pastas
# para ajuda a simplificar o script
SH_PATH='/var/nginx/scripts'
HLS_OUT_PATH='/var/nginx/www/hls'
FFMPEG_LOG_OUTPUT='var/nginx/logs/fflog.txt'

# O comando entrega duas resoluções. Uma 360p e outra 720p.
# O objetivo é entregar um vídeo contínuo e que atenda
# a banda de internet do seu publico.
ffmpeg -re -i rtmp://localhost:1935/$1/$2 -async 1 -vsync -1 \
    -filter_complex "[v:0]split=2[360p][720p];[360p]scale=w=640:h=360[p360];[720p]scale=w=1280:h=720[p720]" \
    -map [p360] -map 0:a -preset veryfast -c:v libx264 -x264opts keyint=48:no-scenecut -sws_flags bilinear -pix_fmt yuv420p -tune zerolatency -c:a aac \
    -r 24 -crf 23 -profile:v main -b:v 800K -b:a 64k -ar 44100 -maxrate 864k \
    -f hls -hls_time 4 -start_number 0 -hls_list_size 10 -hls_flags delete_segments \
    -hls_segment_filename $HLS_OUT_PATH/p360/segment_%03d.ts $HLS_OUT_PATH/p360/master.m3u8 \
    -map [p720] -map 0:a -preset veryfast -c:v libx264 -x264opts keyint=48:no-scenecut -sws_flags bilinear -pix_fmt yuv420p -tune zerolatency -c:a aac \
    -r 24 -crf 23 -profile:v main -b:v 1500k -b:a 96k -ar 44100 -maxrate 1596k  \
    -f hls -hls_time 4 -start_number 0 -hls_list_size 10 -hls_flags delete_segments \
    -hls_segment_filename $HLS_OUT_PATH/p720/segment_%03d.ts $HLS_OUT_PATH/p720/master.m3u8 2> $FFMPEG_LOG_OUTPUT &
wait
Enter fullscreen mode Exit fullscreen mode

Crie também o free.sh

#!/bin/bash

# O FFmpeg está configurado para apagar automaticamente os arquivos de .ts não utilizados. 
# Porém, mesmo com essa configuração ainda fica algum lixo.
cd $1

mkdir tmp

cat master.m3u8 | grep ts | xargs -I {} cp {} tmp/
rm -rf *.ts
cp tmp/* ./

rm -rf tmp
Enter fullscreen mode Exit fullscreen mode
# Torne os scripts executáveis
chmod +x live.sh free.sh 
Enter fullscreen mode Exit fullscreen mode

Agora o passo mais importante! Precisamos alterar o nginx.conf na pasta /usr/local/nginx/conf/ para utilizar os scripts que criamos.

nginx.conf

user nginx nginx;
worker_processes auto;
worker_rlimit_nofile 100000;

error_log  /var/nginx/logs/error.log;

events {
    worker_connections 4000;
    multi_accept on;
}

rtmp {
    server {
        listen 1935;
        ping 30s;
        chunk_size 4096;

        application stream {
            live on;

            record all;
            record_path /var/nginx/rec;
            record_suffix -%d-%b-%y-%T.flv;
            record_unique off;
            # Executa o script live.sh quando um cliente conecta para transmitir
            exec bash /var/nginx/scripts/live.sh $app $name;
            # Quando a transmissão encerra o nginx envia o sinal de "terminate" para o script.
            exec_kill_signal term;
        }
    }
}

http {

    default_type application/octet-stream;
    gzip on;

    server {
        listen 80 default_server;
        root /var/nginx/www;
        location /hls {
            types {
                application/vnd.apple.mpegurl m3u8;
                video/mp2t ts;
            }
            add_header Cache-Control no-cache;
        }

        location / {
                add_header Cache-Control no-cache;
                index index.html;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Para finalizar a configuração crie mais dois arquivos na pasta /var/nginx/www.

O primeiro arquivo master.m3u8 contém o caminho para as duas resoluções que estamos disponibilizando.

master.m3u8

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
p360/master.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720
p720/master.m3u8
Enter fullscreen mode Exit fullscreen mode

O segundo arquivo é uma página HTML com o player Plyr.

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>HLS Player</title>
    <link rel="stylesheet" href="https://cdn.plyr.io/3.6.2/plyr.css" />
  </head>
  <body>
    <style>
      body {
        background-color: black;
        padding: 0;
        margin: 0;
      }
      .center {
        margin: auto;
        width: 50%;
      }
    </style>
    <div class="center" >
      <video preload="none" id="player"  autoplay controls crossorigin data-plyr-config='{ "title": "Example Title", "settings": ["quality"] }'></video>
    </div>
    <script src="https://cdn.plyr.io/3.6.2/plyr.js"></script>
    <script src="https://cdn.jsdelivr.net/hls.js/latest/hls.js"></script>
    <script>
(function () {
  var video = document.querySelector('#player');
  const defaultOptions = {};

  if (Hls.isSupported()) {
    var hls = new Hls();
    hls.loadSource('http://super-live-demo.b-cdn.net/hls/master.m3u8');
    hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
        const availableQualities = hls.levels.map((l) => l.height)

        defaultOptions.quality = {
          default: availableQualities[0],
          options: availableQualities,
          forced: true,        
          onChange: (e) => updateQuality(e),
        }

        const player = new Plyr(video, defaultOptions);
    });
    hls.attachMedia(video);
    window.hls = hls;
  } else {
    const player = new Plyr(video, defaultOptions);
  }

  function updateQuality(newQuality) {
    window.hls.levels.forEach((level, levelIndex) => {
      if (level.height === newQuality) {
        console.log("Found quality match with " + newQuality);
        window.hls.currentLevel = levelIndex;
      }
    });
  }

})();
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Execute o comando chown nginx:nginx -R /var/nginx/* como root e entregue ao usuário do nginx o controle de todas as pastas e arquivos que criamos.

Finalize com o comando Execute "systemctl reload nginx.service" para reiniciar o nginx.

CDN

Para este artigo escolhi o BunnyCDN pela simplicidade de configuração, simplicidade na precificação e API. Você tem 14 dias gratuitos para testar.

Add Pull Zone

TESTES!

Abra seu app de Streaming favorito e o configure com o endereço rtmp://YOUR_SERVER_ADDRESS:1935/stream/{SOME_KEY}. Nesta configuração somente uma pessoa pode transmitir por vez.

Caso você não tenha nenhum app favorito, deixo duas recomendações.
O OBS Studio e o Larix Broadcaster.

E para assistir, basta acessar a URL do seu servidor ou a URL do CDN no navegador (Ex: http://YOUR_SERVER_ADDRESS).

Como de costume, deixo aqui um vídeo com o teste.

RESULTADOS

Na imagem a seguir temos os arquivos .ts gerados pelo módulo RTMP.

File size original

Nesta imagem temos os arquivos gerados pelo FFmpeg na resolução 360p.

360p

Agora também, temos os arquivos gerados pelo FFmpeg na resolução 720p.

720p

Diante deste teste, entendo que comparando os novos arquivos produzidos pelo FFmpeg com os originais (do módulo RTMP), é notável a diferença de tamanho entre eles. Portanto, quando você entrega arquivos menores, você economiza dinheiro no tráfego de internet e seu público recebe uma Live mais estável, pois com um arquivo menor se usa menos banda larga. Além disso, com o uso do CDN a estabilidade aumenta porque seu usuário acessa o vídeo de servidores bem preparados para distribuição de arquivos aliviando esta carga do seu servidor.

Discussion (12)

Collapse
deniscesar profile image
Denis César

Parabéns Paulo muito bom o artigo.

Sabe me dizer em média a quantidade de espectadores que essa configuração sugerida por você suportaria?

O CDN seria apenas para distribuir o conteúdo estático do site ou estou enganado, seria para distribuir os arquivos do stream?

Collapse
cesarpaulomp profile image
Paulo Porto Author

Seria para distribuir os arquivos estáticos e os arquivos do stream.

Collapse
broascaster profile image
Alexandre

Muito fera hein Paulo, parabéns mesmo ! Agora aquela pergunta capciosa:
Conhece um jeito de fazer tudo isso e gerar a saída em LL-HLS fazendo a entrega de streaming também neste NGINX opensource ? Pq normalmente ele só vai suportar o HLS default (latência é muito muito mais alta). Parece que NGINX+ suporta mas é caríssimo, preciso de algo opensource mesmo. Estou aqui varando a madruga buscando a solução...Se souber, compartilha com a galera pq vai ter um utilidade incrível.
Obrigado.

Collapse
cesarpaulomp profile image
Paulo Porto Author

Da uma olhada no ffmpeg. Provavelmente nele vc consegue configurar esse formato na saída.

Collapse
norato profile image
Felipe Norato Lacerda

To querendo validar uma ideia, mas comecei a pesquisar sobre isso agora. tenho como colocar uma autenticaçao para que impeça que alguém capture a url da transmissão? na real estou bem perdido

Collapse
cesarpaulomp profile image
Paulo Porto Author

Você pode criar um token e validar ele no nginx.

Collapse
alexlealgalvao profile image
alexlealgalvao

Olá Amigo, parabens pelo arquivo. Bem detalhado.
é possivel criar uma interface web onde cria outros streaming e tambem envio para plataformas de rede social?

Collapse
cesarpaulomp profile image
Paulo Porto Author

Plataformas de rede social como Facebook já tem esse serviço. Você precisa de apenas softwares como OBS Studio para fazer as lives.

Sobre a interface web você teria que criar.

Collapse
pauloizidorio profile image
PI.Dev

ótimo artigo, está de parabéns Paulo Porto!

Paulo,
vc sabe informa qual seria o tamanho do arquivo gerado após 1h hora de live ?

Collapse
cesarpaulomp profile image
Paulo Porto Author

Opa, boa noite. Isso varia com a resolução + qualidade de áudio.

Collapse
ploquets profile image
ploquets

Eu nao consigo assistir... no seu index tem um super-live-demo.b-cdn.net/hls/mast... e eu substitui pro meu.... mas da erro
o master eu tenho que substituir pelo key ?

Collapse
cesarpaulomp profile image
Paulo Porto Author • Edited

Desculpe a demora. Você conseguiu assistir direto no IP do seu servidor? Nesta configuração, a key é necessária para transmitir, porém, não para assistir.