Thursday, 30 January 2014

Text to speech - Android

I remember back in school when i had little time to read and noticed that i had memorized lyrics of music i heard while sleeping lead me to download audio books but my lecture notes weren't audio books so i decided to write an app to read text for my then mytouch 3g slide, i just felt like visiting that memory once again with this tutorial.

Lets begin, create your android project and open the layout file (activity_main.xml), i left my default TextView but edited the output to something cooler and used it as my header, the main components you need to setup here are the EditText  and Button components which is shown in the code below:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/header"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Have your text read" />
    
    <EditText 
        android:id="@+id/your_text"
        android:layout_below="@+id/header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="textMultiLine"
        android:gravity="center"
        />

    <Button 
        android:id="@+id/speak_btn"
        android:layout_below="@+id/your_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="please read my text"
        />
</RelativeLayout>


Now that we have painted the scene now it is time for action so switch to the MainActivity.java file for the main event (see what i did there :))
Okay we then declare our TextToSpeech, EditText and Button objects as follows:

        TextToSpeech textTS;
 Button speakBtn;
 EditText yourText;
you will a couple of errors, resolve by pressing ctrl+shift+o to import the necessary libraries and resolve the error.
Now you don't just name something and not bring it to life do you?
so we now initialize our objects in the onCreate() function so our objects are initialized when our application is started.
                //Initialize our objects
  textTS = new TextToSpeech(this, this);
  speakBtn = (Button)findViewById(R.id.speak_btn);
  yourText = (EditText)findViewById(R.id.your_text);
you will notice an error when initializing the textTS object you can resolve that by implementing this TextToSpeech.OnInitListener as follows:
public class MainActivity extends Activity implements TextToSpeech.OnInitListener
After doing the above you will have to implement a method to resolve the next error so just hover over the red line and select implement unimplemented method it will add a method (onInit)and resolve the problem.

Lets go back to our onCreate method and setup our onClickListener see the code below

//Hey when i click this button read my text
  speakBtn.setOnClickListener(new View.OnClickListener()
  {

   @Override
   public void onClick(View v) {
    // TODO Auto-generated method stub
    readText();
   }
   
  });
We just called a method readText() which we will implement later on. For now let us code our onInit(), this is where we will set our language, and check if our input is readable by our textTS object. The default speech rate was kinda fast for me so i reduced it from the default 1 to 0.7.
 @Override
 public void onInit(int status) {
  // TODO Auto-generated method stub
  if(status == TextToSpeech.SUCCESS)
  {
   int result = textTS.setLanguage(Locale.UK);
   textTS.setSpeechRate((float) 0.7);
   //textTS.setPitch(1);
   //Make sure that the data is valid
   //we check if our data if invalid or if the language received is invalid
   if(result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED)
   {
    Log.e("!!ER!!", "What crap are you feeding me");
   }
   //if our data is good we then proceed with our reading process
   else
   {
    speakBtn.setEnabled(true);
    readText();
   }
  }
 }
You will notice we call readText() again when our input is good and readable, the time is here to set up our readText() and convert our text to speech. This is the code:
private void readText()
 {
  String toBeRead = yourText.getText().toString();
  textTS.speak(toBeRead, TextToSpeech.QUEUE_FLUSH, null);
 }
And this is the full code below, notice i added the onDestroy(), this is to turn off TextToSpeech when the app goes off and keep the android life cycle clean.
package com.example.retts;

import java.util.Locale;

import android.app.Activity;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends Activity implements TextToSpeech.OnInitListener{

 //Declare our objects
 TextToSpeech textTS;
 Button speakBtn;
 EditText yourText;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  //Initialize our objects
  textTS = new TextToSpeech(this, this);
  speakBtn = (Button)findViewById(R.id.speak_btn);
  yourText = (EditText)findViewById(R.id.your_text);
  
  //Hey when i click this button read my text
  speakBtn.setOnClickListener(new View.OnClickListener()
  {

   @Override
   public void onClick(View v) {
    // TODO Auto-generated method stub
    readText();
   }
   
  });
 }
 //keeping the life cycle clean
 @Override
 protected void onDestroy() {
  // TODO Auto-generated method stub
  super.onDestroy();
  if(textTS != null)
  {
   textTS.stop();
   textTS.shutdown();
  }
 }


 
 @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;
 }

 @Override
 public void onInit(int status) {
  // TODO Auto-generated method stub
  if(status == TextToSpeech.SUCCESS)
  {
   int result = textTS.setLanguage(Locale.UK);
   textTS.setSpeechRate((float) 0.7);
   //textTS.setPitch(1);
   //Make sure that the data is valid
   //we check if our data if invalid or if the language received is invalid
   if(result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED)
   {
    Log.e("!!ER!!", "What crap are you feeding me");
   }
   //if our data is good we then proceed with our reading process
   else
   {
    speakBtn.setEnabled(true);
    readText();
   }
  }
 }
 
 private void readText()
 {
  String toBeRead = yourText.getText().toString();
  textTS.speak(toBeRead, TextToSpeech.QUEUE_FLUSH, null);
 }

}