blob: 404cdf05dab2262b001a4088666b27da175b65ac [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/speech/tts_message_filter.h"
#include <stddef.h>
#include "base/bind.h"
#include "base/logging.h"
#include "base/task/post_task.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/tts_messages.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_process_host.h"
using content::BrowserThread;
TtsMessageFilter::TtsMessageFilter(content::BrowserContext* browser_context)
: BrowserMessageFilter(TtsMsgStart),
browser_context_(browser_context),
valid_(true) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
content::TtsController::GetInstance()->AddVoicesChangedDelegate(this);
// TODO(dmazzoni): make it so that we can listen for a BrowserContext
// being destroyed rather than a Profile. http://crbug.com/444668
Profile* profile = Profile::FromBrowserContext(browser_context);
notification_registrar_.Add(this,
chrome::NOTIFICATION_PROFILE_DESTROYED,
content::Source<Profile>(profile));
// Balanced in OnChannelClosingInUIThread() to keep the ref-count be non-zero
// until all pointers to this class are invalidated.
AddRef();
}
void TtsMessageFilter::OverrideThreadForMessage(
const IPC::Message& message, BrowserThread::ID* thread) {
switch (message.type()) {
case TtsHostMsg_InitializeVoiceList::ID:
case TtsHostMsg_Speak::ID:
case TtsHostMsg_Pause::ID:
case TtsHostMsg_Resume::ID:
case TtsHostMsg_Cancel::ID:
*thread = BrowserThread::UI;
break;
}
}
bool TtsMessageFilter::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(TtsMessageFilter, message)
IPC_MESSAGE_HANDLER(TtsHostMsg_InitializeVoiceList, OnInitializeVoiceList)
IPC_MESSAGE_HANDLER(TtsHostMsg_Speak, OnSpeak)
IPC_MESSAGE_HANDLER(TtsHostMsg_Pause, OnPause)
IPC_MESSAGE_HANDLER(TtsHostMsg_Resume, OnResume)
IPC_MESSAGE_HANDLER(TtsHostMsg_Cancel, OnCancel)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void TtsMessageFilter::OnChannelClosing() {
base::AutoLock lock(mutex_);
valid_ = false;
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::Bind(&TtsMessageFilter::OnChannelClosingInUIThread, this));
}
bool TtsMessageFilter::Valid() {
base::AutoLock lock(mutex_);
return valid_;
}
void TtsMessageFilter::OnDestruct() const {
{
base::AutoLock lock(mutex_);
valid_ = false;
}
BrowserThread::DeleteOnUIThread::Destruct(this);
}
TtsMessageFilter::~TtsMessageFilter() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
Cleanup();
}
void TtsMessageFilter::OnInitializeVoiceList() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!browser_context_)
return;
content::TtsController* tts_controller =
content::TtsController::GetInstance();
std::vector<content::VoiceData> voices;
tts_controller->GetVoices(browser_context_, &voices);
std::vector<TtsVoice> out_voices;
out_voices.resize(voices.size());
for (size_t i = 0; i < voices.size(); ++i) {
TtsVoice& out_voice = out_voices[i];
out_voice.voice_uri = voices[i].name;
out_voice.name = voices[i].name;
out_voice.lang = voices[i].lang;
out_voice.local_service = !voices[i].remote;
out_voice.is_default = (i == 0);
}
Send(new TtsMsg_SetVoiceList(out_voices));
}
void TtsMessageFilter::OnSpeak(const TtsUtteranceRequest& request) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!browser_context_)
return;
std::unique_ptr<content::TtsUtterance> utterance(
content::TtsUtterance::Create((browser_context_)));
utterance->SetSrcId(request.id);
utterance->SetText(request.text);
utterance->SetLang(request.lang);
utterance->SetVoiceName(request.voice);
utterance->SetCanEnqueue(true);
utterance->SetContinuousParameters(request.rate, request.pitch,
request.volume);
utterance->SetEventDelegate(this);
content::TtsController::GetInstance()->SpeakOrEnqueue(utterance.release());
}
void TtsMessageFilter::OnPause() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
content::TtsController::GetInstance()->Pause();
}
void TtsMessageFilter::OnResume() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
content::TtsController::GetInstance()->Resume();
}
void TtsMessageFilter::OnCancel() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
content::TtsController::GetInstance()->Stop();
}
void TtsMessageFilter::OnTtsEvent(content::TtsUtterance* utterance,
content::TtsEventType event_type,
int char_index,
int length,
const std::string& error_message) {
if (!Valid())
return;
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
switch (event_type) {
case content::TTS_EVENT_START:
Send(new TtsMsg_DidStartSpeaking(utterance->GetSrcId()));
break;
case content::TTS_EVENT_END:
Send(new TtsMsg_DidFinishSpeaking(utterance->GetSrcId()));
break;
case content::TTS_EVENT_WORD:
// We do not send the length here because the IPC message
// does not expect it. A SpeechSynthesisEvent does not currently
// take a length parameter in the TTS event.
// TODO(crbug.com/923556) Support length in the SpeechSynthesis API.
Send(new TtsMsg_WordBoundary(utterance->GetSrcId(), char_index));
break;
case content::TTS_EVENT_SENTENCE:
Send(new TtsMsg_SentenceBoundary(utterance->GetSrcId(), char_index));
break;
case content::TTS_EVENT_MARKER:
Send(new TtsMsg_MarkerEvent(utterance->GetSrcId(), char_index));
break;
case content::TTS_EVENT_INTERRUPTED:
Send(new TtsMsg_WasInterrupted(utterance->GetSrcId()));
break;
case content::TTS_EVENT_CANCELLED:
Send(new TtsMsg_WasCancelled(utterance->GetSrcId()));
break;
case content::TTS_EVENT_ERROR:
Send(new TtsMsg_SpeakingErrorOccurred(utterance->GetSrcId(),
error_message));
break;
case content::TTS_EVENT_PAUSE:
Send(new TtsMsg_DidPauseSpeaking(utterance->GetSrcId()));
break;
case content::TTS_EVENT_RESUME:
Send(new TtsMsg_DidResumeSpeaking(utterance->GetSrcId()));
break;
}
}
void TtsMessageFilter::OnVoicesChanged() {
if (!Valid())
return;
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
OnInitializeVoiceList();
}
void TtsMessageFilter::OnChannelClosingInUIThread() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
Cleanup();
Release(); // Balanced in TtsMessageFilter().
}
void TtsMessageFilter::Cleanup() {
content::TtsController::GetInstance()->RemoveVoicesChangedDelegate(this);
content::TtsController::GetInstance()->RemoveUtteranceEventDelegate(this);
}
void TtsMessageFilter::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
browser_context_ = nullptr;
notification_registrar_.RemoveAll();
}