From fd012f767ca818844e3c329685cdd7cf11216421 Mon Sep 17 00:00:00 2001 From: "info@pi-farm.de" Date: Wed, 11 Mar 2026 15:27:14 +0000 Subject: [PATCH] app/src/main/java/com/example/jarvis_stts/MainActivity.kt aktualisiert --- .../com/example/jarvis_stts/MainActivity.kt | 88 ++++++++++++------- 1 file changed, 56 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/example/jarvis_stts/MainActivity.kt b/app/src/main/java/com/example/jarvis_stts/MainActivity.kt index 32b594a..42be6d3 100644 --- a/app/src/main/java/com/example/jarvis_stts/MainActivity.kt +++ b/app/src/main/java/com/example/jarvis_stts/MainActivity.kt @@ -41,10 +41,26 @@ class MainActivity : AppCompatActivity(), RecognitionListener, TextToSpeech.OnIn private var availableVoices = mutableListOf() private var voiceNames = mutableListOf() + // Launcher für Google Spracherkennung + private val speechRecognizerLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + // WICHTIG: Nach der Google-Eingabe starten wir Vosk wieder + if (result.resultCode == RESULT_OK && result.data != null) { + val spokenText = result.data!!.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.get(0) ?: "" + tvStatus.text = "Ich: $spokenText" + webSocket?.send(spokenText) + } + + // Vosk wieder starten, nachdem Google fertig ist + startVosk() + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + // 1. UI initialisieren tvStatus = findViewById(R.id.tvStatus) etUrl = findViewById(R.id.etUrl) spinnerVoices = findViewById(R.id.spinnerVoices) @@ -53,6 +69,7 @@ class MainActivity : AppCompatActivity(), RecognitionListener, TextToSpeech.OnIn tts = TextToSpeech(this, this) + // 2. SharedPreferences (Server URL laden) val prefs = getSharedPreferences("JarvisPrefs", MODE_PRIVATE) etUrl.setText(prefs.getString("server_url", "")) @@ -65,10 +82,15 @@ class MainActivity : AppCompatActivity(), RecognitionListener, TextToSpeech.OnIn } btnSpeak.setOnClickListener { - voskService?.stop() // Vosk stoppen, wenn man manuell klickt + voskService?.stop() // Stoppe Wake-Word, wenn man manuell klickt startVoiceInput() } + // 3. Berechtigungen prüfen & Modell laden + checkPermissionsAndInit() + } + + private fun checkPermissionsAndInit() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO), 1) } else { @@ -76,7 +98,6 @@ class MainActivity : AppCompatActivity(), RecognitionListener, TextToSpeech.OnIn } } - // KORREKTUR: Damit Jarvis sofort nach der Erlaubnis startet override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == 1 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { @@ -85,18 +106,16 @@ class MainActivity : AppCompatActivity(), RecognitionListener, TextToSpeech.OnIn } private fun initVoskModel() { - // Der erste Name "model-de" MUSS exakt dem Ordnernamen in 'assets' entsprechen! + // "model-de" ist der Ordner in assets. "model" ist der Zielordner auf dem Handy. StorageService.unpack(this, "model-de", "model", { model: Model -> - Log.d("JARVIS", "Vosk Modell erfolgreich geladen!") voskModel = model + Log.d("JARVIS", "Modell erfolgreich geladen!") startVosk() }, { exception: IOException -> - Log.e("JARVIS", "Vosk Fehler beim Entpacken: ${exception.message}") - runOnUiThread { - Toast.makeText(this, "Modell Fehler: ${exception.message}", Toast.LENGTH_LONG).show() - } + Log.e("JARVIS", "Vosk Entpack-Fehler: ${exception.message}") + runOnUiThread { tvStatus.text = "Fehler: Modell nicht gefunden" } } ) } @@ -104,50 +123,44 @@ class MainActivity : AppCompatActivity(), RecognitionListener, TextToSpeech.OnIn private fun startVosk() { try { if (voskModel == null) return - // Falls noch ein alter Service läuft, sicherheitshalber stoppen + + // Alten Service sicherheitshalber beenden voskService?.stop() voskService?.shutdown() + // Wir horchen auf "computer" und "jarvis". val rec = Recognizer(voskModel, 16000.0f, "[\"computer\", \"jarvis\", \"[unk]\"]") voskService = SpeechService(rec, 16000.0f) voskService?.startListening(this) - runOnUiThread { tvStatus.text = "Bereit (Warte auf 'Computer')" } + + runOnUiThread { tvStatus.text = "Bereit (Warte auf 'Jarvis' oder 'Computer')" } } catch (e: Exception) { Log.e("JARVIS", "Vosk Start Fehler: ${e.message}") } } - // 1. Hilfsfunktion zum sauberen Filtern des Wortes - private fun extractText(json: String): String { - return json.substringAfter(": \"").substringBefore("\"") - } - + // --- Vosk RecognitionListener --- override fun onPartialResult(hypothesis: String) { val recognizedText = extractText(hypothesis) Log.d("JARVIS", "Vosk hört: $recognizedText") - // KORREKTUR: ignoreCase hinzugefügt für mehr Sicherheit + // Wake-Word Check if (recognizedText.contains("computer", true) || recognizedText.contains("jarvis", true)) { - voskService?.stop() + Log.d("JARVIS", "Wake-Word erkannt!") + voskService?.stop() // Stoppen, um Mikrofon für Google freizugeben startVoiceInput() } } - // 2. Im SpeechRecognizerLauncher - private val speechRecognizerLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { result -> - if (result.resultCode == RESULT_OK && result.data != null) { - val spokenText = result.data!!.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.get(0) ?: "" - tvStatus.text = "Ich: $spokenText" - webSocket?.send(spokenText) - } - - // KORREKTUR: Einfach die Funktion aufrufen, nicht über voskService - startVosk() + private fun extractText(json: String): String { + // Hilft, den Text aus dem JSON {"partial" : "..."} zu ziehen + return json.substringAfter(": \"").substringBefore("\"") + } + + override fun onResult(hypothesis: String) { + // Hier könnte man das finale Wort prüfen, falls Partial nicht reicht } - override fun onResult(hypothesis: String) {} override fun onFinalResult(hypothesis: String) {} override fun onError(e: Exception) { Log.e("JARVIS", "Vosk Error: ${e.message}") } override fun onTimeout() {} @@ -157,8 +170,14 @@ class MainActivity : AppCompatActivity(), RecognitionListener, TextToSpeech.OnIn val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM) putExtra(RecognizerIntent.EXTRA_LANGUAGE, "de-DE") + putExtra(RecognizerIntent.EXTRA_PROMPT, "Ich höre...") + } + try { + speechRecognizerLauncher.launch(intent) + } catch (e: Exception) { + Toast.makeText(this, "Google Spracheingabe nicht verfügbar", Toast.LENGTH_SHORT).show() + startVosk() // Falls Google scheitert, Vosk wieder an } - speechRecognizerLauncher.launch(intent) } private fun connectToServer(url: String) { @@ -203,7 +222,12 @@ class MainActivity : AppCompatActivity(), RecognitionListener, TextToSpeech.OnIn } private fun speakOut(text: String) { - tts.speak(text, TextToSpeech.QUEUE_FLUSH, null, "") + // Wir können hier Vosk stoppen, damit Jarvis sich nicht selbst hört + voskService?.stop() + tts.speak(text, TextToSpeech.QUEUE_FLUSH, null, "TTS_DONE") + + // Nach dem Sprechen müssen wir Vosk wieder starten. + // Das machen wir am besten über einen Listener (siehe unten). } override fun onDestroy() {