import 'dart:async'; import 'package:audioplayers/audioplayers.dart'; import 'package:pad_scanner/services/app_config_service.dart'; class SoundService { static const _keySuccess = 'sound_success'; static const _keyFailure = 'sound_failure'; static const _keyBeep = 'sound_beep'; static const _keyError = 'sound_error'; static const _keyAlert = 'sound_alert'; final _player = AudioPlayer(); final _alertPlayer = AudioPlayer(); final _configService = AppConfigService(); Timer? _alertTimer; // --- Path getters --- Future getSuccessPath() => _configService.getString(_keySuccess); Future getFailurePath() => _configService.getString(_keyFailure); Future getBeepPath() => _configService.getString(_keyBeep); Future getErrorPath() => _configService.getString(_keyError); Future getAlertPath() => _configService.getString(_keyAlert); // --- Path setters --- Future setSuccessPath(String? path) => _setConfigPath(_keySuccess, path); Future setFailurePath(String? path) => _setConfigPath(_keyFailure, path); Future setBeepPath(String? path) => _setConfigPath(_keyBeep, path); Future setErrorPath(String? path) => _setConfigPath(_keyError, path); Future setAlertPath(String? path) => _setConfigPath(_keyAlert, path); Future _setConfigPath(String key, String? path) async { if (path != null) { await _configService.setString(key, path); } else { final config = await _configService.loadConfig(); config.remove(key); } } // --- Playback methods --- Future playSuccess() async { final path = await getSuccessPath(); if (path != null) { await _player.play(DeviceFileSource(path)); } } Future playFailure() async { final path = await getFailurePath(); if (path != null) { await _player.play(DeviceFileSource(path)); } } Future playBeep() async { final path = await getBeepPath(); if (path != null) { await _player.play(DeviceFileSource(path)); } } Future playError() async { final path = await getErrorPath(); if (path != null) { await _player.play(DeviceFileSource(path)); } } /// Play alert sound in a loop until [stopAlert] is called. /// Loops up to 10 times as a safety guard (~15 seconds max). Future playAlertLoop() async { final path = await getAlertPath(); if (path == null) return; _alertTimer?.cancel(); var count = 0; _alertTimer = Timer.periodic(const Duration(milliseconds: 1500), (_) async { if (count >= 10) { _alertTimer?.cancel(); return; } await _alertPlayer.play(DeviceFileSource(path)); count++; }); // Play immediately first time await _alertPlayer.play(DeviceFileSource(path)); count++; } /// Stop the alert loop. void stopAlert() { _alertTimer?.cancel(); _alertTimer = null; _alertPlayer.stop(); } Future play(String path) async { await _player.play(DeviceFileSource(path)); } Future dispose() async { _alertTimer?.cancel(); await _player.dispose(); await _alertPlayer.dispose(); } }