fixxes for background playbacks
This commit is contained in:
parent
f580d67ccb
commit
36d1a36549
@ -15,6 +15,9 @@ from app.utils.helpers import load_default_sounds, load_soundboard_configs, load
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
audio_lock = threading.Lock()
|
||||
auto_random_lock = threading.Lock()
|
||||
auto_random_timer = None
|
||||
auto_random_params = {}
|
||||
|
||||
# Sound-Cache: path -> pygame.mixer.Sound
|
||||
loaded_sounds = {}
|
||||
@ -82,6 +85,55 @@ def _play_sound_entry(sound_entry, channel_req=None, loop_req=0):
|
||||
return None # Erfolg
|
||||
|
||||
|
||||
def _pick_random_sound():
|
||||
if state.current_soundboard is None:
|
||||
return None, "Kein Soundboard geladen"
|
||||
rnd_list = state.current_soundboard.get('random_pool') or state.current_soundboard.get('sounds', [])
|
||||
if not rnd_list:
|
||||
return None, "Keine Random-Sounds definiert"
|
||||
|
||||
now = time.time()
|
||||
candidates = []
|
||||
for sound in rnd_list:
|
||||
sid = sound.get('id') or sound.get('file')
|
||||
dq = _prune_history(sid, now)
|
||||
if len(dq) < MAX_PER_HOUR:
|
||||
candidates.append(sound)
|
||||
|
||||
if not candidates:
|
||||
return None, "Limit erreicht (2x pro Stunde)"
|
||||
|
||||
sound_entry = random.choice(candidates)
|
||||
sid = sound_entry.get('id') or sound_entry.get('file')
|
||||
random_history[sid].append(now)
|
||||
sound_entry.setdefault('id', sid)
|
||||
return sound_entry, None
|
||||
|
||||
|
||||
def _auto_random_tick():
|
||||
global auto_random_timer
|
||||
with auto_random_lock:
|
||||
if not state.auto_random_active:
|
||||
return
|
||||
imin = auto_random_params.get('imin', 5)
|
||||
imax = auto_random_params.get('imax', 10)
|
||||
# Play one random sound
|
||||
sound_entry, err = _pick_random_sound()
|
||||
if sound_entry:
|
||||
_play_sound_entry(sound_entry, sound_entry.get('channel'), sound_entry.get('loop'))
|
||||
# schedule next
|
||||
delay = rand_between(imin, imax) * 60
|
||||
with auto_random_lock:
|
||||
if state.auto_random_active:
|
||||
auto_random_timer = threading.Timer(delay, _auto_random_tick)
|
||||
auto_random_timer.daemon = True
|
||||
auto_random_timer.start()
|
||||
|
||||
|
||||
def rand_between(a, b):
|
||||
return a + random.random() * (b - a)
|
||||
|
||||
|
||||
def _prune_history(sound_id, now):
|
||||
dq = random_history[sound_id]
|
||||
while dq and now - dq[0] > WINDOW_SECONDS:
|
||||
@ -331,6 +383,8 @@ def api_soundboard_load():
|
||||
current_app.config['SOUNDBOARD_CONFIG_DIR'],
|
||||
current_app.config['SOUNDS_DIR'])
|
||||
state.current_soundboard = sb
|
||||
# Stoppe ggf. laufende Auto-Randoms beim Laden neuer Themen
|
||||
_stop_auto_random_internal()
|
||||
logger.info(f"Soundboard geladen: {filename}")
|
||||
return jsonify({"success": True, "soundboard": sb})
|
||||
except Exception as e:
|
||||
@ -343,30 +397,69 @@ def api_soundboard_play_random():
|
||||
if state.current_soundboard is None:
|
||||
return jsonify({"success": False, "message": "Kein Soundboard geladen"}), 400
|
||||
|
||||
rnd_list = state.current_soundboard.get('random_pool') or state.current_soundboard.get('sounds', [])
|
||||
if not rnd_list:
|
||||
return jsonify({"success": False, "message": "Keine Random-Sounds definiert"}), 400
|
||||
|
||||
now = time.time()
|
||||
candidates = []
|
||||
for sound in rnd_list:
|
||||
sid = sound.get('id') or sound.get('file')
|
||||
dq = _prune_history(sid, now)
|
||||
if len(dq) < MAX_PER_HOUR:
|
||||
candidates.append(sound)
|
||||
|
||||
if not candidates:
|
||||
return jsonify({"success": False, "message": "Limit erreicht (2x pro Stunde)"}), 429
|
||||
|
||||
sound_entry = random.choice(candidates)
|
||||
sid = sound_entry.get('id') or sound_entry.get('file')
|
||||
sound_entry, err = _pick_random_sound()
|
||||
if err:
|
||||
return jsonify({"success": False, "message": err}), 429 if "Limit" in err else 400
|
||||
|
||||
res = _play_sound_entry(sound_entry)
|
||||
if res is not None:
|
||||
return res # already Response
|
||||
|
||||
random_history[sid].append(now)
|
||||
return jsonify({"success": True, "message": f"Random: {sound_entry.get('name', sid)}"})
|
||||
return jsonify({"success": True, "message": f"Random: {sound_entry.get('name', sound_entry.get('id'))}"})
|
||||
|
||||
|
||||
@api_bp.route('/soundboard/auto_start', methods=['POST'])
|
||||
def api_soundboard_auto_start():
|
||||
if state.current_soundboard is None:
|
||||
return jsonify({"success": False, "message": "Kein Soundboard geladen"}), 400
|
||||
data = request.get_json() or {}
|
||||
imin = float(data.get('interval_min', 5))
|
||||
imax = float(data.get('interval_max', 10))
|
||||
dmin = float(data.get('delay_min', 3))
|
||||
dmax = float(data.get('delay_max', 12))
|
||||
if imax < imin: imax = imin
|
||||
if dmax < dmin: dmax = dmin
|
||||
|
||||
delay = rand_between(dmin, dmax) * 60
|
||||
with auto_random_lock:
|
||||
_stop_auto_random_internal()
|
||||
state.auto_random_active = True
|
||||
auto_random_params['imin'] = imin
|
||||
auto_random_params['imax'] = imax
|
||||
auto_random_params['dmin'] = dmin
|
||||
auto_random_params['dmax'] = dmax
|
||||
global auto_random_timer
|
||||
auto_random_timer = threading.Timer(delay, _auto_random_tick)
|
||||
auto_random_timer.daemon = True
|
||||
auto_random_timer.start()
|
||||
logger.info(f"Auto-Random gestartet (delay {delay/60:.1f} min, intervall {imin}-{imax} min)")
|
||||
return jsonify({"success": True, "message": "Auto-Random gestartet",
|
||||
"next_seconds": delay})
|
||||
|
||||
|
||||
@api_bp.route('/soundboard/auto_stop', methods=['POST'])
|
||||
def api_soundboard_auto_stop():
|
||||
stopped = _stop_auto_random_internal()
|
||||
return jsonify({"success": True, "message": "Auto-Random gestoppt", "was_running": stopped})
|
||||
|
||||
|
||||
@api_bp.route('/soundboard/status', methods=['GET'])
|
||||
def api_soundboard_status():
|
||||
with auto_random_lock:
|
||||
active = state.auto_random_active
|
||||
return jsonify({"active": active})
|
||||
|
||||
|
||||
def _stop_auto_random_internal():
|
||||
global auto_random_timer
|
||||
stopped = False
|
||||
with auto_random_lock:
|
||||
if auto_random_timer:
|
||||
auto_random_timer.cancel()
|
||||
auto_random_timer = None
|
||||
stopped = True
|
||||
state.auto_random_active = False
|
||||
return stopped
|
||||
|
||||
|
||||
@api_bp.route('/stop_sound', methods=['POST'])
|
||||
|
||||
@ -5,6 +5,7 @@ current_hub = None
|
||||
current_module = None
|
||||
current_device = None
|
||||
current_soundboard = None # aktives Soundboard-Thema
|
||||
auto_random_active = False
|
||||
|
||||
# Optional: Funktionen zum Setzen/Resetten (für Klarheit)
|
||||
def reset_state():
|
||||
|
||||
@ -156,44 +156,36 @@
|
||||
const stop = document.getElementById('auto-stop');
|
||||
const status = document.getElementById('auto-status');
|
||||
|
||||
start.onclick = () => {
|
||||
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 delayMs = randBetween(dmin, dmax) * 60 * 1000;
|
||||
scheduleAuto(imin, imax, delayMs, status, start, stop);
|
||||
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) {
|
||||
status.textContent = `Aktiv (nächster in ${(data.next_seconds/60).toFixed(1)} min)`;
|
||||
start.disabled = true;
|
||||
stop.disabled = false;
|
||||
} else {
|
||||
statusBox(data.message || 'Auto-Start fehlgeschlagen', 'warn');
|
||||
}
|
||||
};
|
||||
|
||||
stop.onclick = () => stopAuto(status, start, stop);
|
||||
}
|
||||
|
||||
function randBetween(min, max) {
|
||||
return min + Math.random() * (max - min);
|
||||
}
|
||||
|
||||
function scheduleAuto(imin, imax, delayMs, status, startBtn, stopBtn) {
|
||||
stopAuto(status, startBtn, stopBtn);
|
||||
status.textContent = `Geplant in ${(delayMs/60000).toFixed(1)} min`;
|
||||
startBtn.disabled = true;
|
||||
stopBtn.disabled = false;
|
||||
autoTimer = setTimeout(async function tick() {
|
||||
await playRandom();
|
||||
const nextMs = randBetween(imin, imax) * 60 * 1000;
|
||||
status.textContent = `Nächster in ${(nextMs/60000).toFixed(1)} min`;
|
||||
autoTimer = setTimeout(tick, nextMs);
|
||||
}, delayMs);
|
||||
}
|
||||
|
||||
function stopAuto(status, startBtn, stopBtn) {
|
||||
if (autoTimer) {
|
||||
clearTimeout(autoTimer);
|
||||
autoTimer = null;
|
||||
}
|
||||
if (status) status.textContent = 'Aus';
|
||||
if (startBtn) startBtn.disabled = false;
|
||||
if (stopBtn) stopBtn.disabled = true;
|
||||
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 playSound(sound) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user