Perché la sintesi vocale conta
La sintesi vocale (Text-To-Speech, o TTS) trasforma il testo in parlato ed è una funzionalità sempre più richiesta: dalle app di accessibilità agli assistenti vocali, dai lettori di articoli alle app educative. In Flutter, il pacchetto flutter_tts offre un'interfaccia semplice e cross-platform (Android, iOS, Web, macOS, Windows) sfruttando i motori TTS nativi di ciascuna piattaforma.
In questo articolo vediamo come integrarlo, configurarlo e gestire i casi reali che incontrerai in produzione.
Installazione
Aggiungi la dipendenza al tuo pubspec.yaml:
dependencies:
flutter_tts: ^4.2.0
Su iOS, apri ios/Runner/Info.plist e assicurati che l'app abbia accesso ai servizi vocali. Su versioni recenti non serve un permesso esplicito per il TTS, ma è buona norma testare su dispositivo fisico perché il simulatore ha voci limitate.
Su Android non è richiesto alcun permesso particolare, ma il motore TTS di Google deve essere installato (lo è di default sulla maggior parte dei dispositivi).
Primo utilizzo
Creiamo una piccola classe wrapper che incapsula la logica TTS, così da tenerla separata dalla UI:
import 'package:flutter_tts/flutter_tts.dart';
class TtsService {
final FlutterTts _tts = FlutterTts();
Future<void> init() async {
await _tts.setLanguage('it-IT');
await _tts.setSpeechRate(0.5); // 0.0 - 1.0
await _tts.setVolume(1.0); // 0.0 - 1.0
await _tts.setPitch(1.0); // 0.5 - 2.0
}
Future<void> speak(String text) async {
if (text.trim().isEmpty) return;
await _tts.speak(text);
}
Future<void> stop() async {
await _tts.stop();
}
Future<void> pause() async {
await _tts.pause();
}
}
Nota sulla
speechRate: la scala non è lineare tra le piattaforme. Su Android un valore di1.0corrisponde alla velocità normale, mentre su iOS lo stesso valore risulta molto più veloce. Il pacchetto normalizza in parte questo comportamento, ma è consigliabile testare su entrambe.
Gestire gli stati con i callback
Un errore comune è non ascoltare lo stato del motore TTS. Sapere quando il parlato inizia, finisce o viene interrotto è fondamentale per aggiornare la UI (ad esempio evidenziare un pulsante "stop"):
enum TtsState { playing, stopped, paused, continued }
class TtsService {
final FlutterTts _tts = FlutterTts();
TtsState state = TtsState.stopped;
void setupHandlers({VoidCallback? onStateChange}) {
_tts.setStartHandler(() {
state = TtsState.playing;
onStateChange?.call();
});
_tts.setCompletionHandler(() {
state = TtsState.stopped;
onStateChange?.call();
});
_tts.setCancelHandler(() {
state = TtsState.stopped;
onStateChange?.call();
});
_tts.setPauseHandler(() {
state = TtsState.paused;
onStateChange?.call();
});
_tts.setErrorHandler((msg) {
state = TtsState.stopped;
debugPrint('Errore TTS: $msg');
onStateChange?.call();
});
}
}
Verificare la disponibilità della lingua
Non tutte le voci sono installate su ogni dispositivo. Prima di impostare una lingua è buona prassi verificarne la disponibilità:
Future<bool> isLanguageAvailable(String lang) async {
final available = await _tts.isLanguageAvailable(lang);
return available == true;
}
Future<List<String>> getLanguages() async {
final langs = await _tts.getLanguages;
return List<String>.from(langs);
}
Se la lingua desiderata non è disponibile, puoi proporre all'utente un fallback o, su Android, aprire le impostazioni del motore TTS.
Evidenziare le parole durante la lettura
Una funzionalità potente per app di lettura è l'evidenziazione progressiva del testo. flutter_tts fornisce un handler dedicato (supportato su Android e iOS 7+):
_tts.setProgressHandler((String text, int start, int end, String word) {
// start ed end sono gli indici del carattere nel testo originale
// aggiorna lo stato per evidenziare la parola corrente
debugPrint('Sto leggendo: "$word" ($start-$end)');
});
Combinando questi indici con un RichText puoi colorare la porzione di testo in fase di lettura, creando un effetto karaoke molto apprezzato nelle app didattiche.
Un esempio di widget completo
class ReaderScreen extends StatefulWidget {
const ReaderScreen({super.key});
@override
State<ReaderScreen> createState() => _ReaderScreenState();
}
class _ReaderScreenState extends State<ReaderScreen> {
final _tts = FlutterTts();
bool _isPlaying = false;
final _testo = 'Benvenuto nella guida a flutter_tts, la sintesi vocale in Flutter.';
@override
void initState() {
super.initState();
_tts.setLanguage('it-IT');
_tts.setSpeechRate(0.5);
_tts.setCompletionHandler(() => setState(() => _isPlaying = false));
}
Future<void> _toggle() async {
if (_isPlaying) {
await _tts.stop();
setState(() => _isPlaying = false);
} else {
setState(() => _isPlaying = true);
await _tts.speak(_testo);
}
}
@override
void dispose() {
_tts.stop();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Lettore vocale')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Text(_testo, style: Theme.of(context).textTheme.bodyLarge),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: _toggle,
icon: Icon(_isPlaying ? Icons.stop : Icons.play_arrow),
label: Text(_isPlaying ? 'Ferma' : 'Leggi'),
),
],
),
),
);
}
}
Best practice
- Chiama sempre
stop()neldispose: se l'utente esce dalla schermata mentre l'app parla, evitare che la voce continui in background migliora l'esperienza. - Gestisci la sessione audio su iOS: usa
setSharedInstance(true)e configura le categorie audio consetIosAudioCategoryse vuoi che il parlato conviva con altre app musicali o funzioni in modalità silenziosa. - Testa su dispositivo reale: i simulatori hanno un set ridotto di voci e alcune funzionalità (come il progress handler) possono comportarsi diversamente.
- Fai attenzione a
await speak: il completamento delFuturerestituito daspeakdipende dalla configurazione della piattaforma. Per un controllo affidabile usaawaitSpeakCompletion(true)su Android.
await _tts.awaitSpeakCompletion(true);
await _tts.speak('Questo Future si completa a fine lettura');
print('Lettura terminata');
Conclusioni
Con flutter_tts bastano poche righe per dare voce alle tue app, ma la vera differenza la fanno i dettagli: gestione degli stati, verifica delle lingue, configurazione della sessione audio e evidenziazione progressiva. Integrare la sintesi vocale non è solo una feature accattivante, ma un passo importante verso app più accessibili e inclusive.
