// Copyright (c) 2012 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 "chrome/browser/speech/extension_api/tts_extension_api.h" #include #include "base/lazy_instance.h" #include "base/values.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/speech/extension_api/tts_engine_extension_api.h" #include "chrome/browser/speech/extension_api/tts_engine_extension_observer.h" #include "chrome/browser/speech/extension_api/tts_extension_api_constants.h" #include "chrome/browser/speech/tts_controller.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_function_registry.h" #include "ui/base/l10n/l10n_util.h" namespace constants = tts_extension_api_constants; namespace events { const char kOnEvent[] = "tts.onEvent"; }; // namespace events const char *TtsEventTypeToString(TtsEventType event_type) { switch (event_type) { case TTS_EVENT_START: return constants::kEventTypeStart; case TTS_EVENT_END: return constants::kEventTypeEnd; case TTS_EVENT_WORD: return constants::kEventTypeWord; case TTS_EVENT_SENTENCE: return constants::kEventTypeSentence; case TTS_EVENT_MARKER: return constants::kEventTypeMarker; case TTS_EVENT_INTERRUPTED: return constants::kEventTypeInterrupted; case TTS_EVENT_CANCELLED: return constants::kEventTypeCancelled; case TTS_EVENT_ERROR: return constants::kEventTypeError; case TTS_EVENT_PAUSE: return constants::kEventTypePause; case TTS_EVENT_RESUME: return constants::kEventTypeResume; default: NOTREACHED(); return constants::kEventTypeError; } } TtsEventType TtsEventTypeFromString(const std::string& str) { if (str == constants::kEventTypeStart) return TTS_EVENT_START; if (str == constants::kEventTypeEnd) return TTS_EVENT_END; if (str == constants::kEventTypeWord) return TTS_EVENT_WORD; if (str == constants::kEventTypeSentence) return TTS_EVENT_SENTENCE; if (str == constants::kEventTypeMarker) return TTS_EVENT_MARKER; if (str == constants::kEventTypeInterrupted) return TTS_EVENT_INTERRUPTED; if (str == constants::kEventTypeCancelled) return TTS_EVENT_CANCELLED; if (str == constants::kEventTypeError) return TTS_EVENT_ERROR; if (str == constants::kEventTypePause) return TTS_EVENT_PAUSE; if (str == constants::kEventTypeResume) return TTS_EVENT_RESUME; NOTREACHED(); return TTS_EVENT_ERROR; } namespace extensions { // One of these is constructed for each utterance, and deleted // when the utterance gets any final event. class TtsExtensionEventHandler : public UtteranceEventDelegate { public: explicit TtsExtensionEventHandler(const std::string& src_extension_id); void OnTtsEvent(Utterance* utterance, TtsEventType event_type, int char_index, const std::string& error_message) override; private: // The extension ID of the extension that called speak() and should // receive events. std::string src_extension_id_; }; TtsExtensionEventHandler::TtsExtensionEventHandler( const std::string& src_extension_id) : src_extension_id_(src_extension_id) { } void TtsExtensionEventHandler::OnTtsEvent(Utterance* utterance, TtsEventType event_type, int char_index, const std::string& error_message) { if (utterance->src_id() < 0) { if (utterance->finished()) delete this; return; } const std::set& desired_event_types = utterance->desired_event_types(); if (desired_event_types.size() > 0 && desired_event_types.find(event_type) == desired_event_types.end()) { if (utterance->finished()) delete this; return; } const char *event_type_string = TtsEventTypeToString(event_type); scoped_ptr details(new base::DictionaryValue()); if (char_index >= 0) details->SetInteger(constants::kCharIndexKey, char_index); details->SetString(constants::kEventTypeKey, event_type_string); if (event_type == TTS_EVENT_ERROR) { details->SetString(constants::kErrorMessageKey, error_message); } details->SetInteger(constants::kSrcIdKey, utterance->src_id()); details->SetBoolean(constants::kIsFinalEventKey, utterance->finished()); scoped_ptr arguments(new base::ListValue()); arguments->Set(0, details.release()); scoped_ptr event( new extensions::Event(events::kOnEvent, arguments.Pass())); event->restrict_to_browser_context = utterance->browser_context(); event->event_url = utterance->src_url(); extensions::EventRouter::Get(utterance->browser_context()) ->DispatchEventToExtension(src_extension_id_, event.Pass()); if (utterance->finished()) delete this; } bool TtsSpeakFunction::RunAsync() { std::string text; EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &text)); if (text.size() > 32768) { error_ = constants::kErrorUtteranceTooLong; return false; } scoped_ptr options(new base::DictionaryValue()); if (args_->GetSize() >= 2) { base::DictionaryValue* temp_options = NULL; if (args_->GetDictionary(1, &temp_options)) options.reset(temp_options->DeepCopy()); } std::string voice_name; if (options->HasKey(constants::kVoiceNameKey)) { EXTENSION_FUNCTION_VALIDATE( options->GetString(constants::kVoiceNameKey, &voice_name)); } std::string lang; if (options->HasKey(constants::kLangKey)) EXTENSION_FUNCTION_VALIDATE(options->GetString(constants::kLangKey, &lang)); if (!lang.empty() && !l10n_util::IsValidLocaleSyntax(lang)) { error_ = constants::kErrorInvalidLang; return false; } std::string gender_str; TtsGenderType gender; if (options->HasKey(constants::kGenderKey)) EXTENSION_FUNCTION_VALIDATE( options->GetString(constants::kGenderKey, &gender_str)); if (gender_str == constants::kGenderMale) { gender = TTS_GENDER_MALE; } else if (gender_str == constants::kGenderFemale) { gender = TTS_GENDER_FEMALE; } else if (gender_str.empty()) { gender = TTS_GENDER_NONE; } else { error_ = constants::kErrorInvalidGender; return false; } double rate = 1.0; if (options->HasKey(constants::kRateKey)) { EXTENSION_FUNCTION_VALIDATE( options->GetDouble(constants::kRateKey, &rate)); if (rate < 0.1 || rate > 10.0) { error_ = constants::kErrorInvalidRate; return false; } } double pitch = 1.0; if (options->HasKey(constants::kPitchKey)) { EXTENSION_FUNCTION_VALIDATE( options->GetDouble(constants::kPitchKey, &pitch)); if (pitch < 0.0 || pitch > 2.0) { error_ = constants::kErrorInvalidPitch; return false; } } double volume = 1.0; if (options->HasKey(constants::kVolumeKey)) { EXTENSION_FUNCTION_VALIDATE( options->GetDouble(constants::kVolumeKey, &volume)); if (volume < 0.0 || volume > 1.0) { error_ = constants::kErrorInvalidVolume; return false; } } bool can_enqueue = false; if (options->HasKey(constants::kEnqueueKey)) { EXTENSION_FUNCTION_VALIDATE( options->GetBoolean(constants::kEnqueueKey, &can_enqueue)); } std::set required_event_types; if (options->HasKey(constants::kRequiredEventTypesKey)) { base::ListValue* list; EXTENSION_FUNCTION_VALIDATE( options->GetList(constants::kRequiredEventTypesKey, &list)); for (size_t i = 0; i < list->GetSize(); ++i) { std::string event_type; if (list->GetString(i, &event_type)) required_event_types.insert(TtsEventTypeFromString(event_type.c_str())); } } std::set desired_event_types; if (options->HasKey(constants::kDesiredEventTypesKey)) { base::ListValue* list; EXTENSION_FUNCTION_VALIDATE( options->GetList(constants::kDesiredEventTypesKey, &list)); for (size_t i = 0; i < list->GetSize(); ++i) { std::string event_type; if (list->GetString(i, &event_type)) desired_event_types.insert(TtsEventTypeFromString(event_type.c_str())); } } std::string voice_extension_id; if (options->HasKey(constants::kExtensionIdKey)) { EXTENSION_FUNCTION_VALIDATE( options->GetString(constants::kExtensionIdKey, &voice_extension_id)); } int src_id = -1; if (options->HasKey(constants::kSrcIdKey)) { EXTENSION_FUNCTION_VALIDATE( options->GetInteger(constants::kSrcIdKey, &src_id)); } // If we got this far, the arguments were all in the valid format, so // send the success response to the callback now - this ensures that // the callback response always arrives before events, which makes // the behavior more predictable and easier to write unit tests for too. SendResponse(true); UtteranceContinuousParameters continuous_params; continuous_params.rate = rate; continuous_params.pitch = pitch; continuous_params.volume = volume; Utterance* utterance = new Utterance(GetProfile()); utterance->set_text(text); utterance->set_voice_name(voice_name); utterance->set_src_id(src_id); utterance->set_src_url(source_url()); utterance->set_lang(lang); utterance->set_gender(gender); utterance->set_continuous_parameters(continuous_params); utterance->set_can_enqueue(can_enqueue); utterance->set_required_event_types(required_event_types); utterance->set_desired_event_types(desired_event_types); utterance->set_extension_id(voice_extension_id); utterance->set_options(options.get()); utterance->set_event_delegate(new TtsExtensionEventHandler(extension_id())); TtsController* controller = TtsController::GetInstance(); controller->SpeakOrEnqueue(utterance); return true; } bool TtsStopSpeakingFunction::RunSync() { TtsController::GetInstance()->Stop(); return true; } bool TtsPauseFunction::RunSync() { TtsController::GetInstance()->Pause(); return true; } bool TtsResumeFunction::RunSync() { TtsController::GetInstance()->Resume(); return true; } bool TtsIsSpeakingFunction::RunSync() { SetResult( new base::FundamentalValue(TtsController::GetInstance()->IsSpeaking())); return true; } bool TtsGetVoicesFunction::RunSync() { std::vector voices; TtsController::GetInstance()->GetVoices(GetProfile(), &voices); scoped_ptr result_voices(new base::ListValue()); for (size_t i = 0; i < voices.size(); ++i) { const VoiceData& voice = voices[i]; base::DictionaryValue* result_voice = new base::DictionaryValue(); result_voice->SetString(constants::kVoiceNameKey, voice.name); result_voice->SetBoolean(constants::kRemoteKey, voice.remote); if (!voice.lang.empty()) result_voice->SetString(constants::kLangKey, voice.lang); if (voice.gender == TTS_GENDER_MALE) result_voice->SetString(constants::kGenderKey, constants::kGenderMale); else if (voice.gender == TTS_GENDER_FEMALE) result_voice->SetString(constants::kGenderKey, constants::kGenderFemale); if (!voice.extension_id.empty()) result_voice->SetString(constants::kExtensionIdKey, voice.extension_id); base::ListValue* event_types = new base::ListValue(); for (std::set::iterator iter = voice.events.begin(); iter != voice.events.end(); ++iter) { const char* event_name_constant = TtsEventTypeToString(*iter); event_types->Append(new base::StringValue(event_name_constant)); } result_voice->Set(constants::kEventTypesKey, event_types); result_voices->Append(result_voice); } SetResult(result_voices.release()); return true; } TtsAPI::TtsAPI(content::BrowserContext* context) { ExtensionFunctionRegistry* registry = ExtensionFunctionRegistry::GetInstance(); registry->RegisterFunction(); registry->RegisterFunction(); registry->RegisterFunction(); registry->RegisterFunction(); registry->RegisterFunction(); registry->RegisterFunction(); registry->RegisterFunction(); // Ensure we're observing newly added engines for the given context. TtsEngineExtensionObserver::GetInstance(Profile::FromBrowserContext(context)); } TtsAPI::~TtsAPI() { } static base::LazyInstance > g_factory = LAZY_INSTANCE_INITIALIZER; BrowserContextKeyedAPIFactory* TtsAPI::GetFactoryInstance() { return g_factory.Pointer(); } } // namespace extensions