Commit 6e24b0ba authored by Julian Rother's avatar Julian Rother

Added controls for livestreams and added livestreams to player page

parent 323ca413
......@@ -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]
schedule_livestream(lecture_id)
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,15 +398,24 @@ 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]
perms = query('''SELECT lectures.id AS lecture, perm.*
FROM streams
JOIN lectures ON (streams.lecture_id = lectures.id)
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 streams.handle = ?
AND (courses.visible AND lectures.visible AND streams.visible)
ORDER BY perm.video_id DESC, perm.lecture_id DESC, perm.course_id DESC''', handle)
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)
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 streams.handle = ?
AND (courses.visible AND lectures.visible AND streams.visible)
ORDER BY perm.video_id DESC, perm.lecture_id DESC, perm.course_id DESC''', handle)
else:
perms = query('''SELECT videos.path, videos.id AS vid, perm.*
FROM videos
......
......@@ -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>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment