From 59bea7f7414d28c3b515f59b25cab9d19842247c Mon Sep 17 00:00:00 2001 From: chgenly Date: Mon, 10 Sep 2012 22:30:22 -0700 Subject: [PATCH] Changes to use audio focus for TTS engine. --- .../voice/AbstractPrologCommandPlayer.java | 65 ++++++++++++++++++- .../plus/voice/MediaCommandPlayerImpl.java | 62 ++++-------------- .../plus/voice/TTSCommandPlayerImpl.java | 27 ++++++++ 3 files changed, 102 insertions(+), 52 deletions(-) diff --git a/OsmAnd/src/net/osmand/plus/voice/AbstractPrologCommandPlayer.java b/OsmAnd/src/net/osmand/plus/voice/AbstractPrologCommandPlayer.java index 8f581ec747..9daec0f271 100644 --- a/OsmAnd/src/net/osmand/plus/voice/AbstractPrologCommandPlayer.java +++ b/OsmAnd/src/net/osmand/plus/voice/AbstractPrologCommandPlayer.java @@ -28,12 +28,13 @@ import alice.tuprolog.Struct; import alice.tuprolog.Term; import alice.tuprolog.Theory; import alice.tuprolog.Var; +import android.annotation.SuppressLint; import android.content.Context; +import android.media.AudioManager; public abstract class AbstractPrologCommandPlayer implements CommandPlayer { - private static final Log log = LogUtil - .getLog(AbstractPrologCommandPlayer.class); + private static final Log log = LogUtil.getLog(AbstractPrologCommandPlayer.class); protected Context ctx; protected File voiceDir; @@ -51,10 +52,14 @@ public abstract class AbstractPrologCommandPlayer implements CommandPlayer { protected static final String DELAY_CONST = "delay_"; /** Must be sorted array! */ private final int[] sortedVoiceVersions; + private AudioFocusHelper mAudioFocusHelper; + + private int streamType; protected AbstractPrologCommandPlayer(Context ctx, OsmandSettings settings, String voiceProvider, String configFile, int[] sortedVoiceVersions) throws CommandPlayerException { + this.ctx = ctx; this.sortedVoiceVersions = sortedVoiceVersions; long time = System.currentTimeMillis(); try { @@ -67,6 +72,7 @@ public abstract class AbstractPrologCommandPlayer implements CommandPlayer { if (log.isInfoEnabled()) { log.info("Initializing prolog system : " + (System.currentTimeMillis() - time)); //$NON-NLS-1$ } + this.streamType = settings.AUDIO_STREAM_GUIDANCE.get(); init(voiceProvider, settings, configFile); } @@ -189,4 +195,59 @@ public abstract class AbstractPrologCommandPlayer implements CommandPlayer { prologSystem = null; } + @Override + public void updateAudioStream(int streamType) { + this.streamType = streamType; + } + + protected void requestAudioFocus() { + log.debug("requestAudioFocus"); + if (android.os.Build.VERSION.SDK_INT >= 8) { + mAudioFocusHelper = new AudioFocusHelper(ctx); + } + if (mAudioFocusHelper != null) + mAudioFocusHelper.requestFocus(); + } + + protected void abandonAudioFocus() { + log.debug("abandonAudioFocus"); + if (mAudioFocusHelper != null) { + mAudioFocusHelper.abandonFocus(); + mAudioFocusHelper = null; + } + } + + /** + * This helper class allows API level 8 calls to be isolated from the rest of the app. + * This class is only be instantiated on OS versions which support it. + * @author genly + * + */ + // We Use API level 8 calls here, suppress warnings. + @SuppressLint("NewApi") + public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener { + private Context mContext; + private AudioManager mAudioManager; + + public AudioFocusHelper(Context context) { + mContext = context; + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + } + + public boolean requestFocus() { + return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == + mAudioManager.requestAudioFocus(this, streamType, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); + } + + public boolean abandonFocus() { + return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.abandonAudioFocus(this); + } + @Override + public void onAudioFocusChange(int focusChange) { + // Basically we ignore audio focus changes. There's not much we can do when we have interrupted audio + // for our speech, and we in turn get interrupted. Ignore it until a scenario comes up which gives us + // reason to change this strategy. + log.error("MediaCommandPlayerImpl.onAudioFocusChange(): Unexpected audio focus change: "+focusChange); + } + } } diff --git a/OsmAnd/src/net/osmand/plus/voice/MediaCommandPlayerImpl.java b/OsmAnd/src/net/osmand/plus/voice/MediaCommandPlayerImpl.java index 325b67e3ee..2645849cc0 100644 --- a/OsmAnd/src/net/osmand/plus/voice/MediaCommandPlayerImpl.java +++ b/OsmAnd/src/net/osmand/plus/voice/MediaCommandPlayerImpl.java @@ -11,11 +11,10 @@ import net.osmand.plus.OsmandSettings; import org.apache.commons.logging.Log; -import android.annotation.SuppressLint; import android.content.Context; -import android.media.AudioManager; import android.media.MediaPlayer; + /** * That class represents command player. * It gets commands from input, analyze what files should be played and play @@ -33,21 +32,12 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implemen // indicates that player is ready to play first file private List filesToPlay = Collections.synchronizedList(new ArrayList()); private int streamType; - private final Context mCtx; - private AudioFocusHelper mAudioFocusHelper; public MediaCommandPlayerImpl(Context ctx, OsmandSettings settings, String voiceProvider) throws CommandPlayerException { super(ctx, settings, voiceProvider, CONFIG_FILE, MEDIA_VOICE_VERSION); - mCtx = ctx; - this.streamType = settings.AUDIO_STREAM_GUIDANCE.get(); - } - - @Override - public void updateAudioStream(int streamType) { - this.streamType = streamType; } @Override @@ -62,13 +52,7 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implemen // If we have not already started to play audio, start. if (mediaPlayer == null) { - if (android.os.Build.VERSION.SDK_INT >= 8) { - mAudioFocusHelper = new AudioFocusHelper(mCtx); - } else { - mAudioFocusHelper = null; - } - if (mAudioFocusHelper != null) - mAudioFocusHelper.requestFocus(); + requestAudioFocus(); playQueue(); } } @@ -89,14 +73,17 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implemen mediaPlayer.release(); mediaPlayer = null; - if (mAudioFocusHelper != null) - mAudioFocusHelper.abandonFocus(); + abandonAudioFocus(); } } } + /** + * Called when the MediaPlayer is done. + */ @Override public void onCompletion(MediaPlayer mp) { + // Work on the next file to play. playQueue(); } @@ -131,6 +118,11 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implemen return null; } + /** + * Starts the MediaPlayer playing a file. This method will return immediately. + * OnCompletionListener() will be called when the MediaPlayer is done. + * @param file + */ private void playFile(File file) { log.debug("Playing file : " + file); //$NON-NLS-1$ try { @@ -150,34 +142,4 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implemen return new File(voiceDir, CONFIG_FILE).exists(); } - /** - * This helper class allows API level 8 calls to be isolated from the rest of the app. - * This class is only be instantiated on OS versions which support it. - * @author genly - * - */ - // We Use API level 8 calls here, suppress warnings. - @SuppressLint("NewApi") - public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener { - private Context mContext; - private AudioManager mAudioManager; - - public AudioFocusHelper(Context context) { - mContext = context; - mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - } - - public boolean requestFocus() { - return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == - mAudioManager.requestAudioFocus(this, streamType, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); - } - - public boolean abandonFocus() { - return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.abandonAudioFocus(this); - } - @Override - public void onAudioFocusChange(int focusChange) { - log.error("MediaCommandPlayerImpl.onAudioFocusChange(): Unexpected audio focus change: "+focusChange); - } - } } diff --git a/OsmAnd/src/net/osmand/plus/voice/TTSCommandPlayerImpl.java b/OsmAnd/src/net/osmand/plus/voice/TTSCommandPlayerImpl.java index ec7f15d27f..3ad601c670 100644 --- a/OsmAnd/src/net/osmand/plus/voice/TTSCommandPlayerImpl.java +++ b/OsmAnd/src/net/osmand/plus/voice/TTSCommandPlayerImpl.java @@ -5,7 +5,10 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; +import org.apache.commons.logging.Log; + import net.osmand.Algoritms; +import net.osmand.LogUtil; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandSettings; import net.osmand.plus.R; @@ -21,6 +24,7 @@ import android.content.Intent; import android.net.Uri; import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech.OnInitListener; +import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener; public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer { @@ -53,6 +57,7 @@ public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer { private static final String CONFIG_FILE = "_ttsconfig.p"; private static final int[] TTS_VOICE_VERSION = new int[] { 100, 101 }; // !! MUST BE SORTED + private static final Log log = LogUtil.getLog(TTSCommandPlayerImpl.class); private TextToSpeech mTts; private Context mTtsContext; private String language; @@ -76,6 +81,15 @@ public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer { + /** + * Since TTS requests are asynchronous, playCommands() can be called before + * the TTS engine is done. We use this field to keep track of concurrent tts + * activity. Where tts activity is defined as the time between tts.speak() + * and the call back to onUtteranceCompletedListener(). This allows us to + * optimize use of requesting and abandoning audio focus. + */ + private int ttsRequests; + @Override public void playCommands(CommandBuilder builder) { if (mTts != null) { @@ -84,7 +98,11 @@ public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer { for (String s : execute) { bld.append(s).append(' '); } + if (ttsRequests++ == 0) + requestAudioFocus(); + log.debug("ttsRequests="+ttsRequests); mTts.speak(bld.toString(), TextToSpeech.QUEUE_ADD, params); + // Audio focus will be released when onUtteranceCompleted() completed is called by the TTS engine. } } @@ -141,6 +159,14 @@ public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer { return ctx instanceof SettingsActivity; } }); + mTts.setOnUtteranceCompletedListener(new OnUtteranceCompletedListener() { + @Override + public void onUtteranceCompleted(String utteranceId) { + if (--ttsRequests == 0) + abandonAudioFocus(); + log.debug("ttsRequests="+ttsRequests); + } + }); } } @@ -175,6 +201,7 @@ public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer { @Override public void updateAudioStream(int streamType) { + super.updateAudioStream(streamType); params.put(TextToSpeech.Engine.KEY_PARAM_STREAM, streamType+""); }