Implement QUEUE_FLUSH and QUEUE_DESTROY addition modes.
Note that this change is slightly more flexible, it defines a priority for each playback request. We needn't really throw away synthesis requests from other apps. We could just play them after higher priority ones (for e.g from talkback). Change-Id: I6c7dd9abe4871e87da08d9aaa4c55225a69078e5
This commit is contained in:
@@ -20,8 +20,9 @@ import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
|
||||
class AudioMessageParams extends MessageParams {
|
||||
private final BlockingMediaPlayer mPlayer;
|
||||
|
||||
AudioMessageParams(UtteranceCompletedDispatcher dispatcher, BlockingMediaPlayer player) {
|
||||
super(dispatcher);
|
||||
AudioMessageParams(UtteranceCompletedDispatcher dispatcher,
|
||||
String callingApp, BlockingMediaPlayer player) {
|
||||
super(dispatcher, callingApp);
|
||||
mPlayer = player;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,13 +17,13 @@ package android.speech.tts;
|
||||
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioTrack;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.speech.tts.SynthesisMessageParams.ListEntry;
|
||||
import android.util.Log;
|
||||
|
||||
class AudioPlaybackHandler extends Handler {
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
class AudioPlaybackHandler {
|
||||
private static final String TAG = "TTS.AudioPlaybackHandler";
|
||||
private static final boolean DBG = false;
|
||||
|
||||
@@ -37,33 +37,31 @@ class AudioPlaybackHandler extends Handler {
|
||||
private static final int PLAY_AUDIO = 5;
|
||||
private static final int PLAY_SILENCE = 6;
|
||||
|
||||
// Accessed by multiple threads, synchronized by "this".
|
||||
private MessageParams mCurrentParams;
|
||||
// Used only for book keeping and error detection.
|
||||
private SynthesisMessageParams mLastSynthesisRequest;
|
||||
private static final int SHUTDOWN = -1;
|
||||
|
||||
AudioPlaybackHandler(Looper looper) {
|
||||
super(looper);
|
||||
private static final int DEFAULT_PRIORITY = 1;
|
||||
private static final int HIGH_PRIORITY = 0;
|
||||
|
||||
private final PriorityBlockingQueue<ListEntry> mQueue =
|
||||
new PriorityBlockingQueue<ListEntry>();
|
||||
private final Thread mHandlerThread;
|
||||
|
||||
private final Object mStateLock = new Object();
|
||||
|
||||
// Accessed by multiple threads, synchronized by "mStateLock".
|
||||
private MessageParams mCurrentParams = null;
|
||||
// Used only for book keeping and error detection.
|
||||
private volatile SynthesisMessageParams mLastSynthesisRequest = null;
|
||||
// Used to order incoming messages in our priority queue.
|
||||
private final AtomicLong mSequenceIdCtr = new AtomicLong(0);
|
||||
|
||||
|
||||
AudioPlaybackHandler() {
|
||||
mHandlerThread = new Thread(new MessageLoop(), "TTS.AudioPlaybackThread");
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void handleMessage(Message msg) {
|
||||
if (msg.what == SYNTHESIS_START) {
|
||||
mCurrentParams = (SynthesisMessageParams) msg.obj;
|
||||
handleSynthesisStart(msg);
|
||||
} else if (msg.what == SYNTHESIS_DATA_AVAILABLE) {
|
||||
handleSynthesisDataAvailable(msg);
|
||||
} else if (msg.what == SYNTHESIS_DONE) {
|
||||
handleSynthesisDone(msg);
|
||||
} else if (msg.what == SYNTHESIS_COMPLETE_DATA_AVAILABLE) {
|
||||
handleSynthesisCompleteDataAvailable(msg);
|
||||
} else if (msg.what == PLAY_AUDIO) {
|
||||
handleAudio(msg);
|
||||
} else if (msg.what == PLAY_SILENCE) {
|
||||
handleSilence(msg);
|
||||
}
|
||||
|
||||
mCurrentParams = null;
|
||||
public void start() {
|
||||
mHandlerThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,63 +70,232 @@ class AudioPlaybackHandler extends Handler {
|
||||
* that is not guaranteed.
|
||||
*/
|
||||
synchronized public void stop(MessageParams token) {
|
||||
removeCallbacksAndMessages(token);
|
||||
if (token == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeMessages(token);
|
||||
|
||||
if (token.getType() == MessageParams.TYPE_SYNTHESIS) {
|
||||
sendMessageAtFrontOfQueue(obtainMessage(SYNTHESIS_DONE, token));
|
||||
} else if (token == mCurrentParams) {
|
||||
if (token.getType() == MessageParams.TYPE_AUDIO) {
|
||||
((AudioMessageParams) mCurrentParams).getPlayer().stop();
|
||||
} else if (token.getType() == MessageParams.TYPE_SILENCE) {
|
||||
((SilenceMessageParams) mCurrentParams).getConditionVariable().open();
|
||||
mQueue.add(new ListEntry(SYNTHESIS_DONE, token, HIGH_PRIORITY));
|
||||
} else {
|
||||
MessageParams current = getCurrentParams();
|
||||
|
||||
if (current != null) {
|
||||
if (token.getType() == MessageParams.TYPE_AUDIO) {
|
||||
((AudioMessageParams) current).getPlayer().stop();
|
||||
} else if (token.getType() == MessageParams.TYPE_SILENCE) {
|
||||
((SilenceMessageParams) current).getConditionVariable().open();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized public void removePlaybackItems(String callingApp) {
|
||||
removeMessages(callingApp);
|
||||
stop(getCurrentParams());
|
||||
}
|
||||
|
||||
synchronized public void removeAllItems() {
|
||||
removeAllMessages();
|
||||
stop(getCurrentParams());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shut down the audio playback thread.
|
||||
*/
|
||||
synchronized public void quit() {
|
||||
if (mCurrentParams != null) {
|
||||
stop(mCurrentParams);
|
||||
}
|
||||
getLooper().quit();
|
||||
stop(getCurrentParams());
|
||||
mQueue.add(new ListEntry(SHUTDOWN, null, HIGH_PRIORITY));
|
||||
}
|
||||
|
||||
void enqueueSynthesisStart(SynthesisMessageParams token) {
|
||||
sendMessage(obtainMessage(SYNTHESIS_START, token));
|
||||
mQueue.add(new ListEntry(SYNTHESIS_START, token));
|
||||
}
|
||||
|
||||
void enqueueSynthesisDataAvailable(SynthesisMessageParams token) {
|
||||
sendMessage(obtainMessage(SYNTHESIS_DATA_AVAILABLE, token));
|
||||
mQueue.add(new ListEntry(SYNTHESIS_DATA_AVAILABLE, token));
|
||||
}
|
||||
|
||||
void enqueueSynthesisCompleteDataAvailable(SynthesisMessageParams token) {
|
||||
sendMessage(obtainMessage(SYNTHESIS_COMPLETE_DATA_AVAILABLE, token));
|
||||
mQueue.add(new ListEntry(SYNTHESIS_COMPLETE_DATA_AVAILABLE, token));
|
||||
}
|
||||
|
||||
void enqueueSynthesisDone(SynthesisMessageParams token) {
|
||||
sendMessage(obtainMessage(SYNTHESIS_DONE, token));
|
||||
mQueue.add(new ListEntry(SYNTHESIS_DONE, token));
|
||||
}
|
||||
|
||||
void enqueueAudio(AudioMessageParams token) {
|
||||
sendMessage(obtainMessage(PLAY_AUDIO, token));
|
||||
mQueue.add(new ListEntry(PLAY_AUDIO, token));
|
||||
}
|
||||
|
||||
void enqueueSilence(SilenceMessageParams token) {
|
||||
sendMessage(obtainMessage(PLAY_SILENCE, token));
|
||||
mQueue.add(new ListEntry(PLAY_SILENCE, token));
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// End of public API methods.
|
||||
// -----------------------------------------
|
||||
|
||||
// -----------------------------------------
|
||||
// Methods for managing the message queue.
|
||||
// -----------------------------------------
|
||||
|
||||
/*
|
||||
* The MessageLoop is a handler like implementation that
|
||||
* processes messages from a priority queue.
|
||||
*/
|
||||
private final class MessageLoop implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
ListEntry entry = null;
|
||||
try {
|
||||
entry = mQueue.take();
|
||||
} catch (InterruptedException ie) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.mWhat == SHUTDOWN) {
|
||||
if (DBG) Log.d(TAG, "MessageLoop : Shutting down");
|
||||
return;
|
||||
}
|
||||
|
||||
if (DBG) {
|
||||
Log.d(TAG, "MessageLoop : Handling message :" + entry.mWhat
|
||||
+ " ,seqId : " + entry.mSequenceId);
|
||||
}
|
||||
|
||||
setCurrentParams(entry.mMessage);
|
||||
handleMessage(entry);
|
||||
setCurrentParams(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove all messages from the queue that contain the supplied token.
|
||||
* Note that the Iterator is thread safe, and other methods can safely
|
||||
* continue adding to the queue at this point.
|
||||
*/
|
||||
synchronized private void removeMessages(MessageParams token) {
|
||||
if (token == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Iterator<ListEntry> it = mQueue.iterator();
|
||||
|
||||
while (it.hasNext()) {
|
||||
final ListEntry current = it.next();
|
||||
if (current.mMessage == token) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Atomically clear the queue of all messages.
|
||||
*/
|
||||
synchronized private void removeAllMessages() {
|
||||
mQueue.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove all messages that originate from a given calling app.
|
||||
*/
|
||||
synchronized private void removeMessages(String callingApp) {
|
||||
Iterator<ListEntry> it = mQueue.iterator();
|
||||
|
||||
while (it.hasNext()) {
|
||||
final ListEntry current = it.next();
|
||||
// The null check is to prevent us from removing control messages,
|
||||
// such as a shutdown message.
|
||||
if (current.mMessage != null &&
|
||||
callingApp.equals(current.mMessage.getCallingApp())) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* An element of our priority queue of messages. Each message has a priority,
|
||||
* and a sequence id (defined by the order of enqueue calls). Among messages
|
||||
* with the same priority, messages that were received earlier win out.
|
||||
*/
|
||||
private final class ListEntry implements Comparable<ListEntry> {
|
||||
final int mWhat;
|
||||
final MessageParams mMessage;
|
||||
final int mPriority;
|
||||
final long mSequenceId;
|
||||
|
||||
private ListEntry(int what, MessageParams message) {
|
||||
this(what, message, DEFAULT_PRIORITY);
|
||||
}
|
||||
|
||||
private ListEntry(int what, MessageParams message, int priority) {
|
||||
mWhat = what;
|
||||
mMessage = message;
|
||||
mPriority = priority;
|
||||
mSequenceId = mSequenceIdCtr.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ListEntry that) {
|
||||
if (that == this) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Note that this is always 0, 1 or -1.
|
||||
int priorityDiff = mPriority - that.mPriority;
|
||||
if (priorityDiff == 0) {
|
||||
// The == case cannot occur.
|
||||
return (mSequenceId < that.mSequenceId) ? -1 : 1;
|
||||
}
|
||||
|
||||
return priorityDiff;
|
||||
}
|
||||
}
|
||||
|
||||
private void setCurrentParams(MessageParams p) {
|
||||
synchronized (mStateLock) {
|
||||
mCurrentParams = p;
|
||||
}
|
||||
}
|
||||
|
||||
private MessageParams getCurrentParams() {
|
||||
synchronized (mStateLock) {
|
||||
return mCurrentParams;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// Methods for dealing with individual messages, the methods
|
||||
// below do the actual work.
|
||||
// -----------------------------------------
|
||||
|
||||
private void handleMessage(ListEntry entry) {
|
||||
final MessageParams msg = entry.mMessage;
|
||||
if (entry.mWhat == SYNTHESIS_START) {
|
||||
handleSynthesisStart(msg);
|
||||
} else if (entry.mWhat == SYNTHESIS_DATA_AVAILABLE) {
|
||||
handleSynthesisDataAvailable(msg);
|
||||
} else if (entry.mWhat == SYNTHESIS_DONE) {
|
||||
handleSynthesisDone(msg);
|
||||
} else if (entry.mWhat == SYNTHESIS_COMPLETE_DATA_AVAILABLE) {
|
||||
handleSynthesisCompleteDataAvailable(msg);
|
||||
} else if (entry.mWhat == PLAY_AUDIO) {
|
||||
handleAudio(msg);
|
||||
} else if (entry.mWhat == PLAY_SILENCE) {
|
||||
handleSilence(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Currently implemented as blocking the audio playback thread for the
|
||||
// specified duration. If a call to stop() is made, the thread
|
||||
// unblocks.
|
||||
private void handleSilence(Message msg) {
|
||||
private void handleSilence(MessageParams msg) {
|
||||
if (DBG) Log.d(TAG, "handleSilence()");
|
||||
SilenceMessageParams params = (SilenceMessageParams) msg.obj;
|
||||
SilenceMessageParams params = (SilenceMessageParams) msg;
|
||||
if (params.getSilenceDurationMs() > 0) {
|
||||
params.getConditionVariable().block(params.getSilenceDurationMs());
|
||||
}
|
||||
@@ -137,9 +304,9 @@ class AudioPlaybackHandler extends Handler {
|
||||
}
|
||||
|
||||
// Plays back audio from a given URI. No TTS engine involvement here.
|
||||
private void handleAudio(Message msg) {
|
||||
private void handleAudio(MessageParams msg) {
|
||||
if (DBG) Log.d(TAG, "handleAudio()");
|
||||
AudioMessageParams params = (AudioMessageParams) msg.obj;
|
||||
AudioMessageParams params = (AudioMessageParams) msg;
|
||||
// Note that the BlockingMediaPlayer spawns a separate thread.
|
||||
//
|
||||
// TODO: This can be avoided.
|
||||
@@ -157,9 +324,9 @@ class AudioPlaybackHandler extends Handler {
|
||||
// handleSynthesisStart -> handleSynthesisDataAvailable(*) -> handleSynthesisDone
|
||||
// OR
|
||||
// handleSynthesisCompleteDataAvailable.
|
||||
private void handleSynthesisStart(Message msg) {
|
||||
private void handleSynthesisStart(MessageParams msg) {
|
||||
if (DBG) Log.d(TAG, "handleSynthesisStart()");
|
||||
final SynthesisMessageParams param = (SynthesisMessageParams) msg.obj;
|
||||
final SynthesisMessageParams param = (SynthesisMessageParams) msg;
|
||||
|
||||
// Oops, looks like the engine forgot to call done(). We go through
|
||||
// extra trouble to clean the data to prevent the AudioTrack resources
|
||||
@@ -177,12 +344,14 @@ class AudioPlaybackHandler extends Handler {
|
||||
param.mStreamType, param.mSampleRateInHz, param.mAudioFormat,
|
||||
param.mChannelCount, param.mVolume, param.mPan);
|
||||
|
||||
if (DBG) Log.d(TAG, "Created audio track [" + audioTrack.hashCode() + "]");
|
||||
|
||||
param.setAudioTrack(audioTrack);
|
||||
}
|
||||
|
||||
// More data available to be flushed to the audio track.
|
||||
private void handleSynthesisDataAvailable(Message msg) {
|
||||
final SynthesisMessageParams param = (SynthesisMessageParams) msg.obj;
|
||||
private void handleSynthesisDataAvailable(MessageParams msg) {
|
||||
final SynthesisMessageParams param = (SynthesisMessageParams) msg;
|
||||
if (param.getAudioTrack() == null) {
|
||||
Log.w(TAG, "Error : null audio track in handleDataAvailable.");
|
||||
return;
|
||||
@@ -194,7 +363,7 @@ class AudioPlaybackHandler extends Handler {
|
||||
}
|
||||
|
||||
final AudioTrack audioTrack = param.getAudioTrack();
|
||||
final ListEntry bufferCopy = param.getNextBuffer();
|
||||
final SynthesisMessageParams.ListEntry bufferCopy = param.getNextBuffer();
|
||||
|
||||
if (bufferCopy == null) {
|
||||
Log.e(TAG, "No buffers available to play.");
|
||||
@@ -218,8 +387,8 @@ class AudioPlaybackHandler extends Handler {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSynthesisDone(Message msg) {
|
||||
final SynthesisMessageParams params = (SynthesisMessageParams) msg.obj;
|
||||
private void handleSynthesisDone(MessageParams msg) {
|
||||
final SynthesisMessageParams params = (SynthesisMessageParams) msg;
|
||||
handleSynthesisDone(params);
|
||||
}
|
||||
|
||||
@@ -233,6 +402,7 @@ class AudioPlaybackHandler extends Handler {
|
||||
if (audioTrack != null) {
|
||||
audioTrack.flush();
|
||||
audioTrack.stop();
|
||||
if (DBG) Log.d(TAG, "Releasing audio track [" + audioTrack.hashCode() + "]");
|
||||
audioTrack.release();
|
||||
}
|
||||
} finally {
|
||||
@@ -242,8 +412,8 @@ class AudioPlaybackHandler extends Handler {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSynthesisCompleteDataAvailable(Message msg) {
|
||||
final SynthesisMessageParams params = (SynthesisMessageParams) msg.obj;
|
||||
private void handleSynthesisCompleteDataAvailable(MessageParams msg) {
|
||||
final SynthesisMessageParams params = (SynthesisMessageParams) msg;
|
||||
if (DBG) Log.d(TAG, "completeAudioAvailable(" + params + ")");
|
||||
|
||||
// Channel config and bytes per frame are checked before
|
||||
@@ -251,7 +421,7 @@ class AudioPlaybackHandler extends Handler {
|
||||
int channelConfig = AudioPlaybackHandler.getChannelConfig(params.mChannelCount);
|
||||
int bytesPerFrame = AudioPlaybackHandler.getBytesPerFrame(params.mAudioFormat);
|
||||
|
||||
ListEntry entry = params.getNextBuffer();
|
||||
SynthesisMessageParams.ListEntry entry = params.getNextBuffer();
|
||||
|
||||
if (entry == null) {
|
||||
Log.w(TAG, "completeDataAvailable : No buffers available to play.");
|
||||
|
||||
@@ -18,19 +18,25 @@ package android.speech.tts;
|
||||
import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
|
||||
|
||||
abstract class MessageParams {
|
||||
private final UtteranceCompletedDispatcher mDispatcher;
|
||||
|
||||
static final int TYPE_SYNTHESIS = 1;
|
||||
static final int TYPE_AUDIO = 2;
|
||||
static final int TYPE_SILENCE = 3;
|
||||
|
||||
MessageParams(UtteranceCompletedDispatcher dispatcher) {
|
||||
private final UtteranceCompletedDispatcher mDispatcher;
|
||||
private final String mCallingApp;
|
||||
|
||||
MessageParams(UtteranceCompletedDispatcher dispatcher, String callingApp) {
|
||||
mDispatcher = dispatcher;
|
||||
mCallingApp = callingApp;
|
||||
}
|
||||
|
||||
UtteranceCompletedDispatcher getDispatcher() {
|
||||
return mDispatcher;
|
||||
}
|
||||
|
||||
String getCallingApp() {
|
||||
return mCallingApp;
|
||||
}
|
||||
|
||||
abstract int getType();
|
||||
}
|
||||
|
||||
@@ -64,15 +64,17 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
|
||||
private volatile boolean mDone = false;
|
||||
|
||||
private final UtteranceCompletedDispatcher mDispatcher;
|
||||
private final String mCallingApp;
|
||||
|
||||
PlaybackSynthesisCallback(int streamType, float volume, float pan,
|
||||
AudioPlaybackHandler audioTrackHandler,
|
||||
UtteranceCompletedDispatcher dispatcher) {
|
||||
AudioPlaybackHandler audioTrackHandler, UtteranceCompletedDispatcher dispatcher,
|
||||
String callingApp) {
|
||||
mStreamType = streamType;
|
||||
mVolume = volume;
|
||||
mPan = pan;
|
||||
mAudioTrackHandler = audioTrackHandler;
|
||||
mDispatcher = dispatcher;
|
||||
mCallingApp = callingApp;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -122,7 +124,7 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
|
||||
}
|
||||
SynthesisMessageParams params = new SynthesisMessageParams(
|
||||
mStreamType, sampleRateInHz, audioFormat, channelCount, mVolume, mPan,
|
||||
mDispatcher);
|
||||
mDispatcher, mCallingApp);
|
||||
mAudioTrackHandler.enqueueSynthesisStart(params);
|
||||
|
||||
mToken = params;
|
||||
@@ -206,7 +208,7 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
|
||||
}
|
||||
SynthesisMessageParams params = new SynthesisMessageParams(
|
||||
mStreamType, sampleRateInHz, audioFormat, channelCount, mVolume, mPan,
|
||||
mDispatcher);
|
||||
mDispatcher, mCallingApp);
|
||||
params.addBuffer(buffer, offset, length);
|
||||
|
||||
mAudioTrackHandler.enqueueSynthesisCompleteDataAvailable(params);
|
||||
|
||||
@@ -22,8 +22,9 @@ class SilenceMessageParams extends MessageParams {
|
||||
private final ConditionVariable mCondVar = new ConditionVariable();
|
||||
private final long mSilenceDurationMs;
|
||||
|
||||
SilenceMessageParams(UtteranceCompletedDispatcher dispatcher, long silenceDurationMs) {
|
||||
super(dispatcher);
|
||||
SilenceMessageParams(UtteranceCompletedDispatcher dispatcher,
|
||||
String callingApp, long silenceDurationMs) {
|
||||
super(dispatcher, callingApp);
|
||||
mSilenceDurationMs = silenceDurationMs;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,9 @@ final class SynthesisMessageParams extends MessageParams {
|
||||
|
||||
SynthesisMessageParams(int streamType, int sampleRate,
|
||||
int audioFormat, int channelCount,
|
||||
float volume, float pan, UtteranceCompletedDispatcher dispatcher) {
|
||||
super(dispatcher);
|
||||
float volume, float pan, UtteranceCompletedDispatcher dispatcher,
|
||||
String callingApp) {
|
||||
super(dispatcher, callingApp);
|
||||
|
||||
mStreamType = streamType;
|
||||
mSampleRateInHz = sampleRate;
|
||||
|
||||
@@ -94,9 +94,8 @@ public abstract class TextToSpeechService extends Service {
|
||||
synthThread.start();
|
||||
mSynthHandler = new SynthHandler(synthThread.getLooper());
|
||||
|
||||
HandlerThread audioTrackThread = new HandlerThread("TTS.audioTrackThread");
|
||||
audioTrackThread.start();
|
||||
mAudioPlaybackHandler = new AudioPlaybackHandler(audioTrackThread.getLooper());
|
||||
mAudioPlaybackHandler = new AudioPlaybackHandler();
|
||||
mAudioPlaybackHandler.start();
|
||||
|
||||
mCallbacks = new CallbackMap();
|
||||
|
||||
@@ -299,11 +298,16 @@ public abstract class TextToSpeechService extends Service {
|
||||
if (!speechItem.isValid()) {
|
||||
return TextToSpeech.ERROR;
|
||||
}
|
||||
// TODO: The old code also supported the undocumented queueMode == 2,
|
||||
// which clears out all pending items from the calling app, as well as all
|
||||
// non-file items from other apps.
|
||||
|
||||
if (queueMode == TextToSpeech.QUEUE_FLUSH) {
|
||||
stop(speechItem.getCallingApp());
|
||||
} else if (queueMode == 2) {
|
||||
// Stop the current speech item.
|
||||
stop(speechItem.getCallingApp());
|
||||
// Remove all other items from the queue.
|
||||
removeCallbacksAndMessages(null);
|
||||
// Remove all pending playback as well.
|
||||
mAudioPlaybackHandler.removeAllItems();
|
||||
}
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
@@ -334,12 +338,16 @@ public abstract class TextToSpeechService extends Service {
|
||||
if (TextUtils.isEmpty(callingApp)) {
|
||||
return TextToSpeech.ERROR;
|
||||
}
|
||||
|
||||
removeCallbacksAndMessages(callingApp);
|
||||
SpeechItem current = setCurrentSpeechItem(null);
|
||||
if (current != null && TextUtils.equals(callingApp, current.getCallingApp())) {
|
||||
current.stop();
|
||||
}
|
||||
|
||||
// Remove any enqueued audio too.
|
||||
mAudioPlaybackHandler.removePlaybackItems(callingApp);
|
||||
|
||||
return TextToSpeech.SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -490,7 +498,7 @@ public abstract class TextToSpeechService extends Service {
|
||||
|
||||
protected AbstractSynthesisCallback createSynthesisCallback() {
|
||||
return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(),
|
||||
mAudioPlaybackHandler, this);
|
||||
mAudioPlaybackHandler, this, getCallingApp());
|
||||
}
|
||||
|
||||
private void setRequestParams(SynthesisRequest request) {
|
||||
@@ -617,7 +625,7 @@ public abstract class TextToSpeechService extends Service {
|
||||
|
||||
@Override
|
||||
protected int playImpl() {
|
||||
mToken = new AudioMessageParams(this, mPlayer);
|
||||
mToken = new AudioMessageParams(this, getCallingApp(), mPlayer);
|
||||
mAudioPlaybackHandler.enqueueAudio(mToken);
|
||||
return TextToSpeech.SUCCESS;
|
||||
}
|
||||
@@ -646,7 +654,7 @@ public abstract class TextToSpeechService extends Service {
|
||||
|
||||
@Override
|
||||
protected int playImpl() {
|
||||
mToken = new SilenceMessageParams(this, mDuration);
|
||||
mToken = new SilenceMessageParams(this, getCallingApp(), mDuration);
|
||||
mAudioPlaybackHandler.enqueueSilence(mToken);
|
||||
return TextToSpeech.SUCCESS;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user