aau-zid / BigBlueButton-liveStreaming

Streams a given BBB Meeting to an RTMP Server.
GNU General Public License v3.0
201 stars 160 forks source link

BigBlueButton-liveStreaming - Installation step without docker and HowTo install your own NGINX RTMP-HLS Live Streaming server #83

Closed aguerson closed 4 years ago

aguerson commented 4 years ago

OK I did it !

Maybe you do not want to send your Live on Youtube and you want to stream yourself the live.

You have just to replace hostname.domain by your own.

To reproduce

0) create a CT from an OpenVZ server with ubuntu 18.04 LTS template ( or a lxd container ? ) in a moderate server ( Intel(R) Xeon(R) CPU X5690 @ 3.47GHz / 16G RAM )

1) create bigbluebutton user and customize it

adduser bigbluebutton

vim /home/bigbluebutton/.bashrc
...
HISTTIMEFORMAT="%F %T : "
HISTSIZE=1000000
HISTFILESIZE=1000000
...
export LANG="en_US.UTF-8"
cd /opt/bigbluebutton/bbb-live

2) create sub dir mkdir -p /opt/bigbluebutton/bbb-live

3) change rights

chown -R bigbluebutton:bigbluebutton /opt/bigbluebutton/bbb-live
chmod 1770 /opt/bigbluebutton/bbb-live

4) you need to install the mandatory packages

echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome-unstable.list

wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -

apt-get update

apt-get install -y google-chrome-stable language-pack-en language-pack-fr software-properties-common tzdata 

add-apt-repository ppa:jonathonf/ffmpeg-4

apt-get update

apt-get install -y python3-pip python3-dev xvfb fluxbox ffmpeg dbus-x11 libasound2 libasound2-plugins alsa-utils alsa-oss pulseaudio pulseaudio-utils

apt-get remove --purge nginx nginx-common nginx-core

apt-get remove --purge apache2*

cd /usr/src

wget http://nginx.org/keys/nginx_signing.key

apt-key add nginx_signing.key

vim /etc/apt/sources.list.d/nginx.list
deb http://nginx.org/packages/ubuntu/ bionic nginx
deb-src http://nginx.org/packages/ubuntu/ bionic nginx
deb http://nginx.org/packages/mainline/ubuntu/ bionic nginx
deb-src http://nginx.org/packages/mainline/ubuntu/ bionic nginx
apt-get update

apt-get install -t bionic nginx

5) Install BigBlueButton-liveStreaming


ln -s /usr/bin/python3 /usr/local/bin/python

ln -snf /usr/share/zoneinfo/Europe/Paris /etc/localtime

echo "Europe/Paris" /etc/timezone

su - bigbluebutton

git clone git://github.com/aau-zid/BigBlueButton-liveStreaming.git /opt/bigbluebutton/bbb-live
google-chrome --product-version | grep -o "[^\.]*\.[^\.]*\.[^\.]*"
85.0.4183
curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_85.0.4183"
85.0.4183.87
wget -q --continue "http://chromedriver.storage.googleapis.com/85.0.4183.87/chromedriver_linux64.zip"

unzip chromedriver_linux64.zip

chown -R bigbluebutton:bigbluebutton chromedriver

( I have a doubt here between the three commands; I have it in my history... )


pip3 install --upgrade pip
pip install --no-cache-dir -r py_requirements.txt
pip3 install --no-cache-dir --upgrade -r py_requirements.txt

6) Customize BigBlueButton-liveStreaming

vim /opt/bigbluebutton/bbb-live/startStream.sh

#!/bin/sh

. /opt/bigbluebutton/bbb-live/bbb-live-config

STREAM_MEETING="";
if [ "${BBB_STREAM_URL}" != "" ]
then
   STREAM_MEETING="-l -t ${BBB_STREAM_URL}";
fi

DOWNLOAD_MEETING="";
if [ "${BBB_DOWNLOAD_MEETING}" = "true" ]
then
   DOWNLOAD_MEETING="-d";
fi

SHOW_CHAT="";
if [ "${BBB_SHOW_CHAT}" = "true" ]
then
   SHOW_CHAT="-c";
fi

INTRO="";
if [ "${BBB_INTRO}" != "" ]
then
   INTRO="-I ${BBB_INTRO}";
fi

BEGIN_INTRO="";
if [ "${BBB_BEGIN_INTRO_AT}" != "" ]
then
   BEGIN_INTRO="-B ${BBB_BEGIN_INTRO_AT}";
fi

END_INTRO="";
if [ "${BBB_END_INTRO_AT}" != "" ]
then
   END_INTRO="-E ${BBB_END_INTRO_AT}";
fi

START_MEETING="";
if [ "${BBB_START_MEETING}" != "" ]
then
   START_MEETING="-S";
fi

ATTENDEE_PASSWORD="";
if [ "${BBB_ATTENDEE_PASSWORD}" != "" ]
then
   ATTENDEE_PASSWORD="-A \"${BBB_ATTENDEE_PASSWORD}\"";
fi

MODERATOR_PASSWORD="";
if [ "${BBB_MODERATOR_PASSWORD}" != "" ]
then
   MODERATOR_PASSWORD="-M \"${BBB_MODERATOR_PASSWORD}\"";
fi

MEETING_TITLE="";
if [ "${BBB_MEETING_TITLE}" != "" ]
then
   MEETING_TITLE="-T \"${BBB_MEETING_TITLE}\"";
fi

if [ "${BBB_ENABLE_CHAT}" = "true" ]
then
   xvfb-run -n 133 --server-args="-screen 0 1920x1080x24" python3 chat.py -s ${BBB_URL} -p ${BBB_SECRET} -i "${BBB_MEETING_ID}" -r ${BBB_REDIS_HOST} -u "${BBB_CHAT_NAME}" -c ${BBB_REDIS_CHANNEL} $START_MEETING "$ATTENDEE_PASSWORD" "$MODERATOR_PASSWORD" -T "$MEETING_TITLE" &
   sleep 10
fi

xvfb-run -n 122 --server-args="-screen 0 1920x1080x24" python3 stream.py -s ${BBB_URL} -p ${BBB_SECRET} -i "${BBB_MEETING_ID}" -u "${BBB_USER_NAME}" ${SHOW_CHAT} $START_MEETING $ATTENDEE_PASSWORD $MODERATOR_PASSWORD $MEETING_TITLE $STREAM_MEETING $INTRO $BEGIN_INTRO $END_INTRO $DOWNLOAD_MEETING

vim /opt/bigbluebutton/bbb-live/launch-pulseaudio.sh

#!/bin/sh

pulseaudio --start -vvv --disallow-exit --log-target=syslog --high-priority --exit-idle-time=-1 --daemonize

exec "$@"

vim /opt/bigbluebutton/bbb-live/bbb-live-config

# Basic config
BBB_AS_MODERATOR=false
BBB_USER_NAME=Live
BBB_SHOW_CHAT=false
#BBB_SHOW_CHAT=true
BBB_ENABLE_CHAT=false
BBB_REDIS_HOST=redis
BBB_REDIS_CHANNEL=chat

# BigBlueButton Server url:
BBB_URL="https://hostname.domain/bigbluebutton/api"

# BigBlueButton secret:
# bbb-conf --secret
BBB_SECRET="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"

# BigBlueButton meetingID:
BBB_MEETING_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# start meeting (optional):
BBB_START_MEETING="false"

# attendee password (optional - has to be set to the attendee password of moodle/greenlight or any other frontend to allow joining via their links):
#BBB_ATTENDEE_PASSWORD=

# moderator password (optional - has to be set to the moderator password of moodle/greenlight or any other frontend to allow joining via their links):
#BBB_MODERATOR_PASSWORD=

# meeting title (optional):
#BBB_MEETING_TITLE="bbb-live-test"

# download / save BigBlueButton meeting
BBB_DOWNLOAD_MEETING="false"

# play intro file (can be a local file in videodata folder e.g. /video/intro.mp4 or a url of a mediastream e.g. https://my.intro.stream)
#BBB_INTRO="false"

# begin the intro at position (optional, e.g. 00:00:05)
#BBB_BEGIN_INTRO_AT="04:40"

# end intro after (optional, e.g. 01:00:00 - after one hour)
#BBB_END_INTRO_AT=

# Media server url:
# test ok
#BBB_STREAM_URL="rtmp://hostname.domain:1935/stream/test"
# test 2
BBB_STREAM_URL="rtmp://hostname.domain:1935/hls/conf"

# Timezone (default: Europe/Paris):
TZ="Europe/Paris"

chmod +x /opt/bigbluebutton/bbb-live/launch-pulseaudio.sh /opt/bigbluebutton/bbb-live/startStream.sh

6) Install RTMP-HLS Live Streaming server

mkdir  /opt/bigbluebutton/bbb-live/videos
chmod 755 /opt/bigbluebutton/bbb-live/videos
chown bigbluebutton:bigbluebutton /opt/bigbluebutton/bbb-live/videos

ln -s /opt/bigbluebutton/bbb-live/videos/ /etc/nginx/html/

mkdir -p /etc/nginx/ssl

mkdir -p /etc/nginx/html

mkdir -p /etc/nginx/video.js

mkdir /tmp/hls

openssl dhparam -out /etc/nginx/ssl/dhp-4096.pem 4096

cd /usr/src

wget http://hg.nginx.org/pkg-oss/archive/tip.tar.gz
tar -zxvf tip.tar.gz
cd pkg-oss-e34c5e4a661f

( The version of nginx is very important here !)

dpkg -l |grep nginx
ii  nginx                             1.19.1-1~bionic                       amd64        high performance web server
ii  nginx-module-rtmp                 1.19.1-1~bionic                       amd64        nginx rtmp dynamic module

./build_module.sh -v 1.19.1 https://github.com/arut/nginx-rtmp-module.git

cd /root

mv debuild debuild_v1

cp /root/debuild_v1/nginx-1.19.1/debian/debuild-module-rtmp/nginx-module-rtmp_1.19.1-1~bionic_amd64.deb /usr/src/

cd /usr/src/

dpkg -i nginx-module-rtmp_1.19.1-1~bionic_amd64.deb 
ls -al /etc/nginx/modules
lrwxrwxrwx 1 root root 22 Jul  7 17:52 /etc/nginx/modules -> /usr/lib/nginx/modules
ls -al /usr/lib/nginx/modules
total 592
drwxr-xr-x 2 root root   4096 Jul 27 21:29 .
drwxr-xr-x 3 root root   4096 Jul 27 21:13 ..
-rw-r--r-- 1 root root 317944 Jul 27 21:26 ngx_rtmp_module-debug.so
-rw-r--r-- 1 root root 276984 Jul 27 21:26 ngx_rtmp_module.so

vim /etc/nginx/nginx.conf


user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log debug;
pid        /var/run/nginx.pid;

load_module modules/ngx_rtmp_module.so;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

#    sendfile        on;
#    #tcp_nopush     on;

    # https://docs.peer5.com/guides/setting-up-hls-live-streaming-server-using-nginx/
    sendfile off;
    tcp_nopush on;
    aio on;
    directio 512;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;

}

# rtmp nginx module
# https://github.com/arut/nginx-rtmp-module

# rtmp_auto_push on;

rtmp {

        server {

                listen 1935;
                chunk_size 4000;

                application stream {
                        # enable live streaming
                        live on;
                        record off;
                }

                application hls {
                        live on;
                        #interleave on;

                        # https://docs.peer5.com/guides/setting-up-hls-live-streaming-server-using-nginx/
                        # https://www.nginx.com/blog/video-streaming-for-remote-learning-with-nginx/

                        hls on;
                        hls_path /tmp/hls;
                        hls_fragment 3s;
                        hls_playlist_length 60;

                        # disable consuming the stream from nginx as rtmp
                        deny play all;

                }

                application dash {
                        live on;

                        dash on;
                        dash_path /tmp/dash;
                        dash_fragment 15s;
                }

        }
}
$ls -al /etc/nginx/conf.d/

-rw-r--r-- 1 root root 2662 Sep  8 13:44 bbb-live.conf
-rw-r--r-- 1 root root  311 Jul 27 21:36 blacklist.conf

vim /etc/nginx/conf.d/bbb-live.conf

#
# hostname.domain
#

server {
        listen   80;
        server_name  hostname.domain;
        return 301 https://hostname.domain$request_uri;
}

server {

     listen 443 ssl;
     server_name hostname.domain;

     ssl_certificate_key /etc/ssl/private/your.key;
     ssl_certificate /etc/ssl/certs/your-chain.pem;
     ssl_session_cache shared:SSL:10m;
     ssl_session_timeout 10m;
     ssl_protocols TLSv1.2;
     ssl_ciphers "ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS:!AES256";
     ssl_prefer_server_ciphers on;
     ssl_dhparam /etc/nginx/ssl/dhp-4096.pem;

     # 2020-07-07T12:00:00+0100 = Suppression des entrées à cette date
     # source : https://developers.google.com/search/reference/robots_meta_tag
     add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive, notranslate, noimageindex, unavailable_after: 2020-07-07T12:00:00+0100";

     root /etc/nginx/html;

     access_log  /var/log/nginx/bbb-live.access.log;

        error_page   401 403 404 /byebye.html;
        error_page   500 502 503 504  /byebye.html;

        location = /byebye.html {
                return 301 https://hostname.domain;
        }

        types {
                text/html html;
                application/dash+xml mpd;
                application/vnd.apple.mpegurl m3u8;
                video/mp2t ts;
                video/x-flv flv;
                video/mp4 mp4;
                video/webm webm;
        }

        location / {
                index index.html;
                include       /etc/nginx/mime.types;

                # Disable cache
                add_header 'Cache-Control' 'no-cache';

                # CORS setup
                add_header 'Access-Control-Allow-Origin' '*' always;
                add_header 'Access-Control-Expose-Headers' 'Content-Length';

                # allow CORS preflight requests
                if ($request_method = 'OPTIONS') {
                        add_header 'Access-Control-Allow-Origin' '*';
                        add_header 'Access-Control-Max-Age' 1728000;
                        add_header 'Content-Type' 'text/plain charset=UTF-8';
                        add_header 'Content-Length' 0;
                        return 204;
                }

        }

        location /hls {
                root /tmp;
        }

        #location /dash {
        #         root /tmp;
        #}

        location ~\.mp4$ {
            #root /opt/bigbluebutton/bbb-live/videos;
            mp4;
            mp4_buffer_size     4M;
            mp4_max_buffer_size 10M;
        }

        # Protection contre les visiteurs de moteurs de recherche
        if ($bad_referer) {
                return 301 https://$bad_referer;
        }

}

vim /etc/nginx/conf.d/blacklist.conf

map $http_referer $bad_referer {
    hostnames;

    default                           0;

    # Put regexes for undesired referers here
    "~google.fr"             1;
    "~google.com"            1;
    "~webcache.googleusercontent.com" 1;
    "~bing.fr"               1;
    "~bing.com"              1;
    # etc...

}

( you have to copy favicon.ico from a bbb server )

$ls -al /etc/nginx/html/

-r--r--r-T 1 root root       61 Jul 27 21:33 byebye.html
-rw-r--r-- 1 root root      194 Jul 28 01:32 crossdomain.xml
-r--r--r-T 1 root root     5430 Jul 27 21:33 favicon.ico
-r--r--r-T 1 root root       61 Jul 28 01:46 index.html
-rw-r--r-- 1 root root      642 Sep  8 01:20 live.html
-r--r--r-T 1 root root       27 Jul 27 21:33 robots.txt
drwxr-xr-x 7 root root     4096 Jul 28 01:17 video.js
lrwxrwxrwx 1 root root       35 Sep  7 23:32 videos -> /opt/bigbluebutton/bbb-live/videos/

vim /etc/nginx/html/byebye.html

<!DOCTYPE html>
<html>
<head>
</head>
<body>
</body>
</html>

vim /etc/nginx/html/crossdomain.xml

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*"/>
</cross-domain-policy>

vim /etc/nginx/html/index.html

<!DOCTYPE html>
<html>
<head>
</head>
<body>
</body>
</html>

vim /etc/nginx/html/robots.txt

User-agent: *
Disallow: /

vim /etc/nginx/html/live.html

<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>BBB Live</title>
<link href="https://hostname.domain/video.js/video-js.css" rel="stylesheet" />
</head>
<body>
        <center>

        <video id="bbb-live" class="video-js" controls preload="auto" width="1280" height="720">
                <source src="https://hostname.domain/hls/conf.m3u8" type="application/x-mpegURL"></source>
        </video>

        <script src='https://hostname.domain/video.js/video.js'> </script>
        <script src='https://hostname.domain/video.js/videojs-contrib-hls.js'> </script>
        <script>
                var player = videojs('bbb-live');
        </script>

        </center>
</body>
</html>
cd /etc/nginx/html/video.js

wget https://github.com/videojs/video.js/releases/download/v7.8.4/video-js-7.8.4.zip

unzip video-js-7.8.4.zip

wget https://github.com/videojs/videojs-contrib-dash/releases/download/v2.11.0/videojs-dash.min.js
wget https://github.com/videojs/videojs-contrib-dash/releases/download/v2.11.0/videojs-dash.js
wget https://github.com/videojs/videojs-contrib-dash/releases/download/v2.11.0/videojs-dash.es.js
wget https://github.com/videojs/videojs-contrib-dash/releases/download/v2.11.0/videojs-dash.cjs.js

wget https://github.com/videojs/videojs-contrib-hls/releases/download/v5.15.0/videojs-contrib-hls.js
wget https://github.com/videojs/videojs-contrib-hls/releases/download/v5.15.0/videojs-contrib-hls.min.js

chmod 1444 /etc/nginx/html/byebye.html /etc/nginx/html/favicon.ico /etc/nginx/html/index.html

7) Restart nginx

systemctl enable nginx.service
systemctl start nginx.service

8) Launch your BBB Live

su - bigbluebutton
./launch-pulseaudio.sh
./startStream.sh

Go to https://hostname.domain/live.html and enjoy !

Just for the fun

put a .mp4 video in /opt/bigbluebutton/bbb-live/videos/ Go to https://hostname.domain/videos/${your_video}.mp4 and enjoy !

It works to play externals videos in BBB ;)

iSamof commented 4 years ago

@aguerson - Thank you for this valuable contribution.

mtsonline commented 4 years ago

@aguerson thanks for this tutorial for a local non docker installation.

please note, that we also provide a docker setup for a streamingserver, if you do not want to stream to other platforms, you can use this: https://github.com/aau-zid/live-streaming-server

aguerson commented 4 years ago

@mtsonline Really cool ;)

Did you intend to adapt the resolution with the bandwith with some javascript tests ?

I found this

exec ffmpeg -i rtmp://localhost/stream/${DOLLAR}name
              -c:a aac -b:a 128k -c:v libx264 -b:v 5000k -g 60 -r 30 -s 1920x1080 -profile:v baseline -preset ultrafast -tune zerolatency -f flv rtmp://localhost/hls/${DOLLAR}name_1080
              -c:a aac -b:a 128k -c:v libx264 -b:v 2500k -g 60 -r 30 -s 1280x720 -profile:v baseline -preset ultrafast -tune zerolatency -f flv rtmp://localhost/hls/${DOLLAR}name_720
              -c:a aac -b:a 128k -c:v libx264 -b:v 1000k -g 60 -r 30 -s 854x480 -profile:v baseline -preset ultrafast -tune zerolatency -f flv rtmp://localhost/hls/${DOLLAR}name_480 ;

You re-encode what you already encode with "BigBlueButton-liveStreaming". Did you have enought ressources on the same host ?

In my case, the BBB conf is encoded one time with BigBlueButton-liveStreaming and the stream to HTTPS with HLS is the same flow. it is just exploded in some pieces with the HLS convertion under /tmp

mtsonline commented 4 years ago

the two projects where developed independently - we already had the video server before we started with the liveStreaming ...

LiveStreaming only sends one resolution to the rtmp server, the streamingServer provides different resolutions as clients are expecting.

If you have improvements to that with the same or a better outcome we would appreciate your pull requests. In both repos, liveStreaming and the server, there are plenty of things that could be developed further :-)

cheers Martin

aguerson commented 4 years ago

;)

I think you have the good approache with the project. For me, it not possible to re-encode 3 times in parallel the same flux. I would prefer to explode one time the pieces for the 3 resolutions, But I don't know if it is possible.

Shaverdoff commented 3 years ago

hello all!

i cant understaund ....WIDM!! root@bbb:/home/bigbluebutton/bbb-live# ./startStream.sh INFO:root:Starting browser!! Traceback (most recent call last): File "stream.py", line 258, in set_up() File "stream.py", line 123, in set_up browser = webdriver.Chrome(executable_path='./chromedriver',options=options) File "/usr/local/lib/python3.6/dist-packages/selenium/webdriver/chrome/webdriver.py", line 81, in init desired_capabilities=desired_capabilities) File "/usr/local/lib/python3.6/dist-packages/selenium/webdriver/remote/webdriver.py", line 157, in init self.start_session(capabilities, browser_profile) File "/usr/local/lib/python3.6/dist-packages/selenium/webdriver/remote/webdriver.py", line 252, in start_session response = self.execute(Command.NEW_SESSION, parameters) File "/usr/local/lib/python3.6/dist-packages/selenium/webdriver/remote/webdriver.py", line 321, in execute self.error_handler.check_response(response) File "/usr/local/lib/python3.6/dist-packages/selenium/webdriver/remote/errorhandler.py", line 242, in check_response raise exception_class(message, screen, stacktrace) selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 85

i run startstream.sh in remotely ssh server, opening rooms and .. get that in console& ofcouse no any chrome not installed on remote server. is purly servers ssh