Crie seu servidor de Live Streaming privado PART 2
Tags: ffmpeg, nginx, streaming, live
Published: 2020-07-15 17:19:20

Se você chegou aqui de paraquedas e ainda não leu a parte 1 clique [aqui](https://dev.to/cesarpaulomp/crie-seu-servidor-de-live-privado-part1-1g2g) 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](https://www.ffmpeg.org/).

####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](https://www.hostinger.com.br/tutoriais/o-que-e-cdn/).

![CDN](https://dev-to-uploads.s3.amazonaws.com/i/v0w4wdm7d5ha14labkll.jpg)

#####Vamos começar!
Download do código fonte [aqui](https://bitbucket.org/pcmporto/live_streaming_server/src/master/part_1/).

Como root execute os próximos comandos.
```shellscript
# 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
```
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*
```shellscript
#!/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
```
Crie também o *free.sh*
```shellscript
#!/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
```

```
# Torne os scripts executáveis
chmod +x live.sh free.sh 
```

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;
        }
    }
}
```
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
``` 

O segundo arquivo é uma página HTML com o player [Plyr](https://plyr.io/).

*index.html*
```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>
```
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](https://bunnycdn.com/) pela simplicidade de configuração, simplicidade na precificação e API. Você tem 14 dias gratuitos para testar.

![Add Pull Zone](https://dev-to-uploads.s3.amazonaws.com/i/omvdi0tkksixkq0bm4do.jpg)

###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](https://obsproject.com/) e o [Larix Broadcaster](https://play.google.com/store/apps/details?id=com.wmspanel.larix_broadcaster&hl=en). 

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](https://youtu.be/a3xPyf8CnX8) um vídeo com o teste.


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

![File size original](https://dev-to-uploads.s3.amazonaws.com/i/z91j7luus9awiwn04069.jpg)

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

![360p](https://dev-to-uploads.s3.amazonaws.com/i/x02vvzpcs1gokaa1fx3o.jpg)

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

![720p](https://dev-to-uploads.s3.amazonaws.com/i/w15mfg0tq09m28panz8o.jpg)

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.