| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/live_caption/live_caption_controller.h" |
| |
| #include <memory> |
| |
| #include "base/command_line.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "build/build_config.h" |
| #include "components/live_caption/caption_bubble_context.h" |
| #include "components/live_caption/caption_bubble_controller.h" |
| #include "components/live_caption/caption_controller_base.h" |
| #include "components/live_caption/caption_util.h" |
| #include "components/live_caption/live_caption_bubble_settings.h" |
| #include "components/live_caption/pref_names.h" |
| #include "components/live_caption/views/caption_bubble.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_change_registrar.h" |
| #include "components/soda/constants.h" |
| #include "components/soda/soda_installer.h" |
| #include "components/sync_preferences/pref_service_syncable.h" |
| #include "media/base/media_switches.h" |
| |
| namespace captions { |
| |
| LiveCaptionController::LiveCaptionController( |
| PrefService* profile_prefs, |
| PrefService* global_prefs, |
| const std::string& application_locale, |
| content::BrowserContext* browser_context, |
| std::unique_ptr<CaptionControllerBase::Delegate> delegate) |
| : CaptionControllerBase(profile_prefs, |
| application_locale, |
| std::move(delegate)), |
| global_prefs_(global_prefs), |
| browser_context_(browser_context), |
| caption_bubble_settings_( |
| std::make_unique<LiveCaptionBubbleSettings>(profile_prefs)) { |
| base::UmaHistogramBoolean("Accessibility.LiveCaption.FeatureEnabled2", |
| IsLiveCaptionFeatureSupported()); |
| |
| // Hidden behind a feature flag. |
| if (!IsLiveCaptionFeatureSupported()) { |
| return; |
| } |
| auto* command_line = base::CommandLine::ForCurrentProcess(); |
| if (command_line && |
| command_line->HasSwitch(switches::kEnableLiveCaptionPrefForTesting)) { |
| profile_prefs->SetBoolean(prefs::kLiveCaptionEnabled, true); |
| } |
| |
| pref_change_registrar()->Add( |
| prefs::kLiveCaptionEnabled, |
| base::BindRepeating(&LiveCaptionController::OnLiveCaptionEnabledChanged, |
| base::Unretained(this))); |
| pref_change_registrar()->Add( |
| prefs::kLiveCaptionLanguageCode, |
| base::BindRepeating(&LiveCaptionController::OnLiveCaptionLanguageChanged, |
| base::Unretained(this))); |
| |
| enabled_ = IsLiveCaptionEnabled(); |
| base::UmaHistogramBoolean("Accessibility.LiveCaption2", enabled_); |
| |
| if (enabled_) { |
| StartLiveCaption(); |
| } |
| } |
| |
| LiveCaptionController::~LiveCaptionController() { |
| if (enabled_) { |
| enabled_ = false; |
| StopLiveCaption(); |
| } |
| } |
| |
| // static |
| void LiveCaptionController::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterBooleanPref( |
| prefs::kLiveCaptionBubbleExpanded, false, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| registry->RegisterBooleanPref( |
| prefs::kLiveCaptionEnabled, false, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| registry->RegisterBooleanPref( |
| prefs::kLiveCaptionMaskOffensiveWords, false, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| registry->RegisterBooleanPref(prefs::kHeadlessCaptionEnabled, false, |
| /*flags=*/0); |
| |
| // Initially default the language to en-US. The language |
| // preference value will be set to a default language when Live Caption is |
| // enabled for the first time. |
| registry->RegisterStringPref(prefs::kLiveCaptionLanguageCode, |
| speech::kUsEnglishLocale, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| |
| registry->RegisterListPref( |
| prefs::kLiveCaptionMediaFoundationRendererErrorSilenced, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| } |
| |
| void LiveCaptionController::OnLiveCaptionEnabledChanged() { |
| bool enabled = IsLiveCaptionEnabled(); |
| if (enabled == enabled_) { |
| return; |
| } |
| enabled_ = enabled; |
| |
| if (enabled) { |
| StartLiveCaption(); |
| } else { |
| StopLiveCaption(); |
| } |
| } |
| |
| void LiveCaptionController::OnFirstListenerAdded() { |
| // We have a listener, so be sure we also have soda. This listener might not |
| // be the UI. |
| |
| MaybeSetLiveCaptionLanguage(); |
| // The SodaInstaller determines whether SODA is already on the device and |
| // whether or not to download. Once SODA is on the device and ready, the |
| // SODAInstaller calls OnSodaInstalled on its observers. |
| if (!speech::SodaInstaller::GetInstance()->IsSodaInstalled( |
| speech::GetLanguageCode(GetLanguageCode()))) { |
| speech::SodaInstaller::GetInstance()->AddObserver(this); |
| speech::SodaInstaller::GetInstance()->Init(profile_prefs(), global_prefs_); |
| } |
| } |
| |
| void LiveCaptionController::OnLastListenerRemoved() { |
| // We might not have installed a listener, but that's okay. |
| speech::SodaInstaller::GetInstance()->RemoveObserver(this); |
| speech::SodaInstaller::GetInstance()->SetUninstallTimer(global_prefs_, |
| GetLanguageCode()); |
| } |
| |
| void LiveCaptionController::OnLiveCaptionLanguageChanged() { |
| if (enabled_) { |
| const auto language_code = GetLanguageCode(); |
| auto* soda_installer = speech::SodaInstaller::GetInstance(); |
| // Only trigger an install when the language is not already installed. |
| if (!soda_installer->IsSodaInstalled( |
| speech::GetLanguageCode(language_code))) { |
| soda_installer->InstallLanguage(language_code, global_prefs_); |
| } |
| } |
| } |
| |
| bool LiveCaptionController::IsLiveCaptionEnabled() { |
| return profile_prefs()->GetBoolean(prefs::kLiveCaptionEnabled); |
| } |
| |
| void LiveCaptionController::StartLiveCaption() { |
| DCHECK(enabled_); |
| // Creating the UI will trigger soda to install if needed. |
| CreateUI(); |
| } |
| |
| void LiveCaptionController::StopLiveCaption() { |
| DCHECK(!enabled_); |
| DestroyUI(); |
| } |
| |
| CaptionBubbleSettings* LiveCaptionController::caption_bubble_settings() { |
| return caption_bubble_settings_.get(); |
| } |
| |
| void LiveCaptionController::OnSodaInstalled( |
| speech::LanguageCode language_code) { |
| // Live Caption might not be enabled right now, because installation might |
| // have been triggered by a caption observer that isn't our UI. That's fine. |
| bool is_language_code_for_live_caption = |
| prefs::IsLanguageCodeForLiveCaption(language_code, profile_prefs()); |
| |
| if (is_language_code_for_live_caption) { |
| speech::SodaInstaller::GetInstance()->RemoveObserver(this); |
| } |
| } |
| |
| void LiveCaptionController::OnSodaInstallError( |
| speech::LanguageCode language_code, |
| speech::SodaInstaller::ErrorCode error_code) { |
| // Check that language code matches the selected language for Live Caption or |
| // is LanguageCode::kNone (signifying the SODA binary failed). |
| if (!prefs::IsLanguageCodeForLiveCaption(language_code, profile_prefs()) && |
| language_code != speech::LanguageCode::kNone) { |
| return; |
| } |
| // If LC is enabled, turn it back off so the UI switch toggles off. It's okay |
| // if there are other caption observers; they simply won't get any captions. |
| if (!base::FeatureList::IsEnabled(media::kLiveCaptionMultiLanguage)) { |
| profile_prefs()->SetBoolean(prefs::kLiveCaptionEnabled, false); |
| } |
| } |
| |
| const std::string LiveCaptionController::GetLanguageCode() const { |
| return prefs::GetLiveCaptionLanguageCode(profile_prefs()); |
| } |
| |
| void LiveCaptionController::OnError( |
| CaptionBubbleContext* caption_bubble_context, |
| CaptionBubbleErrorType error_type, |
| OnErrorClickedCallback error_clicked_callback, |
| OnDoNotShowAgainClickedCallback error_silenced_callback) { |
| if (!caption_bubble_controller()) { |
| CreateUI(); |
| } |
| caption_bubble_controller()->OnError(caption_bubble_context, error_type, |
| std::move(error_clicked_callback), |
| std::move(error_silenced_callback)); |
| } |
| |
| #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS) |
| void LiveCaptionController::OnToggleFullscreen( |
| CaptionBubbleContext* caption_bubble_context) { |
| if (!enabled_) { |
| return; |
| } |
| // The easiest way to move the Live Caption UI to the right workspace is to |
| // simply destroy and recreate the UI. The UI will automatically be created |
| // in the workspace of the browser window that is transmitting captions. |
| DestroyUI(); |
| CreateUI(); |
| } |
| #endif |
| |
| void LiveCaptionController::MaybeSetLiveCaptionLanguage() { |
| // If the current Live Caption language is not installed, |
| // reset the Live Caption language code to the application locale or preferred |
| // language if available. |
| if (speech::SodaInstaller::GetInstance() && |
| profile_prefs()->GetString(prefs::kLiveCaptionLanguageCode) == |
| speech::kUsEnglishLocale && |
| speech::SodaInstaller::GetInstance() |
| ->GetLanguagePath( |
| profile_prefs()->GetString(prefs::kLiveCaptionLanguageCode)) |
| .empty()) { |
| speech::SodaInstaller::GetInstance()->UnregisterLanguage( |
| speech::kUsEnglishLocale, global_prefs_); |
| speech::SodaInstaller::GetInstance()->RegisterLanguage( |
| speech::GetDefaultLiveCaptionLanguage(application_locale(), |
| profile_prefs()), |
| global_prefs_); |
| profile_prefs()->SetString(prefs::kLiveCaptionLanguageCode, |
| speech::GetDefaultLiveCaptionLanguage( |
| application_locale(), profile_prefs())); |
| } |
| } |
| |
| } // namespace captions |