| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include <string> | |
| 6 | |
| 7 #include "base/mac/cocoa_protocols.h" | |
| 8 #include "base/memory/scoped_nsobject.h" | |
| 9 #include "base/memory/singleton.h" | |
| 10 #include "base/sys_string_conversions.h" | |
| 11 #include "base/values.h" | |
| 12 #include "chrome/browser/extensions/extension_function.h" | |
| 13 #include "chrome/browser/extensions/extension_tts_api_constants.h" | |
| 14 #include "chrome/browser/extensions/extension_tts_api_controller.h" | |
| 15 #include "chrome/browser/extensions/extension_tts_api_platform.h" | |
| 16 | |
| 17 #import <Cocoa/Cocoa.h> | |
| 18 | |
| 19 class ExtensionTtsPlatformImplMac; | |
| 20 | |
| 21 @interface ChromeTtsDelegate : NSObject <NSSpeechSynthesizerDelegate> { | |
| 22 @private | |
| 23 ExtensionTtsPlatformImplMac* ttsImplMac_; // weak. | |
| 24 } | |
| 25 | |
| 26 - (id)initWithPlatformImplMac:(ExtensionTtsPlatformImplMac*)ttsImplMac; | |
| 27 | |
| 28 @end | |
| 29 | |
| 30 class ExtensionTtsPlatformImplMac : public ExtensionTtsPlatformImpl { | |
| 31 public: | |
| 32 virtual bool PlatformImplAvailable() { | |
| 33 return true; | |
| 34 } | |
| 35 | |
| 36 virtual bool Speak( | |
| 37 int utterance_id, | |
| 38 const std::string& utterance, | |
| 39 const std::string& lang, | |
| 40 const UtteranceContinuousParameters& params); | |
| 41 | |
| 42 virtual bool StopSpeaking(); | |
| 43 | |
| 44 virtual bool IsSpeaking(); | |
| 45 | |
| 46 virtual bool SendsEvent(TtsEventType event_type); | |
| 47 | |
| 48 // Called by ChromeTtsDelegate when we get a callback from the | |
| 49 // native speech engine. | |
| 50 void OnSpeechEvent(NSSpeechSynthesizer* sender, | |
| 51 TtsEventType event_type, | |
| 52 int char_index, | |
| 53 const std::string& error_message); | |
| 54 | |
| 55 // Get the single instance of this class. | |
| 56 static ExtensionTtsPlatformImplMac* GetInstance(); | |
| 57 | |
| 58 private: | |
| 59 ExtensionTtsPlatformImplMac(); | |
| 60 virtual ~ExtensionTtsPlatformImplMac(); | |
| 61 | |
| 62 scoped_nsobject<NSSpeechSynthesizer> speech_synthesizer_; | |
| 63 scoped_nsobject<ChromeTtsDelegate> delegate_; | |
| 64 int utterance_id_; | |
| 65 std::string utterance_; | |
| 66 bool sent_start_event_; | |
| 67 | |
| 68 friend struct DefaultSingletonTraits<ExtensionTtsPlatformImplMac>; | |
| 69 | |
| 70 DISALLOW_COPY_AND_ASSIGN(ExtensionTtsPlatformImplMac); | |
| 71 }; | |
| 72 | |
| 73 // static | |
| 74 ExtensionTtsPlatformImpl* ExtensionTtsPlatformImpl::GetInstance() { | |
| 75 return ExtensionTtsPlatformImplMac::GetInstance(); | |
| 76 } | |
| 77 | |
| 78 bool ExtensionTtsPlatformImplMac::Speak( | |
| 79 int utterance_id, | |
| 80 const std::string& utterance, | |
| 81 const std::string& lang, | |
| 82 const UtteranceContinuousParameters& params) { | |
| 83 // Deliberately construct a new speech synthesizer every time Speak is | |
| 84 // called, otherwise there's no way to know whether calls to the delegate | |
| 85 // apply to the current utterance or a previous utterance. In | |
| 86 // experimentation, the overhead of constructing and destructing a | |
| 87 // NSSpeechSynthesizer is minimal. | |
| 88 speech_synthesizer_.reset([[NSSpeechSynthesizer alloc] init]); | |
| 89 [speech_synthesizer_ setDelegate:delegate_]; | |
| 90 | |
| 91 utterance_id_ = utterance_id; | |
| 92 sent_start_event_ = false; | |
| 93 | |
| 94 // TODO: convert SSML to SAPI xml. http://crbug.com/88072 | |
| 95 utterance_ = utterance; | |
| 96 | |
| 97 // TODO: support languages other than the default: crbug.com/88059 | |
| 98 | |
| 99 if (params.rate >= 0.0) { | |
| 100 // The TTS api defines rate via words per minute. Let 200 be the default. | |
| 101 [speech_synthesizer_ | |
| 102 setObject:[NSNumber numberWithInt:params.rate * 200] | |
| 103 forProperty:NSSpeechRateProperty error:nil]; | |
| 104 } | |
| 105 | |
| 106 if (params.pitch >= 0.0) { | |
| 107 // The TTS api allows an approximate range of 30 to 65 for speech pitch. | |
| 108 [speech_synthesizer_ | |
| 109 setObject: [NSNumber numberWithInt:(params.pitch * 17 + 30)] | |
| 110 forProperty:NSSpeechPitchBaseProperty error:nil]; | |
| 111 } | |
| 112 | |
| 113 if (params.volume >= 0.0) { | |
| 114 [speech_synthesizer_ | |
| 115 setObject: [NSNumber numberWithFloat:params.volume] | |
| 116 forProperty:NSSpeechVolumeProperty error:nil]; | |
| 117 } | |
| 118 | |
| 119 return [speech_synthesizer_ startSpeakingString: | |
| 120 [NSString stringWithUTF8String: utterance.c_str()]]; | |
| 121 } | |
| 122 | |
| 123 bool ExtensionTtsPlatformImplMac::StopSpeaking() { | |
| 124 if (speech_synthesizer_.get()) { | |
| 125 [speech_synthesizer_ stopSpeaking]; | |
| 126 speech_synthesizer_.reset(nil); | |
| 127 } | |
| 128 return true; | |
| 129 } | |
| 130 | |
| 131 bool ExtensionTtsPlatformImplMac::IsSpeaking() { | |
| 132 // Note: this is OK even if speech_synthesizer_ is nil, it will return false. | |
| 133 return [speech_synthesizer_ isSpeaking]; | |
| 134 } | |
| 135 | |
| 136 bool ExtensionTtsPlatformImplMac::SendsEvent(TtsEventType event_type) { | |
| 137 return (event_type == TTS_EVENT_START || | |
| 138 event_type == TTS_EVENT_END || | |
| 139 event_type == TTS_EVENT_WORD || | |
| 140 event_type == TTS_EVENT_ERROR); | |
| 141 } | |
| 142 | |
| 143 void ExtensionTtsPlatformImplMac::OnSpeechEvent( | |
| 144 NSSpeechSynthesizer* sender, | |
| 145 TtsEventType event_type, | |
| 146 int char_index, | |
| 147 const std::string& error_message) { | |
| 148 // Don't send events from an utterance that's already completed. | |
| 149 // This depends on the fact that we construct a new NSSpeechSynthesizer | |
| 150 // each time we call Speak. | |
| 151 if (sender != speech_synthesizer_.get()) | |
| 152 return; | |
| 153 | |
| 154 if (event_type == TTS_EVENT_END) | |
| 155 char_index = utterance_.size(); | |
| 156 ExtensionTtsController* controller = ExtensionTtsController::GetInstance(); | |
| 157 if (event_type == TTS_EVENT_WORD && !sent_start_event_) { | |
| 158 controller->OnTtsEvent( | |
| 159 utterance_id_, TTS_EVENT_START, 0, ""); | |
| 160 sent_start_event_ = true; | |
| 161 } | |
| 162 controller->OnTtsEvent( | |
| 163 utterance_id_, event_type, char_index, error_message); | |
| 164 } | |
| 165 | |
| 166 ExtensionTtsPlatformImplMac::ExtensionTtsPlatformImplMac() { | |
| 167 utterance_id_ = -1; | |
| 168 sent_start_event_ = true; | |
| 169 | |
| 170 delegate_.reset([[ChromeTtsDelegate alloc] initWithPlatformImplMac:this]); | |
| 171 } | |
| 172 | |
| 173 ExtensionTtsPlatformImplMac::~ExtensionTtsPlatformImplMac() { | |
| 174 } | |
| 175 | |
| 176 // static | |
| 177 ExtensionTtsPlatformImplMac* ExtensionTtsPlatformImplMac::GetInstance() { | |
| 178 return Singleton<ExtensionTtsPlatformImplMac>::get(); | |
| 179 } | |
| 180 | |
| 181 @implementation ChromeTtsDelegate | |
| 182 | |
| 183 - (id)initWithPlatformImplMac:(ExtensionTtsPlatformImplMac*)ttsImplMac { | |
| 184 if ((self = [super init])) { | |
| 185 ttsImplMac_ = ttsImplMac; | |
| 186 } | |
| 187 return self; | |
| 188 } | |
| 189 | |
| 190 - (void)speechSynthesizer:(NSSpeechSynthesizer*)sender | |
| 191 didFinishSpeaking:(BOOL)finished_speaking { | |
| 192 ttsImplMac_->OnSpeechEvent(sender, TTS_EVENT_END, 0, ""); | |
| 193 } | |
| 194 | |
| 195 - (void)speechSynthesizer:(NSSpeechSynthesizer*)sender | |
| 196 willSpeakWord:(NSRange)character_range | |
| 197 ofString:(NSString*)string { | |
| 198 ttsImplMac_->OnSpeechEvent(sender, TTS_EVENT_WORD, | |
| 199 character_range.location, ""); | |
| 200 } | |
| 201 | |
| 202 - (void)speechSynthesizer:(NSSpeechSynthesizer*)sender | |
| 203 didEncounterErrorAtIndex:(NSUInteger)character_index | |
| 204 ofString:(NSString*)string | |
| 205 message:(NSString*)message { | |
| 206 std::string message_utf8 = base::SysNSStringToUTF8(message); | |
| 207 ttsImplMac_->OnSpeechEvent(sender, TTS_EVENT_ERROR, character_index, | |
| 208 message_utf8); | |
| 209 } | |
| 210 | |
| 211 @end | |
| OLD | NEW |