-
-
Notifications
You must be signed in to change notification settings - Fork 315
Description
🐛 Bug Report
Fatal Exception: java.util.ConcurrentModificationException:
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1111)
at java.util.ArrayList$Itr.next(ArrayList.java:1064)
at com.tundralabs.fluttertts.FlutterTtsPlugin.onInitListenerWithoutCallback$lambda$5(FlutterTtsPlugin.java:253)
at android.speech.tts.TextToSpeech.lambda$dispatchOnInit$0(TextToSpeech.java:934)
at android.speech.tts.TextToSpeech.$r8$lambda$y-uaMPlobiYouiY_T7xFoJSwPww()
at android.speech.tts.TextToSpeech$$ExternalSyntheticLambda13.run(D8$$SyntheticClass)
at android.speech.tts.TextToSpeech.dispatchOnInit(TextToSpeech.java:943)
at android.speech.tts.TextToSpeech.-$$Nest$mdispatchOnInit()
at android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask.onPostExecute(TextToSpeech.java:2324)
at android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask.onPostExecute(TextToSpeech.java:2284)
at android.os.AsyncTask.finish(AsyncTask.java:771)
at android.os.AsyncTask.-$$Nest$mfinish()
at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:788)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loopOnce(Looper.java:249)
at android.os.Looper.loop(Looper.java:337)
at android.app.ActivityThread.main(ActivityThread.java:9604)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:615)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
Expected behavior
should be works wihtout error
Reproduction steps
try it in huawei device
Configuration
class ListenToArticleButton extends StatefulWidget {
const ListenToArticleButton({
super.key,
required this.text,
required this.isTablet,
required this.isSoundPlaying,
this.verticalMargin,
this.hideOneNoMoreThanVoice = false,
this.title,
this.topWidget,
this.customButton,
});
final String? title;
final ValueNotifier<bool> isSoundPlaying;
////needed in settings to prevent user select default sound if no sounds
final bool hideOneNoMoreThanVoice;
final bool isTablet;
final double? verticalMargin;
final Function(bool playing)? customButton;
final String? text;
final Widget? topWidget;
@override
State<ListenToArticleButton> createState() => _ListenToArticleButtonState();
}
class _ListenToArticleButtonState extends State<ListenToArticleButton> {
final ValueNotifier<bool> isLoading = ValueNotifier(false);
late bool lastIsMute;
late final AppLifecycleListener _listener;
Map<String, String>? selectedVoid;
late FlutterTts fluttertts;
@override
void initState() {
fluttertts = getIt<FlutterTts>();
_listener = AppLifecycleListener(
onPause: () async {
pauseByCloseApp = true;
fluttertts.stop();
// await fluttertts.pause();
widget.isSoundPlaying.value = false;
},
onInactive: () async {
pauseByCloseApp = true;
// await fluttertts.pause();
fluttertts.stop();
widget.isSoundPlaying.value = false;
},
onHide: () async {
pauseByCloseApp = true;
// await fluttertts.pause();
fluttertts.stop();
widget.isSoundPlaying.value = false;
},
onStateChange: (value) => print("valuedasdsads $value"),
);
lastIsMute = isMute.value;
setFlutterTtsHandler();
fluttertts.getVoices.then(
(value) {
WidgetsBinding.instance.addPostFrameCallback(
(timeStamp) {
if (mounted && context.mounted) {
getDefaultVoice(context);
}
// final filtered = (value ?? [])
// .where((element) =>
// // (element is Map<String, dynamic>) &&
// (element['locale'] as String?)?.contains('ar') == true)
// .map((e) => Map<String, String>.from(e as Map));
//
// voiceList = filtered.toList();
voiceList = List.of((value ?? []).where(
(element) =>
(element is Map) &&
((element['locale'] as String?)?.contains("ar") ?? false),
))
.map(
(e) => e == null ? null : Map<String, String>.from(e),
)
.nonNulls
.toList();
if (mounted) setState(() {});
},
);
},
);
super.initState();
}
List<Map<String, String>>? voiceList;
Future<void> getDefaultVoice(BuildContext context) async {
if (!Platform.isAndroid && mounted && context.mounted) return;
var soundCubit = context.read<DefaultSoundCubit>();
var voice = await fluttertts.getDefaultVoice;
if (voice != null) {
if (soundCubit.state == null) {
soundCubit.setVoice(Map<String, String>.from(voice));
}
}
}
bool pauseByCloseApp = false;
void setFlutterTtsHandler() async {
if (Platform.isAndroid) {
///limitation on android
///https://github.com/dlutton/flutter_tts/issues/66
fluttertts.awaitSpeakCompletion(true);
}
fluttertts.setCompletionHandler(
() {
isLoading.value = false;
if (!lastIsMute) isMute.value = false;
widget.isSoundPlaying.value = false;
},
);
fluttertts.setCancelHandler(
() {
isLoading.value = false;
if (!lastIsMute) isMute.value = false;
widget.isSoundPlaying.value = false;
},
);
fluttertts.setErrorHandler(
(e) {
isLoading.value = false;
if (!lastIsMute) isMute.value = false;
showMessage(e.toString(), true);
widget.isSoundPlaying.value = false;
},
);
fluttertts.setStartHandler(
() {
isLoading.value = false;
if (!lastIsMute) isMute.value = true;
widget.isSoundPlaying.value = true;
},
);
fluttertts.setPauseHandler(
() {
if (!lastIsMute) isMute.value = false;
widget.isSoundPlaying.value = false;
},
);
if (Platform.isAndroid) {
try {
if (hasServiceGoogle) {
await fluttertts.setEngine('com.google.android.tts');
}
} catch (e, st) {
getIt<CrashlyticsService>()
.logFunctionError(e, st, functionName: "fluttertts/setEngine");
}
}
// if (Platform.isAndroid) {
// _getDefaultEngine();
// _getDefaultVoice();
// }
}
playSound(bool playing) async {
getIt<InAppPipCubit>().pause();
var soundCubit = context.read<DefaultSoundCubit>();
pauseByCloseApp = false;
if (playing) {
fluttertts.stop();
} else {
bool isInstalled = Platform.isAndroid
? await fluttertts.isLanguageInstalled("ar")
: true;
// bool isInstalled = await fluttertts.isLanguageInstalled("ar");
if (isInstalled) {
await fluttertts.setLanguage("ar");
isLoading.value = true;
if (soundCubit.state != null &&
(soundCubit.state?.isNotEmpty ?? false)) {
try {
await fluttertts.setVoice(soundCubit.state!);
} catch (e, s) {
getIt<CrashlyticsService>().logFunctionError(e, s,
functionName: "listen_to_article_button/playSound/setVoice");
}
}
var text = widget.text ?? "";
if (Platform.isAndroid) {
///limitation on android
///https://github.com/dlutton/flutter_tts/issues/66
var count = text.length;
var max = await fluttertts.getMaxSpeechInputLength ?? 100;
var loopCount = count ~/ max;
for (var i = 0; i <= loopCount; i++) {
if (i != loopCount) {
if (!mounted) return;
if (!pauseByCloseApp) {
await fluttertts.speak(text.substring(i * max, (i + 1) * max));
}
} else {
if (!mounted) return;
var end = (count - ((i * max)) + (i * max));
if (!pauseByCloseApp) {
await fluttertts.speak(text.substring(i * max, end));
}
}
}
} else {
if (!pauseByCloseApp) {
await fluttertts.speak(widget.text ?? "");
}
}
} else {
if (Platform.isAndroid) {
if (mounted) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: AppText("اللغة غير مثبتة على جهازك"),
content: AppText.style6(
"الرجاء تنزيل اللغة العربية حتى تتمكن من الاستماع إلى الأخبار"),
actionsAlignment: MainAxisAlignment.start,
actions: [
TextButton(
onPressed: () {
TtsInstaller.installVoiceData();
context.pop();
},
child: AppText("تنزيل")),
TextButton(
onPressed: () {
context.pop();
},
child: AppText("إلغاء")),
],
));
}
}
}
}
getIt<AnalyticsService>().logCustomEvent("listen_to_article", {});
}
@override
Widget build(BuildContext context) {
if (widget.text?.isEmpty ?? true) return SizedBox.shrink();
if (widget.hideOneNoMoreThanVoice) {
if ((!((voiceList?.length ?? 0) > 1)) || kDebugMode) return SizedBox();
}
final button = AppOfflineTracker(
refresh: (_) {},
child: (isConnected) => FutureBuilder(
future: Platform.isAndroid
? fluttertts.isLanguageInstalled("ar")
: Future.value(true),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.data == false &&
!isConnected) return SizedBox.shrink();
return ValueListenableBuilder(
valueListenable: widget.isSoundPlaying,
builder: (context, playing, _) {
return AnimatedSize(
duration: Duration(milliseconds: 1000),
child: GestureDetector(
onTap: () async {
playSound(playing);
// if (StorageService.isEmailTest) {
// List<dynamic> voiceList = await fluttertts.getVoices;
// if (playing) {
// await fluttertts.stop();
// } else {
// // final d = await fluttertts.getDefaultVoice;
// // print(d);
// showModalBottomSheet(
// context: context,
// builder: (context) {
// return SafeArea(
// child: SingleChildScrollView(
// child: Column(
// children: voiceList
// .where(
// (element) =>
// (element['locale'] as String?)
// ?.contains("ar") ??
// false,
// )
// .map(
// (e) => GestureDetector(
// onTap: () async {
// pauseByCloseApp = false;
// if (playing) {
// fluttertts.stop();
// } else {
// await fluttertts.clearVoice();
// await fluttertts.setVoice(
// Map<String, String>.from(e));
// selectedVoid =
// Map<String, String>.from(e);
//
// ///TODO check if en or arabic
// // var result = await fluttertts
// // .isLanguageAvailable("ar");
// // log(result.toString());
// // await fluttertts.setLanguage("ar");
// // await fluttertts.setLanguage("en");
// var text = widget.text ?? "";
// context.pop();
// if (Platform.isAndroid) {
// ///limitation on android
// ///https://github.com/dlutton/flutter_tts/issues/66
// var count = text.length;
// var max = 4000;
// var loopCount = count ~/ max;
//
// for (var i = 0;
// i <= loopCount;
// i++) {
// if (i != loopCount) {
// if (!mounted) return;
// if (!pauseByCloseApp)
// await fluttertts.speak(
// text.substring(i * max,
// (i + 1) * max));
// } else {
// if (!mounted) return;
// var end = (count -
// ((i * max)) +
// (i * max));
// if (!pauseByCloseApp)
// await fluttertts.speak(text
// .substring(i * max, end));
// }
// }
// } else {
// if (!pauseByCloseApp)
// await fluttertts
// .speak(widget.text ?? "");
// }
// }
// },
// onLongPress: () {
// Clipboard.setData(
// ClipboardData(text: e.toString()));
// },
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: AppText.style1(
// "$e",
// textAlign: TextAlign.center,
// style: TextStyle(
// color: selectedVoid.toString() ==
// Map<String, String>.from(
// e)
// .toString()
// ? Colors.red
// : null,
// fontWeight: selectedVoid == e
// ? FontWeight.bold
// : FontWeight.w400),
// ),
// ),
// ),
// )
// .toList(),
// ),
// ),
// );
// },
// );
// }
// } else {
},
child: widget.customButton != null
? widget.customButton!(playing)
: Align(
alignment: Alignment.center,
child: Container(
margin: EdgeInsets.symmetric(
vertical: widget.verticalMargin ?? 20),
padding: EdgeInsets.symmetric(
vertical: 6, horizontal: 20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: context.colorScheme.grey10),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if ((voiceList?.length ?? 0) > 1)
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () async {
var soundCubit = context
.read<DefaultSoundCubit>();
bool isTablet =
HelperFunction.isTablet(
context);
await showModalBottomSheet(
context: context,
showDragHandle: true,
// isScrollControlled: false,
backgroundColor: Colors.white,
constraints: BoxConstraints(
maxHeight: 0.9.sh,
minWidth: 1.sw),
builder: (xtx) {
return BlocProvider.value(
value: soundCubit,
child: BlocBuilder<
DefaultSoundCubit,
Map<String, String>?>(
builder: (context, state) {
return SizedBox(
width: 1.sw,
child:
SingleChildScrollView(
child: Column(
children: [
// HeaderBottomSheet(),
AppText.style10(
"قم باختيار الصوت الافتراضي",
textAlign:
TextAlign
.center,
color: Colors
.black,
),
40.verticalSpace,
...?voiceList
?.mapIndexed(
(index, e) {
{
var isSelected = soundCubit
.state
.toString() ==
e.toString();
return Column(
crossAxisAlignment:
CrossAxisAlignment
.center,
children: [
GestureDetector(
onTap:
() async {
soundCubit.setVoice(Map<String,
String>.from(e));
await fluttertts.stop();
playSound(false);
// if (isSelected) {
// searchFilters.value = List.of(searchFilters.value)
// ..removeWhere((e) => e.index == element.index);
// if (searchFilters.value.isEmpty) {
// searchFilters.value = SearchFilter.values;
// }
//
//
// }
},
child:
Column(
children: [
AppText.style6(
"Voice $index",
style: isSelected ? (isTablet ? AppTextStyles.textStyle1Tablet : AppTextStyles.textStyle1) : null,
textAlign: TextAlign.center,
// color: isSelected
// ? context
// .colorScheme
// .mainRedColor1
// : Colors
// .black,
),
],
),
),
NewsDivider(
isTablet:
isTablet,
height:
30,
)
],
);
}
},
)
],
),
),
);
},
),
);
},
);
fluttertts.stop();
},
child: Container(
width: widget.isTablet ? 30.r : 30,
height: widget.isTablet ? 30.r : 30,
// decoration: BoxDecoration(
// shape: BoxShape.circle,
// color: context.colorScheme.mainRedColor,
// ),
margin: const EdgeInsets.only(
left: 8,
),
child: AppImage.asset(
Assets.iconsEditSound),
)),
AppText.style6(
widget.title ??
(playing
? "يتم الاستماع الى نص المقال"
: "استمع إلى نص المقال"),
color: context.colorScheme.fontColor1,
),
8.horizontalSpace,
ValueListenableBuilder(
valueListenable: isLoading,
builder: (context, _, __) {
return (isLoading.value)
? SizedBox.square(
dimension: widget.isTablet
? 30.r
: 30,
child: Center(
child: AppLoading(
dimension: widget.isTablet
? 20.r
: 20,
),
),
)
: Container(
width: widget.isTablet
? 30.r
: 30,
height: widget.isTablet
? 30.r
: 30,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: context.colorScheme
.mainRedColor),
child: Center(
child: AppImage.asset(playing
? Assets.iconsPause
: Assets
.iconsHeadphones),
),
);
}),
],
),
),
),
),
);
});
}),
);
if (widget.topWidget != null) {
return Column(
children: [
widget.topWidget!,
button,
],
);
}
return button;
}
@override
void dispose() {
fluttertts.stop();
// fluttertts.clearVoice();
_listener.dispose();
super.dispose();
}
}
Version:
flutter_tts: ^4.2.3
Platform:
- 📱 iOS
- 🤖 Android