added soundboard
This commit is contained in:
parent
075c4c990e
commit
fc08da804f
56
app.py
56
app.py
@ -222,6 +222,14 @@ def control_page():
|
|||||||
return render_template('control.html', config=current_config)
|
return render_template('control.html', config=current_config)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/soundboard')
|
||||||
|
def soundboard():
|
||||||
|
if current_config is None or 'sounds' not in current_config:
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
sounds = current_config.get('sounds', [])
|
||||||
|
return render_template('soundboard.html', sounds=sounds, config=current_config)
|
||||||
|
|
||||||
# ── Admin ────────────────────────────────────────────────────────────────────
|
# ── Admin ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@app.route('/admin')
|
@app.route('/admin')
|
||||||
@ -513,6 +521,54 @@ def api_reconnect():
|
|||||||
return jsonify({"success": False, "message": str(e)}), 500
|
return jsonify({"success": False, "message": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
import pygame
|
||||||
|
import threading
|
||||||
|
|
||||||
|
# pygame einmalig initialisieren (am besten global)
|
||||||
|
pygame.mixer.init()
|
||||||
|
|
||||||
|
@app.route('/api/play_sound', methods=['POST'])
|
||||||
|
def api_play_sound():
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
sound_id = data.get('sound_id')
|
||||||
|
|
||||||
|
if not sound_id:
|
||||||
|
return jsonify({"success": False, "message": "Kein sound_id angegeben"}), 400
|
||||||
|
|
||||||
|
# Sound aus aktueller Config suchen
|
||||||
|
if current_config is None or 'sounds' not in current_config:
|
||||||
|
return jsonify({"success": False, "message": "Keine Sounds in der aktuellen Konfiguration"}), 400
|
||||||
|
|
||||||
|
sound_entry = next((s for s in current_config['sounds'] if s['id'] == sound_id), None)
|
||||||
|
if not sound_entry:
|
||||||
|
return jsonify({"success": False, "message": f"Sound mit ID '{sound_id}' nicht gefunden"}), 404
|
||||||
|
|
||||||
|
file_path = os.path.join('sounds', sound_entry['file'])
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
logger.error(f"Sound-Datei nicht gefunden: {file_path}")
|
||||||
|
return jsonify({"success": False, "message": "Sound-Datei nicht gefunden"}), 404
|
||||||
|
|
||||||
|
# Sound in separatem Thread abspielen (blockiert nicht)
|
||||||
|
def play():
|
||||||
|
try:
|
||||||
|
pygame.mixer.music.load(file_path)
|
||||||
|
pygame.mixer.music.play()
|
||||||
|
while pygame.mixer.music.get_busy():
|
||||||
|
time.sleep(0.1)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Fehler beim Abspielen von {file_path}: {e}")
|
||||||
|
|
||||||
|
threading.Thread(target=play, daemon=True).start()
|
||||||
|
|
||||||
|
logger.info(f"Spiele Sound: {sound_entry['name']} ({sound_id})")
|
||||||
|
return jsonify({"success": True, "message": f"Spiele: {sound_entry['name']}"})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Play-Sound-Fehler")
|
||||||
|
return jsonify({"success": False, "message": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||||
|
|
||||||
|
|||||||
@ -8,5 +8,25 @@
|
|||||||
{"port": "B", "type": "motor", "name": "Unterstützung", "invert": false, "negative_only": false},
|
{"port": "B", "type": "motor", "name": "Unterstützung", "invert": false, "negative_only": false},
|
||||||
{"port": "C", "type": "light", "name": "Licht vorne", "on_value": 1.0, "off_value": 0.0, "negative_only": false},
|
{"port": "C", "type": "light", "name": "Licht vorne", "on_value": 1.0, "off_value": 0.0, "negative_only": false},
|
||||||
{"port": "D", "type": "fogger", "name": "Dampf", "on_value": -1.0, "off_value": 0.0, "negative_only": true}
|
{"port": "D", "type": "fogger", "name": "Dampf", "on_value": -1.0, "off_value": 0.0, "negative_only": true}
|
||||||
|
],
|
||||||
|
"sounds": [
|
||||||
|
{
|
||||||
|
"id": "pfeife1",
|
||||||
|
"file": "salamisound-3180602-dampflokpfeife-1-mal-lange.mp3",
|
||||||
|
"name": "Pfeife (lang)",
|
||||||
|
"description": "Langgezogene Dampflok-Pfeife"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pfeife2",
|
||||||
|
"file": "salamisound-8089794-dampflokpfeife-2-mal.mp3",
|
||||||
|
"name": "Pfeife (2x lang)",
|
||||||
|
"description": "Dampfpfeife 2 mal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pfeife3",
|
||||||
|
"file": "salamisound-6633853-dampflokpfeife-3-mal-kurz.mp3",
|
||||||
|
"name": "Pfeife (3x kurz)",
|
||||||
|
"description": "3x Kurzer Signalton"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
flask==3.0.3
|
flask==3.0.3
|
||||||
# Deine mkconnect-lib – je nach Installationsweg
|
# Deine mkconnect-lib – je nach Installationsweg
|
||||||
git+https://git.avalon-skynet.work/oberon/mkconnect-lib.git
|
git+https://git.avalon-skynet.work/oberon/mkconnect-lib.git
|
||||||
# oder falls lokal gebaut: ./path/to/mkconnect-lib
|
# oder falls lokal gebaut: ./path/to/mkconnect-lib
|
||||||
|
pygame
|
||||||
@ -39,18 +39,23 @@ pre code.hljs {
|
|||||||
.hljs-symbol { color: #d16969; }
|
.hljs-symbol { color: #d16969; }
|
||||||
.hljs-literal { color: #d16969; }
|
.hljs-literal { color: #d16969; }
|
||||||
|
|
||||||
pre code {
|
/* Zeilennummern verbessern */
|
||||||
counter-reset: line;
|
pre {
|
||||||
}
|
counter-reset: line;
|
||||||
pre code span {
|
position: relative;
|
||||||
counter-increment: line;
|
}
|
||||||
}
|
|
||||||
/* Optional: Zeilennummern (braucht CSS) */
|
pre code {
|
||||||
pre code::before {
|
padding-left: 4.5em !important;
|
||||||
content: counter(line) " ";
|
}
|
||||||
display: inline-block;
|
|
||||||
width: 3em;
|
pre code::before {
|
||||||
text-align: right;
|
content: counter(line) " ";
|
||||||
color: #6a9955;
|
counter-increment: line;
|
||||||
user-select: none;
|
position: absolute;
|
||||||
}
|
left: 0.8em;
|
||||||
|
color: #858585;
|
||||||
|
text-align: right;
|
||||||
|
width: 3em;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
@ -47,6 +47,10 @@
|
|||||||
<a class="nav-link {% if 'admin' in request.path %}active{% endif %}"
|
<a class="nav-link {% if 'admin' in request.path %}active{% endif %}"
|
||||||
href="{{ url_for('admin') }}">Admin</a>
|
href="{{ url_for('admin') }}">Admin</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if 'soundboard' in request.path %}active{% endif %}"
|
||||||
|
href="{{ url_for('soundboard') }}">Soundboard</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
83
templates/soundboard.html
Normal file
83
templates/soundboard.html
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Soundboard – {{ config.name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container my-5">
|
||||||
|
<h1>Soundboard – {{ config.name }}</h1>
|
||||||
|
<p class="lead mb-4">Wähle einen Sound aus – wird direkt über den Raspberry Pi ausgegeben.</p>
|
||||||
|
|
||||||
|
{% if sounds %}
|
||||||
|
<div class="row g-3">
|
||||||
|
{% for sound in sounds %}
|
||||||
|
<div class="col-md-4 col-lg-3">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<h5 class="card-title">{{ sound.name }}</h5>
|
||||||
|
{% if sound.description %}
|
||||||
|
<p class="card-text text-muted small">{{ sound.description }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-primary mt-3 play-sound-btn"
|
||||||
|
data-sound-id="{{ sound.id }}">
|
||||||
|
<i class="bi bi-play-fill me-2"></i> Abspielen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
In dieser Konfiguration sind noch keine Sounds definiert.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="mt-5 text-center">
|
||||||
|
<a href="{{ url_for('control_page') }}" class="btn btn-secondary">Zurück zur Steuerung</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
document.querySelectorAll('.play-sound-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
const soundId = btn.dataset.soundId;
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span> Spielt...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/play_sound', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ sound_id: soundId })
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
btn.classList.remove('btn-primary');
|
||||||
|
btn.classList.add('btn-success');
|
||||||
|
btn.innerHTML = '<i class="bi bi-check-lg me-2"></i> Gespielt';
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.classList.remove('btn-success');
|
||||||
|
btn.classList.add('btn-primary');
|
||||||
|
btn.innerHTML = '<i class="bi bi-play-fill me-2"></i> Abspielen';
|
||||||
|
btn.disabled = false;
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
alert('Fehler: ' + (data.message || 'Unbekannt'));
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.innerHTML = '<i class="bi bi-play-fill me-2"></i> Abspielen';
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Sound-Play-Fehler:', err);
|
||||||
|
alert('Netzwerkfehler beim Abspielen');
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.innerHTML = '<i class="bi bi-play-fill me-2"></i> Abspielen';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
Loading…
x
Reference in New Issue
Block a user