From 9ecec3b12f87dd79e5f283c26ef8616bea712452 Mon Sep 17 00:00:00 2001
From: Michael Koscher <michael.koscher@aau.at>
Date: Fri, 8 May 2020 14:23:16 +0200
Subject: [PATCH] add Chat functionality and smaller improvments

---
 .DS_Store                                     | Bin 0 -> 8196 bytes
 .gitignore                                    |   4 +
 Dockerfile                                    |   5 +
 README.md                                     |  11 ++-
 chat.py                                       |  92 ++++++++++++++++++
 examples/.DS_Store                            | Bin 0 -> 6148 bytes
 examples/.env.chat_example                    |  16 +++
 .env.example => examples/.env.example         |   0
 examples/docker-compose.yml.chat_example      |  30 ++++++
 .../docker-compose.yml.example                |   0
 examples/example_player_and_chat.php          |  58 +++++++++++
 examples/sendChatMessage.php                  |   8 ++
 startStream.sh                                |  10 +-
 stream.py                                     |  30 +++---
 14 files changed, 242 insertions(+), 22 deletions(-)
 create mode 100644 .DS_Store
 create mode 100644 .gitignore
 create mode 100644 chat.py
 create mode 100644 examples/.DS_Store
 create mode 100644 examples/.env.chat_example
 rename .env.example => examples/.env.example (100%)
 create mode 100644 examples/docker-compose.yml.chat_example
 rename docker-compose.yml.example => examples/docker-compose.yml.example (100%)
 create mode 100644 examples/example_player_and_chat.php
 create mode 100644 examples/sendChatMessage.php

diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..040b436ffbc376e7a8800ad7f0a1384559a3737a
GIT binary patch
literal 8196
zcmZQzU|@7AO)+F(kYHe7;9!8z0^AH(0Z1N%F(jFwBElf^7#IW?Jah7slXCKt7(g7T
z<R~>70;3@?8UmvsFd71*Auy;yfDz&v4jlD9M0S)M4S~@R7?B|WDjyUeZF>d>DBS>|
zK~fBi3=H5d03!nf3oL*bp-q2A1_qE;kQ$Iy5Dn7GzzAZ2%>Zj<V1#OA1b0Ii7#JA@
zz(z4bv@<Y*Z3c;hwKFh+ZDwF#glK1Agxbsq?V&J2v@<Y*ZD(L$1ltZaa+DYifzc2^
z3jt8yorOV<A(bJIp$xVE&M3~nz@YIT48XcT>e3l98A{O9wK6a;a)6cn2dm>^&|?6r
zW5{GkXUJp7XDDJwMbq;cO^*yi5*Wra6f=~7)H5V9<T7M3<T0c(=rJTS<TIo&q%!C-
zl!HynW+-MTU`S+0#%3<V3?#R(GbA%)FeEaRFz7K9FjS(tg25Wihe8Y~4EYSn4A~5+
z3`GpOV0&{J3K;SkiWyQF^cX4`av5^a%vgzLh6tJ&ppXUms1j@r$VVWz6f<O?n#0(N
zW)2sF3zA#X7%~}hu=*A`hBz2f87jabR{&DYP>gB^!!rg3hR;Z$gP}gDytn{5f^#yd
zASbi9#K7P>BNH<VD;qlp2NwqyFIQ}EMt*s4Nn%N9u~TAEG>8|FSdx(hWryVF=fK&C
zNnx3(<?#X{&iQ#IiJ5t+MIhzDnJKABiA6EtnRzMs<xcsfc`3zUbD$C&9Go1S@d6Un
z)h5QqhB^wyCbc>W)#l~~ItnJnX0^4P9O9~mww?*Ol~vU>wRJPWi4I;fFz`e8aB>!S
zj0-Jba?%Zhlk;;6;Hp65U}$P`^Icq^fx$6HVaAUAMu!}+nU$NLLX^6K48nGn1sCPz
z<maV>t4Ib0VNjxFhzDngLWWX?OfV}KoLx&8iW&46N*F58GbA`!BWD6mh9Cx421f=L
z244nO20eych7`1f0ZPQkI{3iZFp;5%p@bnADMRWpU?yBpF@da?6Rfw0Ar+huv1B`N
zQitn+RN$laKT=8@WsZgb9YTN^Vuu8%|L@AcfNT68qH2^J4S~@R7?vTx$l?<0;skAR
zVDllUuMO%?CqN}ZeQ;2BoDtM7hv)+-0d?)cwK*fGF@;bI(h8CTcf}bQ7(iM_2Lhns
MGfItyz(9lm07t<(8UO$Q

literal 0
HcmV?d00001

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..adf7a64
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.env
+*.yml
+*.code-workspace
+*.iml
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index acc2b8b..3bb946e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -32,8 +32,13 @@ RUN apt-get update && \
 
 ENV BBB_AS_MODERATOR false
 ENV BBB_USER_NAME Live
+ENV BBB_CHAT_NAME Chat
+ENV BBB_ENABLE_CHAT false
+ENV BBB_REDIS_HOST redis
+ENV BBB_REDIS_CHANNEL chat
 
 COPY stream.py ./
+COPY chat.py ./
 COPY startStream.sh ./
 COPY docker-entrypoint.sh ./
 
diff --git a/README.md b/README.md
index b2ad981..d726e0d 100644
--- a/README.md
+++ b/README.md
@@ -20,15 +20,20 @@ You need to set some environment variables to run the container.
 
 #### Optional
 * BBB_AS_MODERATOR - if set to "true" the meeting will be joined as moderator
-* BBB_USER_NAME - the Username to join the meeting. (Default: Live)
+* BBB_USER_NAME - the username to join the meeting. (Default: Live)
+
+### Chat options
+* BBB_ENABLE_CHAT - Enable Chat
+* BBB_REDIS_HOST - Set REDIS host (Default: redis)
+* BBB_REDIS_CHANNEL - Set REDIS channel (Default: chat)
+* BBB_CHAT_NAME - the username to join the meeting for chatting. (Default: Chat)
 
 ### Starting liveStreaming
 * wget -O docker-compose.yml https://github.com/aau-zid/BigBlueButton-liveStreaming/raw/master/docker-compose.yml.example
 * (change configuration)
 * docker-compose up -d
-* docker-compose down
+* docker-compose down 
 
 ## Known Limitations
 * You must extract and provide the meetingID, which is not visible within the room.
-* Some unnecessary html elements are not hidden yet.
 * ffmpeg settings to be improved
diff --git a/chat.py b/chat.py
new file mode 100644
index 0000000..e5ee705
--- /dev/null
+++ b/chat.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import sys, argparse, time, logging, os, redis
+from bigbluebutton_api_python import BigBlueButton
+
+from selenium import webdriver
+from selenium.webdriver.common.keys import Keys
+from selenium.webdriver.chrome.options import Options
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.common.by import By
+
+browser = None
+selelnium_timeout = 30
+connect_timeout = 5
+
+logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
+
+parser = argparse.ArgumentParser()
+parser.add_argument("-s","--server", help="Big Blue Button Server URL")
+parser.add_argument("-p","--secret", help="Big Blue Button Secret")
+parser.add_argument("-i","--id", help="Big Blue Button Meeting ID")
+parser.add_argument("-m","--moderator", help="Join the meeting as moderator",action="store_true")
+parser.add_argument("-u","--user", help="Name to join the meeting",default="Live")
+parser.add_argument("-r","--redis", help="Redis hostname",default="redis")
+parser.add_argument("-c","--channel", help="Redis channel",default="chat")
+args = parser.parse_args()
+
+bbb = BigBlueButton(args.server,args.secret)
+
+def set_up():
+    global browser
+
+    options = Options()
+    options.add_argument('--disable-infobars')
+    options.add_argument('--no-sandbox')
+    options.add_argument('--kiosk')
+    options.add_argument('--window-size=1920,1080')
+    options.add_argument('--window-position=0,0')
+    options.add_experimental_option("excludeSwitches", ['enable-automation'])
+    options.add_argument('--incognito')
+    options.add_argument('--shm-size=1gb')
+    options.add_argument('--disable-dev-shm-usage')
+    options.add_argument('--start-fullscreen')
+
+    logging.info('Starting browser to chat!!')
+
+    browser = webdriver.Chrome(executable_path='./chromedriver',options=options)
+
+def bbb_browser():
+    global browser
+
+    logging.info('Open BBB for chat!!')
+    browser.get(get_join_url())
+
+    element = EC.presence_of_element_located((By.CSS_SELECTOR, '[aria-label="Listen only"]'))
+    WebDriverWait(browser, selelnium_timeout).until(element)
+    browser.find_elements_by_css_selector('[aria-label="Listen only"]')[0].click()
+
+    element = EC.invisibility_of_element((By.CSS_SELECTOR, '.ReactModal__Overlay'))
+    WebDriverWait(browser, selelnium_timeout).until(element)
+
+    redis_r = redis.Redis(host=args.redis,charset="utf-8", decode_responses=True)
+    redis_s = redis_r.pubsub()
+    redis_s.psubscribe(**{args.channel:chat_handler})
+    thread = redis_s.run_in_thread(sleep_time=0.001)
+
+def chat_handler(message):
+    global browser
+    browser.find_element_by_id('message-input').send_keys(message['data'])
+    browser.find_elements_by_css_selector('[aria-label="Send message"]')[0].click()
+    logging.info(message['data'])
+
+def get_join_url():
+    minfo = bbb.get_meeting_info(args.id)
+    if args.moderator:
+        pwd = minfo.get_meetinginfo().get_moderatorpw()
+    else:
+        pwd = minfo.get_meetinginfo().get_attendeepw()
+    return bbb.get_join_meeting_url(args.user,args.id,pwd)
+
+def chat():
+    while True:
+        time.sleep(60)
+
+
+while bbb.is_meeting_running(args.id).is_meeting_running() != True:
+    time.sleep(connect_timeout)
+set_up()
+bbb_browser()
+chat()
+browser.quit()
\ No newline at end of file
diff --git a/examples/.DS_Store b/examples/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..061191090bf0ca855938b42988da20a9784a760d
GIT binary patch
literal 6148
zcmZQzU|@7AO)+F(5MW?n;9!8zOq>i@0Z1N%F(jFwB5WY@z-H(%Br{|%Br=pR#6#sq
zslgorptuuc&|^qt$YUsDK#spuh6;v6hFpdMh8%`e&z$_^q@4UDXi{0pz`&sK9}K|W
z<3Tb3pT7GH3=9v!+W!Cl&%nSS&yd29&ydWJ&5+7a#GuQN%#aVZF`uEBA(cUop^_n&
zA%|F3F^Z$PN{UF+@wwO<%}i+q6x9sz;P6Rgs08~Uo*|JTk0FI29-a^M7z!9N7z&WX
z59WUu&A`AQ4E8Tbr!zGF_%ft26f+bvBr>FfLjuDT1_s7fG`E4HVw4ySfzc2c4FMuT
zfDxjGgBwDRlA|Fo8Umvs01p9B`Je!4+cP*o=>`Z5l44+FU;uXk7#SE?V44`g{Qw4#
z97rpO25AM+Agv6HAQspRuvP{}s8&XBHw2^))FlDYVC@WyV4FdFuyzJUu+0n%j1cV%
zj8K~yp*<8vh;{}>h;{}>u<bC{jnbnbFd71|5MYKd1VHt_D+2?r{y#+3C^;GeqaiRX
zLx7RRCD_FYTq$GsAE>Sc)u#ziX;2*us*V{!^)f;XTop4x1{5Wr!l3FNq!mPit71k5
U29Va#h5#&pM(NQI7=RD}04h$0)Bpeg

literal 0
HcmV?d00001

diff --git a/examples/.env.chat_example b/examples/.env.chat_example
new file mode 100644
index 0000000..bc844e9
--- /dev/null
+++ b/examples/.env.chat_example
@@ -0,0 +1,16 @@
+# BigBlueButton Server url:
+BBB_URL=https://your_BigBlueButton_server/bigbluebutton/api
+# BigBlueButton secret:
+BBB_SECRET=your_secret
+# BigBlueButton meetingID:
+BBB_MEETING_ID=your_meetingID
+# Media server url:
+BBB_STREAM_URL=rtmp://media_server_url/stream/stream_key
+# Enable chat functionality
+BBB_ENABLE_CHAT=true
+# Set REDIS host (default: 'redis')
+BBB_REDIS_HOST=redis
+# Set REDIS channel to subscribe (default: 'chat')
+BBB_REDIS_CHANNEL=chat
+# Username for the chat (default: 'Chat')
+BBB_CHAT_NAME=Chat
\ No newline at end of file
diff --git a/.env.example b/examples/.env.example
similarity index 100%
rename from .env.example
rename to examples/.env.example
diff --git a/examples/docker-compose.yml.chat_example b/examples/docker-compose.yml.chat_example
new file mode 100644
index 0000000..db09b67
--- /dev/null
+++ b/examples/docker-compose.yml.chat_example
@@ -0,0 +1,30 @@
+version: "3.3"
+services:
+  redis:
+    image: redis
+    networks:
+     - app-tier
+  bbb-streamer:
+    image: aauzid/bigbluebutton-livestreaming
+    environment:
+        # BigBlueButton Server url:
+      - BBB_URL=https://your_BigBlueButton_server/bigbluebutton/api
+      # BigBlueButton secret:
+      - BBB_SECRET=your_secret
+      # BigBlueButton meetingID:
+      - BBB_MEETING_ID=your_meetingID
+      # Media server url:
+      - BBB_STREAM_URL=rtmp://media_server_url/stream/stream_key
+      # Enable chat functionality
+      -BBB_ENABLE_CHAT=true
+      # Set REDIS host (default: 'redis')
+      -BBB_REDIS_HOST=redis
+      # Set REDIS channel to subscribe (default: 'chat')
+      -BBB_REDIS_CHANNEL=chat
+      # Username for the chat (default: 'Chat')
+      -BBB_CHAT_NAME=Chat
+    networks:
+      - app-tier
+networks:
+  app-tier:
+    driver: bridge
\ No newline at end of file
diff --git a/docker-compose.yml.example b/examples/docker-compose.yml.example
similarity index 100%
rename from docker-compose.yml.example
rename to examples/docker-compose.yml.example
diff --git a/examples/example_player_and_chat.php b/examples/example_player_and_chat.php
new file mode 100644
index 0000000..33a922f
--- /dev/null
+++ b/examples/example_player_and_chat.php
@@ -0,0 +1,58 @@
+<head>
+  <link href="https://vjs.zencdn.net/7.6.5/video-js.css" rel="stylesheet">
+  <script
+  src="https://code.jquery.com/jquery-3.5.1.min.js"
+  integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
+  crossorigin="anonymous"></script>
+
+  <!-- If you'd like to support IE8 (for Video.js versions prior to v7) -->
+
+
+</head>
+
+<body>
+
+    <h1>Live Stream</h1>
+    <div style="float:left; width:49%">
+  <video id='my-video-live' class="video-js vjs-default-skin" width='760' height='400'>
+        <source src="LIVE_STREAM_URL">
+      <p class='vjs-no-js'>
+        To view this video please enable JavaScript, and consider upgrading to a web browser that
+        <a href='http://videojs.com/html5-video-support/' target='_blank'>supports HTML5 video</a>
+      </p>
+    </video>
+    </div>
+    <div id="container" style=" float:right; width:49%">
+    <form method="post" action="" id="contactform">
+      <div class="form-group">
+        <h2 >Send Question</h2>
+        <textarea name="message" rows="15" cols="60" class="form-control" id="message"></textarea>
+      </div>
+      <button type="submit" class="btn btn-primary send-message">Submit</button>
+    </form>
+    </div>
+
+  <script src='https://vjs.zencdn.net/7.6.6/video.js'></script>
+  <script type="application/javascript">
+    
+
+    $(document).ready(function () {
+        $('.send-message').click(function (e) {
+        e.preventDefault();
+        var message = $('#message').val();
+        $.ajax
+            ({
+            type: "POST",
+            url: "sendChatMessage.php",
+            data: { "message": message },
+            success: function (data) {
+                $('#contactform')[0].reset();
+            }
+            });
+        });
+    });
+
+
+    </script>
+
+</body>
\ No newline at end of file
diff --git a/examples/sendChatMessage.php b/examples/sendChatMessage.php
new file mode 100644
index 0000000..2059bcb
--- /dev/null
+++ b/examples/sendChatMessage.php
@@ -0,0 +1,8 @@
+<?php
+
+if(!empty($_POST)) {
+    $message = $_POST['message'];
+    $redis = new Redis();
+    $redis->pconnect('REDIS_HOST'); // REDIS_HOST hast to be the same as in BBB_REDIS_HOST
+    $redis->publish('REDIS_CHANNEL', $message); // REDIS_CHANNEL hast to be the same as in BBB_REDIS_CHANNEL
+    echo "Message published\n";
\ No newline at end of file
diff --git a/startStream.sh b/startStream.sh
index 6c80ee4..7bd8c89 100644
--- a/startStream.sh
+++ b/startStream.sh
@@ -4,6 +4,12 @@ JOIN_AS_MODERATOR="";
 if [ "${BBB_AS_MODERATOR}" = "true" ]
 then
    JOIN_AS_MODERATOR="-m";
-fi    
+fi   
 
-xvfb-run -n 122 --server-args="-screen 0 1280x720x24" python3 stream.py -s ${BBB_URL} -p ${BBB_SECRET} -i ${BBB_MEETING_ID} -t ${BBB_STREAM_URL} -u ${BBB_USER_NAME} $JOIN_AS_MODERATOR;
+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} $JOIN_AS_MODERATOR &
+   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} -t ${BBB_STREAM_URL} -u ${BBB_USER_NAME} $JOIN_AS_MODERATOR;
diff --git a/stream.py b/stream.py
index 9ea2e27..1bea469 100644
--- a/stream.py
+++ b/stream.py
@@ -13,7 +13,8 @@ from selenium.webdriver.support import expected_conditions as EC
 from selenium.webdriver.common.by import By
 
 browser = None
-timeout = 5
+selelnium_timeout = 30
+connect_timeout = 5
 
 logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
 
@@ -32,21 +33,14 @@ def set_up():
     global browser
 
     options = Options()  
-    #options.set_headless(headless=True)
-    #options.add_argument("--headless")
-    #options.add_argument('--enable-logging=stderr')
-    #options.add_argument('--disable-gpu') 
-    #options.add_argument('--enable-usermedia-screen-capturing') 
-    #options.add_argument('--allow-http-screen-capture') 
-    #options.add_argument('--auto-select-desktop-capture-source=bbbrecorder') 
     options.add_argument('--disable-infobars') 
     options.add_argument('--no-sandbox') 
     options.add_argument('--kiosk') 
-    options.add_argument('--window-size=1280,720')
+    options.add_argument('--window-size=1920,1080')
     options.add_argument('--window-position=0,0')
     options.add_experimental_option("excludeSwitches", ['enable-automation']);   
-    #options.add_argument('--shm-size=1gb') 
-    #options.add_argument('--disable-dev-shm-usage') 
+    options.add_argument('--shm-size=1gb') 
+    options.add_argument('--disable-dev-shm-usage') 
     options.add_argument('--start-fullscreen') 
     
     logging.info('Starting browser!!')
@@ -59,13 +53,15 @@ def bbb_browser():
     logging.info('Open BBB and hide elements!!')
     browser.get(get_join_url())
     element = EC.presence_of_element_located((By.CSS_SELECTOR, '[aria-label="Listen only"]'))
-    WebDriverWait(browser, timeout).until(element)
+    WebDriverWait(browser, selelnium_timeout).until(element)
     browser.find_elements_by_css_selector('[aria-label="Listen only"]')[0].click()
 
     element = EC.invisibility_of_element((By.CSS_SELECTOR, '.ReactModal__Overlay'))
-    WebDriverWait(browser, timeout).until(element)
+    WebDriverWait(browser, selelnium_timeout).until(element)
     browser.find_elements_by_id('chat-toggle-button')[0].click()
     browser.find_elements_by_css_selector('button[aria-label="Users and messages toggle"]')[0].click()
+    browser.execute_script("document.querySelector('[aria-label=\"Users and messages toggle\"]').style.display='none';")
+    browser.execute_script("document.querySelector('[aria-label=\"Options\"]').style.display='none';")
 
 def get_join_url():
     minfo = bbb.get_meeting_info(args.id)
@@ -80,16 +76,16 @@ def watch():
         time.sleep(60)
 
 def stream():
-    logging.info('Starting Stream with cmd: ffmpeg -fflags +igndts -f x11grab -s 1280x720 -r 24 -draw_mouse 0 -i :%d -f alsa -i pulse -ac 2 -preset ultrafaset -crf 0 -pix_fmt yuv420p -s 1280x720 -c:a aac -b:a 160k -ar 44100 -threads 0 -f flv "%s"' % (122, args.target))
-    ffmpeg_stream = 'ffmpeg -fflags +igndts -f x11grab -s 1280x720 -r 24 -draw_mouse 0 -i :%d -f alsa -i pulse -ac 2 -preset ultrafaset -pix_fmt yuv420p -s 1280x720 -c:a aac -b:a 160k -ar 44100 -threads 0 -f flv "%s"' % (122, args.target)
+    logging.info('Starting Stream with cmd: ffmpeg -fflags +igndts -f x11grab -s 1920x1080 -r 24 -draw_mouse 0 -i :%d -f alsa -i pulse -ac 2 -preset ultrafaset -crf 0 -pix_fmt yuv420p -s 1920x1080 -c:a aac -b:a 160k -ar 44100 -threads 0 -f flv "%s"' % (122, args.target))
+    ffmpeg_stream = 'ffmpeg -fflags +igndts -f x11grab -s 1920x1080 -r 24 -draw_mouse 0 -i :%d -f alsa -i pulse -ac 2 -preset ultrafaset -pix_fmt yuv420p -s 1920x1080 -c:a aac -b:a 160k -ar 44100 -threads 0 -f flv "%s"' % (122, args.target)
     ffmpeg_args = shlex.split(ffmpeg_stream)
     p = subprocess.Popen(ffmpeg_args)
 
 
 
 while bbb.is_meeting_running(args.id).is_meeting_running() != True:
-    logging.info("Meeting isn't running. We will try again in %d seconds!" % timeout)
-    time.sleep(timeout)
+    logging.info("Meeting isn't running. We will try again in %d seconds!" % connect_timeout)
+    time.sleep(connect_timeout)
 set_up()
 bbb_browser()
 stream()
-- 
GitLab