// 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 #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 class ExtensionTtsPlatformImplMac; @interface ChromeTtsDelegate : NSObject { @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 speech_synthesizer_; scoped_nsobject delegate_; int utterance_id_; std::string utterance_; bool sent_start_event_; friend struct DefaultSingletonTraits; 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::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