From 638f487edab5a3e9d6a9429c8ad236dba2db5435 Mon Sep 17 00:00:00 2001 From: PaulStets Date: Fri, 27 Jul 2018 16:30:45 +0300 Subject: [PATCH] Started adding js voice guidance --- OsmAnd/res/values/strings.xml | 2 + .../src/net/osmand/plus/AppInitializer.java | 5 +- .../src/net/osmand/plus/OsmandSettings.java | 2 + .../SettingsDevelopmentActivity.java | 3 + .../net/osmand/plus/routing/VoiceRouter.java | 3 + .../plus/voice/AbstractJSCommandPlayer.java | 54 +++++ .../net/osmand/plus/voice/CommandBuilder.java | 4 +- .../osmand/plus/voice/JSCommandBuilder.java | 220 ++++++++++++++++++ .../plus/voice/JSTTSCommandPlayerImpl.java | 106 +++++++++ 9 files changed, 396 insertions(+), 3 deletions(-) create mode 100644 OsmAnd/src/net/osmand/plus/voice/AbstractJSCommandPlayer.java create mode 100644 OsmAnd/src/net/osmand/plus/voice/JSCommandBuilder.java create mode 100644 OsmAnd/src/net/osmand/plus/voice/JSTTSCommandPlayerImpl.java diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index 367bcab4bc..6ef6f2f9ca 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -2913,4 +2913,6 @@ Searching for the corresponding wiki article Article not found How to open Wikipedia articles? + Use JS voice guidance + Use new voice guidance logic based on JavaScript diff --git a/OsmAnd/src/net/osmand/plus/AppInitializer.java b/OsmAnd/src/net/osmand/plus/AppInitializer.java index 02b129ced4..dd4dd2d1d8 100644 --- a/OsmAnd/src/net/osmand/plus/AppInitializer.java +++ b/OsmAnd/src/net/osmand/plus/AppInitializer.java @@ -46,6 +46,7 @@ import net.osmand.plus.search.QuickSearchHelper; import net.osmand.plus.views.corenative.NativeCoreContext; import net.osmand.plus.voice.CommandPlayer; import net.osmand.plus.voice.CommandPlayerException; +import net.osmand.plus.voice.JSTTSCommandPlayerImpl; import net.osmand.plus.voice.MediaCommandPlayerImpl; import net.osmand.plus.voice.TTSCommandPlayerImpl; import net.osmand.plus.wikivoyage.data.TravelDbHelper; @@ -577,7 +578,9 @@ public class AppInitializer implements IProgress { if (!voiceDir.exists()) { throw new CommandPlayerException(ctx.getString(R.string.voice_data_unavailable)); } - + if (app.getSettings().USE_JS_VOICE_GUIDANCE.get()) { + return new JSTTSCommandPlayerImpl(osmandApplication, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider); + } if (MediaCommandPlayerImpl.isMyData(voiceDir)) { return new MediaCommandPlayerImpl(osmandApplication, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider); } else if (TTSCommandPlayerImpl.isMyData(voiceDir)) { diff --git a/OsmAnd/src/net/osmand/plus/OsmandSettings.java b/OsmAnd/src/net/osmand/plus/OsmandSettings.java index a83a591968..949cdaf490 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandSettings.java +++ b/OsmAnd/src/net/osmand/plus/OsmandSettings.java @@ -1340,6 +1340,8 @@ public class OsmandSettings { public final OsmandPreference ANIMATE_MY_LOCATION = new BooleanPreference("animate_my_location", true).makeGlobal().cache(); + public final OsmandPreference USE_JS_VOICE_GUIDANCE = new BooleanPreference("use_js_voice_guidance", false); + public final OsmandPreference ROUTE_MAP_MARKERS_START_MY_LOC = new BooleanPreference("route_map_markers_start_my_loc", false).makeGlobal().cache(); public final OsmandPreference ROUTE_MAP_MARKERS_ROUND_TRIP = new BooleanPreference("route_map_markers_round_trip", false).makeGlobal().cache(); diff --git a/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java b/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java index bece3ea19a..be1ae15e49 100644 --- a/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java +++ b/OsmAnd/src/net/osmand/plus/development/SettingsDevelopmentActivity.java @@ -63,6 +63,9 @@ public class SettingsDevelopmentActivity extends SettingsBaseActivity { R.string.animate_my_location, R.string.animate_my_location_desc)); + cat.addPreference(createCheckBoxPreference(settings.USE_JS_VOICE_GUIDANCE, getString(R.string.use_js_voice_guidance), + getString(R.string.use_js_voice_guidance_description))); + final Preference firstRunPreference = new Preference(this); firstRunPreference.setTitle(R.string.simulate_initial_startup); firstRunPreference.setSummary(R.string.simulate_initial_startup_descr); diff --git a/OsmAnd/src/net/osmand/plus/routing/VoiceRouter.java b/OsmAnd/src/net/osmand/plus/routing/VoiceRouter.java index af27e575ea..cab1e395ff 100644 --- a/OsmAnd/src/net/osmand/plus/routing/VoiceRouter.java +++ b/OsmAnd/src/net/osmand/plus/routing/VoiceRouter.java @@ -69,6 +69,8 @@ public class VoiceRouter { private static RouteDirectionInfo nextRouteDirection; private Term empty; + private boolean useJS; + public interface VoiceMessageListener { void onVoiceMessage(); } @@ -78,6 +80,7 @@ public class VoiceRouter { public VoiceRouter(RoutingHelper router, final OsmandSettings settings) { this.router = router; this.settings = settings; + useJS = settings.USE_JS_VOICE_GUIDANCE.get(); this.mute = settings.VOICE_MUTE.get(); empty = new Struct(""); voiceMessageListeners = new ConcurrentHashMap(); diff --git a/OsmAnd/src/net/osmand/plus/voice/AbstractJSCommandPlayer.java b/OsmAnd/src/net/osmand/plus/voice/AbstractJSCommandPlayer.java new file mode 100644 index 0000000000..09db3cee5b --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/voice/AbstractJSCommandPlayer.java @@ -0,0 +1,54 @@ +package net.osmand.plus.voice; + +import java.util.List; + +import alice.tuprolog.Struct; + +public class AbstractJSCommandPlayer implements CommandPlayer { + @Override + public String getCurrentVoice() { + return null; + } + + @Override + public CommandBuilder newCommandBuilder() { + JSCommandBuilder commandBuilder = new JSCommandBuilder(this); + commandBuilder.setParameters("km-m", true); + return commandBuilder; + } + + @Override + public void playCommands(CommandBuilder builder) { + + } + + @Override + public void clear() { + + } + + @Override + public List execute(List listStruct) { + return null; + } + + @Override + public void updateAudioStream(int streamType) { + + } + + @Override + public String getLanguage() { + return null; + } + + @Override + public boolean supportsStructuredStreetNames() { + return true; + } + + @Override + public void stop() { + + } +} diff --git a/OsmAnd/src/net/osmand/plus/voice/CommandBuilder.java b/OsmAnd/src/net/osmand/plus/voice/CommandBuilder.java index 6e17ac0543..01640b1c34 100644 --- a/OsmAnd/src/net/osmand/plus/voice/CommandBuilder.java +++ b/OsmAnd/src/net/osmand/plus/voice/CommandBuilder.java @@ -53,8 +53,8 @@ public class CommandBuilder { /** * */ - private final CommandPlayer commandPlayer; - private boolean alreadyExecuted = false; + protected final CommandPlayer commandPlayer; + protected boolean alreadyExecuted = false; private List listStruct = new ArrayList(); public CommandBuilder(CommandPlayer commandPlayer){ diff --git a/OsmAnd/src/net/osmand/plus/voice/JSCommandBuilder.java b/OsmAnd/src/net/osmand/plus/voice/JSCommandBuilder.java new file mode 100644 index 0000000000..e8bffb4912 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/voice/JSCommandBuilder.java @@ -0,0 +1,220 @@ +package net.osmand.plus.voice; + +import net.osmand.PlatformUtil; + +import org.apache.commons.logging.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class JSCommandBuilder extends CommandBuilder { + + private static final Log log = PlatformUtil.getLog(JSCommandBuilder.class); + + protected static final String C_PREPARE_TURN = "prepare_turn"; //$NON-NLS-1$ + protected static final String C_PREPARE_ROUNDABOUT = "prepare_roundabout"; //$NON-NLS-1$ + protected static final String C_PREPARE_MAKE_UT = "prepare_make_ut"; //$NON-NLS-1$ + protected static final String C_ROUNDABOUT = "roundabout"; //$NON-NLS-1$ + protected static final String C_GO_AHEAD = "go_ahead"; //$NON-NLS-1$ + protected static final String C_TURN = "turn"; //$NON-NLS-1$ + protected static final String C_MAKE_UT = "make_ut"; //$NON-NLS-1$ + protected static final String C_MAKE_UTWP = "make_ut_wp"; //$NON-NLS-1$ + protected static final String C_AND_ARRIVE_DESTINATION = "and_arrive_destination"; //$NON-NLS-1$ + protected static final String C_REACHED_DESTINATION = "reached_destination"; //$NON-NLS-1$ + protected static final String C_AND_ARRIVE_INTERMEDIATE = "and_arrive_intermediate"; //$NON-NLS-1$ + protected static final String C_REACHED_INTERMEDIATE = "reached_intermediate"; //$NON-NLS-1$ + protected static final String C_AND_ARRIVE_WAYPOINT = "and_arrive_waypoint"; //$NON-NLS-1$ + protected static final String C_AND_ARRIVE_FAVORITE = "and_arrive_favorite"; //$NON-NLS-1$ + protected static final String C_AND_ARRIVE_POI_WAYPOINT = "and_arrive_poi"; //$NON-NLS-1$ + protected static final String C_REACHED_WAYPOINT = "reached_waypoint"; //$NON-NLS-1$ + protected static final String C_REACHED_FAVORITE = "reached_favorite"; //$NON-NLS-1$ + protected static final String C_REACHED_POI = "reached_poi"; //$NON-NLS-1$ + protected static final String C_THEN = "then"; //$NON-NLS-1$ + protected static final String C_SPEAD_ALARM = "speed_alarm"; //$NON-NLS-1$ + protected static final String C_ATTENTION = "attention"; //$NON-NLS-1$ + protected static final String C_OFF_ROUTE = "off_route"; //$NON-NLS-1$ + protected static final String C_BACK_ON_ROUTE ="back_on_route"; //$NON-NLS-1$ + + + protected static final String C_BEAR_LEFT = "bear_left"; //$NON-NLS-1$ + protected static final String C_BEAR_RIGHT = "bear_right"; //$NON-NLS-1$ + protected static final String C_ROUTE_RECALC = "route_recalc"; //$NON-NLS-1$ + protected static final String C_ROUTE_NEW_CALC = "route_new_calc"; //$NON-NLS-1$ + protected static final String C_LOCATION_LOST = "location_lost"; //$NON-NLS-1$ + protected static final String C_LOCATION_RECOVERED = "location_recovered"; //$NON-NLS-1$ + + + private List listStruct = new ArrayList<>(); + + public JSCommandBuilder(CommandPlayer commandPlayer) { + super(commandPlayer); + } + + + public void setParameters(String metricCons, boolean tts) { + // TODO Set the parameters to js context + } + + private JSCommandBuilder addCommand(String name, Object... args){ + // TODO add JSCore + listStruct.add(name); + return this; + } + + public JSCommandBuilder goAhead(){ + return goAhead(-1, new HashMap()); + } + + public JSCommandBuilder goAhead(double dist, Map streetName){ + return addCommand(C_GO_AHEAD, dist, streetName); + } + + public JSCommandBuilder makeUTwp(){ + return makeUT(new HashMap()); + } + + public JSCommandBuilder makeUT(Map streetName){ + return addCommand(C_MAKE_UT, streetName); + } + @Override + public JSCommandBuilder speedAlarm(int maxSpeed, float speed){ + return addCommand(C_SPEAD_ALARM, maxSpeed, speed); + } + @Override + public JSCommandBuilder attention(String type){ + return addCommand(C_ATTENTION, type); + } + @Override + public JSCommandBuilder offRoute(double dist){ + return addCommand(C_OFF_ROUTE, dist); + } + @Override + public CommandBuilder backOnRoute(){ + return addCommand(C_BACK_ON_ROUTE); + } + + public JSCommandBuilder makeUT(double dist, Map streetName){ + return addCommand(C_MAKE_UT, dist, streetName); + } + + public JSCommandBuilder prepareMakeUT(double dist, Map streetName){ + return addCommand(C_PREPARE_MAKE_UT, dist, streetName); + } + + + public JSCommandBuilder turn(String param, Map streetName) { + return addCommand(C_TURN, param, streetName); + } + + public JSCommandBuilder turn(String param, double dist, Map streetName){ + return addCommand(C_TURN, param, dist, streetName); + } + + /** + * + * @param param A_LEFT, A_RIGHT, ... + * @param dist + * @return + */ + public JSCommandBuilder prepareTurn(String param, double dist, Map streetName){ + return addCommand(C_PREPARE_TURN, param, dist, streetName); + } + + public JSCommandBuilder prepareRoundAbout(double dist, int exit, Map streetName){ + return addCommand(C_PREPARE_ROUNDABOUT, dist, exit, streetName); + } + + public JSCommandBuilder roundAbout(double dist, double angle, int exit, Map streetName){ + return addCommand(C_ROUNDABOUT, dist, angle, exit, streetName); + } + + public JSCommandBuilder roundAbout(double angle, int exit, Map streetName) { + return roundAbout(-1, angle, exit, streetName); + } + @Override + public JSCommandBuilder andArriveAtDestination(String name){ + return addCommand(C_AND_ARRIVE_DESTINATION, name); + } + @Override + public JSCommandBuilder arrivedAtDestination(String name){ + return addCommand(C_REACHED_DESTINATION, name); + } + @Override + public JSCommandBuilder andArriveAtIntermediatePoint(String name){ + return addCommand(C_AND_ARRIVE_INTERMEDIATE, name); + } + @Override + public JSCommandBuilder arrivedAtIntermediatePoint(String name) { + return addCommand(C_REACHED_INTERMEDIATE, name); + } + @Override + public JSCommandBuilder andArriveAtWayPoint(String name){ + return addCommand(C_AND_ARRIVE_WAYPOINT, name); + } + @Override + public JSCommandBuilder arrivedAtWayPoint(String name) { + return addCommand(C_REACHED_WAYPOINT, name); + } + @Override + public JSCommandBuilder andArriveAtFavorite(String name) { + return addCommand(C_AND_ARRIVE_FAVORITE, name); + } + @Override + public JSCommandBuilder arrivedAtFavorite(String name) { + return addCommand(C_REACHED_FAVORITE, name); + } + @Override + public JSCommandBuilder andArriveAtPoi(String name) { + return addCommand(C_AND_ARRIVE_POI_WAYPOINT, name); + } + @Override + public JSCommandBuilder arrivedAtPoi(String name) { + return addCommand(C_REACHED_POI, name); + } + + public JSCommandBuilder bearLeft(Map streetName){ + return addCommand(C_BEAR_LEFT, streetName); + } + + public JSCommandBuilder bearRight(Map streetName){ + return addCommand(C_BEAR_RIGHT, streetName); + } + + @Override + public JSCommandBuilder then(){ + return addCommand(C_THEN); + } + + @Override + public JSCommandBuilder gpsLocationLost() { + return addCommand(C_LOCATION_LOST); + } + + @Override + public JSCommandBuilder gpsLocationRecover() { + return addCommand(C_LOCATION_RECOVERED); + } + + @Override + public JSCommandBuilder newRouteCalculated(double dist, int time){ + return addCommand(C_ROUTE_NEW_CALC, dist, time); + } + + @Override + public JSCommandBuilder routeRecalculated(double dist, int time){ + return addCommand(C_ROUTE_RECALC, dist, time); + } + + @Override + public void play(){ + this.commandPlayer.playCommands(this); + } + + @Override + protected List execute(){ + alreadyExecuted = true; + return listStruct; + } +} diff --git a/OsmAnd/src/net/osmand/plus/voice/JSTTSCommandPlayerImpl.java b/OsmAnd/src/net/osmand/plus/voice/JSTTSCommandPlayerImpl.java new file mode 100644 index 0000000000..4caefad1f7 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/voice/JSTTSCommandPlayerImpl.java @@ -0,0 +1,106 @@ +package net.osmand.plus.voice; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.speech.tts.TextToSpeech; +import android.support.v7.app.AlertDialog; +import android.util.Log; + +import net.osmand.plus.ApplicationMode; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.routing.VoiceRouter; + +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +public class JSTTSCommandPlayerImpl extends AbstractJSCommandPlayer { + + private static final String TAG = JSTTSCommandPlayerImpl.class.getSimpleName(); + private static TextToSpeech mTts; + + private OsmandApplication app; + private ApplicationMode appMode; + private VoiceRouter vrt; + private String voiceProvider; + + private HashMap params = new HashMap(); + + private static int ttsRequests = 0; + + public JSTTSCommandPlayerImpl(OsmandApplication ctx, ApplicationMode applicationMode, VoiceRouter vrt, String voiceProvider) { + this.app = ctx; + this.appMode = applicationMode; + this.vrt = vrt; + this.voiceProvider = voiceProvider; + mTts = new TextToSpeech(ctx, null); + } + + @Override + public String getCurrentVoice() { + return null; + } + + @Override + public JSCommandBuilder newCommandBuilder() { + JSCommandBuilder commandBuilder = new JSCommandBuilder(this); + commandBuilder.setParameters(app.getSettings().METRIC_SYSTEM.get().toHumanString(app), true); + return commandBuilder; + } + + @Override + public void playCommands(CommandBuilder builder) { + final List execute = builder.execute(); //list of strings, the speech text, play it + StringBuilder bld = new StringBuilder(); + for (String s : execute) { + bld.append(s).append(' '); + } + if (mTts != null && !vrt.isMute()) { + if (ttsRequests++ == 0) { + // Delay first prompt of each batch to allow BT SCO connection being established + if (app.getSettings().AUDIO_STREAM_GUIDANCE.getModeValue(appMode) == 0) { + ttsRequests++; + if (android.os.Build.VERSION.SDK_INT < 21) { + params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,""+System.currentTimeMillis()); + mTts.playSilence(app.getSettings().BT_SCO_DELAY.get(), TextToSpeech.QUEUE_ADD, params); + } else { + mTts.playSilentUtterance(app.getSettings().BT_SCO_DELAY.get(), TextToSpeech.QUEUE_ADD, ""+System.currentTimeMillis()); + } + } + } + Log.d(TAG, "ttsRequests= "+ttsRequests); + params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,""+System.currentTimeMillis()); + mTts.speak(bld.toString(), TextToSpeech.QUEUE_ADD, params); + // Audio focus will be released when onUtteranceCompleted() completed is called by the TTS engine. + } else if (app != null && vrt.isMute()) { + // sendAlertToAndroidWear(ctx, bld.toString()); + } + } + + @Override + public void clear() { + + } + + @Override + public void updateAudioStream(int streamType) { + + } + + @Override + public String getLanguage() { + return null; + } + + @Override + public boolean supportsStructuredStreetNames() { + return false; + } + + @Override + public void stop() { + + } +}