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];