| // Copyright 2025 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef COMPONENTS_LIVE_CAPTION_CAPTION_CONTROLLER_BASE_H_ |
| #define COMPONENTS_LIVE_CAPTION_CAPTION_CONTROLLER_BASE_H_ |
| |
| #include <list> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| |
| #include "base/memory/raw_ptr.h" |
| #include "media/mojo/mojom/speech_recognition.mojom.h" |
| #include "ui/native_theme/caption_style.h" |
| #include "ui/native_theme/native_theme_observer.h" |
| |
| class PrefChangeRegistrar; |
| class PrefService; |
| |
| namespace content { |
| class RenderFrameHost; |
| } // namespace content |
| |
| namespace captions { |
| |
| class CaptionBubbleContext; |
| class CaptionBubbleController; |
| class CaptionBubbleSettings; |
| class TranslationViewWrapperBase; |
| |
| class CaptionControllerBase : public ui::NativeThemeObserver { |
| public: |
| class Delegate { |
| public: |
| Delegate(const Delegate&) = delete; |
| Delegate& operator=(const Delegate&) = delete; |
| |
| virtual ~Delegate() = default; |
| |
| virtual std::unique_ptr<CaptionBubbleController> |
| CreateCaptionBubbleController( |
| CaptionBubbleSettings* caption_bubble_settings, |
| const std::string& application_locale, |
| std::unique_ptr<TranslationViewWrapperBase> |
| translation_view_wrapper) = 0; |
| |
| virtual void AddCaptionStyleObserver(ui::NativeThemeObserver* observer) = 0; |
| |
| virtual void RemoveCaptionStyleObserver( |
| ui::NativeThemeObserver* observer) = 0; |
| |
| protected: |
| Delegate() = default; |
| }; |
| |
| // Listener for transcription-related events. Listeners are owned by the |
| // `CaptionControllerBase` for simplicity. |
| class Listener { |
| public: |
| virtual ~Listener() = default; |
| |
| // Called when a transcription is received from the service for audio that |
| // originated in the RFH. This may be null if the audio was not associated |
| // with any particulat RFH. |
| // |
| // Transcriptions will halt if this returns false. |
| virtual bool OnTranscription( |
| content::RenderFrameHost*, |
| CaptionBubbleContext*, |
| const media::SpeechRecognitionResult& result) = 0; |
| |
| // Called when the audio stream has ended for audio from the |
| // RenderFrameHost, which may be null. |
| virtual void OnAudioStreamEnd( |
| content::RenderFrameHost*, |
| CaptionBubbleContext* caption_bubble_context) = 0; |
| |
| // Called when the language is identified for audio from the |
| // RenderFrameHost, which may be null. |
| virtual void OnLanguageIdentificationEvent( |
| content::RenderFrameHost*, |
| CaptionBubbleContext* caption_bubble_context, |
| const media::mojom::LanguageIdentificationEventPtr& event) = 0; |
| |
| private: |
| raw_ptr<CaptionControllerBase> caption_controller_ = nullptr; |
| }; |
| |
| CaptionControllerBase(const CaptionControllerBase&) = delete; |
| CaptionControllerBase& operator=(const CaptionControllerBase&) = delete; |
| |
| ~CaptionControllerBase() override; |
| |
| // Add or remove a `Listener`. We maintain ownership, and destroy the |
| // listener when it is removed. |
| void AddListener(std::unique_ptr<Listener>); |
| |
| // Routes a transcription to all listeners. Returns whether the transcription |
| // was routed successfully, which currently means that at least one listener |
| // considered it to be successful. It is unclear if we should continue to |
| // route transcriptions to a listener once it returns false, but for now |
| // there's at most one listener anyway. |
| // |
| // Transcriptions will halt if this returns false. |
| bool DispatchTranscription(content::RenderFrameHost* rfh, |
| CaptionBubbleContext* caption_bubble_context, |
| const media::SpeechRecognitionResult& result); |
| |
| // Alerts all listeners that the audio stream has ended. |
| void OnAudioStreamEnd(content::RenderFrameHost* rfh, |
| CaptionBubbleContext* caption_bubble_context); |
| |
| // Notifies all listeners about a language identification event. |
| void OnLanguageIdentificationEvent( |
| content::RenderFrameHost* rfh, |
| CaptionBubbleContext* caption_bubble_context, |
| const media::mojom::LanguageIdentificationEventPtr& event); |
| |
| void create_ui_for_testing() { CreateUI(); } |
| void destroy_ui_for_testing() { DestroyUI(); } |
| CaptionBubbleController* caption_bubble_controller_for_testing() const { |
| return caption_bubble_controller(); |
| } |
| |
| void remove_listener_for_testing(Listener* listener) { |
| RemoveListener(listener); |
| } |
| |
| protected: |
| CaptionControllerBase(PrefService* profile_prefs, |
| const std::string& application_locale, |
| std::unique_ptr<Delegate> delegate = nullptr); |
| |
| void CreateUI(); |
| void DestroyUI(); |
| |
| PrefService* profile_prefs() const; |
| const std::string& application_locale() const; |
| PrefChangeRegistrar* pref_change_registrar() const; |
| CaptionBubbleController* caption_bubble_controller() const; |
| |
| virtual std::unique_ptr<TranslationViewWrapperBase> |
| CreateTranslationViewWrapper(); |
| |
| // Called when the size of the listener set goes to or from zero. This allows |
| // subclasses to handle SODA installation as needed on a per-platform basis. |
| virtual void OnFirstListenerAdded() {} |
| virtual void OnLastListenerRemoved() {} |
| |
| private: |
| virtual CaptionBubbleSettings* caption_bubble_settings() = 0; |
| |
| // ui::NativeThemeObserver: |
| void OnCaptionStyleUpdated() override; |
| |
| // The listener will be destroyed when this returns. Private, so that we |
| // don't have to worry about listeners removing themselves during list |
| // iteration. If Listeners ever want to do that, then we need to get smarter |
| // about it. |
| void RemoveListener(Listener*); |
| |
| const raw_ptr<PrefService> profile_prefs_ = nullptr; |
| const std::string application_locale_; |
| const std::unique_ptr<Delegate> delegate_; |
| |
| // The controller is also a `Listener`, and is owned by `listeners_` when we |
| // create it. While it exists, `caption_bubble_controller_` aliases it. This |
| // alias is cleared when it's destroyed. |
| raw_ptr<CaptionBubbleController> caption_bubble_controller_ = nullptr; |
| |
| const std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_; |
| |
| // Whether the UI has been created. The UI is created asynchronously from the |
| // feature being enabled--some implementations may wait for SODA to download |
| // first. This flag ensures that the UI is not constructed or deconstructed |
| // twice. |
| bool is_ui_constructed_ = false; |
| |
| // All the listeners we own. One of them will be `caption_bubble_controller_` |
| // if we have one. |
| std::list<std::unique_ptr<Listener>> listeners_; |
| }; |
| |
| } // namespace captions |
| |
| #endif // COMPONENTS_LIVE_CAPTION_CAPTION_CONTROLLER_BASE_H_ |