From f9691859212b71d93a39842fc50384cf20d18d23 Mon Sep 17 00:00:00 2001 From: Vladimir Byko-Ianko Date: Tue, 1 Sep 2015 17:01:45 +0300 Subject: [PATCH] iOS. Creation TTS on switching on. Doing this through objective C classes. --- .../MWMMapViewControlsManager.h | 2 +- .../MWMMapViewControlsManager.mm | 4 +- .../MWMNavigationDashboardManager.h | 2 +- .../MWMNavigationDashboardManager.mm | 4 +- .../Sound/MWMTextToSpeech.h | 8 +- .../Sound/MWMTextToSpeech.mm | 119 +++++++++++++++--- iphone/Maps/Classes/MapViewController.mm | 5 +- iphone/Maps/Classes/SearchView.mm | 7 +- routing/turns_sound.hpp | 2 + 9 files changed, 123 insertions(+), 30 deletions(-) diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h index de54d03c2b..bbb79a2711 100644 --- a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h +++ b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h @@ -47,7 +47,7 @@ #pragma mark - MWMNavigationDashboardManager - (void)setupRoutingDashboard:(location::FollowingInfo const &)info; -- (void)playSound:(vector const &)notifications; +- (void)playTurnNotifications; - (void)routingReady; - (void)routingNavigation; - (void)handleRoutingError; diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm index 3e13cb6c15..5ff0e7f1d1 100644 --- a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm +++ b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm @@ -120,9 +120,9 @@ [self.navigationManager setupDashboard:info]; } -- (void)playSound:(vector const &)notifications +- (void)playTurnNotifications { - [self.navigationManager playSound:notifications]; + [self.navigationManager playTurnNotifications]; } - (void)handleRoutingError diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager.h b/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager.h index a660e9aebf..dcdcbecc1b 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager.h +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager.h @@ -42,7 +42,7 @@ typedef NS_ENUM(NSUInteger, MWMNavigationDashboardState) - (instancetype)init __attribute__((unavailable("init is not available"))); - (instancetype)initWithParentView:(UIView *)view delegate:(id)delegate; - (void)setupDashboard:(location::FollowingInfo const &)info; -- (void)playSound:(vector const &)notifications; +- (void)playTurnNotifications; - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation; - (void)setRouteBuildingProgress:(CGFloat)progress; diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager.mm b/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager.mm index 8384711816..0d43cb4ec5 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager.mm +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/MWMNavigationDashboardManager.mm @@ -94,9 +94,9 @@ [self updateDashboard]; } -- (void)playSound:(vector const &)notifications +- (void)playTurnNotifications { - [self.tts speakNotifications:notifications]; + [self.tts playTurnNotifications]; } - (void)handleError diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.h b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.h index ab481d3fb2..493fbdaafe 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.h +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.h @@ -11,9 +11,15 @@ #include "std/string.hpp" #include "std/vector.hpp" +FOUNDATION_EXPORT NSString * const MWMTEXTTOSPEECH_ENABLE; +FOUNDATION_EXPORT NSString * const MWMTEXTTOSPEECH_DISABLE; + @interface MWMTextToSpeech : NSObject - (instancetype)init; -- (void)speakNotifications:(vector const &)turnNotifications; +- (BOOL)isEnable; +- (void)enable; +- (void)disable; +- (void)playTurnNotifications; @end diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.mm b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.mm index 293c1a1161..f4cab101ba 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.mm +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.mm @@ -12,6 +12,9 @@ #include "Framework.h" +NSString * const MWMTEXTTOSPEECH_ENABLE = @"MWMTEXTTOSPEECH_ENABLE"; +NSString * const MWMTEXTTOSPEECH_DISABLE = @"MWMTEXTTOSPEECH_DISABLE"; + @interface MWMTextToSpeech() @property (nonatomic) AVSpeechSynthesizer * speechSynthesizer; @property (nonatomic) AVSpeechSynthesisVoice * speechVoice; @@ -25,17 +28,27 @@ self = [super init]; if (self) { - self.speechSynthesizer = [[AVSpeechSynthesizer alloc] init]; - - // TODO(vbykoianko) Use [NSLocale preferredLanguages] instead of [AVSpeechSynthesisVoice currentLanguageCode]. - // [AVSpeechSynthesisVoice currentLanguageCode] is used now because of we need a language code in BCP-47. - [self setLocaleIfAvailable:[AVSpeechSynthesisVoice currentLanguageCode]]; // iOS has an issue with speechRate. AVSpeechUtteranceDefaultSpeechRate does not work correctly. It's a work around. self.speechRate = isIOSVersionLessThan(@"7.1.1") ? 0.3 : 0.15; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(enable) + name:MWMTEXTTOSPEECH_ENABLE + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(disable) + name:MWMTEXTTOSPEECH_DISABLE + object:nil]; } return self; } +-(void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + + (NSString *)twineFromBCP47:(NSString *)bcp47LangName { NSAssert(bcp47LangName, @"bcp47LangName is nil"); @@ -52,24 +65,81 @@ return [bcp47LangName substringToIndex:2]; } -- (void)setLocaleIfAvailable:(NSString *)locale +- (bool)isValid { - NSAssert(locale, @"locale is nil"); + return _speechSynthesizer != nil && _speechVoice != nil; +} + +- (void)enable +{ + if (YES == [self isEnable]) + return; + + if (![self isValid]) + [self createSynthesizer]; + + GetFramework().EnableTurnNotifications(true); +} + +- (void)disable +{ + if (NO == [self isEnable]) + return; + + GetFramework().EnableTurnNotifications(false); +} + +- (BOOL)isEnable +{ + return GetFramework().AreTurnNotificationsEnabled() ? YES : NO; +} + +- (void)createSynthesizer +{ + self.speechSynthesizer = [[AVSpeechSynthesizer alloc] init]; + + // TODO(vbykoianko) Use [NSLocale preferredLanguages] instead of [AVSpeechSynthesisVoice currentLanguageCode]. + // [AVSpeechSynthesisVoice currentLanguageCode] is used now because of we need a language code in BCP-47. + [self createVoice:[AVSpeechSynthesisVoice currentLanguageCode]]; +} + +- (void)createVoice:(NSString *)locale +{ + NSString * const DEFAULT_LANG = @"en-US"; + + if (!locale) + { + locale = DEFAULT_LANG; + NSAssert(locale, @"locale is nil. Trying default locale."); + } NSArray * availTtsLangs = [AVSpeechSynthesisVoice speechVoices]; - NSAssert(availTtsLangs, @"availTtsLangs is nil"); + + if (!availTtsLangs) + { + NSAssert(availTtsLangs, @"availTtsLangs is nil. MWMTextToSpeech is not valid."); + return; // self is not valid. + } AVSpeechSynthesisVoice * voice = [AVSpeechSynthesisVoice voiceWithLanguage:locale]; - if(!voice || ![availTtsLangs containsObject:voice]) - locale = @"en-US"; + if (!voice || ![availTtsLangs containsObject:voice]) + { + locale = DEFAULT_LANG; + AVSpeechSynthesisVoice * voice = [AVSpeechSynthesisVoice voiceWithLanguage:locale]; + if (!voice || ![availTtsLangs containsObject:voice]) + { + NSAssert(availTtsLangs, @"Available language for TTS not found. MWMTextToSpeech is not valid."); + return; // self is not valid. + } + } self.speechVoice = [AVSpeechSynthesisVoice voiceWithLanguage:locale]; GetFramework().SetTurnNotificationsLocale([[MWMTextToSpeech twineFromBCP47:locale] UTF8String]); } -- (void)speakText:(NSString *)textToSpeak +- (void)speakOneString:(NSString *)textToSpeak { - if (!textToSpeak) + if (!textToSpeak || [textToSpeak length] == 0) return; NSLog(@"Speak text: %@", textToSpeak); @@ -79,13 +149,30 @@ [self.speechSynthesizer speakUtterance:utterance]; } -- (void)speakNotifications:(vector const &)turnNotifications +- (void)speak:(vector const &)strings { - if (turnNotifications.empty()) + if (strings.empty()) return; - for (auto const & text : turnNotifications) - [self speakText:@(text.c_str())]; + for (auto const & text : strings) + [self speakOneString:@(text.c_str())]; +} + +- (void)playTurnNotifications +{ + if (![self isValid]) + return; + + Framework & frm = GetFramework(); + if (!frm.IsRoutingActive()) + return; + + vector notifications; + frm.GenerateTurnSound(notifications); + if (notifications.empty()) + return; + + [self speak:notifications]; } @end diff --git a/iphone/Maps/Classes/MapViewController.mm b/iphone/Maps/Classes/MapViewController.mm index 1ef145dbcb..398dadfd3c 100644 --- a/iphone/Maps/Classes/MapViewController.mm +++ b/iphone/Maps/Classes/MapViewController.mm @@ -164,10 +164,7 @@ typedef NS_OPTIONS(NSUInteger, MapInfoView) if (res.IsValid()) [self.controlsManager setupRoutingDashboard:res]; - vector notifications; - frm.GenerateTurnSound(notifications); - if (!notifications.empty()) - [self.controlsManager playSound:notifications]; + [self.controlsManager playTurnNotifications]; } - (void)onCompassUpdate:(location::CompassInfo const &)info diff --git a/iphone/Maps/Classes/SearchView.mm b/iphone/Maps/Classes/SearchView.mm index 2f6e91805a..f22e779761 100644 --- a/iphone/Maps/Classes/SearchView.mm +++ b/iphone/Maps/Classes/SearchView.mm @@ -6,6 +6,7 @@ #import "MapViewController.h" #import "MWMMapViewControlsManager.h" #import "MWMSearchDownloadMapRequest.h" +#import "MWMTextToSpeech.h" #import "SearchCategoryCell.h" #import "SearchResultCell.h" #import "SearchShowOnMapCell.h" @@ -401,10 +402,10 @@ static BOOL keyboardLoaded = NO; // turn notification if (sound) - GetFramework().EnableTurnNotifications(true); + [[NSNotificationCenter defaultCenter] postNotificationName:MWMTEXTTOSPEECH_ENABLE object:nil]; if (nosound) - GetFramework().EnableTurnNotifications(false); - + [[NSNotificationCenter defaultCenter] postNotificationName:MWMTEXTTOSPEECH_DISABLE object:nil]; + // close Search panel [self searchBarDidPressCancelButton:nil]; diff --git a/routing/turns_sound.hpp b/routing/turns_sound.hpp index 0db7f14327..c13fc4adf5 100644 --- a/routing/turns_sound.hpp +++ b/routing/turns_sound.hpp @@ -33,6 +33,8 @@ string DebugPrint(PronouncedNotification const notificationProgress); /// and relevant speed. class TurnsSound { + /// m_enabled == true when tts is switch on. + /// Important! Clients (iOS/Android) implies that m_enabled is false by default. bool m_enabled; /// In m_speedMetersPerSecond is intended for some speed which will be used for /// convertion a distance in seconds to distance in meters. It could be a current