Commit 7f880726 authored by Julian Rother's avatar Julian Rother

Completed livestream model and made live sources page visible

parent b164ce7e
......@@ -62,7 +62,7 @@ def restart_failed_live_transcode(id, type, data, state, status):
restart_job(id)
@app.route('/internal/streaming')
#@register_navbar('Streaming', icon='transfer')
@register_navbar('Streaming', icon='broadcast-tower', iconlib='fa')
@mod_required
def streaming():
sources = query('SELECT * FROM live_sources WHERE NOT deleted')
......@@ -152,3 +152,65 @@ def streamauth(server):
modify('UPDATE live_sources SET server = NULL, clientid = NULL, preview_key = NULL, last_active = ? WHERE server = ? AND clientid = ?', datetime.now(), server, request.values['clientid'])
return 'Ok', 200
return 'Bad request', 400
def schedule_livestream(lecture_id):
def build_filter(l):
return ','.join(l) if l else None
server = 'rwth.video'
lecture = query('SELECT * FROM lectures WHERE id = ?', lecture_id)[0]
settings = json.loads(lecture['stream_settings'])
data = {'src1': {'afilter': [], 'vfilter': []}, 'src2': {'afilter': [], 'vfilter': []}, 'afilter': [], 'videoag_logo': int(bool(settings.get('video_showlogo'))), 'lecture_id': lecture['id']}
src1 = (query('SELECT * FROM live_sources WHERE NOT deleted AND id = ?', settings.get('source1')) or [{}])[0]
src2 = (query('SELECT * FROM live_sources WHERE NOT deleted AND id = ?', settings.get('source2')) or [{}])[0]
for idx, obj in zip([1,2], [src1, src2]):
if obj:
server = obj['server']
data['src%i'%idx]['url'] = 'rtmp://%s/src/%i'%(obj['server'], obj['id'])
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
if mode == 'mono':
data['src%i'%idx]['afilter'].append('pan=mono|c0=%f*c0+%f*c1'%(0.5*leftvol, 0.5*rightvol))
elif mode == 'stereo':
data['src%i'%idx]['afilter'].append('pan=stereo|c0=%f*c0|c1=%f*c1'%(leftvol, rightvol))
elif mode == 'unchanged':
pass
elif mode == 'off':
data['src%i'%idx]['afilter'].append('pan=mono|c0=0*c0')
else:
raise(Exception())
mode = settings.get('videomode')
if mode == '1':
data['vmix'] = 'streamselect=map=0'
elif mode == '2':
data['vmix'] = 'streamselect=map=1'
elif vmode == 'lecture4:3':
data['src1']['vfilter'].append('scale=1440:1080')
data['src2']['vfilter'].append('scale=1440:810,pad=1440:1080:0:135,crop=480:1080')
data['vmix'] = 'hstack'
elif vmode == 'lecture16:9':
data['src1']['vfilter'].append('scale=1440:810,pad=1440:1080:0:135')
data['src2']['vfilter'].append('scale=1440:810,pad=1440:1080:0:135,crop=480:1080')
data['vmix'] = 'hstack'
elif vmode == 'sidebyside':
data['src1']['vfilter'].append('scale=960:540')
data['src2']['vfilter'].append('scale=960:540')
data['vmix'] = 'hstack,pad=1920:1080:0:270'
if settings.get('audio_normalize'):
data['afilter'].append('loudnorm')
data['afilter'] = build_filter(data['afilter'])
data['src1']['afilter'] = build_filter(data['src1']['afilter'])
data['src1']['vfilter'] = build_filter(data['src1']['vfilter'])
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'])
job_id = schedule_job('complex_live_transcode', data, priority=10)
return job_id
@app.route('/internal/streaming/start', methods=['POST'])
@mod_required
def start_stream():
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)
return redirect(url_for('course', handle=course['handle']))
......@@ -160,12 +160,21 @@
<option value="{{ source.id }}">{{ source.name }}</option>
{% endfor %}
</select>
<img src="{{ config.VIDEOPREFIX }}/thumbnail/s_none.jpg" style="width: 100%; margin-bottom: 0.5em; margin-top: 0.5em"/>
<select name="source{{ snum }}_audiomode" class="form-control">
<option selected value="unchanged">Audio unverändert</option>
<option value="left">Nur linke Tonspur</option>
<option value="right">Nur rechte Tonspur</option>
<option value="mix">Monomix aller Tonspuren</option>
<img src="{{ url_for('static', filename='smptebars.jpg') }}" style="width: 100%; margin-bottom: 0.5em; margin-top: 0.5em"/>
<label>Lautstärke</label>
<div class="row">
<div class="col-xs-6">
<input type="range" name="source{{ snum }}_leftvolume" value="100">
</div>
<div class="col-xs-6">
<input type="range" name="source{{ snum }}_rightvolume" value="100">
</div>
</div>
<select name="source{{ snum }}_audiomode" class="form-control" style="margin-top: 0.5em">
<option value="mono" selected>Mono-Mix</option>
<option value="stereo">Stereo</option>
<option value="unchanged">Audio unverändert</option>
<option value="off">Kein Audio</option>
</select>
</div>
{% endfor %}
......@@ -174,36 +183,27 @@
<select name="videomode" class="form-control">
<option value="1" selected>Nur Quelle 1</option>
<option value="2">Nur Quelle 2</option>
<option value="sidebyside">Side-by-Side (Quelle 1 groß, 1/3 von 2 daneben)</option>
<option value="lecture4:3">Quelle 1 (4:3) links, Ausschnitt von 2 rechts</option>
<option value="lecture16:9">Quelle 1 (16:9) links, Ausschnitt von 2 rechts</option>
<option value="sidebyside">Side-by-Side (Quelle 1 links, 2 rechts)</option>
</select>
<div class="checkbox"><label><input name="video_showlogo" type="checkbox" checked>Video AG-Logo einblenden</label></div>
</div>
<div class="col-xs-12" style="margin-top: 1em">
<label>Audio</label>
<select name="audiomode" class="form-control">
<option value="1" selected>Quelle 1</option>
<option value="2">Quelle 2</option>
<option value="splitstereo">Quelle 1 links, 2 rechts</option>
<option value="mix">Mix beider Quellen</option>
</select>
<div class="checkbox"><label><input name="audio_normalize" type="checkbox">Lautstärke normalisieren</label></div>
</div>
<div class="col-xs-12" style="margin-top: 1em">
<label>Ausgabeformat</label>
<select name="outputmode" class="form-control">
<option selected value="multi">1080p/720p/360p (Standard)</option>
<option value="720p">Nur 720p</option>
</select>
</div>
<div class="col-xs-12" style="margin-top: 1em">
<label>Weitere Einstellungen</label>
<div class="checkbox"><label><input name="autostart" type="checkbox" checked>Automatisch starten</label></div>
<div style="margin-top: -1em;">
<div class="checkbox"><label><input name="audio_normalize" type="checkbox">Lautstärke normalisieren</label></div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<form class="form-inline" method="post" action="{{ url_for('start_stream') }}">
<input type="hidden" id="editstream-lectureid" name="lecture_id" 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>
</div>
</div>
</div>
......@@ -212,7 +212,10 @@
<script>
function editstream_update() {
$('#editstream .source-select').each(function () {
$(this).siblings('img').prop('src', '{{ config.VIDEOPREFIX }}/thumbnail/s_'+$(this).val()+'.jpg');
if ($(this).val())
$(this).siblings('img').prop('src', '{{ config.VIDEOPREFIX }}/thumbnail/s_'+$(this).val()+'.jpg');
else
$(this).siblings('img').prop('src', {{ url_for('static', filename='smptebars.jpg')|tojson }});
});
};
function editstream_dump() {
......@@ -223,6 +226,9 @@ function editstream_dump() {
$("#editstream select[name!='']").each(function () {
res[$(this).attr('name')] = $(this).val();
});
$("#editstream input[type='range'][name!='']").each(function () {
res[$(this).attr('name')] = $(this).val();
});
return res;
};
function editstream_load(obj) {
......@@ -234,15 +240,24 @@ function editstream_load(obj) {
if ($(this).attr('name') in obj)
$(this).val(obj[$(this).attr('name')]);
});
$("#editstream input[type='range'][name!='']").each(function () {
if ($(this).attr('name') in obj)
$(this).val(obj[$(this).attr('name')]);
});
};
$('#editstream .source-select').on('change', editstream_update);
$('#editstream-submit').on('click', function () {
moderator.api.set($('#editstream').data('currentpath'), JSON.stringify(editstream_dump()), true);
$('#editstream').modal('hide');
});
$('#editstream-start').on('click', function () {
moderator.api.set($('#editstream').data('currentpath'), JSON.stringify(editstream_dump()), true);
return true;
});
$('#editstream').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget);
$('#editstream').data('currentpath', button.data('path'));
$('#editstream-lectureid').val(button.data('lectureid'));
$("#editstream-form")[0].reset();
if (button.data('value'))
editstream_load(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-path="{{ 'lectures.%i.stream_settings'%lecture.id }}" data-value='{{ lecture.stream_settings|e }}'>
<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 }}'>
<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