Android nach TTS

Noki99

Mitglied
Hey Leute,

ich mache gerade eine App, welche nach einer Sprachausgabe einen Text ändern soll. Die Sprachausgabe habe ich, bloß wie bekomme ich heraus, wann die nette Stimme fertig mit sprechen ist?

Hab schon gegoogelt, aber nix gefunden.

Danke :)
 

Noki99

Mitglied
Klaro:

Code:
package de.vollkanone.sprachhilfe;

import java.util.ArrayList;
import java.util.Locale;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity implements OnInitListener{

    Button btn_go;
    TextView tw;
   
    private TextToSpeech tts;
   
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
       
        tts = new TextToSpeech(this, this);
       
       
        btn_go = (Button) findViewById(R.id.btn_go);
       
        btn_go.setVisibility(View.INVISIBLE);
       
        tw = (TextView) findViewById(R.id.textView1);
       
       
        btn_go.setOnClickListener(new View.OnClickListener() {
           
            @Override
            public void onClick(View v) {
                button("btn_go");
            }
        });
       
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
   
    public void button(String btn) {
        if(btn.equalsIgnoreCase("btn_go")) {
            erkennen(this);
        }
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == SPEECH_RECOGNIZED) {
            if(data != null) {
               
                ArrayList<String> result = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
               
                if(!result.isEmpty()) {
                    tw.setText(result.get(0));
                    tts.speak(result.get(0), TextToSpeech.QUEUE_FLUSH, null);
                }
               
               
               
            }
        }
       
    }
/*
    public void onUtteranceCompleted (String utteranceId) {
        tw.setText("Fertig...");
    }
    */
   
   

    static public final int SPEECH_RECOGNIZED = 1;
   
    static public void erkennen(Activity activity) {
        Intent intent = new Intent();
        intent.setAction(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        activity.startActivityForResult(intent, SPEECH_RECOGNIZED);
    }


    @Override
    public void onInit(int status) {
       
        tts.setLanguage(Locale.GERMAN);
        tts.setSpeechRate((float) 1.5);
       
       
        btn_go.setVisibility(View.VISIBLE);
       
    }
   
   
}

Was die App bis jetzt macht: Man sagt ihr was und sie spricht es nach.

Was mein Ziel ist: Den Button ausblenden und wenn die nette Frau fertig ist mit sprechen wieder einblenden.

Danke :)
 

saftmeister

Nutze den Saft!
Ok, der Code hat geholfen, habe mit TTS noch nichts gemacht. Also, TextToSpeech bietet eine Methode setOnUtteranceCompletedListener() an, die jedoch seit API Level 15 deprecated ist und daher nicht mehr eingesetzt werden sollte. Statt dessen soll man laut API Docs die Methode setOnUtteranceProgressListener() verwenden.

Die abstrakte Klasse UtteranceProgressListener hat eine Methode onDone(), welche getriggert wird, wenn die nette Dame fertig mit schwafeln ist.

Du müsstest also deine eigene Klasse MyUtteranceProgressListener schreiben, welche die abstrake welche ableitet und die Methode onDone implementieren. Wenn du Objekte auf deiner Activity manipulieren willst, sollte MyUtteranceProgressListener einen Constructor anbieten, dem du entweder das Control oder die komplette Activity übergeben kannst. Denke auch daran, dass nur der Eigentümer-Thread Controls bzw. deren Verhalten der Activity steuern kann und du ggf. ein Callback-Thread bauen musst, wie auch am Ende dieses Threads beschrieben:

http://www.tutorials.de/threads/json-code-von-webseite-in-java-strings-android.398210/
 

Noki99

Mitglied
Mhm ok, um ehrlich zu sein versteh ich das nicht ganz.

Also eine neue Klasse namens "MyUtteranceProgressListener" kann ich noch machen ^^.
Wie genau bekomme ich da onDone rein, bei mir zeigt er iwi an die existiert nicht. Muss ich vorher noch setOnUtteranceProgressListener() irgendwie verwenden? Dass mit dem abändern würde ich glaub ich hinbekommen.

Danke :)
 

saftmeister

Nutze den Saft!
Ohne dir zu nahe treten zu wollen, aber du solltest dir ein Buch für die Basics besorgen. "Java ist auch eine Insel" ist kostenneutral im Internet auch als PDF verfügbar. Dann sind die Standard-Begriffe wie "ableiten" keine Hürde mehr und der folgende Code ergibt sich quasi von selbst.

Java:
package de.vollkanone.sprachhilfe;

import android.app.Activity;
import android.speech.tts.UtteranceProgressListener;

public class MyUtteranceProgressListener extends UtteranceProgressListener
{
  private MainActivity mainActivity;

  public MyUtteranceProgressListener(MainActivity mainActivity)
  {
    this.mainActivity = mainActivity;
  }

  @Override
  public void onStart(String utteranceId)
  {
    // Was soll passieren, wenn die nette Dame mit schwafeln anfängt ...
    this.mainActivity.setGoVisible(false);
  }

  @Override
  public void onDone(String utteranceId)
  {
    // Was soll passieren, wenn die nette Dame mit schwafeln fertig ist ...
    this.mainActivity.setGoVisible(true);
  }

  @Override
  public void onError(String utteranceId)
  {
    // Was soll passieren, wenn ein Fehler aufgetreten ist...
    this.mainActivity.setGoVisible(true);
  }
}

In deiner Main-Activity dann halt die onInit()-Methode erweitern und eine neue Methode setButtonEnabled() einbauen:

Java:
@Override
    public void onInit(int status) {
       
        tts.setLanguage(Locale.GERMAN);
        tts.setSpeechRate((float) 1.5);
        tts.setOnUtteranceProgressListener(new MyUtteranceProgressListener(this)); // Callback für TTS registrieren
       
        this.setGoButtonVisible(true);
    }

    // Neue Methode, um das Verhalten des Buttons zu steuern
    public void setGoVisible(boolean visibility)
    {
        // TODO: Eigentümer-Thread!!!
        btn_go.setVisibility( visibility ? View.VISIBLE : View.INVISIBLE );
    }

Ungetestet!
 

Noki99

Mitglied
Das Buch wollte ich mir eh hohlen, wird wohl Zeit dass ich mir das Ding bestell.

Habs jetzt so gemacht:

MainActivity.java:
Code:
package de.vollkanone.sprachhilfe;

import java.util.ArrayList;
import java.util.Locale;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity implements OnInitListener{

    Button btn_go;
    TextView tw;
   
    private TextToSpeech tts;
   
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
       
        tts = new TextToSpeech(this, this);
       
       
        btn_go = (Button) findViewById(R.id.btn_go);
       
        btn_go.setVisibility(View.INVISIBLE);
       
        tw = (TextView) findViewById(R.id.textView1);
       
       
        btn_go.setOnClickListener(new View.OnClickListener() {
           
            @Override
            public void onClick(View v) {
                button("btn_go");
            }
        });
       
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
   
    public void button(String btn) {
        if(btn.equalsIgnoreCase("btn_go")) {
            erkennen(this);
        }
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == SPEECH_RECOGNIZED) {
            if(data != null) {
               
                ArrayList<String> result = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
               
                if(!result.isEmpty()) {
                    tw.setText(result.get(0));
                    tts.speak(result.get(0), TextToSpeech.QUEUE_FLUSH, null);
                }
            }
        }
    }
   
    static public final int SPEECH_RECOGNIZED = 1;
   
    static public void erkennen(Activity activity) {
        Intent intent = new Intent();
        intent.setAction(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        activity.startActivityForResult(intent, SPEECH_RECOGNIZED);
    }

    @Override
    public void onInit(int status) {
       
        tts.setLanguage(Locale.GERMAN);
        tts.setSpeechRate((float) 1.5);
       
        btn_go.setVisibility(View.VISIBLE);
       
        tts.setOnUtteranceProgressListener(new MyUtteranceProgressListener(this));
       
    }
   
    public void setGoVisible(boolean visibility)
    {
        btn_go.setVisibility( visibility ? View.VISIBLE : View.INVISIBLE );
    }
     
}


Und die MyUtteranceProgressListener.java:

Code:
package de.vollkanone.sprachhilfe;

import android.speech.tts.UtteranceProgressListener;

public class MyUtteranceProgressListener extends UtteranceProgressListener{
   
    private MainActivity mainActivity;
     
     public MyUtteranceProgressListener(MainActivity mainActivity)
     {
       this.mainActivity = mainActivity;
     }
     
     @Override
     public void onStart(String utteranceId)
     {
       // Was soll passieren, wenn die nette Dame mit schwafeln anfängt ...
       this.mainActivity.setGoVisible(false);
     }
     
     @Override
     public void onDone(String utteranceId)
     {
       // Was soll passieren, wenn die nette Dame mit schwafeln fertig ist ...
       this.mainActivity.setGoVisible(true);
     }
     
     @Override
     public void onError(String utteranceId)
     {
       // Was soll passieren, wenn ein Fehler aufgetreten ist...
       this.mainActivity.setGoVisible(true);
     }

}

Geht nur leider nicht, weißt du warum?
 

saftmeister

Nutze den Saft!
Geht nur leider nicht, weißt du warum?

Ich hab meine Glaskugel in Reparatur :)

Aber ich habe so eine Ahnung. Du hast meinen Kommentar zum Thema "TODO: Eigentümer-Thread" einfach rausgelöscht, ohne ihn zu beachten - ich habe extra darauf hingewiesen (runOnUiThread...). Ein Blick ins LogCat wird das evtl. bestätigen oder die tatsächliche Meldung bringt den Fehler ans Tageslicht.
 

Noki99

Mitglied
Mhm,

iwi steh ich grad aufm Schlauch. Dass ist doch in der MainActivity, dort liegt doch auch der Button...

Naja muss erstmal das mit LogCat hinbekommen melde mich nochmal dann :)
 

saftmeister

Nutze den Saft!
Das der Code in der MainActivity-Klasse liegt, heißt nicht, dass er auch dort ausgeführt wird. Der Name UtteranceProgressListener lässt darauf schließen, dass die onXXX-Methoden in einem separaten Thread ausgeführt werden. Wenn du in onDone() eine Methode aus der MainActivity aufrufst, wird die Funktion im Thread-Context des Listeners ausgeführt. Wenn der ein anderer als der Main-Thread der Aktivität ist, unterbindet Android die Steuerung von Controls oder das Ändern des Verhaltens derselben.

Stell es dir ungefähr so vor:

- Du rufst beim Weckdienst an und sagst, dass du um 6:30 geweckt werden willst (analog zu du gibt TTS den Auftrag eine bestimmte Zeichenfolge aufzusagen).
- Der Weckdienst nimmt den Auftrag entgegen, dann trennen sich zunächst eure Wege (analog zu TTS erstellt einen Thread => onStart() im Listener).
- Du legst dich schlafen, der Weckdienst unternimmt andere Sachen, wie z.B. das Wecken anderer Leute (analog zu TTS spricht).
- Um 6:30 ruft der Weckdienst bei dir an um dir mitzuteilen, dass jetzt Zeit ist aufzustehen (analog zum Aufruf der onDone()-Methode).
- Wenn du jetzt nicht ans Telefon gehst - oder noch besser das Telefon lautlos ist, wird der Weckdienst seinen Service nicht erfüllen können, er wird quasi von dir daran gehindert, seinen Auftrag abzuschließen.
- Das Wrappen der Funktion btn_go.setVisibility() in einen Thread, der es gestattet, die Controls auf der MainActivity zu manipulieren, in dem die Funktion innerhalb des Haupt-Threads ausgeführt wird umgeht das Problem. Du gibst quasi dem Weckdienst die Möglichkeit, dein Telefon von Lautlos zu Laut umzustellen, damit er seinen Dienst erledigen kann.

Ich hoffe, ich habe mit der Analogie nicht noch mehr Verwirrung gestiftet.