feat: Mise à jour à la version 1.1.14 et refonte du support Audio/TTS pour le Web
- Mise à jour de la version de l'application à `1.1.14` dans `app_version.dart` et `version.json`. - Migration de `AudioFeedbackService` vers l'API Web native (`dart:js_interop`, `package:web`) pour corriger les problèmes d'autoplay et supprimer la dépendance `audioplayers`. - Réécriture de `TextToSpeechService` utilisant `window.speechSynthesis` en remplacement de `flutter_tts` pour une meilleure compatibilité Web (notamment sous Linux). - Suppression des dépendances obsolètes `audioplayers` et `flutter_tts` du `pubspec.yaml`. - Ajout d'une gestion de file d'attente (`_scanQueue`) dans `EventPreparationPage` pour traiter les scans de codes-barres de manière séquentielle. - Intégration d'un bouton de diagnostic (`AudioDiagnosticButton`) pour tester manuellement l'audio et la synthèse vocale. - Ajout d'un script de test JavaScript `test_audio_tts.js` pour faciliter le débogage dans la console du navigateur. - Ajout de directives de style et d'architecture Dart/Flutter dans `.github/agents/`.
This commit is contained in:
@@ -1,54 +1,128 @@
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'dart:js_interop';
|
||||
import 'package:web/web.dart' as web;
|
||||
import 'package:em2rp/utils/debug_log.dart';
|
||||
|
||||
/// Service pour émettre des feedbacks sonores lors des interactions
|
||||
/// Service pour émettre des feedbacks sonores lors des interactions (Web)
|
||||
class AudioFeedbackService {
|
||||
static final AudioPlayer _player = AudioPlayer();
|
||||
static bool _isInitialized = false;
|
||||
static bool _audioUnlocked = false;
|
||||
|
||||
/// Initialiser le service
|
||||
static Future<void> _initialize() async {
|
||||
if (_isInitialized) return;
|
||||
|
||||
try {
|
||||
DebugLog.info('[AudioFeedbackService] Initializing audio service for Web...');
|
||||
_isInitialized = true;
|
||||
} catch (e) {
|
||||
DebugLog.error('[AudioFeedbackService] Error initializing audio', e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Débloquer l'audio (à appeler lors de la première interaction utilisateur)
|
||||
static Future<void> unlockAudio() async {
|
||||
if (_audioUnlocked) {
|
||||
DebugLog.info('[AudioFeedbackService] Audio already unlocked');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!_isInitialized) await _initialize();
|
||||
|
||||
DebugLog.info('[AudioFeedbackService] Attempting to unlock audio...');
|
||||
|
||||
// Créer un audio temporaire et le jouer avec volume 0
|
||||
final tempAudio = web.HTMLAudioElement();
|
||||
tempAudio.src = 'assets/assets/sounds/ok.mp3';
|
||||
tempAudio.volume = 0.01; // Volume très faible mais pas 0
|
||||
tempAudio.preload = 'auto';
|
||||
|
||||
try {
|
||||
await tempAudio.play().toDart;
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
tempAudio.pause();
|
||||
_audioUnlocked = true;
|
||||
DebugLog.info('[AudioFeedbackService] ✓ Audio unlocked successfully');
|
||||
} catch (e) {
|
||||
DebugLog.warning('[AudioFeedbackService] ⚠ Could not unlock audio: $e');
|
||||
DebugLog.warning('[AudioFeedbackService] User interaction may be required');
|
||||
}
|
||||
} catch (e) {
|
||||
DebugLog.error('[AudioFeedbackService] Error unlocking audio', e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Créer et jouer un son
|
||||
static Future<void> _playSound(String assetPath, double volume) async {
|
||||
try {
|
||||
if (!_isInitialized) await _initialize();
|
||||
|
||||
DebugLog.info('[AudioFeedbackService] Attempting to play: $assetPath (volume: $volume)');
|
||||
|
||||
// Créer un nouvel élément audio à chaque fois
|
||||
final audio = web.HTMLAudioElement();
|
||||
audio.src = assetPath;
|
||||
audio.volume = volume;
|
||||
audio.preload = 'auto';
|
||||
|
||||
// Ajouter des événements pour debug
|
||||
audio.onloadeddata = ((web.Event event) {
|
||||
DebugLog.info('[AudioFeedbackService] Audio data loaded: $assetPath');
|
||||
}.toJS);
|
||||
|
||||
audio.onerror = ((web.Event event) {
|
||||
DebugLog.error('[AudioFeedbackService] ✗ Audio error for $assetPath: ${audio.error}');
|
||||
}.toJS);
|
||||
|
||||
audio.onplay = ((web.Event event) {
|
||||
DebugLog.info('[AudioFeedbackService] Audio started playing');
|
||||
}.toJS);
|
||||
|
||||
audio.onended = ((web.Event event) {
|
||||
DebugLog.info('[AudioFeedbackService] Audio finished playing');
|
||||
}.toJS);
|
||||
|
||||
try {
|
||||
// Essayer de jouer
|
||||
await audio.play().toDart;
|
||||
DebugLog.info('[AudioFeedbackService] ✓ Sound played successfully');
|
||||
} catch (e) {
|
||||
DebugLog.error('[AudioFeedbackService] ✗ Play failed: $e');
|
||||
|
||||
// Si c'est un problème d'autoplay, essayer de débloquer
|
||||
if (!_audioUnlocked) {
|
||||
DebugLog.info('[AudioFeedbackService] Trying to unlock audio on error...');
|
||||
_audioUnlocked = false; // Forcer le déblocage
|
||||
await unlockAudio();
|
||||
|
||||
// Réessayer une fois après déblocage
|
||||
try {
|
||||
final retryAudio = web.HTMLAudioElement();
|
||||
retryAudio.src = assetPath;
|
||||
retryAudio.volume = volume;
|
||||
await retryAudio.play().toDart;
|
||||
DebugLog.info('[AudioFeedbackService] ✓ Sound played on retry');
|
||||
} catch (retryError) {
|
||||
DebugLog.error('[AudioFeedbackService] ✗ Retry also failed: $retryError');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
DebugLog.error('[AudioFeedbackService] Error in _playSound', e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Jouer un son de succès
|
||||
static Future<void> playSuccessBeep() async {
|
||||
try {
|
||||
if (kIsWeb) {
|
||||
// Sur Web, utiliser le chemin absolu
|
||||
await _player.play(UrlSource('assets/sounds/ok.mp3'));
|
||||
} else {
|
||||
// Sur mobile/desktop, utiliser AssetSource
|
||||
await _player.play(AssetSource('sounds/ok.mp3'));
|
||||
}
|
||||
await HapticFeedback.lightImpact();
|
||||
} catch (e) {
|
||||
DebugLog.error('[AudioFeedbackService] Error playing success beep', e);
|
||||
}
|
||||
await _playSound('assets/assets/sounds/ok.mp3', 1.0);
|
||||
}
|
||||
|
||||
/// Jouer un son d'erreur
|
||||
static Future<void> playErrorBeep() async {
|
||||
try {
|
||||
if (kIsWeb) {
|
||||
// Sur Web, utiliser le chemin absolu
|
||||
await _player.play(UrlSource('assets/sounds/error.mp3'));
|
||||
} else {
|
||||
// Sur mobile/desktop, utiliser AssetSource
|
||||
await _player.play(AssetSource('sounds/error.mp3'));
|
||||
}
|
||||
await HapticFeedback.heavyImpact();
|
||||
} catch (e) {
|
||||
DebugLog.error('[AudioFeedbackService] Error playing error beep', e);
|
||||
}
|
||||
await _playSound('assets/assets/sounds/error.mp3', 0.8);
|
||||
}
|
||||
|
||||
/// Jouer une vibration haptique (si disponible)
|
||||
static Future<void> playHapticFeedback() async {
|
||||
try {
|
||||
await HapticFeedback.mediumImpact();
|
||||
} catch (e) {
|
||||
DebugLog.error('[AudioFeedbackService] Error playing haptic feedback', e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Jouer un feedback complet (son + vibration)
|
||||
/// Jouer un feedback complet (son uniquement, sans vibration)
|
||||
static Future<void> playFullFeedback({bool isSuccess = true}) async {
|
||||
if (isSuccess) {
|
||||
await playSuccessBeep();
|
||||
@@ -59,6 +133,12 @@ class AudioFeedbackService {
|
||||
|
||||
/// Nettoyer les ressources
|
||||
static Future<void> dispose() async {
|
||||
await _player.dispose();
|
||||
try {
|
||||
_isInitialized = false;
|
||||
_audioUnlocked = false;
|
||||
} catch (e) {
|
||||
DebugLog.error('[AudioFeedbackService] Error disposing', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user