blob: 2030aa696916ec69436f1c4e610b0a39f929efa2 [file] [log] [blame]
// 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/views/caption_bubble_controller_views.h"
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include "base/functional/bind.h"
#include "components/live_caption/caption_bubble_context.h"
#include "components/live_caption/live_caption_controller.h"
#include "components/live_caption/views/caption_bubble.h"
#include "components/live_caption/views/caption_bubble_model.h"
#include "components/prefs/pref_service.h"
namespace captions {
// Static
std::unique_ptr<CaptionBubbleController> CaptionBubbleController::Create(
PrefService* profile_prefs,
const std::string& application_locale) {
return std::make_unique<CaptionBubbleControllerViews>(profile_prefs,
application_locale);
}
CaptionBubbleControllerViews::CaptionBubbleControllerViews(
PrefService* profile_prefs,
const std::string& application_locale) {
caption_bubble_ = new CaptionBubble(
profile_prefs, application_locale,
base::BindOnce(&CaptionBubbleControllerViews::OnCaptionBubbleDestroyed,
base::Unretained(this)));
caption_widget_ =
views::BubbleDialogDelegateView::CreateBubble(caption_bubble_);
caption_bubble_->SetCaptionBubbleStyle();
}
CaptionBubbleControllerViews::~CaptionBubbleControllerViews() {
if (caption_widget_)
caption_widget_->CloseNow();
}
void CaptionBubbleControllerViews::OnCaptionBubbleDestroyed() {
caption_bubble_ = nullptr;
caption_widget_ = nullptr;
}
bool CaptionBubbleControllerViews::OnTranscription(
CaptionBubbleContext* caption_bubble_context,
const media::SpeechRecognitionResult& result) {
if (!caption_bubble_)
return false;
SetActiveModel(caption_bubble_context);
if (active_model_->IsClosed())
return false;
// If the caption bubble has no activity and it receives a final
// transcription, don't set text. The speech service sends a final
// transcription after several seconds of no audio. This prevents the bubble
// reappearing with a final transcription after it had disappeared due to no
// activity.
if (!caption_bubble_->HasActivity() && result.is_final)
return true;
active_model_->SetPartialText(result.transcription);
if (result.is_final)
active_model_->CommitPartialText();
return true;
}
void CaptionBubbleControllerViews::OnError(
CaptionBubbleContext* caption_bubble_context,
CaptionBubbleErrorType error_type,
OnErrorClickedCallback error_clicked_callback,
OnDoNotShowAgainClickedCallback error_silenced_callback) {
if (!caption_bubble_)
return;
SetActiveModel(caption_bubble_context);
if (active_model_->IsClosed())
return;
active_model_->OnError(error_type, std::move(error_clicked_callback),
std::move(error_silenced_callback));
}
void CaptionBubbleControllerViews::OnAudioStreamEnd(
CaptionBubbleContext* caption_bubble_context) {
if (!caption_bubble_)
return;
auto caption_bubble_model_it =
caption_bubble_models_.find(caption_bubble_context);
if (caption_bubble_model_it == caption_bubble_models_.end()) {
return;
}
if (active_model_ != nullptr &&
active_model_->unique_id() ==
caption_bubble_model_it->second->unique_id()) {
active_model_ = nullptr;
caption_bubble_->SetModel(nullptr);
}
caption_bubble_models_.erase(caption_bubble_model_it);
}
void CaptionBubbleControllerViews::UpdateCaptionStyle(
absl::optional<ui::CaptionStyle> caption_style) {
caption_bubble_->UpdateCaptionStyle(caption_style);
}
void CaptionBubbleControllerViews::SetActiveModel(
CaptionBubbleContext* caption_bubble_context) {
auto caption_bubble_model_it =
caption_bubble_models_.find(caption_bubble_context);
if (caption_bubble_model_it == caption_bubble_models_.end()) {
auto caption_bubble_model = std::make_unique<CaptionBubbleModel>(
caption_bubble_context,
base::BindRepeating(
&CaptionBubbleControllerViews::OnSessionEnded,
// Unretained is safe because |CaptionBubbleControllerViews|
// owns |caption_bubble_model|.
base::Unretained(this)));
if (base::Contains(closed_sessions_,
caption_bubble_context->GetSessionId())) {
caption_bubble_model->Close();
}
caption_bubble_model_it =
caption_bubble_models_
.emplace(caption_bubble_context, std::move(caption_bubble_model))
.first;
}
if (!caption_bubble_session_observers_.count(
caption_bubble_context->GetSessionId())) {
std::unique_ptr<CaptionBubbleSessionObserver> observer =
caption_bubble_context->GetCaptionBubbleSessionObserver();
if (observer) {
observer->SetEndSessionCallback(
base::BindRepeating(&CaptionBubbleControllerViews::OnSessionReset,
base::Unretained(this)));
caption_bubble_session_observers_.emplace(
caption_bubble_context->GetSessionId(), std::move(observer));
}
}
if (active_model_ == nullptr ||
active_model_->unique_id() !=
caption_bubble_model_it->second->unique_id()) {
active_model_ = caption_bubble_model_it->second.get();
caption_bubble_->SetModel(active_model_);
}
}
void CaptionBubbleControllerViews::OnSessionEnded(
const std::string& session_id) {
// Close all other CaptionBubbleModels that share this WebContents identifier.
for (const auto& caption_bubble_model : caption_bubble_models_) {
if (caption_bubble_model.first->GetSessionId() == session_id) {
caption_bubble_model.second->Close();
}
}
closed_sessions_.insert(session_id);
}
void CaptionBubbleControllerViews::OnSessionReset(
const std::string& session_id) {
if (base::Contains(closed_sessions_, session_id)) {
closed_sessions_.erase(session_id);
}
caption_bubble_session_observers_.erase(session_id);
}
bool CaptionBubbleControllerViews::IsWidgetVisibleForTesting() {
return caption_widget_ && caption_widget_->IsVisible();
}
bool CaptionBubbleControllerViews::IsGenericErrorMessageVisibleForTesting() {
return caption_bubble_ &&
caption_bubble_->IsGenericErrorMessageVisibleForTesting(); // IN-TEST
}
std::string CaptionBubbleControllerViews::GetBubbleLabelTextForTesting() {
return caption_bubble_
? base::UTF16ToUTF8(
caption_bubble_->GetLabelForTesting()->GetText()) // IN-TEST
: "";
}
void CaptionBubbleControllerViews::CloseActiveModelForTesting() {
if (active_model_)
active_model_->Close();
}
views::Widget* CaptionBubbleControllerViews::GetCaptionWidgetForTesting() {
return caption_widget_;
}
CaptionBubble* CaptionBubbleControllerViews::GetCaptionBubbleForTesting() {
return caption_bubble_;
}
void CaptionBubbleControllerViews::OnLanguageIdentificationEvent(
CaptionBubbleContext* caption_bubble_context,
const media::mojom::LanguageIdentificationEventPtr& event) {
if (!caption_bubble_) {
return;
}
SetActiveModel(caption_bubble_context);
if (active_model_->IsClosed()) {
return;
}
if (event->asr_switch_result ==
media::mojom::AsrSwitchResult::kSwitchSucceeded) {
active_model_->SetLanguage(event->language);
}
}
} // namespace captions