blob: 903c0e6c04a12b18f2e6359d94aca0f29b3183f8 [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_model.h"
#include "base/feature_list.h"
#include "base/functional/callback_forward.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "components/live_caption/caption_bubble_context.h"
#include "components/live_caption/views/caption_bubble.h"
#include "media/base/media_switches.h"
namespace {
// Non-scrollable caption bubble contains 2 lines of text in its normal size and
// 8 lines in its expanded size, so the maximum number of lines before
// truncating is 9.
// For scrollable caption bubble the number of lines in text is limited by
// `kLiveCaptionScrollableMaxLines` feature parameter (see below).
constexpr int kMaxLines = 9;
// Returns the length of the longest common prefix between two strings.
int GetLongestCommonPrefixLength(const std::string& str1,
const std::string& str2) {
int length = 0;
for (unsigned long i = 0, j = 0; i < str1.length() && j < str2.length();
i++, j++, length++) {
if (str1[i] != str2[j]) {
break;
}
}
return length;
}
} // namespace
namespace captions {
BASE_FEATURE(kLiveCaptionScrollable,
"LiveCaptionScrollable",
base::FEATURE_DISABLED_BY_DEFAULT);
BASE_FEATURE_PARAM(size_t,
kLiveCaptionScrollableMaxLines,
&kLiveCaptionScrollable,
"live_caption_scrollable_max_lines",
250);
CaptionBubbleModel::CaptionBubbleModel(CaptionBubbleContext* context,
OnCaptionBubbleClosedCallback callback)
: unique_id_(GetNextId()),
caption_bubble_closed_callback_(callback),
context_(context) {
DCHECK(context_);
}
CaptionBubbleModel::~CaptionBubbleModel() {
if (base::FeatureList::IsEnabled(media::kLiveCaptionLogFlickerRate)) {
// Log the number of erasures per partial result.
double flicker_rate = (partial_result_count_ > 0)
? erasure_count_ / partial_result_count_
: 0;
LOG(WARNING) << "Live caption flicker rate:" << flicker_rate
<< ". (not a warning)";
}
if (observer_)
observer_->SetModel(nullptr);
}
void CaptionBubbleModel::SetObserver(CaptionBubble* observer) {
if (observer_)
return;
observer_ = observer;
if (observer_) {
observer_->OnTextChanged();
observer_->OnErrorChanged(
CaptionBubbleErrorType::kGeneric, base::RepeatingClosure(),
base::BindRepeating(
[](CaptionBubbleErrorType error_type, bool checked) {}));
}
}
void CaptionBubbleModel::RemoveObserver() {
observer_ = nullptr;
}
void CaptionBubbleModel::OnTextChanged() {
if (observer_) {
observer_->OnTextChanged();
}
}
void CaptionBubbleModel::OnAutoDetectedLanguageChanged() {
if (observer_) {
observer_->OnAutoDetectedLanguageChanged();
}
}
void CaptionBubbleModel::SetPartialText(const std::string& partial_text) {
if (base::FeatureList::IsEnabled(media::kLiveCaptionLogFlickerRate)) {
erasure_count_ +=
partial_text_.size() -
GetLongestCommonPrefixLength(partial_text, partial_text_);
partial_result_count_++;
}
partial_text_ = partial_text;
OnTextChanged();
if (has_error_) {
has_error_ = false;
if (observer_)
observer_->OnErrorChanged(
CaptionBubbleErrorType::kGeneric, base::RepeatingClosure(),
base::BindRepeating(
[](CaptionBubbleErrorType error_type, bool checked) {}));
}
}
void CaptionBubbleModel::SetDownloadProgressText(
const std::u16string& download_progress_text) {
download_progress_text_ = download_progress_text;
if (observer_) {
observer_->OnDownloadProgressTextChanged();
}
}
void CaptionBubbleModel::OnLanguagePackInstalled() {
if (observer_) {
observer_->OnLanguagePackInstalled();
}
}
void CaptionBubbleModel::CloseButtonPressed() {
caption_bubble_closed_callback_.Run(context_->GetSessionId());
Close();
}
void CaptionBubbleModel::Close() {
is_closed_ = true;
ClearText();
}
std::string CaptionBubbleModel::GetFullText() const {
// Ensure that there is a space between the final and partial texts.
if (!final_text_.empty() && !partial_text_.empty() &&
!std::isspace(final_text_.back()) && !std::isspace(partial_text_[0])) {
return final_text_ + " " + partial_text_;
}
return final_text_ + partial_text_;
}
void CaptionBubbleModel::OnError(
CaptionBubbleErrorType error_type,
OnErrorClickedCallback error_clicked_callback,
OnDoNotShowAgainClickedCallback error_silenced_callback) {
has_error_ = true;
error_type_ = error_type;
if (observer_) {
base::UmaHistogramEnumeration(
"Accessibility.LiveCaption.CaptionBubbleError", error_type);
observer_->OnErrorChanged(error_type, std::move(error_clicked_callback),
std::move(error_silenced_callback));
}
}
void CaptionBubbleModel::ClearText() {
partial_text_.clear();
final_text_.clear();
OnTextChanged();
}
void CaptionBubbleModel::CommitPartialText() {
final_text_ = GetFullText();
partial_text_.clear();
if (!observer_)
return;
const size_t max_lines = base::FeatureList::IsEnabled(kLiveCaptionScrollable)
? kLiveCaptionScrollableMaxLines.Get()
: kMaxLines;
// Truncate the final text to max_lines lines long. This time, alert the
// observer that the text has changed.
const size_t num_lines = observer_->GetNumLinesInLabel();
if (num_lines > max_lines) {
const size_t truncate_index =
observer_->GetTextIndexOfLineInLabel(num_lines - max_lines);
final_text_.erase(0, truncate_index);
OnTextChanged();
}
}
void CaptionBubbleModel::SetLanguage(const std::string& language_code) {
if (!observer_) {
return;
}
auto_detected_language_code_ = language_code;
OnAutoDetectedLanguageChanged();
}
// static
CaptionBubbleModel::Id CaptionBubbleModel::GetNextId() {
static Id::Generator generator;
return generator.GenerateNextId();
}
} // namespace captions