Merge branch 'master' of ssh://github.com/osmandapp/Osmand into MyLocationSharingMode

This commit is contained in:
Chumva 2018-08-09 13:53:33 +03:00
commit 7dc01155f5
32 changed files with 1444 additions and 127 deletions

View file

@ -34,7 +34,8 @@ public class IndexConstants {
public static final String GEN_LOG_EXT = ".gen.log"; //$NON-NLS-1$
public static final String VOICE_INDEX_EXT_ZIP = ".voice.zip"; //$NON-NLS-1$
public static final String TTSVOICE_INDEX_EXT_ZIP = ".ttsvoice.zip"; //$NON-NLS-1$
public static final String TTSVOICE_INDEX_EXT_ZIP = ".ttsvoice.zip";
public static final String TTSVOICE_INDEX_EXT_JS = "tts.js";
public static final String ANYVOICE_INDEX_EXT_ZIP = "voice.zip"; //$NON-NLS-1$ //to cactch both voices, .voice.zip and .ttsvoice.zip
public static final String FONT_INDEX_EXT = ".otf"; //$NON-NLS-1$
@ -67,9 +68,4 @@ public class IndexConstants {
public static final String VOICE_INDEX_DIR = "voice/"; //$NON-NLS-1$
public static final String RENDERERS_DIR = "rendering/"; //$NON-NLS-1$
public static final String ROUTING_XML_FILE= "routing.xml";
}

View file

@ -113,9 +113,9 @@ object AndroidUtils {
}
}
fun isGooglePlayInstalled(ctx: Context): Boolean {
fun isAppInstalled(ctx: Context, appPackage: String): Boolean {
try {
ctx.packageManager.getPackageInfo("com.android.vending", 0)
ctx.packageManager.getPackageInfo(appPackage, 0)
} catch (e: PackageManager.NameNotFoundException) {
return false
}
@ -127,7 +127,7 @@ object AndroidUtils {
Intent(Intent.ACTION_VIEW, Uri.parse(AndroidUtils.getPlayMarketLink(ctx, packageName)))
fun getPlayMarketLink(ctx: Context, packageName: String): String {
if (isGooglePlayInstalled(ctx)) {
if (isAppInstalled(ctx, "com.android.vending")) {
return "market://details?id=$packageName"
}
return "https://play.google.com/store/apps/details?id=$packageName"

View file

@ -43,6 +43,45 @@
<asset source="voice/zh/ttsconfig.p" destination="voice/zh-tts/_ttsconfig.p" mode="alwaysOverwriteOrCopy" />
<asset source="voice/zh-hk/ttsconfig.p" destination="voice/zh-hk-tts/_ttsconfig.p" mode="overwriteOnlyIfExists" />
<!--JavaScript files-->
<!--<asset source="voice/ar/ar_tts.js" destination="voice/ar-tts/ar_tts.js" mode="overwriteOnlyIfExists" />-->
<!--<asset source="voice/be/be_tts.js" destination="voice/be-tts/be_tts.js" mode="overwriteOnlyIfExists" />-->
<!--<asset source="voice/cs/cs_tts.js" destination="voice/cs-tts/cs_tts.js" mode="overwriteOnlyIfExists" />-->
<!--<asset source="voice/da/da_tts.js" destination="voice/da-tts/da_tts.js" mode="overwriteOnlyIfExists" />-->
<asset source="voice/de/de_tts.js" destination="voice/de-tts/de_tts.js" mode="alwaysOverwriteOrCopy" />
<!--<asset source="voice/el/el_tts.js" destination="voice/el-tts/el_tts.js" mode="overwriteOnlyIfExists" />-->
<asset source="voice/en/en_tts.js" destination="voice/en-tts/en_tts.js" mode="alwaysOverwriteOrCopy" />
<!--<asset source="voice/en-gb/en-gb_tts.js" destination="voice/en-gb-tts/en-gb_tts.js" mode="overwriteOnlyIfExists" />-->
<asset source="voice/es/es_tts.js" destination="voice/es-tts/es_tts.js" mode="alwaysOverwriteOrCopy" />
<!--<asset source="voice/es-ar/es-ar_tts.js" destination="voice/es-ar-tts/es-ar_tts.js" mode="overwriteOnlyIfExists" />-->
<!--<asset source="voice/et/et_tts.js" destination="voice/et-tts/et_tts.js" mode="overwriteOnlyIfExists" />-->
<!--<asset source="voice/fa/fa_tts.js" destination="voice/fa-tts/fa_tts.js" mode="overwriteOnlyIfExists" />-->
<!--<asset source="voice/fi/fi_tts.js" destination="voice/fi-tts/fi_tts.js" mode="overwriteOnlyIfExists" />-->
<asset source="voice/fr/fr_tts.js" destination="voice/fr-tts/fr_tts.js" mode="alwaysOverwriteOrCopy" />
<!--<asset source="voice/he/he_tts.js" destination="voice/he-tts/he_tts.js" mode="overwriteOnlyIfExists" />-->
<!--<asset source="voice/hi/hi_tts.js" destination="voice/hi-tts/hi_tts.js" mode="overwriteOnlyIfExists" />-->
<!--<asset source="voice/hr/hr_tts.js" destination="voice/hr-tts/hr_tts.js" mode="overwriteOnlyIfExists" />-->
<asset source="voice/hu/hu_tts.js" destination="voice/hu-tts/hu_tts.js" mode="overwriteOnlyIfExists" />
<!--<asset source="voice/hu-formal/hu-formal_tts.js" destination="voice/hu_formal-tts/hu-formal_tts.js" mode="overwriteOnlyIfExists" />-->
<asset source="voice/it/it_tts.js" destination="voice/it-tts/it_tts.js" mode="alwaysOverwriteOrCopy" />
<!--<asset source="voice/ja/ja_tts.js" destination="voice/ja-tts/ja_tts.js" mode="alwaysOverwriteOrCopy" />-->
<!--<asset source="voice/ko/ko_tts.js" destination="voice/ko-tts/ko_tts.js" mode="overwriteOnlyIfExists" />-->
<!--<asset source="voice/lv/lv_tts.js" destination="voice/lv-tts/lv_tts.js" mode="overwriteOnlyIfExists" />-->
<asset source="voice/nl/nl_tts.js" destination="voice/nl-tts/nl_tts.js" mode="alwaysOverwriteOrCopy" />
<asset source="voice/pl/pl_tts.js" destination="voice/pl-tts/pl_tts.js" mode="alwaysOverwriteOrCopy" />
<asset source="voice/pt/pt_tts.js" destination="voice/pt-tts/pt_tts.js" mode="alwaysOverwriteOrCopy" />
<!--<asset source="voice/pt-br/pt-br_tts.js" destination="voice/pt-br-tts/pt-br_tts.js" mode="overwriteOnlyIfExists" />-->
<!--<asset source="voice/ro/ro_tts.js" destination="voice/ro-tts/ro_tts.js" mode="overwriteOnlyIfExists" />-->
<asset source="voice/ru/ru_tts.js" destination="voice/ru-tts/ru_tts.js" mode="alwaysOverwriteOrCopy" />
<!--<asset source="voice/sc/sc_tts.js" destination="voice/sc-tts/sc_tts.js" mode="overwriteOnlyIfExists" />-->
<!--<asset source="voice/sk/sk_tts.js" destination="voice/sk-tts/sk_tts.js" mode="overwriteOnlyIfExists" />-->
<!--<asset source="voice/sl/sl_tts.js" destination="voice/sl-tts/sl_tts.js" mode="overwriteOnlyIfExists" />-->
<!--<asset source="voice/sv/sv_tts.js" destination="voice/sv-tts/sv_tts.js" mode="overwriteOnlyIfExists" />-->
<!--<asset source="voice/sw/sw_tts.js" destination="voice/sw-tts/sw_tts.js" mode="overwriteOnlyIfExists" />-->
<!--<asset source="voice/uk/uk_tts.js" destination="voice/uk-tts/uk_tts.js" mode="overwriteOnlyIfExists" />-->
<!--<asset source="voice/zh/zh_tts.js" destination="voice/zh-tts/zh_tts.js" mode="alwaysOverwriteOrCopy" />-->
<!--<asset source="voice/zh-hk/zh-hk_tts.js" destination="voice/zh-hk-tts/zh-hk_tts.js" mode="overwriteOnlyIfExists" />-->
<asset source="voice/de/config.p" destination="voice/de/_config.p" mode="overwriteOnlyIfExists" />
<asset source="voice/en/config.p" destination="voice/en/_config.p" mode="overwriteOnlyIfExists" />
<asset source="voice/es/config.p" destination="voice/es/_config.p" mode="overwriteOnlyIfExists" />

View file

@ -238,6 +238,7 @@ task collectVoiceAssets(type: Sync) {
from "../../resources/voice"
into "assets/voice"
include "**/*.p"
include "**/*.js"
}
task cleanNoTranslate(type: Delete) {
@ -361,6 +362,9 @@ dependencies {
implementation 'com.vividsolutions:jts-core:1.14.0'
implementation 'com.squareup.picasso:picasso:2.71828'
// JS core
implementation group: 'org.mozilla', name: 'rhino', version: '1.7.9'
// size restrictions
// implementation 'com.ibm.icu:icu4j:50.1'
// implementation 'net.sf.trove4j:trove4j:3.0.3'

View file

@ -58,7 +58,7 @@
android:gravity="center_vertical"
android:lines="1"
android:singleLine="true"
android:text="5 selected"
tools:text="5 selected"
android:textColor="@color/color_white"
android:textSize="@dimen/default_list_text_size_large"/>
@ -264,7 +264,7 @@
android:paddingLeft="@dimen/context_menu_padding_margin_small"
android:paddingRight="@dimen/context_menu_padding_margin_small"
android:paddingTop="@dimen/context_menu_padding_margin_small"
android:text="Отправить"
android:text="@string/shared_string_send"
android:textColor="?attr/wikivoyage_active_color"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_medium" />

View file

@ -3763,6 +3763,8 @@
<string name="poi_payment_centre">Platební centrum</string>
<string name="poi_money_transfer">Služba převodu peněz</string>
<string name="poi_climbing_sport_yes">Sport: Ano</string>
<string name="poi_climbing_sport_no">Sport: Ne</string>
<string name="poi_climbing_sport_yes">Sportovní horolezectví: Ano</string>
<string name="poi_climbing_sport_no">Sportovní horolezectví: Ne</string>
<string name="poi_glacier_type_icecap">Ledovec typu ledová čepice</string>
<string name="poi_glacier_type_icefield">Ledovec typu ledové pole</string>
</resources>

View file

@ -3707,7 +3707,7 @@
<string name="poi_post_flats">Piso de oficina</string>
<string name="poi_payment_centre">Centro de pagos</string>
<string name="poi_money_transfer">Transferencia de dinero</string>
<string name="poi_money_transfer">Transferencia de dinero;Giros de dinero</string>
<string name="poi_route_subway_ref">Subte</string>

View file

@ -3469,7 +3469,7 @@
<string name="poi_post_flats">Piso de oficina</string>
<string name="poi_payment_centre">Centro de pagos</string>
<string name="poi_money_transfer">Transferencia de dinero</string>
<string name="poi_money_transfer">Transferencia de dinero; Giros de dinero</string>
<string name="poi_route_subway_ref">Metro</string>

View file

@ -2956,4 +2956,9 @@ Stendur fyrir svæði: %1$s x %2$s</string>
<string name="search_no_results_description">Engar niðurstöður?
\nSegðu okkur meira um þetta.</string>
<string name="send_search_query">Senda leitarfyrirspurn?</string>
<string name="thank_you_for_feedback">Takk fyrir umsögn þína</string>
<string name="poi_cannot_be_found">Hnútur eða leið fannst ekki.</string>
<string name="search_no_results_feedback">Engar leitarniðurstöður?
\nLáttu okkur vita</string>
<string name="commiting_way">Sendi leið inn…</string>
</resources>

View file

@ -2961,7 +2961,7 @@ OsmAndとOSMをサポートする方法として現状最適な方法である
<string name="add_track_to_markers_descr">マーカーとして追加する経由地点を経路データから選択してください。(経由地点を含む経路のみリストアップされます)</string>
<string name="add_favourites_group_to_markers_descr">マーカーとして追加するお気に入りのカテゴリを選択してください。</string>
<string name="add_group_descr">お気に入り地点や経路データ内の経由地点をまとめてインポートできます。</string>
<string name="add_group_descr">お気に入り地点やGPX経由地点をグループとしてインポートできます。</string>
<string name="empty_state_markers_groups">グループでインポート</string>
<string name="empty_state_markers_groups_desc">お気に入り地点や経路データ内の経由地点をマーカーとしてまとめて追加できます。</string>
<string name="shared_string_two">二つ</string>
@ -3016,7 +3016,7 @@ OsmAndとOSMをサポートする方法として現状最適な方法である
<string name="add_group">グループの追加</string>
<string name="empty_state_osm_edits_descr">OSM編集はPOIの作成や更新、注釈の追加などが可能です。記録したGPXファイルを用いてあなたもOSMに貢献することができるでしょう。</string>
<string name="mark_passed">通過済みにする</string>
<string name="add_segment_to_the_track">GPX経路に追加</string>
<string name="add_segment_to_the_track">GPXファイルに追加</string>
<string name="shared_string_current">現在</string>
<string name="shared_string_actions">その他</string>
@ -3050,4 +3050,8 @@ OsmAndとOSMをサポートする方法として現状最適な方法である
<string name="default_render_descr">標準的な汎用スタイルです。人口密集地の場合描写の簡素化がなされます。主な機能としては等高線、ルート、路面品質、通行制限、道路標識、SACスケールの通路描写をサポートし、急流下りなどのウォータースポーツにも使用できます。</string>
<string name="open_wikipedia_link_online">Wikipediaをオンライン参照</string>
<string name="open_wikipedia_link_online_description">このリンクを開くとウェブブラウザで閲覧することができます。</string>
<string name="import_as_gpx">GPXファイルとしてインポート</string>
<string name="empty_state_my_tracks">GPXファイルに追加しよう</string>
<string name="empty_state_my_tracks_desc">GPXファイルまたは記録した経路からインポートします。</string>
<string name="import_track">GPXファイルからインポート</string>
</resources>

View file

@ -2931,4 +2931,5 @@
<string name="wiki_article_search_text">Searching for the corresponding wiki article</string>
<string name="wiki_article_not_found">Article not found</string>
<string name="how_to_open_wiki_title">How to open Wikipedia articles?</string>
<string name="test_voice_desrc">Tap a button and listen to the corresponding voice prompt to identify missing or faulty prompts.</string>
</resources>

View file

@ -46,6 +46,8 @@ 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.JSMediaCommandPlayerImpl;
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,9 +579,12 @@ public class AppInitializer implements IProgress {
if (!voiceDir.exists()) {
throw new CommandPlayerException(ctx.getString(R.string.voice_data_unavailable));
}
if (MediaCommandPlayerImpl.isMyData(voiceDir)) {
return new MediaCommandPlayerImpl(osmandApplication, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider);
boolean useJs = app.getSettings().USE_JS_VOICE_GUIDANCE.get();
if (useJs && JSTTSCommandPlayerImpl.isMyData(voiceDir)) {
return new JSTTSCommandPlayerImpl(ctx, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider);
} else if (MediaCommandPlayerImpl.isMyData(voiceDir)) {
return useJs && JSMediaCommandPlayerImpl.isMyData(voiceDir) ? new JSMediaCommandPlayerImpl(osmandApplication, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider)
: new MediaCommandPlayerImpl(osmandApplication, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider);
} else if (TTSCommandPlayerImpl.isMyData(voiceDir)) {
return new TTSCommandPlayerImpl(ctx, applicationMode, osmandApplication.getRoutingHelper().getVoiceRouter(), voiceProvider);
}

View file

@ -1346,6 +1346,7 @@ public class OsmandSettings {
public final OsmandPreference<Boolean> ANIMATE_MY_LOCATION = new BooleanPreference("animate_my_location", true).makeGlobal().cache();
public final OsmandPreference<Boolean> USE_JS_VOICE_GUIDANCE = new BooleanPreference("use_js_voice_guidance", false);
public final OsmandPreference<Boolean> ROUTE_MAP_MARKERS_START_MY_LOC = new BooleanPreference("route_map_markers_start_my_loc", false).makeGlobal().cache();
public final OsmandPreference<Boolean> ROUTE_MAP_MARKERS_ROUND_TRIP = new BooleanPreference("route_map_markers_round_trip", false).makeGlobal().cache();

View file

@ -13,6 +13,7 @@ import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.SQLiteTileSource;
import net.osmand.plus.download.ui.AbstractLoadLocalIndexTask;
import net.osmand.plus.voice.JSTTSCommandPlayerImpl;
import net.osmand.plus.voice.MediaCommandPlayerImpl;
import net.osmand.plus.voice.TTSCommandPlayerImpl;
import net.osmand.util.Algorithms;
@ -215,11 +216,12 @@ public class LocalIndexHelper {
private void loadVoiceData(File voiceDir, List<LocalIndexInfo> result, boolean backup, AbstractLoadLocalIndexTask loadTask) {
if (voiceDir.canRead()) {
boolean useJs = app.getSettings().USE_JS_VOICE_GUIDANCE.get();
//First list TTS files, they are preferred
for (File voiceF : listFilesSorted(voiceDir)) {
if (voiceF.isDirectory() && !MediaCommandPlayerImpl.isMyData(voiceF) && (Build.VERSION.SDK_INT >= 4)) {
if (voiceF.isDirectory() && !MediaCommandPlayerImpl.isMyData(voiceF)) {
LocalIndexInfo info = null;
if (TTSCommandPlayerImpl.isMyData(voiceF)) {
if ((TTSCommandPlayerImpl.isMyData(voiceF) && !useJs) || (JSTTSCommandPlayerImpl.isMyData(voiceF) && useJs)) {
info = new LocalIndexInfo(LocalIndexType.TTS_VOICE_DATA, voiceF, backup, app);
}
if (info != null) {

View file

@ -52,6 +52,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import static net.osmand.plus.mapcontextmenu.other.RoutePreferencesMenu.getVoiceFiles;
public class SettingsNavigationActivity extends SettingsBaseActivity {
public static final String MORE_VALUE = "MORE_VALUE";
@ -244,7 +246,7 @@ public class SettingsNavigationActivity extends SettingsBaseActivity {
private void reloadVoiceListPreference(PreferenceScreen screen) {
String[] entries;
String[] entrieValues;
Set<String> voiceFiles = getVoiceFiles();
Set<String> voiceFiles = getVoiceFiles(this);
entries = new String[voiceFiles.size() + 2];
entrieValues = new String[voiceFiles.size() + 2];
int k = 0;
@ -263,20 +265,6 @@ public class SettingsNavigationActivity extends SettingsBaseActivity {
}
private Set<String> getVoiceFiles() {
// read available voice data
File extStorage = getMyApplication().getAppPath(IndexConstants.VOICE_INDEX_DIR);
Set<String> setFiles = new LinkedHashSet<String>();
if (extStorage.exists()) {
for (File f : extStorage.listFiles()) {
if (f.isDirectory()) {
setFiles.add(f.getName());
}
}
}
return setFiles;
}
private void addVoicePrefs(PreferenceGroup cat) {
if (!Version.isBlackberry((OsmandApplication) getApplication())) {
ListPreference lp = createListPreference(

View file

@ -14,6 +14,7 @@ import android.preference.PreferenceScreen;
import android.support.v7.app.AlertDialog;
import android.view.View;
import net.osmand.StateChangedListener;
import net.osmand.plus.ApplicationMode;
import net.osmand.plus.OsmAndLocationSimulation;
import net.osmand.plus.OsmandApplication;
@ -32,6 +33,7 @@ import java.util.Set;
public class SettingsDevelopmentActivity extends SettingsBaseActivity {
private StateChangedListener<Boolean> useJSVoiceGuidanceListener;
@SuppressLint("SimpleDateFormat")
@Override
@ -62,6 +64,15 @@ public class SettingsDevelopmentActivity extends SettingsBaseActivity {
cat.addPreference(createCheckBoxPreference(settings.ANIMATE_MY_LOCATION,
R.string.animate_my_location,
R.string.animate_my_location_desc));
useJSVoiceGuidanceListener = new StateChangedListener<Boolean>() {
@Override
public void stateChanged(Boolean change) {
getMyApplication().getDownloadThread().runReloadIndexFilesSilent();
}
};
cat.addPreference(createCheckBoxPreference(settings.USE_JS_VOICE_GUIDANCE, "Use JS voice guidance",
"Use new voice guidance logic based on JavaScript"));
settings.USE_JS_VOICE_GUIDANCE.addListener(useJSVoiceGuidanceListener);
final Preference firstRunPreference = new Preference(this);
firstRunPreference.setTitle(R.string.simulate_initial_startup);
@ -249,6 +260,9 @@ public class SettingsDevelopmentActivity extends SettingsBaseActivity {
b.show();
}
@Override
public void onDestroy() {
super.onDestroy();
settings.USE_JS_VOICE_GUIDANCE.removeListener(useJSVoiceGuidanceListener);
}
}

View file

@ -22,6 +22,9 @@ import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.activities.OsmandActionBarActivity;
import net.osmand.plus.voice.AbstractPrologCommandPlayer;
import net.osmand.plus.voice.JSCommandBuilder;
import net.osmand.plus.voice.JSMediaCommandPlayerImpl;
import net.osmand.plus.voice.JSTTSCommandPlayerImpl;
import net.osmand.plus.voice.TTSCommandPlayerImpl;
import net.osmand.plus.voice.CommandBuilder;
import net.osmand.plus.voice.CommandPlayer;
@ -29,12 +32,16 @@ import net.osmand.plus.routing.VoiceRouter;
import net.osmand.util.Algorithms;
import java.io.File;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import alice.tuprolog.Struct;
import alice.tuprolog.Term;
import static net.osmand.plus.mapcontextmenu.other.RoutePreferencesMenu.getVoiceFiles;
/**
* Test Voice activity
@ -62,7 +69,7 @@ public class TestVoiceActivity extends OsmandActionBarActivity {
gl.setPadding(3, 3, 3, 3);
TextView tv = new TextView(this);
tv.setText("Tap a button and listen to the corresponding voice prompt to identify missing or faulty propmts.");
tv.setText(R.string.test_voice_desrc);
tv.setPadding(0, 5, 0, 7);
ScrollView sv = new ScrollView(this);
@ -75,28 +82,15 @@ public class TestVoiceActivity extends OsmandActionBarActivity {
// add buttons
setContentView(gl);
getSupportActionBar(). setTitle(R.string.test_voice_prompts);
getSupportActionBar().setTitle(R.string.test_voice_prompts);
selectVoice(ll);
}
private Set<String> getVoiceFiles() {
// read available voice data
File extStorage = ((OsmandApplication) getApplication()).getAppPath(IndexConstants.VOICE_INDEX_DIR);
Set<String> setFiles = new LinkedHashSet<String>();
if (extStorage.exists()) {
for (File f : extStorage.listFiles()) {
if (f.isDirectory()) {
setFiles.add(f.getName());
}
}
}
return setFiles;
}
private void selectVoice(final LinearLayout ll) {
String[] entries;
final String[] entrieValues;
Set<String> voiceFiles = getVoiceFiles();
Set<String> voiceFiles = getVoiceFiles(this);
entries = new String[voiceFiles.size() ];
entrieValues = new String[voiceFiles.size() ];
int k = 0;
@ -216,51 +210,72 @@ public class TestVoiceActivity extends OsmandActionBarActivity {
}
private void addButtons(final LinearLayout ll, CommandPlayer p) {
boolean isJS = p instanceof JSTTSCommandPlayerImpl || p instanceof JSMediaCommandPlayerImpl;
addButton(ll, "Route calculated and number tests:", builder(p));
addButton(ll, "\u25BA (1.1) New route calculated, 150m, 230sec (00:03:50)", builder(p).newRouteCalculated(150, 230));
addButton(ll, "\u25BA (1.2) New route calculated, 1350m, 3680sec (01:01:20)", builder(p).newRouteCalculated(1350, 3680));
addButton(ll, "\u25BA (1.3) New route calculated 3700m, 7320sec (02:02)", builder(p).newRouteCalculated(3700, 7320));
addButton(ll, "\u25BA (1.4) New route calculated 9100m, 10980sec (03:03)", builder(p).newRouteCalculated(9100, 10980));
addButton(ll, "\u25BA (2.1) Route recalculated 11500m, 18600sec (05:10)", builder(p).routeRecalculated(11500, 18600));
addButton(ll, "\u25BA (2.2) Route recalculated 19633m, 26700sec (07:25)", builder(p).routeRecalculated(19633, 26700));
addButton(ll, "\u25BA (2.3) Route recalculated 89750m, 55800sec (15:30)", builder(p).routeRecalculated(89750, 55800));
addButton(ll, "\u25BA (2.4) Route recalculated 125900m, 92700sec (25:45)", builder(p).routeRecalculated(125900, 92700));
addButton(ll, "\u25BA (1.1) New route calculated, 150m, 230sec (00:03:50)", !isJS ? builder(p).newRouteCalculated(150, 230) : jsBuilder(p).newRouteCalculated(150, 230));
addButton(ll, "\u25BA (1.2) New route calculated, 1350m, 3680sec (01:01:20)", !isJS ? builder(p).newRouteCalculated(1350, 3680) : jsBuilder(p).newRouteCalculated(1350, 3680));
addButton(ll, "\u25BA (1.3) New route calculated 3700m, 7320sec (02:02)", !isJS ? builder(p).newRouteCalculated(3700, 7320) : jsBuilder(p).newRouteCalculated(3700, 7320));
addButton(ll, "\u25BA (1.4) New route calculated 9100m, 10980sec (03:03)", !isJS ? builder(p).newRouteCalculated(9100, 10980) : jsBuilder(p).newRouteCalculated(9100, 10980));
addButton(ll, "\u25BA (2.1) Route recalculated 11500m, 18600sec (05:10)", !isJS ? builder(p).routeRecalculated(11500, 18600) : jsBuilder(p).routeRecalculated(11500, 18600));
addButton(ll, "\u25BA (2.2) Route recalculated 19633m, 26700sec (07:25)", !isJS ? builder(p).routeRecalculated(19633, 26700) : jsBuilder(p).routeRecalculated(19633, 26700));
addButton(ll, "\u25BA (2.3) Route recalculated 89750m, 55800sec (15:30)", !isJS ? builder(p).routeRecalculated(89750, 55800) : jsBuilder(p).routeRecalculated(89750, 55800));
addButton(ll, "\u25BA (2.4) Route recalculated 125900m, 92700sec (25:45)", !isJS ? builder(p).routeRecalculated(125900, 92700) : jsBuilder(p).routeRecalculated(125900, 92700));
addButton(ll, "All turn types: prepareTurn, makeTurnIn, turn:", builder(p));
addButton(ll, "\u25BA (3.1) After 1520m turn slightly left", builder(p).prepareTurn(AbstractPrologCommandPlayer.A_LEFT_SL, 1520, street(p, "")));
addButton(ll, "\u25BA (3.2) In 450m turn sharply left onto 'Hauptstra"+"\u00df"+"e', then bear right", builder(p).turn(AbstractPrologCommandPlayer.A_LEFT_SH, 450, street(p, "Hauptstraße")).then().bearRight(street(p, "")));
addButton(ll, "\u25BA (3.3) Turn left, then in 100m turn slightly right", builder(p).turn(AbstractPrologCommandPlayer.A_LEFT, street(p, "")).then().turn(AbstractPrologCommandPlayer.A_RIGHT_SL, 100, street(p, "")));
addButton(ll, "\u25BA (3.4) After 3100m turn right onto 'SR 80' toward 'Rome'", builder(p).prepareTurn(AbstractPrologCommandPlayer.A_RIGHT, 3100, street(p, "", "SR 80", "Rome")));
addButton(ll, "\u25BA (3.5) In 370m turn slightly right onto 'Route 23' 'Main Street', then bear left", builder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_SL, 370, street(p, "Main Street", "Route 23")).then().bearLeft(street(p, "")));
addButton(ll, "\u25BA (3.6) Turn sharply right onto 'Dr.-Quinn-Stra"+"\u00df"+"e'", builder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_SH, street(p, "Dr.-Quinn-Straße")));
addButton(ll, "\u25BA (3.1) After 1520m turn slightly left", !isJS ? builder(p).prepareTurn(AbstractPrologCommandPlayer.A_LEFT_SL, 1520, street(p, "")) :
jsBuilder(p).prepareTurn(AbstractPrologCommandPlayer.A_LEFT_SL, 1520, jsStreet(p, "")));
addButton(ll, "\u25BA (3.2) In 450m turn sharply left onto 'Hauptstra"+"\u00df"+"e', then bear right", !isJS ? builder(p).turn(AbstractPrologCommandPlayer.A_LEFT_SH, 450, street(p, "Hauptstraße")).then().bearRight(street(p, "")) :
jsBuilder(p).turn(AbstractPrologCommandPlayer.A_LEFT_SH, 450, jsStreet(p, "Hauptstraße")).then().bearRight(jsStreet(p, "")));
addButton(ll, "\u25BA (3.3) Turn left, then in 100m turn slightly right", !isJS ? builder(p).turn(AbstractPrologCommandPlayer.A_LEFT, street(p, "")).then().turn(AbstractPrologCommandPlayer.A_RIGHT_SL, 100, street(p, "")) :
jsBuilder(p).turn(AbstractPrologCommandPlayer.A_LEFT, jsStreet(p, "")).then().turn(AbstractPrologCommandPlayer.A_RIGHT_SL, 100, jsStreet(p, "")));
addButton(ll, "\u25BA (3.4) After 3100m turn right onto 'SR 80' toward 'Rome'", !isJS ? builder(p).prepareTurn(AbstractPrologCommandPlayer.A_RIGHT, 3100, street(p, "", "SR 80", "Rome")) :
jsBuilder(p).prepareTurn(AbstractPrologCommandPlayer.A_RIGHT, 3100, jsStreet(p, "", "SR 80", "Rome")));
addButton(ll, "\u25BA (3.5) In 370m turn slightly right onto 'Route 23' 'Main Street', then bear left", !isJS ? builder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_SL, 370, street(p, "Main Street", "Route 23")).then().bearLeft(street(p, "")) :
jsBuilder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_SL, 370, jsStreet(p, "Main jsStreet", "Route 23")).then().bearLeft(jsStreet(p, "")));
addButton(ll, "\u25BA (3.6) Turn sharply right onto 'Dr.-Quinn-Stra"+"\u00df"+"e'", !isJS ? builder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_SH, street(p, "Dr.-Quinn-Straße")) :
jsBuilder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_SH, jsStreet(p, "Dr.-Quinn-Straße")));
addButton(ll, "Keep left/right: prepareTurn, makeTurnIn, turn:", builder(p));
addButton(ll, "\u25BA (4.1) After 1810m keep left ' '", builder(p).prepareTurn(AbstractPrologCommandPlayer.A_LEFT_KEEP, 1810, street(p, "")));
addButton(ll, "\u25BA (4.2) In 400m keep left ' ' then in 80m keep right onto 'A1'", builder(p).turn(AbstractPrologCommandPlayer.A_LEFT_KEEP, 400, street(p, "")).then().turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, 80, street(p,"", "A1")));
addButton(ll, "\u25BA (4.3) Keep right on 'Highway 60'", builder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, street(p, "Highway 60", "", "", "Highway 60")));
addButton(ll, "\u25BA (4.4) Turn left onto 'Broadway', then in 100m keep right and arrive at your destination 'Town Hall'",
addButton(ll, "\u25BA (4.1) After 1810m keep left ' '", !isJS ? builder(p).prepareTurn(AbstractPrologCommandPlayer.A_LEFT_KEEP, 1810, street(p, "")) :
jsBuilder(p).prepareTurn(AbstractPrologCommandPlayer.A_LEFT_KEEP, 1810, jsStreet(p, "")));
addButton(ll, "\u25BA (4.2) In 400m keep left ' ' then in 80m keep right onto 'A1'", !isJS ? builder(p).turn(AbstractPrologCommandPlayer.A_LEFT_KEEP, 400, street(p, "")).then().turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, 80, street(p,"", "A1")) :
jsBuilder(p).turn(AbstractPrologCommandPlayer.A_LEFT_KEEP, 400, jsStreet(p, "")).then().turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, 80, jsStreet(p,"", "A1")));
addButton(ll, "\u25BA (4.3) Keep right on 'Highway 60'", !isJS ? builder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, street(p, "Highway 60", "", "", "Highway 60")) :
jsBuilder(p).turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, jsStreet(p, "Highway 60", "", "", "Highway 60")));
addButton(ll, "\u25BA (4.4) Turn left onto 'Broadway', then in 100m keep right and arrive at your destination 'Town Hall'", !isJS ?
builder(p).turn(AbstractPrologCommandPlayer.A_LEFT, street(p, "Broadway"))
.then().turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, 100, street(p, "")).andArriveAtDestination("Town Hall"));
.then().turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, 100, street(p, "")).andArriveAtDestination("Town Hall") :
jsBuilder(p).turn(AbstractPrologCommandPlayer.A_LEFT, jsStreet(p, "Broadway"))
.then().turn(AbstractPrologCommandPlayer.A_RIGHT_KEEP, 100, jsStreet(p, "")).andArriveAtDestination("Town Hall"));
addButton(ll, "Roundabouts: prepareTurn, makeTurnIn, turn:", builder(p));
addButton(ll, "\u25BA (5.1) After 1250m enter a roundabout", builder(p).prepareRoundAbout(1250, 3, street(p,"", "I 15", "Los Angeles")));
addButton(ll, "\u25BA (5.2) In 450m enter the roundabout and take the 1st exit onto 'I 15' toward 'Los Angeles'", builder(p).roundAbout(450, 0, 1, street(p,"", "I 15", "Los Angeles")));
addButton(ll, "\u25BA (5.3) Roundabout: Take the 2nd exit onto 'Highway 60'", builder(p).roundAbout(0, 2, street(p, "Highway 60")));
addButton(ll, "\u25BA (5.1) After 1250m enter a roundabout", !isJS ? builder(p).prepareRoundAbout(1250, 3, street(p,"", "I 15", "Los Angeles")) :
jsBuilder(p).prepareRoundAbout(1250, 3, jsStreet(p,"", "I 15", "Los Angeles")));
addButton(ll, "\u25BA (5.2) In 450m enter the roundabout and take the 1st exit onto 'I 15' toward 'Los Angeles'", isJS ? builder(p).roundAbout(450, 0, 1, street(p,"", "I 15", "Los Angeles")) :
jsBuilder(p).roundAbout(450, 0, 1, jsStreet(p,"", "I 15", "Los Angeles")));
addButton(ll, "\u25BA (5.3) Roundabout: Take the 2nd exit onto 'Highway 60'", !isJS ? builder(p).roundAbout(0, 2, street(p, "Highway 60")) :
jsBuilder(p).roundAbout(0, 2, jsStreet(p, "Highway 60")));
addButton(ll, "U-turns: prepareTurn, makeTurnIn, turn, when possible:", builder(p));
addButton(ll, "\u25BA (6.1) After 640m make a U-turn", builder(p).prepareMakeUT(640, street(p, "")));
addButton(ll, "\u25BA (6.2) In 400m make a U-turn", builder(p).makeUT(400, street(p, "")));
addButton(ll, "\u25BA (6.3) Make a U-turn on 'Riviera'", builder(p).makeUT(street(p, "Riviera", "", "", "Riviera")));
addButton(ll, "\u25BA (6.1) After 640m make a U-turn", !isJS ? builder(p).prepareMakeUT(640, street(p, "")) :
jsBuilder(p).prepareMakeUT(640, jsStreet(p, "")));
addButton(ll, "\u25BA (6.2) In 400m make a U-turn", !isJS ? builder(p).makeUT(400, street(p, "")) :
jsBuilder(p).makeUT(400, jsStreet(p, "")));
addButton(ll, "\u25BA (6.3) Make a U-turn on 'Riviera'", !isJS ? builder(p).makeUT(street(p, "Riviera", "", "", "Riviera")) :
jsBuilder(p).makeUT(jsStreet(p, "Riviera", "", "", "Riviera")));
addButton(ll, "\u25BA (6.4) When possible, make a U-turn", builder(p).makeUTwp());
addButton(ll, "Go straight, follow the road, approaching:", builder(p));
addButton(ll, "\u25BA (7.1) Straight ahead", builder(p).goAhead());
addButton(ll, "\u25BA (7.2) Continue for 2350m to ' '", builder(p).goAhead(2350, street(p, "")));
addButton(ll, "\u25BA (7.3) Continue for 360m to 'Broadway' and arrive at your intermediate destination ' '", builder(p).goAhead(360, street(p,"Broadway")).andArriveAtIntermediatePoint(""));
addButton(ll, "\u25BA (7.4) Continue for 800m to 'Dr Martin Luther King Jr Boulevard' and arrive at your destination ' '", builder(p).goAhead(800, street(p,"", "Dr Martin Luther King Jr Boulevard")).andArriveAtDestination(""));
addButton(ll, "\u25BA (7.5) Continue for 200m and pass GPX waypoint 'Trailhead'", builder(p).goAhead(200, null).andArriveAtWayPoint("Trailhead"));
addButton(ll, "\u25BA (7.6) Continue for 400m and pass favorite 'Brewery'", builder(p).goAhead(400, null).andArriveAtFavorite("Brewery"));
addButton(ll, "\u25BA (7.7) Continue for 600m and pass POI 'Museum'", builder(p).goAhead(600, null).andArriveAtPoi("Museum"));
addButton(ll, "\u25BA (7.2) Continue for 2350m to ' '", !isJS ? builder(p).goAhead(2350, street(p, "")) :
jsBuilder(p).goAhead(2350, jsStreet(p, "")));
addButton(ll, "\u25BA (7.3) Continue for 360m to 'Broadway' and arrive at your intermediate destination ' '", !isJS ? builder(p).goAhead(360, street(p,"Broadway")).andArriveAtIntermediatePoint("") :
jsBuilder(p).goAhead(360, jsStreet(p,"Broadway")).andArriveAtIntermediatePoint(""));
addButton(ll, "\u25BA (7.4) Continue for 800m to 'Dr Martin Luther King Jr Boulevard' and arrive at your destination ' '", !isJS ? builder(p).goAhead(800, street(p,"", "Dr Martin Luther King Jr Boulevard")).andArriveAtDestination("") :
jsBuilder(p).goAhead(800, jsStreet(p,"", "Dr Martin Luther King Jr Boulevard")).andArriveAtDestination(""));
addButton(ll, "\u25BA (7.5) Continue for 200m and pass GPX waypoint 'Trailhead'", !isJS ? builder(p).goAhead(200, null).andArriveAtWayPoint("Trailhead") : jsBuilder(p).goAhead(200, new HashMap<String, String>()).andArriveAtWayPoint("Trailhead"));
addButton(ll, "\u25BA (7.6) Continue for 400m and pass favorite 'Brewery'", !isJS ? builder(p).goAhead(400, null).andArriveAtFavorite("Brewery") : jsBuilder(p).goAhead(400, new HashMap<String, String>()).andArriveAtFavorite("Brewery"));
addButton(ll, "\u25BA (7.7) Continue for 600m and pass POI 'Museum'", !isJS ? builder(p).goAhead(600, null).andArriveAtPoi("Museum") : jsBuilder(p).goAhead(600, new HashMap<String, String>()).andArriveAtPoi("Museum"));
addButton(ll, "Arriving and passing points:", builder(p));
addButton(ll, "\u25BA (8.1) Arrive at your destination 'Home'", builder(p).arrivedAtDestination("Home"));
@ -292,10 +307,31 @@ public class TestVoiceActivity extends OsmandActionBarActivity {
ll.forceLayout();
}
private Map<String, String> jsStreet(CommandPlayer p, String... args) {
Map<String, String> res = new HashMap<>();
if (!p.supportsStructuredStreetNames()) {
return res;
}
String[] streetNames = new String[]{"toRef", "toStreetName", "toDest", "fromRef", "fromStreetName", "fromDest"};
for (int i = 0; i < args.length; i++) {
res.put(streetNames[i], args[i]);
}
for (String streetName : streetNames) {
if (res.get(streetName) == null) {
res.put(streetName, "");
}
}
return res;
}
private CommandBuilder builder(CommandPlayer p){
return p.newCommandBuilder();
}
private JSCommandBuilder jsBuilder(CommandPlayer p) {
return (JSCommandBuilder) p.newCommandBuilder();
}
public void addButton(ViewGroup layout, final String description, final CommandBuilder builder){
final Button button = new Button(this);
button.setGravity(Gravity.LEFT);

View file

@ -298,6 +298,8 @@ public class DownloadActivityType {
return FileNameTranslationHelper.getVoiceName(ctx, getBasename(indexItem));
} else if (fileName.endsWith(IndexConstants.TTSVOICE_INDEX_EXT_ZIP)) {
return FileNameTranslationHelper.getVoiceName(ctx, getBasename(indexItem));
} else if (fileName.endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS)) {
return FileNameTranslationHelper.getVoiceName(ctx, getBasename(indexItem));
}
return getBasename(indexItem);
}
@ -336,6 +338,9 @@ public class DownloadActivityType {
// if(fileName.endsWith(IndexConstants.VOICE_INDEX_EXT_ZIP) ||
// fileName.endsWith(IndexConstants.TTSVOICE_INDEX_EXT_ZIP)) {
if (this == VOICE_FILE) {
if (fileName.contains(".js")) {
return fileName.replace('_', '-').replaceAll(".js", "");
}
int l = fileName.lastIndexOf('_');
if (l == -1) {
l = fileName.length();
@ -408,7 +413,8 @@ public class DownloadActivityType {
if (l == -1) {
l = fileName.length();
}
return fileName.substring(0, l);
return fileName.endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS) ? fileName.replace('_', '-')
.replaceAll(".js", "") : fileName.substring(0, l);
}
if (this == FONT_FILE) {
int l = fileName.indexOf('.');

View file

@ -165,6 +165,13 @@ public class DownloadOsmandIndexesHelper {
result.add(new AssetIndexItem(voice + ext, "voice", date, dateModified, "0.1", destFile.length(), key,
destFile.getPath(), DownloadActivityType.VOICE_FILE));
} else if (target.endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS) && target.startsWith("voice/")) {
String lang = target.substring("voice/".length(), target.indexOf("-"));
File destFile = new File(voicePath, target.substring("voice/".length(),
target.indexOf("/", "voice/".length())) + "/" + lang + "_tts.js");
result.add(new AssetIndexItem(lang + "_" + IndexConstants.TTSVOICE_INDEX_EXT_JS,
"voice", date, dateModified, "0.1", destFile.length(), key,
destFile.getPath(), DownloadActivityType.VOICE_FILE));
}
}
result.sort();

View file

@ -83,7 +83,8 @@ public class DownloadResourceGroup {
}
public boolean isHeader() {
return this == VOICE_HEADER_REC || this == VOICE_HEADER_TTS || this == SUBREGIONS
return this == VOICE_HEADER_REC || this == VOICE_HEADER_TTS
|| this == SUBREGIONS
|| this == WORLD_MAPS || this == REGION_MAPS || this == OTHER_GROUP
|| this == HILLSHADE_HEADER || this == SRTM_HEADER
|| this == OTHER_MAPS_HEADER || this == OTHER_MAPS_GROUP

View file

@ -316,11 +316,14 @@ public class DownloadResources extends DownloadResourceGroup {
Map<WorldRegion, List<IndexItem> > groupByRegion = new LinkedHashMap<WorldRegion, List<IndexItem>>();
OsmandRegions regs = app.getRegions();
boolean useJS = app.getSettings().USE_JS_VOICE_GUIDANCE.get();
for (IndexItem ii : resources) {
if (ii.getType() == DownloadActivityType.VOICE_FILE) {
if (ii.getFileName().endsWith(IndexConstants.TTSVOICE_INDEX_EXT_ZIP)) {
if (ii.getFileName().endsWith(IndexConstants.TTSVOICE_INDEX_EXT_ZIP) && !useJS) {
voiceTTS.addItem(ii);
} else {
} else if (ii.getFileName().endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS) && useJS){
voiceTTS.addItem(ii);
} else if (ii.getFileName().endsWith(IndexConstants.VOICE_INDEX_EXT_ZIP)){
voiceRec.addItem(ii);
}
continue;

View file

@ -1,5 +1,6 @@
package net.osmand.plus.mapcontextmenu.other;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@ -329,20 +330,36 @@ public class RoutePreferencesMenu {
app.initVoiceCommandPlayer(mapActivity, app.getRoutingHelper().getAppMode(), false, null, true, false);
}
private static Set<String> getVoiceFiles(MapActivity mapActivity) {
public static Set<String> getVoiceFiles(Activity activity) {
// read available voice data
File extStorage = mapActivity.getMyApplication().getAppPath(IndexConstants.VOICE_INDEX_DIR);
Set<String> setFiles = new LinkedHashSet<>();
OsmandApplication app = ((OsmandApplication) activity.getApplication());
File extStorage = app.getAppPath(IndexConstants.VOICE_INDEX_DIR);
Set<String> setFiles = new LinkedHashSet<String>();
boolean addJS = app.getSettings().USE_JS_VOICE_GUIDANCE.get();
if (extStorage.exists()) {
for (File f : extStorage.listFiles()) {
if (f.isDirectory()) {
setFiles.add(f.getName());
if (addJS && hasJavaScript(f)) {
setFiles.add(f.getName());
} else if (!addJS) {
setFiles.add(f.getName());
}
}
}
}
return setFiles;
}
private static boolean hasJavaScript(File f) {
for (File file : f.listFiles()) {
if (file.getName().endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS)) {
return true;
}
}
return false;
}
public OnItemClickListener getItemClickListener(final ArrayAdapter<?> listAdapter) {
return new OnItemClickListener() {
@Override

View file

@ -386,8 +386,11 @@ public class ResourceManager {
for (File f : lf) {
if (f.isDirectory()) {
File conf = new File(f, "_config.p");
boolean useJS = context.getSettings().USE_JS_VOICE_GUIDANCE.get();
if (!conf.exists()) {
conf = new File(f, "_ttsconfig.p");
String lang = f.getName().replace("-tts", "");
conf = useJS ? new File(f, lang + "_" + IndexConstants.TTSVOICE_INDEX_EXT_JS) :
new File(f, "_ttsconfig.p");
}
if (conf.exists()) {
indexFileNames.put(f.getName(), dateFormat.format(conf.lastModified())); //$NON-NLS-1$

View file

@ -0,0 +1,330 @@
package net.osmand.plus.routing;
import net.osmand.Location;
import net.osmand.binary.RouteDataObject;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.voice.CommandBuilder;
import net.osmand.plus.voice.JSCommandBuilder;
import net.osmand.router.RouteSegmentResult;
import net.osmand.router.TurnType;
import java.util.HashMap;
import java.util.Map;
public class JSVoiceRouter extends VoiceRouter {
public JSVoiceRouter(RoutingHelper router, OsmandSettings settings) {
super(router, settings);
}
public Map<String, String> getSpeakableJSStreetName(RouteSegmentResult currentSegment, RouteDirectionInfo i, boolean includeDest) {
Map<String, String> result = new HashMap<>();
if (i == null || !router.getSettings().SPEAK_STREET_NAMES.get()) {
return result;
}
if (player != null && player.supportsStructuredStreetNames()) {
// Issue 2377: Play Dest here only if not already previously announced, to avoid repetition
if (includeDest == true) {
result.put("toRef", getNonNullString(getSpeakablePointName(i.getRef())));
result.put("toStreetName", getNonNullString(getSpeakablePointName(i.getStreetName())));
result.put("toDest", getNonNullString(getSpeakablePointName(i.getDestinationName())));
} else {
result.put("toRef", getNonNullString(getSpeakablePointName(i.getRef())));
result.put("toStreetName", getNonNullString(getSpeakablePointName(i.getStreetName())));
result.put("toDest", "");
}
if (currentSegment != null) {
// Issue 2377: Play Dest here only if not already previously announced, to avoid repetition
if (includeDest == true) {
RouteDataObject obj = currentSegment.getObject();
result.put("fromRef", getNonNullString(getSpeakablePointName(obj.getRef(settings.MAP_PREFERRED_LOCALE.get(),
settings.MAP_TRANSLITERATE_NAMES.get(), currentSegment.isForwardDirection()))));
result.put("fromStreetName", getNonNullString(getSpeakablePointName(obj.getName(settings.MAP_PREFERRED_LOCALE.get(),
settings.MAP_TRANSLITERATE_NAMES.get()))));
result.put("fromDest", getNonNullString(getSpeakablePointName(obj.getDestinationName(settings.MAP_PREFERRED_LOCALE.get(),
settings.MAP_TRANSLITERATE_NAMES.get(), currentSegment.isForwardDirection()))));
} else {
RouteDataObject obj = currentSegment.getObject();
result.put("fromRef", getNonNullString(getSpeakablePointName(obj.getRef(settings.MAP_PREFERRED_LOCALE.get(),
settings.MAP_TRANSLITERATE_NAMES.get(), currentSegment.isForwardDirection()))));
result.put("fromStreetName", getNonNullString(getSpeakablePointName(obj.getName(settings.MAP_PREFERRED_LOCALE.get(),
settings.MAP_TRANSLITERATE_NAMES.get()))));
result.put("fromDest", "");
}
}
} else {
result.put("toRef", getNonNullString(getSpeakablePointName(i.getRef())));
result.put("toStreetName", getNonNullString(getSpeakablePointName(i.getStreetName())));
result.put("toDest", "");
}
return result;
}
private String getNonNullString(String speakablePointName) {
return speakablePointName == null ? "" : speakablePointName;
}
/**
* Updates status of voice guidance
* @param currentLocation
*/
@Override
protected void updateStatus(Location currentLocation, boolean repeat) {
// Directly after turn: goAhead (dist), unless:
// < PREPARE_LONG_DISTANCE (e.g. 3500m): playPrepareTurn (-not played any more-)
// < PREPARE_DISTANCE (e.g. 1500m): playPrepareTurn ("Turn after ...")
// < TURN_IN_DISTANCE (e.g. 390m or 30sec): playMakeTurnIn ("Turn in ...")
// < TURN_DISTANCE (e.g. 50m or 7sec): playMakeTurn ("Turn ...")
float speed = DEFAULT_SPEED;
if (currentLocation != null && currentLocation.hasSpeed()) {
speed = Math.max(currentLocation.getSpeed(), speed);
}
RouteCalculationResult.NextDirectionInfo nextInfo = router.getNextRouteDirectionInfo(new RouteCalculationResult.NextDirectionInfo(), true);
RouteSegmentResult currentSegment = router.getCurrentSegmentResult();
if (nextInfo == null || nextInfo.directionInfo == null) {
return;
}
int dist = nextInfo.distanceTo;
RouteDirectionInfo next = nextInfo.directionInfo;
// If routing is changed update status to unknown
if (next != nextRouteDirection) {
nextRouteDirection = next;
currentStatus = STATUS_UNKNOWN;
suppressDest = false;
playedAndArriveAtTarget = false;
announceBackOnRoute = false;
if (playGoAheadDist != -1) {
playGoAheadDist = 0;
}
}
if (!repeat) {
if (dist <= 0) {
return;
} else if (needsInforming()) {
playGoAhead(dist, getSpeakableJSStreetName(currentSegment, next, false));
return;
} else if (currentStatus == STATUS_TOLD) {
// nothing said possibly that's wrong case we should say before that
// however it should be checked manually ?
return;
}
}
if (currentStatus == STATUS_UNKNOWN) {
// Play "Continue for ..." if (1) after route calculation no other prompt is due, or (2) after a turn if next turn is more than PREPARE_LONG_DISTANCE away
if ((playGoAheadDist == -1) || (dist > PREPARE_LONG_DISTANCE)) {
playGoAheadDist = dist - 3 * TURN_DISTANCE;
}
}
RouteCalculationResult.NextDirectionInfo nextNextInfo = router.getNextRouteDirectionInfoAfter(nextInfo, new RouteCalculationResult.NextDirectionInfo(), true); //I think "true" is correct here, not "!repeat"
// Note: getNextRouteDirectionInfoAfter(nextInfo, x, y).distanceTo is distance from nextInfo, not from current position!
// STATUS_TURN = "Turn (now)"
if ((repeat || statusNotPassed(STATUS_TURN)) && isDistanceLess(speed, dist, TURN_DISTANCE, TURN_DEFAULT_SPEED)) {
if (nextNextInfo.distanceTo < TURN_IN_DISTANCE_END && nextNextInfo != null) {
playMakeTurn(currentSegment, next, nextNextInfo);
} else {
playMakeTurn(currentSegment, next, null);
}
if (!next.getTurnType().goAhead() && isTargetPoint(nextNextInfo)) { // !goAhead() avoids isolated "and arrive.." prompt, as goAhead() is not pronounced
if (nextNextInfo.distanceTo < TURN_IN_DISTANCE_END) {
// Issue #2865: Ensure a distance associated with the destination arrival is always announced, either here, or in subsequent "Turn in" prompt
// Distance fon non-straights already announced in "Turn (now)"'s nextnext code above
if ((nextNextInfo != null) && (nextNextInfo.directionInfo != null) && nextNextInfo.directionInfo.getTurnType().goAhead()) {
playThen();
playGoAhead(nextNextInfo.distanceTo, new HashMap<String, String>());
}
playAndArriveAtDestination(nextNextInfo);
} else if (nextNextInfo.distanceTo < 1.2f * TURN_IN_DISTANCE_END) {
// 1.2 is safety margin should the subsequent "Turn in" prompt not fit in amy more
playThen();
playGoAhead(nextNextInfo.distanceTo, new HashMap<String, String>());
playAndArriveAtDestination(nextNextInfo);
}
}
nextStatusAfter(STATUS_TURN);
// STATUS_TURN_IN = "Turn in ..."
} else if ((repeat || statusNotPassed(STATUS_TURN_IN)) && isDistanceLess(speed, dist, TURN_IN_DISTANCE, 0f)) {
if (repeat || dist >= TURN_IN_DISTANCE_END) {
if ((isDistanceLess(speed, nextNextInfo.distanceTo, TURN_DISTANCE, 0f) || nextNextInfo.distanceTo < TURN_IN_DISTANCE_END) &&
nextNextInfo != null) {
playMakeTurnIn(currentSegment, next, dist - (int) btScoDelayDistance, nextNextInfo.directionInfo);
} else {
playMakeTurnIn(currentSegment, next, dist - (int) btScoDelayDistance, null);
}
playGoAndArriveAtDestination(repeat, nextInfo, currentSegment);
}
nextStatusAfter(STATUS_TURN_IN);
// STATUS_PREPARE = "Turn after ..."
} else if ((repeat || statusNotPassed(STATUS_PREPARE)) && (dist <= PREPARE_DISTANCE)) {
if (repeat || dist >= PREPARE_DISTANCE_END) {
if (!repeat && (next.getTurnType().keepLeft() || next.getTurnType().keepRight())) {
// Do not play prepare for keep left/right
} else {
playPrepareTurn(currentSegment, next, dist);
playGoAndArriveAtDestination(repeat, nextInfo, currentSegment);
}
}
nextStatusAfter(STATUS_PREPARE);
// STATUS_LONG_PREPARE = also "Turn after ...", we skip this now, users said this is obsolete
} else if ((repeat || statusNotPassed(STATUS_LONG_PREPARE)) && (dist <= PREPARE_LONG_DISTANCE)) {
if (repeat || dist >= PREPARE_LONG_DISTANCE_END) {
playPrepareTurn(currentSegment, next, dist);
playGoAndArriveAtDestination(repeat, nextInfo, currentSegment);
}
nextStatusAfter(STATUS_LONG_PREPARE);
// STATUS_UNKNOWN = "Continue for ..." if (1) after route calculation no other prompt is due, or (2) after a turn if next turn is more than PREPARE_LONG_DISTANCE away
} else if (statusNotPassed(STATUS_UNKNOWN)) {
// Strange how we get here but
nextStatusAfter(STATUS_UNKNOWN);
} else if (repeat || (statusNotPassed(STATUS_PREPARE) && dist < playGoAheadDist)) {
playGoAheadDist = 0;
playGoAhead(dist, getSpeakableJSStreetName(currentSegment, next, false));
}
}
private void playPrepareTurn(RouteSegmentResult currentSegment, RouteDirectionInfo next, int dist) {
JSCommandBuilder play = (JSCommandBuilder) getNewCommandPlayerToPlay();
if (play != null) {
String tParam = getTurnType(next.getTurnType());
if (tParam != null) {
notifyOnVoiceMessage();
play.prepareTurn(tParam, dist, getSpeakableJSStreetName(currentSegment, next, true)).play();
} else if (next.getTurnType().isRoundAbout()) {
notifyOnVoiceMessage();
play.prepareRoundAbout(dist, next.getTurnType().getExitOut(), getSpeakableJSStreetName(currentSegment, next, true)).play();
} else if (next.getTurnType().getValue() == TurnType.TU || next.getTurnType().getValue() == TurnType.TRU) {
notifyOnVoiceMessage();
play.prepareMakeUT(dist, getSpeakableJSStreetName(currentSegment, next, true)).play();
}
}
}
private void playMakeTurnIn(RouteSegmentResult currentSegment, RouteDirectionInfo next, int dist, RouteDirectionInfo pronounceNextNext) {
JSCommandBuilder play = (JSCommandBuilder) getNewCommandPlayerToPlay();
if (play != null) {
String tParam = getTurnType(next.getTurnType());
boolean isPlay = true;
if (tParam != null) {
play.turn(tParam, dist, getSpeakableJSStreetName(currentSegment, next, true));
suppressDest = true;
} else if (next.getTurnType().isRoundAbout()) {
play.roundAbout(dist, next.getTurnType().getTurnAngle(), next.getTurnType().getExitOut(), getSpeakableJSStreetName(currentSegment, next, true));
// Other than in prepareTurn, in prepareRoundabout we do not announce destination, so we can repeat it one more time
suppressDest = false;
} else if (next.getTurnType().getValue() == TurnType.TU || next.getTurnType().getValue() == TurnType.TRU) {
play.makeUT(dist, getSpeakableJSStreetName(currentSegment, next, true));
suppressDest = true;
} else {
isPlay = false;
}
// 'then keep' preparation for next after next. (Also announces an interim straight segment, which is not pronounced above.)
if (pronounceNextNext != null) {
TurnType t = pronounceNextNext.getTurnType();
isPlay = true;
if (t.getValue() != TurnType.C && next.getTurnType().getValue() == TurnType.C) {
play.goAhead(dist, getSpeakableJSStreetName(currentSegment, next, true));
}
if (t.getValue() == TurnType.TL || t.getValue() == TurnType.TSHL || t.getValue() == TurnType.TSLL
|| t.getValue() == TurnType.TU || t.getValue() == TurnType.KL ) {
play.then().bearLeft(getSpeakableJSStreetName(currentSegment, next, false));
} else if (t.getValue() == TurnType.TR || t.getValue() == TurnType.TSHR || t.getValue() == TurnType.TSLR
|| t.getValue() == TurnType.TRU || t.getValue() == TurnType.KR) {
play.then().bearRight(getSpeakableJSStreetName(currentSegment, next, false));
}
}
if (isPlay) {
notifyOnVoiceMessage();
play.play();
}
}
}
private void playGoAhead(int dist, Map<String, String> streetName) {
CommandBuilder play = getNewCommandPlayerToPlay();
JSCommandBuilder playJs = (JSCommandBuilder) play;
if (play != null) {
notifyOnVoiceMessage();
playJs.goAhead(dist, streetName).play();
}
}
private void playMakeTurn(RouteSegmentResult currentSegment, RouteDirectionInfo next, RouteCalculationResult.NextDirectionInfo nextNextInfo) {
JSCommandBuilder play = (JSCommandBuilder) getNewCommandPlayerToPlay();
if (play != null) {
String tParam = getTurnType(next.getTurnType());
boolean isplay = true;
if (tParam != null) {
play.turn(tParam, getSpeakableJSStreetName(currentSegment, next, !suppressDest));
} else if (next.getTurnType().isRoundAbout()) {
play.roundAbout(next.getTurnType().getTurnAngle(), next.getTurnType().getExitOut(), getSpeakableJSStreetName(currentSegment, next, !suppressDest));
} else if (next.getTurnType().getValue() == TurnType.TU || next.getTurnType().getValue() == TurnType.TRU) {
play.makeUT(getSpeakableJSStreetName(currentSegment, next, !suppressDest));
// Do not announce goAheads
//} else if (next.getTurnType().getValue() == TurnType.C)) {
// play.goAhead();
} else {
isplay = false;
}
// Add turn after next
if ((nextNextInfo != null) && (nextNextInfo.directionInfo != null)) {
// This case only needed should we want a prompt at the end of straight segments (equivalent of makeTurn) when nextNextInfo should be announced again there.
if (nextNextInfo.directionInfo.getTurnType().getValue() != TurnType.C && next.getTurnType().getValue() == TurnType.C) {
play.goAhead();
isplay = true;
}
String t2Param = getTurnType(nextNextInfo.directionInfo.getTurnType());
if (t2Param != null) {
if (isplay) {
play.then();
play.turn(t2Param, nextNextInfo.distanceTo, new HashMap<String, String>());
}
} else if (nextNextInfo.directionInfo.getTurnType().isRoundAbout()) {
if (isplay) {
play.then();
play.roundAbout(nextNextInfo.distanceTo, nextNextInfo.directionInfo.getTurnType().getTurnAngle(),
nextNextInfo.directionInfo.getTurnType().getExitOut(), new HashMap<String, String>());
}
} else if (nextNextInfo.directionInfo.getTurnType().getValue() == TurnType.TU) {
if (isplay) {
play.then();
play.makeUT(nextNextInfo.distanceTo, new HashMap<String, String>());
}
}
}
if (isplay) {
notifyOnVoiceMessage();
play.play();
}
}
}
private void playGoAndArriveAtDestination(boolean repeat, RouteCalculationResult.NextDirectionInfo nextInfo, RouteSegmentResult currentSegment) {
RouteDirectionInfo next = nextInfo.directionInfo;
if (isTargetPoint(nextInfo) && (!playedAndArriveAtTarget || repeat)) {
if (next.getTurnType().goAhead()) {
playGoAhead(nextInfo.distanceTo, getSpeakableJSStreetName(currentSegment, next, false));
playAndArriveAtDestination(nextInfo);
playedAndArriveAtTarget = true;
} else if (nextInfo.distanceTo <= 2 * TURN_IN_DISTANCE) {
playAndArriveAtDestination(nextInfo);
playedAndArriveAtTarget = true;
}
}
}
}

View file

@ -97,7 +97,9 @@ public class RoutingHelper {
public RoutingHelper(OsmandApplication context){
this.app = context;
settings = context.getSettings();
voiceRouter = new VoiceRouter(this, settings);
boolean useJS = settings.USE_JS_VOICE_GUIDANCE.get();
voiceRouter = useJS ? new JSVoiceRouter(this, settings)
: new VoiceRouter(this, settings);
provider = new RouteProvider();
setAppMode(settings.APPLICATION_MODE.get());
}

View file

@ -28,27 +28,27 @@ import android.media.SoundPool;
public class VoiceRouter {
private static final int STATUS_UTWP_TOLD = -1;
private static final int STATUS_UNKNOWN = 0;
private static final int STATUS_LONG_PREPARE = 1;
private static final int STATUS_PREPARE = 2;
private static final int STATUS_TURN_IN = 3;
private static final int STATUS_TURN = 4;
private static final int STATUS_TOLD = 5;
static final int STATUS_UNKNOWN = 0;
static final int STATUS_LONG_PREPARE = 1;
static final int STATUS_PREPARE = 2;
static final int STATUS_TURN_IN = 3;
static final int STATUS_TURN = 4;
static final int STATUS_TOLD = 5;
private final RoutingHelper router;
private static CommandPlayer player;
private final OsmandSettings settings;
protected final RoutingHelper router;
protected static CommandPlayer player;
protected final OsmandSettings settings;
private static boolean mute = false;
private static int currentStatus = STATUS_UNKNOWN;
private static boolean playedAndArriveAtTarget = false;
private static float playGoAheadDist = 0;
static int currentStatus = STATUS_UNKNOWN;
static boolean playedAndArriveAtTarget = false;
static float playGoAheadDist = 0;
private static long lastAnnouncedSpeedLimit = 0;
private static long waitAnnouncedSpeedLimit = 0;
private static long lastAnnouncedOffRoute = 0;
private static long waitAnnouncedOffRoute = 0;
private static boolean suppressDest = false;
private static boolean announceBackOnRoute = false;
static boolean suppressDest = false;
static boolean announceBackOnRoute = false;
// private static long lastTimeRouteRecalcAnnounced = 0;
// Remember when last announcement was made
private static long lastAnnouncement = 0;
@ -66,9 +66,11 @@ public class VoiceRouter {
protected int TURN_DISTANCE = 0;
protected static VoiceCommandPending pendingCommand = null;
private static RouteDirectionInfo nextRouteDirection;
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<VoiceRouter.VoiceMessageListener, Integer>();
@ -164,7 +167,7 @@ public class VoiceRouter {
}
}
private double btScoDelayDistance = 0;
double btScoDelayDistance = 0;
public boolean isDistanceLess(float currentSpeed, double dist, double etalon, float defSpeed) {
if (defSpeed <= 0) {
@ -201,7 +204,7 @@ public class VoiceRouter {
}
}
private void nextStatusAfter(int previousStatus) {
void nextStatusAfter(int previousStatus) {
//STATUS_UNKNOWN=0 -> STATUS_LONG_PREPARE=1 -> STATUS_PREPARE=2 -> STATUS_TURN_IN=3 -> STATUS_TURN=4 -> STATUS_TOLD=5
if (previousStatus != STATUS_TOLD) {
this.currentStatus = previousStatus + 1;
@ -210,7 +213,7 @@ public class VoiceRouter {
}
}
private boolean statusNotPassed(int statusToCheck) {
boolean statusNotPassed(int statusToCheck) {
return currentStatus <= statusToCheck;
}
@ -395,14 +398,14 @@ public class VoiceRouter {
}
}
private boolean isTargetPoint(NextDirectionInfo info) {
boolean isTargetPoint(NextDirectionInfo info) {
boolean in = info != null && info.intermediatePoint;
boolean target = info == null || info.directionInfo == null
|| info.directionInfo.distance == 0;
return in || target;
}
private boolean needsInforming() {
boolean needsInforming() {
final Integer repeat = settings.KEEP_INFORMING.get();
if (repeat == null || repeat == 0) return false;
@ -557,7 +560,7 @@ public class VoiceRouter {
return false;
}
private void playThen() {
void playThen() {
CommandBuilder play = getNewCommandPlayerToPlay();
if (play != null) {
notifyOnVoiceMessage();
@ -715,7 +718,7 @@ public class VoiceRouter {
}
}
private void playAndArriveAtDestination(NextDirectionInfo info) {
void playAndArriveAtDestination(NextDirectionInfo info) {
if (isTargetPoint(info)) {
String pointName = info == null ? "" : info.pointName;
CommandBuilder play = getNewCommandPlayerToPlay();
@ -781,7 +784,7 @@ public class VoiceRouter {
}
}
private String getTurnType(TurnType t) {
String getTurnType(TurnType t) {
if (TurnType.TL == t.getValue()) {
return AbstractPrologCommandPlayer.A_LEFT;
} else if (TurnType.TSHL == t.getValue()) {

View file

@ -0,0 +1,164 @@
package net.osmand.plus.voice;
import android.content.Context;
import android.media.AudioManager;
import net.osmand.PlatformUtil;
import net.osmand.StateChangedListener;
import net.osmand.plus.ApplicationMode;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.api.AudioFocusHelper;
import org.apache.commons.logging.Log;
import java.util.List;
import alice.tuprolog.InvalidLibraryException;
import alice.tuprolog.Prolog;
import alice.tuprolog.Struct;
import alice.tuprolog.Term;
import alice.tuprolog.Var;
public abstract class AbstractJSCommandPlayer implements CommandPlayer, StateChangedListener<ApplicationMode> {
private static final Log log = PlatformUtil.getLog(AbstractJSCommandPlayer.class);
protected String language = "";
protected OsmandApplication ctx;
protected int streamType;
private ApplicationMode applicationMode;
public static boolean btScoStatus = false;
private static AudioFocusHelper mAudioFocusHelper;
public static String btScoInit = "";
protected AbstractJSCommandPlayer(OsmandApplication ctx, ApplicationMode applicationMode,
String voiceProvider) {
this.ctx = ctx;
this.applicationMode = applicationMode;
long time = System.currentTimeMillis();
this.ctx = ctx;
if (log.isInfoEnabled()) {
log.info("Initializing prolog system : " + (System.currentTimeMillis() - time)); //$NON-NLS-1$
}
this.streamType = ctx.getSettings().AUDIO_STREAM_GUIDANCE.getModeValue(applicationMode);
language = voiceProvider.substring(0, voiceProvider.indexOf("-"));
}
@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() {
if(ctx != null && ctx.getSettings() != null) {
ctx.getSettings().APPLICATION_MODE.removeListener(this);
}
abandonAudioFocus();
ctx = null;
}
@Override
public List<String> execute(List<Struct> listStruct) {
return null;
}
@Override
public void updateAudioStream(int streamType) {
}
@Override
public String getLanguage() {
return "en";
}
@Override
public boolean supportsStructuredStreetNames() {
return true;
}
@Override
public void stop() {
}
protected synchronized void abandonAudioFocus() {
log.debug("abandonAudioFocus");
if ((ctx != null && ctx.getSettings().AUDIO_STREAM_GUIDANCE.getModeValue(applicationMode) == 0) || (btScoStatus == true)) {
toggleBtSco(false);
}
if (ctx != null && mAudioFocusHelper != null) {
mAudioFocusHelper.abandonFocus(ctx, applicationMode, streamType);
}
mAudioFocusHelper = null;
}
private synchronized boolean toggleBtSco(boolean on) {
// Hardy, 2016-07-03: Establish a low quality BT SCO (Synchronous Connection-Oriented) link to interrupt e.g. a car stereo FM radio
if (on) {
try {
AudioManager mAudioManager = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);
if (mAudioManager == null || !mAudioManager.isBluetoothScoAvailableOffCall()) {
btScoInit = "Reported not available.";
return false;
}
mAudioManager.setMode(0);
mAudioManager.startBluetoothSco();
mAudioManager.setBluetoothScoOn(true);
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
btScoStatus = true;
} catch (Exception e) {
System.out.println("Exception starting BT SCO " + e.getMessage() );
btScoStatus = false;
btScoInit = "Available, but not initializad.\n(" + e.getMessage() + ")";
return false;
}
btScoInit = "Available, initialized OK.";
return true;
} else {
AudioManager mAudioManager = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);
if (mAudioManager == null) {
return false;
}
mAudioManager.setBluetoothScoOn(false);
mAudioManager.stopBluetoothSco();
mAudioManager.setMode(AudioManager.MODE_NORMAL);
btScoStatus = false;
return true;
}
}
public ApplicationMode getApplicationMode() {
return applicationMode;
}
@Override
public void stateChanged(ApplicationMode change) {
//
// if(prologSystem != null) {
// try {
// prologSystem.getTheoryManager().retract(new Struct("appMode", new Var()));
// } catch (Exception e) {
// log.error("Retract error: ", e);
// }
// prologSystem.getTheoryManager()
// .assertA(
// new Struct("appMode", new Struct(ctx.getSettings().APPLICATION_MODE.get().getStringKey()
// .toLowerCase())), true, "", true);
// }
}
}

View file

@ -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<Struct> listStruct = new ArrayList<Struct>();
public CommandBuilder(CommandPlayer commandPlayer){

View file

@ -0,0 +1,235 @@
package net.osmand.plus.voice;
import net.osmand.PlatformUtil;
import org.apache.commons.logging.Log;
import org.json.JSONObject;
import org.mozilla.javascript.Callable;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.NativeJSON;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
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);
private static final String SET_MODE = "setMode";
private static final String SET_METRIC_CONST = "setMetricConst";
private Context jsContext;
private List<String> listStruct = new ArrayList<>();
private ScriptableObject jsScope;
JSCommandBuilder(CommandPlayer commandPlayer) {
super(commandPlayer);
}
public void setJSContext(ScriptableObject jsScope) {
jsContext = Context.enter();
this.jsScope = jsScope;
}
private Object convertStreetName(Map<String, String> streetName) {
return NativeJSON.parse(jsContext, jsScope, new JSONObject(streetName).toString(),
new NullCallable());
}
public void setParameters(String metricCons, boolean tts) {
Object mode = jsScope.get(SET_MODE, jsScope);
Object metrics = jsScope.get(SET_METRIC_CONST, jsScope);
callVoidJsFunction(mode, new Object[]{tts});
callVoidJsFunction(metrics, new Object[]{metricCons});
}
private void callVoidJsFunction(Object function, Object[] params) {
if (function instanceof Function) {
Function jsFunction = (Function) function;
jsFunction.call(jsContext, jsScope, jsScope, params);
}
}
private JSCommandBuilder addCommand(String name, Object... args){
Object obj = jsScope.get(name);
if (obj instanceof Function) {
Function jsFunction = (Function) obj;
Object jsResult = jsFunction.call(jsContext, jsScope, jsScope, args);
listStruct.add(Context.toString(jsResult));
}
return this;
}
public JSCommandBuilder goAhead(){
return goAhead(-1, new HashMap<String, String>());
}
public JSCommandBuilder goAhead(double dist, Map<String, String> streetName){
return addCommand(C_GO_AHEAD, dist, convertStreetName(streetName));
}
public JSCommandBuilder makeUTwp(){
return makeUT(new HashMap<String, String>());
}
public JSCommandBuilder makeUT(Map<String, String> streetName){
return makeUT(-1, 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<String,String> streetName){
return addCommand(C_MAKE_UT, dist, convertStreetName(streetName));
}
public JSCommandBuilder prepareMakeUT(double dist, Map<String, String> streetName){
return addCommand(C_PREPARE_MAKE_UT, dist, convertStreetName(streetName));
}
public JSCommandBuilder turn(String param, Map<String, String> streetName) {
return turn(param, -1, streetName);
}
public JSCommandBuilder turn(String param, double dist, Map<String, String> streetName) {
return addCommand(C_TURN, param, dist, convertStreetName(streetName));
}
/**
*
* @param param A_LEFT, A_RIGHT, ...
* @param dist
* @return
*/
public JSCommandBuilder prepareTurn(String param, double dist, Map<String, String> streetName){
return addCommand(C_PREPARE_TURN, param, dist, convertStreetName(streetName));
}
public JSCommandBuilder prepareRoundAbout(double dist, int exit, Map<String, String> streetName){
return addCommand(C_PREPARE_ROUNDABOUT, dist, exit, convertStreetName(streetName));
}
public JSCommandBuilder roundAbout(double dist, double angle, int exit, Map<String, String> streetName){
return addCommand(C_ROUNDABOUT, dist, angle, exit, convertStreetName(streetName));
}
public JSCommandBuilder roundAbout(double angle, int exit, Map<String, String> 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<String,String> streetName){
return addCommand(C_BEAR_LEFT, convertStreetName(streetName));
}
public JSCommandBuilder bearRight(Map<String, String> streetName){
return addCommand(C_BEAR_RIGHT, convertStreetName(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<String> execute(){
alreadyExecuted = true;
return listStruct;
}
public class NullCallable implements Callable
{
@Override
public Object call(Context context, Scriptable scope, Scriptable holdable, Object[] objects)
{
return objects[1];
}
}
}

View file

@ -0,0 +1,101 @@
package net.osmand.plus.voice;
import android.media.MediaPlayer;
import android.system.Os;
import net.osmand.IndexConstants;
import net.osmand.PlatformUtil;
import net.osmand.plus.ApplicationMode;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.routing.VoiceRouter;
import org.apache.commons.logging.Log;
import org.mozilla.javascript.ScriptableObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static net.osmand.plus.voice.AbstractPrologCommandPlayer.DELAY_CONST;
public class JSMediaCommandPlayerImpl extends MediaCommandPlayerImpl {
private static final org.apache.commons.logging.Log log = PlatformUtil.getLog(JSMediaCommandPlayerImpl.class);
private ScriptableObject jsScope;
private OsmandApplication app;
public JSMediaCommandPlayerImpl(OsmandApplication ctx, ApplicationMode applicationMode, VoiceRouter vrt, String voiceProvider) throws CommandPlayerException {
super(ctx, applicationMode, vrt, voiceProvider);
app = ctx;
org.mozilla.javascript.Context context = org.mozilla.javascript.Context.enter();
context.setOptimizationLevel(-1);
jsScope = context.initSafeStandardObjects();
try {
BufferedReader br = new BufferedReader(new FileReader(new File(
app.getAppPath(IndexConstants.VOICE_INDEX_DIR).getAbsolutePath() +
"/" + voiceProvider + "/" + language + "_tts.js")));
context.evaluateReader(jsScope, br, "JS", 1, null);
br.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
org.mozilla.javascript.Context.exit();
}
}
@Override
public synchronized void playCommands(CommandBuilder builder) {
if(vrt.isMute()) {
return;
}
filesToPlay.addAll(splitAnnouncements(builder.execute()));
// If we have not already started to play audio, start.
if (mediaPlayer == null) {
requestAudioFocus();
// Delay first prompt of each batch to allow BT SCO connection being established
if (ctx != null && ctx.getSettings().AUDIO_STREAM_GUIDANCE.getModeValue(getApplicationMode()) == 0) {
try {
log.debug("Delaying MediaCommandPlayer for BT SCO");
Thread.sleep(ctx.getSettings().BT_SCO_DELAY.get());
} catch (InterruptedException e) {
}
}
}
playQueue();
}
private List<String> splitAnnouncements(List<String> execute) {
List<String> result = new ArrayList<>();
for (String files : execute) {
result.addAll(Arrays.asList(files.split(" ")));
}
return result;
}
@Override
public JSCommandBuilder newCommandBuilder() {
JSCommandBuilder commandBuilder = new JSCommandBuilder(this);
commandBuilder.setJSContext(jsScope);
commandBuilder.setParameters(app.getSettings().METRIC_SYSTEM.get().toTTSString(), false);
return commandBuilder;
}
public static boolean isMyData(File voiceDir) {
for (File f : voiceDir.listFiles()) {
if (f.getName().endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS)) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,348 @@
package net.osmand.plus.voice;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
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.IndexConstants;
import net.osmand.PlatformUtil;
import net.osmand.plus.ApplicationMode;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.activities.SettingsActivity;
import net.osmand.plus.routing.VoiceRouter;
import net.osmand.util.Algorithms;
import org.mozilla.javascript.ScriptableObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
public class JSTTSCommandPlayerImpl extends AbstractJSCommandPlayer {
private static final org.apache.commons.logging.Log log = PlatformUtil.getLog(JSTTSCommandPlayerImpl.class);
private static final String TAG = JSTTSCommandPlayerImpl.class.getSimpleName();
private static TextToSpeech mTts;
private static String ttsVoiceStatus = "";
private static String ttsVoiceUsed = "";
private boolean speechAllowed = false;
private Context mTtsContext;
private ScriptableObject jsScope;
private float cSpeechRate = 1;
private static final class IntentStarter implements
DialogInterface.OnClickListener {
private final Context ctx;
private final String intentAction;
private final Uri intentData;
private IntentStarter(Context ctx, String intentAction) {
this(ctx,intentAction, null);
}
private IntentStarter(Context ctx, String intentAction, Uri intentData) {
this.ctx = ctx;
this.intentAction = intentAction;
this.intentData = intentData;
}
@Override
public void onClick(DialogInterface dialog, int which) {
Intent installIntent = new Intent();
installIntent.setAction(intentAction);
if (intentData != null) {
installIntent.setData(intentData);
}
ctx.startActivity(installIntent);
}
}
private OsmandApplication app;
private ApplicationMode appMode;
private VoiceRouter vrt;
private HashMap<String, String> params = new HashMap<String, String>();
private static int ttsRequests = 0;
public JSTTSCommandPlayerImpl(Activity ctx, ApplicationMode applicationMode, VoiceRouter vrt, String voiceProvider) throws CommandPlayerException {
super((OsmandApplication) ctx.getApplication(), applicationMode, voiceProvider);
this.app = (OsmandApplication) ctx.getApplicationContext();
this.appMode = applicationMode;
this.vrt = vrt;
if (Algorithms.isEmpty(language)) {
throw new CommandPlayerException(
ctx.getString(R.string.voice_data_corrupted));
}
OsmandApplication app = (OsmandApplication) ctx.getApplicationContext();
if(app.accessibilityEnabled()) {
cSpeechRate = app.getSettings().SPEECH_RATE.get();
}
initializeEngine(app, ctx);
params.put(TextToSpeech.Engine.KEY_PARAM_STREAM, app.getSettings().AUDIO_STREAM_GUIDANCE
.getModeValue(getApplicationMode()).toString());
org.mozilla.javascript.Context context = org.mozilla.javascript.Context.enter();
context.setOptimizationLevel(-1);
jsScope = context.initSafeStandardObjects();
try {
BufferedReader br = new BufferedReader(new FileReader(new File(
app.getAppPath(IndexConstants.VOICE_INDEX_DIR).getAbsolutePath() +
"/" + voiceProvider + "/" + language + "_tts.js")));
context.evaluateReader(jsScope, br, "JS", 1, null);
br.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
org.mozilla.javascript.Context.exit();
}
}
private void initializeEngine(final Context ctx, final Activity act) {
if (mTtsContext != ctx) {
internalClear();
}
if (mTts == null) {
mTtsContext = ctx;
ttsVoiceStatus = "";
ttsVoiceUsed = "";
ttsRequests = 0;
final float speechRate = cSpeechRate;
final String[] lsplit = (language + "____.").split("[_\\-]");
// constructor supports lang_country_variant
Locale newLocale0 = new Locale(lsplit[0], lsplit[1], lsplit[2]);
// #3344: Try Locale builder instead of constructor (only available from API 21). Also supports script (for now supported as trailing x_x_x_Scrp)
if (android.os.Build.VERSION.SDK_INT >= 21) {
try {
newLocale0 = new Locale.Builder().setLanguage(lsplit[0]).setScript(lsplit[3]).setRegion(lsplit[1]).setVariant(lsplit[2]).build();
} catch (RuntimeException e) {
// Falls back to constructor
}
}
final Locale newLocale = newLocale0;
mTts = new TextToSpeech(ctx, new TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
if (status != TextToSpeech.SUCCESS) {
ttsVoiceStatus = "NO INIT SUCCESS";
internalClear();
} else if (mTts != null) {
speechAllowed = true;
switch (mTts.isLanguageAvailable(newLocale)) {
case TextToSpeech.LANG_MISSING_DATA:
if (isSettingsActivity(act)) {
AlertDialog.Builder builder = createAlertDialog(
R.string.tts_missing_language_data_title,
R.string.tts_missing_language_data,
new JSTTSCommandPlayerImpl.IntentStarter(
act,
TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA),
act);
builder.show();
}
ttsVoiceStatus = newLocale.getDisplayName() + ": LANG_MISSING_DATA";
ttsVoiceUsed = getVoiceUsed();
break;
case TextToSpeech.LANG_AVAILABLE:
ttsVoiceStatus = newLocale.getDisplayName() + ": LANG_AVAILABLE";
case TextToSpeech.LANG_COUNTRY_AVAILABLE:
ttsVoiceStatus = "".equals(ttsVoiceStatus) ? newLocale.getDisplayName() + ": LANG_COUNTRY_AVAILABLE" : ttsVoiceStatus;
case TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE:
try {
mTts.setLanguage(newLocale);
} catch(Exception e) {
e.printStackTrace();
mTts.setLanguage(Locale.getDefault());
}
if(speechRate != 1) {
mTts.setSpeechRate(speechRate);
}
ttsVoiceStatus = "".equals(ttsVoiceStatus) ? newLocale.getDisplayName() + ": LANG_COUNTRY_VAR_AVAILABLE" : ttsVoiceStatus;
ttsVoiceUsed = getVoiceUsed();
break;
case TextToSpeech.LANG_NOT_SUPPORTED:
//maybe weird, but I didn't want to introduce parameter in around 5 methods just to do this if condition
if (isSettingsActivity(act)) {
AlertDialog.Builder builder = createAlertDialog(
R.string.tts_language_not_supported_title,
R.string.tts_language_not_supported,
new JSTTSCommandPlayerImpl.IntentStarter(
act,
Intent.ACTION_VIEW, Uri.parse("market://search?q=text to speech engine"
)),
act);
builder.show();
}
ttsVoiceStatus = newLocale.getDisplayName() + ": LANG_NOT_SUPPORTED";
ttsVoiceUsed = getVoiceUsed();
break;
}
}
}
private boolean isSettingsActivity(final Context ctx) {
return ctx instanceof SettingsActivity;
}
private String getVoiceUsed() {
try {
if (android.os.Build.VERSION.SDK_INT >= 21) {
if (mTts.getVoice() != null) {
return mTts.getVoice().toString() + " (API " + android.os.Build.VERSION.SDK_INT + ")";
}
} else {
return mTts.getLanguage() + " (API " + android.os.Build.VERSION.SDK_INT + " only reports language)";
}
} catch (RuntimeException e) {
// mTts.getVoice() might throw NPE
}
return "-";
}
});
mTts.setOnUtteranceCompletedListener(new TextToSpeech.OnUtteranceCompletedListener() {
// The call back is on a binder thread.
@Override
public synchronized void onUtteranceCompleted(String utteranceId) {
if (--ttsRequests <= 0)
abandonAudioFocus();
log.debug("ttsRequests="+ttsRequests);
if (ttsRequests < 0) {
ttsRequests = 0;
}
}
});
}
}
@Override
public String getCurrentVoice() {
return null;
}
@Override
public JSCommandBuilder newCommandBuilder() {
JSCommandBuilder commandBuilder = new JSCommandBuilder(this);
commandBuilder.setJSContext(jsScope);
commandBuilder.setParameters(app.getSettings().METRIC_SYSTEM.get().toTTSString(), true);
return commandBuilder;
}
@Override
public void playCommands(CommandBuilder builder) {
final List<String> 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() && speechAllowed) {
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 stop(){
ttsRequests = 0;
if (mTts != null){
mTts.stop();
}
abandonAudioFocus();
}
public static String getTtsVoiceStatus() {
return ttsVoiceStatus;
}
public static String getTtsVoiceUsed() {
return ttsVoiceUsed;
}
@Override
public void clear() {
super.clear();
internalClear();
}
@Override
public void updateAudioStream(int streamType) {
super.updateAudioStream(streamType);
params.put(TextToSpeech.Engine.KEY_PARAM_STREAM, streamType+"");
}
@Override
public String getLanguage() {
return language;
}
@Override
public boolean supportsStructuredStreetNames() {
return true;
}
private void internalClear() {
ttsRequests = 0;
speechAllowed = false;
if (mTts != null) {
mTts.shutdown();
mTts = null;
}
abandonAudioFocus();
mTtsContext = null;
ttsVoiceStatus = "";
ttsVoiceUsed = "";
}
public static boolean isMyData(File voiceDir) {
boolean isTTS = voiceDir.getName().contains("tts");
if (!isTTS) {
return false;
}
for (File f : voiceDir.listFiles()) {
if (f.getName().endsWith(IndexConstants.TTSVOICE_INDEX_EXT_JS)) {
return true;
}
}
return false;
}
private AlertDialog.Builder createAlertDialog(int titleResID, int messageResID,
JSTTSCommandPlayerImpl.IntentStarter intentStarter, final Activity ctx) {
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setCancelable(true);
builder.setNegativeButton(R.string.shared_string_no, null);
builder.setPositiveButton(R.string.shared_string_yes, intentStarter);
builder.setTitle(titleResID);
builder.setMessage(messageResID);
return builder;
}
}

View file

@ -29,10 +29,10 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implemen
private static final Log log = PlatformUtil.getLog(MediaCommandPlayerImpl.class);
// playing media
private MediaPlayer mediaPlayer;
MediaPlayer mediaPlayer;
// indicates that player is ready to play first file
private List<String> filesToPlay = Collections.synchronizedList(new ArrayList<String>());
private VoiceRouter vrt;
List<String> filesToPlay = Collections.synchronizedList(new ArrayList<String>());
VoiceRouter vrt;
public MediaCommandPlayerImpl(OsmandApplication ctx, ApplicationMode applicationMode, VoiceRouter vrt, String voiceProvider)
@ -95,7 +95,7 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implemen
playQueue();
}
private synchronized void playQueue() {
synchronized void playQueue() {
if (mediaPlayer == null) {
mediaPlayer = new MediaPlayer();
}
@ -159,7 +159,7 @@ public class MediaCommandPlayerImpl extends AbstractPrologCommandPlayer implemen
* @param file
*/
private void playFile(File file) {
if (!file.exists()) {
if (!file.exists() || file.isDirectory()) {
log.error("Unable to play, does not exist: "+file);
playQueue();
return;