chromium/chrome/browser/speech/tts_controller_delegate_impl.cc

// Copyright 2014 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_controller_delegate_impl.h"

#include <stddef.h>

#include <string>

#include "base/json/json_reader.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/tts_controller.h"
#include "third_party/blink/public/mojom/speech/speech_synthesis.mojom.h"
#include "ui/base/l10n/l10n_util.h"

namespace {

std::optional<content::TtsControllerDelegate::PreferredVoiceId>
PreferredVoiceIdFromString(const base::Value::Dict& pref,
                           const std::string& pref_key) {
  const std::string* voice_id =
      pref.FindStringByDottedPath(l10n_util::GetLanguage(pref_key));
  if (!voice_id || voice_id->empty())
    return std::nullopt;

  std::optional<base::Value> json = base::JSONReader::Read(*voice_id);
  std::string name;
  std::string id;
  if (json && json->is_dict()) {
    const base::Value::Dict& dict = json->GetDict();
    const std::string* name_str = dict.FindString("name");
    if (name_str)
      name = *name_str;
    const std::string* id_str = dict.FindString("extension");
    if (id_str)
      id = *id_str;
  }

  return std::optional<content::TtsControllerDelegate::PreferredVoiceId>(
      {name, id});
}

}  // namespace

//
// TtsControllerDelegateImpl
//

// static
TtsControllerDelegateImpl* TtsControllerDelegateImpl::GetInstance() {
  return base::Singleton<TtsControllerDelegateImpl>::get();
}

TtsControllerDelegateImpl::TtsControllerDelegateImpl() = default;

TtsControllerDelegateImpl::~TtsControllerDelegateImpl() = default;

std::unique_ptr<content::TtsControllerDelegate::PreferredVoiceIds>
TtsControllerDelegateImpl::GetPreferredVoiceIdsForUtterance(
    content::TtsUtterance* utterance) {
  const base::Value::Dict* lang_to_voice_pref = GetLangToVoicePref(utterance);
  if (!lang_to_voice_pref)
    return nullptr;

  std::unique_ptr<PreferredVoiceIds> preferred_ids =
      std::make_unique<PreferredVoiceIds>();

  if (!utterance->GetLang().empty()) {
    preferred_ids->lang_voice_id = PreferredVoiceIdFromString(
        *lang_to_voice_pref, l10n_util::GetLanguage(utterance->GetLang()));
  }

  const std::string app_lang = g_browser_process->GetApplicationLocale();
  preferred_ids->locale_voice_id = PreferredVoiceIdFromString(
      *lang_to_voice_pref, l10n_util::GetLanguage(app_lang));

  preferred_ids->any_locale_voice_id =
      PreferredVoiceIdFromString(*lang_to_voice_pref, "noLanguageCode");
  return preferred_ids;
}

void TtsControllerDelegateImpl::UpdateUtteranceDefaultsFromPrefs(
    content::TtsUtterance* utterance,
    double* rate,
    double* pitch,
    double* volume) {
  // Update pitch, rate and volume from user prefs if not set explicitly
  // on this utterance.
  const PrefService* prefs = GetPrefService(utterance);
  if (*rate == blink::mojom::kSpeechSynthesisDoublePrefNotSet) {
    *rate = prefs ? prefs->GetDouble(prefs::kTextToSpeechRate)
                  : blink::mojom::kSpeechSynthesisDefaultRate;
  }
  if (*pitch == blink::mojom::kSpeechSynthesisDoublePrefNotSet) {
    *pitch = prefs ? prefs->GetDouble(prefs::kTextToSpeechPitch)
                   : blink::mojom::kSpeechSynthesisDefaultPitch;
  }
  if (*volume == blink::mojom::kSpeechSynthesisDoublePrefNotSet) {
    *volume = prefs ? prefs->GetDouble(prefs::kTextToSpeechVolume)
                    : blink::mojom::kSpeechSynthesisDefaultVolume;
  }
}

const PrefService* TtsControllerDelegateImpl::GetPrefService(
    content::TtsUtterance* utterance) {
  // The utterance->GetBrowserContext() is null in tests.
  if (!utterance->GetBrowserContext())
    return nullptr;

  const Profile* profile =
      Profile::FromBrowserContext(utterance->GetBrowserContext());
  return profile ? profile->GetPrefs() : nullptr;
}

const base::Value::Dict* TtsControllerDelegateImpl::GetLangToVoicePref(
    content::TtsUtterance* utterance) {
  const PrefService* prefs = GetPrefService(utterance);
  return prefs == nullptr
             ? nullptr
             : &prefs->GetDict(prefs::kTextToSpeechLangToVoiceName);
}