blob: 32dd50edadd1e9da090e4678f31fe9184ee5523d [file] [log] [blame]
// 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_