forked from manishkatyan/bbb-streaming
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstream.py
221 lines (189 loc) · 10.2 KB
/
stream.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#!/usr/bin/env python # -*- coding: utf-8 -*-
import sys, argparse, time, subprocess, shlex, logging, os, re
from bigbluebutton_api_python import BigBlueButton, exception
from bigbluebutton_api_python import util as bbbUtil
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import NoSuchElementException
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
from datetime import datetime
downloadProcess = None
browser = None
selenium_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("-I","--intro", help="Intro file to play before streaming")
parser.add_argument("-B","--beginIntroAt", help="begin intro at position (e.g. 00:01:05)")
parser.add_argument("-E","--endIntroAt", help="End intro at position (e.g. 01:00:04)")
parser.add_argument("-l","--stream", help="live stream a BigBlueButton meeting",action="store_true")
parser.add_argument("-d","--download", help="download / save a BigBlueButton meeting",action="store_true")
parser.add_argument("-S","--startMeeting", help="start the meeting if not running",action="store_true")
parser.add_argument("-P","--hidePresentation", help="hide presentation",action="store_true")
parser.add_argument("-C","--customCSS", help="custom CSS to resize video")
parser.add_argument("-V","--viewerURL", help="url for users to watch live video")
parser.add_argument("-A","--attendeePassword", help="attendee password (required to create meetings)")
parser.add_argument("-M","--moderatorPassword", help="moderator password (required to create a meeting)")
parser.add_argument("-T","--meetingTitle", help="meeting title (required to create a meeting)")
parser.add_argument("-u","--user", help="Name to join the meeting",default="Live")
parser.add_argument("-t","--target", help="RTMP Streaming URL")
parser.add_argument("-c","--chat", help="Show the chat",action="store_true")
args = parser.parse_args()
bbb = BigBlueButton(args.server,args.secret)
bbbUB = bbbUtil.UrlBuilder(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('--shm-size=2gb')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--start-fullscreen')
# added options for allowing permissions
options.add_experimental_option("prefs", { \
"profile.default_content_setting_values.media_stream_mic": 1,
"profile.default_content_setting_values.media_stream_camera": 1,
"profile.default_content_setting_values.geolocation": 1,
"profile.default_content_setting_values.notifications": 1 })
logging.info('Starting browser!!')
browser = webdriver.Chrome(executable_path='./chromedriver',options=options)
def bbb_browser():
global browser
logging.info('Open BBB and hide elements!!')
if args.startMeeting is True:
try:
logging.info("create_meeting...")
create_meeting()
except exception.bbbexception.BBBException as ERR:
logging.info(ERR)
logging.info("updated get_join_url...")
join_url = get_join_url()
logging.info(join_url)
browser.get(join_url)
element = EC.presence_of_element_located((By.XPATH, '//button[contains(@aria-label,"Play audio")]'))
WebDriverWait(browser, selenium_timeout).until(element)
browser.find_elements_by_xpath('//button[contains(@aria-label,"Play audio")]')[0].click()
element = EC.invisibility_of_element((By.CSS_SELECTOR, '.ReactModal__Overlay'))
WebDriverWait(browser, selenium_timeout).until(element)
try:
element = browser.find_element_by_id('message-input')
chat_send = browser.find_elements_by_css_selector('[aria-label="Send message"]')[0]
# ensure chat is enabled (might be locked by moderator)
if element.is_enabled() and chat_send.is_enabled() and args.viewerURL:
element.send_keys("This meeting is streamed to: %s" % args.viewerURL)
chat_send.click()
if args.chat:
browser.execute_script("document.querySelector('[aria-label=\"User list\"]').parentElement.style.display='none';")
else:
element = browser.find_elements_by_id('chat-toggle-button')[0]
if element.is_enabled():
element.click()
except NoSuchElementException:
# ignore (chat might be disabled)
logging.info("could not find chat input or chat toggle")
if not args.chat:
try:
element = browser.find_elements_by_css_selector('button[aria-label="Users and messages toggle"]')[0]
if element.is_enabled():
element.click()
except NoSuchElementException:
logging.info("could not find users and messages toggle")
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';")
browser.execute_script("document.querySelector('[aria-label=\"Actions bar\"]').style.display='none';")
browser.execute_script("document.getElementById('container').setAttribute('style','margin-bottom:30px');")
#if args.hidePresentation is False:
#browser.execute_script("document.getElementById('container').setAttribute('style','margin:100px');")
#browser.execute_script("document.getElementById('container').firstChild.setAttribute('style','height:500px !important');")
#browser.execute_script("document.getElementById('container').setAttribute('style','padding-top:200px !important');")
def create_meeting():
create_params = {}
if args.moderatorPassword:
create_params['moderatorPW'] = args.moderatorPassword
if args.attendeePassword:
create_params['attendeePW'] = args.attendeePassword
if args.meetingTitle:
create_params['name'] = args.meetingTitle
return bbb.create_meeting(args.id, params=create_params)
def get_join_url():
minfo = bbb.get_meeting_info(args.id)
pwd = minfo.get_meetinginfo().get_attendeepw()
joinParams = {}
joinParams['meetingID'] = args.id
joinParams['fullName'] = args.user
joinParams['password'] = pwd
joinParams['userdata-bbb_auto_join_audio'] = "true"
joinParams['userdata-bbb_enable_video'] = 'true'
joinParams['userdata-bbb_listen_only_mode'] = "true"
joinParams['userdata-bbb_force_listen_only'] = "true"
joinParams['userdata-bbb_skip_check_audio'] = 'true'
joinParams['joinViaHtml5'] = 'true'
if args.hidePresentation is True:
joinParams['userdata-bbb_auto_swap_layout'] = 'true'
if args.customCSS:
joinParams['userdata-bbb_custom_style'] = args.customCSS
return bbbUB.buildUrl("join", params=joinParams)
def stream_intro():
audio_options = '-f alsa -i pulse -ac 2 -c:a aac -b:a 160k -ar 44100'
video_options = '-c:v libx264 -x264-params "nal-hrd=cbr" -profile:v high -level:v 4.2 -vf format=yuv420p -b:v 4000k -maxrate 4000k -minrate 2000k -bufsize 8000k -g 60 -preset ultrafast'
introBegin = ""
if args.beginIntroAt:
introBegin = "-ss %s"%(args.beginIntroAt)
introEnd = ""
if args.endIntroAt:
introEnd = "-to %s"%(args.endIntroAt)
ffmpeg_stream = 'ffmpeg -re %s %s -thread_queue_size 1024 -i %s -thread_queue_size 1024 %s -threads 0 %s -f flv "%s"' % ( introBegin, introEnd, args.intro, audio_options, video_options, args.target)
ffmpeg_args = shlex.split(ffmpeg_stream)
logging.info("streaming intro...")
p = subprocess.call(ffmpeg_args)
def stream():
#audio_options = '-f alsa -i pulse -ac 2 -c:a aac -b:a 160k -ar 44100'
#video_options = ' -c:v libvpx-vp9 -b:v 2000k -crf 33 -quality realtime -speed 5'
#video_options = '-c:v libx264 -x264-params "nal-hrd=cbr" -profile:v high -level:v 4.2 -vf format=yuv420p -b:v 4000k -maxrate 4000k -minrate 2000k -bufsize 8000k -g 60 -preset ultrafast -tune zerolatency'
#ffmpeg_stream = 'ffmpeg -thread_queue_size 1024 -f x11grab -draw_mouse 0 -s 1920x1080 -i :%d -thread_queue_size 1024 %s -threads 0 %s -f flv -flvflags no_duration_filesize "%s"' % ( 122, audio_options, video_options, args.target)
# reference - https://github.com/aau-zid/BigBlueButton-liveStreaming/issues/62
#fixed Audio/video not synchronized, reference = https://github.com/aau-zid/BigBlueButton-liveStreaming/issues/100
audio_options = '-f pulse -itsoffset 1.5 -i default -ac 2 -c:a aac -b:a 160k -ar 48000'
video_options = '-c:v libx264 -x264-params "nal-hrd=vbr" -profile:v high -level:v 4.2 -vf format=yuv420p -b:v 4000k -maxrate 4000k -minrate 2000k -bufsize 8000k -g 60 -preset ultrafast -tune zerolatency'
ffmpeg_stream = 'ffmpeg -y -nostats -thread_queue_size 4096 -f x11grab -probesize 10M -draw_mouse 0 -framerate 30 -vsync 1 -s 1920x1080 -i :%d -thread_queue_size 4096 %s -threads 0 %s -f flv -flvflags no_duration_filesize "%s"' % ( 122, audio_options, video_options, args.target)
ffmpeg_args = shlex.split(ffmpeg_stream)
logging.info("streaming meeting...")
p = subprocess.call(ffmpeg_args)
def download():
downloadFile = "/video/meeting-%s.mkv" % fileTimeStamp
audio_options = '-f alsa -i pulse -ac 2'
video_options = '-c:v libx264rgb -crf 0 -preset ultrafast'
ffmpeg_stream = 'ffmpeg -thread_queue_size 1024 -f x11grab -draw_mouse 0 -s 1920x1080 -i :%d -thread_queue_size 1024 %s %s %s' % ( 122, audio_options, video_options, downloadFile)
ffmpeg_args = shlex.split(ffmpeg_stream)
logging.info("saving meeting as %s" % downloadFile)
return subprocess.Popen(ffmpeg_args)
if args.startMeeting is False:
while bbb.is_meeting_running(args.id).is_meeting_running() != True:
logging.info("Meeting isn't running. We will try again in %d seconds!" % connect_timeout)
time.sleep(connect_timeout)
# current date and time
now = datetime.now()
fileTimeStamp = now.strftime("%Y%m%d%H%M%S")
set_up()
if args.stream and args.intro:
stream_intro()
if args.stream or args.download:
bbb_browser()
if args.download:
downloadProcess = download()
if args.stream:
stream()
if downloadProcess:
downloadProcess.communicate(input=None)
if browser:
browser.quit()