// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/speech/tts_crosapi_util.h"
#include "base/feature_list.h"
#include "base/values.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "content/public/browser/tts_controller.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/constants/ash_features.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chromeos/startup/browser_params_proxy.h"
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
namespace tts_crosapi_util {
content::TtsEventType FromMojo(crosapi::mojom::TtsEventType mojo_event) {
switch (mojo_event) {
case crosapi::mojom::TtsEventType::kStart:
return content::TtsEventType::TTS_EVENT_START;
case crosapi::mojom::TtsEventType::kEnd:
return content::TtsEventType::TTS_EVENT_END;
case crosapi::mojom::TtsEventType::kWord:
return content::TtsEventType::TTS_EVENT_WORD;
case crosapi::mojom::TtsEventType::kSentence:
return content::TtsEventType::TTS_EVENT_SENTENCE;
case crosapi::mojom::TtsEventType::kMarker:
return content::TtsEventType::TTS_EVENT_MARKER;
case crosapi::mojom::TtsEventType::kInterrupted:
return content::TtsEventType::TTS_EVENT_INTERRUPTED;
case crosapi::mojom::TtsEventType::kCanceled:
return content::TtsEventType::TTS_EVENT_CANCELLED;
case crosapi::mojom::TtsEventType::kError:
return content::TtsEventType::TTS_EVENT_ERROR;
case crosapi::mojom::TtsEventType::kPause:
return content::TtsEventType::TTS_EVENT_PAUSE;
case crosapi::mojom::TtsEventType::kResume:
return content::TtsEventType::TTS_EVENT_RESUME;
}
}
crosapi::mojom::TtsEventType ToMojo(content::TtsEventType event_type) {
switch (event_type) {
case content::TtsEventType::TTS_EVENT_START:
return crosapi::mojom::TtsEventType::kStart;
case content::TtsEventType::TTS_EVENT_END:
return crosapi::mojom::TtsEventType::kEnd;
case content::TtsEventType::TTS_EVENT_WORD:
return crosapi::mojom::TtsEventType::kWord;
case content::TtsEventType::TTS_EVENT_SENTENCE:
return crosapi::mojom::TtsEventType::kSentence;
case content::TtsEventType::TTS_EVENT_MARKER:
return crosapi::mojom::TtsEventType::kMarker;
case content::TtsEventType::TTS_EVENT_INTERRUPTED:
return crosapi::mojom::TtsEventType::kInterrupted;
case content::TtsEventType::TTS_EVENT_CANCELLED:
return crosapi::mojom::TtsEventType::kCanceled;
case content::TtsEventType::TTS_EVENT_ERROR:
return crosapi::mojom::TtsEventType::kError;
case content::TtsEventType::TTS_EVENT_PAUSE:
return crosapi::mojom::TtsEventType::kPause;
case content::TtsEventType::TTS_EVENT_RESUME:
return crosapi::mojom::TtsEventType::kResume;
}
}
content::VoiceData FromMojo(const crosapi::mojom::TtsVoicePtr& mojo_voice) {
content::VoiceData voice_data;
#if BUILDFLAG(IS_CHROMEOS_ASH)
// The mojo_voice is from the remote TTS engine in Lacros.
voice_data.from_remote_tts_engine = true;
#endif
voice_data.name = mojo_voice->voice_name;
voice_data.lang = mojo_voice->lang;
voice_data.engine_id = mojo_voice->engine_id;
voice_data.remote = mojo_voice->remote;
voice_data.native = mojo_voice->native;
voice_data.native_voice_identifier = mojo_voice->native_voice_identifier;
for (const auto& mojo_event : mojo_voice->events)
voice_data.events.insert(tts_crosapi_util::FromMojo(mojo_event));
return voice_data;
}
crosapi::mojom::TtsVoicePtr ToMojo(const content::VoiceData& voice) {
auto mojo_voice = crosapi::mojom::TtsVoice::New();
mojo_voice->voice_name = voice.name;
mojo_voice->lang = voice.lang;
mojo_voice->remote = voice.remote;
mojo_voice->engine_id = voice.engine_id;
mojo_voice->native = voice.native;
mojo_voice->native_voice_identifier = voice.native_voice_identifier;
std::vector<crosapi::mojom::TtsEventType> mojo_events;
for (const auto& event : voice.events) {
mojo_events.push_back(tts_crosapi_util::ToMojo(event));
}
mojo_voice->events = std::move(mojo_events);
return mojo_voice;
}
crosapi::mojom::TtsUtterancePtr ToMojo(content::TtsUtterance* utterance) {
auto mojo_utterance = crosapi::mojom::TtsUtterance::New();
mojo_utterance->utterance_id = utterance->GetId();
mojo_utterance->text = utterance->GetText();
mojo_utterance->lang = utterance->GetLang();
mojo_utterance->voice_name = utterance->GetVoiceName();
mojo_utterance->volume = utterance->GetContinuousParameters().volume;
mojo_utterance->rate = utterance->GetContinuousParameters().rate;
mojo_utterance->pitch = utterance->GetContinuousParameters().pitch;
mojo_utterance->engine_id = utterance->GetEngineId();
mojo_utterance->should_clear_queue = utterance->GetShouldClearQueue();
mojo_utterance->src_id = utterance->GetSrcId();
mojo_utterance->src_url = utterance->GetSrcUrl();
for (const auto& event : utterance->GetDesiredEventTypes())
mojo_utterance->desired_event_types.push_back(
tts_crosapi_util::ToMojo(event));
for (const auto& event : utterance->GetRequiredEventTypes())
mojo_utterance->required_event_types.push_back(
tts_crosapi_util::ToMojo(event));
content::WebContents* web_contents = utterance->GetWebContents();
mojo_utterance->was_created_with_web_contents = web_contents != nullptr;
base::Value::Dict options = utterance->GetOptions()->Clone();
mojo_utterance->options = std::move(options);
return mojo_utterance;
}
std::unique_ptr<content::TtsUtterance> CreateUtteranceFromMojo(
crosapi::mojom::TtsUtterancePtr& mojo_utterance,
bool should_always_be_spoken) {
// Construct TtsUtterance object.
content::BrowserContext* browser_context =
ProfileManager::GetPrimaryUserProfile();
std::unique_ptr<content::TtsUtterance> utterance =
content::TtsUtterance::Create(browser_context, should_always_be_spoken);
utterance->SetText(mojo_utterance->text);
utterance->SetLang(mojo_utterance->lang);
utterance->SetVoiceName(mojo_utterance->voice_name);
utterance->SetContinuousParameters(
mojo_utterance->rate, mojo_utterance->pitch, mojo_utterance->volume);
utterance->SetEngineId(mojo_utterance->engine_id);
utterance->SetShouldClearQueue(mojo_utterance->should_clear_queue);
utterance->SetSrcUrl(mojo_utterance->src_url);
utterance->SetSrcId(mojo_utterance->src_id);
std::set<content::TtsEventType> desired_events;
for (const auto& mojo_event : mojo_utterance->desired_event_types)
desired_events.insert(tts_crosapi_util::FromMojo(mojo_event));
utterance->SetDesiredEventTypes(std::move(desired_events));
std::set<content::TtsEventType> required_events;
for (const auto& mojo_event : mojo_utterance->required_event_types)
required_events.insert(tts_crosapi_util::FromMojo(mojo_event));
utterance->SetRequiredEventTypes(std::move(required_events));
base::Value::Dict options = mojo_utterance->options.Clone();
utterance->SetOptions(std::move(options));
return utterance;
}
bool ShouldEnableLacrosTtsSupport() {
#if BUILDFLAG(IS_CHROMEOS_ASH)
bool lacros_tts_support_enabled =
crosapi::browser_util::IsLacrosEnabled() &&
!base::FeatureList::IsEnabled(ash::features::kDisableLacrosTtsSupport);
return lacros_tts_support_enabled;
#else // IS_CHROMEOS_LACROS
return chromeos::BrowserParamsProxy::Get()->EnableLacrosTtsSupport();
#endif
}
void GetAllVoicesForTesting(content::BrowserContext* browser_context,
const GURL& source_url,
std::vector<content::VoiceData>* out_voices) {
content::TtsController::GetInstance()->GetVoices(
ProfileManager::GetActiveUserProfile(), GURL(), out_voices);
}
void SpeakForTesting(std::unique_ptr<content::TtsUtterance> utterance) {
content::TtsController::GetInstance()->SpeakOrEnqueue(std::move(utterance));
}
int GetTtsUtteranceQueueSizeForTesting() {
return content::TtsController::GetInstance()->QueueSize();
}
} // namespace tts_crosapi_util