diff options
Diffstat (limited to 'chrome')
24 files changed, 411 insertions, 45 deletions
diff --git a/chrome/browser/extensions/extension_function_histogram_value.h b/chrome/browser/extensions/extension_function_histogram_value.h index c80d1ca..c2f4502 100644 --- a/chrome/browser/extensions/extension_function_histogram_value.h +++ b/chrome/browser/extensions/extension_function_histogram_value.h @@ -538,6 +538,8 @@ enum HistogramValue { RUNTIME_SETUNINSTALLURL, INPUTMETHODPRIVATE_STARTIME, MUSICMANAGERPRIVATE_GETDEVICEID, + TTS_PAUSE, + TTS_RESUME, ENUM_BOUNDARY // Last entry: Add new entries above. }; diff --git a/chrome/browser/speech/extension_api/tts_engine_extension_api.cc b/chrome/browser/speech/extension_api/tts_engine_extension_api.cc index 4c9b8d5..fa7f944 100644 --- a/chrome/browser/speech/extension_api/tts_engine_extension_api.cc +++ b/chrome/browser/speech/extension_api/tts_engine_extension_api.cc @@ -9,6 +9,8 @@ #include "base/json/json_writer.h" #include "base/values.h" #include "chrome/browser/extensions/event_router.h" +#include "chrome/browser/extensions/extension_host.h" +#include "chrome/browser/extensions/extension_process_manager.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/profiles/profile.h" @@ -17,21 +19,50 @@ #include "chrome/browser/speech/tts_controller.h" #include "chrome/common/extensions/api/speech/tts_engine_manifest_handler.h" #include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_messages.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/common/console_message_level.h" +using extensions::EventRouter; using extensions::Extension; +using extensions::ExtensionSystem; namespace constants = tts_extension_api_constants; namespace tts_engine_events { const char kOnSpeak[] = "ttsEngine.onSpeak"; const char kOnStop[] = "ttsEngine.onStop"; +const char kOnPause[] = "ttsEngine.onPause"; +const char kOnResume[] = "ttsEngine.onResume"; }; // namespace tts_engine_events +namespace { +void WarnIfMissingPauseOrResumeListener( + Profile* profile, EventRouter* event_router, std::string extension_id) { + bool has_onpause = event_router->ExtensionHasEventListener( + extension_id, tts_engine_events::kOnPause); + bool has_onresume = event_router->ExtensionHasEventListener( + extension_id, tts_engine_events::kOnResume); + if (has_onpause == has_onresume) + return; + + ExtensionProcessManager* process_manager = + ExtensionSystem::Get(profile)->process_manager(); + extensions::ExtensionHost* host = + process_manager->GetBackgroundHostForExtension(extension_id); + host->render_process_host()->Send(new ExtensionMsg_AddMessageToConsole( + host->render_view_host()->GetRoutingID(), + content::CONSOLE_MESSAGE_LEVEL_WARNING, + constants::kErrorMissingPauseOrResume)); +}; +} // anonymous namespace + void GetExtensionVoices(Profile* profile, std::vector<VoiceData>* out_voices) { ExtensionService* service = profile->GetExtensionService(); DCHECK(service); - extensions::EventRouter* event_router = - extensions::ExtensionSystem::Get(profile)->event_router(); + EventRouter* event_router = + ExtensionSystem::Get(profile)->event_router(); DCHECK(event_router); const ExtensionSet* extensions = service->extensions(); @@ -118,7 +149,7 @@ void ExtensionTtsEngineSpeak(Utterance* utterance, const VoiceData& voice) { scoped_ptr<extensions::Event> event(new extensions::Event( tts_engine_events::kOnSpeak, args.Pass())); event->restrict_to_profile = utterance->profile(); - extensions::ExtensionSystem::Get(utterance->profile())->event_router()-> + ExtensionSystem::Get(utterance->profile())->event_router()-> DispatchEventToExtension(utterance->extension_id(), event.Pass()); } @@ -127,10 +158,34 @@ void ExtensionTtsEngineStop(Utterance* utterance) { scoped_ptr<extensions::Event> event(new extensions::Event( tts_engine_events::kOnStop, args.Pass())); event->restrict_to_profile = utterance->profile(); - extensions::ExtensionSystem::Get(utterance->profile())->event_router()-> + ExtensionSystem::Get(utterance->profile())->event_router()-> DispatchEventToExtension(utterance->extension_id(), event.Pass()); } +void ExtensionTtsEnginePause(Utterance* utterance) { + scoped_ptr<ListValue> args(new ListValue()); + scoped_ptr<extensions::Event> event(new extensions::Event( + tts_engine_events::kOnPause, args.Pass())); + Profile* profile = utterance->profile(); + event->restrict_to_profile = profile; + EventRouter* event_router = ExtensionSystem::Get(profile)->event_router(); + std::string id = utterance->extension_id(); + event_router->DispatchEventToExtension(id, event.Pass()); + WarnIfMissingPauseOrResumeListener(profile, event_router, id); +} + +void ExtensionTtsEngineResume(Utterance* utterance) { + scoped_ptr<ListValue> args(new ListValue()); + scoped_ptr<extensions::Event> event(new extensions::Event( + tts_engine_events::kOnResume, args.Pass())); + Profile* profile = utterance->profile(); + event->restrict_to_profile = profile; + EventRouter* event_router = ExtensionSystem::Get(profile)->event_router(); + std::string id = utterance->extension_id(); + event_router->DispatchEventToExtension(id, event.Pass()); + WarnIfMissingPauseOrResumeListener(profile, event_router, id); +} + bool ExtensionTtsEngineSendTtsEventFunction::RunImpl() { int utterance_id; std::string error_message; @@ -192,6 +247,12 @@ bool ExtensionTtsEngineSendTtsEventFunction::RunImpl() { event->GetString(constants::kErrorMessageKey, &error_message); controller->OnTtsEvent( utterance_id, TTS_EVENT_ERROR, char_index, error_message); + } else if (event_type == constants::kEventTypePause) { + controller->OnTtsEvent( + utterance_id, TTS_EVENT_PAUSE, char_index, std::string()); + } else if (event_type == constants::kEventTypeResume) { + controller->OnTtsEvent( + utterance_id, TTS_EVENT_RESUME, char_index, std::string()); } else { EXTENSION_FUNCTION_VALIDATE(false); } diff --git a/chrome/browser/speech/extension_api/tts_engine_extension_api.h b/chrome/browser/speech/extension_api/tts_engine_extension_api.h index b87de3a..b77f4d9 100644 --- a/chrome/browser/speech/extension_api/tts_engine_extension_api.h +++ b/chrome/browser/speech/extension_api/tts_engine_extension_api.h @@ -24,6 +24,8 @@ class Extension; namespace tts_engine_events { extern const char kOnSpeak[]; extern const char kOnStop[]; +extern const char kOnPause[]; +extern const char kOnResume[]; } // Return a list of all available voices registered by extensions. @@ -47,6 +49,12 @@ void ExtensionTtsEngineSpeak(Utterance* utterance, // associated with this utterance. void ExtensionTtsEngineStop(Utterance* utterance); +// Pause in the middle of speaking this utterance. +void ExtensionTtsEnginePause(Utterance* utterance); + +// Resume speaking this utterance. +void ExtensionTtsEngineResume(Utterance* utterance); + // Hidden/internal extension function used to allow TTS engine extensions // to send events back to the client that's calling tts.speak(). class ExtensionTtsEngineSendTtsEventFunction : public SyncExtensionFunction { diff --git a/chrome/browser/speech/extension_api/tts_extension_api.cc b/chrome/browser/speech/extension_api/tts_extension_api.cc index 117bf732..4137421 100644 --- a/chrome/browser/speech/extension_api/tts_extension_api.cc +++ b/chrome/browser/speech/extension_api/tts_extension_api.cc @@ -40,6 +40,10 @@ const char *TtsEventTypeToString(TtsEventType event_type) { 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; @@ -63,6 +67,10 @@ TtsEventType TtsEventTypeFromString(const std::string& str) { 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; @@ -274,6 +282,16 @@ bool TtsStopSpeakingFunction::RunImpl() { return true; } +bool TtsPauseFunction::RunImpl() { + TtsController::GetInstance()->Pause(); + return true; +} + +bool TtsResumeFunction::RunImpl() { + TtsController::GetInstance()->Resume(); + return true; +} + bool TtsIsSpeakingFunction::RunImpl() { SetResult(Value::CreateBooleanValue( TtsController::GetInstance()->IsSpeaking())); @@ -301,35 +319,8 @@ bool TtsGetVoicesFunction::RunImpl() { ListValue* event_types = new ListValue(); for (std::set<TtsEventType>::iterator iter = voice.events.begin(); iter != voice.events.end(); ++iter) { - const char* event_name_constant = NULL; - switch (*iter) { - case TTS_EVENT_START: - event_name_constant = constants::kEventTypeStart; - break; - case TTS_EVENT_END: - event_name_constant = constants::kEventTypeEnd; - break; - case TTS_EVENT_WORD: - event_name_constant = constants::kEventTypeWord; - break; - case TTS_EVENT_SENTENCE: - event_name_constant = constants::kEventTypeSentence; - break; - case TTS_EVENT_MARKER: - event_name_constant = constants::kEventTypeMarker; - break; - case TTS_EVENT_INTERRUPTED: - event_name_constant = constants::kEventTypeInterrupted; - break; - case TTS_EVENT_CANCELLED: - event_name_constant = constants::kEventTypeCancelled; - break; - case TTS_EVENT_ERROR: - event_name_constant = constants::kEventTypeError; - break; - } - if (event_name_constant) - event_types->Append(Value::CreateStringValue(event_name_constant)); + const char* event_name_constant = TtsEventTypeToString(*iter); + event_types->Append(Value::CreateStringValue(event_name_constant)); } result_voice->Set(constants::kEventTypesKey, event_types); @@ -353,6 +344,8 @@ TtsAPI::TtsAPI(Profile* profile) { registry->RegisterFunction<TtsIsSpeakingFunction>(); registry->RegisterFunction<TtsSpeakFunction>(); registry->RegisterFunction<TtsStopSpeakingFunction>(); + registry->RegisterFunction<TtsPauseFunction>(); + registry->RegisterFunction<TtsResumeFunction>(); } TtsAPI::~TtsAPI() { diff --git a/chrome/browser/speech/extension_api/tts_extension_api.h b/chrome/browser/speech/extension_api/tts_extension_api.h index a70d95e..e2ed95c 100644 --- a/chrome/browser/speech/extension_api/tts_extension_api.h +++ b/chrome/browser/speech/extension_api/tts_extension_api.h @@ -33,6 +33,20 @@ class TtsStopSpeakingFunction : public SyncExtensionFunction { DECLARE_EXTENSION_FUNCTION("tts.stop", TTS_STOP) }; +class TtsPauseFunction : public SyncExtensionFunction { + private: + virtual ~TtsPauseFunction() {} + virtual bool RunImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION("tts.pause", TTS_PAUSE) +}; + +class TtsResumeFunction : public SyncExtensionFunction { + private: + virtual ~TtsResumeFunction() {} + virtual bool RunImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION("tts.resume", TTS_RESUME) +}; + class TtsIsSpeakingFunction : public SyncExtensionFunction { private: virtual ~TtsIsSpeakingFunction() {} diff --git a/chrome/browser/speech/extension_api/tts_extension_api_constants.cc b/chrome/browser/speech/extension_api/tts_extension_api_constants.cc index 3568a8d..7b8c45b 100644 --- a/chrome/browser/speech/extension_api/tts_extension_api_constants.cc +++ b/chrome/browser/speech/extension_api/tts_extension_api_constants.cc @@ -35,6 +35,8 @@ const char kEventTypeMarker[] = "marker"; const char kEventTypeInterrupted[] = "interrupted"; const char kEventTypeCancelled[] = "cancelled"; const char kEventTypeError[] = "error"; +const char kEventTypePause[] = "pause"; +const char kEventTypeResume[] = "resume"; const char kErrorUndeclaredEventType[] = "Cannot send an event type that is not declared in the extension manifest."; @@ -44,5 +46,8 @@ const char kErrorInvalidGender[] = "Invalid gender."; const char kErrorInvalidRate[] = "Invalid rate."; const char kErrorInvalidPitch[] = "Invalid pitch."; const char kErrorInvalidVolume[] = "Invalid volume."; +const char kErrorMissingPauseOrResume[] = + "A TTS engine extension should either listen for both onPause and onResume " + "events, or neither."; } // namespace tts_extension_api_constants. diff --git a/chrome/browser/speech/extension_api/tts_extension_api_constants.h b/chrome/browser/speech/extension_api/tts_extension_api_constants.h index 3270e97..0809473 100644 --- a/chrome/browser/speech/extension_api/tts_extension_api_constants.h +++ b/chrome/browser/speech/extension_api/tts_extension_api_constants.h @@ -40,6 +40,8 @@ extern const char kEventTypeMarker[]; extern const char kEventTypeInterrupted[]; extern const char kEventTypeCancelled[]; extern const char kEventTypeError[]; +extern const char kEventTypePause[]; +extern const char kEventTypeResume[]; extern const char kErrorUndeclaredEventType[]; extern const char kErrorUtteranceTooLong[]; @@ -48,6 +50,7 @@ extern const char kErrorInvalidGender[]; extern const char kErrorInvalidRate[]; extern const char kErrorInvalidPitch[]; extern const char kErrorInvalidVolume[]; +extern const char kErrorMissingPauseOrResume[]; } // namespace tts_extension_api_constants. #endif // CHROME_BROWSER_SPEECH_EXTENSION_API_TTS_EXTENSION_API_CONSTANTS_H_ diff --git a/chrome/browser/speech/extension_api/tts_extension_apitest.cc b/chrome/browser/speech/extension_api/tts_extension_apitest.cc index dc4dc90..fcf86b1 100644 --- a/chrome/browser/speech/extension_api/tts_extension_apitest.cc +++ b/chrome/browser/speech/extension_api/tts_extension_apitest.cc @@ -24,9 +24,14 @@ using ::testing::DoAll; using ::testing::InSequence; using ::testing::InvokeWithoutArgs; using ::testing::Return; +using ::testing::SaveArg; using ::testing::StrictMock; using ::testing::_; +namespace { +int g_saved_utterance_id; +} + class MockTtsPlatformImpl : public TtsPlatformImpl { public: MockTtsPlatformImpl() @@ -45,6 +50,10 @@ class MockTtsPlatformImpl : public TtsPlatformImpl { MOCK_METHOD0(StopSpeaking, bool(void)); + MOCK_METHOD0(Pause, void(void)); + + MOCK_METHOD0(Resume, void(void)); + MOCK_METHOD0(IsSpeaking, bool(void)); MOCK_METHOD1(GetVoices, void(std::vector<VoiceData>*)); @@ -53,6 +62,15 @@ class MockTtsPlatformImpl : public TtsPlatformImpl { set_error("epic fail"); } + void SendEndEventOnSavedUtteranceId() { + MessageLoop::current()->PostDelayedTask( + FROM_HERE, base::Bind( + &MockTtsPlatformImpl::SendEvent, + ptr_factory_.GetWeakPtr(), + false, g_saved_utterance_id, TTS_EVENT_END, 0, std::string()), + base::TimeDelta()); + } + void SendEndEvent(int utterance_id, const std::string& utterance, const std::string& lang, @@ -276,6 +294,35 @@ IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformWordCallbacks) { ASSERT_TRUE(RunExtensionTest("tts/word_callbacks")) << message_; } +IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformPauseResume) { + EXPECT_CALL(mock_platform_impl_, IsSpeaking()) + .Times(AnyNumber()); + + InSequence s; + EXPECT_CALL(mock_platform_impl_, Speak(_, "test 1", _, _, _)) + .WillOnce(DoAll( + Invoke(&mock_platform_impl_, + &MockTtsPlatformImpl::SendEndEvent), + Return(true))); + EXPECT_CALL(mock_platform_impl_, StopSpeaking()) + .WillOnce(Return(true)); + EXPECT_CALL(mock_platform_impl_, Speak(_, "test 2", _, _, _)) + .WillOnce(DoAll( + SaveArg<0>(&g_saved_utterance_id), + Return(true))); + EXPECT_CALL(mock_platform_impl_, Pause()); + EXPECT_CALL(mock_platform_impl_, Resume()) + .WillOnce( + InvokeWithoutArgs( + &mock_platform_impl_, + &MockTtsPlatformImpl::SendEndEventOnSavedUtteranceId)); + ASSERT_TRUE(RunExtensionTest("tts/pause_resume")) << message_; +} + +// +// TTS Engine tests. +// + IN_PROC_BROWSER_TEST_F(TtsApiTest, RegisterEngine) { EXPECT_CALL(mock_platform_impl_, IsSpeaking()) .Times(AnyNumber()); diff --git a/chrome/browser/speech/tts_android.cc b/chrome/browser/speech/tts_android.cc index 8c7105c..36e6e22 100644 --- a/chrome/browser/speech/tts_android.cc +++ b/chrome/browser/speech/tts_android.cc @@ -69,6 +69,13 @@ bool TtsPlatformImplAndroid::StopSpeaking() { return true; } +void TtsPlatformImplAndroid::Pause() { + StopSpeaking(); +} + +void TtsPlatformImplAndroid::Resume() { +} + bool TtsPlatformImplAndroid::IsSpeaking() { return (utterance_id_ != 0); } diff --git a/chrome/browser/speech/tts_android.h b/chrome/browser/speech/tts_android.h index 6417f732..cd3830a 100644 --- a/chrome/browser/speech/tts_android.h +++ b/chrome/browser/speech/tts_android.h @@ -20,6 +20,8 @@ class TtsPlatformImplAndroid : public TtsPlatformImpl { const VoiceData& voice, const UtteranceContinuousParameters& params) OVERRIDE; virtual bool StopSpeaking() OVERRIDE; + virtual void Pause() OVERRIDE; + virtual void Resume() OVERRIDE; virtual bool IsSpeaking() OVERRIDE; virtual void GetVoices(std::vector<VoiceData>* out_voices) OVERRIDE; diff --git a/chrome/browser/speech/tts_chromeos.cc b/chrome/browser/speech/tts_chromeos.cc index 57d3387..e42cb0c 100644 --- a/chrome/browser/speech/tts_chromeos.cc +++ b/chrome/browser/speech/tts_chromeos.cc @@ -34,6 +34,10 @@ class TtsPlatformImplChromeOs return false; } + virtual void Pause() OVERRIDE {} + + virtual void Resume() OVERRIDE {} + virtual bool IsSpeaking() OVERRIDE { return false; } diff --git a/chrome/browser/speech/tts_controller.cc b/chrome/browser/speech/tts_controller.cc index 73b094e..2ae1678d 100644 --- a/chrome/browser/speech/tts_controller.cc +++ b/chrome/browser/speech/tts_controller.cc @@ -117,6 +117,7 @@ TtsController* TtsController::GetInstance() { TtsController::TtsController() : current_utterance_(NULL), + paused_(false), platform_impl_(NULL) { } @@ -131,7 +132,15 @@ TtsController::~TtsController() { } void TtsController::SpeakOrEnqueue(Utterance* utterance) { - if (IsSpeaking() && utterance->can_enqueue()) { + // If we're paused and we get an utterance that can't be queued, + // flush the queue but stay in the paused state. + if (paused_ && !utterance->can_enqueue()) { + Stop(); + paused_ = true; + return; + } + + if (paused_ || (IsSpeaking() && utterance->can_enqueue())) { utterance_queue_.push(utterance); } else { Stop(); @@ -198,6 +207,7 @@ void TtsController::SpeakNow(Utterance* utterance) { } void TtsController::Stop() { + paused_ = false; if (current_utterance_ && !current_utterance_->extension_id().empty()) { #if !defined(OS_ANDROID) ExtensionTtsEngineStop(current_utterance_); @@ -214,6 +224,32 @@ void TtsController::Stop() { ClearUtteranceQueue(true); // Send events. } +void TtsController::Pause() { + paused_ = true; + if (current_utterance_ && !current_utterance_->extension_id().empty()) { +#if !defined(OS_ANDROID) + ExtensionTtsEnginePause(current_utterance_); +#endif + } else if (current_utterance_) { + GetPlatformImpl()->clear_error(); + GetPlatformImpl()->Pause(); + } +} + +void TtsController::Resume() { + paused_ = false; + if (current_utterance_ && !current_utterance_->extension_id().empty()) { +#if !defined(OS_ANDROID) + ExtensionTtsEngineResume(current_utterance_); +#endif + } else if (current_utterance_) { + GetPlatformImpl()->clear_error(); + GetPlatformImpl()->Resume(); + } else { + SpeakNextUtterance(); + } +} + void TtsController::OnTtsEvent(int utterance_id, TtsEventType event_type, int char_index, @@ -259,6 +295,9 @@ void TtsController::FinishCurrentUtterance() { } void TtsController::SpeakNextUtterance() { + if (paused_) + return; + // Start speaking the next utterance in the queue. Keep trying in case // one fails but there are still more in the queue to try. while (!utterance_queue_.empty() && !current_utterance_) { diff --git a/chrome/browser/speech/tts_controller.h b/chrome/browser/speech/tts_controller.h index b6b7372..b55600a 100644 --- a/chrome/browser/speech/tts_controller.h +++ b/chrome/browser/speech/tts_controller.h @@ -31,7 +31,9 @@ enum TtsEventType { TTS_EVENT_MARKER, TTS_EVENT_INTERRUPTED, TTS_EVENT_CANCELLED, - TTS_EVENT_ERROR + TTS_EVENT_ERROR, + TTS_EVENT_PAUSE, + TTS_EVENT_RESUME }; enum TtsGenderType { @@ -251,9 +253,17 @@ class TtsController { // immediately. void SpeakOrEnqueue(Utterance* utterance); - // Stop all utterances and flush the queue. + // Stop all utterances and flush the queue. Implies leaving pause mode + // as well. void Stop(); + // Pause the speech queue. Some engines may support pausing in the middle + // of an utterance. + void Pause(); + + // Resume speaking. + void Resume(); + // Handle events received from the speech engine. Events are forwarded to // the callback function, and in addition, completion and error events // trigger finishing the current utterance and starting the next one, if @@ -317,6 +327,9 @@ class TtsController { // The current utterance being spoken. Utterance* current_utterance_; + // Whether the queue is paused or not. + bool paused_; + // A queue of utterances to speak after the current one finishes. std::queue<Utterance*> utterance_queue_; diff --git a/chrome/browser/speech/tts_controller_unittest.cc b/chrome/browser/speech/tts_controller_unittest.cc index 16e912d..9b77830 100644 --- a/chrome/browser/speech/tts_controller_unittest.cc +++ b/chrome/browser/speech/tts_controller_unittest.cc @@ -28,6 +28,8 @@ class DummyTtsPlatformImpl : public TtsPlatformImpl { } virtual bool IsSpeaking() OVERRIDE { return false; } virtual bool StopSpeaking() OVERRIDE { return true; } + virtual void Pause() OVERRIDE {} + virtual void Resume() OVERRIDE {} virtual void GetVoices(std::vector<VoiceData>* out_voices) OVERRIDE {} virtual std::string error() OVERRIDE { return std::string(); } virtual void clear_error() OVERRIDE {} diff --git a/chrome/browser/speech/tts_linux.cc b/chrome/browser/speech/tts_linux.cc index 83e1e27..0a61373 100644 --- a/chrome/browser/speech/tts_linux.cc +++ b/chrome/browser/speech/tts_linux.cc @@ -38,6 +38,8 @@ class TtsPlatformImplLinux : public TtsPlatformImpl { const VoiceData& voice, const UtteranceContinuousParameters& params) OVERRIDE; virtual bool StopSpeaking() OVERRIDE; + virtual void Pause() OVERRIDE; + virtual void Resume() OVERRIDE; virtual bool IsSpeaking() OVERRIDE; virtual void GetVoices(std::vector<VoiceData>* out_voices) OVERRIDE; @@ -198,6 +200,18 @@ bool TtsPlatformImplLinux::StopSpeaking() { return true; } +void TtsPlatformImplLinux::Pause() { + if (!PlatformImplAvailable()) + return; + libspeechd_loader_.spd_pause(conn_); +} + +void TtsPlatformImplLinux::Resume() { + if (!PlatformImplAvailable()) + return; + libspeechd_loader_.spd_resume(conn_); +} + bool TtsPlatformImplLinux::IsSpeaking() { return current_notification_ == SPD_EVENT_BEGIN; } @@ -247,6 +261,8 @@ void TtsPlatformImplLinux::GetVoices( voice.events.insert(TTS_EVENT_END); voice.events.insert(TTS_EVENT_CANCELLED); voice.events.insert(TTS_EVENT_MARKER); + voice.events.insert(TTS_EVENT_PAUSE); + voice.events.insert(TTS_EVENT_RESUME); } } @@ -254,14 +270,19 @@ void TtsPlatformImplLinux::OnSpeechEvent(SPDNotificationType type) { TtsController* controller = TtsController::GetInstance(); switch (type) { case SPD_EVENT_BEGIN: - case SPD_EVENT_RESUME: controller->OnTtsEvent(utterance_id_, TTS_EVENT_START, 0, std::string()); break; + case SPD_EVENT_RESUME: + controller->OnTtsEvent(utterance_id_, TTS_EVENT_RESUME, 0, std::string()); + break; case SPD_EVENT_END: - case SPD_EVENT_PAUSE: controller->OnTtsEvent( utterance_id_, TTS_EVENT_END, utterance_.size(), std::string()); break; + case SPD_EVENT_PAUSE: + controller->OnTtsEvent( + utterance_id_, TTS_EVENT_PAUSE, utterance_.size(), std::string()); + break; case SPD_EVENT_CANCEL: controller->OnTtsEvent( utterance_id_, TTS_EVENT_CANCELLED, 0, std::string()); diff --git a/chrome/browser/speech/tts_mac.mm b/chrome/browser/speech/tts_mac.mm index 689effc..bdb9b1c 100644 --- a/chrome/browser/speech/tts_mac.mm +++ b/chrome/browser/speech/tts_mac.mm @@ -63,6 +63,10 @@ class TtsPlatformImplMac : public TtsPlatformImpl { virtual bool StopSpeaking() OVERRIDE; + virtual void Pause() OVERRIDE; + + virtual void Resume() OVERRIDE; + virtual bool IsSpeaking() OVERRIDE; virtual void GetVoices(std::vector<VoiceData>* out_voices) OVERRIDE; @@ -86,6 +90,8 @@ class TtsPlatformImplMac : public TtsPlatformImpl { int utterance_id_; std::string utterance_; bool sent_start_event_; + int last_char_index_; + bool paused_; friend struct DefaultSingletonTraits<TtsPlatformImplMac>; @@ -105,6 +111,7 @@ bool TtsPlatformImplMac::Speak( const UtteranceContinuousParameters& params) { // TODO: convert SSML to SAPI xml. http://crbug.com/88072 utterance_ = utterance; + paused_ = false; NSString* utterance_nsstring = [NSString stringWithUTF8String:utterance_.c_str()]; @@ -165,9 +172,28 @@ bool TtsPlatformImplMac::StopSpeaking() { [speech_synthesizer_ stopSpeaking]; speech_synthesizer_.reset(nil); } + paused_ = false; return true; } +void TtsPlatformImplMac::Pause() { + if (speech_synthesizer_.get() && utterance_id_ && !paused_) { + [speech_synthesizer_ pauseSpeakingAtBoundary:NSSpeechImmediateBoundary]; + paused_ = true; + TtsController::GetInstance()->OnTtsEvent( + utterance_id_, TTS_EVENT_PAUSE, last_char_index_, ""); + } +} + +void TtsPlatformImplMac::Resume() { + if (speech_synthesizer_.get() && utterance_id_ && paused_) { + [speech_synthesizer_ continueSpeaking]; + paused_ = false; + TtsController::GetInstance()->OnTtsEvent( + utterance_id_, TTS_EVENT_RESUME, last_char_index_, ""); + } +} + bool TtsPlatformImplMac::IsSpeaking() { return [NSSpeechSynthesizer isAnyApplicationSpeaking]; } @@ -223,6 +249,8 @@ void TtsPlatformImplMac::GetVoices(std::vector<VoiceData>* outVoices) { data.events.insert(TTS_EVENT_ERROR); data.events.insert(TTS_EVENT_CANCELLED); data.events.insert(TTS_EVENT_INTERRUPTED); + data.events.insert(TTS_EVENT_PAUSE); + data.events.insert(TTS_EVENT_RESUME); } } @@ -247,11 +275,13 @@ void TtsPlatformImplMac::OnSpeechEvent( } controller->OnTtsEvent( utterance_id_, event_type, char_index, error_message); + last_char_index_ = char_index; } TtsPlatformImplMac::TtsPlatformImplMac() { utterance_id_ = -1; sent_start_event_ = true; + paused_ = false; delegate_.reset([[ChromeTtsDelegate alloc] initWithPlatformImplMac:this]); } diff --git a/chrome/browser/speech/tts_message_filter.cc b/chrome/browser/speech/tts_message_filter.cc index c7dec7a..6b94b8c 100644 --- a/chrome/browser/speech/tts_message_filter.cc +++ b/chrome/browser/speech/tts_message_filter.cc @@ -89,11 +89,11 @@ void TtsMessageFilter::OnSpeak(const TtsUtteranceRequest& request) { } void TtsMessageFilter::OnPause() { - // TODO(dmazzoni): Not supported by TtsController yet. + TtsController::GetInstance()->Pause(); } void TtsMessageFilter::OnResume() { - // TODO(dmazzoni): Not supported by TtsController yet. + TtsController::GetInstance()->Resume(); } void TtsMessageFilter::OnCancel() { @@ -130,6 +130,12 @@ void TtsMessageFilter::OnTtsEvent(Utterance* utterance, Send(new TtsMsg_SpeakingErrorOccurred( utterance->src_id(), error_message)); break; + case TTS_EVENT_PAUSE: + Send(new TtsMsg_DidPauseSpeaking(utterance->src_id())); + break; + case TTS_EVENT_RESUME: + Send(new TtsMsg_DidResumeSpeaking(utterance->src_id())); + break; } } diff --git a/chrome/browser/speech/tts_platform.h b/chrome/browser/speech/tts_platform.h index c6b5706..4b57fd4 100644 --- a/chrome/browser/speech/tts_platform.h +++ b/chrome/browser/speech/tts_platform.h @@ -49,6 +49,13 @@ class TtsPlatformImpl { // to |out_voices|. virtual void GetVoices(std::vector<VoiceData>* out_voices) = 0; + // Pause the current utterance, if any, until a call to Resume, + // Speak, or StopSpeaking. + virtual void Pause() = 0; + + // Resume speaking the current utterance, if it was paused. + virtual void Resume() = 0; + virtual std::string error(); virtual void clear_error(); virtual void set_error(const std::string& error); diff --git a/chrome/browser/speech/tts_win.cc b/chrome/browser/speech/tts_win.cc index 06a16f5..bfa2874 100644 --- a/chrome/browser/speech/tts_win.cc +++ b/chrome/browser/speech/tts_win.cc @@ -28,6 +28,10 @@ class TtsPlatformImplWin : public TtsPlatformImpl { virtual bool StopSpeaking(); + virtual void Pause(); + + virtual void Resume(); + virtual bool IsSpeaking(); virtual void GetVoices(std::vector<VoiceData>* out_voices) OVERRIDE; @@ -51,6 +55,7 @@ class TtsPlatformImplWin : public TtsPlatformImpl { int prefix_len_; ULONG stream_number_; int char_position_; + bool paused_; friend struct DefaultSingletonTraits<TtsPlatformImplWin>; @@ -125,10 +130,32 @@ bool TtsPlatformImplWin::StopSpeaking() { // Stop speech by speaking the empty string with the purge flag. speech_synthesizer_->Speak(L"", SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL); } + if (paused_) { + speech_synthesizer_->Resume(); + paused_ = false; + } } return true; } +void TtsPlatformImplWin::Pause() { + if (speech_synthesizer_.get() && utterance_id_ && !paused_) { + speech_synthesizer_->Pause(); + paused_ = true; + TtsController::GetInstance()->OnTtsEvent( + utterance_id_, TTS_EVENT_PAUSE, char_position_, ""); + } +} + +void TtsPlatformImplWin::Resume() { + if (speech_synthesizer_.get() && utterance_id_ && paused_) { + speech_synthesizer_->Resume(); + paused_ = false; + TtsController::GetInstance()->OnTtsEvent( + utterance_id_, TTS_EVENT_RESUME, char_position_, ""); + } +} + bool TtsPlatformImplWin::IsSpeaking() { if (speech_synthesizer_.get()) { SPVOICESTATUS status; @@ -156,6 +183,8 @@ void TtsPlatformImplWin::GetVoices( voice.events.insert(TTS_EVENT_MARKER); voice.events.insert(TTS_EVENT_WORD); voice.events.insert(TTS_EVENT_SENTENCE); + voice.events.insert(TTS_EVENT_PAUSE); + voice.events.insert(TTS_EVENT_RESUME); } void TtsPlatformImplWin::OnSpeechEvent() { @@ -199,7 +228,8 @@ TtsPlatformImplWin::TtsPlatformImplWin() : utterance_id_(0), prefix_len_(0), stream_number_(0), - char_position_(0) { + char_position_(0), + paused_(false) { speech_synthesizer_.CreateInstance(CLSID_SpVoice); if (speech_synthesizer_.get()) { ULONGLONG event_mask = diff --git a/chrome/common/extensions/api/tts.json b/chrome/common/extensions/api/tts.json index 3b2abf2..49bd4b2 100644 --- a/chrome/common/extensions/api/tts.json +++ b/chrome/common/extensions/api/tts.json @@ -13,8 +13,8 @@ "properties": { "type": { "type": "string", - "enum": ["start", "end", "word", "sentence", "marker", "interrupted", "cancelled", "error"], - "description": "The type can be 'start' as soon as speech has started, 'word' when a word boundary is reached, 'sentence' when a sentence boundary is reached, 'marker' when an SSML mark element is reached, 'end' when the end of the utterance is reached, 'interrupted' when the utterance is stopped or interrupted before reaching the end, 'cancelled' when it's removed from the queue before ever being synthesized, or 'error' when any other error occurs." + "enum": ["start", "end", "word", "sentence", "marker", "interrupted", "cancelled", "error", "pause", "resume"], + "description": "The type can be 'start' as soon as speech has started, 'word' when a word boundary is reached, 'sentence' when a sentence boundary is reached, 'marker' when an SSML mark element is reached, 'end' when the end of the utterance is reached, 'interrupted' when the utterance is stopped or interrupted before reaching the end, 'cancelled' when it's removed from the queue before ever being synthesized, or 'error' when any other error occurs. When pausing speech, a 'pause' event is fired if a particular utterance is paused in the middle, and 'resume' if an utterance resumes speech. Note that pause and resume events may not fire if speech is paused in-between utterances." }, "charIndex": { "type": "number", @@ -177,7 +177,19 @@ { "name": "stop", "type": "function", - "description": "Stops any current speech.", + "description": "Stops any current speech and flushes the queue of any pending utterances. In addition, if speech was paused, it will now be un-paused for the next call to speak.", + "parameters": [] + }, + { + "name": "pause", + "type": "function", + "description": "Pauses speech synthesis, potentially in the middle of an utterance. A call to resume or stop will un-pause speech.", + "parameters": [] + }, + { + "name": "resume", + "type": "function", + "description": "If speech was paused, resumes speaking where it left off.", "parameters": [] }, { diff --git a/chrome/common/extensions/api/tts_engine.json b/chrome/common/extensions/api/tts_engine.json index 30b33fc..4c6bb26 100644 --- a/chrome/common/extensions/api/tts_engine.json +++ b/chrome/common/extensions/api/tts_engine.json @@ -96,7 +96,17 @@ { "name": "onStop", "type": "function", - "description": "Fired when a call is made to tts.stop and this extension may be in the middle of speaking. If an extension receives a call to onStop and speech is already stopped, it should do nothing (not raise an error)." + "description": "Fired when a call is made to tts.stop and this extension may be in the middle of speaking. If an extension receives a call to onStop and speech is already stopped, it should do nothing (not raise an error). If speech is in the paused state, this should cancel the paused state." + }, + { + "name": "onPause", + "type": "function", + "description": "Optional: if an engine supports the pause event, it should pause the current utterance being spoken, if any, until it receives a resume event or stop event. Note that a stop event should also clear the paused state." + }, + { + "name": "onResume", + "type": "function", + "description": "Optional: if an engine supports the pause event, it should also support the resume event, to continue speaking the current utterance, if any. Note that a stop event should also clear the paused state." } ] } diff --git a/chrome/test/data/extensions/api_test/tts/pause_resume/manifest.json b/chrome/test/data/extensions/api_test/tts/pause_resume/manifest.json new file mode 100644 index 0000000..ec7efb3 --- /dev/null +++ b/chrome/test/data/extensions/api_test/tts/pause_resume/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "chrome.tts", + "version": "0.1", + "manifest_version": 2, + "description": "browser test for chrome.tts API", + "background": { + "page": "test.html" + }, + "permissions": ["tts"] +} diff --git a/chrome/test/data/extensions/api_test/tts/pause_resume/test.html b/chrome/test/data/extensions/api_test/tts/pause_resume/test.html new file mode 100644 index 0000000..5000771 --- /dev/null +++ b/chrome/test/data/extensions/api_test/tts/pause_resume/test.html @@ -0,0 +1,6 @@ +<!-- + * Copyright (c) 2013 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. +--> +<script src="test.js"></script> diff --git a/chrome/test/data/extensions/api_test/tts/pause_resume/test.js b/chrome/test/data/extensions/api_test/tts/pause_resume/test.js new file mode 100644 index 0000000..89fd4b1 --- /dev/null +++ b/chrome/test/data/extensions/api_test/tts/pause_resume/test.js @@ -0,0 +1,34 @@ +// Copyright 2013 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. + +// TTS api test +// browser_tests.exe --gtest_filter="TtsApiTest.*" + +chrome.test.runTests([ + function testPauseBeforeSpeak() { + chrome.tts.pause(); + chrome.tts.speak( + 'test 1', + { + 'enqueue': true, + 'onEvent': function(event) { + if (event.type == 'end') + chrome.test.succeed(); + } + }); + chrome.tts.resume(); + }, + function testPauseDuringSpeak() { + chrome.tts.speak( + 'test 2', + { + 'onEvent': function(event) { + if (event.type == 'end') + chrome.test.succeed(); + } + }); + chrome.tts.pause(); + chrome.tts.resume(); + } +]); |