Commit 733cb400 authored by David Tseng's avatar David Tseng Committed by Commit Bot

Enqueue tts utterances when built in tts engines have yet to initialize

This change builds on top of the logic to enqueue utterances for tts
platforms (which are interfaces to native tts api for that platform)
while initializing/loading. It adds the same treatment for
TtsEngineDelegate, which is responsible for built-in tts engines shipped
as extensions for each platform.

This starts off for built in engines on Chrome OS, but can encompass the
network tts engine we ship for other platforms. The new behavior will be
to enqueue until the built-in engine(s) are ready; readiness is signaled
by detecting at least one voice for the built-in engines
(Googletts/Espeak). Note it can be uninitialized in this scheme.

R=dmazzoni@chromium.org

Change-Id: Ie681e5830909245b566969b491043225c0640be1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2552369
Commit-Queue: David Tseng <dtseng@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#829933}
parent aae491e9
...@@ -34,6 +34,10 @@ ...@@ -34,6 +34,10 @@
#include "third_party/blink/public/mojom/devtools/console_message.mojom.h" #include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
#if defined(OS_CHROMEOS)
#include "chrome/common/extensions/extension_constants.h"
#endif
using extensions::EventRouter; using extensions::EventRouter;
using extensions::Extension; using extensions::Extension;
using extensions::ExtensionSystem; using extensions::ExtensionSystem;
...@@ -345,6 +349,29 @@ bool TtsExtensionEngine::LoadBuiltInTtsEngine( ...@@ -345,6 +349,29 @@ bool TtsExtensionEngine::LoadBuiltInTtsEngine(
#endif #endif
} }
bool TtsExtensionEngine::IsBuiltInTtsEngineInitialized(
content::BrowserContext* browser_context) {
if (!browser_context || disable_built_in_tts_engine_for_testing_)
return true;
#if defined(OS_CHROMEOS)
std::vector<content::VoiceData> voices;
GetVoices(browser_context, &voices);
bool saw_google_tts = false;
bool saw_espeak = false;
for (const auto& voice : voices) {
saw_google_tts |=
voice.engine_id == extension_misc::kGoogleSpeechSynthesisExtensionId;
saw_espeak |=
voice.engine_id == extension_misc::kEspeakSpeechSynthesisExtensionId;
}
return saw_google_tts && saw_espeak;
#else
// Vacuously; no built in engines on other platforms yet. TODO: network tts?
return true;
#endif
}
ExtensionFunction::ResponseAction ExtensionFunction::ResponseAction
ExtensionTtsEngineUpdateVoicesFunction::Run() { ExtensionTtsEngineUpdateVoicesFunction::Run() {
base::ListValue* voices_data = nullptr; base::ListValue* voices_data = nullptr;
......
...@@ -36,6 +36,8 @@ class TtsExtensionEngine : public content::TtsEngineDelegate { ...@@ -36,6 +36,8 @@ class TtsExtensionEngine : public content::TtsEngineDelegate {
void Pause(content::TtsUtterance* utterance) override; void Pause(content::TtsUtterance* utterance) override;
void Resume(content::TtsUtterance* utterance) override; void Resume(content::TtsUtterance* utterance) override;
bool LoadBuiltInTtsEngine(content::BrowserContext* browser_context) override; bool LoadBuiltInTtsEngine(content::BrowserContext* browser_context) override;
bool IsBuiltInTtsEngineInitialized(
content::BrowserContext* browser_context) override;
void DisableBuiltInTTSEngineForTesting() { void DisableBuiltInTTSEngineForTesting() {
disable_built_in_tts_engine_for_testing_ = true; disable_built_in_tts_engine_for_testing_ = true;
......
...@@ -259,6 +259,7 @@ class TtsApiTest : public ExtensionApiTest { ...@@ -259,6 +259,7 @@ class TtsApiTest : public ExtensionApiTest {
content::TtsController* tts_controller = content::TtsController* tts_controller =
content::TtsController::GetInstance(); content::TtsController::GetInstance();
tts_controller->SetTtsPlatform(&mock_platform_impl_); tts_controller->SetTtsPlatform(&mock_platform_impl_);
TtsExtensionEngine::GetInstance()->DisableBuiltInTTSEngineForTesting();
tts_controller->SetTtsEngineDelegate(TtsExtensionEngine::GetInstance()); tts_controller->SetTtsEngineDelegate(TtsExtensionEngine::GetInstance());
} }
......
...@@ -129,11 +129,15 @@ void TtsControllerImpl::SpeakOrEnqueue( ...@@ -129,11 +129,15 @@ void TtsControllerImpl::SpeakOrEnqueue(
return; return;
} }
// If the TTS platform is still loading, queue or flush the utterance. The // If the TTS platform or tts engine delegate is still loading or
// utterances can be sent to platform specific implementation or to the // initializing, queue or flush the utterance. The utterances can be sent to
// engine implementation. Every utterances are postponed until the platform // platform specific implementation or to the engine implementation. Every
// specific implementation is loaded to avoid racy behaviors. // utterances are postponed until the platform specific implementation and
if (TtsPlatformLoading()) { // built in tts engine are loaded to avoid races where the utterance gets
// dropped unexpectedly.
if (TtsPlatformLoading() ||
(engine_delegate_ && !engine_delegate_->IsBuiltInTtsEngineInitialized(
utterance->GetBrowserContext()))) {
if (utterance->GetShouldClearQueue()) if (utterance->GetShouldClearQueue())
ClearUtteranceQueue(true); ClearUtteranceQueue(true);
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/values.h" #include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h" #include "build/chromeos_buildflags.h"
#include "content/browser/speech/tts_utterance_impl.h" #include "content/browser/speech/tts_utterance_impl.h"
#include "content/public/browser/tts_platform.h" #include "content/public/browser/tts_platform.h"
...@@ -107,6 +108,45 @@ class MockTtsPlatformImpl : public TtsPlatform { ...@@ -107,6 +108,45 @@ class MockTtsPlatformImpl : public TtsPlatform {
base::OnceCallback<void(bool)> did_start_speaking_callback_; base::OnceCallback<void(bool)> did_start_speaking_callback_;
}; };
class MockTtsEngineDelegate : public TtsEngineDelegate {
public:
int utterance_id() { return utterance_id_; }
void set_is_built_in_tts_engine_initialized(bool value) {
is_built_in_tts_engine_initialized_ = value;
}
void set_voices(const std::vector<VoiceData>& voices) { voices_ = voices; }
// TtsEngineDelegate:
void Speak(TtsUtterance* utterance, const VoiceData& voice) override {
utterance_id_ = utterance->GetId();
}
bool LoadBuiltInTtsEngine(BrowserContext* browser_context) override {
return true;
}
bool IsBuiltInTtsEngineInitialized(BrowserContext* browser_context) override {
return is_built_in_tts_engine_initialized_;
}
void GetVoices(BrowserContext* browser_context,
std::vector<VoiceData>* out_voices) override {
*out_voices = voices_;
}
// Unused (TtsEngineDelegate:)
void Stop(TtsUtterance* utterance) override {}
void Pause(TtsUtterance* utterance) override {}
void Resume(TtsUtterance* utterance) override {}
private:
bool is_built_in_tts_engine_initialized_ = true;
int utterance_id_ = -1;
std::vector<VoiceData> voices_;
};
#if BUILDFLAG(IS_CHROMEOS_ASH) #if BUILDFLAG(IS_CHROMEOS_ASH)
class MockTtsControllerDelegate : public TtsControllerDelegate { class MockTtsControllerDelegate : public TtsControllerDelegate {
public: public:
...@@ -173,6 +213,11 @@ class TtsControllerTest : public testing::Test { ...@@ -173,6 +213,11 @@ class TtsControllerTest : public testing::Test {
platform_impl_ = std::make_unique<MockTtsPlatformImpl>(controller_.get()); platform_impl_ = std::make_unique<MockTtsPlatformImpl>(controller_.get());
browser_context_ = std::make_unique<TestBrowserContext>(); browser_context_ = std::make_unique<TestBrowserContext>();
controller()->SetTtsPlatform(platform_impl_.get()); controller()->SetTtsPlatform(platform_impl_.get());
#if !defined(OS_ANDROID)
// TtsEngineDelegate isn't set for Android in ChromeContentBrowserClient
// since it has no extensions.
controller()->SetTtsEngineDelegate(&engine_delegate_);
#endif // !defined(OS_ANDROID)
#if BUILDFLAG(IS_CHROMEOS_ASH) #if BUILDFLAG(IS_CHROMEOS_ASH)
controller()->SetTtsControllerDelegateForTesting(&delegate_); controller()->SetTtsControllerDelegateForTesting(&delegate_);
#endif #endif
...@@ -187,6 +232,7 @@ class TtsControllerTest : public testing::Test { ...@@ -187,6 +232,7 @@ class TtsControllerTest : public testing::Test {
MockTtsPlatformImpl* platform_impl() { return platform_impl_.get(); } MockTtsPlatformImpl* platform_impl() { return platform_impl_.get(); }
TestTtsControllerImpl* controller() { return controller_.get(); } TestTtsControllerImpl* controller() { return controller_.get(); }
TestBrowserContext* browser_context() { return browser_context_.get(); } TestBrowserContext* browser_context() { return browser_context_.get(); }
MockTtsEngineDelegate* engine_delegate() { return &engine_delegate_; }
#if BUILDFLAG(IS_CHROMEOS_ASH) #if BUILDFLAG(IS_CHROMEOS_ASH)
MockTtsControllerDelegate* delegate() { return &delegate_; } MockTtsControllerDelegate* delegate() { return &delegate_; }
...@@ -225,6 +271,7 @@ class TtsControllerTest : public testing::Test { ...@@ -225,6 +271,7 @@ class TtsControllerTest : public testing::Test {
std::unique_ptr<TestTtsControllerImpl> controller_; std::unique_ptr<TestTtsControllerImpl> controller_;
std::unique_ptr<MockTtsPlatformImpl> platform_impl_; std::unique_ptr<MockTtsPlatformImpl> platform_impl_;
std::unique_ptr<TestBrowserContext> browser_context_; std::unique_ptr<TestBrowserContext> browser_context_;
MockTtsEngineDelegate engine_delegate_;
#if BUILDFLAG(IS_CHROMEOS_ASH) #if BUILDFLAG(IS_CHROMEOS_ASH)
MockTtsControllerDelegate delegate_; MockTtsControllerDelegate delegate_;
#endif #endif
...@@ -791,7 +838,7 @@ TEST_F(TtsControllerTest, PlatformNotSupported) { ...@@ -791,7 +838,7 @@ TEST_F(TtsControllerTest, PlatformNotSupported) {
EXPECT_EQ(0, platform_impl()->stop_speaking_called()); EXPECT_EQ(0, platform_impl()->stop_speaking_called());
} }
TEST_F(TtsControllerTest, SpeakWhenLoading) { TEST_F(TtsControllerTest, SpeakWhenLoadingPlatformImpl) {
platform_impl()->SetPlatformImplInitialized(false); platform_impl()->SetPlatformImplInitialized(false);
std::unique_ptr<WebContents> web_contents = CreateWebContents(); std::unique_ptr<WebContents> web_contents = CreateWebContents();
...@@ -821,4 +868,47 @@ TEST_F(TtsControllerTest, SpeakWhenLoading) { ...@@ -821,4 +868,47 @@ TEST_F(TtsControllerTest, SpeakWhenLoading) {
EXPECT_FALSE(controller()->IsSpeaking()); EXPECT_FALSE(controller()->IsSpeaking());
} }
#if !defined(OS_ANDROID)
TEST_F(TtsControllerTest, SpeakWhenLoadingBuiltInEngine) {
engine_delegate()->set_is_built_in_tts_engine_initialized(false);
std::vector<VoiceData> voices;
VoiceData voice_data;
voice_data.engine_id = "x";
voice_data.events.insert(TTS_EVENT_START);
voice_data.events.insert(TTS_EVENT_END);
voices.push_back(voice_data);
engine_delegate()->set_voices(voices);
std::unique_ptr<WebContents> web_contents = CreateWebContents();
std::unique_ptr<TtsUtteranceImpl> utterance =
CreateUtteranceImpl(web_contents.get());
utterance->SetShouldClearQueue(false);
// Speak an utterance while the built in engine is initializing, the utterance
// should be queued.
controller()->SpeakOrEnqueue(std::move(utterance));
EXPECT_FALSE(IsUtteranceListEmpty());
EXPECT_FALSE(TtsControllerCurrentUtterance());
// Simulate the completion of the initialisation.
engine_delegate()->set_is_built_in_tts_engine_initialized(true);
controller()->VoicesChanged();
int utterance_id = engine_delegate()->utterance_id();
controller()->OnTtsEvent(utterance_id, content::TTS_EVENT_START, 0, 0,
std::string());
EXPECT_TRUE(IsUtteranceListEmpty());
EXPECT_TRUE(TtsControllerCurrentUtterance());
EXPECT_TRUE(controller()->IsSpeaking());
// Complete the playing utterance.
controller()->OnTtsEvent(utterance_id, content::TTS_EVENT_END, 0, 0,
std::string());
EXPECT_TRUE(IsUtteranceListEmpty());
EXPECT_FALSE(TtsControllerCurrentUtterance());
EXPECT_FALSE(controller()->IsSpeaking());
}
#endif // !defined(OS_ANDROID)
} // namespace content } // namespace content
...@@ -67,6 +67,10 @@ class CONTENT_EXPORT TtsEngineDelegate { ...@@ -67,6 +67,10 @@ class CONTENT_EXPORT TtsEngineDelegate {
// Load the built-in TTS engine. // Load the built-in TTS engine.
virtual bool LoadBuiltInTtsEngine(BrowserContext* browser_context) = 0; virtual bool LoadBuiltInTtsEngine(BrowserContext* browser_context) = 0;
// Returns whether the built in engine is initialized.
virtual bool IsBuiltInTtsEngineInitialized(
BrowserContext* browser_context) = 0;
}; };
// Class that wants to be notified when the set of // Class that wants to be notified when the set of
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment