390 lines
10 KiB
C++
390 lines
10 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "AudioChannelService.h"
|
|
#include "AudioSegment.h"
|
|
#include "nsSpeechTask.h"
|
|
#include "nsSynthVoiceRegistry.h"
|
|
#include "nsXULAppAPI.h"
|
|
#include "SharedBuffer.h"
|
|
#include "SpeechSynthesis.h"
|
|
#include "nsGlobalWindowInner.h"
|
|
|
|
#undef LOG
|
|
extern mozilla::LogModule* GetSpeechSynthLog();
|
|
#define LOG(type, msg) MOZ_LOG(GetSpeechSynthLog(), type, msg)
|
|
|
|
#define AUDIO_TRACK 1
|
|
|
|
namespace mozilla::dom {
|
|
|
|
// nsSpeechTask
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_WEAK(nsSpeechTask, mSpeechSynthesis, mUtterance,
|
|
mCallback)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSpeechTask)
|
|
NS_INTERFACE_MAP_ENTRY(nsISpeechTask)
|
|
NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTask)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsSpeechTask)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsSpeechTask)
|
|
|
|
nsSpeechTask::nsSpeechTask(SpeechSynthesisUtterance* aUtterance,
|
|
bool aShouldResistFingerprinting)
|
|
: mUtterance(aUtterance),
|
|
mInited(false),
|
|
mPrePaused(false),
|
|
mPreCanceled(false),
|
|
mCallback(nullptr),
|
|
mShouldResistFingerprinting(aShouldResistFingerprinting),
|
|
mState(STATE_PENDING) {
|
|
mText = aUtterance->mText;
|
|
mVolume = aUtterance->Volume();
|
|
}
|
|
|
|
nsSpeechTask::nsSpeechTask(float aVolume, const nsAString& aText,
|
|
bool aShouldResistFingerprinting)
|
|
: mUtterance(nullptr),
|
|
mVolume(aVolume),
|
|
mText(aText),
|
|
mInited(false),
|
|
mPrePaused(false),
|
|
mPreCanceled(false),
|
|
mCallback(nullptr),
|
|
mShouldResistFingerprinting(aShouldResistFingerprinting),
|
|
mState(STATE_PENDING) {}
|
|
|
|
nsSpeechTask::~nsSpeechTask() { LOG(LogLevel::Debug, ("~nsSpeechTask")); }
|
|
|
|
void nsSpeechTask::Init() { mInited = true; }
|
|
|
|
void nsSpeechTask::SetChosenVoiceURI(const nsAString& aUri) {
|
|
mChosenVoiceURI = aUri;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSpeechTask::Setup(nsISpeechTaskCallback* aCallback) {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
LOG(LogLevel::Debug, ("nsSpeechTask::Setup"));
|
|
|
|
mCallback = aCallback;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSpeechTask::DispatchStart() {
|
|
nsSynthVoiceRegistry::GetInstance()->SetIsSpeaking(true);
|
|
return DispatchStartImpl();
|
|
}
|
|
|
|
nsresult nsSpeechTask::DispatchStartImpl() {
|
|
return DispatchStartImpl(mChosenVoiceURI);
|
|
}
|
|
|
|
nsresult nsSpeechTask::DispatchStartImpl(const nsAString& aUri) {
|
|
LOG(LogLevel::Debug, ("nsSpeechTask::DispatchStartImpl"));
|
|
|
|
MOZ_ASSERT(mUtterance);
|
|
if (NS_WARN_IF(mState != STATE_PENDING)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
CreateAudioChannelAgent();
|
|
|
|
mState = STATE_SPEAKING;
|
|
mUtterance->mChosenVoiceURI = aUri;
|
|
mUtterance->DispatchSpeechSynthesisEvent(u"start"_ns, 0, nullptr, 0, u""_ns);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSpeechTask::DispatchEnd(float aElapsedTime, uint32_t aCharIndex) {
|
|
// After we end, no callback functions should go through.
|
|
mCallback = nullptr;
|
|
|
|
if (!mPreCanceled) {
|
|
nsSynthVoiceRegistry::GetInstance()->SpeakNext();
|
|
}
|
|
|
|
return DispatchEndImpl(aElapsedTime, aCharIndex);
|
|
}
|
|
|
|
nsresult nsSpeechTask::DispatchEndImpl(float aElapsedTime,
|
|
uint32_t aCharIndex) {
|
|
LOG(LogLevel::Debug, ("nsSpeechTask::DispatchEndImpl"));
|
|
|
|
DestroyAudioChannelAgent();
|
|
|
|
MOZ_ASSERT(mUtterance);
|
|
if (NS_WARN_IF(mState == STATE_ENDED)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
RefPtr<SpeechSynthesisUtterance> utterance = mUtterance;
|
|
|
|
if (mSpeechSynthesis) {
|
|
mSpeechSynthesis->OnEnd(this);
|
|
}
|
|
|
|
mState = STATE_ENDED;
|
|
utterance->DispatchSpeechSynthesisEvent(u"end"_ns, aCharIndex, nullptr,
|
|
aElapsedTime, u""_ns);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSpeechTask::DispatchPause(float aElapsedTime, uint32_t aCharIndex) {
|
|
return DispatchPauseImpl(aElapsedTime, aCharIndex);
|
|
}
|
|
|
|
nsresult nsSpeechTask::DispatchPauseImpl(float aElapsedTime,
|
|
uint32_t aCharIndex) {
|
|
LOG(LogLevel::Debug, ("nsSpeechTask::DispatchPauseImpl"));
|
|
MOZ_ASSERT(mUtterance);
|
|
if (NS_WARN_IF(mUtterance->mPaused)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
if (NS_WARN_IF(mState == STATE_ENDED)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
mUtterance->mPaused = true;
|
|
if (mState == STATE_SPEAKING) {
|
|
mUtterance->DispatchSpeechSynthesisEvent(u"pause"_ns, aCharIndex, nullptr,
|
|
aElapsedTime, u""_ns);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSpeechTask::DispatchResume(float aElapsedTime, uint32_t aCharIndex) {
|
|
return DispatchResumeImpl(aElapsedTime, aCharIndex);
|
|
}
|
|
|
|
nsresult nsSpeechTask::DispatchResumeImpl(float aElapsedTime,
|
|
uint32_t aCharIndex) {
|
|
LOG(LogLevel::Debug, ("nsSpeechTask::DispatchResumeImpl"));
|
|
MOZ_ASSERT(mUtterance);
|
|
if (NS_WARN_IF(!(mUtterance->mPaused))) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
if (NS_WARN_IF(mState == STATE_ENDED)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
mUtterance->mPaused = false;
|
|
if (mState == STATE_SPEAKING) {
|
|
mUtterance->DispatchSpeechSynthesisEvent(u"resume"_ns, aCharIndex, nullptr,
|
|
aElapsedTime, u""_ns);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsSpeechTask::ForceError(float aElapsedTime, uint32_t aCharIndex) {
|
|
DispatchError(aElapsedTime, aCharIndex);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSpeechTask::DispatchError(float aElapsedTime, uint32_t aCharIndex) {
|
|
if (!mPreCanceled) {
|
|
nsSynthVoiceRegistry::GetInstance()->SpeakNext();
|
|
}
|
|
|
|
return DispatchErrorImpl(aElapsedTime, aCharIndex);
|
|
}
|
|
|
|
nsresult nsSpeechTask::DispatchErrorImpl(float aElapsedTime,
|
|
uint32_t aCharIndex) {
|
|
LOG(LogLevel::Debug, ("nsSpeechTask::DispatchErrorImpl"));
|
|
|
|
DestroyAudioChannelAgent();
|
|
|
|
MOZ_ASSERT(mUtterance);
|
|
if (NS_WARN_IF(mState == STATE_ENDED)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (mSpeechSynthesis) {
|
|
mSpeechSynthesis->OnEnd(this);
|
|
}
|
|
|
|
mState = STATE_ENDED;
|
|
mUtterance->DispatchSpeechSynthesisEvent(u"error"_ns, aCharIndex, nullptr,
|
|
aElapsedTime, u""_ns);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSpeechTask::DispatchBoundary(const nsAString& aName, float aElapsedTime,
|
|
uint32_t aCharIndex, uint32_t aCharLength,
|
|
uint8_t argc) {
|
|
return DispatchBoundaryImpl(aName, aElapsedTime, aCharIndex, aCharLength,
|
|
argc);
|
|
}
|
|
|
|
nsresult nsSpeechTask::DispatchBoundaryImpl(const nsAString& aName,
|
|
float aElapsedTime,
|
|
uint32_t aCharIndex,
|
|
uint32_t aCharLength,
|
|
uint8_t argc) {
|
|
MOZ_ASSERT(mUtterance);
|
|
if (NS_WARN_IF(mState != STATE_SPEAKING)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
mUtterance->DispatchSpeechSynthesisEvent(
|
|
u"boundary"_ns, aCharIndex,
|
|
argc ? static_cast<Nullable<uint32_t> >(aCharLength) : nullptr,
|
|
aElapsedTime, aName);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSpeechTask::DispatchMark(const nsAString& aName, float aElapsedTime,
|
|
uint32_t aCharIndex) {
|
|
return DispatchMarkImpl(aName, aElapsedTime, aCharIndex);
|
|
}
|
|
|
|
nsresult nsSpeechTask::DispatchMarkImpl(const nsAString& aName,
|
|
float aElapsedTime,
|
|
uint32_t aCharIndex) {
|
|
MOZ_ASSERT(mUtterance);
|
|
if (NS_WARN_IF(mState != STATE_SPEAKING)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
mUtterance->DispatchSpeechSynthesisEvent(u"mark"_ns, aCharIndex, nullptr,
|
|
aElapsedTime, aName);
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsSpeechTask::Pause() {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
if (mCallback) {
|
|
DebugOnly<nsresult> rv = mCallback->OnPause();
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to call onPause() callback");
|
|
}
|
|
|
|
if (!mInited) {
|
|
mPrePaused = true;
|
|
}
|
|
}
|
|
|
|
void nsSpeechTask::Resume() {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
if (mCallback) {
|
|
DebugOnly<nsresult> rv = mCallback->OnResume();
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"Unable to call onResume() callback");
|
|
}
|
|
|
|
if (mPrePaused) {
|
|
mPrePaused = false;
|
|
nsSynthVoiceRegistry::GetInstance()->ResumeQueue();
|
|
}
|
|
}
|
|
|
|
void nsSpeechTask::Cancel() {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
LOG(LogLevel::Debug, ("nsSpeechTask::Cancel"));
|
|
|
|
if (mCallback) {
|
|
DebugOnly<nsresult> rv = mCallback->OnCancel();
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"Unable to call onCancel() callback");
|
|
}
|
|
|
|
if (!mInited) {
|
|
mPreCanceled = true;
|
|
}
|
|
}
|
|
|
|
void nsSpeechTask::ForceEnd() {
|
|
if (!mInited) {
|
|
mPreCanceled = true;
|
|
}
|
|
|
|
DispatchEnd(0, 0);
|
|
}
|
|
|
|
void nsSpeechTask::SetSpeechSynthesis(SpeechSynthesis* aSpeechSynthesis) {
|
|
mSpeechSynthesis = aSpeechSynthesis;
|
|
}
|
|
|
|
void nsSpeechTask::CreateAudioChannelAgent() {
|
|
if (!mUtterance) {
|
|
return;
|
|
}
|
|
|
|
if (mAudioChannelAgent) {
|
|
mAudioChannelAgent->NotifyStoppedPlaying();
|
|
}
|
|
|
|
mAudioChannelAgent = new AudioChannelAgent();
|
|
mAudioChannelAgent->InitWithWeakCallback(mUtterance->GetOwnerWindow(), this);
|
|
|
|
nsresult rv = mAudioChannelAgent->NotifyStartedPlaying(
|
|
AudioChannelService::AudibleState::eAudible);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
mAudioChannelAgent->PullInitialUpdate();
|
|
}
|
|
|
|
void nsSpeechTask::DestroyAudioChannelAgent() {
|
|
if (mAudioChannelAgent) {
|
|
mAudioChannelAgent->NotifyStoppedPlaying();
|
|
mAudioChannelAgent = nullptr;
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSpeechTask::WindowVolumeChanged(float aVolume, bool aMuted) {
|
|
SetAudioOutputVolume(aMuted ? 0.0 : mVolume * aVolume);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSpeechTask::WindowSuspendChanged(nsSuspendedTypes aSuspend) {
|
|
if (!mUtterance) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (aSuspend == nsISuspendedTypes::NONE_SUSPENDED && mUtterance->mPaused) {
|
|
Resume();
|
|
} else if (aSuspend != nsISuspendedTypes::NONE_SUSPENDED &&
|
|
!mUtterance->mPaused) {
|
|
Pause();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSpeechTask::WindowAudioCaptureChanged(bool aCapture) {
|
|
// This is not supported yet.
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsSpeechTask::SetAudioOutputVolume(float aVolume) {
|
|
if (mCallback) {
|
|
mCallback->OnVolumeChanged(aVolume);
|
|
}
|
|
}
|
|
|
|
} // namespace mozilla::dom
|