279 lines
10 KiB
HTML
279 lines
10 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Soundboard – Themen{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container my-5">
|
||
<h1>Soundboard</h1>
|
||
<p class="lead mb-4">Themenbezogene Soundsets, unabhängig vom Hub.</p>
|
||
|
||
<div class="row mb-4">
|
||
<div class="col-md-6">
|
||
<label class="form-label fw-bold">Thema wählen</label>
|
||
<select id="soundboard-select" class="form-select">
|
||
{% for sb in soundboard_configs %}
|
||
<option value="{{ sb.filename }}">{{ sb.name }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-6 d-flex align-items-end">
|
||
<div class="btn-group">
|
||
<button id="btn-load-sb" class="btn btn-primary">Laden</button>
|
||
<button id="btn-play-random" class="btn btn-outline-secondary" disabled>1x Zufällig</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="sb-content" style="display:none">
|
||
<div class="mb-4">
|
||
<label class="form-label fw-bold">Lautstärke</label>
|
||
<input type="range" class="form-range" id="sb-volume" min="0" max="100" value="80" step="5">
|
||
<small class="text-muted">Wirkt global auf alle Soundboard-Kanäle.</small>
|
||
</div>
|
||
<div id="sb-backgrounds" class="mb-4"></div>
|
||
<div id="sb-sounds" class="mb-4"></div>
|
||
<div id="sb-random" class="mb-4"></div>
|
||
<div id="sb-auto" class="mb-4"></div>
|
||
</div>
|
||
|
||
<div class="mt-4">
|
||
<a href="{{ url_for('main.index') }}" class="btn btn-secondary">Zurück</a>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block scripts %}
|
||
<script>
|
||
const selectEl = document.getElementById('soundboard-select');
|
||
const loadBtn = document.getElementById('btn-load-sb');
|
||
const randBtn = document.getElementById('btn-play-random');
|
||
const content = document.getElementById('sb-content');
|
||
|
||
let currentSB = null;
|
||
const statusBox = (msg, type='info') => {
|
||
console.log(msg);
|
||
};
|
||
|
||
async function loadSoundboard(filename) {
|
||
loadBtn.disabled = true;
|
||
try {
|
||
const res = await fetch('/api/soundboard/load', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ filename })
|
||
});
|
||
const data = await res.json();
|
||
if (!data.success) throw new Error(data.message || 'Load failed');
|
||
currentSB = data.soundboard;
|
||
renderSoundboard(currentSB);
|
||
content.style.display = 'block';
|
||
randBtn.disabled = false;
|
||
} catch (e) {
|
||
alert('Konnte Soundboard nicht laden: ' + e.message);
|
||
} finally {
|
||
loadBtn.disabled = false;
|
||
}
|
||
}
|
||
|
||
function renderSoundboard(sb) {
|
||
renderBackgrounds(sb.backgrounds || []);
|
||
renderSounds(sb.sounds || []);
|
||
renderRandom(sb.random_pool || []);
|
||
updateAutoStatus();
|
||
}
|
||
|
||
function renderBackgrounds(list) {
|
||
const container = document.getElementById('sb-backgrounds');
|
||
if (!list.length) { container.innerHTML=''; return; }
|
||
container.innerHTML = '<h4>Hintergrund (Loop)</h4><div class="d-flex flex-wrap gap-2"></div>';
|
||
const wrap = container.querySelector('div');
|
||
list.forEach(s => {
|
||
const btn = document.createElement('button');
|
||
btn.className = 'btn btn-outline-secondary';
|
||
btn.textContent = s.name || s.id || s.file;
|
||
btn.onclick = () => playSound(s);
|
||
const stop = document.createElement('button');
|
||
stop.className = 'btn btn-outline-danger';
|
||
stop.textContent = 'Stop';
|
||
stop.onclick = () => stopSound(s.channel);
|
||
wrap.append(btn, stop);
|
||
});
|
||
}
|
||
|
||
function renderSounds(list) {
|
||
const container = document.getElementById('sb-sounds');
|
||
if (!list.length) { container.innerHTML=''; return; }
|
||
container.innerHTML = '<h4>Einzelsounds</h4><div class="d-flex flex-wrap gap-2"></div>';
|
||
const wrap = container.querySelector('div');
|
||
list.forEach(s => {
|
||
const btn = document.createElement('button');
|
||
btn.className = 'btn btn-primary';
|
||
btn.textContent = s.name || s.id || s.file;
|
||
btn.onclick = () => playSound(s);
|
||
wrap.append(btn);
|
||
});
|
||
}
|
||
|
||
function renderRandom(list) {
|
||
const container = document.getElementById('sb-random');
|
||
if (!list.length) { container.innerHTML=''; randBtn.disabled=true; return; }
|
||
const items = list.map(s => `<li>${s.name || s.id || s.file}</li>`).join('');
|
||
container.innerHTML = '<h4>Zufallspool</h4><p class="text-muted">max 2x pro Stunde je Datei.</p><ul>' + items + '</ul>';
|
||
|
||
renderAutoRandomControls();
|
||
}
|
||
|
||
function renderAutoRandomControls() {
|
||
const container = document.getElementById('sb-auto');
|
||
container.innerHTML = `
|
||
<h4>Automatik (Random)</h4>
|
||
<div class="row g-3 align-items-end">
|
||
<div class="col-md-3">
|
||
<label class="form-label">Intervall min (Minuten)</label>
|
||
<input id="auto-interval-min" type="number" class="form-control" value="5" min="1" max="60">
|
||
</div>
|
||
<div class="col-md-3">
|
||
<label class="form-label">Intervall max (Minuten)</label>
|
||
<input id="auto-interval-max" type="number" class="form-control" value="10" min="1" max="120">
|
||
</div>
|
||
<div class="col-md-3">
|
||
<label class="form-label">Startverzögerung min (Minuten)</label>
|
||
<input id="auto-delay-min" type="number" class="form-control" value="3" min="0" max="60">
|
||
</div>
|
||
<div class="col-md-3">
|
||
<label class="form-label">Startverzögerung max (Minuten)</label>
|
||
<input id="auto-delay-max" type="number" class="form-control" value="12" min="0" max="120">
|
||
</div>
|
||
</div>
|
||
<div class="mt-3 d-flex gap-2 align-items-center">
|
||
<button id="auto-start" class="btn btn-success" ${!randBtn.disabled ? '' : 'disabled'}>Auto-Start</button>
|
||
<button id="auto-stop" class="btn btn-outline-danger" disabled>Stop</button>
|
||
<span id="auto-status" class="text-muted ms-2">Aus</span>
|
||
</div>
|
||
`;
|
||
|
||
const start = document.getElementById('auto-start');
|
||
const stop = document.getElementById('auto-stop');
|
||
const status = document.getElementById('auto-status');
|
||
|
||
start.onclick = async () => {
|
||
const imin = Math.max(1, parseInt(document.getElementById('auto-interval-min').value, 10) || 5);
|
||
const imax = Math.max(imin, parseInt(document.getElementById('auto-interval-max').value, 10) || 10);
|
||
const dmin = Math.max(0, parseInt(document.getElementById('auto-delay-min').value, 10) || 3);
|
||
const dmax = Math.max(dmin, parseInt(document.getElementById('auto-delay-max').value, 10) || 12);
|
||
|
||
const res = await fetch('/api/soundboard/auto_start', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ interval_min: imin, interval_max: imax, delay_min: dmin, delay_max: dmax })
|
||
});
|
||
const data = await res.json();
|
||
if (data.success) {
|
||
if (data.next_seconds !== undefined && data.next_seconds !== null) {
|
||
status.textContent = `Aktiv (nächster in ${(data.next_seconds/60).toFixed(1)} min)`;
|
||
} else {
|
||
status.textContent = 'Aktiv';
|
||
}
|
||
start.disabled = true;
|
||
stop.disabled = false;
|
||
} else {
|
||
statusBox(data.message || 'Auto-Start fehlgeschlagen', 'warn');
|
||
}
|
||
};
|
||
|
||
stop.onclick = async () => {
|
||
const res = await fetch('/api/soundboard/auto_stop', { method: 'POST' });
|
||
const data = await res.json();
|
||
if (data.success) {
|
||
status.textContent = 'Aus';
|
||
start.disabled = false;
|
||
stop.disabled = true;
|
||
}
|
||
};
|
||
}
|
||
|
||
async function updateAutoStatus() {
|
||
const res = await fetch('/api/soundboard/status');
|
||
const data = await res.json();
|
||
const status = document.getElementById('auto-status');
|
||
const start = document.getElementById('auto-start');
|
||
const stop = document.getElementById('auto-stop');
|
||
if (!status || !start || !stop) return;
|
||
if (data.active) {
|
||
if (data.next_seconds !== null && data.next_seconds !== undefined) {
|
||
status.textContent = `Aktiv (nächster in ${(data.next_seconds/60).toFixed(1)} min)`;
|
||
} else {
|
||
status.textContent = 'Aktiv';
|
||
}
|
||
start.disabled = true;
|
||
stop.disabled = false;
|
||
// optional: Formularwerte mit Serverparametern füllen
|
||
if (data.interval_min) document.getElementById('auto-interval-min').value = data.interval_min;
|
||
if (data.interval_max) document.getElementById('auto-interval-max').value = data.interval_max;
|
||
if (data.delay_min !== undefined && data.delay_min !== null) document.getElementById('auto-delay-min').value = data.delay_min;
|
||
if (data.delay_max !== undefined && data.delay_max !== null) document.getElementById('auto-delay-max').value = data.delay_max;
|
||
} else {
|
||
status.textContent = 'Aus';
|
||
start.disabled = false;
|
||
stop.disabled = true;
|
||
}
|
||
}
|
||
|
||
async function playSound(sound) {
|
||
const payload = {
|
||
sound_id: sound.id || sound.file,
|
||
channel: sound.channel ?? null,
|
||
loop: !!sound.loop
|
||
};
|
||
const res = await fetch('/api/play_sound', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(payload)
|
||
});
|
||
const data = await res.json();
|
||
if (!data.success) statusBox(data.message || 'Fehler beim Abspielen', 'warn');
|
||
}
|
||
|
||
async function stopSound(channel) {
|
||
const res = await fetch('/api/stop_sound', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ channel })
|
||
});
|
||
const data = await res.json();
|
||
if (!data.success) statusBox(data.message || 'Fehler beim Stoppen', 'warn');
|
||
}
|
||
|
||
loadBtn.onclick = () => loadSoundboard(selectEl.value);
|
||
randBtn.onclick = async () => {
|
||
await playRandom();
|
||
};
|
||
|
||
async function playRandom() {
|
||
const res = await fetch('/api/soundboard/play_random', { method: 'POST' });
|
||
const data = await res.json();
|
||
if (!data.success) statusBox(data.message || 'Random fehlgeschlagen', 'warn');
|
||
}
|
||
|
||
// Lautstärke
|
||
const vol = document.getElementById('sb-volume');
|
||
if (vol) {
|
||
vol.addEventListener('input', async () => {
|
||
const v = parseInt(vol.value, 10) / 100;
|
||
await fetch('/api/set_volume', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ volume: v })
|
||
});
|
||
});
|
||
}
|
||
|
||
// Autoload erstes Thema (falls vorhanden)
|
||
if (selectEl && selectEl.value) {
|
||
loadSoundboard(selectEl.value);
|
||
} else {
|
||
updateAutoStatus();
|
||
}
|
||
</script>
|
||
{% endblock %}
|