无法可靠地调用UtteranceProgressListener [英] UtteranceProgressListener not being reliably called

查看:116
本文介绍了无法可靠地调用UtteranceProgressListener的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个Android应用程序,以从文件夹中获取最新电子邮件并使用TTS播放.我希望能够在开车时使用它,因此它必须基本上是自动的.到目前为止,一切都运转良好,直到我尝试捕获TextToSpeech结束的讲话后,我们才能继续阅读下一封电子邮件.

I am writing an Android app to fetch the latest email from a folder and play it using TTS. I want to be able to use it whilst driving so it has to be mostly automatic. Everything so far is working fine until I attempt to capture when the TextToSpeech has finished speaking so we can move on to the next email.

这是完整的MainActivity.java文件:

package uk.co.letsdelight.emailreader;

import android.os.AsyncTask;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import java.util.Properties;

import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.internet.MimeBodyPart;

public class MainActivity extends AppCompatActivity implements TextToSpeech.OnInitListener {

public TextToSpeech tts;
private Bundle ttsParam = new Bundle();
public UtteranceProgressListener utListener;
private boolean isPlaying = false;
private Properties imap = new Properties();
private String textToSpeak = "";

@Override
public void onInit(int ttsStatus) {
    if (ttsStatus == TextToSpeech.SUCCESS) {
        utListener = new UtteranceProgressListener() {
            @Override
            public void onStart(String s) {
                TextView status = findViewById(R.id.status);
                status.setText("started reading (Listener)");
            }

            @Override
            public void onDone(String s) {
                Toast.makeText(getApplicationContext(), "Done Event Listener", Toast.LENGTH_LONG).show();
                TextView status = findViewById(R.id.status);
                status.setText("finished reading (Listener)");
                /*ImageButton i = findViewById(R.id.playButton);
                i.setImageResource(R.drawable.button_play);*/
                isPlaying = false;
            }

            @Override
            public void onStop(String s, boolean b) {
                Toast.makeText(getApplicationContext(), "Stop Event Listener", Toast.LENGTH_LONG).show();
                TextView status = findViewById(R.id.status);
                status.setText("stopped reading (Listener)");
                /*ImageButton i = findViewById(R.id.playButton);
                i.setImageResource(R.drawable.button_play);*/
                isPlaying = false;
            }

            @Override
            public void onError(String s) {
                Toast.makeText(getApplicationContext(), "Error Event Listener", Toast.LENGTH_LONG).show();
                TextView status = findViewById(R.id.status);
                status.setText("Error reading email");
                ImageButton i = findViewById(R.id.playButton);
                i.setImageResource(R.drawable.button_play);
                isPlaying = false;
            }
        };
        tts.setOnUtteranceProgressListener(utListener);
        TextView status = findViewById(R.id.status);
        status.setText("initialised");
    } else {
        TextView status = findViewById(R.id.status);
        status.setText("failed to initialise");
    }
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    imap.setProperty("mail.store.protocol", "imap");
    imap.setProperty("mail.imaps.port", "143");
    tts = new TextToSpeech(this,this);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.menu_main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();

    //noinspection SimplifiableIfStatement
    if (id == R.id.action_settings) {
        return true;
    }

    return super.onOptionsItemSelected(item);
}

public void restartPressed(View v) {
    if (isPlaying) {
        tts.stop();
        speak();
    }
}

public void playPressed(View v) {
    ImageButton i = (ImageButton) v;
    if (isPlaying) {
        isPlaying = false;
        i.setImageResource(R.drawable.button_play);
        TextView status = findViewById(R.id.status);
        status.setText("");
        if (tts != null) {
            tts.stop();
        }
    } else {
        isPlaying = true;
        i.setImageResource(R.drawable.button_stop);
        new Reader().execute();
    }
}

class Reader extends AsyncTask<String, Void, String> {
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        TextView status = findViewById(R.id.status);
        status.setText("fetching email");
    }

    @Override
    protected String doInBackground(String... params) {
        String toRead = "nothing to fetch";
        try {
            Session session = Session.getDefaultInstance(imap, null);
            Store store = session.getStore();
            store.connect(getText(R.string.hostname).toString(), getText(R.string.username).toString(), getText(R.string.password).toString());
            Folder inbox = store.getFolder("INBOX.Articles.listen");
            if (inbox.exists() && inbox.getMessageCount() > 0) {
                inbox.open(Folder.READ_ONLY);
                Message msg = inbox.getMessage(inbox.getMessageCount() - 6);
                if (msg.getContentType().contains("multipart")) {
                    Multipart multiPart = (Multipart) msg.getContent();
                    MimeBodyPart part = (MimeBodyPart) multiPart.getBodyPart(multiPart.getCount() - 1);
                    toRead = part.getContent().toString();
                } else {
                    toRead = msg.getContent().toString();
                }
            } else {
                toRead = "The folder is empty or doesn't exist";
            }
        } catch (Throwable ex) {
            toRead = "Error fetching email - " + ex.toString();
        }
        return toRead;
    }

    @Override
    protected void onPostExecute(String s) {
        super.onPostExecute(s);
        String body;
        TextView status = findViewById(R.id.status);
        status.setText("");
        try {
            Document doc = Jsoup.parse(s);
            body = doc.body().text();
        } catch (Throwable ex) {
            body = "Error parsing email - " + ex.toString();
        }
        status.setText("email successfully fetched");
        textToSpeak = body;
        if (isPlaying) {
            speak();
        }
    }
}

private void speak() {
    int maxLength = TextToSpeech.getMaxSpeechInputLength();
    if (textToSpeak.length() > maxLength) {
        textToSpeak = "The email text is too long! The maximum length is " + maxLength + " characters";
    }
    ttsParam.putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "EmailReader");
    tts.speak(textToSpeak, TextToSpeech.QUEUE_FLUSH, ttsParam, "EmailReader");
}

@Override
protected void onDestroy() {
    if (tts != null) {
        tts.stop();
        tts.shutdown();
    }
    super.onDestroy();
}
}

内部类Reader正常工作. doInBackground提取电子邮件,而onPostExec去除所有HTML,以保留电子邮件的实际文本内容.这将传递给speak()方法,该方法可以进行实际的讲话并起作用.

The inner class Reader works fine. doInBackground fetches the email and onPostExec strips out any HTML to leave the actual text content of the email. This is passed to the speak() method which does the actual speaking and works.

问题出在onUtteranceProgressListener.

有时会调用onStart(String s)方法,有时却不会!电子邮件第一次读出似乎从未被称为.通常,随后调用speak()时会调用它,但并非总是如此.大约有五分之一的呼叫失败.如果调用了侦听器,则状态显示为开始阅读(侦听器)",否则显示为已成功获取电子邮件".

Sometimes the onStart(String s) method is called, sometimes it isn't! It seems to never be called the first time the email read out. Mostly it is called for subsequent calls to speak() but not always. About 1 in 5 times it fails to get called. If the listener is called the status displays 'started reading (Listener)' otherwise it shows 'email successfully fetched'.

onDoneonErroronStop.

我尝试在tts.speak()调用中使用不同的utteranceIDBundle值,但这有所不同.

I have tried using different utteranceID and Bundle values in the tts.speak() call but this makes to difference.

启动应用程序时,第一个状态显示为已初始化",这意味着onUtteranceListener必须已在onInit方法中设置. TextToSpeech对象是在活动的onCreate方法中实例化的.

When the app is started, the first status display is 'initialised' which means that the onUtteranceListener must have been set within the onInit method. The TextToSpeech object is instantiated within the activity's onCreate method.

我仔细阅读了所有可以找到的信息,这些信息大部分建议正确使用utteranceID.为了更好地理解这个问题,我还可以尝试其他什么方法?

I have looked through all the information I can find which has mostly suggested getting the utteranceID correct. What else can I try in order to get a better understanding of this problem please?

推荐答案

问题是onDone()方法(实际上是任何进度回调)都在后台线程上运行,因此Toast不会进行正常工作,并且访问您的UI的任何代码(例如setText(...))可能会也可能不会正常工作.

The problem is that the onDone() method (and in fact any of the progress callbacks) is run on a background thread, and therefore the Toast is not going to work, and any code that accesses your UI such as setText(...) may or may not work.

所以...可能调用了 方法,但您根本看不到.

So... the methods probably are being called, but you just can't see that.

解决方案是使用runOnUiThread()将回调中的代码括起来,如下所示:

The solution to this would be to surround the code in your callbacks with runOnUiThread() like this:

@Override
public void onDone(String s) {

    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            Toast.makeText(getApplicationContext(), "Done Event Listener", Toast.LENGTH_LONG).show();
            TextView status = findViewById(R.id.status);
            status.setText("finished reading (Listener)");
            /*ImageButton i = findViewById(R.id.playButton);
            i.setImageResource(R.drawable.button_play);*/
            isPlaying = false;
        }
    });

}

注意:最好在onCreate()中初始化TextView以及其他所有东西,而不是在进度回调中初始化.

Note: It's probably best to initialize your TextView in onCreate() along with everything else instead of in the progress callbacks.

此外,utteranceID的目的是给每个对talk()的调用一个唯一的标识符,该标识符然后作为进度回调中的"String s"参数传递回给您.

Also, the purpose of the utteranceID is to give each call to speak() a unique identifier that is then passed back to you as the "String s" argument in the progress callbacks.

最好使用某种随机数生成器给每个通话说一个新的(最近的")ID,然后在进度回调中对其进行检查.

It's a good idea to give each call to speak a new ("most recent") ID using some kind of random number generator, and then checking it in the progress callbacks.

关于此旁注:

因为有一个重新启动"按钮,所以您应该知道在<23的API上,对TextToSpeech.stop()的调用将导致在进度侦听器中调用onDone().在API 23+上,它改为调用onStop().

Since you have a "restart" button, you should know that on APIs <23, calls to TextToSpeech.stop() will cause the onDone() in your progress listener to be called. On APIs 23+, it calls onStop() instead.

这篇关于无法可靠地调用UtteranceProgressListener的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆