Merge remote-tracking branch 'origin/master'

This commit is contained in:
Weblate 2012-09-12 16:00:33 +02:00
commit 35bec870ee
3 changed files with 183 additions and 70 deletions

View file

@ -28,12 +28,13 @@ import alice.tuprolog.Struct;
import alice.tuprolog.Term; import alice.tuprolog.Term;
import alice.tuprolog.Theory; import alice.tuprolog.Theory;
import alice.tuprolog.Var; import alice.tuprolog.Var;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.media.AudioManager;
public abstract class AbstractPrologCommandPlayer implements CommandPlayer { public abstract class AbstractPrologCommandPlayer implements CommandPlayer {
private static final Log log = LogUtil private static final Log log = LogUtil.getLog(AbstractPrologCommandPlayer.class);
.getLog(AbstractPrologCommandPlayer.class);
protected Context ctx; protected Context ctx;
protected File voiceDir; protected File voiceDir;
@ -51,10 +52,14 @@ public abstract class AbstractPrologCommandPlayer implements CommandPlayer {
protected static final String DELAY_CONST = "delay_"; protected static final String DELAY_CONST = "delay_";
/** Must be sorted array! */ /** Must be sorted array! */
private final int[] sortedVoiceVersions; private final int[] sortedVoiceVersions;
private AudioFocusHelper mAudioFocusHelper;
private int streamType;
protected AbstractPrologCommandPlayer(Context ctx, OsmandSettings settings, String voiceProvider, String configFile, int[] sortedVoiceVersions) protected AbstractPrologCommandPlayer(Context ctx, OsmandSettings settings, String voiceProvider, String configFile, int[] sortedVoiceVersions)
throws CommandPlayerException throws CommandPlayerException
{ {
this.ctx = ctx;
this.sortedVoiceVersions = sortedVoiceVersions; this.sortedVoiceVersions = sortedVoiceVersions;
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
try { try {
@ -67,6 +72,7 @@ public abstract class AbstractPrologCommandPlayer implements CommandPlayer {
if (log.isInfoEnabled()) { if (log.isInfoEnabled()) {
log.info("Initializing prolog system : " + (System.currentTimeMillis() - time)); //$NON-NLS-1$ log.info("Initializing prolog system : " + (System.currentTimeMillis() - time)); //$NON-NLS-1$
} }
this.streamType = settings.AUDIO_STREAM_GUIDANCE.get();
init(voiceProvider, settings, configFile); init(voiceProvider, settings, configFile);
} }
@ -189,4 +195,59 @@ public abstract class AbstractPrologCommandPlayer implements CommandPlayer {
prologSystem = null; 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);
}
}
} }

View file

@ -14,12 +14,13 @@ import org.apache.commons.logging.Log;
import android.content.Context; import android.content.Context;
import android.media.MediaPlayer; import android.media.MediaPlayer;
/** /**
* That class represents command player. * That class represents command player.
* It gets commands from input, analyze what files should be played and play * It gets commands from input, analyze what files should be played and play
* them using media player * them using media player
*/ */
public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer { public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implements MediaPlayer.OnCompletionListener {
private static final String CONFIG_FILE = "_config.p"; private static final String CONFIG_FILE = "_config.p";
private static final int[] MEDIA_VOICE_VERSION = new int[] { 0 }; // MUST BE SORTED, list of supported versions private static final int[] MEDIA_VOICE_VERSION = new int[] { 0 }; // MUST BE SORTED, list of supported versions
@ -29,7 +30,6 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer {
// playing media // playing media
private MediaPlayer mediaPlayer; private MediaPlayer mediaPlayer;
// indicates that player is ready to play first file // indicates that player is ready to play first file
private volatile boolean playNext = true;
private List<String> filesToPlay = Collections.synchronizedList(new ArrayList<String>()); private List<String> filesToPlay = Collections.synchronizedList(new ArrayList<String>());
private int streamType; private int streamType;
@ -38,14 +38,6 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer {
throws CommandPlayerException throws CommandPlayerException
{ {
super(ctx, settings, voiceProvider, CONFIG_FILE, MEDIA_VOICE_VERSION); super(ctx, settings, voiceProvider, CONFIG_FILE, MEDIA_VOICE_VERSION);
mediaPlayer = new MediaPlayer();
this.streamType = settings.AUDIO_STREAM_GUIDANCE.get();
mediaPlayer.setAudioStreamType(streamType);
}
@Override
public void updateAudioStream(int streamType) {
this.streamType = streamType;
} }
@Override @Override
@ -54,74 +46,104 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer {
mediaPlayer = null; mediaPlayer = null;
} }
// Called from the calculating route thread.
@Override @Override
public void playCommands(CommandBuilder builder){ public synchronized void playCommands(CommandBuilder builder) {
filesToPlay.addAll(builder.execute()); filesToPlay.addAll(builder.execute());
playQueue();
// If we have not already started to play audio, start.
if (mediaPlayer == null) {
requestAudioFocus();
playQueue();
}
} }
private synchronized void playQueue() { private synchronized void playQueue() {
while (!filesToPlay.isEmpty() && playNext) { if (mediaPlayer == null) {
mediaPlayer = new MediaPlayer();
}
performDelays();
File file = getNextFileToPlay();
if (file != null) {
playFile(file);
// Will continue with onCompletion()
} else {
// Release the media player only when we are done speaking.
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
abandonAudioFocus();
}
}
}
/**
* Called when the MediaPlayer is done. The call back is on the main thread.
*/
@Override
public void onCompletion(MediaPlayer mp) {
// Work on the next file to play.
playQueue();
}
private void performDelays() {
int sleep = 0;
while (!filesToPlay.isEmpty() && filesToPlay.get(0).startsWith(DELAY_CONST)) {
String s = filesToPlay.remove(0).substring(DELAY_CONST.length());
try {
sleep += Integer.parseInt(s);
} catch (NumberFormatException e) {
}
}
try {
if (sleep != 0) {
log.debug("Delaying "+sleep);
Thread.sleep(sleep);
}
} catch (InterruptedException e) {
}
}
private File getNextFileToPlay() {
while (!filesToPlay.isEmpty()) {
String f = filesToPlay.remove(0); String f = filesToPlay.remove(0);
if (f != null && voiceDir != null) { if (f != null && voiceDir != null) {
boolean exists = false; File file = new File(voiceDir, f);
// if(voiceZipFile != null){ return file;
// ZipEntry entry = voiceZipFile.getEntry(f); }
// exists = entry != null; }
// voiceZipFile.getInputStream(entry); return null;
// }
// } else {
File file = new File(voiceDir, f); /**
exists = file.exists(); * Starts the MediaPlayer playing a file. This method will return immediately.
// } * OnCompletionListener() will be called when the MediaPlayer is done.
if (exists) { * @param file
log.debug("Playing file : " + f); //$NON-NLS-1$ */
playNext = false; private void playFile(File file) {
try { if (!file.exists()) {
// Can't play sound file from zip it seams to be impossible only unpack and play!!! log.error("Unable to play, does not exist: "+file);
mediaPlayer.setDataSource(file.getAbsolutePath()); playQueue();
mediaPlayer.prepare(); return;
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { }
@Override try {
public void onCompletion(MediaPlayer mp) { log.debug("Playing file : " + file); //$NON-NLS-1$
mp.release(); mediaPlayer.reset();
mediaPlayer = new MediaPlayer(); mediaPlayer.setAudioStreamType(streamType);
mediaPlayer.setAudioStreamType(streamType); mediaPlayer.setDataSource(file.getAbsolutePath());
int sleep = 60; mediaPlayer.prepare();
boolean delay = true; mediaPlayer.setOnCompletionListener(this);
while (!filesToPlay.isEmpty() && delay) { mediaPlayer.start();
delay = filesToPlay.get(0).startsWith(DELAY_CONST); } catch (Exception e) {
if (delay) { log.error("Error while playing voice command", e); //$NON-NLS-1$
String s = filesToPlay.remove(0).substring(DELAY_CONST.length()); playQueue();
try {
sleep += Integer.parseInt(s);
} catch (NumberFormatException e) {
}
}
}
try {
Thread.sleep(sleep);
} catch (InterruptedException e) {
}
playNext = true;
playQueue();
}
});
mediaPlayer.start();
} catch (Exception e) {
log.error("Error while playing voice command", e); //$NON-NLS-1$
playNext = true;
}
} else {
log.info("Play file not found : " + f); //$NON-NLS-1$
}
}
} }
} }
public static boolean isMyData(File voiceDir) { public static boolean isMyData(File voiceDir) {
return new File(voiceDir, CONFIG_FILE).exists(); return new File(voiceDir, CONFIG_FILE).exists();
} }
} }

View file

@ -5,7 +5,10 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import org.apache.commons.logging.Log;
import net.osmand.Algoritms; import net.osmand.Algoritms;
import net.osmand.LogUtil;
import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandSettings; import net.osmand.plus.OsmandSettings;
import net.osmand.plus.R; import net.osmand.plus.R;
@ -21,6 +24,7 @@ import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener; import android.speech.tts.TextToSpeech.OnInitListener;
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer { 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 String CONFIG_FILE = "_ttsconfig.p";
private static final int[] TTS_VOICE_VERSION = new int[] { 100, 101 }; // !! MUST BE SORTED 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 TextToSpeech mTts;
private Context mTtsContext; private Context mTtsContext;
private String language; private String language;
@ -76,15 +81,30 @@ 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;
// Called from the calculating route thread.
@Override @Override
public void playCommands(CommandBuilder builder) { public synchronized void playCommands(CommandBuilder builder) {
if (mTts != null) { if (mTts != null) {
final List<String> execute = builder.execute(); //list of strings, the speech text, play it final List<String> execute = builder.execute(); //list of strings, the speech text, play it
StringBuilder bld = new StringBuilder(); StringBuilder bld = new StringBuilder();
for (String s : execute) { for (String s : execute) {
bld.append(s).append(' '); bld.append(s).append(' ');
} }
if (ttsRequests++ == 0)
requestAudioFocus();
log.debug("ttsRequests="+ttsRequests);
params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,""+System.currentTimeMillis());
mTts.speak(bld.toString(), TextToSpeech.QUEUE_ADD, params); mTts.speak(bld.toString(), TextToSpeech.QUEUE_ADD, params);
// Audio focus will be released when onUtteranceCompleted() completed is called by the TTS engine.
} }
} }
@ -141,6 +161,15 @@ public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer {
return ctx instanceof SettingsActivity; return ctx instanceof SettingsActivity;
} }
}); });
mTts.setOnUtteranceCompletedListener(new OnUtteranceCompletedListener() {
// The call back is on a binder thread.
@Override
public synchronized void onUtteranceCompleted(String utteranceId) {
if (--ttsRequests == 0)
abandonAudioFocus();
log.debug("ttsRequests="+ttsRequests);
}
});
} }
} }
@ -175,6 +204,7 @@ public class TTSCommandPlayerImpl extends AbstractPrologCommandPlayer {
@Override @Override
public void updateAudioStream(int streamType) { public void updateAudioStream(int streamType) {
super.updateAudioStream(streamType);
params.put(TextToSpeech.Engine.KEY_PARAM_STREAM, streamType+""); params.put(TextToSpeech.Engine.KEY_PARAM_STREAM, streamType+"");
} }