diff --git a/.vscode/launch.json b/.vscode/launch.json
index 024463d7..898b39c1 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,28 +1,28 @@
{
- "configurations": [
- {
- "name": "Example (Debug)",
- "type": "dart",
- "request": "launch",
- "cwd": "example",
- "program": "lib\\main.dart",
- "flutterMode": "debug"
- },
- {
- "name": "Example (Release)",
- "type": "dart",
- "request": "launch",
- "cwd": "example",
- "program": "lib\\main.dart",
- "flutterMode": "release"
- },
- {
- "name": "Example (Profile)",
- "type": "dart",
- "request": "launch",
- "cwd": "example",
- "program": "lib\\main.dart",
- "flutterMode": "profile"
- }
- ]
+ "configurations": [
+ {
+ "name": "Example (Debug)",
+ "type": "dart",
+ "request": "launch",
+ "cwd": "apps/example",
+ "program": "lib/main.dart",
+ "flutterMode": "debug"
+ },
+ {
+ "name": "Example (Release)",
+ "type": "dart",
+ "request": "launch",
+ "cwd": "apps/example",
+ "program": "lib/main.dart",
+ "flutterMode": "release"
+ },
+ {
+ "name": "Example (Profile)",
+ "type": "dart",
+ "request": "launch",
+ "cwd": "apps/example",
+ "program": "lib/main.dart",
+ "flutterMode": "profile"
+ }
+ ]
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..cf604961
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,7 @@
+{
+ "files.associations": {
+ "*.arb": "json",
+ "xstring": "cpp",
+ "string": "cpp"
+ }
+}
diff --git a/android/build.gradle b/android/build.gradle
deleted file mode 100644
index 24d029a9..00000000
--- a/android/build.gradle
+++ /dev/null
@@ -1,64 +0,0 @@
-def args = ["-Xlint:deprecation"]
-
-group 'com.tundralabs.fluttertts'
-version '1.0-SNAPSHOT'
-
-buildscript {
- ext.kotlin_version = '1.9.10'
- repositories {
- google()
- mavenCentral()
- }
-
- dependencies {
- classpath 'com.android.tools.build:gradle:8.2.0'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- }
-}
-
-rootProject.allprojects {
- repositories {
- google()
- mavenCentral()
- }
-}
-
-project.getTasks().withType(JavaCompile).configureEach {
- options.compilerArgs.addAll(args)
-}
-
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-
-android {
- compileSdk 34
- if (project.android.hasProperty("namespace")) {
- namespace 'com.tundralabs.fluttertts'
- }
-
- defaultConfig {
- minSdkVersion 21
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
-
- lintOptions {
- disable 'InvalidPackage'
- disable 'GradleDependency'
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-
- kotlinOptions {
- jvmTarget = '1.8'
- }
-}
-dependencies {
- implementation "androidx.core:core-ktx:1.8.0"
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
-}
-repositories {
- mavenCentral()
-}
diff --git a/android/gradle.properties b/android/gradle.properties
deleted file mode 100644
index 94adc3a3..00000000
--- a/android/gradle.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-org.gradle.jvmargs=-Xmx1536M
-android.useAndroidX=true
-android.enableJetifier=true
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
deleted file mode 100644
index a88a091e..00000000
--- a/android/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/android/src/main/kotlin/com/tundralabs/fluttertts/FlutterTtsPlugin.kt b/android/src/main/kotlin/com/tundralabs/fluttertts/FlutterTtsPlugin.kt
deleted file mode 100644
index 36fd1421..00000000
--- a/android/src/main/kotlin/com/tundralabs/fluttertts/FlutterTtsPlugin.kt
+++ /dev/null
@@ -1,815 +0,0 @@
-package com.tundralabs.fluttertts
-
-import android.content.ContentValues
-import android.content.Context
-import android.media.AudioAttributes
-import android.media.AudioFocusRequest
-import android.media.AudioManager
-import android.net.Uri
-import android.os.Build
-import android.os.Bundle
-import android.os.Environment
-import android.os.Handler
-import android.os.Looper
-import android.os.ParcelFileDescriptor
-import android.provider.MediaStore
-import android.provider.OpenableColumns
-import android.speech.tts.TextToSpeech
-import android.speech.tts.UtteranceProgressListener
-import android.speech.tts.Voice
-import io.flutter.Log
-import io.flutter.embedding.engine.plugins.FlutterPlugin
-import io.flutter.plugin.common.BinaryMessenger
-import io.flutter.plugin.common.MethodCall
-import io.flutter.plugin.common.MethodChannel
-import io.flutter.plugin.common.MethodChannel.MethodCallHandler
-import io.flutter.plugin.common.MethodChannel.Result
-import java.io.File
-import java.lang.reflect.Field
-import java.util.Locale
-import java.util.MissingResourceException
-import java.util.UUID
-
-
-/** FlutterTtsPlugin */
-class FlutterTtsPlugin : MethodCallHandler, FlutterPlugin {
- private var handler: Handler? = null
- private var methodChannel: MethodChannel? = null
- private var speakResult: Result? = null
- private var synthResult: Result? = null
- private var awaitSpeakCompletion = false
- private var speaking = false
- private var awaitSynthCompletion = false
- private var synth = false
- private var context: Context? = null
- private var tts: TextToSpeech? = null
- private val tag = "TTS"
- private val pendingMethodCalls = ArrayList()
- private val utterances = HashMap()
- private var bundle: Bundle? = null
- private var silencems = 0
- private var lastProgress = 0
- private var currentText: String? = null
- private var pauseText: String? = null
- private var isPaused: Boolean = false
- private var queueMode: Int = TextToSpeech.QUEUE_FLUSH
- private var ttsStatus: Int? = null
- private var selectedEngine: String? = null
- private var engineResult: Result? = null
- private var parcelFileDescriptor: ParcelFileDescriptor? = null
- private var audioManager: AudioManager? = null
- private var audioFocusRequest: AudioFocusRequest? = null
-
- companion object {
- private const val SILENCE_PREFIX = "SIL_"
- private const val SYNTHESIZE_TO_FILE_PREFIX = "STF_"
- }
-
- private fun initInstance(messenger: BinaryMessenger, context: Context) {
- this.context = context
- methodChannel = MethodChannel(messenger, "flutter_tts")
- methodChannel!!.setMethodCallHandler(this)
- handler = Handler(Looper.getMainLooper())
- bundle = Bundle()
- tts = TextToSpeech(context, onInitListenerWithoutCallback)
- }
-
- /** Android Plugin APIs */
- override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
- initInstance(binding.binaryMessenger, binding.applicationContext)
- }
-
- override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
- stop()
- tts!!.shutdown()
- context = null
- methodChannel!!.setMethodCallHandler(null)
- methodChannel = null
- }
-
- private val utteranceProgressListener: UtteranceProgressListener =
- object : UtteranceProgressListener() {
- override fun onStart(utteranceId: String) {
- if (utteranceId.startsWith(SYNTHESIZE_TO_FILE_PREFIX)) {
- invokeMethod("synth.onStart", true)
- } else {
- if (isPaused) {
- invokeMethod("speak.onContinue", true)
- isPaused = false
- } else {
- Log.d(tag, "Utterance ID has started: $utteranceId")
- invokeMethod("speak.onStart", true)
- }
- }
- if (Build.VERSION.SDK_INT < 26) {
- onProgress(utteranceId, 0, utterances[utteranceId]!!.length)
- }
- }
-
- override fun onDone(utteranceId: String) {
- if (utteranceId.startsWith(SILENCE_PREFIX)) return
- if (utteranceId.startsWith(SYNTHESIZE_TO_FILE_PREFIX)) {
- closeParcelFileDescriptor(false)
- Log.d(tag, "Utterance ID has completed: $utteranceId")
- if (awaitSynthCompletion) {
- synthCompletion(1)
- }
- invokeMethod("synth.onComplete", true)
- } else {
- Log.d(tag, "Utterance ID has completed: $utteranceId")
- if (awaitSpeakCompletion && queueMode == TextToSpeech.QUEUE_FLUSH) {
- speakCompletion(1)
- }
- invokeMethod("speak.onComplete", true)
- }
- lastProgress = 0
- pauseText = null
- utterances.remove(utteranceId)
- releaseAudioFocus()
- }
-
- override fun onStop(utteranceId: String, interrupted: Boolean) {
- Log.d(
- tag,
- "Utterance ID has been stopped: $utteranceId. Interrupted: $interrupted"
- )
- if (awaitSpeakCompletion) {
- speaking = false
- }
- if (isPaused) {
- invokeMethod("speak.onPause", true)
- } else {
- invokeMethod("speak.onCancel", true)
- }
- releaseAudioFocus()
- }
-
- private fun onProgress(utteranceId: String?, startAt: Int, endAt: Int) {
- if (utteranceId != null && !utteranceId.startsWith(SYNTHESIZE_TO_FILE_PREFIX)) {
- val text = utterances[utteranceId]
- val data = HashMap()
- data["text"] = text
- data["start"] = startAt.toString()
- data["end"] = endAt.toString()
- data["word"] = text!!.substring(startAt, endAt)
- invokeMethod("speak.onProgress", data)
- }
- }
-
- // Requires Android 26 or later
- override fun onRangeStart(utteranceId: String, startAt: Int, endAt: Int, frame: Int) {
- if (!utteranceId.startsWith(SYNTHESIZE_TO_FILE_PREFIX)) {
- lastProgress = startAt
- super.onRangeStart(utteranceId, startAt, endAt, frame)
- onProgress(utteranceId, startAt, endAt)
- }
- }
-
- @Deprecated("")
- override fun onError(utteranceId: String) {
- if (utteranceId.startsWith(SYNTHESIZE_TO_FILE_PREFIX)) {
- closeParcelFileDescriptor(true)
- if (awaitSynthCompletion) {
- synth = false
- }
- invokeMethod("synth.onError", "Error from TextToSpeech (synth)")
- } else {
- if (awaitSpeakCompletion) {
- speaking = false
- }
- invokeMethod("speak.onError", "Error from TextToSpeech (speak)")
- }
- releaseAudioFocus()
- }
-
- override fun onError(utteranceId: String, errorCode: Int) {
- if (utteranceId.startsWith(SYNTHESIZE_TO_FILE_PREFIX)) {
- closeParcelFileDescriptor(true)
- if (awaitSynthCompletion) {
- synth = false
- }
- invokeMethod("synth.onError", "Error from TextToSpeech (synth) - $errorCode")
- } else {
- if (awaitSpeakCompletion) {
- speaking = false
- }
- invokeMethod("speak.onError", "Error from TextToSpeech (speak) - $errorCode")
- }
- }
- }
-
- fun speakCompletion(success: Int) {
- speaking = false
- handler!!.post {
- speakResult?.success(success)
- speakResult = null
- }
- }
-
- fun synthCompletion(success: Int) {
- synth = false
- handler!!.post {
- synthResult?.success(success)
- synthResult = null
- }
- }
-
- private val onInitListenerWithCallback: TextToSpeech.OnInitListener =
- TextToSpeech.OnInitListener { status ->
- // Handle pending method calls (sent while TTS was initializing)
- synchronized(this@FlutterTtsPlugin) {
- ttsStatus = status
- for (call in pendingMethodCalls) {
- call.run()
- }
- pendingMethodCalls.clear()
- }
-
- if (status == TextToSpeech.SUCCESS) {
- tts!!.setOnUtteranceProgressListener(utteranceProgressListener)
- try {
- val locale: Locale = tts!!.defaultVoice.locale
- if (isLanguageAvailable(locale)) {
- tts!!.language = locale
- }
- } catch (e: NullPointerException) {
- Log.e(tag, "getDefaultLocale: " + e.message)
- } catch (e: IllegalArgumentException) {
- Log.e(tag, "getDefaultLocale: " + e.message)
- }
-
- engineResult!!.success(1)
- } else {
- engineResult!!.error("TtsError","Failed to initialize TextToSpeech with status: $status", null)
- }
- //engineResult = null
- }
-
- private val onInitListenerWithoutCallback: TextToSpeech.OnInitListener =
- TextToSpeech.OnInitListener { status ->
- // Handle pending method calls (sent while TTS was initializing)
- synchronized(this@FlutterTtsPlugin) {
- ttsStatus = status
- for (call in pendingMethodCalls) {
- call.run()
- }
- pendingMethodCalls.clear()
- }
-
- if (status == TextToSpeech.SUCCESS) {
- tts!!.setOnUtteranceProgressListener(utteranceProgressListener)
- try {
- val locale: Locale = tts!!.defaultVoice.locale
- if (isLanguageAvailable(locale)) {
- tts!!.language = locale
- }
- } catch (e: NullPointerException) {
- Log.e(tag, "getDefaultLocale: " + e.message)
- } catch (e: IllegalArgumentException) {
- Log.e(tag, "getDefaultLocale: " + e.message)
- }
- } else {
- Log.e(tag, "Failed to initialize TextToSpeech with status: $status")
- }
- }
-
- override fun onMethodCall(call: MethodCall, result: Result) {
- // If TTS is still loading
- synchronized(this@FlutterTtsPlugin) {
- if (ttsStatus == null) {
- // Suspend method call until the TTS engine is ready
- val suspendedCall = Runnable { onMethodCall(call, result) }
- pendingMethodCalls.add(suspendedCall)
- return
- }
- }
- when (call.method) {
- "speak" -> {
- var text: String = call.argument("text")!!
- val focus: Boolean = call.argument("focus")!!
- if (pauseText == null) {
- pauseText = text
- currentText = pauseText!!
- }
- if (isPaused) {
- // Ensure the text hasn't changed
- if (currentText == text) {
- text = pauseText!!
- } else {
- pauseText = text
- currentText = pauseText!!
- lastProgress = 0
- }
- }
- if (speaking) {
- // If TTS is set to queue mode, allow the utterance to be queued up rather than discarded
- if (queueMode == TextToSpeech.QUEUE_FLUSH) {
- result.success(0)
- return
- }
- }
- val b = speak(text, focus)
- if (!b) {
- synchronized(this@FlutterTtsPlugin) {
- val suspendedCall = Runnable { onMethodCall(call, result) }
- pendingMethodCalls.add(suspendedCall)
- }
- return
- }
- // Only use await speak completion if queueMode is set to QUEUE_FLUSH
- if (awaitSpeakCompletion && queueMode == TextToSpeech.QUEUE_FLUSH) {
- speaking = true
- speakResult = result
- } else {
- result.success(1)
- }
- }
-
- "awaitSpeakCompletion" -> {
- awaitSpeakCompletion = java.lang.Boolean.parseBoolean(call.arguments.toString())
- result.success(1)
- }
-
- "awaitSynthCompletion" -> {
- awaitSynthCompletion = java.lang.Boolean.parseBoolean(call.arguments.toString())
- result.success(1)
- }
-
- "getMaxSpeechInputLength" -> {
- val res = maxSpeechInputLength
- result.success(res)
- }
-
- "synthesizeToFile" -> {
- val text: String? = call.argument("text")
- if (synth) {
- result.success(0)
- return
- }
- val fileName: String? = call.argument("fileName")
- val isFullPath: Boolean? = call.argument("isFullPath")
- synthesizeToFile(text!!, fileName!!, isFullPath!!)
- if (awaitSynthCompletion) {
- synth = true
- synthResult = result
- } else {
- result.success(1)
- }
- }
-
- "pause" -> {
- isPaused = true
- if (pauseText != null) {
- pauseText = pauseText!!.substring(lastProgress)
- }
- stop()
- result.success(1)
- if (speakResult != null) {
- speakResult!!.success(0)
- speakResult = null
- }
- }
-
- "stop" -> {
- isPaused = false
- pauseText = null
- stop()
- lastProgress = 0
- result.success(1)
- if (speakResult != null) {
- speakResult!!.success(0)
- speakResult = null
- }
- }
-
- "setEngine" -> {
- val engine: String = call.arguments.toString()
- setEngine(engine, result)
- }
-
- "setSpeechRate" -> {
- val rate: String = call.arguments.toString()
- // To make the FlutterTts API consistent across platforms,
- // Android 1.0 is mapped to flutter 0.5.
- setSpeechRate(rate.toFloat() * 2.0f)
- result.success(1)
- }
-
- "setVolume" -> {
- val volume: String = call.arguments.toString()
- setVolume(volume.toFloat(), result)
- }
-
- "setPitch" -> {
- val pitch: String = call.arguments.toString()
- setPitch(pitch.toFloat(), result)
- }
-
- "setLanguage" -> {
- val language: String = call.arguments.toString()
- setLanguage(language, result)
- }
-
- "getLanguages" -> getLanguages(result)
- "getVoices" -> getVoices(result)
- "getSpeechRateValidRange" -> getSpeechRateValidRange(result)
- "getEngines" -> getEngines(result)
- "getDefaultEngine" -> getDefaultEngine(result)
- "getDefaultVoice" -> getDefaultVoice(result)
- "setVoice" -> {
- val voice: HashMap? = call.arguments()
- setVoice(voice!!, result)
- }
-
- "clearVoice" -> clearVoice(result)
-
- "isLanguageAvailable" -> {
- val language: String = call.arguments.toString()
- val locale: Locale = Locale.forLanguageTag(language)
- result.success(isLanguageAvailable(locale))
- }
-
- "setSilence" -> {
- val silencems: String = call.arguments.toString()
- this.silencems = silencems.toInt()
- }
-
- "setSharedInstance" -> result.success(1)
- "isLanguageInstalled" -> {
- val language: String = call.arguments.toString()
- result.success(isLanguageInstalled(language))
- }
-
- "areLanguagesInstalled" -> {
- val languages: List? = call.arguments()
- result.success(areLanguagesInstalled(languages!!))
- }
-
- "setQueueMode" -> {
- val queueMode: String = call.arguments.toString()
- this.queueMode = queueMode.toInt()
- result.success(1)
- }
-
- "setAudioAttributesForNavigation" -> {
- setAudioAttributesForNavigation()
- result.success(1)
- }
-
- else -> result.notImplemented()
- }
- }
-
- private fun setSpeechRate(rate: Float) {
- tts!!.setSpeechRate(rate)
- }
-
- private fun isLanguageAvailable(locale: Locale?): Boolean {
- return tts!!.isLanguageAvailable(locale) >= TextToSpeech.LANG_AVAILABLE
- }
-
- private fun areLanguagesInstalled(languages: List): Map {
- val result: MutableMap = HashMap()
- for (language in languages) {
- result[language] = isLanguageInstalled(language)
- }
- return result
- }
-
- private fun isLanguageInstalled(language: String?): Boolean {
- val locale: Locale = Locale.forLanguageTag(language!!)
- if (isLanguageAvailable(locale)) {
- var voiceToCheck: Voice? = null
- for (v in tts!!.voices) {
- if (v.locale == locale && !v.isNetworkConnectionRequired) {
- voiceToCheck = v
- break
- }
- }
- if (voiceToCheck != null) {
- val features: Set = voiceToCheck.features
- return (!features.contains(TextToSpeech.Engine.KEY_FEATURE_NOT_INSTALLED))
- }
- }
- return false
- }
-
- private fun setEngine(engine: String?, result: Result) {
- ttsStatus = null
- selectedEngine = engine
- engineResult = result
- tts = TextToSpeech(context, onInitListenerWithCallback, engine)
- }
-
- private fun setLanguage(language: String?, result: Result) {
- val locale: Locale = Locale.forLanguageTag(language!!)
- if (isLanguageAvailable(locale)) {
- tts!!.language = locale
- result.success(1)
- } else {
- result.success(0)
- }
- }
-
- private fun setVoice(voice: HashMap, result: Result) {
- for (ttsVoice in tts!!.voices) {
- if (ttsVoice.name == voice["name"] && ttsVoice.locale
- .toLanguageTag() == voice["locale"]
- ) {
- tts!!.voice = ttsVoice
- result.success(1)
- return
- }
- }
- Log.d(tag, "Voice name not found: $voice")
- result.success(0)
- }
-
- private fun clearVoice(result: Result) {
- tts!!.voice = tts!!.defaultVoice
- result.success(1)
- }
-
- private fun setVolume(volume: Float, result: Result) {
- if (volume in (0.0f..1.0f)) {
- bundle!!.putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, volume)
- result.success(1)
- } else {
- Log.d(tag, "Invalid volume $volume value - Range is from 0.0 to 1.0")
- result.success(0)
- }
- }
-
- private fun setPitch(pitch: Float, result: Result) {
- if (pitch in (0.5f..2.0f)) {
- tts!!.setPitch(pitch)
- result.success(1)
- } else {
- Log.d(tag, "Invalid pitch $pitch value - Range is from 0.5 to 2.0")
- result.success(0)
- }
- }
-
- private fun getVoices(result: Result) {
- val voices = ArrayList>()
- try {
- for (voice in tts!!.voices) {
- val voiceMap = HashMap()
- readVoiceProperties(voiceMap, voice)
- voices.add(voiceMap)
- }
- result.success(voices)
- } catch (e: NullPointerException) {
- Log.d(tag, "getVoices: " + e.message)
- result.success(null)
- }
- }
-
- private fun getLanguages(result: Result) {
- val locales = ArrayList()
- try {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- // While this method was introduced in API level 21, it seems that it
- // has not been implemented in the speech service side until API Level 23.
- for (locale in tts!!.availableLanguages) {
- locales.add(locale.toLanguageTag())
- }
- } else {
- for (locale in Locale.getAvailableLocales()) {
- if (locale.variant.isEmpty() && isLanguageAvailable(locale)) {
- locales.add(locale.toLanguageTag())
- }
- }
- }
- } catch (e: MissingResourceException) {
- Log.d(tag, "getLanguages: " + e.message)
- } catch (e: NullPointerException) {
- Log.d(tag, "getLanguages: " + e.message)
- }
- result.success(locales)
- }
-
- private fun getEngines(result: Result) {
- val engines = ArrayList()
- try {
- for (engineInfo in tts!!.engines) {
- engines.add(engineInfo.name)
- }
- } catch (e: Exception) {
- Log.d(tag, "getEngines: " + e.message)
- }
- result.success(engines)
- }
-
- private fun getDefaultEngine(result: Result) {
- val defaultEngine: String? = tts!!.defaultEngine
- result.success(defaultEngine)
- }
-
- private fun getDefaultVoice(result: Result) {
- val defaultVoice: Voice? = tts!!.defaultVoice
- val voice = HashMap()
- if (defaultVoice != null) {
- readVoiceProperties(voice, defaultVoice)
- }
- result.success(voice)
- }
- // Add voice properties into the voice map
- fun readVoiceProperties(map: MutableMap, voice: Voice) {
- map["name"] = voice.name
- map["locale"] = voice.locale.toLanguageTag()
- map["quality"] = qualityToString(voice.quality)
- map["latency"] = latencyToString(voice.latency)
- map["network_required"] = if (voice.isNetworkConnectionRequired) "1" else "0"
- map["features"] = voice.features.joinToString(separator = "\t")
-
- }
-
- // Function to map quality integer to the constant name
- fun qualityToString(quality: Int): String {
- return when (quality) {
- Voice.QUALITY_VERY_HIGH -> "very high"
- Voice.QUALITY_HIGH -> "high"
- Voice.QUALITY_NORMAL -> "normal"
- Voice.QUALITY_LOW -> "low"
- Voice.QUALITY_VERY_LOW -> "very low"
- else -> "unknown"
- }
- }
- // Function to map latency integer to the constant name
- fun latencyToString(quality: Int): String {
- return when (quality) {
- Voice.LATENCY_VERY_HIGH -> "very high"
- Voice.LATENCY_HIGH -> "high"
- Voice.LATENCY_NORMAL -> "normal"
- Voice.LATENCY_LOW -> "low"
- Voice.LATENCY_VERY_LOW -> "very low"
- else -> "unknown"
- }
- }
-
- private fun getSpeechRateValidRange(result: Result) {
- // Valid values available in the android documentation.
- // https://developer.android.com/reference/android/speech/tts/TextToSpeech#setSpeechRate(float)
- // To make the FlutterTts API consistent across platforms,
- // we map Android 1.0 to flutter 0.5 and so on.
- val data = HashMap()
- data["min"] = "0"
- data["normal"] = "0.5"
- data["max"] = "1.5"
- data["platform"] = "android"
- result.success(data)
- }
-
- private fun speak(text: String, focus: Boolean): Boolean {
- val uuid: String = UUID.randomUUID().toString()
- utterances[uuid] = text
- return if (ismServiceConnectionUsable(tts)) {
- if(focus){
- requestAudioFocus()
- }
-
- if (silencems > 0) {
- tts!!.playSilentUtterance(
- silencems.toLong(),
- TextToSpeech.QUEUE_FLUSH,
- SILENCE_PREFIX + uuid
- )
- tts!!.speak(text, TextToSpeech.QUEUE_ADD, bundle, uuid) == 0
- } else {
- tts!!.speak(text, queueMode, bundle, uuid) == 0
- }
- } else {
- ttsStatus = null
- tts = TextToSpeech(context, onInitListenerWithoutCallback, selectedEngine)
- false
- }
- }
-
- private fun stop() {
- if (awaitSynthCompletion) synth = false
- if (awaitSpeakCompletion) speaking = false
- tts!!.stop()
- }
-
- private val maxSpeechInputLength: Int
- get() = TextToSpeech.getMaxSpeechInputLength()
-
- private fun closeParcelFileDescriptor(isError: Boolean) {
- if (this.parcelFileDescriptor != null) {
- if (isError) {
- this.parcelFileDescriptor!!.closeWithError("Error synthesizing TTS to file")
- } else {
- this.parcelFileDescriptor!!.close()
- }
- }
- }
-
- private fun synthesizeToFile(text: String, fileName: String, isFullPath: Boolean) {
- val fullPath: String
- val uuid: String = UUID.randomUUID().toString()
- bundle!!.putString(
- TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,
- SYNTHESIZE_TO_FILE_PREFIX + uuid
- )
-
- val result: Int =
- if(isFullPath){
- val file = File(fileName)
- fullPath = file.path
-
- tts!!.synthesizeToFile(text, bundle!!, file!!, SYNTHESIZE_TO_FILE_PREFIX + uuid)
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- val resolver = this.context?.contentResolver
- val contentValues = ContentValues().apply {
- put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
- put(MediaStore.MediaColumns.MIME_TYPE, "audio/wav")
- put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_MUSIC)
- }
- val uri = resolver?.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, contentValues)
- this.parcelFileDescriptor = resolver?.openFileDescriptor(uri!!, "rw")
- fullPath = uri?.path + File.separatorChar + fileName
-
- tts!!.synthesizeToFile(text, bundle!!, parcelFileDescriptor!!, SYNTHESIZE_TO_FILE_PREFIX + uuid)
- } else {
- val musicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)
- val file = File(musicDir, fileName)
- fullPath = file.path
-
- tts!!.synthesizeToFile(text, bundle!!, file!!, SYNTHESIZE_TO_FILE_PREFIX + uuid)
- }
-
- if (result == TextToSpeech.SUCCESS) {
- Log.d(tag, "Successfully created file : $fullPath")
- } else {
- Log.d(tag, "Failed creating file : $fullPath")
- }
- }
-
- private fun invokeMethod(method: String, arguments: Any) {
- handler!!.post {
- if (methodChannel != null) methodChannel!!.invokeMethod(
- method,
- arguments
- )
- }
- }
-
- private fun ismServiceConnectionUsable(tts: TextToSpeech?): Boolean {
- var isBindConnection = true
- if (tts == null) {
- return false
- }
- val fields: Array = tts.javaClass.declaredFields
- for (j in fields.indices) {
- fields[j].isAccessible = true
- if ("mServiceConnection" == fields[j].name && "android.speech.tts.TextToSpeech\$Connection" == fields[j].type.name) {
- try {
- if (fields[j][tts] == null) {
- isBindConnection = false
- Log.e(tag, "*******TTS -> mServiceConnection == null*******")
- }
- } catch (e: IllegalArgumentException) {
- e.printStackTrace()
- } catch (e: IllegalAccessException) {
- e.printStackTrace()
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }
- }
- return isBindConnection
- }
-
- // Method to set AudioAttributes for navigation usage
- private fun setAudioAttributesForNavigation() {
- if (tts != null) {
- val audioAttributes = AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
- .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
- .build()
- tts!!.setAudioAttributes(audioAttributes)
- }
- }
-
- private fun requestAudioFocus() {
- audioManager = context?.getSystemService(Context.AUDIO_SERVICE) as AudioManager
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
- .setOnAudioFocusChangeListener { /* opcional para monitorar mudanças de foco */ }
- .build()
- audioManager?.requestAudioFocus(audioFocusRequest!!)
- } else {
- audioManager?.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
- }
- }
-
- private fun releaseAudioFocus() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- audioFocusRequest?.let { audioManager?.abandonAudioFocusRequest(it) }
- } else {
- audioManager?.abandonAudioFocus(null)
- }
- }
-}
diff --git a/example/.gitignore b/apps/example/.gitignore
similarity index 100%
rename from example/.gitignore
rename to apps/example/.gitignore
diff --git a/apps/example/.metadata b/apps/example/.metadata
new file mode 100644
index 00000000..b9fd747a
--- /dev/null
+++ b/apps/example/.metadata
@@ -0,0 +1,30 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2"
+ channel: "stable"
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
+ base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
+ - platform: ios
+ create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
+ base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/example/README.md b/apps/example/README.md
similarity index 100%
rename from example/README.md
rename to apps/example/README.md
diff --git a/apps/example/analysis_options.yaml b/apps/example/analysis_options.yaml
new file mode 100644
index 00000000..0d290213
--- /dev/null
+++ b/apps/example/analysis_options.yaml
@@ -0,0 +1,28 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at https://dart.dev/lints.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/example/android/.gitignore b/apps/example/android/.gitignore
similarity index 100%
rename from example/android/.gitignore
rename to apps/example/android/.gitignore
diff --git a/example/android/app/.classpath b/apps/example/android/app/.classpath
similarity index 100%
rename from example/android/app/.classpath
rename to apps/example/android/app/.classpath
diff --git a/example/android/app/.settings/org.eclipse.buildship.core.prefs b/apps/example/android/app/.settings/org.eclipse.buildship.core.prefs
similarity index 100%
rename from example/android/app/.settings/org.eclipse.buildship.core.prefs
rename to apps/example/android/app/.settings/org.eclipse.buildship.core.prefs
diff --git a/apps/example/android/app/build.gradle.kts b/apps/example/android/app/build.gradle.kts
new file mode 100644
index 00000000..92ff5830
--- /dev/null
+++ b/apps/example/android/app/build.gradle.kts
@@ -0,0 +1,44 @@
+plugins {
+ id("com.android.application")
+ id("kotlin-android")
+ // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
+ id("dev.flutter.flutter-gradle-plugin")
+}
+
+android {
+ namespace = "com.tundralabs.example"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_11.toString()
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId = "com.tundralabs.flutterttsexample"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://flutter.dev/to/review-gradle-config.
+ minSdk = flutter.minSdkVersion
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/example/android/app/src/debug/AndroidManifest.xml b/apps/example/android/app/src/debug/AndroidManifest.xml
similarity index 100%
rename from example/android/app/src/debug/AndroidManifest.xml
rename to apps/example/android/app/src/debug/AndroidManifest.xml
diff --git a/example/android/app/src/main/AndroidManifest.xml b/apps/example/android/app/src/main/AndroidManifest.xml
similarity index 100%
rename from example/android/app/src/main/AndroidManifest.xml
rename to apps/example/android/app/src/main/AndroidManifest.xml
diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/apps/example/android/app/src/main/res/drawable/launch_background.xml
similarity index 100%
rename from example/android/app/src/main/res/drawable/launch_background.xml
rename to apps/example/android/app/src/main/res/drawable/launch_background.xml
diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
similarity index 100%
rename from example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
rename to apps/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
similarity index 100%
rename from example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
rename to apps/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
similarity index 100%
rename from example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
rename to apps/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
similarity index 100%
rename from example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
rename to apps/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
similarity index 100%
rename from example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
rename to apps/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
diff --git a/example/android/app/src/main/res/values/styles.xml b/apps/example/android/app/src/main/res/values/styles.xml
similarity index 100%
rename from example/android/app/src/main/res/values/styles.xml
rename to apps/example/android/app/src/main/res/values/styles.xml
diff --git a/example/android/app/src/profile/AndroidManifest.xml b/apps/example/android/app/src/profile/AndroidManifest.xml
similarity index 100%
rename from example/android/app/src/profile/AndroidManifest.xml
rename to apps/example/android/app/src/profile/AndroidManifest.xml
diff --git a/apps/example/android/build.gradle.kts b/apps/example/android/build.gradle.kts
new file mode 100644
index 00000000..dbee657b
--- /dev/null
+++ b/apps/example/android/build.gradle.kts
@@ -0,0 +1,24 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+val newBuildDir: Directory =
+ rootProject.layout.buildDirectory
+ .dir("../../build")
+ .get()
+rootProject.layout.buildDirectory.value(newBuildDir)
+
+subprojects {
+ val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
+ project.layout.buildDirectory.value(newSubprojectBuildDir)
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register("clean") {
+ delete(rootProject.layout.buildDirectory)
+}
diff --git a/apps/example/android/gradle.properties b/apps/example/android/gradle.properties
new file mode 100644
index 00000000..f018a618
--- /dev/null
+++ b/apps/example/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/apps/example/android/gradle/wrapper/gradle-wrapper.properties
similarity index 69%
rename from example/android/gradle/wrapper/gradle-wrapper.properties
rename to apps/example/android/gradle/wrapper/gradle-wrapper.properties
index 10e94ad6..cdebde04 100644
--- a/example/android/gradle/wrapper/gradle-wrapper.properties
+++ b/apps/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
+# distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.13-all.zip
diff --git a/apps/example/android/settings.gradle.kts b/apps/example/android/settings.gradle.kts
new file mode 100644
index 00000000..75af8a40
--- /dev/null
+++ b/apps/example/android/settings.gradle.kts
@@ -0,0 +1,26 @@
+pluginManagement {
+ val flutterSdkPath =
+ run {
+ val properties = java.util.Properties()
+ file("local.properties").inputStream().use { properties.load(it) }
+ val flutterSdkPath = properties.getProperty("flutter.sdk")
+ require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
+ flutterSdkPath
+ }
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id("dev.flutter.flutter-plugin-loader") version "1.0.0"
+ id("com.android.application") version "8.13.0" apply false
+ id("org.jetbrains.kotlin.android") version "2.1.0" apply false
+}
+
+include(":app")
diff --git a/apps/example/ios/.gitignore b/apps/example/ios/.gitignore
new file mode 100644
index 00000000..7a7f9873
--- /dev/null
+++ b/apps/example/ios/.gitignore
@@ -0,0 +1,34 @@
+**/dgph
+*.mode1v3
+*.mode2v3
+*.moved-aside
+*.pbxuser
+*.perspectivev3
+**/*sync/
+.sconsign.dblite
+.tags*
+**/.vagrant/
+**/DerivedData/
+Icon?
+**/Pods/
+**/.symlinks/
+profile
+xcuserdata
+**/.generated/
+Flutter/App.framework
+Flutter/Flutter.framework
+Flutter/Flutter.podspec
+Flutter/Generated.xcconfig
+Flutter/ephemeral/
+Flutter/app.flx
+Flutter/app.zip
+Flutter/flutter_assets/
+Flutter/flutter_export_environment.sh
+ServiceDefinitions.json
+Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!default.mode1v3
+!default.mode2v3
+!default.pbxuser
+!default.perspectivev3
diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/apps/example/ios/Flutter/AppFrameworkInfo.plist
similarity index 86%
rename from example/ios/Flutter/AppFrameworkInfo.plist
rename to apps/example/ios/Flutter/AppFrameworkInfo.plist
index 9b41e7d8..1dc6cf76 100644
--- a/example/ios/Flutter/AppFrameworkInfo.plist
+++ b/apps/example/ios/Flutter/AppFrameworkInfo.plist
@@ -20,11 +20,7 @@
????
CFBundleVersion
1.0
- UIRequiredDeviceCapabilities
-
- arm64
-
MinimumOSVersion
- 11.0
+ 13.0
diff --git a/apps/example/ios/Flutter/Debug.xcconfig b/apps/example/ios/Flutter/Debug.xcconfig
new file mode 100644
index 00000000..ec97fc6f
--- /dev/null
+++ b/apps/example/ios/Flutter/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
+#include "Generated.xcconfig"
diff --git a/apps/example/ios/Flutter/Release.xcconfig b/apps/example/ios/Flutter/Release.xcconfig
new file mode 100644
index 00000000..c4855bfe
--- /dev/null
+++ b/apps/example/ios/Flutter/Release.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
+#include "Generated.xcconfig"
diff --git a/example/ios/Podfile b/apps/example/ios/Podfile
similarity index 94%
rename from example/ios/Podfile
rename to apps/example/ios/Podfile
index 88359b22..620e46eb 100644
--- a/example/ios/Podfile
+++ b/apps/example/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-# platform :ios, '11.0'
+# platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@@ -29,9 +29,11 @@ flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
- use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+ target 'RunnerTests' do
+ inherit! :search_paths
+ end
end
post_install do |installer|
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/apps/example/ios/Runner.xcodeproj/project.pbxproj
similarity index 56%
rename from example/ios/Runner.xcodeproj/project.pbxproj
rename to apps/example/ios/Runner.xcodeproj/project.pbxproj
index 38e2ecbc..7a9624c2 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/apps/example/ios/Runner.xcodeproj/project.pbxproj
@@ -8,14 +8,26 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+ 59185AECC1AC289A18E08B27 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA1F5E9E308D75711360FAF6 /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 936BDC305018E86F43152805 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B99FC96601B9D60C00E3472 /* Pods_RunnerTests.framework */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
- B9BC3BE22F97E1DD9B850D52 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B53E79FD7E80803BF54D70DB /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
+/* Begin PBXContainerItemProxy section */
+ 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 97C146E61CF9000F007C117D /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 97C146ED1CF9000F007C117D;
+ remoteInfo = Runner;
+ };
+/* End PBXContainerItemProxy section */
+
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
@@ -30,10 +42,15 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 014F010A93253295F5620238 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
+ 08484B4D3782F96E7B6281AA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
+ 113E5C815831E8E83B1D24EE /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
+ 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
+ 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
- 7404F988052D3E92A73EBD7E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
+ 4B99FC96601B9D60C00E3472 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
@@ -44,8 +61,10 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- B53E79FD7E80803BF54D70DB /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- F597851B8230B83CB87D4CF3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ CCF7BC6F4B7334EFCB1BEC67 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
+ DEBFC7FD9205FF41F556DBF2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ F62E49ABA81F430B99525DED /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
+ FA1F5E9E308D75711360FAF6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -53,23 +72,52 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- B9BC3BE22F97E1DD9B850D52 /* Pods_Runner.framework in Frameworks */,
+ 59185AECC1AC289A18E08B27 /* Pods_Runner.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ FA8382EB2FA8D2065DE8E9F1 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 936BDC305018E86F43152805 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
- 9511C1F4E928E9B5DC1D54EE /* Pods */ = {
+ 331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
- 7404F988052D3E92A73EBD7E /* Pods-Runner.debug.xcconfig */,
- F597851B8230B83CB87D4CF3 /* Pods-Runner.release.xcconfig */,
+ 331C807B294A618700263BE5 /* RunnerTests.swift */,
+ );
+ path = RunnerTests;
+ sourceTree = "";
+ };
+ 3EE7EBE41B6B5FCD51539099 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ CCF7BC6F4B7334EFCB1BEC67 /* Pods-Runner.debug.xcconfig */,
+ DEBFC7FD9205FF41F556DBF2 /* Pods-Runner.release.xcconfig */,
+ 08484B4D3782F96E7B6281AA /* Pods-Runner.profile.xcconfig */,
+ F62E49ABA81F430B99525DED /* Pods-RunnerTests.debug.xcconfig */,
+ 014F010A93253295F5620238 /* Pods-RunnerTests.release.xcconfig */,
+ 113E5C815831E8E83B1D24EE /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "";
};
+ 8E7C32B957AAD8E132B67CCE /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ FA1F5E9E308D75711360FAF6 /* Pods_Runner.framework */,
+ 4B99FC96601B9D60C00E3472 /* Pods_RunnerTests.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@@ -87,8 +135,9 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
- 9511C1F4E928E9B5DC1D54EE /* Pods */,
- AA777AFD3B6B2AF061748624 /* Frameworks */,
+ 331C8082294A63A400263BE5 /* RunnerTests */,
+ 3EE7EBE41B6B5FCD51539099 /* Pods */,
+ 8E7C32B957AAD8E132B67CCE /* Frameworks */,
);
sourceTree = "";
};
@@ -96,6 +145,7 @@
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
+ 331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "";
@@ -107,7 +157,6 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
- 97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
@@ -116,29 +165,33 @@
path = Runner;
sourceTree = "";
};
- 97C146F11CF9000F007C117D /* Supporting Files */ = {
- isa = PBXGroup;
- children = (
- );
- name = "Supporting Files";
- sourceTree = "";
- };
- AA777AFD3B6B2AF061748624 /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- B53E79FD7E80803BF54D70DB /* Pods_Runner.framework */,
- );
- name = Frameworks;
- sourceTree = "";
- };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
+ 331C8080294A63A400263BE5 /* RunnerTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
+ buildPhases = (
+ F83437C9E1D805C7C1B2D2B3 /* [CP] Check Pods Manifest.lock */,
+ 331C807D294A63A400263BE5 /* Sources */,
+ 331C807F294A63A400263BE5 /* Resources */,
+ FA8382EB2FA8D2065DE8E9F1 /* Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 331C8086294A63A400263BE5 /* PBXTargetDependency */,
+ );
+ name = RunnerTests;
+ productName = RunnerTests;
+ productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
- 7A815B17627E798BC9FBBF3E /* [CP] Check Pods Manifest.lock */,
+ 91679AE7AACFDEE8A4C79959 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
@@ -161,18 +214,23 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 1430;
- ORGANIZATIONNAME = "The Chromium Authors";
+ BuildIndependentTargetsInParallel = YES;
+ LastUpgradeCheck = 1510;
+ ORGANIZATIONNAME = "";
TargetAttributes = {
+ 331C8080294A63A400263BE5 = {
+ CreatedOnToolsVersion = 14.0;
+ TestTargetID = 97C146ED1CF9000F007C117D;
+ };
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
- LastSwiftMigration = 0910;
+ LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
- compatibilityVersion = "Xcode 3.2";
- developmentRegion = English;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
@@ -184,11 +242,19 @@
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
+ 331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
+ 331C807F294A63A400263BE5 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -219,7 +285,7 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
- 7A815B17627E798BC9FBBF3E /* [CP] Check Pods Manifest.lock */ = {
+ 91679AE7AACFDEE8A4C79959 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -256,9 +322,39 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
+ F83437C9E1D805C7C1B2D2B3 /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
+ 331C807D294A63A400263BE5 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -270,6 +366,14 @@
};
/* End PBXSourcesBuildPhase section */
+/* Begin PBXTargetDependency section */
+ 331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 97C146ED1CF9000F007C117D /* Runner */;
+ targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
@@ -290,10 +394,134 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
+ 249021D3217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Profile;
+ };
+ 249021D4217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.tundralabs.fluttertts.example.flutterTtsExample;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Profile;
+ };
+ 331C8088294A63A400263BE5 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = F62E49ABA81F430B99525DED /* Pods-RunnerTests.debug.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.tundralabs.fluttertts.example.flutterTtsExample.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+ };
+ name = Debug;
+ };
+ 331C8089294A63A400263BE5 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 014F010A93253295F5620238 /* Pods-RunnerTests.release.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.tundralabs.fluttertts.example.flutterTtsExample.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+ };
+ name = Release;
+ };
+ 331C808A294A63A400263BE5 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 113E5C815831E8E83B1D24EE /* Pods-RunnerTests.profile.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.tundralabs.fluttertts.example.flutterTtsExample.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+ };
+ name = Profile;
+ };
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -323,6 +551,7 @@
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
@@ -337,7 +566,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -349,6 +578,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -378,6 +608,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -386,10 +617,12 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
- SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+ SUPPORTED_PLATFORMS = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
@@ -401,24 +634,18 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
- FRAMEWORK_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter",
- );
INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- LIBRARY_SEARCH_PATHS = (
+ LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
- "$(PROJECT_DIR)/Flutter",
+ "@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = com.tundralabs.flutterTtsExample;
+ PRODUCT_BUNDLE_IDENTIFIER = com.tundralabs.fluttertts.example.flutterTtsExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_SWIFT3_OBJC_INFERENCE = On;
- SWIFT_VERSION = 4.0;
+ SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
@@ -429,23 +656,17 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
- FRAMEWORK_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter",
- );
INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- LIBRARY_SEARCH_PATHS = (
+ LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
- "$(PROJECT_DIR)/Flutter",
+ "@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = com.tundralabs.flutterTtsExample;
+ PRODUCT_BUNDLE_IDENTIFIER = com.tundralabs.fluttertts.example.flutterTtsExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
- SWIFT_SWIFT3_OBJC_INFERENCE = On;
- SWIFT_VERSION = 4.0;
+ SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
@@ -453,11 +674,22 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
+ 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 331C8088294A63A400263BE5 /* Debug */,
+ 331C8089294A63A400263BE5 /* Release */,
+ 331C808A294A63A400263BE5 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
+ 249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
@@ -467,6 +699,7 @@
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
+ 249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/apps/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
similarity index 100%
rename from example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
rename to apps/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
similarity index 100%
rename from example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
rename to apps/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/apps/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
similarity index 100%
rename from example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
rename to apps/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/apps/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
similarity index 81%
rename from example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
rename to apps/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index 41adb77b..e3773d42 100644
--- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/apps/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
-
-
-
-
+
+
+
+
+
+
@@ -61,11 +73,9 @@
ReferencedContainer = "container:Runner.xcodeproj">
-
-
Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
similarity index 100%
rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
rename to apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
diff --git a/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 00000000..dc9ada47
Binary files /dev/null and b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 00000000..7353c41e
Binary files /dev/null and b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 00000000..797d452e
Binary files /dev/null and b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 00000000..6ed2d933
Binary files /dev/null and b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 00000000..4cd7b009
Binary files /dev/null and b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 00000000..fe730945
Binary files /dev/null and b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 00000000..321773cd
Binary files /dev/null and b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 00000000..797d452e
Binary files /dev/null and b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 00000000..502f463a
Binary files /dev/null and b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 00000000..0ec30343
Binary files /dev/null and b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 00000000..0ec30343
Binary files /dev/null and b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 00000000..e9f5fea2
Binary files /dev/null and b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 00000000..84ac32ae
Binary files /dev/null and b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 00000000..8953cba0
Binary files /dev/null and b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 00000000..0467bf12
Binary files /dev/null and b/apps/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/apps/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
similarity index 100%
rename from example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
rename to apps/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/apps/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
similarity index 100%
rename from example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
rename to apps/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/apps/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
similarity index 100%
rename from example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
rename to apps/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/apps/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
similarity index 100%
rename from example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
rename to apps/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/apps/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
similarity index 100%
rename from example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
rename to apps/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
diff --git a/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/apps/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
similarity index 100%
rename from example/ios/Runner/Base.lproj/LaunchScreen.storyboard
rename to apps/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
diff --git a/example/ios/Runner/Base.lproj/Main.storyboard b/apps/example/ios/Runner/Base.lproj/Main.storyboard
similarity index 100%
rename from example/ios/Runner/Base.lproj/Main.storyboard
rename to apps/example/ios/Runner/Base.lproj/Main.storyboard
diff --git a/example/ios/Runner/Info.plist b/apps/example/ios/Runner/Info.plist
similarity index 87%
rename from example/ios/Runner/Info.plist
rename to apps/example/ios/Runner/Info.plist
index 51845a36..4702e343 100644
--- a/example/ios/Runner/Info.plist
+++ b/apps/example/ios/Runner/Info.plist
@@ -3,7 +3,9 @@
CFBundleDevelopmentRegion
- en
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Flutter Tts Example
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
@@ -15,21 +17,17 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.0
+ $(FLUTTER_BUILD_NAME)
CFBundleSignature
????
CFBundleVersion
- 1
+ $(FLUTTER_BUILD_NUMBER)
LSRequiresIPhoneOS
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
Main
- UIRequiredDeviceCapabilities
-
- arm64
-
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
@@ -43,8 +41,6 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- UIViewControllerBasedStatusBarAppearance
-
CADisableMinimumFrameDurationOnPhone
UIApplicationSupportsIndirectInputEvents
diff --git a/apps/example/ios/Runner/Runner-Bridging-Header.h b/apps/example/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 00000000..308a2a56
--- /dev/null
+++ b/apps/example/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/apps/example/ios/RunnerTests/RunnerTests.swift b/apps/example/ios/RunnerTests/RunnerTests.swift
new file mode 100644
index 00000000..86a7c3b1
--- /dev/null
+++ b/apps/example/ios/RunnerTests/RunnerTests.swift
@@ -0,0 +1,12 @@
+import Flutter
+import UIKit
+import XCTest
+
+class RunnerTests: XCTestCase {
+
+ func testExample() {
+ // If you add code to the Runner application, consider adding tests here.
+ // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
+ }
+
+}
diff --git a/apps/example/lib/main.dart b/apps/example/lib/main.dart
new file mode 100644
index 00000000..21a321e8
--- /dev/null
+++ b/apps/example/lib/main.dart
@@ -0,0 +1,661 @@
+// ignore_for_file: avoid_print
+
+import 'dart:async';
+import 'dart:io' show Platform;
+import 'dart:math' as math;
+
+import 'package:flutter/foundation.dart' show kIsWeb;
+import 'package:flutter/material.dart';
+import 'package:flutter_tts/flutter_tts.dart';
+import 'package:flutter_tts_android/flutter_tts_android.dart';
+import 'package:flutter_tts_windows/flutter_tts_windows.dart';
+
+extension on Voice {
+ String get displayName {
+ final elements = [name, locale];
+ if (gender != null) {
+ elements.add(gender!);
+ }
+ if (quality != null) {
+ elements.add(quality!);
+ }
+ return elements.join(' - ');
+ }
+}
+
+void main() {
+ runZonedGuarded(() => runApp(MyApp()), (error, stackTrace) {
+ print("Error: $error");
+ print("Stack Trace: $stackTrace");
+ });
+}
+
+class MyApp extends StatefulWidget {
+ const MyApp({super.key});
+
+ @override
+ State createState() => _MyAppState();
+}
+
+enum TtsState { playing, stopped, paused, continued }
+
+class _MyAppState extends State {
+ late FlutterTtsPlatform flutterTts;
+ String? language;
+ String? engine;
+ Voice? voice;
+ final voices = [];
+ double volume = 0.5;
+ double pitch = 1.0;
+ double rate = 0.5;
+ bool isCurrentLanguageInstalled = false;
+
+ String? _newVoiceText;
+ int? _inputLength;
+ final _editingController = TextEditingController();
+
+ TtsProgress? _speakingProgess;
+
+ bool _isWordBoundary = true;
+
+ TtsState ttsState = TtsState.stopped;
+
+ bool get isPlaying => ttsState == TtsState.playing;
+ bool get isStopped => ttsState == TtsState.stopped;
+ bool get isPaused => ttsState == TtsState.paused;
+ bool get isContinued => ttsState == TtsState.continued;
+
+ bool get isIOS => !kIsWeb && Platform.isIOS;
+ bool get isAndroid => !kIsWeb && Platform.isAndroid;
+ bool get isWindows => !kIsWeb && Platform.isWindows;
+ bool get isWeb => kIsWeb;
+
+ @override
+ initState() {
+ super.initState();
+ initTts();
+ }
+
+ dynamic initTts() {
+ flutterTts = FlutterTts.platform;
+
+ _setAwaitOptions();
+
+ if (isAndroid) {
+ _getDefaultEngine();
+ _getDefaultVoice();
+ }
+
+ flutterTts.onSpeakStart = () {
+ setState(() {
+ print("Playing");
+ ttsState = TtsState.playing;
+ });
+ };
+
+ flutterTts.onSpeakComplete = () {
+ setState(() {
+ print("Complete");
+ ttsState = TtsState.stopped;
+ _speakingProgess = null;
+ });
+ };
+
+ flutterTts.onSpeakCancel = () {
+ setState(() {
+ print("Cancel");
+ ttsState = TtsState.stopped;
+ _speakingProgess = null;
+ });
+ };
+
+ flutterTts.onSpeakPause = () {
+ setState(() {
+ print("Paused");
+ ttsState = TtsState.paused;
+ });
+ };
+
+ flutterTts.onSpeakResume = () {
+ setState(() {
+ print("Continued");
+ ttsState = TtsState.continued;
+ });
+ };
+
+ flutterTts.onSpeakError = (msg) {
+ print("error: $msg");
+ setState(() {
+ ttsState = TtsState.stopped;
+ _speakingProgess = null;
+ });
+ };
+
+ flutterTts.onSpeakProgress = (progress) {
+ setState(() {
+ _speakingProgess = progress;
+ });
+ };
+
+ flutterTts.getVoices().then((value) {
+ value.when(
+ (newVoices) => setState(() {
+ voices.clear();
+ voices.addAll(newVoices);
+ }),
+ (e) => print("Error: $e"),
+ );
+ });
+ }
+
+ Future> _getLanguages() async {
+ final languages = await flutterTts.getLanguages();
+ switch (languages) {
+ case SuccessDart():
+ return languages.success;
+ case FailureDart():
+ print("Error: ${languages.error}");
+ return [];
+ }
+ }
+
+ Future> _getEngines() async {
+ if (flutterTts case final FlutterTtsAndroid tts) {
+ final engines = await tts.getEngines();
+ switch (engines) {
+ case SuccessDart(success: var newEngines):
+ return newEngines;
+ case FailureDart(error: var e):
+ print("Error: $e");
+ return [];
+ }
+ }
+
+ return [];
+ }
+
+ Future _getDefaultEngine() async {
+ if (flutterTts case final FlutterTtsAndroid tts) {
+ final defaultEngine = await tts.getDefaultEngine();
+ switch (defaultEngine) {
+ case SuccessDart(success: var newEngine):
+ engine = newEngine;
+ break;
+ case FailureDart(error: var e):
+ print("Error: $e");
+ engine = null;
+ break;
+ }
+ }
+ }
+
+ Future _getDefaultVoice() async {
+ if (flutterTts case final FlutterTtsAndroid tts) {
+ final defVoice = await tts.getDefaultVoice();
+ switch (defVoice) {
+ case SuccessDart():
+ voice = defVoice.success;
+ break;
+ case FailureDart():
+ print("Error: ${defVoice.error}");
+ voice = null;
+ break;
+ }
+ }
+ }
+
+ Future _speak() async {
+ await flutterTts.setVolume(volume);
+ await flutterTts.setSpeechRate(rate);
+ await flutterTts.setPitch(pitch);
+
+ if (_newVoiceText != null) {
+ if (_newVoiceText!.isNotEmpty) {
+ await flutterTts.speak(_newVoiceText!);
+ }
+ }
+ }
+
+ Future _setAwaitOptions() async {
+ await flutterTts.awaitSpeakCompletion(awaitCompletion: true);
+ }
+
+ Future _stop() async {
+ var result = await flutterTts.stop();
+ switch (result) {
+ case SuccessDart():
+ if (result.success.success) {
+ setState(() => ttsState = TtsState.stopped);
+ }
+ break;
+ case FailureDart():
+ print("Error: ${result.error}");
+ break;
+ }
+ }
+
+ Future _pause() async {
+ var result = await flutterTts.pause();
+ switch (result) {
+ case SuccessDart():
+ if (result.success.success) {
+ setState(() => ttsState = TtsState.paused);
+ }
+ break;
+ case FailureDart():
+ print("Error: ${result.error}");
+ break;
+ }
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ flutterTts.stop();
+ }
+
+ List> getEnginesDropDownMenuItems(
+ List engines,
+ ) {
+ var items = >[];
+ for (dynamic type in engines) {
+ items.add(
+ DropdownMenuItem(value: type as String?, child: Text((type as String))),
+ );
+ }
+ return items;
+ }
+
+ void changedEnginesDropDownItem(String? selectedEngine) async {
+ if (selectedEngine != null && selectedEngine.isNotEmpty) {
+ if (flutterTts case final FlutterTtsAndroid tts) {
+ await tts.setEngine(selectedEngine);
+ setState(() {
+ engine = selectedEngine;
+ language = null;
+ voice = null;
+ voices.clear();
+ });
+ }
+ }
+ }
+
+ List> getLanguageDropDownMenuItems(
+ List languages,
+ ) {
+ var items = >[];
+ for (dynamic type in languages) {
+ items.add(
+ DropdownMenuItem(value: type as String?, child: Text((type as String))),
+ );
+ }
+ return items;
+ }
+
+ void changedLanguageDropDownItem(String? selectedType) async {
+ var newIsCurrentLanguageInstalled = false;
+ setState(() {
+ language = selectedType;
+ voice = null;
+ isCurrentLanguageInstalled = newIsCurrentLanguageInstalled;
+ });
+ }
+
+ void _onChange(String text) {
+ setState(() {
+ _newVoiceText = text;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ theme: ThemeData.light(),
+ darkTheme: ThemeData.dark(),
+ themeMode: ThemeMode.system,
+ home: Scaffold(
+ appBar: AppBar(title: Text('Flutter TTS')),
+ body: SingleChildScrollView(
+ scrollDirection: Axis.vertical,
+ child: Column(
+ children: [
+ if (_speakingProgess case final progess?) ...[
+ Padding(
+ padding: EdgeInsets.only(top: 25.0, left: 25.0, right: 25.0),
+ child: Text(
+ progess.word,
+ style: TextStyle(color: Colors.red),
+ ),
+ ),
+ Padding(
+ padding: EdgeInsets.only(top: 25.0, left: 25.0, right: 25.0),
+ child: Text.rich(
+ TextSpan(
+ children: [
+ if (progess.start > 0)
+ TextSpan(
+ text: progess.text.substring(0, progess.start),
+ ),
+ TextSpan(
+ text: progess.text.substring(
+ math.max(0, progess.start),
+ math.min(progess.text.length, progess.end),
+ ),
+ style: TextStyle(color: Colors.red),
+ ),
+ if (progess.end < progess.text.length - 1)
+ TextSpan(text: progess.text.substring(progess.end)),
+ ],
+ ),
+ ),
+ ),
+ ],
+ _inputSection(),
+ _btnSection(),
+ _engineSection(),
+ _futureBuilder(),
+ Row(
+ mainAxisSize: MainAxisSize.min,
+ spacing: 16,
+ children: [_buildGetVoiceBtn(), _buildAddTextToSpeak()],
+ ),
+ if (voices.isNotEmpty) _buildSelectVoice(),
+ _buildSliders(),
+ if (isAndroid) _getMaxSpeechInputLengthSection(),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ TextButton _buildAddTextToSpeak() {
+ return TextButton(
+ onPressed: () {
+ _newVoiceText = """
+The quick brown fox jumps over the lazy dog.
+混沌未分天地乱,茫茫渺渺无人见。 自从盘古破鸿蒙,开辟从兹清浊辨。 覆载群生仰至仁,发明万物皆为善。 欲知造化会元功,须看《西游释厄传》。
+The quick brown fox jumps over the lazy dog.
+我说"1<2" & 3>0,对吧?
+Whether the weather be fine or whether the weather be not.
+Whether the weather be cold or whether the weather be hot.
+We'll weather the weather whether we like it or not.
+季姬寂,集鸡,鸡即棘鸡。棘鸡饥叽,季姬及箕稷济鸡。鸡既济,跻姬笈,季姬忌,急咭鸡,鸡急,继圾几,季姬急,即籍箕击鸡,箕疾击几伎,伎即齑,鸡叽集几基,季姬急极屐击鸡,鸡既殛,季姬激,即记《季姬击鸡记》。
+なまむぎ なまごめ なまたまご
+간장공장 공장장은 강공장장이고된장공장 공장장은 공공장장이다
+Cinq chiens chassent six chats.
+На дворе-трава, на траве-дрова. Не руби дрова на траве-двора.
+""";
+ _editingController.text = _newVoiceText!;
+ },
+ child: Text("Add Default Text"),
+ );
+ }
+
+ TextButton _buildGetVoiceBtn() {
+ return TextButton(
+ onPressed: () async {
+ var result = await flutterTts.getVoices();
+ switch (result) {
+ case SuccessDart():
+ setState(() {
+ voices.clear();
+ voices.addAll(result.success);
+ });
+ break;
+ case FailureDart():
+ print("Error: ${result.error}");
+ break;
+ }
+ },
+ child: Text("Get Voices"),
+ );
+ }
+
+ DropdownButton _buildSelectVoice() {
+ final selectedLang = language?.split('-').first;
+ var voiceToShow = voices.where(
+ (element) =>
+ selectedLang == null || element.locale.startsWith(selectedLang),
+ );
+
+ if (voiceToShow.isEmpty) {
+ voiceToShow = voices;
+ }
+
+ return DropdownButton(
+ value: voice,
+ items: [
+ for (final ii in voiceToShow)
+ DropdownMenuItem(value: ii, child: Text(ii.displayName)),
+ ],
+ onChanged: (value) {
+ setState(() {
+ voice = value;
+ });
+
+ if (value != null) {
+ flutterTts.setVoice(value);
+ }
+ },
+ );
+ }
+
+ Widget _engineSection() {
+ if (isAndroid) {
+ return FutureBuilder(
+ future: _getEngines(),
+ builder: (BuildContext context, AsyncSnapshot snapshot) {
+ if (snapshot.hasData) {
+ return _enginesDropDownSection(snapshot.data as List);
+ } else if (snapshot.hasError) {
+ return Text('Error loading engines...');
+ } else {
+ return Text('Loading engines...');
+ }
+ },
+ );
+ } else if (flutterTts case final FlutterTtsWindows winTts) {
+ return Row(
+ spacing: 8,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(_isWordBoundary ? "Word Boundary" : "Sentence Boundary"),
+ Switch(
+ value: _isWordBoundary,
+ onChanged: (value) async {
+ await winTts.setBoundaryType(isWordBoundary: value);
+ setState(() => _isWordBoundary = value);
+ },
+ ),
+ ],
+ );
+ } else {
+ return SizedBox(width: 0, height: 0);
+ }
+ }
+
+ Widget _futureBuilder() => FutureBuilder(
+ future: _getLanguages(),
+ builder: (BuildContext context, AsyncSnapshot snapshot) {
+ if (snapshot.hasData) {
+ return _languageDropDownSection(snapshot.data as List);
+ } else if (snapshot.hasError) {
+ return Text('Error loading languages...\n${snapshot.error}');
+ } else {
+ return Text('Loading Languages...');
+ }
+ },
+ );
+
+ Widget _inputSection() => Container(
+ alignment: Alignment.topCenter,
+ padding: EdgeInsets.only(top: 25.0, left: 25.0, right: 25.0),
+ child: TextField(
+ controller: _editingController,
+ maxLines: 11,
+ minLines: 6,
+ onChanged: (String value) {
+ _onChange(value);
+ },
+ ),
+ );
+
+ Widget _btnSection() {
+ 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 _enginesDropDownSection(List engines) => Container(
+ padding: EdgeInsets.only(top: 50.0),
+ child: DropdownButton(
+ value: engine,
+ items: getEnginesDropDownMenuItems(engines),
+ onChanged: changedEnginesDropDownItem,
+ ),
+ );
+
+ Widget _languageDropDownSection(List languages) => Container(
+ padding: EdgeInsets.only(top: 10.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ DropdownButton(
+ value: language,
+ items: getLanguageDropDownMenuItems(languages),
+ 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 _getMaxSpeechInputLengthSection() {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ ElevatedButton(
+ child: Text('Get max speech input length'),
+ onPressed: () async {},
+ ),
+ Text("$_inputLength characters"),
+ ],
+ );
+ }
+
+ Widget _buildSliders() {
+ return Column(children: [_volume(), _pitch(), _rate()]);
+ }
+
+ Widget _volume() {
+ return Slider(
+ value: volume,
+ onChanged: (newVolume) {
+ setState(() {
+ volume = newVolume;
+ flutterTts.setVolume(volume);
+ });
+ },
+ min: 0.0,
+ max: 1.0,
+ divisions: 10,
+ label: "Volume: ${volume.toStringAsFixed(1)}",
+ );
+ }
+
+ Widget _pitch() {
+ return Slider(
+ value: pitch,
+ onChanged: (newPitch) {
+ setState(() {
+ pitch = newPitch;
+ flutterTts.setPitch(pitch);
+ });
+ },
+ min: 0.5,
+ max: 2.0,
+ divisions: 15,
+ label: "Pitch: ${pitch.toStringAsFixed(1)}",
+ activeColor: Colors.red,
+ );
+ }
+
+ Widget _rate() {
+ return Slider(
+ value: rate,
+ onChanged: (newRate) {
+ setState(() {
+ rate = newRate;
+ flutterTts.setSpeechRate(rate);
+ });
+ },
+ min: 0.0,
+ max: 2.0,
+ divisions: 20,
+ label: "Rate: ${rate.toStringAsFixed(1)}",
+ activeColor: Colors.green,
+ );
+ }
+}
diff --git a/example/macos/.gitignore b/apps/example/macos/.gitignore
similarity index 65%
rename from example/macos/.gitignore
rename to apps/example/macos/.gitignore
index f73015c4..746adbb6 100644
--- a/example/macos/.gitignore
+++ b/apps/example/macos/.gitignore
@@ -1,7 +1,7 @@
# Flutter-related
**/Flutter/ephemeral/
**/Pods/
-**/Flutter/GeneratedPluginRegistrant.swift
# Xcode-related
+**/dgph
**/xcuserdata/
diff --git a/apps/example/macos/Flutter/Flutter-Debug.xcconfig b/apps/example/macos/Flutter/Flutter-Debug.xcconfig
new file mode 100644
index 00000000..4b81f9b2
--- /dev/null
+++ b/apps/example/macos/Flutter/Flutter-Debug.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/apps/example/macos/Flutter/Flutter-Release.xcconfig b/apps/example/macos/Flutter/Flutter-Release.xcconfig
new file mode 100644
index 00000000..5caa9d15
--- /dev/null
+++ b/apps/example/macos/Flutter/Flutter-Release.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/apps/example/macos/Flutter/GeneratedPluginRegistrant.swift b/apps/example/macos/Flutter/GeneratedPluginRegistrant.swift
new file mode 100644
index 00000000..53005524
--- /dev/null
+++ b/apps/example/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -0,0 +1,12 @@
+//
+// Generated file. Do not edit.
+//
+
+import FlutterMacOS
+import Foundation
+
+import flutter_tts_macos
+
+func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+ FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin"))
+}
diff --git a/example/macos/Podfile b/apps/example/macos/Podfile
similarity index 95%
rename from example/macos/Podfile
rename to apps/example/macos/Podfile
index 9ec46f8c..ff5ddb3b 100644
--- a/example/macos/Podfile
+++ b/apps/example/macos/Podfile
@@ -28,9 +28,11 @@ flutter_macos_podfile_setup
target 'Runner' do
use_frameworks!
- use_modular_headers!
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
+ target 'RunnerTests' do
+ inherit! :search_paths
+ end
end
post_install do |installer|
diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/apps/example/macos/Runner.xcodeproj/project.pbxproj
similarity index 69%
rename from example/macos/Runner.xcodeproj/project.pbxproj
rename to apps/example/macos/Runner.xcodeproj/project.pbxproj
index 7529558d..7a842fc2 100644
--- a/example/macos/Runner.xcodeproj/project.pbxproj
+++ b/apps/example/macos/Runner.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 51;
+ objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
@@ -21,15 +21,24 @@
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
+ 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
- AF47F6675ACF6F652B5F619C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 59757445EA1BFBFD80865428 /* Pods_Runner.framework */; };
+ 81814821D98A230A3082EB4F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DD57EF221AB951F22FF40F3 /* Pods_Runner.framework */; };
+ A164236EF8D8116C89137765 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A9E7F1F99AEEE2115716D78 /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
+ 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 33CC10EC2044A3C60003C045;
+ remoteInfo = Runner;
+ };
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
@@ -53,10 +62,13 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- 1D4577081AF3B0ACCB9634B9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
+ 0DD57EF221AB951F22FF40F3 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 1A9E7F1F99AEEE2115716D78 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; };
- 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 33CC10ED2044A3C60003C045 /* flutter_tts_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = flutter_tts_example.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
@@ -68,25 +80,44 @@
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; };
- 59757445EA1BFBFD80865428 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 7A444ED245F431EA6C18BDDF /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ 50544B26B78743C9E5BA9730 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
+ 68D1BAE90FC8AACBF92FAE46 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
+ 6F589161388A001191BBEB3E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
+ 73479AF64B450DC9B8338EA3 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; };
+ 83F1C99C4E6DECE1A2A77CD0 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; };
- C937088BEE8A4C6EFDA6C14D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
+ AAF8BEE663C9E43E751A266C /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ 331C80D2294CF70F00263BE5 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ A164236EF8D8116C89137765 /* Pods_RunnerTests.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
33CC10EA2044A3C60003C045 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- AF47F6675ACF6F652B5F619C /* Pods_Runner.framework in Frameworks */,
+ 81814821D98A230A3082EB4F /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 331C80D6294CF71000263BE5 /* RunnerTests */ = {
+ isa = PBXGroup;
+ children = (
+ 331C80D7294CF71000263BE5 /* RunnerTests.swift */,
+ );
+ path = RunnerTests;
+ sourceTree = "";
+ };
33BA886A226E78AF003329D5 /* Configs */ = {
isa = PBXGroup;
children = (
@@ -103,16 +134,18 @@
children = (
33FAB671232836740065AC1E /* Runner */,
33CEB47122A05771004F2AC0 /* Flutter */,
+ 331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
- 5F9F48FBDE9334256A3D8D1C /* Pods */,
- 653A588D5B8D527EE4357BF9 /* Frameworks */,
+ D73912EC22F37F3D000D13A0 /* Frameworks */,
+ FCDE38BA0FB84A2FBC55BACD /* Pods */,
);
sourceTree = "";
};
33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup;
children = (
- 33CC10ED2044A3C60003C045 /* example.app */,
+ 33CC10ED2044A3C60003C045 /* flutter_tts_example.app */,
+ 331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "";
@@ -152,38 +185,62 @@
path = Runner;
sourceTree = "";
};
- 5F9F48FBDE9334256A3D8D1C /* Pods */ = {
+ D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
- C937088BEE8A4C6EFDA6C14D /* Pods-Runner.debug.xcconfig */,
- 7A444ED245F431EA6C18BDDF /* Pods-Runner.release.xcconfig */,
- 1D4577081AF3B0ACCB9634B9 /* Pods-Runner.profile.xcconfig */,
+ 0DD57EF221AB951F22FF40F3 /* Pods_Runner.framework */,
+ 1A9E7F1F99AEEE2115716D78 /* Pods_RunnerTests.framework */,
);
- path = Pods;
+ name = Frameworks;
sourceTree = "";
};
- 653A588D5B8D527EE4357BF9 /* Frameworks */ = {
+ FCDE38BA0FB84A2FBC55BACD /* Pods */ = {
isa = PBXGroup;
children = (
- 59757445EA1BFBFD80865428 /* Pods_Runner.framework */,
- );
- name = Frameworks;
+ 6F589161388A001191BBEB3E /* Pods-Runner.debug.xcconfig */,
+ 83F1C99C4E6DECE1A2A77CD0 /* Pods-Runner.release.xcconfig */,
+ 73479AF64B450DC9B8338EA3 /* Pods-Runner.profile.xcconfig */,
+ AAF8BEE663C9E43E751A266C /* Pods-RunnerTests.debug.xcconfig */,
+ 50544B26B78743C9E5BA9730 /* Pods-RunnerTests.release.xcconfig */,
+ 68D1BAE90FC8AACBF92FAE46 /* Pods-RunnerTests.profile.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
sourceTree = "";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
+ 331C80D4294CF70F00263BE5 /* RunnerTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
+ buildPhases = (
+ 9231E46C7194B080EB3F8C1D /* [CP] Check Pods Manifest.lock */,
+ 331C80D1294CF70F00263BE5 /* Sources */,
+ 331C80D2294CF70F00263BE5 /* Frameworks */,
+ 331C80D3294CF70F00263BE5 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 331C80DA294CF71000263BE5 /* PBXTargetDependency */,
+ );
+ name = RunnerTests;
+ productName = RunnerTests;
+ productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
33CC10EC2044A3C60003C045 /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
- 55F474F11A569479CD85540F /* [CP] Check Pods Manifest.lock */,
+ EEFF4A371A563C47E79A4525 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
- 9A8FDD7662D2D721983EFE47 /* [CP] Embed Pods Frameworks */,
+ 43771EAC433B4215C3B41E58 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -192,7 +249,7 @@
);
name = Runner;
productName = Runner;
- productReference = 33CC10ED2044A3C60003C045 /* example.app */;
+ productReference = 33CC10ED2044A3C60003C045 /* flutter_tts_example.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
@@ -201,10 +258,15 @@
33CC10E52044A3C60003C045 /* Project object */ = {
isa = PBXProject;
attributes = {
+ BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 0920;
- LastUpgradeCheck = 1150;
- ORGANIZATIONNAME = "The Flutter Authors";
+ LastUpgradeCheck = 1510;
+ ORGANIZATIONNAME = "";
TargetAttributes = {
+ 331C80D4294CF70F00263BE5 = {
+ CreatedOnToolsVersion = 14.0;
+ TestTargetID = 33CC10EC2044A3C60003C045;
+ };
33CC10EC2044A3C60003C045 = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1100;
@@ -222,7 +284,7 @@
};
};
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
- compatibilityVersion = "Xcode 8.0";
+ compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@@ -235,12 +297,20 @@
projectRoot = "";
targets = (
33CC10EC2044A3C60003C045 /* Runner */,
+ 331C80D4294CF70F00263BE5 /* RunnerTests */,
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
+ 331C80D3294CF70F00263BE5 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
33CC10EB2044A3C60003C045 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -255,6 +325,7 @@
/* Begin PBXShellScriptBuildPhase section */
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -288,9 +359,26 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh\ntouch Flutter/ephemeral/tripwire\n";
+ shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
+ };
+ 43771EAC433B4215C3B41E58 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
};
- 55F474F11A569479CD85540F /* [CP] Check Pods Manifest.lock */ = {
+ 9231E46C7194B080EB3F8C1D /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -305,34 +393,46 @@
outputFileListPaths = (
);
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
- 9A8FDD7662D2D721983EFE47 /* [CP] Embed Pods Frameworks */ = {
+ EEFF4A371A563C47E79A4525 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
+ inputFileListPaths = (
+ );
inputPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/flutter_tts/flutter_tts.framework",
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
);
- name = "[CP] Embed Pods Frameworks";
outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_tts.framework",
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
+ 331C80D1294CF70F00263BE5 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
33CC10E92044A3C60003C045 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -346,6 +446,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
+ 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 33CC10EC2044A3C60003C045 /* Runner */;
+ targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */;
+ };
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
@@ -366,11 +471,57 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
+ 331C80DB294CF71000263BE5 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = AAF8BEE663C9E43E751A266C /* Pods-RunnerTests.debug.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.tundralabs.fluttertts.example.flutterTtsExample.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flutter_tts_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flutter_tts_example";
+ };
+ name = Debug;
+ };
+ 331C80DC294CF71000263BE5 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 50544B26B78743C9E5BA9730 /* Pods-RunnerTests.release.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.tundralabs.fluttertts.example.flutterTtsExample.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flutter_tts_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flutter_tts_example";
+ };
+ name = Release;
+ };
+ 331C80DD294CF71000263BE5 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 68D1BAE90FC8AACBF92FAE46 /* Pods-RunnerTests.profile.xcconfig */;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.tundralabs.fluttertts.example.flutterTtsExample.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flutter_tts_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flutter_tts_example";
+ };
+ name = Profile;
+ };
338D0CE9231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@@ -394,9 +545,11 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
+ DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -419,13 +572,8 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
- CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
- FRAMEWORK_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter/ephemeral",
- );
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -449,6 +597,7 @@
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@@ -472,9 +621,11 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
+ DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
@@ -502,6 +653,7 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
@@ -525,9 +677,11 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
+ DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -550,13 +704,8 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
- CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
- FRAMEWORK_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter/ephemeral",
- );
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -575,13 +724,8 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
- CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
- FRAMEWORK_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter/ephemeral",
- );
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -611,6 +755,16 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
+ 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 331C80DB294CF71000263BE5 /* Debug */,
+ 331C80DC294CF71000263BE5 /* Release */,
+ 331C80DD294CF71000263BE5 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
similarity index 100%
rename from example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
rename to apps/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/apps/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
similarity index 85%
rename from example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
rename to apps/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index 6111bd0a..a1c30175 100644
--- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/apps/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
@@ -31,19 +31,20 @@
+ skipped = "NO"
+ parallelizable = "YES">
@@ -58,20 +59,21 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
+ enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
diff --git a/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/apps/example/macos/Runner.xcworkspace/contents.xcworkspacedata
similarity index 100%
rename from example/macos/Runner.xcworkspace/contents.xcworkspacedata
rename to apps/example/macos/Runner.xcworkspace/contents.xcworkspacedata
diff --git a/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
similarity index 100%
rename from example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
rename to apps/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
diff --git a/example/macos/Runner/AppDelegate.swift b/apps/example/macos/Runner/AppDelegate.swift
similarity index 62%
rename from example/macos/Runner/AppDelegate.swift
rename to apps/example/macos/Runner/AppDelegate.swift
index d53ef643..b3c17614 100644
--- a/example/macos/Runner/AppDelegate.swift
+++ b/apps/example/macos/Runner/AppDelegate.swift
@@ -1,9 +1,13 @@
import Cocoa
import FlutterMacOS
-@NSApplicationMain
+@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
+
+ override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
+ return true
+ }
}
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
similarity index 100%
rename from example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
rename to apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
diff --git a/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
new file mode 100644
index 00000000..82b6f9d9
Binary files /dev/null and b/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ
diff --git a/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
new file mode 100644
index 00000000..13b35eba
Binary files /dev/null and b/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ
diff --git a/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
new file mode 100644
index 00000000..0a3f5fa4
Binary files /dev/null and b/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ
diff --git a/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
new file mode 100644
index 00000000..bdb57226
Binary files /dev/null and b/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ
diff --git a/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
new file mode 100644
index 00000000..f083318e
Binary files /dev/null and b/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ
diff --git a/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
new file mode 100644
index 00000000..326c0e72
Binary files /dev/null and b/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ
diff --git a/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
new file mode 100644
index 00000000..2f1632cf
Binary files /dev/null and b/apps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ
diff --git a/example/macos/Runner/Base.lproj/MainMenu.xib b/apps/example/macos/Runner/Base.lproj/MainMenu.xib
similarity index 98%
rename from example/macos/Runner/Base.lproj/MainMenu.xib
rename to apps/example/macos/Runner/Base.lproj/MainMenu.xib
index 537341ab..80e867a4 100644
--- a/example/macos/Runner/Base.lproj/MainMenu.xib
+++ b/apps/example/macos/Runner/Base.lproj/MainMenu.xib
@@ -323,6 +323,10 @@
+
diff --git a/example/macos/Runner/Configs/AppInfo.xcconfig b/apps/example/macos/Runner/Configs/AppInfo.xcconfig
similarity index 68%
rename from example/macos/Runner/Configs/AppInfo.xcconfig
rename to apps/example/macos/Runner/Configs/AppInfo.xcconfig
index f960fd48..41f79de2 100644
--- a/example/macos/Runner/Configs/AppInfo.xcconfig
+++ b/apps/example/macos/Runner/Configs/AppInfo.xcconfig
@@ -5,10 +5,10 @@
// 'flutter create' template.
// The application's name. By default this is also the title of the Flutter window.
-PRODUCT_NAME = example
+PRODUCT_NAME = flutter_tts_example
// The application's bundle identifier
-PRODUCT_BUNDLE_IDENTIFIER = com.tundralabs.example
+PRODUCT_BUNDLE_IDENTIFIER = com.tundralabs.fluttertts.example.flutterTtsExample
// The copyright displayed in application information
-PRODUCT_COPYRIGHT = Copyright © 2020 com.tundralabs. All rights reserved.
+PRODUCT_COPYRIGHT = Copyright © 2025 com.tundralabs.fluttertts.example. All rights reserved.
diff --git a/example/macos/Runner/Configs/Debug.xcconfig b/apps/example/macos/Runner/Configs/Debug.xcconfig
similarity index 100%
rename from example/macos/Runner/Configs/Debug.xcconfig
rename to apps/example/macos/Runner/Configs/Debug.xcconfig
diff --git a/example/macos/Runner/Configs/Release.xcconfig b/apps/example/macos/Runner/Configs/Release.xcconfig
similarity index 100%
rename from example/macos/Runner/Configs/Release.xcconfig
rename to apps/example/macos/Runner/Configs/Release.xcconfig
diff --git a/example/macos/Runner/Configs/Warnings.xcconfig b/apps/example/macos/Runner/Configs/Warnings.xcconfig
similarity index 100%
rename from example/macos/Runner/Configs/Warnings.xcconfig
rename to apps/example/macos/Runner/Configs/Warnings.xcconfig
diff --git a/example/macos/Runner/DebugProfile.entitlements b/apps/example/macos/Runner/DebugProfile.entitlements
similarity index 100%
rename from example/macos/Runner/DebugProfile.entitlements
rename to apps/example/macos/Runner/DebugProfile.entitlements
diff --git a/example/macos/Runner/Info.plist b/apps/example/macos/Runner/Info.plist
similarity index 100%
rename from example/macos/Runner/Info.plist
rename to apps/example/macos/Runner/Info.plist
diff --git a/example/macos/Runner/MainFlutterWindow.swift b/apps/example/macos/Runner/MainFlutterWindow.swift
similarity index 84%
rename from example/macos/Runner/MainFlutterWindow.swift
rename to apps/example/macos/Runner/MainFlutterWindow.swift
index 2722837e..3cc05eb2 100644
--- a/example/macos/Runner/MainFlutterWindow.swift
+++ b/apps/example/macos/Runner/MainFlutterWindow.swift
@@ -3,7 +3,7 @@ import FlutterMacOS
class MainFlutterWindow: NSWindow {
override func awakeFromNib() {
- let flutterViewController = FlutterViewController.init()
+ let flutterViewController = FlutterViewController()
let windowFrame = self.frame
self.contentViewController = flutterViewController
self.setFrame(windowFrame, display: true)
diff --git a/example/macos/Runner/Release.entitlements b/apps/example/macos/Runner/Release.entitlements
similarity index 100%
rename from example/macos/Runner/Release.entitlements
rename to apps/example/macos/Runner/Release.entitlements
diff --git a/apps/example/macos/RunnerTests/RunnerTests.swift b/apps/example/macos/RunnerTests/RunnerTests.swift
new file mode 100644
index 00000000..61f3bd1f
--- /dev/null
+++ b/apps/example/macos/RunnerTests/RunnerTests.swift
@@ -0,0 +1,12 @@
+import Cocoa
+import FlutterMacOS
+import XCTest
+
+class RunnerTests: XCTestCase {
+
+ func testExample() {
+ // If you add code to the Runner application, consider adding tests here.
+ // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
+ }
+
+}
diff --git a/example/pubspec.yaml b/apps/example/pubspec.yaml
similarity index 87%
rename from example/pubspec.yaml
rename to apps/example/pubspec.yaml
index 1f16ca43..a50cf577 100644
--- a/example/pubspec.yaml
+++ b/apps/example/pubspec.yaml
@@ -1,5 +1,11 @@
name: flutter_tts_example
description: Demonstrates how to use the flutter_tts plugin.
+version: 0.0.1+1
+publish_to: none
+resolution: workspace
+
+environment:
+ sdk: ^3.9.0
dependencies:
flutter:
@@ -8,22 +14,20 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.1+1
+ flutter_tts: ^5.0.0
+ flutter_tts_android: ^5.0.0
+ flutter_tts_windows: ^5.0.0
dev_dependencies:
flutter_test:
sdk: flutter
+ flutter_lints: ^6.0.0
- flutter_tts:
- path: ../
-
-dependency_overrides:
- material_color_utilities: 0.11.1
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
-
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
@@ -57,8 +61,5 @@ flutter:
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
- # For details regarding fonts from package dependencies,
+ # For details regarding fonts from package dependencies,
# see https://flutter.io/custom-fonts/#from-packages
-
-environment:
- sdk: '>=2.12.0-0 <3.0.0'
diff --git a/example/test/widget_test.dart b/apps/example/test/widget_test.dart
similarity index 100%
rename from example/test/widget_test.dart
rename to apps/example/test/widget_test.dart
diff --git a/example/web/favicon.png b/apps/example/web/favicon.png
similarity index 100%
rename from example/web/favicon.png
rename to apps/example/web/favicon.png
diff --git a/example/web/icons/Icon-192.png b/apps/example/web/icons/Icon-192.png
similarity index 100%
rename from example/web/icons/Icon-192.png
rename to apps/example/web/icons/Icon-192.png
diff --git a/example/web/icons/Icon-512.png b/apps/example/web/icons/Icon-512.png
similarity index 100%
rename from example/web/icons/Icon-512.png
rename to apps/example/web/icons/Icon-512.png
diff --git a/example/web/index.html b/apps/example/web/index.html
similarity index 100%
rename from example/web/index.html
rename to apps/example/web/index.html
diff --git a/example/web/manifest.json b/apps/example/web/manifest.json
similarity index 100%
rename from example/web/manifest.json
rename to apps/example/web/manifest.json
diff --git a/example/windows/.gitignore b/apps/example/windows/.gitignore
similarity index 100%
rename from example/windows/.gitignore
rename to apps/example/windows/.gitignore
diff --git a/example/windows/CMakeLists.txt b/apps/example/windows/CMakeLists.txt
similarity index 100%
rename from example/windows/CMakeLists.txt
rename to apps/example/windows/CMakeLists.txt
diff --git a/example/windows/flutter/CMakeLists.txt b/apps/example/windows/flutter/CMakeLists.txt
similarity index 100%
rename from example/windows/flutter/CMakeLists.txt
rename to apps/example/windows/flutter/CMakeLists.txt
diff --git a/example/windows/runner/CMakeLists.txt b/apps/example/windows/runner/CMakeLists.txt
similarity index 100%
rename from example/windows/runner/CMakeLists.txt
rename to apps/example/windows/runner/CMakeLists.txt
diff --git a/example/windows/runner/Runner.rc b/apps/example/windows/runner/Runner.rc
similarity index 100%
rename from example/windows/runner/Runner.rc
rename to apps/example/windows/runner/Runner.rc
diff --git a/example/windows/runner/flutter_window.cpp b/apps/example/windows/runner/flutter_window.cpp
similarity index 100%
rename from example/windows/runner/flutter_window.cpp
rename to apps/example/windows/runner/flutter_window.cpp
diff --git a/example/windows/runner/flutter_window.h b/apps/example/windows/runner/flutter_window.h
similarity index 100%
rename from example/windows/runner/flutter_window.h
rename to apps/example/windows/runner/flutter_window.h
diff --git a/example/windows/runner/main.cpp b/apps/example/windows/runner/main.cpp
similarity index 100%
rename from example/windows/runner/main.cpp
rename to apps/example/windows/runner/main.cpp
diff --git a/example/windows/runner/resource.h b/apps/example/windows/runner/resource.h
similarity index 100%
rename from example/windows/runner/resource.h
rename to apps/example/windows/runner/resource.h
diff --git a/example/windows/runner/resources/app_icon.ico b/apps/example/windows/runner/resources/app_icon.ico
similarity index 100%
rename from example/windows/runner/resources/app_icon.ico
rename to apps/example/windows/runner/resources/app_icon.ico
diff --git a/example/windows/runner/runner.exe.manifest b/apps/example/windows/runner/runner.exe.manifest
similarity index 100%
rename from example/windows/runner/runner.exe.manifest
rename to apps/example/windows/runner/runner.exe.manifest
diff --git a/example/windows/runner/utils.cpp b/apps/example/windows/runner/utils.cpp
similarity index 100%
rename from example/windows/runner/utils.cpp
rename to apps/example/windows/runner/utils.cpp
diff --git a/example/windows/runner/utils.h b/apps/example/windows/runner/utils.h
similarity index 100%
rename from example/windows/runner/utils.h
rename to apps/example/windows/runner/utils.h
diff --git a/example/windows/runner/win32_window.cpp b/apps/example/windows/runner/win32_window.cpp
similarity index 100%
rename from example/windows/runner/win32_window.cpp
rename to apps/example/windows/runner/win32_window.cpp
diff --git a/example/windows/runner/win32_window.h b/apps/example/windows/runner/win32_window.h
similarity index 100%
rename from example/windows/runner/win32_window.h
rename to apps/example/windows/runner/win32_window.h
diff --git a/example/winuwp/.gitignore b/apps/example/winuwp/.gitignore
similarity index 100%
rename from example/winuwp/.gitignore
rename to apps/example/winuwp/.gitignore
diff --git a/example/winuwp/CMakeLists.txt b/apps/example/winuwp/CMakeLists.txt
similarity index 100%
rename from example/winuwp/CMakeLists.txt
rename to apps/example/winuwp/CMakeLists.txt
diff --git a/example/winuwp/flutter/CMakeLists.txt b/apps/example/winuwp/flutter/CMakeLists.txt
similarity index 100%
rename from example/winuwp/flutter/CMakeLists.txt
rename to apps/example/winuwp/flutter/CMakeLists.txt
diff --git a/example/winuwp/flutter/flutter_windows.h b/apps/example/winuwp/flutter/flutter_windows.h
similarity index 100%
rename from example/winuwp/flutter/flutter_windows.h
rename to apps/example/winuwp/flutter/flutter_windows.h
diff --git a/example/winuwp/project_version b/apps/example/winuwp/project_version
similarity index 100%
rename from example/winuwp/project_version
rename to apps/example/winuwp/project_version
diff --git a/example/winuwp/runner_uwp/Assets/LargeTile.scale-100.png b/apps/example/winuwp/runner_uwp/Assets/LargeTile.scale-100.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/LargeTile.scale-100.png
rename to apps/example/winuwp/runner_uwp/Assets/LargeTile.scale-100.png
diff --git a/example/winuwp/runner_uwp/Assets/LargeTile.scale-125.png b/apps/example/winuwp/runner_uwp/Assets/LargeTile.scale-125.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/LargeTile.scale-125.png
rename to apps/example/winuwp/runner_uwp/Assets/LargeTile.scale-125.png
diff --git a/example/winuwp/runner_uwp/Assets/LargeTile.scale-150.png b/apps/example/winuwp/runner_uwp/Assets/LargeTile.scale-150.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/LargeTile.scale-150.png
rename to apps/example/winuwp/runner_uwp/Assets/LargeTile.scale-150.png
diff --git a/example/winuwp/runner_uwp/Assets/LargeTile.scale-200.png b/apps/example/winuwp/runner_uwp/Assets/LargeTile.scale-200.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/LargeTile.scale-200.png
rename to apps/example/winuwp/runner_uwp/Assets/LargeTile.scale-200.png
diff --git a/example/winuwp/runner_uwp/Assets/LargeTile.scale-400.png b/apps/example/winuwp/runner_uwp/Assets/LargeTile.scale-400.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/LargeTile.scale-400.png
rename to apps/example/winuwp/runner_uwp/Assets/LargeTile.scale-400.png
diff --git a/example/winuwp/runner_uwp/Assets/LockScreenLogo.scale-200.png b/apps/example/winuwp/runner_uwp/Assets/LockScreenLogo.scale-200.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/LockScreenLogo.scale-200.png
rename to apps/example/winuwp/runner_uwp/Assets/LockScreenLogo.scale-200.png
diff --git a/example/winuwp/runner_uwp/Assets/SmallTile.scale-100.png b/apps/example/winuwp/runner_uwp/Assets/SmallTile.scale-100.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/SmallTile.scale-100.png
rename to apps/example/winuwp/runner_uwp/Assets/SmallTile.scale-100.png
diff --git a/example/winuwp/runner_uwp/Assets/SmallTile.scale-125.png b/apps/example/winuwp/runner_uwp/Assets/SmallTile.scale-125.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/SmallTile.scale-125.png
rename to apps/example/winuwp/runner_uwp/Assets/SmallTile.scale-125.png
diff --git a/example/winuwp/runner_uwp/Assets/SmallTile.scale-150.png b/apps/example/winuwp/runner_uwp/Assets/SmallTile.scale-150.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/SmallTile.scale-150.png
rename to apps/example/winuwp/runner_uwp/Assets/SmallTile.scale-150.png
diff --git a/example/winuwp/runner_uwp/Assets/SmallTile.scale-200.png b/apps/example/winuwp/runner_uwp/Assets/SmallTile.scale-200.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/SmallTile.scale-200.png
rename to apps/example/winuwp/runner_uwp/Assets/SmallTile.scale-200.png
diff --git a/example/winuwp/runner_uwp/Assets/SmallTile.scale-400.png b/apps/example/winuwp/runner_uwp/Assets/SmallTile.scale-400.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/SmallTile.scale-400.png
rename to apps/example/winuwp/runner_uwp/Assets/SmallTile.scale-400.png
diff --git a/example/winuwp/runner_uwp/Assets/SplashScreen.scale-100.png b/apps/example/winuwp/runner_uwp/Assets/SplashScreen.scale-100.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/SplashScreen.scale-100.png
rename to apps/example/winuwp/runner_uwp/Assets/SplashScreen.scale-100.png
diff --git a/example/winuwp/runner_uwp/Assets/SplashScreen.scale-125.png b/apps/example/winuwp/runner_uwp/Assets/SplashScreen.scale-125.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/SplashScreen.scale-125.png
rename to apps/example/winuwp/runner_uwp/Assets/SplashScreen.scale-125.png
diff --git a/example/winuwp/runner_uwp/Assets/SplashScreen.scale-150.png b/apps/example/winuwp/runner_uwp/Assets/SplashScreen.scale-150.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/SplashScreen.scale-150.png
rename to apps/example/winuwp/runner_uwp/Assets/SplashScreen.scale-150.png
diff --git a/example/winuwp/runner_uwp/Assets/SplashScreen.scale-200.png b/apps/example/winuwp/runner_uwp/Assets/SplashScreen.scale-200.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/SplashScreen.scale-200.png
rename to apps/example/winuwp/runner_uwp/Assets/SplashScreen.scale-200.png
diff --git a/example/winuwp/runner_uwp/Assets/SplashScreen.scale-400.png b/apps/example/winuwp/runner_uwp/Assets/SplashScreen.scale-400.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/SplashScreen.scale-400.png
rename to apps/example/winuwp/runner_uwp/Assets/SplashScreen.scale-400.png
diff --git a/example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-100.png b/apps/example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-100.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-100.png
rename to apps/example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-100.png
diff --git a/example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-125.png b/apps/example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-125.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-125.png
rename to apps/example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-125.png
diff --git a/example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-150.png b/apps/example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-150.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-150.png
rename to apps/example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-150.png
diff --git a/example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-200.png b/apps/example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-200.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-200.png
rename to apps/example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-200.png
diff --git a/example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-400.png b/apps/example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-400.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-400.png
rename to apps/example/winuwp/runner_uwp/Assets/Square150x150Logo.scale-400.png
diff --git a/example/winuwp/runner_uwp/Assets/Square44x44Logo.altform-unplated_targetsize-16.png b/apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.altform-unplated_targetsize-16.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square44x44Logo.altform-unplated_targetsize-16.png
rename to apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.altform-unplated_targetsize-16.png
diff --git a/example/winuwp/runner_uwp/Assets/Square44x44Logo.altform-unplated_targetsize-256.png b/apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.altform-unplated_targetsize-256.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square44x44Logo.altform-unplated_targetsize-256.png
rename to apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.altform-unplated_targetsize-256.png
diff --git a/example/winuwp/runner_uwp/Assets/Square44x44Logo.altform-unplated_targetsize-32.png b/apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.altform-unplated_targetsize-32.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square44x44Logo.altform-unplated_targetsize-32.png
rename to apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.altform-unplated_targetsize-32.png
diff --git a/example/winuwp/runner_uwp/Assets/Square44x44Logo.altform-unplated_targetsize-48.png b/apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.altform-unplated_targetsize-48.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square44x44Logo.altform-unplated_targetsize-48.png
rename to apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.altform-unplated_targetsize-48.png
diff --git a/example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-100.png b/apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-100.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-100.png
rename to apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-100.png
diff --git a/example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-125.png b/apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-125.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-125.png
rename to apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-125.png
diff --git a/example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-150.png b/apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-150.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-150.png
rename to apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-150.png
diff --git a/example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-200.png b/apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-200.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-200.png
rename to apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-200.png
diff --git a/example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-400.png b/apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-400.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-400.png
rename to apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.scale-400.png
diff --git a/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-16.png b/apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-16.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-16.png
rename to apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-16.png
diff --git a/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-24.png b/apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-24.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-24.png
rename to apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-24.png
diff --git a/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
rename to apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
diff --git a/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-256.png b/apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-256.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-256.png
rename to apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-256.png
diff --git a/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-32.png b/apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-32.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-32.png
rename to apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-32.png
diff --git a/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-48.png b/apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-48.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-48.png
rename to apps/example/winuwp/runner_uwp/Assets/Square44x44Logo.targetsize-48.png
diff --git a/example/winuwp/runner_uwp/Assets/StoreLogo.png b/apps/example/winuwp/runner_uwp/Assets/StoreLogo.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/StoreLogo.png
rename to apps/example/winuwp/runner_uwp/Assets/StoreLogo.png
diff --git a/example/winuwp/runner_uwp/Assets/StoreLogo.scale-100.png b/apps/example/winuwp/runner_uwp/Assets/StoreLogo.scale-100.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/StoreLogo.scale-100.png
rename to apps/example/winuwp/runner_uwp/Assets/StoreLogo.scale-100.png
diff --git a/example/winuwp/runner_uwp/Assets/StoreLogo.scale-125.png b/apps/example/winuwp/runner_uwp/Assets/StoreLogo.scale-125.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/StoreLogo.scale-125.png
rename to apps/example/winuwp/runner_uwp/Assets/StoreLogo.scale-125.png
diff --git a/example/winuwp/runner_uwp/Assets/StoreLogo.scale-150.png b/apps/example/winuwp/runner_uwp/Assets/StoreLogo.scale-150.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/StoreLogo.scale-150.png
rename to apps/example/winuwp/runner_uwp/Assets/StoreLogo.scale-150.png
diff --git a/example/winuwp/runner_uwp/Assets/StoreLogo.scale-200.png b/apps/example/winuwp/runner_uwp/Assets/StoreLogo.scale-200.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/StoreLogo.scale-200.png
rename to apps/example/winuwp/runner_uwp/Assets/StoreLogo.scale-200.png
diff --git a/example/winuwp/runner_uwp/Assets/StoreLogo.scale-400.png b/apps/example/winuwp/runner_uwp/Assets/StoreLogo.scale-400.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/StoreLogo.scale-400.png
rename to apps/example/winuwp/runner_uwp/Assets/StoreLogo.scale-400.png
diff --git a/example/winuwp/runner_uwp/Assets/Wide310x150Logo.scale-200.png b/apps/example/winuwp/runner_uwp/Assets/Wide310x150Logo.scale-200.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/Wide310x150Logo.scale-200.png
rename to apps/example/winuwp/runner_uwp/Assets/Wide310x150Logo.scale-200.png
diff --git a/example/winuwp/runner_uwp/Assets/WideTile.scale-100.png b/apps/example/winuwp/runner_uwp/Assets/WideTile.scale-100.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/WideTile.scale-100.png
rename to apps/example/winuwp/runner_uwp/Assets/WideTile.scale-100.png
diff --git a/example/winuwp/runner_uwp/Assets/WideTile.scale-125.png b/apps/example/winuwp/runner_uwp/Assets/WideTile.scale-125.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/WideTile.scale-125.png
rename to apps/example/winuwp/runner_uwp/Assets/WideTile.scale-125.png
diff --git a/example/winuwp/runner_uwp/Assets/WideTile.scale-150.png b/apps/example/winuwp/runner_uwp/Assets/WideTile.scale-150.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/WideTile.scale-150.png
rename to apps/example/winuwp/runner_uwp/Assets/WideTile.scale-150.png
diff --git a/example/winuwp/runner_uwp/Assets/WideTile.scale-200.png b/apps/example/winuwp/runner_uwp/Assets/WideTile.scale-200.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/WideTile.scale-200.png
rename to apps/example/winuwp/runner_uwp/Assets/WideTile.scale-200.png
diff --git a/example/winuwp/runner_uwp/Assets/WideTile.scale-400.png b/apps/example/winuwp/runner_uwp/Assets/WideTile.scale-400.png
similarity index 100%
rename from example/winuwp/runner_uwp/Assets/WideTile.scale-400.png
rename to apps/example/winuwp/runner_uwp/Assets/WideTile.scale-400.png
diff --git a/example/winuwp/runner_uwp/CMakeLists.txt b/apps/example/winuwp/runner_uwp/CMakeLists.txt
similarity index 100%
rename from example/winuwp/runner_uwp/CMakeLists.txt
rename to apps/example/winuwp/runner_uwp/CMakeLists.txt
diff --git a/example/winuwp/runner_uwp/CMakeSettings.json b/apps/example/winuwp/runner_uwp/CMakeSettings.json
similarity index 100%
rename from example/winuwp/runner_uwp/CMakeSettings.json
rename to apps/example/winuwp/runner_uwp/CMakeSettings.json
diff --git a/example/winuwp/runner_uwp/Windows_TemporaryKey.pfx b/apps/example/winuwp/runner_uwp/Windows_TemporaryKey.pfx
similarity index 100%
rename from example/winuwp/runner_uwp/Windows_TemporaryKey.pfx
rename to apps/example/winuwp/runner_uwp/Windows_TemporaryKey.pfx
diff --git a/example/winuwp/runner_uwp/appxmanifest.in b/apps/example/winuwp/runner_uwp/appxmanifest.in
similarity index 100%
rename from example/winuwp/runner_uwp/appxmanifest.in
rename to apps/example/winuwp/runner_uwp/appxmanifest.in
diff --git a/example/winuwp/runner_uwp/flutter_frameworkview.cpp b/apps/example/winuwp/runner_uwp/flutter_frameworkview.cpp
similarity index 100%
rename from example/winuwp/runner_uwp/flutter_frameworkview.cpp
rename to apps/example/winuwp/runner_uwp/flutter_frameworkview.cpp
diff --git a/example/winuwp/runner_uwp/main.cpp b/apps/example/winuwp/runner_uwp/main.cpp
similarity index 100%
rename from example/winuwp/runner_uwp/main.cpp
rename to apps/example/winuwp/runner_uwp/main.cpp
diff --git a/example/winuwp/runner_uwp/resources.pri b/apps/example/winuwp/runner_uwp/resources.pri
similarity index 100%
rename from example/winuwp/runner_uwp/resources.pri
rename to apps/example/winuwp/runner_uwp/resources.pri
diff --git a/build.yaml b/build.yaml
new file mode 100644
index 00000000..95e10778
--- /dev/null
+++ b/build.yaml
@@ -0,0 +1,2 @@
+additional_public_assets:
+ - pigeons/**
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
deleted file mode 100644
index 4586c053..00000000
--- a/example/android/app/build.gradle
+++ /dev/null
@@ -1,49 +0,0 @@
-plugins {
- id "com.android.application"
- id "kotlin-android"
- id "dev.flutter.flutter-gradle-plugin"
-}
-
-def localProperties = new Properties()
-def localPropertiesFile = rootProject.file('local.properties')
-if (localPropertiesFile.exists()) {
- localPropertiesFile.withReader('UTF-8') { reader ->
- localProperties.load(reader)
- }
-}
-
-android {
- compileSdk 34
- namespace 'com.tundralabs.example'
-
-
- defaultConfig {
- applicationId "com.tundralabs.flutterttsexample"
- minSdkVersion 21
- targetSdkVersion 34
- versionCode 1
- versionName "1.0"
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
-
- buildTypes {
- release {
- // TODO: Add your own signing config for the release build.
- // Signing with the debug keys for now, so `flutter run --release` works.
- signingConfig signingConfigs.debug
- }
- }
- lint {
- disable 'InvalidPackage'
- }
-}
-
-flutter {
- source '../..'
-}
-
-dependencies {
- testImplementation 'junit:junit:4.13.2'
- androidTestImplementation 'androidx.test:runner:1.5.2'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
-}
diff --git a/example/android/build.gradle b/example/android/build.gradle
deleted file mode 100644
index bc157bd1..00000000
--- a/example/android/build.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-allprojects {
- repositories {
- google()
- mavenCentral()
- }
-}
-
-rootProject.buildDir = '../build'
-subprojects {
- project.buildDir = "${rootProject.buildDir}/${project.name}"
-}
-subprojects {
- project.evaluationDependsOn(':app')
-}
-
-tasks.register("clean", Delete) {
- delete rootProject.buildDir
-}
diff --git a/example/android/gradle.properties b/example/android/gradle.properties
deleted file mode 100644
index 022f0d32..00000000
--- a/example/android/gradle.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-android.defaults.buildfeatures.buildconfig=true
-android.enableJetifier=true
-android.nonFinalResIds=false
-android.nonTransitiveRClass=false
-android.useAndroidX=true
-org.gradle.jvmargs=-Xmx1536M
diff --git a/example/android/settings.gradle b/example/android/settings.gradle
deleted file mode 100644
index 60906601..00000000
--- a/example/android/settings.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-pluginManagement {
- def flutterSdkPath = {
- def properties = new Properties()
- file("local.properties").withInputStream { properties.load(it) }
- def flutterSdkPath = properties.getProperty("flutter.sdk")
- assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
- return flutterSdkPath
- }()
-
- includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
-
- repositories {
- google()
- mavenCentral()
- gradlePluginPortal()
- }
-}
-
-plugins {
- id "dev.flutter.flutter-plugin-loader" version "1.0.0"
- id "com.android.application" version "8.2.0" apply false
- id "org.jetbrains.kotlin.android" version "1.9.10" apply false
-}
-
-include ":app"
\ No newline at end of file
diff --git a/example/android/settings_aar.gradle b/example/android/settings_aar.gradle
deleted file mode 100644
index e7b4def4..00000000
--- a/example/android/settings_aar.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include ':app'
diff --git a/example/ios/.gitignore b/example/ios/.gitignore
deleted file mode 100644
index 1e1aafd6..00000000
--- a/example/ios/.gitignore
+++ /dev/null
@@ -1,42 +0,0 @@
-.idea/
-.vagrant/
-.sconsign.dblite
-.svn/
-
-.DS_Store
-*.swp
-profile
-
-DerivedData/
-build/
-GeneratedPluginRegistrant.h
-GeneratedPluginRegistrant.m
-
-*.pbxuser
-*.mode1v3
-*.mode2v3
-*.perspectivev3
-
-!default.pbxuser
-!default.mode1v3
-!default.mode2v3
-!default.perspectivev3
-
-xcuserdata
-
-*.moved-aside
-
-*.pyc
-*sync/
-Icon?
-.tags*
-
-/Flutter/app.flx
-/Flutter/app.zip
-/Flutter/flutter_assets/
-/Flutter/App.framework
-/Flutter/Flutter.framework
-/Flutter/Generated.xcconfig
-/ServiceDefinitions.json
-
-Pods/
diff --git a/example/ios/Flutter/.last_build_id b/example/ios/Flutter/.last_build_id
deleted file mode 100644
index bfaaeba1..00000000
--- a/example/ios/Flutter/.last_build_id
+++ /dev/null
@@ -1 +0,0 @@
-b443c2d1b6b1e4df6c6fcc277a981410
\ No newline at end of file
diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig
deleted file mode 100644
index e8efba11..00000000
--- a/example/ios/Flutter/Debug.xcconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
-#include "Generated.xcconfig"
diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig
deleted file mode 100644
index 399e9340..00000000
--- a/example/ios/Flutter/Release.xcconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
-#include "Generated.xcconfig"
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
deleted file mode 100644
index 3d43d11e..00000000
Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
deleted file mode 100644
index 28c6bf03..00000000
Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
deleted file mode 100644
index 2ccbfd96..00000000
Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
deleted file mode 100644
index f091b6b0..00000000
Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
deleted file mode 100644
index 4cde1211..00000000
Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
deleted file mode 100644
index d0ef06e7..00000000
Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
deleted file mode 100644
index dcdc2306..00000000
Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
deleted file mode 100644
index 2ccbfd96..00000000
Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
deleted file mode 100644
index c8f9ed8f..00000000
Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
deleted file mode 100644
index a6d6b860..00000000
Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
deleted file mode 100644
index a6d6b860..00000000
Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
deleted file mode 100644
index 75b2d164..00000000
Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
deleted file mode 100644
index c4df70d3..00000000
Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
deleted file mode 100644
index 6a84f41e..00000000
Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
deleted file mode 100644
index d0e1f585..00000000
Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ
diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h
deleted file mode 100644
index 7335fdf9..00000000
--- a/example/ios/Runner/Runner-Bridging-Header.h
+++ /dev/null
@@ -1 +0,0 @@
-#import "GeneratedPluginRegistrant.h"
\ No newline at end of file
diff --git a/example/lib/main.dart b/example/lib/main.dart
deleted file mode 100644
index ae748393..00000000
--- a/example/lib/main.dart
+++ /dev/null
@@ -1,382 +0,0 @@
-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 {
- late FlutterTts flutterTts;
- String? language;
- String? engine;
- double volume = 0.5;
- double pitch = 1.0;
- double rate = 0.5;
- bool isCurrentLanguageInstalled = false;
-
- String? _newVoiceText;
- int? _inputLength;
-
- TtsState ttsState = TtsState.stopped;
-
- bool get isPlaying => ttsState == TtsState.playing;
- bool get isStopped => ttsState == TtsState.stopped;
- bool get isPaused => ttsState == TtsState.paused;
- bool get isContinued => ttsState == TtsState.continued;
-
- bool get isIOS => !kIsWeb && Platform.isIOS;
- bool get isAndroid => !kIsWeb && Platform.isAndroid;
- bool get isWindows => !kIsWeb && Platform.isWindows;
- bool get isWeb => kIsWeb;
-
- @override
- initState() {
- super.initState();
- initTts();
- }
-
- dynamic initTts() {
- flutterTts = FlutterTts();
-
- _setAwaitOptions();
-
- if (isAndroid) {
- _getDefaultEngine();
- _getDefaultVoice();
- }
-
- flutterTts.setStartHandler(() {
- setState(() {
- print("Playing");
- ttsState = TtsState.playing;
- });
- });
-
- flutterTts.setCompletionHandler(() {
- setState(() {
- print("Complete");
- ttsState = TtsState.stopped;
- });
- });
-
- flutterTts.setCancelHandler(() {
- setState(() {
- print("Cancel");
- ttsState = TtsState.stopped;
- });
- });
-
- 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;
- });
- });
- }
-
- Future _getLanguages() async => await flutterTts.getLanguages;
-
- Future _getEngines() async => await flutterTts.getEngines;
-
- Future _getDefaultEngine() async {
- var engine = await flutterTts.getDefaultEngine;
- if (engine != null) {
- print(engine);
- }
- }
-
- Future _getDefaultVoice() async {
- var voice = await flutterTts.getDefaultVoice;
- if (voice != null) {
- print(voice);
- }
- }
-
- Future _speak() async {
- await flutterTts.setVolume(volume);
- await flutterTts.setSpeechRate(rate);
- await flutterTts.setPitch(pitch);
-
- if (_newVoiceText != null) {
- if (_newVoiceText!.isNotEmpty) {
- await flutterTts.speak(_newVoiceText!);
- }
- }
- }
-
- Future _setAwaitOptions() async {
- await flutterTts.awaitSpeakCompletion(true);
- }
-
- 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> getEnginesDropDownMenuItems(
- List engines) {
- var items = >[];
- for (dynamic type in engines) {
- items.add(DropdownMenuItem(
- value: type as String?, child: Text((type as String))));
- }
- return items;
- }
-
- void changedEnginesDropDownItem(String? selectedEngine) async {
- await flutterTts.setEngine(selectedEngine!);
- language = null;
- setState(() {
- engine = selectedEngine;
- });
- }
-
- List> getLanguageDropDownMenuItems(
- List languages) {
- var items = >[];
- for (dynamic type in languages) {
- items.add(DropdownMenuItem(
- value: type as String?, child: Text((type as String))));
- }
- 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(),
- _btnSection(),
- _engineSection(),
- _futureBuilder(),
- _buildSliders(),
- if (isAndroid) _getMaxSpeechInputLengthSection(),
- ],
- ),
- ),
- ),
- );
- }
-
- Widget _engineSection() {
- if (isAndroid) {
- return FutureBuilder(
- future: _getEngines(),
- builder: (BuildContext context, AsyncSnapshot snapshot) {
- if (snapshot.hasData) {
- return _enginesDropDownSection(snapshot.data as List);
- } else if (snapshot.hasError) {
- return Text('Error loading engines...');
- } else {
- return Text('Loading engines...');
- }
- });
- } else {
- return Container(width: 0, height: 0);
- }
- }
-
- Widget _futureBuilder() => FutureBuilder(
- future: _getLanguages(),
- builder: (BuildContext context, AsyncSnapshot snapshot) {
- if (snapshot.hasData) {
- return _languageDropDownSection(snapshot.data as List);
- } else if (snapshot.hasError) {
- return Text('Error loading languages...');
- } else
- return Text('Loading Languages...');
- });
-
- Widget _inputSection() => Container(
- alignment: Alignment.topCenter,
- padding: EdgeInsets.only(top: 25.0, left: 25.0, right: 25.0),
- child: TextField(
- maxLines: 11,
- minLines: 6,
- onChanged: (String value) {
- _onChange(value);
- },
- ));
-
- Widget _btnSection() {
- 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 _enginesDropDownSection(List engines) => Container(
- padding: EdgeInsets.only(top: 50.0),
- child: DropdownButton(
- value: engine,
- items: getEnginesDropDownMenuItems(engines),
- onChanged: changedEnginesDropDownItem,
- ),
- );
-
- Widget _languageDropDownSection(List languages) => Container(
- padding: EdgeInsets.only(top: 10.0),
- child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
- DropdownButton(
- value: language,
- items: getLanguageDropDownMenuItems(languages),
- 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 _getMaxSpeechInputLengthSection() {
- return Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: [
- ElevatedButton(
- child: Text('Get max speech input length'),
- onPressed: () async {
- _inputLength = await flutterTts.getMaxSpeechInputLength;
- setState(() {});
- },
- ),
- Text("$_inputLength characters"),
- ],
- );
- }
-
- 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.toStringAsFixed(1)}");
- }
-
- Widget _pitch() {
- return Slider(
- value: pitch,
- onChanged: (newPitch) {
- setState(() => pitch = newPitch);
- },
- min: 0.5,
- max: 2.0,
- divisions: 15,
- label: "Pitch: ${pitch.toStringAsFixed(1)}",
- 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.toStringAsFixed(1)}",
- activeColor: Colors.green,
- );
- }
-}
diff --git a/example/macos/Flutter/Flutter-Debug.xcconfig b/example/macos/Flutter/Flutter-Debug.xcconfig
deleted file mode 100644
index 785633d3..00000000
--- a/example/macos/Flutter/Flutter-Debug.xcconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
-#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/example/macos/Flutter/Flutter-Release.xcconfig b/example/macos/Flutter/Flutter-Release.xcconfig
deleted file mode 100644
index 5fba960c..00000000
--- a/example/macos/Flutter/Flutter-Release.xcconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
-#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
deleted file mode 100644
index 3c4935a7..00000000
Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and /dev/null differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
deleted file mode 100644
index ed4cc164..00000000
Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and /dev/null differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
deleted file mode 100644
index 483be613..00000000
Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and /dev/null differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
deleted file mode 100644
index bcbf36df..00000000
Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and /dev/null differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
deleted file mode 100644
index 9c0a6528..00000000
Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and /dev/null differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
deleted file mode 100644
index e71a7261..00000000
Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and /dev/null differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
deleted file mode 100644
index 8a31fe2d..00000000
Binary files a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and /dev/null differ
diff --git a/ios/Classes/AudioCategory.swift b/ios/Classes/AudioCategory.swift
deleted file mode 100644
index 74e8ea0b..00000000
--- a/ios/Classes/AudioCategory.swift
+++ /dev/null
@@ -1,21 +0,0 @@
-import AVFoundation
-
-enum AudioCategory: String {
- case iosAudioCategoryAmbientSolo
- case iosAudioCategoryAmbient
- case iosAudioCategoryPlayback
- case iosAudioCategoryPlaybackAndRecord
-
- func toAVAudioSessionCategory() -> AVAudioSession.Category {
- switch self {
- case .iosAudioCategoryAmbientSolo:
- return .soloAmbient
- case .iosAudioCategoryAmbient:
- return .ambient
- case .iosAudioCategoryPlayback:
- return .playback
- case .iosAudioCategoryPlaybackAndRecord:
- return .playAndRecord
- }
- }
-}
diff --git a/ios/Classes/AudioCategoryOptions.swift b/ios/Classes/AudioCategoryOptions.swift
deleted file mode 100644
index f593bfdb..00000000
--- a/ios/Classes/AudioCategoryOptions.swift
+++ /dev/null
@@ -1,42 +0,0 @@
-import AVFoundation
-
-enum AudioCategoryOptions: String {
- case iosAudioCategoryOptionsMixWithOthers
- case iosAudioCategoryOptionsDuckOthers
- case iosAudioCategoryOptionsInterruptSpokenAudioAndMixWithOthers
- case iosAudioCategoryOptionsAllowBluetooth
- case iosAudioCategoryOptionsAllowBluetoothA2DP
- case iosAudioCategoryOptionsAllowAirPlay
- case iosAudioCategoryOptionsDefaultToSpeaker
-
- func toAVAudioSessionCategoryOptions() -> AVAudioSession.CategoryOptions? {
- switch self {
- case .iosAudioCategoryOptionsMixWithOthers:
- return .mixWithOthers
- case .iosAudioCategoryOptionsDuckOthers:
- return .duckOthers
- case .iosAudioCategoryOptionsInterruptSpokenAudioAndMixWithOthers:
- if #available(iOS 9.0, *) {
- return .interruptSpokenAudioAndMixWithOthers
- } else {
- return nil
- }
- case .iosAudioCategoryOptionsAllowBluetooth:
- return .allowBluetooth
- case .iosAudioCategoryOptionsAllowBluetoothA2DP:
- if #available(iOS 10.0, *) {
- return .allowBluetoothA2DP
- } else {
- return nil
- }
- case .iosAudioCategoryOptionsAllowAirPlay:
- if #available(iOS 10.0, *) {
- return .allowAirPlay
- } else {
- return nil
- }
- case .iosAudioCategoryOptionsDefaultToSpeaker:
- return .defaultToSpeaker
- }
- }
-}
diff --git a/ios/Classes/AudioModes.swift b/ios/Classes/AudioModes.swift
deleted file mode 100644
index e2e30716..00000000
--- a/ios/Classes/AudioModes.swift
+++ /dev/null
@@ -1,63 +0,0 @@
-import AVFoundation
-
-enum AudioModes: String {
- case iosAudioModeDefault
- case iosAudioModeGameChat
- case iosAudioModeMeasurement
- case iosAudioModeMoviePlayback
- case iosAudioModeSpokenAudio
- case iosAudioModeVideoChat
- case iosAudioModeVideoRecording
- case iosAudioModeVoiceChat
- case iosAudioModeVoicePrompt
-
- func toAVAudioSessionMode() -> AVAudioSession.Mode? {
- switch self {
- case .iosAudioModeDefault:
- if #available(iOS 12.0, *) {
- return .default
- }
- return nil
- case .iosAudioModeGameChat:
- if #available(iOS 12.0, *) {
- return .gameChat
- }
- return nil
- case .iosAudioModeMeasurement:
- if #available(iOS 12.0, *) {
- return .measurement
- }
- return nil
- case .iosAudioModeMoviePlayback:
- if #available(iOS 12.0, *) {
- return .moviePlayback
- }
- return nil
- case .iosAudioModeSpokenAudio:
- if #available(iOS 12.0, *) {
- return .spokenAudio
- }
- return nil
- case .iosAudioModeVideoChat:
- if #available(iOS 12.0, *) {
- return .videoChat
- }
- return nil
- case .iosAudioModeVideoRecording:
- if #available(iOS 12.0, *) {
- return .videoRecording
- }
- return nil
- case .iosAudioModeVoiceChat:
- if #available(iOS 12.0, *) {
- return .voiceChat
- }
- return nil
- case .iosAudioModeVoicePrompt:
- if #available(iOS 12.0, *) {
- return .voicePrompt
- }
- return nil
- }
- }
-}
diff --git a/ios/Classes/FlutterTtsPlugin.h b/ios/Classes/FlutterTtsPlugin.h
deleted file mode 100644
index 13e3a21a..00000000
--- a/ios/Classes/FlutterTtsPlugin.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#import
-
-@interface FlutterTtsPlugin : NSObject
-@end
diff --git a/ios/Classes/FlutterTtsPlugin.m b/ios/Classes/FlutterTtsPlugin.m
deleted file mode 100644
index 3fc014ff..00000000
--- a/ios/Classes/FlutterTtsPlugin.m
+++ /dev/null
@@ -1,15 +0,0 @@
-#import "FlutterTtsPlugin.h"
-#if __has_include()
-#import
-#else
-// Support project import fallback if the generated compatibility header
-// is not copied when this plugin is created as a library.
-// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816
-#import "flutter_tts-Swift.h"
-#endif
-
-@implementation FlutterTtsPlugin
-+ (void)registerWithRegistrar:(NSObject*)registrar {
- [SwiftFlutterTtsPlugin registerWithRegistrar:registrar];
-}
-@end
diff --git a/ios/Classes/SwiftFlutterTtsPlugin.swift b/ios/Classes/SwiftFlutterTtsPlugin.swift
deleted file mode 100644
index 7b18a353..00000000
--- a/ios/Classes/SwiftFlutterTtsPlugin.swift
+++ /dev/null
@@ -1,506 +0,0 @@
-import Flutter
-import UIKit
-import AVFoundation
-
-public class SwiftFlutterTtsPlugin: NSObject, FlutterPlugin, AVSpeechSynthesizerDelegate {
- let iosAudioCategoryKey = "iosAudioCategoryKey"
- let iosAudioCategoryOptionsKey = "iosAudioCategoryOptionsKey"
- let iosAudioModeKey = "iosAudioModeKey"
-
- let synthesizer = AVSpeechSynthesizer()
- var rate: Float = AVSpeechUtteranceDefaultSpeechRate
- var volume: Float = 1.0
- var pitch: Float = 1.0
- var voice: AVSpeechSynthesisVoice?
- var awaitSpeakCompletion: Bool = false
- var awaitSynthCompletion: Bool = false
- var autoStopSharedSession: Bool = true
- var speakResult: FlutterResult? = nil
- var synthResult: FlutterResult? = nil
-
- lazy var audioSession = AVAudioSession.sharedInstance()
- lazy var language: String = {
- AVSpeechSynthesisVoice.currentLanguageCode()
- }()
- lazy var languages: Set = {
- Set(AVSpeechSynthesisVoice.speechVoices().map(\.language))
- }()
-
-
- var channel = FlutterMethodChannel()
- init(channel: FlutterMethodChannel) {
- super.init()
- self.channel = channel
- synthesizer.delegate = self
- }
-
- public static func register(with registrar: FlutterPluginRegistrar) {
- let channel = FlutterMethodChannel(name: "flutter_tts", binaryMessenger: registrar.messenger())
- let instance = SwiftFlutterTtsPlugin(channel: channel)
- registrar.addMethodCallDelegate(instance, channel: channel)
- }
-
- public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
- switch call.method {
- case "speak":
- let text: String = call.arguments as! String
- self.speak(text: text, result: result)
- break
- case "awaitSpeakCompletion":
- self.awaitSpeakCompletion = call.arguments as! Bool
- result(1)
- break
- case "awaitSynthCompletion":
- self.awaitSynthCompletion = call.arguments as! Bool
- result(1)
- break
- case "synthesizeToFile":
- guard let args = call.arguments as? [String: Any] else {
- result("iOS could not recognize flutter arguments in method: (sendParams)")
- return
- }
- let text = args["text"] as! String
- let fileName = args["fileName"] as! String
- let isFullPath = args["isFullPath"] as! Bool
- self.synthesizeToFile(text: text, fileName: fileName, isFullPath: isFullPath, result: result)
- break
- case "pause":
- self.pause(result: result)
- break
- case "setLanguage":
- let language: String = call.arguments as! String
- self.setLanguage(language: language, result: result)
- break
- case "setSpeechRate":
- let rate: Double = call.arguments as! Double
- self.setRate(rate: Float(rate))
- result(1)
- break
- case "setVolume":
- let volume: Double = call.arguments as! Double
- self.setVolume(volume: Float(volume), result: result)
- break
- case "setPitch":
- let pitch: Double = call.arguments as! Double
- self.setPitch(pitch: Float(pitch), result: result)
- break
- case "stop":
- self.stop()
- result(1)
- break
- case "getLanguages":
- self.getLanguages(result: result)
- break
- case "getSpeechRateValidRange":
- self.getSpeechRateValidRange(result: result)
- break
- case "isLanguageAvailable":
- let language: String = call.arguments as! String
- self.isLanguageAvailable(language: language, result: result)
- break
- case "getVoices":
- self.getVoices(result: result)
- break
- case "setVoice":
- guard let args = call.arguments as? [String: String] else {
- result("iOS could not recognize flutter arguments in method: (sendParams)")
- return
- }
- self.setVoice(voice: args, result: result)
- break
- case "clearVoice":
- self.clearVoice()
- result(1)
- break
- case "setSharedInstance":
- let sharedInstance = call.arguments as! Bool
- self.setSharedInstance(sharedInstance: sharedInstance, result: result)
- break
- case "autoStopSharedSession":
- let autoStop = call.arguments as! Bool
- self.autoStopSharedSession = autoStop
- result(1)
- break
- case "setIosAudioCategory":
- guard let args = call.arguments as? [String: Any] else {
- result("iOS could not recognize flutter arguments in method: (sendParams)")
- return
- }
- let audioCategory = args["iosAudioCategoryKey"] as? String
- let audioOptions = args[iosAudioCategoryOptionsKey] as? Array
- let audioModes = args[iosAudioModeKey] as? String
- self.setAudioCategory(audioCategory: audioCategory, audioOptions: audioOptions, audioMode: audioModes, result: result)
- break
- default:
- result(FlutterMethodNotImplemented)
- }
- }
-
- private func speak(text: String, result: @escaping FlutterResult) {
- if (self.synthesizer.isPaused) {
- if (self.synthesizer.continueSpeaking()) {
- if self.awaitSpeakCompletion {
- self.speakResult = result
- } else {
- result(1)
- }
- } else {
- result(0)
- }
- } else {
- let utterance = AVSpeechUtterance(string: text)
- if self.voice != nil {
- utterance.voice = self.voice!
- } else {
- utterance.voice = AVSpeechSynthesisVoice(language: self.language)
- }
- utterance.rate = self.rate
- utterance.volume = self.volume
- utterance.pitchMultiplier = self.pitch
-
- self.synthesizer.speak(utterance)
- if self.awaitSpeakCompletion {
- self.speakResult = result
- } else {
- result(1)
- }
- }
- }
-
- private func synthesizeToFile(text: String, fileName: String, isFullPath: Bool, result: @escaping FlutterResult) {
- var output: AVAudioFile?
- var failed = false
- let utterance = AVSpeechUtterance(string: text)
-
- if self.voice != nil {
- utterance.voice = self.voice!
- } else {
- utterance.voice = AVSpeechSynthesisVoice(language: self.language)
- }
- utterance.rate = self.rate
- utterance.volume = self.volume
- utterance.pitchMultiplier = self.pitch
-
- if #available(iOS 13.0, *) {
- self.synthesizer.write(utterance) { (buffer: AVAudioBuffer) in
- guard let pcmBuffer = buffer as? AVAudioPCMBuffer else {
- NSLog("unknow buffer type: \(buffer)")
- failed = true
- return
- }
- print(pcmBuffer.format)
- if pcmBuffer.frameLength == 0 {
- // finished
- } else {
- // append buffer to file
- let fileURL: URL
- if isFullPath {
- fileURL = URL(fileURLWithPath: fileName)
- } else {
- fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent(fileName)
- }
- NSLog("Saving utterance to file: \(fileURL.absoluteString)")
-
- if output == nil {
- do {
- if #available(iOS 17.0, *) {
- guard let audioFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: pcmBuffer.format.sampleRate, channels: 1, interleaved: false) else {
- NSLog("Error creating audio format for iOS 17+")
- failed = true
- return
- }
- output = try AVAudioFile(forWriting: fileURL, settings: audioFormat.settings)
- } else {
- output = try AVAudioFile(forWriting: fileURL, settings: pcmBuffer.format.settings, commonFormat: .pcmFormatFloat32, interleaved: false)
- }
- } catch {
- NSLog("Error creating AVAudioFile: \(error.localizedDescription)")
- failed = true
- return
- }
- }
-
-
- try! output!.write(from: pcmBuffer)
- }
- }
- } else {
- result("Unsupported iOS version")
- }
- if failed {
- result(0)
- }
- if self.awaitSynthCompletion {
- self.synthResult = result
- } else {
- result(1)
- }
- }
-
- private func pause(result: FlutterResult) {
- if (self.synthesizer.pauseSpeaking(at: AVSpeechBoundary.word)) {
- result(1)
- } else {
- result(0)
- }
- }
-
- private func setLanguage(language: String, result: FlutterResult) {
- if !(self.languages.contains(where: {$0.range(of: language, options: [.caseInsensitive, .anchored]) != nil})) {
- result(0)
- } else {
- self.language = language
- self.voice = nil
- result(1)
- }
- }
-
- private func setRate(rate: Float) {
- self.rate = rate
- }
-
- private func setVolume(volume: Float, result: FlutterResult) {
- if (volume >= 0.0 && volume <= 1.0) {
- self.volume = volume
- result(1)
- } else {
- result(0)
- }
- }
-
- private func setPitch(pitch: Float, result: FlutterResult) {
- if (volume >= 0.5 && volume <= 2.0) {
- self.pitch = pitch
- result(1)
- } else {
- result(0)
- }
- }
-
- private func setSharedInstance(sharedInstance: Bool, result: FlutterResult) {
- do {
- try AVAudioSession.sharedInstance().setActive(sharedInstance)
- result(1)
- } catch {
- result(0)
- }
- }
-
- private func setAudioCategory(audioCategory: String?, audioOptions: Array?, audioMode: String?, result: FlutterResult){
- let category: AVAudioSession.Category = AudioCategory(rawValue: audioCategory ?? "")?.toAVAudioSessionCategory() ?? audioSession.category
- let options: AVAudioSession.CategoryOptions = audioOptions?.reduce([], { (result, option) -> AVAudioSession.CategoryOptions in
- return result.union(AudioCategoryOptions(rawValue: option)?.toAVAudioSessionCategoryOptions() ?? [])}) ?? []
- do {
- if #available(iOS 12.0, *) {
- if audioMode == nil {
- try audioSession.setCategory(category, options: options)
- } else {
- let mode: AVAudioSession.Mode? = AudioModes(rawValue: audioMode ?? "")?.toAVAudioSessionMode() ?? AVAudioSession.Mode.default
- try audioSession.setCategory(category, mode: mode!, options: options)
- }
- } else {
- try audioSession.setCategory(category, options: options)
- }
- result(1)
- } catch {
- print(error)
- result(0)
- }
- }
-
- private func stop() {
- self.synthesizer.stopSpeaking(at: AVSpeechBoundary.immediate)
- }
-
- private func getLanguages(result: FlutterResult) {
- result(Array(self.languages))
- }
-
- private func getSpeechRateValidRange(result: FlutterResult) {
- let validSpeechRateRange: [String:String] = [
- "min": String(AVSpeechUtteranceMinimumSpeechRate),
- "normal": String(AVSpeechUtteranceDefaultSpeechRate),
- "max": String(AVSpeechUtteranceMaximumSpeechRate),
- "platform": "ios"
- ]
- result(validSpeechRateRange)
- }
-
- private func isLanguageAvailable(language: String, result: FlutterResult) {
- var isAvailable: Bool = false
- if (self.languages.contains(where: {$0.range(of: language, options: [.caseInsensitive, .anchored]) != nil})) {
- isAvailable = true
- }
- result(isAvailable);
- }
-
- private func getVoices(result: FlutterResult) {
- if #available(iOS 9.0, *) {
- let voices = NSMutableArray()
- var voiceDict: [String: String] = [:]
- for voice in AVSpeechSynthesisVoice.speechVoices() {
- voiceDict["name"] = voice.name
- voiceDict["locale"] = voice.language
- voiceDict["quality"] = voice.quality.stringValue
- if #available(iOS 13.0, *) {
- voiceDict["gender"] = voice.gender.stringValue
- }
- voiceDict["identifier"] = voice.identifier
- voices.add(voiceDict)
- }
- result(voices)
- } else {
- // Since voice selection is not supported below iOS 9, make voice getter and setter
- // have the same bahavior as language selection.
- getLanguages(result: result)
- }
- }
-
- private func setVoice(voice: [String: String], result: FlutterResult) {
- if #available(iOS 9.0, *) {
- // Check if identifier exists and is not empty
- if let identifier = voice["identifier"], !identifier.isEmpty {
- // Find the voice by identifier
- if let selectedVoice = AVSpeechSynthesisVoice(identifier: identifier) {
- self.voice = selectedVoice
- self.language = selectedVoice.language
- result(1)
- return
- }
- }
-
- // If no valid identifier, search by name and locale, then prioritize by quality
- if let name = voice["name"], let locale = voice["locale"] {
- let matchingVoices = AVSpeechSynthesisVoice.speechVoices().filter { $0.name == name && $0.language == locale }
-
- if !matchingVoices.isEmpty {
- // Sort voices by quality: premium (if available) > enhanced > others
- let sortedVoices = matchingVoices.sorted { (voice1, voice2) -> Bool in
- let quality1 = voice1.quality
- let quality2 = voice2.quality
-
- // macOS 13.0+ supports premium quality
- if #available(iOS 16.0, *) {
- if quality1 == .premium {
- return true
- } else if quality1 == .enhanced && quality2 != .premium {
- return true
- } else {
- return false
- }
- } else {
- // Fallback for macOS versions before 13.0 (no premium)
- if quality1 == .enhanced {
- return true
- } else {
- return false
- }
- }
- }
-
- // Select the highest quality voice
- if let selectedVoice = sortedVoices.first {
- self.voice = selectedVoice
- self.language = selectedVoice.language
- result(1)
- return
- }
- }
- }
-
- // No matching voice found
- result(0)
- } else {
- // Handle older iOS versions if needed
- setLanguage(language: voice["name"]!, result: result)
- }
- }
-
- private func clearVoice() {
- self.voice = nil
- }
-
- private func shouldDeactivateAndNotifyOthers(_ session: AVAudioSession) -> Bool {
- var options: AVAudioSession.CategoryOptions = .duckOthers
- if #available(iOS 9.0, *) {
- options.insert(.interruptSpokenAudioAndMixWithOthers)
- }
- options.remove(.mixWithOthers)
-
- return !options.isDisjoint(with: session.categoryOptions)
- }
-
- public func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
- if shouldDeactivateAndNotifyOthers(audioSession) && self.autoStopSharedSession {
- do {
- try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
- } catch {
- print(error)
- }
- }
- if self.awaitSpeakCompletion && self.speakResult != nil {
- self.speakResult!(1)
- self.speakResult = nil
- }
- if self.awaitSynthCompletion && self.synthResult != nil {
- self.synthResult!(1)
- self.synthResult = nil
- }
- self.channel.invokeMethod("speak.onComplete", arguments: nil)
- }
-
- public func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didStart utterance: AVSpeechUtterance) {
- self.channel.invokeMethod("speak.onStart", arguments: nil)
- }
-
- public func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didPause utterance: AVSpeechUtterance) {
- self.channel.invokeMethod("speak.onPause", arguments: nil)
- }
-
- public func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didContinue utterance: AVSpeechUtterance) {
- self.channel.invokeMethod("speak.onContinue", arguments: nil)
- }
-
- public func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didCancel utterance: AVSpeechUtterance) {
- self.channel.invokeMethod("speak.onCancel", arguments: nil)
- }
-
- public func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance) {
- let nsWord = utterance.speechString as NSString
- let data: [String:String] = [
- "text": utterance.speechString,
- "start": String(characterRange.location),
- "end": String(characterRange.location + characterRange.length),
- "word": nsWord.substring(with: characterRange)
- ]
- self.channel.invokeMethod("speak.onProgress", arguments: data)
- }
-
-}
-
-extension AVSpeechSynthesisVoiceQuality {
- var stringValue: String {
- switch self {
- case .default:
- return "default"
- case .premium:
- return "premium"
- case .enhanced:
- return "enhanced"
- }
- }
-}
-
-@available(iOS 13.0, *)
-extension AVSpeechSynthesisVoiceGender {
- var stringValue: String {
- switch self {
- case .male:
- return "male"
- case .female:
- return "female"
- case .unspecified:
- return "unspecified"
- }
- }
-}
diff --git a/lib/flutter_tts.dart b/lib/flutter_tts.dart
deleted file mode 100644
index 0d2d2f2a..00000000
--- a/lib/flutter_tts.dart
+++ /dev/null
@@ -1,666 +0,0 @@
-import 'dart:async';
-import 'dart:io' show Platform;
-
-import 'package:flutter/services.dart';
-import 'package:flutter/foundation.dart' show kIsWeb;
-
-typedef ErrorHandler = void Function(dynamic message);
-typedef ProgressHandler = void Function(
- String text, int start, int end, String word);
-
-const String iosAudioCategoryOptionsKey = 'iosAudioCategoryOptionsKey';
-const String iosAudioCategoryKey = 'iosAudioCategoryKey';
-const String iosAudioModeKey = 'iosAudioModeKey';
-const String iosAudioCategoryAmbientSolo = 'iosAudioCategoryAmbientSolo';
-const String iosAudioCategoryAmbient = 'iosAudioCategoryAmbient';
-const String iosAudioCategoryPlayback = 'iosAudioCategoryPlayback';
-const String iosAudioCategoryPlaybackAndRecord =
- 'iosAudioCategoryPlaybackAndRecord';
-
-const String iosAudioCategoryOptionsMixWithOthers =
- 'iosAudioCategoryOptionsMixWithOthers';
-const String iosAudioCategoryOptionsDuckOthers =
- 'iosAudioCategoryOptionsDuckOthers';
-const String iosAudioCategoryOptionsInterruptSpokenAudioAndMixWithOthers =
- 'iosAudioCategoryOptionsInterruptSpokenAudioAndMixWithOthers';
-const String iosAudioCategoryOptionsAllowBluetooth =
- 'iosAudioCategoryOptionsAllowBluetooth';
-const String iosAudioCategoryOptionsAllowBluetoothA2DP =
- 'iosAudioCategoryOptionsAllowBluetoothA2DP';
-const String iosAudioCategoryOptionsAllowAirPlay =
- 'iosAudioCategoryOptionsAllowAirPlay';
-const String iosAudioCategoryOptionsDefaultToSpeaker =
- 'iosAudioCategoryOptionsDefaultToSpeaker';
-
-const String iosAudioModeDefault = 'iosAudioModeDefault';
-const String iosAudioModeGameChat = 'iosAudioModeGameChat';
-const String iosAudioModeMeasurement = 'iosAudioModeMeasurement';
-const String iosAudioModeMoviePlayback = 'iosAudioModeMoviePlayback';
-const String iosAudioModeSpokenAudio = 'iosAudioModeSpokenAudio';
-const String iosAudioModeVideoChat = 'iosAudioModeVideoChat';
-const String iosAudioModeVideoRecording = 'iosAudioModeVideoRecording';
-const String iosAudioModeVoiceChat = 'iosAudioModeVoiceChat';
-const String iosAudioModeVoicePrompt = 'iosAudioModeVoicePrompt';
-
-enum TextToSpeechPlatform { android, ios }
-
-/// Audio session category identifiers for iOS.
-///
-/// See also:
-/// * https://developer.apple.com/documentation/avfaudio/avaudiosession/category
-enum IosTextToSpeechAudioCategory {
- /// The default audio session category.
- ///
- /// Your audio is silenced by screen locking and by the Silent switch.
- ///
- /// By default, using this category implies that your app’s audio
- /// is nonmixable—activating your session will interrupt
- /// any other audio sessions which are also nonmixable.
- /// To allow mixing, use the [ambient] category instead.
- ambientSolo,
-
- /// The category for an app in which sound playback is nonprimary — that is,
- /// your app also works with the sound turned off.
- ///
- /// This category is also appropriate for “play-along” apps,
- /// such as a virtual piano that a user plays while the Music app is playing.
- /// When you use this category, audio from other apps mixes with your audio.
- /// Screen locking and the Silent switch (on iPhone, the Ring/Silent switch) silence your audio.
- ambient,
-
- /// The category for playing recorded music or other sounds
- /// that are central to the successful use of your app.
- ///
- /// When using this category, your app audio continues
- /// with the Silent switch set to silent or when the screen locks.
- ///
- /// By default, using this category implies that your app’s audio
- /// is nonmixable—activating your session will interrupt
- /// any other audio sessions which are also nonmixable.
- /// To allow mixing for this category, use the
- /// [IosTextToSpeechAudioCategoryOptions.mixWithOthers] option.
- playback,
-
- /// The category for recording (input) and playback (output) of audio,
- /// such as for a Voice over Internet Protocol (VoIP) app.
- ///
- /// Your audio continues with the Silent switch set to silent and with the screen locked.
- /// This category is appropriate for simultaneous recording and playback,
- /// and also for apps that record and play back, but not simultaneously.
- playAndRecord,
-}
-
-/// Audio session mode identifiers for iOS.
-///
-/// See also:
-/// * https://developer.apple.com/documentation/avfaudio/avaudiosession/mode
-enum IosTextToSpeechAudioMode {
- /// The default audio session mode.
- ///
- /// You can use this mode with every [IosTextToSpeechAudioCategory].
- defaultMode,
-
- /// A mode that the GameKit framework sets on behalf of an application
- /// that uses GameKit’s voice chat service.
- ///
- /// This mode is valid only with the
- /// [IosTextToSpeechAudioCategory.playAndRecord] category.
- ///
- /// Don’t set this mode directly. If you need similar behavior and aren’t
- /// using a `GKVoiceChat` object, use [voiceChat] or [videoChat] instead.
- gameChat,
-
- /// A mode that indicates that your app is performing measurement of audio input or output.
- ///
- /// Use this mode for apps that need to minimize the amount of
- /// system-supplied signal processing to input and output signals.
- /// If recording on devices with more than one built-in microphone,
- /// the session uses the primary microphone.
- ///
- /// For use with the [IosTextToSpeechAudioCategory.playback] or
- /// [IosTextToSpeechAudioCategory.playAndRecord] category.
- ///
- /// **Important:** This mode disables some dynamics processing on input and output signals,
- /// resulting in a lower-output playback level.
- measurement,
-
- /// A mode that indicates that your app is playing back movie content.
- ///
- /// When you set this mode, the audio session uses signal processing to enhance
- /// movie playback for certain audio routes such as built-in speaker or headphones.
- /// You may only use this mode with the
- /// [IosTextToSpeechAudioCategory.playback] category.
- moviePlayback,
-
- /// A mode used for continuous spoken audio to pause the audio when another app plays a short audio prompt.
- ///
- /// This mode is appropriate for apps that play continuous spoken audio,
- /// such as podcasts or audio books. Setting this mode indicates that your app
- /// should pause, rather than duck, its audio if another app plays
- /// a spoken audio prompt. After the interrupting app’s audio ends, you can
- /// resume your app’s audio playback.
- spokenAudio,
-
- /// A mode that indicates that your app is engaging in online video conferencing.
- ///
- /// Use this mode for video chat apps that use the
- /// [IosTextToSpeechAudioCategory.playAndRecord] category.
- /// When you set this mode, the audio session optimizes the device’s tonal
- /// equalization for voice. It also reduces the set of allowable audio routes
- /// to only those appropriate for video chat.
- ///
- /// Using this mode has the side effect of enabling the
- /// [IosTextToSpeechAudioCategoryOptions.allowBluetooth] category option.
- videoChat,
-
- /// A mode that indicates that your app is recording a movie.
- ///
- /// This mode is valid only with the
- /// [IosTextToSpeechAudioCategory.playAndRecord] category.
- /// On devices with more than one built-in microphone,
- /// the audio session uses the microphone closest to the video camera.
- ///
- /// Use this mode to ensure that the system provides appropriate audio-signal processing.
- videoRecording,
-
- /// A mode that indicates that your app is performing two-way voice communication,
- /// such as using Voice over Internet Protocol (VoIP).
- ///
- /// Use this mode for Voice over IP (VoIP) apps that use the
- /// [IosTextToSpeechAudioCategory.playAndRecord] category.
- /// When you set this mode, the session optimizes the device’s tonal
- /// equalization for voice and reduces the set of allowable audio routes
- /// to only those appropriate for voice chat.
- ///
- /// Using this mode has the side effect of enabling the
- /// [IosTextToSpeechAudioCategoryOptions.allowBluetooth] category option.
- voiceChat,
-
- /// A mode that indicates that your app plays audio using text-to-speech.
- ///
- /// Setting this mode allows for different routing behaviors when your app
- /// is connected to certain audio devices, such as CarPlay.
- /// An example of an app that uses this mode is a turn-by-turn navigation app
- /// that plays short prompts to the user.
- ///
- /// Typically, apps of the same type also configure their sessions to use the
- /// [IosTextToSpeechAudioCategoryOptions.duckOthers] and
- /// [IosTextToSpeechAudioCategoryOptions.interruptSpokenAudioAndMixWithOthers] options.
- voicePrompt,
-}
-
-/// Audio session category options for iOS.
-///
-/// See also:
-/// * https://developer.apple.com/documentation/avfaudio/avaudiosession/categoryoptions
-enum IosTextToSpeechAudioCategoryOptions {
- /// An option that indicates whether audio from this session mixes with audio
- /// from active sessions in other audio apps.
- ///
- /// You can set this option explicitly only if the audio session category
- /// is [IosTextToSpeechAudioCategory.playAndRecord] or
- /// [IosTextToSpeechAudioCategory.playback].
- /// If you set the audio session category to [IosTextToSpeechAudioCategory.ambient],
- /// the session automatically sets this option.
- /// Likewise, setting the [duckOthers] or [interruptSpokenAudioAndMixWithOthers]
- /// options also enables this option.
- ///
- /// If you set this option, your app mixes its audio with audio playing
- /// in background apps, such as the Music app.
- mixWithOthers,
-
- /// An option that reduces the volume of other audio sessions while audio
- /// from this session plays.
- ///
- /// You can set this option only if the audio session category is
- /// [IosTextToSpeechAudioCategory.playAndRecord] or
- /// [IosTextToSpeechAudioCategory.playback].
- /// Setting it implicitly sets the [mixWithOthers] option.
- ///
- /// Use this option to mix your app’s audio with that of others.
- /// While your app plays its audio, the system reduces the volume of other
- /// audio sessions to make yours more prominent. If your app provides
- /// occasional spoken audio, such as in a turn-by-turn navigation app
- /// or an exercise app, you should also set the [interruptSpokenAudioAndMixWithOthers] option.
- ///
- /// Note that ducking begins when you activate your app’s audio session
- /// and ends when you deactivate the session.
- ///
- /// See also:
- /// * [FlutterTts.setSharedInstance]
- duckOthers,
-
- /// An option that determines whether to pause spoken audio content
- /// from other sessions when your app plays its audio.
- ///
- /// You can set this option only if the audio session category is
- /// [IosTextToSpeechAudioCategory.playAndRecord] or
- /// [IosTextToSpeechAudioCategory.playback].
- /// Setting this option also sets [mixWithOthers].
- ///
- /// If you set this option, the system mixes your audio with other
- /// audio sessions, but interrupts (and stops) audio sessions that use the
- /// [IosTextToSpeechAudioMode.spokenAudio] audio session mode.
- /// It pauses the audio from other apps as long as your session is active.
- /// After your audio session deactivates, the system resumes the interrupted app’s audio.
- ///
- /// Set this option if your app’s audio is occasional and spoken,
- /// such as in a turn-by-turn navigation app or an exercise app.
- /// This avoids intelligibility problems when two spoken audio apps mix.
- /// If you set this option, also set the [duckOthers] option unless
- /// you have a specific reason not to. Ducking other audio, rather than
- /// interrupting it, is appropriate when the other audio isn’t spoken audio.
- interruptSpokenAudioAndMixWithOthers,
-
- /// An option that determines whether Bluetooth hands-free devices appear
- /// as available input routes.
- ///
- /// You can set this option only if the audio session category is
- /// [IosTextToSpeechAudioCategory.playAndRecord] or
- /// [IosTextToSpeechAudioCategory.playback].
- ///
- /// You’re required to set this option to allow routing audio input and output
- /// to a paired Bluetooth Hands-Free Profile (HFP) device.
- /// If you clear this option, paired Bluetooth HFP devices don’t show up
- /// as available audio input routes.
- allowBluetooth,
-
- /// An option that determines whether you can stream audio from this session
- /// to Bluetooth devices that support the Advanced Audio Distribution Profile (A2DP).
- ///
- /// A2DP is a stereo, output-only profile intended for higher bandwidth
- /// audio use cases, such as music playback.
- /// The system automatically routes to A2DP ports if you configure an
- /// app’s audio session to use the [IosTextToSpeechAudioCategory.ambient],
- /// [IosTextToSpeechAudioCategory.ambientSolo], or
- /// [IosTextToSpeechAudioCategory.playback] categories.
- ///
- /// Starting with iOS 10.0, apps using the
- /// [IosTextToSpeechAudioCategory.playAndRecord] category may also allow
- /// routing output to paired Bluetooth A2DP devices. To enable this behavior,
- /// pass this category option when setting your audio session’s category.
- ///
- /// Note: If this option and the [allowBluetooth] option are both set,
- /// when a single device supports both the Hands-Free Profile (HFP) and A2DP,
- /// the system gives hands-free ports a higher priority for routing.
- allowBluetoothA2DP,
-
- /// An option that determines whether you can stream audio
- /// from this session to AirPlay devices.
- ///
- /// Setting this option enables the audio session to route audio output
- /// to AirPlay devices. You can only explicitly set this option if the
- /// audio session’s category is set to [IosTextToSpeechAudioCategory.playAndRecord].
- /// For most other audio session categories, the system sets this option implicitly.
- allowAirPlay,
-
- /// An option that determines whether audio from the session defaults to the built-in speaker instead of the receiver.
- ///
- /// You can set this option only when using the
- /// [IosTextToSpeechAudioCategory.playAndRecord] category.
- /// It’s used to modify the category’s routing behavior so that audio
- /// is always routed to the speaker rather than the receiver if
- /// no other accessories, such as headphones, are in use.
- ///
- /// When using this option, the system honors user gestures.
- /// For example, plugging in a headset causes the route to change to
- /// headset mic/headphones, and unplugging the headset causes the route
- /// to change to built-in mic/speaker (as opposed to built-in mic/receiver)
- /// when you’ve set this override.
- ///
- /// In the case of using a USB input-only accessory, audio input
- /// comes from the accessory, and the system routes audio to the headphones,
- /// if attached, or to the speaker if the headphones aren’t plugged in.
- /// The use case is to route audio to the speaker instead of the receiver
- /// in cases where the audio would normally go to the receiver.
- defaultToSpeaker,
-}
-
-class SpeechRateValidRange {
- final double min;
- final double normal;
- final double max;
- final TextToSpeechPlatform platform;
-
- SpeechRateValidRange(this.min, this.normal, this.max, this.platform);
-}
-
-// Provides Platform specific TTS services (Android: TextToSpeech, IOS: AVSpeechSynthesizer)
-class FlutterTts {
- static const MethodChannel _channel = MethodChannel('flutter_tts');
-
- VoidCallback? startHandler;
- VoidCallback? completionHandler;
- VoidCallback? pauseHandler;
- VoidCallback? continueHandler;
- VoidCallback? cancelHandler;
- ProgressHandler? progressHandler;
- ErrorHandler? errorHandler;
-
- FlutterTts() {
- _channel.setMethodCallHandler(platformCallHandler);
- }
-
- /// [Future] which sets speak's future to return on completion of the utterance
- Future awaitSpeakCompletion(bool awaitCompletion) async =>
- await _channel.invokeMethod('awaitSpeakCompletion', awaitCompletion);
-
- /// [Future] which sets synthesize to file's future to return on completion of the synthesize
- /// ***Android, iOS, and macOS supported only***
- Future awaitSynthCompletion(bool awaitCompletion) async =>
- await _channel.invokeMethod('awaitSynthCompletion', awaitCompletion);
-
- /// [Future] which invokes the platform specific method for speaking
- Future speak(String text, {bool focus = false}) async {
- if (!kIsWeb && Platform.isAndroid) {
- return await _channel.invokeMethod('speak', {
- "text": text,
- "focus": focus,
- });
- } else {
- return await _channel.invokeMethod('speak', text);
- }
- }
-
- /// [Future] which invokes the platform specific method for pause
- Future pause() async => await _channel.invokeMethod('pause');
-
- /// [Future] which invokes the platform specific method for getMaxSpeechInputLength
- /// ***Android supported only***
- Future get getMaxSpeechInputLength async {
- return await _channel.invokeMethod('getMaxSpeechInputLength');
- }
-
- /// [Future] which invokes the platform specific method for synthesizeToFile
- /// ***Android and iOS supported only***
- Future synthesizeToFile(String text, String fileName,
- [bool isFullPath = false]) async =>
- _channel.invokeMethod('synthesizeToFile', {
- "text": text,
- "fileName": fileName,
- "isFullPath": isFullPath,
- });
-
- /// [Future] which invokes the platform specific method for setLanguage
- Future setLanguage(String language) async =>
- await _channel.invokeMethod('setLanguage', language);
-
- /// [Future] which invokes the platform specific method for setSpeechRate
- /// Allowed values are in the range from 0.0 (slowest) to 1.0 (fastest)
- Future setSpeechRate(double rate) async =>
- await _channel.invokeMethod('setSpeechRate', rate);
-
- /// [Future] which invokes the platform specific method for setVolume
- /// Allowed values are in the range from 0.0 (silent) to 1.0 (loudest)
- Future setVolume(double volume) async =>
- await _channel.invokeMethod('setVolume', volume);
-
- /// [Future] which invokes the platform specific method for shared instance
- /// ***iOS supported only***
- Future setSharedInstance(bool sharedSession) async =>
- await _channel.invokeMethod('setSharedInstance', sharedSession);
-
- /// [Future] which invokes the platform specific method for setting the autoStopSharedSession
- /// default value is true
- /// *** iOS, and macOS supported only***
- Future autoStopSharedSession(bool autoStop) async =>
- await _channel.invokeMethod('autoStopSharedSession', autoStop);
-
- /// [Future] which invokes the platform specific method for setting audio category
- /// ***Ios supported only***
- Future setIosAudioCategory(IosTextToSpeechAudioCategory category,
- List options,
- [IosTextToSpeechAudioMode mode =
- IosTextToSpeechAudioMode.defaultMode]) async {
- const categoryToString = {
- IosTextToSpeechAudioCategory.ambientSolo: iosAudioCategoryAmbientSolo,
- IosTextToSpeechAudioCategory.ambient: iosAudioCategoryAmbient,
- IosTextToSpeechAudioCategory.playback: iosAudioCategoryPlayback
- };
- const optionsToString = {
- IosTextToSpeechAudioCategoryOptions.mixWithOthers:
- 'iosAudioCategoryOptionsMixWithOthers',
- IosTextToSpeechAudioCategoryOptions.duckOthers:
- 'iosAudioCategoryOptionsDuckOthers',
- IosTextToSpeechAudioCategoryOptions.interruptSpokenAudioAndMixWithOthers:
- 'iosAudioCategoryOptionsInterruptSpokenAudioAndMixWithOthers',
- IosTextToSpeechAudioCategoryOptions.allowBluetooth:
- 'iosAudioCategoryOptionsAllowBluetooth',
- IosTextToSpeechAudioCategoryOptions.allowBluetoothA2DP:
- 'iosAudioCategoryOptionsAllowBluetoothA2DP',
- IosTextToSpeechAudioCategoryOptions.allowAirPlay:
- 'iosAudioCategoryOptionsAllowAirPlay',
- IosTextToSpeechAudioCategoryOptions.defaultToSpeaker:
- 'iosAudioCategoryOptionsDefaultToSpeaker',
- };
- const modeToString = {
- IosTextToSpeechAudioMode.defaultMode: iosAudioModeDefault,
- IosTextToSpeechAudioMode.gameChat: iosAudioModeGameChat,
- IosTextToSpeechAudioMode.measurement: iosAudioModeMeasurement,
- IosTextToSpeechAudioMode.moviePlayback: iosAudioModeMoviePlayback,
- IosTextToSpeechAudioMode.spokenAudio: iosAudioModeSpokenAudio,
- IosTextToSpeechAudioMode.videoChat: iosAudioModeVideoChat,
- IosTextToSpeechAudioMode.videoRecording: iosAudioModeVideoRecording,
- IosTextToSpeechAudioMode.voiceChat: iosAudioModeVoiceChat,
- IosTextToSpeechAudioMode.voicePrompt: iosAudioModeVoicePrompt,
- };
- if (!Platform.isIOS) return;
- try {
- return await _channel
- .invokeMethod('setIosAudioCategory', {
- iosAudioCategoryKey: categoryToString[category],
- iosAudioCategoryOptionsKey:
- options.map((o) => optionsToString[o]).toList(),
- iosAudioModeKey: modeToString[mode],
- });
- } on PlatformException catch (e) {
- print(
- 'setIosAudioCategory error, category: $category, mode: $mode, error: ${e.message}');
- }
- }
-
- /// [Future] which invokes the platform specific method for setEngine
- /// ***Android supported only***
- Future setEngine(String engine) async {
- await _channel.invokeMethod('setEngine', engine);
- }
-
- /// [Future] which invokes the platform specific method for setPitch
- /// 1.0 is default and ranges from .5 to 2.0
- Future setPitch(double pitch) async =>
- await _channel.invokeMethod('setPitch', pitch);
-
- /// [Future] which invokes the platform specific method for setVoice
- /// ***Android, iOS, and macOS supported only***
- Future setVoice(Map voice) async =>
- await _channel.invokeMethod('setVoice', voice);
-
- /// [Future] which resets the platform voice to the default
- Future clearVoice() async =>
- await _channel.invokeMethod('clearVoice');
-
- /// [Future] which invokes the platform specific method for stop
- Future stop() async => await _channel.invokeMethod('stop');
-
- /// [Future] which invokes the platform specific method for getLanguages
- /// Android issues with API 21 & 22
- /// Returns a list of available languages
- Future get getLanguages async {
- final languages = await _channel.invokeMethod('getLanguages');
- return languages;
- }
-
- /// [Future] which invokes the platform specific method for getEngines
- /// Returns a list of installed TTS engines
- /// ***Android supported only***
- Future get getEngines async {
- final engines = await _channel.invokeMethod('getEngines');
- return engines;
- }
-
- /// [Future] which invokes the platform specific method for getDefaultEngine
- /// Returns a `String` of the default engine name
- /// ***Android supported only ***
- Future get getDefaultEngine async {
- final engineName = await _channel.invokeMethod('getDefaultEngine');
- return engineName;
- }
-
- /// [Future] which invokes the platform specific method for getDefaultVoice
- /// Returns a `Map` containing a voice name and locale
- /// ***Android supported only ***
- Future get getDefaultVoice async {
- final voice = await _channel.invokeMethod('getDefaultVoice');
- return voice;
- }
-
- /// [Future] which invokes the platform specific method for getVoices
- /// Returns a `List` of `Maps` containing a voice name and locale
- /// For iOS specifically, it also includes quality, gender, and identifier
- /// ***Android, iOS, and macOS supported only***
- Future get getVoices async {
- final voices = await _channel.invokeMethod('getVoices');
- return voices;
- }
-
- /// [Future] which invokes the platform specific method for isLanguageAvailable
- /// Returns `true` or `false`
- Future isLanguageAvailable(String language) async =>
- await _channel.invokeMethod('isLanguageAvailable', language);
-
- /// [Future] which invokes the platform specific method for isLanguageInstalled
- /// Returns `true` or `false`
- /// ***Android supported only***
- Future isLanguageInstalled(String language) async =>
- await _channel.invokeMethod('isLanguageInstalled', language);
-
- /// [Future] which invokes the platform specific method for areLanguagesInstalled
- /// Returns a HashMap with `true` or `false` for each submitted language.
- /// ***Android supported only***
- Future areLanguagesInstalled(List languages) async =>
- await _channel.invokeMethod('areLanguagesInstalled', languages);
-
- Future get getSpeechRateValidRange async {
- final validRange = await _channel.invokeMethod('getSpeechRateValidRange')
- as Map;
- final min = double.parse(validRange['min'].toString());
- final normal = double.parse(validRange['normal'].toString());
- final max = double.parse(validRange['max'].toString());
- final platformStr = validRange['platform'].toString();
- final platform =
- TextToSpeechPlatform.values.firstWhere((e) => e.name == platformStr);
-
- return SpeechRateValidRange(min, normal, max, platform);
- }
-
- /// [Future] which invokes the platform specific method for setSilence
- /// 0 means start the utterance immediately. If the value is greater than zero a silence period in milliseconds is set according to the parameter
- /// ***Android supported only***
- Future setSilence(int timems) async =>
- await _channel.invokeMethod('setSilence', timems);
-
- /// [Future] which invokes the platform specific method for setQueueMode
- /// 0 means QUEUE_FLUSH - Queue mode where all entries in the playback queue (media to be played and text to be synthesized) are dropped and replaced by the new entry.
- /// Queues are flushed with respect to a given calling app. Entries in the queue from other calls are not discarded.
- /// 1 means QUEUE_ADD - Queue mode where the new entry is added at the end of the playback queue.
- /// ***Android supported only***
- Future setQueueMode(int queueMode) async =>
- await _channel.invokeMethod('setQueueMode', queueMode);
-
- void setStartHandler(VoidCallback callback) {
- startHandler = callback;
- }
-
- void setCompletionHandler(VoidCallback callback) {
- completionHandler = callback;
- }
-
- void setContinueHandler(VoidCallback callback) {
- continueHandler = callback;
- }
-
- void setPauseHandler(VoidCallback callback) {
- pauseHandler = callback;
- }
-
- void setCancelHandler(VoidCallback callback) {
- cancelHandler = callback;
- }
-
- void setProgressHandler(ProgressHandler callback) {
- progressHandler = callback;
- }
-
- void setErrorHandler(ErrorHandler handler) {
- errorHandler = handler;
- }
-
- /// Platform listeners
- Future platformCallHandler(MethodCall call) async {
- switch (call.method) {
- case "speak.onStart":
- if (startHandler != null) {
- startHandler!();
- }
- break;
-
- case "synth.onStart":
- if (startHandler != null) {
- startHandler!();
- }
- break;
- case "speak.onComplete":
- if (completionHandler != null) {
- completionHandler!();
- }
- break;
- case "synth.onComplete":
- if (completionHandler != null) {
- completionHandler!();
- }
- break;
- case "speak.onPause":
- if (pauseHandler != null) {
- pauseHandler!();
- }
- break;
- case "speak.onContinue":
- if (continueHandler != null) {
- continueHandler!();
- }
- break;
- case "speak.onCancel":
- if (cancelHandler != null) {
- cancelHandler!();
- }
- break;
- case "speak.onError":
- if (errorHandler != null) {
- errorHandler!(call.arguments);
- }
- break;
- case 'speak.onProgress':
- if (progressHandler != null) {
- final args = call.arguments as Map;
- progressHandler!(
- args['text'].toString(),
- int.parse(args['start'].toString()),
- int.parse(args['end'].toString()),
- args['word'].toString(),
- );
- }
- break;
- case "synth.onError":
- if (errorHandler != null) {
- errorHandler!(call.arguments);
- }
- break;
- default:
- print('Unknown method ${call.method}');
- }
- }
-
- Future setAudioAttributesForNavigation() async {
- await _channel.invokeMethod('setAudioAttributesForNavigation');
- }
-}
diff --git a/lib/flutter_tts_web.dart b/lib/flutter_tts_web.dart
deleted file mode 100644
index abff5ad3..00000000
--- a/lib/flutter_tts_web.dart
+++ /dev/null
@@ -1,265 +0,0 @@
-import 'dart:async';
-import 'dart:collection';
-import 'dart:js_interop';
-import 'dart:js_interop_unsafe';
-
-import 'package:flutter/services.dart';
-import 'package:flutter_web_plugins/flutter_web_plugins.dart';
-
-import 'interop_types.dart';
-
-enum TtsState { playing, stopped, paused, continued }
-
-class FlutterTtsPlugin {
- static const String platformChannel = "flutter_tts";
- static late MethodChannel channel;
- bool awaitSpeakCompletion = false;
-
- TtsState ttsState = TtsState.stopped;
-
- Completer? _speechCompleter;
-
- bool get isPlaying => ttsState == TtsState.playing;
-
- bool get isStopped => ttsState == TtsState.stopped;
-
- bool get isPaused => ttsState == TtsState.paused;
-
- bool get isContinued => ttsState == TtsState.continued;
-
- static void registerWith(Registrar registrar) {
- channel =
- MethodChannel(platformChannel, const StandardMethodCodec(), registrar);
- final instance = FlutterTtsPlugin();
- channel.setMethodCallHandler(instance.handleMethodCall);
- }
-
- late final SpeechSynthesisUtterance utterance;
- List voices = [];
- List languages = [];
- Timer? t;
- bool supported = false;
-
- FlutterTtsPlugin() {
- try {
- utterance = SpeechSynthesisUtterance();
- _listeners();
- supported = true;
- } catch (e) {
- print('Initialization of TTS failed. Functions are disabled. Error: $e');
- }
- }
-
- void _listeners() {
- utterance.onStart = (JSAny e) {
- ttsState = TtsState.playing;
- channel.invokeMethod("speak.onStart", null);
- var bLocal = (utterance.voice?.isLocalService ?? false);
- if (!bLocal) {
- t = Timer.periodic(Duration(seconds: 14), (t) {
- if (ttsState == TtsState.playing) {
- synth.pause();
- synth.resume();
- } else {
- t.cancel();
- }
- });
- }
- }.toJS;
- // js.JsFunction.withThis((e) {
- // ttsState = TtsState.playing;
- // channel.invokeMethod("speak.onStart", null);
- // });
- utterance.onEnd = (JSAny e) {
- ttsState = TtsState.stopped;
- if (_speechCompleter != null) {
- _speechCompleter?.complete();
- _speechCompleter = null;
- }
- t?.cancel();
- channel.invokeMethod("speak.onComplete", null);
- }.toJS;
-
- utterance.onPause = (JSAny e) {
- ttsState = TtsState.paused;
- channel.invokeMethod("speak.onPause", null);
- }.toJS;
-
- utterance.onResume = (JSAny e) {
- ttsState = TtsState.continued;
- channel.invokeMethod("speak.onContinue", null);
- }.toJS;
-
- utterance.onError = (JSObject event) {
- ttsState = TtsState.stopped;
- if (_speechCompleter != null) {
- _speechCompleter = null;
- }
- t?.cancel();
- print(event); // Log the entire event object to get more details
- channel.invokeMethod("speak.onError", event["error"]);
- }.toJS;
-
- utterance.onBoundary = (JSObject event) {
- int charIndex = event['charIndex'] as int;
- String name = event['name'] as String;
- if (name == 'sentence') return;
- String text = utterance['text'] as String;
- int endIndex = charIndex;
- while (endIndex < text.length &&
- !RegExp(r'[\s,.!?]').hasMatch(text[endIndex])) {
- endIndex++;
- }
- String word = text.substring(charIndex, endIndex);
- Map progressArgs = {
- 'text': text,
- 'start': charIndex,
- 'end': endIndex,
- 'word': word
- };
- channel.invokeMethod("speak.onProgress", progressArgs);
- }.toJS;
- }
-
- Future handleMethodCall(MethodCall call) async {
- if (!supported) return;
- switch (call.method) {
- case 'speak':
- final text = call.arguments as String?;
- if (awaitSpeakCompletion) {
- _speechCompleter = Completer();
- }
- _speak(text);
- if (awaitSpeakCompletion) {
- return _speechCompleter?.future;
- }
- break;
- case 'awaitSpeakCompletion':
- awaitSpeakCompletion = (call.arguments as bool?) ?? false;
- return 1;
- case 'stop':
- _stop();
- return 1;
- case 'pause':
- _pause();
- return 1;
- case 'setLanguage':
- final language = call.arguments as String;
- _setLanguage(language);
- return 1;
- case 'getLanguages':
- return _getLanguages();
- case 'getVoices':
- return getVoices();
- case 'setVoice':
- final tmpVoiceMap =
- Map.from(call.arguments as LinkedHashMap);
- return _setVoice(tmpVoiceMap);
- case 'setSpeechRate':
- final rate = call.arguments as double;
- _setRate(rate);
- return 1;
- case 'setVolume':
- final volume = call.arguments as double;
- _setVolume(volume);
- return 1;
- case 'setPitch':
- final pitch = call.arguments as double;
- _setPitch(pitch);
- return 1;
- case 'isLanguageAvailable':
- final lang = call.arguments as String;
- return _isLanguageAvailable(lang);
- default:
- throw PlatformException(
- code: 'Unimplemented',
- details: "The flutter_tts plugin for web doesn't implement "
- "the method '${call.method}'");
- }
- }
-
- void _speak(String? text) {
- if (text == null || text.isEmpty) return;
- if (ttsState == TtsState.stopped || ttsState == TtsState.paused) {
- utterance.text = text;
- if (ttsState == TtsState.paused) {
- synth.resume();
- } else {
- synth.speak(utterance);
- }
- }
- }
-
- void _stop() {
- if (ttsState != TtsState.stopped) {
- synth.cancel();
- }
- }
-
- void _pause() {
- if (ttsState == TtsState.playing || ttsState == TtsState.continued) {
- synth.pause();
- }
- }
-
- void _setRate(double rate) => utterance.rate = rate;
- void _setVolume(double volume) => utterance.volume = volume;
- void _setPitch(double pitch) => utterance.pitch = pitch;
- void _setLanguage(String language) {
- var targetList = synth.getVoices().toDart.where((e) {
- return e.lang.toLowerCase().startsWith(language.toLowerCase());
- });
- if (targetList.isNotEmpty) {
- utterance.voice = targetList.first;
- utterance.lang = targetList.first.lang;
- }
- }
-
- void _setVoice(Map voice) {
- var tmpVoices = synth.getVoices().toDart;
- var targetList = tmpVoices.where((e) {
- return voice["name"] == e.name && voice["locale"] == e.lang;
- });
- if (targetList.isNotEmpty) {
- utterance.voice = targetList.first;
- }
- }
-
- bool _isLanguageAvailable(String? language) {
- if (voices.isEmpty) _setVoices();
- if (languages.isEmpty) _setLanguages();
- for (var lang in languages) {
- if (!language!.contains('-')) {
- lang = lang.split('-').first;
- }
- if (lang.toLowerCase() == language.toLowerCase()) return true;
- }
- return false;
- }
-
- List? _getLanguages() {
- if (voices.isEmpty) _setVoices();
- if (languages.isEmpty) _setLanguages();
- return languages;
- }
-
- void _setVoices() {
- voices = synth.getVoices().toDart;
- }
-
- Future>> getVoices() async {
- var tmpVoices = synth.getVoices().toDart;
- return tmpVoices
- .map((voice) => {"name": voice.name, "locale": voice.lang})
- .toList();
- }
-
- void _setLanguages() {
- var langs = {};
- for (var v in voices) {
- langs.add(v.lang);
- }
-
- languages = langs.toList();
- }
-}
diff --git a/macos/Classes/FlutterTtsPlugin.swift b/macos/Classes/FlutterTtsPlugin.swift
deleted file mode 100644
index f30bca60..00000000
--- a/macos/Classes/FlutterTtsPlugin.swift
+++ /dev/null
@@ -1,411 +0,0 @@
-import FlutterMacOS
-import Foundation
-import AVFoundation
-
-public class FlutterTtsPlugin: NSObject, FlutterPlugin, AVSpeechSynthesizerDelegate {
- final var iosAudioCategoryKey = "iosAudioCategoryKey"
- final var iosAudioCategoryOptionsKey = "iosAudioCategoryOptionsKey"
-
- let synthesizer = AVSpeechSynthesizer()
- var language: String = AVSpeechSynthesisVoice.currentLanguageCode()
- var rate: Float = AVSpeechUtteranceDefaultSpeechRate
- var languages = Set()
- var volume: Float = 1.0
- var pitch: Float = 1.0
- var voice: AVSpeechSynthesisVoice?
- var awaitSpeakCompletion: Bool = false
- var awaitSynthCompletion: Bool = false
- var speakResult: FlutterResult!
- var synthResult: FlutterResult!
-
- var channel = FlutterMethodChannel()
- init(channel: FlutterMethodChannel) {
- super.init()
- self.channel = channel
- synthesizer.delegate = self
- setLanguages()
- }
-
- private func setLanguages() {
- for voice in AVSpeechSynthesisVoice.speechVoices(){
- self.languages.insert(voice.language)
- }
- }
-
- public static func register(with registrar: FlutterPluginRegistrar) {
- let channel = FlutterMethodChannel(name: "flutter_tts", binaryMessenger: registrar.messenger)
- let instance = FlutterTtsPlugin(channel: channel)
- registrar.addMethodCallDelegate(instance, channel: channel)
- }
-
- public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
- switch call.method {
- case "speak":
- let text: String = call.arguments as! String
- self.speak(text: text, result: result)
- break
- case "awaitSpeakCompletion":
- self.awaitSpeakCompletion = call.arguments as! Bool
- result(1)
- break
- case "awaitSynthCompletion":
- self.awaitSynthCompletion = call.arguments as! Bool
- result(1)
- break
- case "synthesizeToFile":
- guard let args = call.arguments as? [String: Any] else {
- result("iOS could not recognize flutter arguments in method: (sendParams)")
- return
- }
- let text = args["text"] as! String
- let fileName = args["fileName"] as! String
- self.synthesizeToFile(text: text, fileName: fileName, result: result)
- break
- case "pause":
- self.pause(result: result)
- break
- case "setLanguage":
- let language: String = call.arguments as! String
- self.setLanguage(language: language, result: result)
- break
- case "setSpeechRate":
- let rate: Double = call.arguments as! Double
- self.setRate(rate: Float(rate))
- result(1)
- break
- case "setVolume":
- let volume: Double = call.arguments as! Double
- self.setVolume(volume: Float(volume), result: result)
- break
- case "setPitch":
- let pitch: Double = call.arguments as! Double
- self.setPitch(pitch: Float(pitch), result: result)
- break
- case "stop":
- self.stop()
- result(1)
- break
- case "getLanguages":
- self.getLanguages(result: result)
- break
- case "getSpeechRateValidRange":
- self.getSpeechRateValidRange(result: result)
- break
- case "isLanguageAvailable":
- let language: String = call.arguments as! String
- self.isLanguageAvailable(language: language, result: result)
- break
- case "getVoices":
- self.getVoices(result: result)
- break
- case "setVoice":
- guard let args = call.arguments as? [String: String] else {
- result("iOS could not recognize flutter arguments in method: (sendParams)")
- return
- }
- self.setVoice(voice: args, result: result)
- break
- case "autoStopSharedSession":
- // MacOS does not have a shared audio session so just accept the call
- result(1)
- break
- default:
- result(FlutterMethodNotImplemented)
- }
- }
-
- private func speak(text: String, result: @escaping FlutterResult) {
- if (self.synthesizer.isPaused) {
- if (self.synthesizer.continueSpeaking()) {
- if self.awaitSpeakCompletion {
- self.speakResult = result
- } else {
- result(1)
- }
- } else {
- result(0)
- }
- } else {
- let utterance = AVSpeechUtterance(string: text)
- if self.voice != nil {
- utterance.voice = self.voice!
- } else {
- utterance.voice = AVSpeechSynthesisVoice(language: self.language)
- }
- utterance.rate = self.rate
- utterance.volume = self.volume
- utterance.pitchMultiplier = self.pitch
-
- self.synthesizer.speak(utterance)
- if self.awaitSpeakCompletion {
- self.speakResult = result
- } else {
- result(1)
- }
- }
- }
-
- private func synthesizeToFile(text: String, fileName: String, result: @escaping FlutterResult) {
- var output: AVAudioFile?
- var failed = false
- let utterance = AVSpeechUtterance(string: text)
-
- if #available(iOS 13.0, *) {
- self.synthesizer.write(utterance) { (buffer: AVAudioBuffer) in
- guard let pcmBuffer = buffer as? AVAudioPCMBuffer else {
- NSLog("unknow buffer type: \(buffer)")
- failed = true
- return
- }
- if pcmBuffer.frameLength == 0 {
- // finished
- } else {
- // append buffer to file
- let fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent(fileName)
- NSLog("Saving utterance to file: \(fileURL.absoluteString)")
-
- if output == nil {
- do {
- output = try AVAudioFile(
- forWriting: fileURL,
- settings: pcmBuffer.format.settings,
- commonFormat: .pcmFormatFloat32,
- interleaved: false)
- } catch {
- NSLog(error.localizedDescription)
- failed = true
- return
- }
- }
-
- try! output!.write(from: pcmBuffer)
- }
- }
- } else {
- result("Unsupported iOS version")
- }
- if failed {
- result(0)
- }
- if self.awaitSynthCompletion {
- self.synthResult = result
- } else {
- result(1)
- }
- }
-
- private func pause(result: FlutterResult) {
- if (self.synthesizer.pauseSpeaking(at: AVSpeechBoundary.word)) {
- result(1)
- } else {
- result(0)
- }
- }
-
- private func setLanguage(language: String, result: FlutterResult) {
- if !(self.languages.contains(where: {$0.range(of: language, options: [.caseInsensitive, .anchored]) != nil})) {
- result(0)
- } else {
- self.language = language
- self.voice = nil
- result(1)
- }
- }
-
- private func setRate(rate: Float) {
- self.rate = rate
- }
-
- private func setVolume(volume: Float, result: FlutterResult) {
- if (volume >= 0.0 && volume <= 1.0) {
- self.volume = volume
- result(1)
- } else {
- result(0)
- }
- }
-
- private func setPitch(pitch: Float, result: FlutterResult) {
- if (volume >= 0.5 && volume <= 2.0) {
- self.pitch = pitch
- result(1)
- } else {
- result(0)
- }
- }
-
- private func stop() {
- self.synthesizer.stopSpeaking(at: AVSpeechBoundary.immediate)
- }
-
- private func getLanguages(result: FlutterResult) {
- result(Array(self.languages))
- }
-
- private func getSpeechRateValidRange(result: FlutterResult) {
- let validSpeechRateRange: [String:String] = [
- "min": String(AVSpeechUtteranceMinimumSpeechRate),
- "normal": String(AVSpeechUtteranceDefaultSpeechRate),
- "max": String(AVSpeechUtteranceMaximumSpeechRate),
- "platform": "ios"
- ]
- result(validSpeechRateRange)
- }
-
- private func isLanguageAvailable(language: String, result: FlutterResult) {
- var isAvailable: Bool = false
- if (self.languages.contains(where: {$0.range(of: language, options: [.caseInsensitive, .anchored]) != nil})) {
- isAvailable = true
- }
- result(isAvailable);
- }
-
- private func getVoices(result: FlutterResult) {
- if #available(macOS 10.15, *) {
- let voices = NSMutableArray()
- var voiceDict: [String: String] = [:]
- for voice in AVSpeechSynthesisVoice.speechVoices() {
- voiceDict["name"] = voice.name
- voiceDict["locale"] = voice.language
- voiceDict["quality"] = voice.quality.stringValue
- if #available(macOS 10.15, *) {
- voiceDict["gender"] = voice.gender.stringValue
- }
- voiceDict["identifier"] = voice.identifier
- voices.add(voiceDict)
- }
- result(voices)
- } else {
- // Since voice selection is not supported below iOS 9, make voice getter and setter
- // have the same bahavior as language selection.
- getLanguages(result: result)
- }
- }
-
-
-
- private func setVoice(voice: [String: String], result: FlutterResult) {
- if #available(iOS 9.0, *) {
- // Check if identifier exists and is not empty
- if let identifier = voice["identifier"], !identifier.isEmpty {
- // Find the voice by identifier
- if let selectedVoice = AVSpeechSynthesisVoice(identifier: identifier) {
- self.voice = selectedVoice
- self.language = selectedVoice.language
- result(1)
- return
- }
- }
-
- // If no valid identifier, search by name and locale, then prioritize by quality
- if let name = voice["name"], let locale = voice["locale"] {
- let matchingVoices = AVSpeechSynthesisVoice.speechVoices().filter { $0.name == name && $0.language == locale }
-
- if !matchingVoices.isEmpty {
- // Sort voices by quality: premium (if available) > enhanced > others
- let sortedVoices = matchingVoices.sorted { (voice1, voice2) -> Bool in
- let quality1 = voice1.quality
- let quality2 = voice2.quality
-
- // macOS 13.0+ supports premium quality
- if #available(macOS 13.0, *) {
- if quality1 == .premium {
- return true
- } else if quality1 == .enhanced && quality2 != .premium {
- return true
- } else {
- return false
- }
- } else {
- // Fallback for macOS versions before 13.0 (no premium)
- if quality1 == .enhanced {
- return true
- } else {
- return false
- }
- }
- }
-
- // Select the highest quality voice
- if let selectedVoice = sortedVoices.first {
- self.voice = selectedVoice
- self.language = selectedVoice.language
- result(1)
- return
- }
- }
- }
-
- // No matching voice found
- result(0)
- } else {
- // Handle older iOS versions if needed
- setLanguage(language: voice["name"]!, result: result)
- }
- }
-
- public func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
- if self.awaitSpeakCompletion {
- self.speakResult(1)
- }
- if self.awaitSynthCompletion {
- self.synthResult(1)
- }
- self.channel.invokeMethod("speak.onComplete", arguments: nil)
- }
-
- public func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didStart utterance: AVSpeechUtterance) {
- self.channel.invokeMethod("speak.onStart", arguments: nil)
- }
-
- public func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didPause utterance: AVSpeechUtterance) {
- self.channel.invokeMethod("speak.onPause", arguments: nil)
- }
-
- public func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didContinue utterance: AVSpeechUtterance) {
- self.channel.invokeMethod("speak.onContinue", arguments: nil)
- }
-
- public func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didCancel utterance: AVSpeechUtterance) {
- self.channel.invokeMethod("speak.onCancel", arguments: nil)
- }
-
- public func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance) {
- let nsWord = utterance.speechString as NSString
- let data: [String:String] = [
- "text": utterance.speechString,
- "start": String(characterRange.location),
- "end": String(characterRange.location + characterRange.length),
- "word": nsWord.substring(with: characterRange)
- ]
- self.channel.invokeMethod("speak.onProgress", arguments: data)
- }
-
-}
-
-extension AVSpeechSynthesisVoiceQuality {
- var stringValue: String {
- switch self {
- case .default:
- return "default"
- case .premium:
- return "premium"
- case .enhanced:
- return "enhanced"
- }
- }
-}
-
-@available(macOS 10.15, *)
-extension AVSpeechSynthesisVoiceGender {
- var stringValue: String {
- switch self {
- case .male:
- return "male"
- case .female:
- return "female"
- case .unspecified:
- return "unspecified"
- }
- }
-}
diff --git a/packages/.gitignore b/packages/.gitignore
new file mode 100644
index 00000000..4aa0df8d
--- /dev/null
+++ b/packages/.gitignore
@@ -0,0 +1,48 @@
+.DS_Store
+.atom/
+.idea/
+.vscode/
+
+.packages
+.pub/
+.dart_tool/
+pubspec.lock
+flutter_export_environment.sh
+coverage/
+
+Podfile.lock
+Pods/
+.symlinks/
+**/Flutter/App.framework/
+**/Flutter/ephemeral/
+**/Flutter/Flutter.podspec
+**/Flutter/Flutter.framework/
+**/Flutter/Generated.xcconfig
+**/Flutter/flutter_assets/
+
+ServiceDefinitions.json
+xcuserdata/
+**/DerivedData/
+
+local.properties
+keystore.properties
+.gradle/
+gradlew
+gradlew.bat
+gradle-wrapper.jar
+.flutter-plugins-dependencies
+*.iml
+
+generated_plugin_registrant.cc
+generated_plugin_registrant.h
+generated_plugin_registrant.dart
+GeneratedPluginRegistrant.java
+GeneratedPluginRegistrant.h
+GeneratedPluginRegistrant.m
+GeneratedPluginRegistrant.swift
+build/
+.flutter-plugins
+
+.project
+.classpath
+.settings
\ No newline at end of file
diff --git a/packages/flutter_tts/README.md b/packages/flutter_tts/README.md
new file mode 100644
index 00000000..957445bf
--- /dev/null
+++ b/packages/flutter_tts/README.md
@@ -0,0 +1,26 @@
+# flutter_tts
+
+[![Very Good Ventures][logo_white]][very_good_ventures_link_dark]
+[![Very Good Ventures][logo_black]][very_good_ventures_link_light]
+
+Developed with 💙 by [Very Good Ventures][very_good_ventures_link] 🦄
+
+![coverage][coverage_badge]
+[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
+[![License: MIT][license_badge]][license_link]
+
+A Very Good Flutter Federated Plugin created by the [Very Good Ventures Team][very_good_ventures_link].
+
+Generated by the [Very Good CLI][very_good_cli_link] 🤖
+
+[coverage_badge]: coverage_badge.svg
+[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
+[license_link]: https://opensource.org/licenses/MIT
+[logo_black]: https://raw.githubusercontent.com/VGVentures/very_good_brand/main/styles/README/vgv_logo_black.png#gh-light-mode-only
+[logo_white]: https://raw.githubusercontent.com/VGVentures/very_good_brand/main/styles/README/vgv_logo_white.png#gh-dark-mode-only
+[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
+[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
+[very_good_cli_link]: https://github.com/VeryGoodOpenSource/very_good_cli
+[very_good_ventures_link]: https://verygood.ventures/?utm_source=github&utm_medium=banner&utm_campaign=core
+[very_good_ventures_link_dark]: https://verygood.ventures/?utm_source=github&utm_medium=banner&utm_campaign=core#gh-dark-mode-only
+[very_good_ventures_link_light]: https://verygood.ventures/?utm_source=github&utm_medium=banner&utm_campaign=core#gh-light-mode-only
diff --git a/packages/flutter_tts/analysis_options.yaml b/packages/flutter_tts/analysis_options.yaml
new file mode 100644
index 00000000..4c56a11a
--- /dev/null
+++ b/packages/flutter_tts/analysis_options.yaml
@@ -0,0 +1,4 @@
+include: package:very_good_analysis/analysis_options.yaml
+analyzer:
+ errors:
+ lines_longer_than_80_chars: ignore
diff --git a/packages/flutter_tts/coverage_badge.svg b/packages/flutter_tts/coverage_badge.svg
new file mode 100644
index 00000000..499e98ce
--- /dev/null
+++ b/packages/flutter_tts/coverage_badge.svg
@@ -0,0 +1,20 @@
+
diff --git a/packages/flutter_tts/lib/flutter_tts.dart b/packages/flutter_tts/lib/flutter_tts.dart
new file mode 100644
index 00000000..1c98a044
--- /dev/null
+++ b/packages/flutter_tts/lib/flutter_tts.dart
@@ -0,0 +1,16 @@
+import 'package:flutter_tts_platform_interface/flutter_tts_platform_interface.dart';
+
+export 'package:flutter_tts_platform_interface/flutter_tts_platform_interface.dart';
+
+/// app facing class
+abstract class FlutterTts {
+ /// get the instance of FlutterTtsPlatform
+ static FlutterTtsPlatform get platform => FlutterTtsPlatform.instance;
+
+ /// Platform-specific implementations should set this with their own
+ /// platform-specific class that extends [FlutterTtsPlatform ] when
+ /// they register themselves.
+ static set platform(FlutterTtsPlatform instance) {
+ FlutterTtsPlatform.instance = instance;
+ }
+}
diff --git a/packages/flutter_tts/pubspec.yaml b/packages/flutter_tts/pubspec.yaml
new file mode 100644
index 00000000..82887b4d
--- /dev/null
+++ b/packages/flutter_tts/pubspec.yaml
@@ -0,0 +1,40 @@
+name: flutter_tts
+description: A flutter plugin for Text to Speech. This plugin is supported on iOS, macOS, Android, Web, & Windows.
+version: 5.0.0
+homepage: https://github.com/dlutton/flutter_tts
+resolution: workspace
+
+environment:
+ sdk: ^3.9.0
+
+flutter:
+ plugin:
+ platforms:
+ android:
+ default_package: flutter_tts_android
+ ios:
+ default_package: flutter_tts_ios
+ macos:
+ default_package: flutter_tts_macos
+ web:
+ default_package: flutter_tts_web
+ windows:
+ default_package: flutter_tts_windows
+
+dependencies:
+ flutter:
+ sdk: flutter
+ flutter_tts_android: ^5.0.0
+ flutter_tts_ios: ^5.0.0
+ flutter_tts_macos: ^5.0.0
+ flutter_tts_platform_interface: ^5.0.0
+ flutter_tts_web: ^5.0.0
+ flutter_tts_windows: ^5.0.0
+ multiple_result: ^5.2.0
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ mocktail: ^1.0.4
+ plugin_platform_interface: ^2.1.8
+ very_good_analysis: ^10.0.0
diff --git a/packages/flutter_tts_android/README.md b/packages/flutter_tts_android/README.md
new file mode 100644
index 00000000..987134c3
--- /dev/null
+++ b/packages/flutter_tts_android/README.md
@@ -0,0 +1,14 @@
+# flutter_tts_android
+
+[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
+
+The Android implementation of `flutter_tts`.
+
+## Usage
+
+This package is [endorsed][endorsed_link], which means you can simply use `flutter_tts`
+normally. This package will be automatically included in your app when you do.
+
+[endorsed_link]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin
+[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
+[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
\ No newline at end of file
diff --git a/packages/flutter_tts_android/analysis_options.yaml b/packages/flutter_tts_android/analysis_options.yaml
new file mode 100644
index 00000000..4c56a11a
--- /dev/null
+++ b/packages/flutter_tts_android/analysis_options.yaml
@@ -0,0 +1,4 @@
+include: package:very_good_analysis/analysis_options.yaml
+analyzer:
+ errors:
+ lines_longer_than_80_chars: ignore
diff --git a/android/.gitignore b/packages/flutter_tts_android/android/.gitignore
similarity index 100%
rename from android/.gitignore
rename to packages/flutter_tts_android/android/.gitignore
diff --git a/packages/flutter_tts_android/android/build.gradle b/packages/flutter_tts_android/android/build.gradle
new file mode 100644
index 00000000..1850753a
--- /dev/null
+++ b/packages/flutter_tts_android/android/build.gradle
@@ -0,0 +1,61 @@
+group = "com.tundralabs.fluttertts"
+version = "1.0-SNAPSHOT"
+
+buildscript {
+ ext.kotlin_version = "2.1.0"
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath("com.android.tools.build:gradle:8.9.1")
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+apply plugin: "com.android.library"
+apply plugin: "kotlin-android"
+
+android {
+ namespace = "m.tundralabs.fluttertts"
+
+ compileSdk = 36
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_11
+ }
+
+ defaultConfig {
+ minSdk = 24
+ }
+
+ dependencies {
+ testImplementation("org.jetbrains.kotlin:kotlin-test")
+ testImplementation("org.mockito:mockito-core:5.0.0")
+ }
+
+ testOptions {
+ unitTests.all {
+ useJUnitPlatform()
+
+ testLogging {
+ events "passed", "skipped", "failed", "standardOut", "standardError"
+ outputs.upToDateWhen {false}
+ showStandardStreams = true
+ }
+ }
+ }
+}
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/packages/flutter_tts_android/android/gradle/wrapper/gradle-wrapper.properties
similarity index 69%
rename from android/gradle/wrapper/gradle-wrapper.properties
rename to packages/flutter_tts_android/android/gradle/wrapper/gradle-wrapper.properties
index 4310b9d6..fc176a8e 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/flutter_tts_android/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
+# distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.13-all.zip
diff --git a/android/settings.gradle b/packages/flutter_tts_android/android/settings.gradle
similarity index 100%
rename from android/settings.gradle
rename to packages/flutter_tts_android/android/settings.gradle
diff --git a/packages/flutter_tts_android/android/src/main/AndroidManifest.xml b/packages/flutter_tts_android/android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..21c699c7
--- /dev/null
+++ b/packages/flutter_tts_android/android/src/main/AndroidManifest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/flutter_tts_android/android/src/main/kotlin/com/tundralabs/fluttertts/FlutterTtsPlugin.kt b/packages/flutter_tts_android/android/src/main/kotlin/com/tundralabs/fluttertts/FlutterTtsPlugin.kt
new file mode 100644
index 00000000..44f92023
--- /dev/null
+++ b/packages/flutter_tts_android/android/src/main/kotlin/com/tundralabs/fluttertts/FlutterTtsPlugin.kt
@@ -0,0 +1,1110 @@
+package com.tundralabs.fluttertts
+
+import android.content.ContentValues
+import android.content.Context
+import android.media.AudioAttributes
+import android.media.AudioFocusRequest
+import android.media.AudioManager
+import android.os.Build
+import android.os.Bundle
+import android.os.Environment
+import android.os.Handler
+import android.os.Looper
+import android.os.ParcelFileDescriptor
+import android.provider.MediaStore
+import android.speech.tts.TextToSpeech
+import android.speech.tts.UtteranceProgressListener
+import android.speech.tts.Voice
+import io.flutter.Log
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+import java.io.File
+import java.lang.reflect.Field
+import java.util.Locale
+import java.util.MissingResourceException
+import java.util.UUID
+
+typealias ResultCallback = (Result) -> Unit
+
+fun FlutterTtsErrorCode.toKtResult(): Result {
+ return Result.failure(FlutterError("FlutterTtsErrorCode.$raw", name))
+}
+
+/** FlutterTtsPlugin */
+class FlutterTtsPlugin : FlutterPlugin, TtsHostApi, AndroidTtsHostApi {
+ private val kTtsInitTimeOutMs: Long = 1000
+
+ private var handler: Handler? = null
+ private var speakResult: ResultCallback? = null
+ private var synthResult: ResultCallback? = null
+ private var awaitSpeakCompletion = false
+ private var speaking = false
+ private var awaitSynthCompletion = false
+ private var synth = false
+ private var context: Context? = null
+ private var tts: TextToSpeech? = null
+ private val tag = "TTS"
+ private val pendingMethodCalls = ArrayList()
+ private val utterances = HashMap()
+ private var bundle: Bundle? = null
+ private var silenceMs = 0
+ private var lastProgress = 0
+ private var currentText: String? = null
+ private var pauseText: String? = null
+ private var isPaused: Boolean = false
+ private var queueMode: Int = TextToSpeech.QUEUE_FLUSH
+ private var ttsStatus: Int? = null
+ private var selectedEngine: String? = null
+ private var engineResult: ResultCallback? = null
+ private var parcelFileDescriptor: ParcelFileDescriptor? = null
+ private var audioManager: AudioManager? = null
+ private var audioFocusRequest: AudioFocusRequest? = null
+
+ private var flutterApi: TtsFlutterApi? = null
+
+ companion object {
+ private const val SILENCE_PREFIX = "SIL_"
+ private const val SYNTHESIZE_TO_FILE_PREFIX = "STF_"
+ }
+
+ private fun initInstance(context: Context) {
+ this.context = context
+ handler = Handler(Looper.getMainLooper())
+ bundle = Bundle()
+
+ handler?.postDelayed(onInitTimeoutRunnable, kTtsInitTimeOutMs)
+ tts = TextToSpeech(context, onInitListenerWithoutCallback)
+ }
+
+ /** Android Plugin APIs */
+ override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+ flutterApi = TtsFlutterApi(binding.binaryMessenger)
+ initInstance(binding.applicationContext)
+ TtsHostApi.setUp(binding.binaryMessenger, this)
+ AndroidTtsHostApi.setUp(binding.binaryMessenger, this)
+ }
+
+ override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+ stopImpl()
+ tts!!.shutdown()
+ TtsHostApi.setUp(binding.binaryMessenger, null)
+ AndroidTtsHostApi.setUp(binding.binaryMessenger, null)
+ context = null
+ }
+
+ private val utteranceProgressListener: UtteranceProgressListener =
+ object : UtteranceProgressListener() {
+ override fun onStart(utteranceId: String) {
+ if (utteranceId.startsWith(SYNTHESIZE_TO_FILE_PREFIX)) {
+ handler?.post {
+ flutterApi?.onSynthStartCb { }
+ }
+ } else {
+ if (isPaused) {
+ handler?.post {
+ flutterApi?.onSpeakResumeCb { }
+ }
+ isPaused = false
+ } else {
+ Log.d(tag, "Utterance ID has started: $utteranceId")
+ handler?.post {
+ flutterApi?.onSpeakStartCb { }
+ }
+ }
+ }
+ if (Build.VERSION.SDK_INT < 26) {
+ onProgress(utteranceId, 0, utterances[utteranceId]!!.length)
+ }
+ }
+
+ override fun onDone(utteranceId: String) {
+ if (utteranceId.startsWith(SILENCE_PREFIX)) return
+ if (utteranceId.startsWith(SYNTHESIZE_TO_FILE_PREFIX)) {
+ closeParcelFileDescriptor(false)
+ Log.d(tag, "Utterance ID has completed: $utteranceId")
+ if (awaitSynthCompletion) {
+ synthCompletion(1)
+ }
+
+ handler?.post {
+ flutterApi?.onSynthCompleteCb { }
+ }
+ } else {
+ Log.d(tag, "Utterance ID has completed: $utteranceId")
+ if (awaitSpeakCompletion && queueMode == TextToSpeech.QUEUE_FLUSH) {
+ speakCompletion(1)
+ }
+
+ handler?.post {
+ flutterApi?.onSpeakCompleteCb { }
+ }
+ }
+ lastProgress = 0
+ pauseText = null
+ utterances.remove(utteranceId)
+ releaseAudioFocus()
+ }
+
+ override fun onStop(utteranceId: String, interrupted: Boolean) {
+ Log.d(
+ tag, "Utterance ID has been stopped: $utteranceId. Interrupted: $interrupted"
+ )
+ if (awaitSpeakCompletion) {
+ speaking = false
+ }
+ if (isPaused) {
+ handler?.post {
+ flutterApi?.onSpeakPauseCb { }
+ }
+ } else {
+ handler?.post {
+ flutterApi?.onSpeakCancelCb { }
+ }
+ }
+ releaseAudioFocus()
+ }
+
+ private fun onProgress(utteranceId: String?, startAt: Int, endAt: Int) {
+ if (utteranceId != null && !utteranceId.startsWith(SYNTHESIZE_TO_FILE_PREFIX)) {
+ val text = utterances[utteranceId]
+ if (text != null) {
+ var data = TtsProgress(
+ text = text,
+ start = startAt.toLong(),
+ end = endAt.toLong(),
+ word = text.substring(startAt, endAt)
+ )
+
+ handler?.post {
+ flutterApi?.onSpeakProgressCb(data) { }
+ }
+ }
+ }
+ }
+
+ // Requires Android 26 or later
+ override fun onRangeStart(utteranceId: String, startAt: Int, endAt: Int, frame: Int) {
+ if (!utteranceId.startsWith(SYNTHESIZE_TO_FILE_PREFIX)) {
+ lastProgress = startAt
+ super.onRangeStart(utteranceId, startAt, endAt, frame)
+ onProgress(utteranceId, startAt, endAt)
+ }
+ }
+
+ @Deprecated("Deprecated in Java")
+ override fun onError(utteranceId: String) {
+ if (utteranceId.startsWith(SYNTHESIZE_TO_FILE_PREFIX)) {
+ closeParcelFileDescriptor(true)
+ if (awaitSynthCompletion) {
+ synth = false
+ }
+
+ handler?.post {
+ flutterApi?.onSynthErrorCb("Error from TextToSpeech (synth)") {}
+ }
+ } else {
+ if (awaitSpeakCompletion) {
+ speaking = false
+ }
+
+ handler?.post {
+ flutterApi?.onSpeakErrorCb("Error from TextToSpeech (speak)") {}
+ }
+ }
+ releaseAudioFocus()
+ }
+
+ override fun onError(utteranceId: String, errorCode: Int) {
+ if (utteranceId.startsWith(SYNTHESIZE_TO_FILE_PREFIX)) {
+ closeParcelFileDescriptor(true)
+ if (awaitSynthCompletion) {
+ synth = false
+ }
+
+ handler?.post {
+ flutterApi?.onSynthErrorCb("Error from TextToSpeech (synth) - $errorCode") {}
+ }
+ } else {
+ if (awaitSpeakCompletion) {
+ speaking = false
+ }
+ handler?.post {
+ flutterApi?.onSpeakErrorCb("Error from TextToSpeech (speak) - $errorCode") {}
+ }
+ }
+ }
+ }
+
+ fun speakCompletion(success: Int) {
+ speaking = false
+ handler!!.post {
+ speakResult?.invoke(Result.success(TtsResult(success != 0)))
+ speakResult = null
+ }
+ }
+
+ fun synthCompletion(success: Int) {
+ synth = false
+ handler!!.post {
+ synthResult?.invoke(Result.success(TtsResult(success != 0)))
+ synthResult = null
+ }
+ }
+
+ private val onInitListenerWithCallback: TextToSpeech.OnInitListener =
+ TextToSpeech.OnInitListener { status ->
+ handler?.removeCallbacks(onInitTimeoutRunnable)
+ // Handle pending method calls (sent while TTS was initializing)
+ synchronized(this@FlutterTtsPlugin) {
+ ttsStatus = status
+ for (call in pendingMethodCalls) {
+ call.run()
+ }
+ pendingMethodCalls.clear()
+ }
+
+ if (status == TextToSpeech.SUCCESS) {
+ tts!!.setOnUtteranceProgressListener(utteranceProgressListener)
+ try {
+ val locale: Locale = tts!!.defaultVoice.locale
+ if (isLanguageAvailableImpl(locale)) {
+ tts!!.language = locale
+ }
+ } catch (e: NullPointerException) {
+ Log.e(tag, "getDefaultLocale: " + e.message)
+ } catch (e: IllegalArgumentException) {
+ Log.e(tag, "getDefaultLocale: " + e.message)
+ }
+
+ engineResult?.invoke(Result.success(TtsResult(true)))
+ } else {
+ engineResult?.invoke(
+ FlutterTtsErrorCode.TTS_NOT_AVAILABLE.toKtResult()
+ )
+ }
+ //engineResult = null
+ }
+
+ private val onInitTimeoutRunnable = Runnable {
+ Log.e("TTS", "TTS init timeout")
+
+ engineResult?.invoke(FlutterTtsErrorCode.TTS_INIT_TIMEOUT.toKtResult())
+
+ ttsStatus = TextToSpeech.ERROR
+ for (call in pendingMethodCalls) {
+ call.run()
+ }
+ pendingMethodCalls.clear()
+ }
+
+ private val onInitListenerWithoutCallback: TextToSpeech.OnInitListener =
+ TextToSpeech.OnInitListener { status ->
+ handler?.removeCallbacks(onInitTimeoutRunnable)
+ // Handle pending method calls (sent while TTS was initializing)
+ synchronized(this@FlutterTtsPlugin) {
+ ttsStatus = status
+ for (call in pendingMethodCalls) {
+ call.run()
+ }
+ pendingMethodCalls.clear()
+ }
+
+ if (status == TextToSpeech.SUCCESS) {
+ tts!!.setOnUtteranceProgressListener(utteranceProgressListener)
+ try {
+ val locale: Locale = tts!!.defaultVoice.locale
+ if (isLanguageAvailableImpl(locale)) {
+ tts!!.language = locale
+ }
+ } catch (e: NullPointerException) {
+ Log.e(tag, "getDefaultLocale: " + e.message)
+ } catch (e: IllegalArgumentException) {
+ Log.e(tag, "getDefaultLocale: " + e.message)
+ }
+ } else {
+ Log.e(tag, "Failed to initialize TextToSpeech with status: $status")
+ }
+ }
+
+ private fun setSpeechRateImpl(rate: Float) {
+ tts!!.setSpeechRate(rate)
+ }
+
+ private fun isLanguageAvailableImpl(locale: Locale?): Boolean {
+ return tts!!.isLanguageAvailable(locale) >= TextToSpeech.LANG_AVAILABLE
+ }
+
+ private fun areLanguagesInstalledImpl(languages: List): Map {
+ val result: MutableMap = HashMap()
+ for (language in languages) {
+ result[language] = isLanguageInstalledImpl(language)
+ }
+ return result
+ }
+
+ private fun isLanguageInstalledImpl(language: String?): Boolean {
+ val locale: Locale = Locale.forLanguageTag(language!!)
+ if (isLanguageAvailableImpl(locale)) {
+ var voiceToCheck: Voice? = null
+ for (v in tts!!.voices) {
+ if (v.locale == locale && !v.isNetworkConnectionRequired) {
+ voiceToCheck = v
+ break
+ }
+ }
+ if (voiceToCheck != null) {
+ val features: Set = voiceToCheck.features
+ return (!features.contains(TextToSpeech.Engine.KEY_FEATURE_NOT_INSTALLED))
+ }
+ }
+ return false
+ }
+
+ private fun setEngineImpl(engine: String?, result: ResultCallback) {
+ ttsStatus = null
+ selectedEngine = engine
+ engineResult = result
+
+ handler?.postDelayed(onInitTimeoutRunnable, kTtsInitTimeOutMs)
+ tts = TextToSpeech(context, onInitListenerWithCallback, engine)
+ }
+
+ private fun setVoiceImpl(
+ voice: com.tundralabs.fluttertts.Voice, callback: (Result) -> Unit
+ ) {
+ for (ttsVoice in tts!!.voices) {
+ if (ttsVoice.name == voice.name && ttsVoice.locale.toLanguageTag() == voice.locale) {
+ tts!!.voice = ttsVoice
+ callback(Result.success(TtsResult(true)))
+ return
+ }
+ }
+ Log.d(tag, "Voice name not found: $voice")
+ callback(Result.success(TtsResult(false)))
+ }
+
+ private fun clearVoiceImpl(callback: ResultCallback) {
+ tts!!.voice = tts!!.defaultVoice
+ callback(Result.success(TtsResult(true)))
+ }
+
+ private fun setVolumeImpl(volume: Float, callback: ResultCallback) {
+ if (volume in (0.0f..1.0f)) {
+ bundle!!.putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, volume)
+ callback(Result.success(TtsResult(true)))
+ } else {
+ Log.d(tag, "Invalid volume $volume value - Range is from 0.0 to 1.0")
+ callback(Result.success(TtsResult(false)))
+ }
+ }
+
+ private fun setPitchImpl(pitch: Float, callback: ResultCallback) {
+ if (pitch in (0.5f..2.0f)) {
+ tts!!.setPitch(pitch)
+ callback(Result.success(TtsResult(true)))
+ } else {
+ Log.d(tag, "Invalid pitch $pitch value - Range is from 0.5 to 2.0")
+ callback(Result.success(TtsResult(false)))
+ }
+ }
+
+ private fun getVoicesImpl(result: ResultCallback>) {
+ val voices = ArrayList()
+ try {
+ for (voice in tts!!.voices) {
+ voices.add(readVoiceProperties(voice))
+ }
+ result(Result.success(voices))
+ } catch (e: NullPointerException) {
+ Log.d(tag, "getVoices: " + e.message)
+ result(Result.success(voices))
+ }
+ }
+
+ private fun getLanguagesImpl(result: ResultCallback>) {
+ val locales = ArrayList()
+ try {
+ // While this method was introduced in API level 21, it seems that it
+ // has not been implemented in the speech service side until API Level 23.
+ for (locale in tts!!.availableLanguages) {
+ locales.add(locale.toLanguageTag())
+ }
+ } catch (e: MissingResourceException) {
+ Log.d(tag, "getLanguages: " + e.message)
+ } catch (e: NullPointerException) {
+ Log.d(tag, "getLanguages: " + e.message)
+ }
+ result(Result.success(locales))
+ }
+
+ private fun getEnginesImpl(result: ResultCallback>) {
+ val engines = ArrayList()
+ try {
+ for (engineInfo in tts!!.engines) {
+ engines.add(engineInfo.name)
+ }
+ } catch (e: Exception) {
+ Log.d(tag, "getEngines: " + e.message)
+ }
+ result(Result.success(engines))
+ }
+
+ private fun getDefaultEngineImpl(result: ResultCallback) {
+ val defaultEngine: String? = tts!!.defaultEngine
+ result(Result.success(defaultEngine))
+ }
+
+ private fun getDefaultVoiceImpl(result: ResultCallback) {
+ val defaultVoice: Voice? = tts!!.defaultVoice
+ var voice: com.tundralabs.fluttertts.Voice? = null
+ if (defaultVoice != null) {
+ voice = readVoiceProperties(defaultVoice)
+ }
+ result(Result.success(voice))
+ }
+
+ // Add voice properties into the voice map
+ fun readVoiceProperties(voice: Voice): com.tundralabs.fluttertts.Voice {
+ return Voice(
+ voice.name, voice.locale.toLanguageTag(), null, qualityToString(voice.quality), null
+ )
+ }
+
+ // Function to map quality integer to the constant name
+ fun qualityToString(quality: Int): String {
+ return when (quality) {
+ Voice.QUALITY_VERY_HIGH -> "very high"
+ Voice.QUALITY_HIGH -> "high"
+ Voice.QUALITY_NORMAL -> "normal"
+ Voice.QUALITY_LOW -> "low"
+ Voice.QUALITY_VERY_LOW -> "very low"
+ else -> "unknown"
+ }
+ }
+
+ private fun getSpeechRateValidRangeImpl(result: ResultCallback) {
+ // Valid values available in the android documentation.
+ // https://developer.android.com/reference/android/speech/tts/TextToSpeech#setSpeechRate(float)
+ // To make the FlutterTts API consistent across platforms,
+ // we map Android 1.0 to flutter 0.5 and so on.
+ val data = TtsRateValidRange(
+ 0.0, 0.5, 1.5, TtsPlatform.ANDROID
+ )
+ result(Result.success(data))
+ }
+
+ private fun speakImpl(text: String, focus: Boolean): Boolean {
+ val uuid: String = UUID.randomUUID().toString()
+ utterances[uuid] = text
+ return if (ismServiceConnectionUsable(tts)) {
+ if (focus) {
+ requestAudioFocus()
+ }
+
+ if (silenceMs > 0) {
+ tts!!.playSilentUtterance(
+ silenceMs.toLong(), TextToSpeech.QUEUE_FLUSH, SILENCE_PREFIX + uuid
+ )
+ tts!!.speak(text, TextToSpeech.QUEUE_ADD, bundle, uuid) == 0
+ } else {
+ tts!!.speak(text, queueMode, bundle, uuid) == 0
+ }
+ } else {
+ ttsStatus = null
+ handler?.postDelayed(onInitTimeoutRunnable, kTtsInitTimeOutMs)
+ tts = TextToSpeech(context, onInitListenerWithoutCallback, selectedEngine)
+ false
+ }
+ }
+
+ private fun stopImpl() {
+ if (awaitSynthCompletion) synth = false
+ if (awaitSpeakCompletion) speaking = false
+ tts!!.stop()
+ }
+
+ private val maxSpeechInputLength: Int
+ get() = TextToSpeech.getMaxSpeechInputLength()
+
+ private fun closeParcelFileDescriptor(isError: Boolean) {
+ if (this.parcelFileDescriptor != null) {
+ if (isError) {
+ this.parcelFileDescriptor!!.closeWithError("Error synthesizing TTS to file")
+ } else {
+ this.parcelFileDescriptor!!.close()
+ }
+ }
+ }
+
+ private fun synthesizeToFileImpl(text: String, fileName: String, isFullPath: Boolean) {
+ val fullPath: String
+ val uuid: String = UUID.randomUUID().toString()
+ bundle!!.putString(
+ TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, SYNTHESIZE_TO_FILE_PREFIX + uuid
+ )
+
+ val result: Int = if (isFullPath) {
+ val file = File(fileName)
+ fullPath = file.path
+
+ tts!!.synthesizeToFile(text, bundle!!, file, SYNTHESIZE_TO_FILE_PREFIX + uuid)
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ val resolver = this.context?.contentResolver
+ val contentValues = ContentValues().apply {
+ put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
+ put(MediaStore.MediaColumns.MIME_TYPE, "audio/wav")
+ put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_MUSIC)
+ }
+ val uri = resolver?.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, contentValues)
+ this.parcelFileDescriptor = resolver?.openFileDescriptor(uri!!, "rw")
+ fullPath = uri?.path + File.separatorChar + fileName
+
+ tts!!.synthesizeToFile(
+ text, bundle!!, parcelFileDescriptor!!, SYNTHESIZE_TO_FILE_PREFIX + uuid
+ )
+ } else {
+ val musicDir =
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)
+ val file = File(musicDir, fileName)
+ fullPath = file.path
+
+ tts!!.synthesizeToFile(text, bundle!!, file, SYNTHESIZE_TO_FILE_PREFIX + uuid)
+ }
+
+ if (result == TextToSpeech.SUCCESS) {
+ Log.d(tag, "Successfully created file : $fullPath")
+ } else {
+ Log.d(tag, "Failed creating file : $fullPath")
+ }
+ }
+
+ private fun ismServiceConnectionUsable(tts: TextToSpeech?): Boolean {
+ var isBindConnection = true
+ if (tts == null) {
+ return false
+ }
+ val fields: Array = tts.javaClass.declaredFields
+ for (j in fields.indices) {
+ fields[j].isAccessible = true
+ if ("mServiceConnection" == fields[j].name && "android.speech.tts.TextToSpeech\$Connection" == fields[j].type.name) {
+ try {
+ if (fields[j][tts] == null) {
+ isBindConnection = false
+ Log.e(tag, "*******TTS -> mServiceConnection == null*******")
+ }
+ } catch (e: IllegalArgumentException) {
+ e.printStackTrace()
+ } catch (e: IllegalAccessException) {
+ e.printStackTrace()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+ return isBindConnection
+ }
+
+ // Method to set AudioAttributes for navigation usage
+ private fun setAudioAttributesForNavigationImpl() {
+ if (tts != null) {
+ val audioAttributes = AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH).build()
+ tts!!.setAudioAttributes(audioAttributes)
+ }
+ }
+
+ private fun requestAudioFocus() {
+ audioManager = context?.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ audioFocusRequest =
+ AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
+ .setOnAudioFocusChangeListener { /* opcional para monitorar mudanças de foco */ }
+ .build()
+ audioManager?.requestAudioFocus(audioFocusRequest!!)
+ } else {
+ audioManager?.requestAudioFocus(
+ null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
+ )
+ }
+ }
+
+ private fun releaseAudioFocus() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ audioFocusRequest?.let { audioManager?.abandonAudioFocusRequest(it) }
+ } else {
+ audioManager?.abandonAudioFocus(null)
+ }
+ }
+
+ override fun speak(
+ text: String, forceFocus: Boolean, callback: (Result) -> Unit
+ ) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { speak(text, forceFocus, callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ } else if (ttsStatus != TextToSpeech.SUCCESS) {
+ callback(
+ FlutterTtsErrorCode.TTS_NOT_AVAILABLE.toKtResult()
+ )
+ return
+ }
+ }
+
+ if (pauseText == null) {
+ pauseText = text
+ currentText = pauseText!!
+ }
+
+ if (isPaused) {
+ // Ensure the text hasn't changed
+ if (currentText != text) {
+ pauseText = text
+ currentText = pauseText!!
+ lastProgress = 0
+ }
+ }
+ if (speaking) {
+ // If TTS is set to queue mode, allow the utterance to be queued up rather than discarded
+ if (queueMode == TextToSpeech.QUEUE_FLUSH) {
+ callback(Result.success(TtsResult(false)))
+ return
+ }
+ }
+ val b = speakImpl(text, forceFocus)
+ if (!b) {
+ synchronized(this@FlutterTtsPlugin) {
+ speak(text, forceFocus, callback)
+ }
+ return
+ }
+ // Only use await speak completion if queueMode is set to QUEUE_FLUSH
+ if (awaitSpeakCompletion && queueMode == TextToSpeech.QUEUE_FLUSH) {
+ speaking = true
+ speakResult = callback
+ } else {
+ callback(Result.success(TtsResult(true)))
+ }
+
+ }
+
+ override fun pause(callback: (Result) -> Unit) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { pause(callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ } else if (ttsStatus != TextToSpeech.SUCCESS) {
+ callback(
+ FlutterTtsErrorCode.TTS_NOT_AVAILABLE.toKtResult()
+ )
+ return
+ }
+ }
+
+ isPaused = true
+ if (pauseText != null) {
+ pauseText = pauseText!!.substring(lastProgress)
+ }
+ stopImpl()
+ callback(Result.success(TtsResult(true)))
+ if (speakResult != null) {
+ speakResult?.invoke(Result.success(TtsResult(false)))
+ speakResult = null
+ }
+ }
+
+ override fun stop(callback: (Result) -> Unit) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { stop(callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ } else if (ttsStatus != TextToSpeech.SUCCESS) {
+ callback(
+ FlutterTtsErrorCode.TTS_NOT_AVAILABLE.toKtResult()
+ )
+ return
+ }
+ }
+
+ isPaused = false
+ pauseText = null
+ stopImpl()
+ lastProgress = 0
+ callback(Result.success(TtsResult(true)))
+ if (speakResult != null) {
+ speakResult?.invoke(Result.success(TtsResult(false)))
+ speakResult = null
+ }
+ }
+
+ override fun setSpeechRate(
+ rate: Double, callback: (Result) -> Unit
+ ) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { setSpeechRate(rate, callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ } else if (ttsStatus != TextToSpeech.SUCCESS) {
+ callback(FlutterTtsErrorCode.TTS_NOT_AVAILABLE.toKtResult())
+ return
+ }
+ }
+
+ // To make the FlutterTts API consistent across platforms,
+ // Android 1.0 is mapped to flutter 0.5.
+ setSpeechRateImpl(rate.toFloat() * 2.0f)
+ callback(Result.success(TtsResult(true)))
+ }
+
+ override fun setVolume(
+ volume: Double, callback: (Result) -> Unit
+ ) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { setVolume(volume, callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ } else if (ttsStatus != TextToSpeech.SUCCESS) {
+ callback(FlutterTtsErrorCode.TTS_NOT_AVAILABLE.toKtResult())
+ return
+ }
+ }
+
+ setVolumeImpl(volume.toFloat(), callback)
+ }
+
+ override fun setPitch(
+ pitch: Double, callback: (Result) -> Unit
+ ) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { setPitch(pitch, callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ } else if (ttsStatus != TextToSpeech.SUCCESS) {
+ callback(FlutterTtsErrorCode.TTS_NOT_AVAILABLE.toKtResult())
+ return
+ }
+ }
+
+ setPitchImpl(pitch.toFloat(), callback)
+ }
+
+ override fun setVoice(
+ voice: com.tundralabs.fluttertts.Voice, callback: (Result) -> Unit
+ ) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { setVoice(voice, callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ } else if (ttsStatus != TextToSpeech.SUCCESS) {
+ callback(FlutterTtsErrorCode.TTS_NOT_AVAILABLE.toKtResult())
+ return
+ }
+ }
+
+ setVoiceImpl(voice, callback)
+ }
+
+ override fun clearVoice(callback: (Result) -> Unit) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { clearVoice(callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ } else if (ttsStatus != TextToSpeech.SUCCESS) {
+ callback(FlutterTtsErrorCode.TTS_NOT_AVAILABLE.toKtResult())
+ return
+ }
+ }
+
+ clearVoiceImpl(callback)
+ }
+
+ override fun awaitSpeakCompletion(
+ awaitCompletion: Boolean, callback: (Result) -> Unit
+ ) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { awaitSpeakCompletion(awaitCompletion, callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ }
+ }
+
+ awaitSpeakCompletion = awaitCompletion
+ callback(Result.success(TtsResult(true)))
+ }
+
+ override fun getLanguages(callback: (Result>) -> Unit) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { getLanguages(callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ } else if (ttsStatus != TextToSpeech.SUCCESS) {
+ callback(FlutterTtsErrorCode.TTS_NOT_AVAILABLE.toKtResult())
+ return
+ }
+ }
+
+ getLanguagesImpl(callback)
+ }
+
+ override fun getVoices(callback: (Result>) -> Unit) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { getVoices(callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ } else if (ttsStatus != TextToSpeech.SUCCESS) {
+ callback(FlutterTtsErrorCode.TTS_NOT_AVAILABLE.toKtResult())
+ return
+ }
+ }
+
+ getVoicesImpl(callback)
+ }
+
+ override fun awaitSynthCompletion(
+ awaitCompletion: Boolean, callback: (Result) -> Unit
+ ) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { awaitSynthCompletion(awaitCompletion, callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ }
+ }
+
+ awaitSynthCompletion = awaitCompletion
+ callback(Result.success(TtsResult(true)))
+ }
+
+ override fun getMaxSpeechInputLength(callback: (Result) -> Unit) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { getMaxSpeechInputLength(callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ }
+ }
+
+ callback(Result.success(maxSpeechInputLength.toLong()))
+ }
+
+ override fun setEngine(
+ engine: String, callback: (Result) -> Unit
+ ) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { setEngine(engine, callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ }
+ }
+
+ setEngineImpl(engine, callback)
+ }
+
+ override fun getEngines(callback: (Result>) -> Unit) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { getEngines(callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ }
+ }
+
+ getEnginesImpl(callback)
+ }
+
+ override fun getDefaultEngine(callback: (Result) -> Unit) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { getDefaultEngine(callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ } else if (ttsStatus != TextToSpeech.SUCCESS) {
+ callback(FlutterTtsErrorCode.TTS_NOT_AVAILABLE.toKtResult())
+ return
+ }
+ }
+
+ getDefaultEngineImpl(callback)
+ }
+
+ override fun getDefaultVoice(callback: (Result) -> Unit) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { getDefaultVoice(callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ } else if (ttsStatus != TextToSpeech.SUCCESS) {
+ callback(FlutterTtsErrorCode.TTS_NOT_AVAILABLE.toKtResult())
+ return
+ }
+ }
+
+ getDefaultVoiceImpl(callback)
+ }
+
+ override fun synthesizeToFile(
+ text: String, fileName: String, isFullPath: Boolean, callback: (Result) -> Unit
+ ) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall =
+ Runnable { synthesizeToFile(text, fileName, isFullPath, callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ } else if (ttsStatus != TextToSpeech.SUCCESS) {
+ callback(FlutterTtsErrorCode.TTS_NOT_AVAILABLE.toKtResult())
+ return
+ }
+ }
+
+ if (synth) {
+ callback(Result.success(TtsResult(false)))
+ return
+ }
+ synthesizeToFileImpl(text, fileName, isFullPath)
+ if (awaitSynthCompletion) {
+ synth = true
+ synthResult = callback
+ } else {
+ callback(Result.success(TtsResult(true)))
+ }
+ }
+
+ override fun isLanguageInstalled(
+ language: String, callback: (Result) -> Unit
+ ) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { isLanguageInstalled(language, callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ } else if (ttsStatus != TextToSpeech.SUCCESS) {
+ callback(FlutterTtsErrorCode.TTS_NOT_AVAILABLE.toKtResult())
+ return
+ }
+ }
+
+ callback(Result.success(isLanguageInstalledImpl(language)))
+ }
+
+ override fun isLanguageAvailable(
+ language: String, callback: (Result) -> Unit
+ ) {
+ synchronized(this@FlutterTtsPlugin) {
+ if (ttsStatus == null) {
+ // Suspend method call until the TTS engine is ready
+ val suspendedCall = Runnable { isLanguageAvailable(language, callback); }
+ pendingMethodCalls.add(suspendedCall)
+ return
+ } else if (ttsStatus != TextToSpeech.SUCCESS) {
+ callback(FlutterTtsErrorCode.TTS_NOT_AVAILABLE.toKtResult())
+ return
+ }
+ }
+
+ val locale: Locale = Locale.forLanguageTag(language)
+ callback(Result.success(isLanguageAvailableImpl(locale)))
+ }
+
+ override fun areLanguagesInstalled(
+ languages: List, callback: (Result