Skip to content

Commit

Permalink
Text to speech in reader mode added.
Browse files Browse the repository at this point in the history
  • Loading branch information
boocmp committed Jul 26, 2023
1 parent c2eff20 commit ec66e2d
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 61 deletions.
54 changes: 31 additions & 23 deletions browser/speedreader/page_distiller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ void PageDistiller::GetDistilledText(DistillContentCallback callback) {
weak_factory_.GetWeakPtr(), std::move(callback)));
}

void PageDistiller::GetTextToSpeak(TextToSpeechContentCallback callback) {
if (state_ != State::kDistilled) {
return std::move(callback).Run(base::Value());
}

constexpr const char16_t kGetTextToSpeak[] = uR"js( extractTextToSpeak() )js";

web_contents_->GetPrimaryMainFrame()->ExecuteJavaScriptInIsolatedWorld(
kGetTextToSpeak,
base::BindOnce(&PageDistiller::OnGetTextToSpeak,
weak_factory_.GetWeakPtr(), std::move(callback)),
ISOLATED_WORLD_ID_BRAVE_INTERNAL);
}

void PageDistiller::UpdateState(State state) {
state_ = state;
for (auto& observer : observers_) {
Expand All @@ -62,22 +76,17 @@ void PageDistiller::StartDistill(DistillContentCallback callback) {
return std::move(callback).Run(false, {});
}

if (state_ == State::kDistilled) {
constexpr const char16_t kScript[] = uR"js( extractText() )js";
web_contents_->GetPrimaryMainFrame()->ExecuteJavaScriptInIsolatedWorld(
kScript,
base::BindOnce(&PageDistiller::OnGetText, weak_factory_.GetWeakPtr(),
std::move(callback)),
ISOLATED_WORLD_ID_BRAVE_INTERNAL);
} else {
constexpr const char16_t kScript[] =
uR"js( document.documentElement.outerHTML )js";
web_contents_->GetPrimaryMainFrame()->ExecuteJavaScriptInIsolatedWorld(
kScript,
base::BindOnce(&PageDistiller::OnGetOuterHTML,
weak_factory_.GetWeakPtr(), std::move(callback)),
ISOLATED_WORLD_ID_BRAVE_INTERNAL);
}
constexpr const char16_t kGetDocumentSource[] =
uR"js( document.documentElement.outerHTML )js";

constexpr const char16_t kGetBodySource[] =
uR"js( document.body.outerHTML )js";

web_contents_->GetPrimaryMainFrame()->ExecuteJavaScriptInIsolatedWorld(
(state_ != State::kDistilled) ? kGetDocumentSource : kGetBodySource,
base::BindOnce(&PageDistiller::OnGetOuterHTML, weak_factory_.GetWeakPtr(),
std::move(callback)),
ISOLATED_WORLD_ID_BRAVE_INTERNAL);
}

void PageDistiller::OnGetOuterHTML(DistillContentCallback callback,
Expand Down Expand Up @@ -106,14 +115,12 @@ void PageDistiller::OnGetOuterHTML(DistillContentCallback callback,
}
}

void PageDistiller::OnGetText(DistillContentCallback callback,
base::Value result) {
if (!web_contents_ || !result.is_dict() ||
!result.GetDict().FindString("content")) {
return std::move(callback).Run(false, {});
void PageDistiller::OnGetTextToSpeak(TextToSpeechContentCallback callback,
base::Value result) {
if (!result.is_dict()) {
return std::move(callback).Run(base::Value());
}
std::move(callback).Run(true,
std::move(*result.GetDict().FindString("content")));
std::move(callback).Run(std::move(result));
}

void PageDistiller::OnPageDistilled(DistillContentCallback callback,
Expand Down Expand Up @@ -149,6 +156,7 @@ void PageDistiller::ExtractText(DistillContentCallback callback,
return std::move(callback).Run(false, {});
}

re2::RE2::GlobalReplace(&html_content, "<[^>]*>", " ");
std::move(callback).Run(true, html_content);
}

Expand Down
5 changes: 4 additions & 1 deletion browser/speedreader/page_distiller.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class PageDistiller {

using DistillContentCallback =
base::OnceCallback<void(bool success, std::string content)>;
using TextToSpeechContentCallback = base::OnceCallback<void(base::Value)>;

State GetState() const;

Expand All @@ -49,6 +50,7 @@ class PageDistiller {

void GetDistilledHTML(DistillContentCallback callback);
void GetDistilledText(DistillContentCallback callback);
void GetTextToSpeak(TextToSpeechContentCallback callback);

protected:
explicit PageDistiller(content::WebContents* web_contents);
Expand All @@ -60,7 +62,8 @@ class PageDistiller {
private:
void StartDistill(DistillContentCallback callback);
void OnGetOuterHTML(DistillContentCallback callback, base::Value result);
void OnGetText(DistillContentCallback callback, base::Value result);
void OnGetTextToSpeak(TextToSpeechContentCallback callback,
base::Value result);
void OnPageDistilled(DistillContentCallback callback,
DistillationResult result,
std::string original_data,
Expand Down
4 changes: 2 additions & 2 deletions browser/speedreader/speedreader_tab_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -594,14 +594,14 @@ void SpeedreaderTabHelper::SetDocumentAttribute(const std::string& attribute,

void SpeedreaderTabHelper::OnGetDocumentSource(bool success, std::string html) {
DCHECK(single_shot_next_request_);
if (!success) {
if (!success || html.empty()) {
// TODO(boocmp): Show error dialog [Distillation failed on this page].
SetNextRequestState(DistillState::kPageProbablyReadable);
UpdateUI();
return;
}

single_show_content_.swap(html);
single_show_content_ = std::move(html);
ReloadContents();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <memory>
#include <utility>

#include "base/strings/string_number_conversions.h"
#include "brave/browser/speedreader/speedreader_service_factory.h"
#include "brave/browser/speedreader/speedreader_tab_helper.h"
#include "brave/browser/ui/brave_browser_window.h"
Expand All @@ -22,6 +23,7 @@
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_isolated_world_ids.h"
#include "ui/color/color_provider.h"

namespace {
Expand All @@ -31,14 +33,13 @@ class TtsPlayerDelegate : public speedreader::TtsPlayer::Delegate {

void RequestReadingContent(
content::WebContents* web_contents,
base::OnceCallback<void(bool success, std::string content)> result_cb)
override {
base::OnceCallback<void(base::Value content)> result_cb) override {
auto* page_distiller =
speedreader::SpeedreaderTabHelper::GetPageDistiller(web_contents);
if (page_distiller) {
page_distiller->GetDistilledText(std::move(result_cb));
page_distiller->GetTextToSpeak(std::move(result_cb));
} else {
std::move(result_cb).Run(false, {});
std::move(result_cb).Run(base::Value());
}
}
};
Expand Down Expand Up @@ -232,9 +233,24 @@ void SpeedreaderToolbarDataHandlerImpl::OnReadingStop(

void SpeedreaderToolbarDataHandlerImpl::OnReadingProgress(
content::WebContents* web_contents,
const std::string& element_id,
int paragraph_index,
int char_index,
int length) {}
int length) {
if (!web_contents) {
return;
}

constexpr const char16_t kHighlight[] = uR"js( highlightText($1, $2, $3) )js";

const auto script = base::ReplaceStringPlaceholders(
kHighlight,
{base::NumberToString16(paragraph_index),
base::NumberToString16(char_index), base::NumberToString16(length)},
nullptr);

web_contents->GetPrimaryMainFrame()->ExecuteJavaScriptInIsolatedWorld(
script, base::DoNothing(), ISOLATED_WORLD_ID_BRAVE_INTERNAL);
}

void SpeedreaderToolbarDataHandlerImpl::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
#ifndef BRAVE_BROWSER_UI_WEBUI_SPEEDREADER_SPEEDREADER_TOOLBAR_DATA_HANDLER_IMPL_H_
#define BRAVE_BROWSER_UI_WEBUI_SPEEDREADER_SPEEDREADER_TOOLBAR_DATA_HANDLER_IMPL_H_

#include <string>

#include "base/scoped_observation.h"
#include "brave/components/speedreader/common/speedreader_toolbar.mojom.h"
#include "brave/components/speedreader/speedreader_service.h"
Expand Down Expand Up @@ -94,7 +92,7 @@ class SpeedreaderToolbarDataHandlerImpl
void OnReadingStart(content::WebContents* web_contents) override;
void OnReadingStop(content::WebContents* web_contents) override;
void OnReadingProgress(content::WebContents* web_contents,
const std::string& element_id,
int paragraph_index,
int char_index,
int length) override;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ const mainButtonsOptions = [
id: 'tts',
type: MainButtonType.TextToSpeech,
iconName: 'headphones',
hidden: true, // TODO(boocmp): Enable in future PR.
title: getLocale('braveReaderModeTextToSpeech')
},
{
Expand Down
32 changes: 31 additions & 1 deletion components/speedreader/resources/speedreader-desktop.css
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@ iframe {

html,
html[data-theme='light'] {
position: relative; !important;
background-color: var(--background-color);

--article-background-color: #FFFFFF;
Expand Down Expand Up @@ -643,4 +644,33 @@ html[data-font-family='dyslexic'] {

html[data-content-style='text-only'] img {
display: none !important;
}
}

.tts-highlighted, .tts-highlighted * {
position: relative;
}

@keyframes tts-fade-in {
0% { opacity: 0; }
100% { opacity: 1; }
}

.tts-highlighted::after {
animation: tts-fade-in 0.75s;

content: '';
background: linear-gradient(90deg, rgba(168, 168, 168, 0.5), rgba(128, 128, 128, 0.1));
mix-blend-mode: luminosity;

background-size: 100%;
border-radius: 10px;

position: absolute;

left: -1.5rem;
right: -1.5rem;
top: -0.5rem;
bottom: -0.5rem;

pointer-events: none;
}
53 changes: 51 additions & 2 deletions components/speedreader/resources/speedreader-desktop.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,61 @@ const defaultSpeedreaderData = {
minutesText: 'min. read',
}

const extractText = () => {
const extractTextToSpeak = () => {
const textTags = ['P', 'DIV', 'MAIN', 'ARTICLE']

const extractParagraphs = (node) => {
let paragraphs = []
if (!node) {
return paragraphs
}
for (const child of node.children) {
if (textTags.indexOf(child.tagName) >= 0) {
const childParagraphs = extractParagraphs(child)
if (childParagraphs.length == 0) {
paragraphs.push(child)
} else {
paragraphs = paragraphs.concat(childParagraphs)
}
}
}
return paragraphs
}

const paragraphs = extractParagraphs($(contentDivId))

const textToSpeak = []
for (const p of paragraphs) {
const text = p.innerText.replace(/\n|\r +/g, ' ').trim()
if (text) {
p.setAttribute('tts-paragraph-index', textToSpeak.length)
textToSpeak.push(p.innerText.replace(/\n|\r +/g, ' '))
}
}

return {
title: document.title,
author: $(metaDataDivId)?.querySelector('.author')?.textContent,
desciption: $(metaDataDivId)?.querySelector('.subhead')?.textContent,
content: $(contentDivId)?.innerText.replace(/\n|\r +/g, ' ')
paragraphs: textToSpeak
}
}

const highlightText = (ttsParagraphIndex, charIndex, length) => {
document.querySelectorAll('.tts-highlighted').forEach((e) => {
if (e.getAttribute('tts-paragraph-index') != ttsParagraphIndex) {
e.classList.remove('tts-highlighted')
}
})

const paragraph = document.querySelector(
'[tts-paragraph-index="' + ttsParagraphIndex + '"]')
if (paragraph) {
paragraph.classList.add('tts-highlighted')
const ttsContent = document.createElement('tts-content')
ttsContent.append(...paragraph.childNodes)
paragraph.append(ttsContent)
document.documentElement.style.setProperty('--tts-highlight-progress', (charIndex / paragraph.textContent.length) * 100 + '%')
}
}

Expand Down
Loading

0 comments on commit ec66e2d

Please sign in to comment.