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` (
`live` INTEGER NOT NULL DEFAULT 0,
`norecording` INTEGER NOT NULL DEFAULT 0,
`profile` varchar(64),
`stream_settings` text NOT NULL DEFAULT ''
`stream_settings` text NOT NULL DEFAULT '',
`stream_job` INTEGER
);
CREATE TABLE IF NOT EXISTS `places` (
`place` varchar(20) NOT NULL PRIMARY KEY,
......
......@@ -7,8 +7,9 @@ import string
@sched_func(120)
def livestream_thumbnail():
livestreams = query('SELECT streams.lecture_id, streams.handle AS livehandle FROM streams WHERE streams.active')
for v in genlive(livestreams):
schedule_job('thumbnail', {'src': v['path'], 'filename': 'l_%i.jpg'%lecture['id']})
lectures = query('SELECT * FROM lectures WHERE stream_job IS NOT NULL')
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/<server>', methods=['GET', 'POST'])
......@@ -149,7 +150,14 @@ def streamauth(server):
return 'Ok', 200
return 'Forbidden', 403
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'])
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 'Bad request', 400
......@@ -166,6 +174,9 @@ def schedule_livestream(lecture_id):
if obj:
server = obj['server']
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)
leftvol = float(settings.get('source%i_leftvolume'%idx, 100))/100.0
rightvol = float(settings.get('source%i_rightvolume'%idx, 100))/100.0
......@@ -204,17 +215,36 @@ def schedule_livestream(lecture_id):
data['src2']['afilter'] = build_filter(data['src2']['afilter'])
data['src2']['vfilter'] = build_filter(data['src2']['vfilter'])
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)
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
@job_handler('complex_live_transcode', state='failed')
def restart_failed_complex_live_transcode(id, type, data, state, status):
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
def start_stream():
def control_stream():
action = request.values['action']
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]
if action == 'start':
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']))
......@@ -135,6 +135,16 @@ def genlive(streams):
stream['file_size'] = 0
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('/')
@register_navbar('Home', icon='home')
......@@ -170,6 +180,13 @@ def index():
JOIN courses ON courses.id = lectures.course_id
WHERE streams.active AND (? OR (streams.visible AND courses.visible AND courses.listed AND lectures.visible))
''', 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 = list(filter(lambda x: not x['deleted'], featured))
for item in featured:
......@@ -192,7 +209,7 @@ def index():
WHERE videos.lecture_id = ? AND videos.visible
ORDER BY formats.prio DESC
''', 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')
@register_navbar('Videos', icon='film')
......@@ -247,10 +264,11 @@ def course(id=None, handle=None):
JOIN formats ON formats.keywords = "hls"
WHERE streams.active AND (? OR streams.visible) AND lectures.course_id = ?
''', ismod(), course['id'])
videos += genlive(livestreams)
videos += genlive_new(lectures)
chapters = []
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'])
videos += genlive(livestreams)
responsible = query('''SELECT users.*, responsible.course_id AS responsible
FROM users
LEFT JOIN responsible ON (responsible.user_id = users.id AND responsible.course_id = ?)
......@@ -286,6 +304,7 @@ def lecture(id, course=None, courseid=None):
WHERE streams.active AND (? OR streams.visible) AND lectures.id = ?
''', ismod(), id)
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 = ?))',
lecture['id'], lecture['course_id'])
if not videos:
......@@ -379,7 +398,16 @@ def auth(): # For use with nginx auth_request
if url.endswith('jpg') or ismod():
return "OK", 200
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.*
FROM streams
JOIN lectures ON (streams.lecture_id = lectures.id)
......
......@@ -199,8 +199,9 @@
</form>
</div>
<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-action" name="action" value="">
<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>
</form>
......@@ -257,6 +258,14 @@ $('#editstream-start').on('click', function () {
$('#editstream').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget);
$('#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-form")[0].reset();
if (button.data('value'))
......
......@@ -262,7 +262,7 @@ $('#embedcodebtn').popover(
</li>
{% if ismod() %}
<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>
</button>
</li>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment