diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/android/src/main/AndroidManifest.xml b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/android/src/main/AndroidManifest.xml
index 9a40236b..e97615e1 100644
--- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/android/src/main/AndroidManifest.xml
+++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/android/src/main/AndroidManifest.xml
@@ -1,3 +1,8 @@
+
+
+
+
+
diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/android/src/main/java/NativeTTSPlugin.kt b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/android/src/main/java/NativeTTSPlugin.kt
index 8147c227..af9b5c09 100644
--- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/android/src/main/java/NativeTTSPlugin.kt
+++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/android/src/main/java/NativeTTSPlugin.kt
@@ -3,6 +3,7 @@ package com.readest.native_tts
import android.os.Bundle
import android.app.Activity
import android.content.Context
+import android.provider.Settings
import android.speech.tts.TextToSpeech
import android.speech.tts.UtteranceProgressListener
import android.speech.tts.Voice
@@ -97,7 +98,11 @@ class NativeTTSPlugin(private val activity: Activity) : Plugin(activity) {
private suspend fun initializeTTS(): Boolean = suspendCancellableCoroutine { continuation ->
try {
- textToSpeech = TextToSpeech(activity) { status ->
+ val preferredEngine = Settings.Secure.getString(
+ activity.contentResolver,
+ Settings.Secure.TTS_DEFAULT_SYNTH
+ )
+ textToSpeech = TextToSpeech(activity, { status ->
when (status) {
TextToSpeech.SUCCESS -> {
setupTTSListener()
@@ -111,7 +116,7 @@ class NativeTTSPlugin(private val activity: Activity) : Plugin(activity) {
continuation.resume(false) {}
}
}
- }
+ }, preferredEngine)
} catch (e: Exception) {
Log.e(TAG, "Exception during TTS initialization", e)
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
diff --git a/apps/readest-app/src/services/tts/EdgeTTSClient.ts b/apps/readest-app/src/services/tts/EdgeTTSClient.ts
index 9e8e02ae..5d127d18 100644
--- a/apps/readest-app/src/services/tts/EdgeTTSClient.ts
+++ b/apps/readest-app/src/services/tts/EdgeTTSClient.ts
@@ -264,7 +264,7 @@ export class EdgeTTSClient implements TTSClient {
const voicesGroup: TTSVoicesGroup = {
id: 'edge-tts',
name: 'Edge TTS',
- voices: filteredVoices,
+ voices: filteredVoices.sort(TTSUtils.sortVoicesFunc),
disabled: !this.initialized || filteredVoices.length === 0,
};
diff --git a/apps/readest-app/src/services/tts/NativeTTSClient.ts b/apps/readest-app/src/services/tts/NativeTTSClient.ts
index 1244acd7..148a6f16 100644
--- a/apps/readest-app/src/services/tts/NativeTTSClient.ts
+++ b/apps/readest-app/src/services/tts/NativeTTSClient.ts
@@ -14,12 +14,25 @@ type TTSEventPayload = {
const TTSEngines = {
default: 'System TTS',
+ msctts: 'Msc TTS',
mstrans: 'MSTrans TTS',
microsoft: 'Microsoft TTS',
+ bytedance: 'ByteDance TTS',
+ peiyinya: 'PeiYinYa TTS',
+ huoshan: 'HuoShan TTS',
+ sougou: 'Sougou TTS',
+ xiaomi: 'XiaoMi TTS',
+ bdetts: 'BDeTTS',
+ bdotts: 'BDoTTS',
+ vcstts: 'VcsTTS',
+ isstts: 'IssTTS',
+ xfpeiyin: 'XFPeiYin',
+ azure: 'Azure TTS',
edgetts: 'Edge TTS',
google: 'Google TTS',
gemini: 'Gemini TTS',
weread: 'WeRead TTS',
+ aispeech: 'Aispeech',
} as Record;
export class NativeTTSClient implements TTSClient {
@@ -189,7 +202,7 @@ export class NativeTTSClient implements TTSClient {
const defaultVoice = preferredVoice
? preferredVoice
: (await this.getVoices(lang))[0]?.voices[0] || null;
- return defaultVoice?.id || 'NOT_SET';
+ return defaultVoice?.id || '';
}
async pause() {
@@ -244,7 +257,6 @@ export class NativeTTSClient implements TTSClient {
const filteredVoices = voices.filter(
(v) => v.lang.startsWith(locale) || (lang === 'en' && ['en-US', 'en-GB'].includes(v.lang)),
);
-
const voiceGroups = new Map();
filteredVoices.forEach((voice) => {
const { name, lang } = voice;
@@ -271,7 +283,7 @@ export class NativeTTSClient implements TTSClient {
({
id: groupId,
name: TTSEngines[groupId] || groupId,
- voices: voices,
+ voices: voices.sort(TTSUtils.sortVoicesFunc),
disabled: !this.initialized || voices.length === 0,
}) as TTSVoicesGroup,
)
diff --git a/apps/readest-app/src/services/tts/TTSController.ts b/apps/readest-app/src/services/tts/TTSController.ts
index 98c85f83..db127ef2 100644
--- a/apps/readest-app/src/services/tts/TTSController.ts
+++ b/apps/readest-app/src/services/tts/TTSController.ts
@@ -61,6 +61,7 @@ export class TTSController extends EventTarget {
if (await this.ttsWebClient.init()) {
availableClients.push(this.ttsWebClient);
}
+ this.ttsClient = availableClients[0] || this.ttsWebClient;
const preferredClientName = TTSUtils.getPreferredClient();
if (preferredClientName) {
const preferredClient = availableClients.find(
@@ -70,9 +71,6 @@ export class TTSController extends EventTarget {
this.ttsClient = preferredClient;
}
}
- if (!this.ttsClient) {
- this.ttsClient = availableClients[0] || this.ttsWebClient;
- }
this.ttsWebVoices = await this.ttsWebClient.getAllVoices();
this.ttsEdgeVoices = await this.ttsEdgeClient.getAllVoices();
}
@@ -288,26 +286,6 @@ export class TTSController extends EventTarget {
const ttsNativeVoices = (await this.ttsNativeClient?.getVoices(lang)) ?? [];
const voicesGroups = [...ttsNativeVoices, ...ttsEdgeVoices, ...ttsWebVoices];
- voicesGroups.forEach((group) => {
- group.voices = group.voices.sort((a, b) => {
- const aRegion = a.lang.split('-')[1] || '';
- const bRegion = b.lang.split('-')[1] || '';
- if (aRegion === bRegion) {
- return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
- }
- if (aRegion === 'CN') return -1;
- if (bRegion === 'CN') return 1;
- if (aRegion === 'TW') return -1;
- if (bRegion === 'TW') return 1;
- if (aRegion === 'HK') return -1;
- if (bRegion === 'HK') return 1;
- if (aRegion === 'US') return -1;
- if (bRegion === 'US') return 1;
- if (aRegion === 'GB') return -1;
- if (bRegion === 'GB') return 1;
- return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
- });
- });
return voicesGroups;
}
diff --git a/apps/readest-app/src/services/tts/TTSUtils.ts b/apps/readest-app/src/services/tts/TTSUtils.ts
index f3bfc3c3..1cfe296b 100644
--- a/apps/readest-app/src/services/tts/TTSUtils.ts
+++ b/apps/readest-app/src/services/tts/TTSUtils.ts
@@ -1,3 +1,5 @@
+import { TTSVoice } from './types';
+
export class TTSUtils {
private static readonly LOCAL_STORAGE_KEY = 'ttsPreferredVoices';
private static readonly PREFERRED_CLIENT_KEY = 'preferredClient';
@@ -37,4 +39,23 @@ export class TTSUtils {
const storedPreferences = localStorage.getItem(this.LOCAL_STORAGE_KEY);
return storedPreferences ? JSON.parse(storedPreferences) : {};
}
+
+ static sortVoicesFunc(a: TTSVoice, b: TTSVoice): number {
+ const aRegion = a.lang.split('-')[1] || '';
+ const bRegion = b.lang.split('-')[1] || '';
+ if (aRegion === bRegion) {
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
+ }
+ if (aRegion === 'CN') return -1;
+ if (bRegion === 'CN') return 1;
+ if (aRegion === 'TW') return -1;
+ if (bRegion === 'TW') return 1;
+ if (aRegion === 'HK') return -1;
+ if (bRegion === 'HK') return 1;
+ if (aRegion === 'US') return -1;
+ if (bRegion === 'US') return 1;
+ if (aRegion === 'GB') return -1;
+ if (bRegion === 'GB') return 1;
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
+ }
}
diff --git a/apps/readest-app/src/services/tts/WebSpeechClient.ts b/apps/readest-app/src/services/tts/WebSpeechClient.ts
index bd9fbb15..fa58f383 100644
--- a/apps/readest-app/src/services/tts/WebSpeechClient.ts
+++ b/apps/readest-app/src/services/tts/WebSpeechClient.ts
@@ -270,7 +270,7 @@ export class WebSpeechClient implements TTSClient {
const voicesGroup: TTSVoicesGroup = {
id: 'web-speech-api',
name: 'Web TTS',
- voices,
+ voices: voices.sort(TTSUtils.sortVoicesFunc),
disabled: !this.initialized || voices.length === 0,
};
return [voicesGroup];