| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <string> |
| |
| #include "base/mac/cocoa_protocols.h" |
| #include "base/memory/scoped_nsobject.h" |
| #include "base/memory/singleton.h" |
| #include "base/sys_string_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/extensions/extension_function.h" |
| #include "chrome/browser/extensions/extension_tts_api_constants.h" |
| #include "chrome/browser/extensions/extension_tts_api_controller.h" |
| #include "chrome/browser/extensions/extension_tts_api_platform.h" |
| |
| #import <Cocoa/Cocoa.h> |
| |
| class ExtensionTtsPlatformImplMac; |
| |
| @interface ChromeTtsDelegate : NSObject <NSSpeechSynthesizerDelegate> { |
| @private |
| ExtensionTtsPlatformImplMac* ttsImplMac_; // weak. |
| } |
| |
| - (id)initWithPlatformImplMac:(ExtensionTtsPlatformImplMac*)ttsImplMac; |
| |
| @end |
| |
| class ExtensionTtsPlatformImplMac : public ExtensionTtsPlatformImpl { |
| public: |
| virtual bool PlatformImplAvailable() { |
| return true; |
| } |
| |
| virtual bool Speak( |
| int utterance_id, |
| const std::string& utterance, |
| const std::string& lang, |
| const UtteranceContinuousParameters& params); |
| |
| virtual bool StopSpeaking(); |
| |
| virtual bool IsSpeaking(); |
| |
| virtual bool SendsEvent(TtsEventType event_type); |
| |
| // Called by ChromeTtsDelegate when we get a callback from the |
| // native speech engine. |
| void OnSpeechEvent(NSSpeechSynthesizer* sender, |
| TtsEventType event_type, |
| int char_index, |
| const std::string& error_message); |
| |
| // Get the single instance of this class. |
| static ExtensionTtsPlatformImplMac* GetInstance(); |
| |
| private: |
| ExtensionTtsPlatformImplMac(); |
| virtual ~ExtensionTtsPlatformImplMac(); |
| |
| scoped_nsobject<NSSpeechSynthesizer> speech_synthesizer_; |
| scoped_nsobject<ChromeTtsDelegate> delegate_; |
| int utterance_id_; |
| std::string utterance_; |
| bool sent_start_event_; |
| |
| friend struct DefaultSingletonTraits<ExtensionTtsPlatformImplMac>; |
| |
| DISALLOW_COPY_AND_ASSIGN(ExtensionTtsPlatformImplMac); |
| }; |
| |
| // static |
| ExtensionTtsPlatformImpl* ExtensionTtsPlatformImpl::GetInstance() { |
| return ExtensionTtsPlatformImplMac::GetInstance(); |
| } |
| |
| bool ExtensionTtsPlatformImplMac::Speak( |
| int utterance_id, |
| const std::string& utterance, |
| const std::string& lang, |
| const UtteranceContinuousParameters& params) { |
| // Deliberately construct a new speech synthesizer every time Speak is |
| // called, otherwise there's no way to know whether calls to the delegate |
| // apply to the current utterance or a previous utterance. In |
| // experimentation, the overhead of constructing and destructing a |
| // NSSpeechSynthesizer is minimal. |
| speech_synthesizer_.reset([[NSSpeechSynthesizer alloc] init]); |
| [speech_synthesizer_ setDelegate:delegate_]; |
| |
| utterance_id_ = utterance_id; |
| sent_start_event_ = false; |
| |
| // TODO: convert SSML to SAPI xml. http://crbug.com/88072 |
| utterance_ = utterance; |
| |
| // TODO: support languages other than the default: crbug.com/88059 |
| |
| if (params.rate >= 0.0) { |
| // The TTS api defines rate via words per minute. Let 200 be the default. |
| [speech_synthesizer_ |
| setObject:[NSNumber numberWithInt:params.rate * 200] |
| forProperty:NSSpeechRateProperty error:nil]; |
| } |
| |
| if (params.pitch >= 0.0) { |
| // The TTS api allows an approximate range of 30 to 65 for speech pitch. |
| [speech_synthesizer_ |
| setObject: [NSNumber numberWithInt:(params.pitch * 17 + 30)] |
| forProperty:NSSpeechPitchBaseProperty error:nil]; |
| } |
| |
| if (params.volume >= 0.0) { |
| [speech_synthesizer_ |
| setObject: [NSNumber numberWithFloat:params.volume] |
| forProperty:NSSpeechVolumeProperty error:nil]; |
| } |
| |
| return [speech_synthesizer_ startSpeakingString: |
| [NSString stringWithUTF8String: utterance.c_str()]]; |
| } |
| |
| bool ExtensionTtsPlatformImplMac::StopSpeaking() { |
| if (speech_synthesizer_.get()) { |
| [speech_synthesizer_ stopSpeaking]; |
| speech_synthesizer_.reset(nil); |
| } |
| return true; |
| } |
| |
| bool ExtensionTtsPlatformImplMac::IsSpeaking() { |
| // Note: this is OK even if speech_synthesizer_ is nil, it will return false. |
| return [speech_synthesizer_ isSpeaking]; |
| } |
| |
| bool ExtensionTtsPlatformImplMac::SendsEvent(TtsEventType event_type) { |
| return (event_type == TTS_EVENT_START || |
| event_type == TTS_EVENT_END || |
| event_type == TTS_EVENT_WORD || |
| event_type == TTS_EVENT_ERROR); |
| } |
| |
| void ExtensionTtsPlatformImplMac::OnSpeechEvent( |
| NSSpeechSynthesizer* sender, |
| TtsEventType event_type, |
| int char_index, |
| const std::string& error_message) { |
| // Don't send events from an utterance that's already completed. |
| // This depends on the fact that we construct a new NSSpeechSynthesizer |
| // each time we call Speak. |
| if (sender != speech_synthesizer_.get()) |
| return; |
| |
| if (event_type == TTS_EVENT_END) |
| char_index = utterance_.size(); |
| ExtensionTtsController* controller = ExtensionTtsController::GetInstance(); |
| if (event_type == TTS_EVENT_WORD && !sent_start_event_) { |
| controller->OnTtsEvent( |
| utterance_id_, TTS_EVENT_START, 0, ""); |
| sent_start_event_ = true; |
| } |
| controller->OnTtsEvent( |
| utterance_id_, event_type, char_index, error_message); |
| } |
| |
| ExtensionTtsPlatformImplMac::ExtensionTtsPlatformImplMac() { |
| utterance_id_ = -1; |
| sent_start_event_ = true; |
| |
| delegate_.reset([[ChromeTtsDelegate alloc] initWithPlatformImplMac:this]); |
| } |
| |
| ExtensionTtsPlatformImplMac::~ExtensionTtsPlatformImplMac() { |
| } |
| |
| // static |
| ExtensionTtsPlatformImplMac* ExtensionTtsPlatformImplMac::GetInstance() { |
| return Singleton<ExtensionTtsPlatformImplMac>::get(); |
| } |
| |
| @implementation ChromeTtsDelegate |
| |
| - (id)initWithPlatformImplMac:(ExtensionTtsPlatformImplMac*)ttsImplMac { |
| if ((self = [super init])) { |
| ttsImplMac_ = ttsImplMac; |
| } |
| return self; |
| } |
| |
| - (void)speechSynthesizer:(NSSpeechSynthesizer*)sender |
| didFinishSpeaking:(BOOL)finished_speaking { |
| ttsImplMac_->OnSpeechEvent(sender, TTS_EVENT_END, 0, ""); |
| } |
| |
| - (void)speechSynthesizer:(NSSpeechSynthesizer*)sender |
| willSpeakWord:(NSRange)character_range |
| ofString:(NSString*)string { |
| ttsImplMac_->OnSpeechEvent(sender, TTS_EVENT_WORD, |
| character_range.location, ""); |
| } |
| |
| - (void)speechSynthesizer:(NSSpeechSynthesizer*)sender |
| didEncounterErrorAtIndex:(NSUInteger)character_index |
| ofString:(NSString*)string |
| message:(NSString*)message { |
| std::string message_utf8 = base::SysNSStringToUTF8(message); |
| ttsImplMac_->OnSpeechEvent(sender, TTS_EVENT_ERROR, character_index, |
| message_utf8); |
| } |
| |
| @end |