Bug 1945643 - Update to mozilla-nimbus-schemas 2025.1.1 r=chumphreys
[gecko.git] / dom / media / webspeech / synth / nsSpeechTask.cpp
blob0f637c29c926f1b100bec286ddcb5b0189a500c4
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "AudioChannelService.h"
8 #include "AudioSegment.h"
9 #include "nsSpeechTask.h"
10 #include "nsSynthVoiceRegistry.h"
11 #include "nsXULAppAPI.h"
12 #include "SharedBuffer.h"
13 #include "SpeechSynthesis.h"
14 #include "nsGlobalWindowInner.h"
16 #undef LOG
17 extern mozilla::LogModule* GetSpeechSynthLog();
18 #define LOG(type, msg) MOZ_LOG(GetSpeechSynthLog(), type, msg)
20 #define AUDIO_TRACK 1
22 namespace mozilla::dom {
24 // nsSpeechTask
26 NS_IMPL_CYCLE_COLLECTION_WEAK(nsSpeechTask, mSpeechSynthesis, mUtterance,
27 mCallback)
29 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSpeechTask)
30 NS_INTERFACE_MAP_ENTRY(nsISpeechTask)
31 NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
32 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
33 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTask)
34 NS_INTERFACE_MAP_END
36 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsSpeechTask)
37 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsSpeechTask)
39 nsSpeechTask::nsSpeechTask(SpeechSynthesisUtterance* aUtterance,
40 bool aShouldResistFingerprinting)
41 : mUtterance(aUtterance),
42 mInited(false),
43 mPrePaused(false),
44 mPreCanceled(false),
45 mCallback(nullptr),
46 mShouldResistFingerprinting(aShouldResistFingerprinting),
47 mState(STATE_PENDING) {
48 mText = aUtterance->mText;
49 mVolume = aUtterance->Volume();
52 nsSpeechTask::nsSpeechTask(float aVolume, const nsAString& aText,
53 bool aShouldResistFingerprinting)
54 : mUtterance(nullptr),
55 mVolume(aVolume),
56 mText(aText),
57 mInited(false),
58 mPrePaused(false),
59 mPreCanceled(false),
60 mCallback(nullptr),
61 mShouldResistFingerprinting(aShouldResistFingerprinting),
62 mState(STATE_PENDING) {}
64 nsSpeechTask::~nsSpeechTask() { LOG(LogLevel::Debug, ("~nsSpeechTask")); }
66 void nsSpeechTask::Init() { mInited = true; }
68 void nsSpeechTask::SetChosenVoiceURI(const nsAString& aUri) {
69 mChosenVoiceURI = aUri;
72 NS_IMETHODIMP
73 nsSpeechTask::Setup(nsISpeechTaskCallback* aCallback) {
74 MOZ_ASSERT(XRE_IsParentProcess());
76 LOG(LogLevel::Debug, ("nsSpeechTask::Setup"));
78 mCallback = aCallback;
80 return NS_OK;
83 NS_IMETHODIMP
84 nsSpeechTask::DispatchStart() {
85 nsSynthVoiceRegistry::GetInstance()->SetIsSpeaking(true);
86 return DispatchStartImpl();
89 nsresult nsSpeechTask::DispatchStartImpl() {
90 return DispatchStartImpl(mChosenVoiceURI);
93 nsresult nsSpeechTask::DispatchStartImpl(const nsAString& aUri) {
94 LOG(LogLevel::Debug, ("nsSpeechTask::DispatchStartImpl"));
96 MOZ_ASSERT(mUtterance);
97 if (NS_WARN_IF(mState != STATE_PENDING)) {
98 return NS_ERROR_NOT_AVAILABLE;
101 CreateAudioChannelAgent();
103 mState = STATE_SPEAKING;
104 mUtterance->mChosenVoiceURI = aUri;
105 mUtterance->DispatchSpeechSynthesisEvent(u"start"_ns, 0, nullptr, 0, u""_ns);
107 return NS_OK;
110 NS_IMETHODIMP
111 nsSpeechTask::DispatchEnd(float aElapsedTime, uint32_t aCharIndex) {
112 // After we end, no callback functions should go through.
113 mCallback = nullptr;
115 if (!mPreCanceled) {
116 nsSynthVoiceRegistry::GetInstance()->SpeakNext();
119 return DispatchEndImpl(aElapsedTime, aCharIndex);
122 nsresult nsSpeechTask::DispatchEndImpl(float aElapsedTime,
123 uint32_t aCharIndex) {
124 LOG(LogLevel::Debug, ("nsSpeechTask::DispatchEndImpl"));
126 DestroyAudioChannelAgent();
128 MOZ_ASSERT(mUtterance);
129 if (NS_WARN_IF(mState == STATE_ENDED)) {
130 return NS_ERROR_NOT_AVAILABLE;
133 RefPtr<SpeechSynthesisUtterance> utterance = mUtterance;
135 if (mSpeechSynthesis) {
136 mSpeechSynthesis->OnEnd(this);
139 mState = STATE_ENDED;
140 utterance->DispatchSpeechSynthesisEvent(u"end"_ns, aCharIndex, nullptr,
141 aElapsedTime, u""_ns);
143 return NS_OK;
146 NS_IMETHODIMP
147 nsSpeechTask::DispatchPause(float aElapsedTime, uint32_t aCharIndex) {
148 return DispatchPauseImpl(aElapsedTime, aCharIndex);
151 nsresult nsSpeechTask::DispatchPauseImpl(float aElapsedTime,
152 uint32_t aCharIndex) {
153 LOG(LogLevel::Debug, ("nsSpeechTask::DispatchPauseImpl"));
154 MOZ_ASSERT(mUtterance);
155 if (NS_WARN_IF(mUtterance->mPaused)) {
156 return NS_ERROR_NOT_AVAILABLE;
158 if (NS_WARN_IF(mState == STATE_ENDED)) {
159 return NS_ERROR_NOT_AVAILABLE;
162 mUtterance->mPaused = true;
163 if (mState == STATE_SPEAKING) {
164 mUtterance->DispatchSpeechSynthesisEvent(u"pause"_ns, aCharIndex, nullptr,
165 aElapsedTime, u""_ns);
168 return NS_OK;
171 NS_IMETHODIMP
172 nsSpeechTask::DispatchResume(float aElapsedTime, uint32_t aCharIndex) {
173 return DispatchResumeImpl(aElapsedTime, aCharIndex);
176 nsresult nsSpeechTask::DispatchResumeImpl(float aElapsedTime,
177 uint32_t aCharIndex) {
178 LOG(LogLevel::Debug, ("nsSpeechTask::DispatchResumeImpl"));
179 MOZ_ASSERT(mUtterance);
180 if (NS_WARN_IF(!(mUtterance->mPaused))) {
181 return NS_ERROR_NOT_AVAILABLE;
183 if (NS_WARN_IF(mState == STATE_ENDED)) {
184 return NS_ERROR_NOT_AVAILABLE;
187 mUtterance->mPaused = false;
188 if (mState == STATE_SPEAKING) {
189 mUtterance->DispatchSpeechSynthesisEvent(u"resume"_ns, aCharIndex, nullptr,
190 aElapsedTime, u""_ns);
193 return NS_OK;
196 void nsSpeechTask::ForceError(float aElapsedTime, uint32_t aCharIndex) {
197 DispatchError(aElapsedTime, aCharIndex);
200 NS_IMETHODIMP
201 nsSpeechTask::DispatchError(float aElapsedTime, uint32_t aCharIndex) {
202 if (!mPreCanceled) {
203 nsSynthVoiceRegistry::GetInstance()->SpeakNext();
206 return DispatchErrorImpl(aElapsedTime, aCharIndex);
209 nsresult nsSpeechTask::DispatchErrorImpl(float aElapsedTime,
210 uint32_t aCharIndex) {
211 LOG(LogLevel::Debug, ("nsSpeechTask::DispatchErrorImpl"));
213 DestroyAudioChannelAgent();
215 MOZ_ASSERT(mUtterance);
216 if (NS_WARN_IF(mState == STATE_ENDED)) {
217 return NS_ERROR_NOT_AVAILABLE;
220 if (mSpeechSynthesis) {
221 mSpeechSynthesis->OnEnd(this);
224 mState = STATE_ENDED;
225 mUtterance->DispatchSpeechSynthesisEvent(u"error"_ns, aCharIndex, nullptr,
226 aElapsedTime, u""_ns);
227 return NS_OK;
230 NS_IMETHODIMP
231 nsSpeechTask::DispatchBoundary(const nsAString& aName, float aElapsedTime,
232 uint32_t aCharIndex, uint32_t aCharLength,
233 uint8_t argc) {
234 return DispatchBoundaryImpl(aName, aElapsedTime, aCharIndex, aCharLength,
235 argc);
238 nsresult nsSpeechTask::DispatchBoundaryImpl(const nsAString& aName,
239 float aElapsedTime,
240 uint32_t aCharIndex,
241 uint32_t aCharLength,
242 uint8_t argc) {
243 MOZ_ASSERT(mUtterance);
244 if (NS_WARN_IF(mState != STATE_SPEAKING)) {
245 return NS_ERROR_NOT_AVAILABLE;
247 mUtterance->DispatchSpeechSynthesisEvent(
248 u"boundary"_ns, aCharIndex,
249 argc ? static_cast<Nullable<uint32_t> >(aCharLength) : nullptr,
250 aElapsedTime, aName);
252 return NS_OK;
255 NS_IMETHODIMP
256 nsSpeechTask::DispatchMark(const nsAString& aName, float aElapsedTime,
257 uint32_t aCharIndex) {
258 return DispatchMarkImpl(aName, aElapsedTime, aCharIndex);
261 nsresult nsSpeechTask::DispatchMarkImpl(const nsAString& aName,
262 float aElapsedTime,
263 uint32_t aCharIndex) {
264 MOZ_ASSERT(mUtterance);
265 if (NS_WARN_IF(mState != STATE_SPEAKING)) {
266 return NS_ERROR_NOT_AVAILABLE;
268 mUtterance->DispatchSpeechSynthesisEvent(u"mark"_ns, aCharIndex, nullptr,
269 aElapsedTime, aName);
270 return NS_OK;
273 void nsSpeechTask::Pause() {
274 MOZ_ASSERT(XRE_IsParentProcess());
276 if (mCallback) {
277 DebugOnly<nsresult> rv = mCallback->OnPause();
278 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to call onPause() callback");
281 if (!mInited) {
282 mPrePaused = true;
286 void nsSpeechTask::Resume() {
287 MOZ_ASSERT(XRE_IsParentProcess());
289 if (mCallback) {
290 DebugOnly<nsresult> rv = mCallback->OnResume();
291 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
292 "Unable to call onResume() callback");
295 if (mPrePaused) {
296 mPrePaused = false;
297 nsSynthVoiceRegistry::GetInstance()->ResumeQueue();
301 void nsSpeechTask::Cancel() {
302 MOZ_ASSERT(XRE_IsParentProcess());
304 LOG(LogLevel::Debug, ("nsSpeechTask::Cancel"));
306 if (mCallback) {
307 DebugOnly<nsresult> rv = mCallback->OnCancel();
308 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
309 "Unable to call onCancel() callback");
312 if (!mInited) {
313 mPreCanceled = true;
317 void nsSpeechTask::ForceEnd() {
318 if (!mInited) {
319 mPreCanceled = true;
322 DispatchEnd(0, 0);
325 void nsSpeechTask::SetSpeechSynthesis(SpeechSynthesis* aSpeechSynthesis) {
326 mSpeechSynthesis = aSpeechSynthesis;
329 void nsSpeechTask::CreateAudioChannelAgent() {
330 if (!mUtterance) {
331 return;
334 if (mAudioChannelAgent) {
335 mAudioChannelAgent->NotifyStoppedPlaying();
338 mAudioChannelAgent = new AudioChannelAgent();
339 mAudioChannelAgent->InitWithWeakCallback(mUtterance->GetOwnerWindow(), this);
341 nsresult rv = mAudioChannelAgent->NotifyStartedPlaying(
342 AudioChannelService::AudibleState::eAudible);
343 if (NS_WARN_IF(NS_FAILED(rv))) {
344 return;
347 mAudioChannelAgent->PullInitialUpdate();
350 void nsSpeechTask::DestroyAudioChannelAgent() {
351 if (mAudioChannelAgent) {
352 mAudioChannelAgent->NotifyStoppedPlaying();
353 mAudioChannelAgent = nullptr;
357 NS_IMETHODIMP
358 nsSpeechTask::WindowVolumeChanged(float aVolume, bool aMuted) {
359 SetAudioOutputVolume(aMuted ? 0.0 : mVolume * aVolume);
360 return NS_OK;
363 NS_IMETHODIMP
364 nsSpeechTask::WindowSuspendChanged(nsSuspendedTypes aSuspend) {
365 if (!mUtterance) {
366 return NS_OK;
369 if (aSuspend == nsISuspendedTypes::NONE_SUSPENDED && mUtterance->mPaused) {
370 Resume();
371 } else if (aSuspend != nsISuspendedTypes::NONE_SUSPENDED &&
372 !mUtterance->mPaused) {
373 Pause();
375 return NS_OK;
378 NS_IMETHODIMP
379 nsSpeechTask::WindowAudioCaptureChanged(bool aCapture) {
380 // This is not supported yet.
381 return NS_OK;
384 void nsSpeechTask::SetAudioOutputVolume(float aVolume) {
385 if (mCallback) {
386 mCallback->OnVolumeChanged(aVolume);
390 } // namespace mozilla::dom