Skip to content
Snippets Groups Projects
Commit 6e24b0ba authored by Julian Rother's avatar Julian Rother
Browse files

Added controls for livestreams and added livestreams to player page

parent 323ca413
No related branches found
No related tags found
No related merge requests found
...@@ -108,7 +108,8 @@ CREATE TABLE IF NOT EXISTS `lectures_data` ( ...@@ -108,7 +108,8 @@ CREATE TABLE IF NOT EXISTS `lectures_data` (
`live` INTEGER NOT NULL DEFAULT 0, `live` INTEGER NOT NULL DEFAULT 0,
`norecording` INTEGER NOT NULL DEFAULT 0, `norecording` INTEGER NOT NULL DEFAULT 0,
`profile` varchar(64), `profile` varchar(64),
`stream_settings` text NOT NULL DEFAULT '' `stream_settings` text NOT NULL DEFAULT '',
`stream_job` INTEGER
); );
CREATE TABLE IF NOT EXISTS `places` ( CREATE TABLE IF NOT EXISTS `places` (
`place` varchar(20) NOT NULL PRIMARY KEY, `place` varchar(20) NOT NULL PRIMARY KEY,
......
...@@ -7,8 +7,9 @@ import string ...@@ -7,8 +7,9 @@ import string
@sched_func(120) @sched_func(120)
def livestream_thumbnail(): def livestream_thumbnail():
livestreams = query('SELECT streams.lecture_id, streams.handle AS livehandle FROM streams WHERE streams.active') livestreams = query('SELECT streams.lecture_id, streams.handle AS livehandle FROM streams WHERE streams.active')
for v in genlive(livestreams): lectures = query('SELECT * FROM lectures WHERE stream_job IS NOT NULL')
schedule_job('thumbnail', {'src': v['path'], 'filename': 'l_%i.jpg'%lecture['id']}) for v in genlive(livestreams)+genlive_new(lectures):
schedule_job('thumbnail', {'src': v['path'], 'filename': 'l_%i.jpg'%v['lecture_id']})
@app.route('/internal/streaming/legacy_auth', methods=['GET', 'POST']) @app.route('/internal/streaming/legacy_auth', methods=['GET', 'POST'])
@app.route('/internal/streaming/legacy_auth/<server>', methods=['GET', 'POST']) @app.route('/internal/streaming/legacy_auth/<server>', methods=['GET', 'POST'])
...@@ -149,7 +150,14 @@ def streamauth(server): ...@@ -149,7 +150,14 @@ def streamauth(server):
return 'Ok', 200 return 'Ok', 200
return 'Forbidden', 403 return 'Forbidden', 403
elif request.values['call'] == 'publish_done': elif request.values['call'] == 'publish_done':
source = (query('SELECT * FROM live_sources WHERE server = ? AND clientid = ?', server, request.values['clientid']) or [None])[0]
modify('UPDATE live_sources SET server = NULL, clientid = NULL, preview_key = NULL, last_active = ? WHERE server = ? AND clientid = ?', datetime.now(), server, request.values['clientid']) modify('UPDATE live_sources SET server = NULL, clientid = NULL, preview_key = NULL, last_active = ? WHERE server = ? AND clientid = ?', datetime.now(), server, request.values['clientid'])
if not source:
return 'Ok', 200
for lecture in query('SELECT lectures FROM lectures WHERE stream_job IS NOT NULL'):
settings = json.loads(lecture['stream_settings'])
if source['id'] in [settings.get('source1'), settings.get('source2')]:
cancel_job(lecture['stream_job'])
return 'Ok', 200 return 'Ok', 200
return 'Bad request', 400 return 'Bad request', 400
...@@ -166,6 +174,9 @@ def schedule_livestream(lecture_id): ...@@ -166,6 +174,9 @@ def schedule_livestream(lecture_id):
if obj: if obj:
server = obj['server'] server = obj['server']
data['src%i'%idx]['url'] = 'rtmp://%s/src/%i'%(obj['server'], obj['id']) data['src%i'%idx]['url'] = 'rtmp://%s/src/%i'%(obj['server'], obj['id'])
if not obj['clientid']:
flash('Quelle „%s“ ist nicht aktiv!'%obj['name'])
return None
mode = settings.get('source%i_audiomode'%idx) mode = settings.get('source%i_audiomode'%idx)
leftvol = float(settings.get('source%i_leftvolume'%idx, 100))/100.0 leftvol = float(settings.get('source%i_leftvolume'%idx, 100))/100.0
rightvol = float(settings.get('source%i_rightvolume'%idx, 100))/100.0 rightvol = float(settings.get('source%i_rightvolume'%idx, 100))/100.0
...@@ -204,17 +215,36 @@ def schedule_livestream(lecture_id): ...@@ -204,17 +215,36 @@ def schedule_livestream(lecture_id):
data['src2']['afilter'] = build_filter(data['src2']['afilter']) data['src2']['afilter'] = build_filter(data['src2']['afilter'])
data['src2']['vfilter'] = build_filter(data['src2']['vfilter']) data['src2']['vfilter'] = build_filter(data['src2']['vfilter'])
data['destbase'] = 'rtmp://%s/hls/l_%i'%(server, lecture['id']) data['destbase'] = 'rtmp://%s/hls/l_%i'%(server, lecture['id'])
if lecture['stream_job']:
flash('Stream läuft bereits!')
return None
job_id = schedule_job('complex_live_transcode', data, priority=10) job_id = schedule_job('complex_live_transcode', data, priority=10)
modify('UPDATE lectures_data SET stream_job = ? WHERE id = ? AND stream_job IS NULL', job_id, lecture_id)
if query('SELECT stream_job FROM lectures WHERE id = ?', lecture_id)[0]['stream_job'] != job_id:
flash('Stream läuft bereits!')
cancel_job(job_id)
return None
return job_id return job_id
@job_handler('complex_live_transcode', state='failed') @job_handler('complex_live_transcode', state='failed')
def restart_failed_complex_live_transcode(id, type, data, state, status): def restart_failed_complex_live_transcode(id, type, data, state, status):
restart_job(id) restart_job(id)
@app.route('/internal/streaming/start', methods=['POST']) @job_handler('complex_live_transcode')
def cleanup_after_complex_live_transcode_ended(id, type, data, state, status):
job = query('SELECT * FROM jobs WHERE id = ?', id, nlfix=False)[0]
if state == 'finished' or (state == 'failed' and job['canceled']):
modify('UPDATE lectures_data SET stream_job = NULL WHERE stream_job = ?', id)
@app.route('/internal/streaming/control', methods=['POST'])
@mod_required @mod_required
def start_stream(): def control_stream():
action = request.values['action']
lecture_id = int(request.values['lecture_id']) lecture_id = int(request.values['lecture_id'])
course = (query('SELECT courses.* FROM courses JOIN lectures ON (courses.id = lectures.course_id) WHERE lectures.id = ?', lecture_id) or [None])[0] course = (query('SELECT courses.* FROM courses JOIN lectures ON (courses.id = lectures.course_id) WHERE lectures.id = ?', lecture_id) or [None])[0]
if action == 'start':
schedule_livestream(lecture_id) schedule_livestream(lecture_id)
elif action == 'stop':
lecture = query('SELECT * FROM lectures WHERE id = ?', lecture_id)[0]
cancel_job(lecture['stream_job'])
return redirect(url_for('course', handle=course['handle'])) return redirect(url_for('course', handle=course['handle']))
...@@ -135,6 +135,16 @@ def genlive(streams): ...@@ -135,6 +135,16 @@ def genlive(streams):
stream['file_size'] = 0 stream['file_size'] = 0
return streams return streams
def genlive_new(lectures):
hls_format = (query('SELECT * FROM formats WHERE keywords = "hls"') or [{}])[0]
res = []
for lecture in lectures:
if not lecture['stream_job']:
continue
res.append({'livehandle': 'l_%i'%lecture['id'], 'visible': True,
'downloadable': False, 'path': 'pub/hls/l_%i.m3u8'%lecture['id'],
'file_size': 0, 'formats': hls_format, 'lecture_id': lecture['id']})
return res
@app.route('/') @app.route('/')
@register_navbar('Home', icon='home') @register_navbar('Home', icon='home')
...@@ -170,6 +180,13 @@ def index(): ...@@ -170,6 +180,13 @@ def index():
JOIN courses ON courses.id = lectures.course_id JOIN courses ON courses.id = lectures.course_id
WHERE streams.active AND (? OR (streams.visible AND courses.visible AND courses.listed AND lectures.visible)) WHERE streams.active AND (? OR (streams.visible AND courses.visible AND courses.listed AND lectures.visible))
''', ismod()) ''', ismod())
livestreams_new = query('''SELECT lectures.*, "course" AS sep, courses.*
FROM lectures
JOIN courses ON courses.id = lectures.course_id
WHERE lectures.stream_job IS NOT NULL AND (? OR (courses.visible AND courses.listed AND lectures.visible))
''', ismod())
for stream in livestreams_new:
stream['livehandle'] = 'l_%i'%stream['id']
featured = query('SELECT * FROM featured WHERE (? OR visible) ORDER BY `order`', ismod()) featured = query('SELECT * FROM featured WHERE (? OR visible) ORDER BY `order`', ismod())
featured = list(filter(lambda x: not x['deleted'], featured)) featured = list(filter(lambda x: not x['deleted'], featured))
for item in featured: for item in featured:
...@@ -192,7 +209,7 @@ def index(): ...@@ -192,7 +209,7 @@ def index():
WHERE videos.lecture_id = ? AND videos.visible WHERE videos.lecture_id = ? AND videos.visible
ORDER BY formats.prio DESC ORDER BY formats.prio DESC
''', item['param'])+genlive(streams) ''', item['param'])+genlive(streams)
return render_template('index.html', latestvideos=livestreams+latestvideos, upcomming=upcomming, featured=featured) return render_template('index.html', latestvideos=livestreams_new+livestreams+latestvideos, upcomming=upcomming, featured=featured)
@app.route('/courses') @app.route('/courses')
@register_navbar('Videos', icon='film') @register_navbar('Videos', icon='film')
...@@ -247,10 +264,11 @@ def course(id=None, handle=None): ...@@ -247,10 +264,11 @@ def course(id=None, handle=None):
JOIN formats ON formats.keywords = "hls" JOIN formats ON formats.keywords = "hls"
WHERE streams.active AND (? OR streams.visible) AND lectures.course_id = ? WHERE streams.active AND (? OR streams.visible) AND lectures.course_id = ?
''', ismod(), course['id']) ''', ismod(), course['id'])
videos += genlive(livestreams)
videos += genlive_new(lectures)
chapters = [] chapters = []
if course['coursechapters']: if course['coursechapters']:
chapters = query('SELECT chapters.* FROM chapters JOIN lectures ON lectures.id = chapters.lecture_id WHERE lectures.course_id = ? AND NOT chapters.deleted AND chapters.visible ORDER BY time ASC', course['id']) chapters = query('SELECT chapters.* FROM chapters JOIN lectures ON lectures.id = chapters.lecture_id WHERE lectures.course_id = ? AND NOT chapters.deleted AND chapters.visible ORDER BY time ASC', course['id'])
videos += genlive(livestreams)
responsible = query('''SELECT users.*, responsible.course_id AS responsible responsible = query('''SELECT users.*, responsible.course_id AS responsible
FROM users FROM users
LEFT JOIN responsible ON (responsible.user_id = users.id AND responsible.course_id = ?) LEFT JOIN responsible ON (responsible.user_id = users.id AND responsible.course_id = ?)
...@@ -286,6 +304,7 @@ def lecture(id, course=None, courseid=None): ...@@ -286,6 +304,7 @@ def lecture(id, course=None, courseid=None):
WHERE streams.active AND (? OR streams.visible) AND lectures.id = ? WHERE streams.active AND (? OR streams.visible) AND lectures.id = ?
''', ismod(), id) ''', ismod(), id)
videos += genlive(livestreams) videos += genlive(livestreams)
videos += genlive_new([lecture])
perms = query('SELECT perm.* FROM perm WHERE ((NOT perm.deleted) AND (perm.lecture_id = ? OR perm.course_id = ?))', perms = query('SELECT perm.* FROM perm WHERE ((NOT perm.deleted) AND (perm.lecture_id = ? OR perm.course_id = ?))',
lecture['id'], lecture['course_id']) lecture['id'], lecture['course_id'])
if not videos: if not videos:
...@@ -379,7 +398,16 @@ def auth(): # For use with nginx auth_request ...@@ -379,7 +398,16 @@ def auth(): # For use with nginx auth_request
if url.endswith('jpg') or ismod(): if url.endswith('jpg') or ismod():
return "OK", 200 return "OK", 200
if url.startswith('pub/hls/'): if url.startswith('pub/hls/'):
handle = url[len('pub/hls/'):].split('_')[0].split('.')[0] handle = url[len('pub/hls/'):].rsplit('_')[0].split('.')[0]
if handle.startswith('l_'):
perms = query('''SELECT lectures.id AS lecture, perm.*
FROM lectures
JOIN courses ON (lectures.course_id = courses.id)
LEFT JOIN perm ON ((lectures.id = perm.lecture_id OR courses.id = perm.course_id) AND NOT perm.deleted)
WHERE lectures.id = ?
AND (courses.visible AND lectures.visible)
ORDER BY perm.video_id DESC, perm.lecture_id DESC, perm.course_id DESC''', int(handle[2:]))
else:
perms = query('''SELECT lectures.id AS lecture, perm.* perms = query('''SELECT lectures.id AS lecture, perm.*
FROM streams FROM streams
JOIN lectures ON (streams.lecture_id = lectures.id) JOIN lectures ON (streams.lecture_id = lectures.id)
......
...@@ -199,8 +199,9 @@ ...@@ -199,8 +199,9 @@
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<form class="form-inline" method="post" action="{{ url_for('start_stream') }}"> <form class="form-inline" method="post" action="{{ url_for('control_stream') }}">
<input type="hidden" id="editstream-lectureid" name="lecture_id" value=""> <input type="hidden" id="editstream-lectureid" name="lecture_id" value="">
<input type="hidden" id="editstream-action" name="action" value="">
<button type="submit" id="editstream-start" class="btn btn-danger">Speichern und starten</button> <button type="submit" id="editstream-start" class="btn btn-danger">Speichern und starten</button>
<button type="button" id="editstream-submit" class="btn btn-primary">Speichern</button> <button type="button" id="editstream-submit" class="btn btn-primary">Speichern</button>
</form> </form>
...@@ -257,6 +258,14 @@ $('#editstream-start').on('click', function () { ...@@ -257,6 +258,14 @@ $('#editstream-start').on('click', function () {
$('#editstream').on('show.bs.modal', function (event) { $('#editstream').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); var button = $(event.relatedTarget);
$('#editstream').data('currentpath', button.data('path')); $('#editstream').data('currentpath', button.data('path'));
alert(button.data('active'));
if (button.data('active')) {
$('#editstream-action').val('stop');
$('#editstream-start').text('Stream stoppen');
} else {
$('#editstream-action').val('start');
$('#editstream-start').text('Speichern und starten');
}
$('#editstream-lectureid').val(button.data('lectureid')); $('#editstream-lectureid').val(button.data('lectureid'));
$("#editstream-form")[0].reset(); $("#editstream-form")[0].reset();
if (button.data('value')) if (button.data('value'))
......
...@@ -262,7 +262,7 @@ $('#embedcodebtn').popover( ...@@ -262,7 +262,7 @@ $('#embedcodebtn').popover(
</li> </li>
{% if ismod() %} {% if ismod() %}
<li class="pull-right"> <li class="pull-right">
<button class="btn btn-default" data-toggle="modal" data-target="#editstream" data-lectureid="{{ lecture.id }}" data-path="{{ 'lectures.%i.stream_settings'%lecture.id }}" data-value='{{ lecture.stream_settings|e }}'> <button class="btn btn-{{ 'default' if not lecture.stream_settings else 'danger' if lecture.stream_job else 'primary' }}" data-toggle="modal" data-target="#editstream" data-lectureid="{{ lecture.id }}" data-active="{{ 1 if lecture.stream_job else '' }}" data-path="{{ 'lectures.%i.stream_settings'%lecture.id }}" data-value='{{ lecture.stream_settings|e }}'>
<span class="fas fa-broadcast-tower"></span> <span class="fas fa-broadcast-tower"></span>
</button> </button>
</li> </li>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment