Last active
June 18, 2024 06:51
-
-
Save dlutton/30b065449cb2eb941dfd6bef86aeefaf to your computer and use it in GitHub Desktop.
TTS example using setProgressHandler
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'dart:io' show Platform; | |
import 'package:flutter/foundation.dart' show kIsWeb; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter_tts/flutter_tts.dart'; | |
void main() => runApp(MyApp()); | |
class MyApp extends StatefulWidget { | |
@override | |
_MyAppState createState() => _MyAppState(); | |
} | |
enum TtsState { playing, stopped, paused, continued } | |
class _MyAppState extends State<MyApp> { | |
late FlutterTts flutterTts; | |
dynamic languages; | |
String? language; | |
double volume = 0.5; | |
double pitch = 1.0; | |
double rate = 0.5; | |
bool isCurrentLanguageInstalled = false; | |
int start = 0; | |
int end = 0; | |
String? _newVoiceText; | |
TtsState ttsState = TtsState.stopped; | |
get isPlaying => ttsState == TtsState.playing; | |
get isStopped => ttsState == TtsState.stopped; | |
get isPaused => ttsState == TtsState.paused; | |
get isContinued => ttsState == TtsState.continued; | |
bool get isIOS => !kIsWeb && Platform.isIOS; | |
bool get isAndroid => !kIsWeb && Platform.isAndroid; | |
bool get isWeb => kIsWeb; | |
@override | |
initState() { | |
super.initState(); | |
initTts(); | |
} | |
initTts() { | |
flutterTts = FlutterTts(); | |
_getLanguages(); | |
if (isAndroid) { | |
_getEngines(); | |
} | |
flutterTts.setStartHandler(() { | |
setState(() { | |
print("Playing"); | |
ttsState = TtsState.playing; | |
}); | |
}); | |
flutterTts.setCompletionHandler(() { | |
setState(() { | |
print("Complete"); | |
ttsState = TtsState.stopped; | |
}); | |
}); | |
flutterTts.setCancelHandler(() { | |
setState(() { | |
print("Cancel"); | |
ttsState = TtsState.stopped; | |
}); | |
}); | |
if (isWeb || isIOS) { | |
flutterTts.setPauseHandler(() { | |
setState(() { | |
print("Paused"); | |
ttsState = TtsState.paused; | |
}); | |
}); | |
flutterTts.setContinueHandler(() { | |
setState(() { | |
print("Continued"); | |
ttsState = TtsState.continued; | |
}); | |
}); | |
} | |
flutterTts.setErrorHandler((msg) { | |
setState(() { | |
print("error: $msg"); | |
ttsState = TtsState.stopped; | |
}); | |
}); | |
flutterTts.setProgressHandler( | |
(String text, int startOffset, int endOffset, String word) { | |
setState(() { | |
start = startOffset; | |
end = endOffset; | |
}); | |
}); | |
} | |
Future _getLanguages() async { | |
languages = await flutterTts.getLanguages; | |
if (languages != null) setState(() => languages); | |
} | |
Future _getEngines() async { | |
var engines = await flutterTts.getEngines; | |
if (engines != null) { | |
for (dynamic engine in engines) { | |
print(engine); | |
} | |
} | |
} | |
Future _speak() async { | |
await flutterTts.setVolume(volume); | |
await flutterTts.setSpeechRate(rate); | |
await flutterTts.setPitch(pitch); | |
if (_newVoiceText != null) { | |
await flutterTts.awaitSpeakCompletion(true); | |
await flutterTts.speak(_newVoiceText!); | |
} | |
} | |
Future _stop() async { | |
var result = await flutterTts.stop(); | |
if (result == 1) setState(() => ttsState = TtsState.stopped); | |
} | |
Future _pause() async { | |
var result = await flutterTts.pause(); | |
if (result == 1) setState(() => ttsState = TtsState.paused); | |
} | |
@override | |
void dispose() { | |
super.dispose(); | |
flutterTts.stop(); | |
} | |
List<DropdownMenuItem<String>> getLanguageDropDownMenuItems() { | |
var items = <DropdownMenuItem<String>>[]; | |
for (dynamic type in languages) { | |
items.add(DropdownMenuItem(value: type as String, child: Text(type))); | |
} | |
return items; | |
} | |
void changedLanguageDropDownItem(String? selectedType) { | |
setState(() { | |
language = selectedType; | |
flutterTts.setLanguage(language!); | |
if (isAndroid) { | |
flutterTts | |
.isLanguageInstalled(language!) | |
.then((value) => isCurrentLanguageInstalled = (value as bool)); | |
} | |
}); | |
} | |
void _onChange(String text) { | |
setState(() { | |
_newVoiceText = text; | |
}); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
home: Scaffold( | |
appBar: AppBar( | |
title: Text('Flutter TTS'), | |
), | |
body: SingleChildScrollView( | |
scrollDirection: Axis.vertical, | |
child: Column(children: [ | |
_inputSection(), | |
ttsState == TtsState.playing | |
? _textFromInput(start, end) | |
: Text(""), | |
_btnSection(), | |
languages != null ? _languageDropDownSection() : Text(""), | |
_buildSliders() | |
])))); | |
} | |
Widget _inputSection() => Container( | |
alignment: Alignment.topCenter, | |
padding: EdgeInsets.only(top: 25.0, left: 25.0, right: 25.0), | |
child: TextField( | |
onChanged: (String value) { | |
_onChange(value); | |
start = 0; | |
end = value.length; | |
}, | |
)); | |
Widget _textFromInput(int start, int end) => Container( | |
alignment: Alignment.topCenter, | |
padding: EdgeInsets.only(top: 25.0, left: 25.0, right: 25.0), | |
child: RichText( | |
textAlign: TextAlign.center, | |
text: TextSpan(children: <TextSpan>[ | |
TextSpan( | |
text: _newVoiceText != null && start != 0 | |
? _newVoiceText!.substring(0, start) | |
: "", | |
style: TextStyle(color: Colors.black)), | |
TextSpan( | |
text: _newVoiceText != null | |
? _newVoiceText!.substring(start, end) | |
: "", | |
style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold)), | |
TextSpan( | |
text: _newVoiceText != null ? _newVoiceText!.substring(end) : "", | |
style: TextStyle(color: Colors.black)), | |
]), | |
)); | |
Widget _btnSection() { | |
if (isAndroid) { | |
return Container( | |
padding: EdgeInsets.only(top: 50.0), | |
child: | |
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ | |
_buildButtonColumn(Colors.green, Colors.greenAccent, | |
Icons.play_arrow, 'PLAY', _speak), | |
_buildButtonColumn( | |
Colors.red, Colors.redAccent, Icons.stop, 'STOP', _stop), | |
])); | |
} else { | |
return Container( | |
padding: EdgeInsets.only(top: 50.0), | |
child: | |
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ | |
_buildButtonColumn(Colors.green, Colors.greenAccent, | |
Icons.play_arrow, 'PLAY', _speak), | |
_buildButtonColumn( | |
Colors.red, Colors.redAccent, Icons.stop, 'STOP', _stop), | |
_buildButtonColumn( | |
Colors.blue, Colors.blueAccent, Icons.pause, 'PAUSE', _pause), | |
])); | |
} | |
} | |
Widget _languageDropDownSection() => Container( | |
padding: EdgeInsets.only(top: 50.0), | |
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ | |
DropdownButton( | |
value: language, | |
items: getLanguageDropDownMenuItems(), | |
onChanged: changedLanguageDropDownItem, | |
), | |
Visibility( | |
visible: isAndroid, | |
child: Text("Is installed: $isCurrentLanguageInstalled"), | |
), | |
])); | |
Column _buildButtonColumn(Color color, Color splashColor, IconData icon, | |
String label, Function func) { | |
return Column( | |
mainAxisSize: MainAxisSize.min, | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
IconButton( | |
icon: Icon(icon), | |
color: color, | |
splashColor: splashColor, | |
onPressed: () => func()), | |
Container( | |
margin: const EdgeInsets.only(top: 8.0), | |
child: Text(label, | |
style: TextStyle( | |
fontSize: 12.0, | |
fontWeight: FontWeight.w400, | |
color: color))) | |
]); | |
} | |
Widget _buildSliders() { | |
return Column( | |
children: [_volume(), _pitch(), _rate()], | |
); | |
} | |
Widget _volume() { | |
return Slider( | |
value: volume, | |
onChanged: (newVolume) { | |
setState(() => volume = newVolume); | |
}, | |
min: 0.0, | |
max: 1.0, | |
divisions: 10, | |
label: "Volume: $volume"); | |
} | |
Widget _pitch() { | |
return Slider( | |
value: pitch, | |
onChanged: (newPitch) { | |
setState(() => pitch = newPitch); | |
}, | |
min: 0.5, | |
max: 2.0, | |
divisions: 15, | |
label: "Pitch: $pitch", | |
activeColor: Colors.red, | |
); | |
} | |
Widget _rate() { | |
return Slider( | |
value: rate, | |
onChanged: (newRate) { | |
setState(() => rate = newRate); | |
}, | |
min: 0.0, | |
max: 1.0, | |
divisions: 10, | |
label: "Rate: $rate", | |
activeColor: Colors.green, | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi , This code is working for English Language, speech and word highlight are in sync.
My language is Tamil, when I use this code speech and word highlight are not in sync for Tamil language. How to sync?