UtteranceProgressListener не вызывается надежно - PullRequest
0 голосов
/ 23 сентября 2018

Я пишу приложение Android для получения последней электронной почты из папки и воспроизведения ее с помощью TTS.Я хочу иметь возможность использовать его во время вождения, поэтому он должен быть в основном автоматическим.Пока что все работает нормально, пока я не попытаюсь захватить, когда TextToSpeech закончит говорить, чтобы мы могли перейти к следующему письму.

Вот полный файл 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(), который фактически говорит и работает.

Проблема связана с onUtteranceProgressListener.

Иногда вызывается метод onStart(String s), иногда - нет.«т!Кажется, он никогда не будет вызван при первом прочтении письма.В основном он вызывается для последующих вызовов speak(), но не всегда.Примерно 1 раз из 5 он не может быть вызван.Если прослушиватель вызывается, отображается состояние «начал читать (Listener)», в противном случае он показывает «электронная почта успешно получена».

onDone, onError и onStop никогда не вызываются.

Я пытался использовать разные значения utteranceID и Bundle в вызове tts.speak(), но это имеет значение.

Когда приложение запускается, первый экран состояния «инициализируется», что означает, чтоonUtteranceListener должно быть установлено в методе onInit.Объект TextToSpeech создается в методе действия onCreate.

Я просмотрел всю информацию, которую смог найти, которая в основном предлагала получить правильный utteranceID.Что еще я могу попробовать, чтобы лучше понять эту проблему, пожалуйста?

Ответы [ 2 ]

0 голосов
/ 24 сентября 2018

Проблема в том, что метод onDone () (и фактически любой из обратных вызовов хода выполнения) запускается в фоновом потоке, и поэтому Toast не будет работать, а также любой код, который обращается к вашему пользовательскому интерфейсу, такой как setText (...) может или не может работать.

Итак ... методы , вероятно, вызываются, но вы просто не можете этого увидеть.

Решениеэто будет заключаться в том, чтобы окружить код в ваших обратных вызовах с помощью runOnUiThread () следующим образом:

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

}

Примечание: вероятно, лучше всего инициализировать ваш TextView в onCreate () вместе со всем остальным, а не в обратных вызовах прогресса,

Кроме того, цель utteranceID состоит в том, чтобы каждый вызов говорить () уникальным идентификатором, который затем передается вам в качестве аргумента «String s» в обратных вызовах процесса.

Хорошей идеей будет давать каждому звонку новый («последний») идентификатор, используя какой-то генератор случайных чисел, а затем проверять его в обратных вызовах хода выполнения.

Вы можете увидеть похожий вопрос и ответ.в отношении этого здесь .

Примечание:

Поскольку у вас есть кнопка «перезапустить», вы должны знать, что в API <23 вызовы TextToSpeech.stop ()вызовет onDone () в вашем обработчике прогресса.В API 23+ вместо этого вызывается onStop ().</p>

0 голосов
/ 23 сентября 2018

Во-первых, убедитесь, что у вас действительно есть проблема, а не гонка между тем, кто устанавливает текст в каком порядке.Используйте операторы журнала, чтобы убедиться, что он на самом деле не вызывается.

Попробуйте установить queueMode в QUEUE_ADD, например:

tts.speak(textToSpeak, TextToSpeech.QUEUE_ADD, ttsParam, "EmailReader");

, возможно, последующие вызовы отменяют события слушателя из предыдущих текстовых входов, как предполагает QUEUE_FLUSH.

Кроме того, пакет там на самом деле не нужен, вы можете установить его в null.

Надеюсь, что любая из этих подсказок поможет.

...