Line data Source code
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/. */
6 :
7 : #include "AudioChannelAgent.h"
8 : #include "AudioChannelService.h"
9 : #include "AudioSegment.h"
10 : #include "MediaStreamListener.h"
11 : #include "nsSpeechTask.h"
12 : #include "nsSynthVoiceRegistry.h"
13 : #include "SharedBuffer.h"
14 : #include "SpeechSynthesis.h"
15 :
16 : // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
17 : // GetTickCount() and conflicts with nsSpeechTask::GetCurrentTime().
18 : #ifdef GetCurrentTime
19 : #undef GetCurrentTime
20 : #endif
21 :
22 : #undef LOG
23 : extern mozilla::LogModule* GetSpeechSynthLog();
24 : #define LOG(type, msg) MOZ_LOG(GetSpeechSynthLog(), type, msg)
25 :
26 : #define AUDIO_TRACK 1
27 :
28 : namespace mozilla {
29 : namespace dom {
30 :
31 0 : class SynthStreamListener : public MediaStreamListener
32 : {
33 : public:
34 0 : SynthStreamListener(nsSpeechTask* aSpeechTask,
35 : MediaStream* aStream,
36 : AbstractThread* aMainThread)
37 0 : : mSpeechTask(aSpeechTask)
38 : , mStream(aStream)
39 0 : , mStarted(false)
40 : {
41 0 : }
42 :
43 0 : void DoNotifyStarted()
44 : {
45 0 : if (mSpeechTask) {
46 0 : mSpeechTask->DispatchStartInner();
47 : }
48 0 : }
49 :
50 0 : void DoNotifyFinished()
51 : {
52 0 : if (mSpeechTask) {
53 0 : mSpeechTask->DispatchEndInner(mSpeechTask->GetCurrentTime(),
54 0 : mSpeechTask->GetCurrentCharOffset());
55 : }
56 0 : }
57 :
58 0 : void NotifyEvent(MediaStreamGraph* aGraph,
59 : MediaStreamGraphEvent event) override
60 : {
61 0 : switch (event) {
62 : case MediaStreamGraphEvent::EVENT_FINISHED:
63 : {
64 0 : if (!mStarted) {
65 0 : mStarted = true;
66 0 : aGraph->DispatchToMainThreadAfterStreamStateUpdate(
67 0 : NewRunnableMethod("dom::SynthStreamListener::DoNotifyStarted",
68 : this,
69 0 : &SynthStreamListener::DoNotifyStarted));
70 : }
71 :
72 0 : aGraph->DispatchToMainThreadAfterStreamStateUpdate(
73 0 : NewRunnableMethod("dom::SynthStreamListener::DoNotifyFinished",
74 : this,
75 0 : &SynthStreamListener::DoNotifyFinished));
76 : }
77 0 : break;
78 : case MediaStreamGraphEvent::EVENT_REMOVED:
79 0 : mSpeechTask = nullptr;
80 : // Dereference MediaStream to destroy safety
81 0 : mStream = nullptr;
82 0 : break;
83 : default:
84 0 : break;
85 : }
86 0 : }
87 :
88 0 : void NotifyBlockingChanged(MediaStreamGraph* aGraph, Blocking aBlocked) override
89 : {
90 0 : if (aBlocked == MediaStreamListener::UNBLOCKED && !mStarted) {
91 0 : mStarted = true;
92 0 : aGraph->DispatchToMainThreadAfterStreamStateUpdate(
93 0 : NewRunnableMethod("dom::SynthStreamListener::DoNotifyStarted",
94 : this,
95 0 : &SynthStreamListener::DoNotifyStarted));
96 : }
97 0 : }
98 :
99 : private:
100 : // Raw pointer; if we exist, the stream exists,
101 : // and 'mSpeechTask' exclusively owns it and therefor exists as well.
102 : nsSpeechTask* mSpeechTask;
103 : // This is KungFuDeathGrip for MediaStream
104 : RefPtr<MediaStream> mStream;
105 :
106 : bool mStarted;
107 : };
108 :
109 : // nsSpeechTask
110 :
111 0 : NS_IMPL_CYCLE_COLLECTION(nsSpeechTask, mSpeechSynthesis, mUtterance, mCallback);
112 :
113 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSpeechTask)
114 0 : NS_INTERFACE_MAP_ENTRY(nsISpeechTask)
115 0 : NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
116 0 : NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
117 0 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTask)
118 0 : NS_INTERFACE_MAP_END
119 :
120 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsSpeechTask)
121 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(nsSpeechTask)
122 :
123 0 : nsSpeechTask::nsSpeechTask(SpeechSynthesisUtterance* aUtterance)
124 : : mUtterance(aUtterance)
125 : , mInited(false)
126 : , mPrePaused(false)
127 : , mPreCanceled(false)
128 : , mCallback(nullptr)
129 0 : , mIndirectAudio(false)
130 : {
131 0 : mText = aUtterance->mText;
132 0 : mVolume = aUtterance->Volume();
133 0 : }
134 :
135 0 : nsSpeechTask::nsSpeechTask(float aVolume, const nsAString& aText)
136 : : mUtterance(nullptr)
137 : , mVolume(aVolume)
138 : , mText(aText)
139 : , mInited(false)
140 : , mPrePaused(false)
141 : , mPreCanceled(false)
142 : , mCallback(nullptr)
143 0 : , mIndirectAudio(false)
144 : {
145 0 : }
146 :
147 0 : nsSpeechTask::~nsSpeechTask()
148 : {
149 0 : LOG(LogLevel::Debug, ("~nsSpeechTask"));
150 0 : if (mStream) {
151 0 : if (!mStream->IsDestroyed()) {
152 0 : mStream->Destroy();
153 : }
154 :
155 : // This will finally destroyed by SynthStreamListener becasue
156 : // MediaStream::Destroy() is async.
157 0 : mStream = nullptr;
158 : }
159 :
160 0 : if (mPort) {
161 0 : mPort->Destroy();
162 0 : mPort = nullptr;
163 : }
164 0 : }
165 :
166 : void
167 0 : nsSpeechTask::InitDirectAudio()
168 : {
169 : // nullptr as final argument here means that this is not tied to a window.
170 : // This is a global MSG.
171 : mStream = MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
172 : AudioChannel::Normal, nullptr)->
173 0 : CreateSourceStream();
174 0 : mIndirectAudio = false;
175 0 : mInited = true;
176 0 : }
177 :
178 : void
179 0 : nsSpeechTask::InitIndirectAudio()
180 : {
181 0 : mIndirectAudio = true;
182 0 : mInited = true;
183 0 : }
184 :
185 : void
186 0 : nsSpeechTask::SetChosenVoiceURI(const nsAString& aUri)
187 : {
188 0 : mChosenVoiceURI = aUri;
189 0 : }
190 :
191 : NS_IMETHODIMP
192 0 : nsSpeechTask::Setup(nsISpeechTaskCallback* aCallback,
193 : uint32_t aChannels, uint32_t aRate, uint8_t argc)
194 : {
195 0 : MOZ_ASSERT(XRE_IsParentProcess());
196 :
197 0 : LOG(LogLevel::Debug, ("nsSpeechTask::Setup"));
198 :
199 0 : mCallback = aCallback;
200 :
201 0 : if (mIndirectAudio) {
202 0 : MOZ_ASSERT(!mStream);
203 0 : if (argc > 0) {
204 0 : NS_WARNING("Audio info arguments in Setup() are ignored for indirect audio services.");
205 : }
206 0 : return NS_OK;
207 : }
208 :
209 : // mStream is set up in Init() that should be called before this.
210 0 : MOZ_ASSERT(mStream);
211 :
212 0 : mStream->AddListener(
213 : // Non DocGroup-version of AbstractThread::MainThread for the task in parent.
214 0 : new SynthStreamListener(this, mStream, AbstractThread::MainThread()));
215 :
216 : // XXX: Support more than one channel
217 0 : if(NS_WARN_IF(!(aChannels == 1))) {
218 0 : return NS_ERROR_FAILURE;
219 : }
220 :
221 0 : mChannels = aChannels;
222 :
223 0 : AudioSegment* segment = new AudioSegment();
224 0 : mStream->AddAudioTrack(AUDIO_TRACK, aRate, 0, segment);
225 0 : mStream->AddAudioOutput(this);
226 0 : mStream->SetAudioOutputVolume(this, mVolume);
227 :
228 0 : return NS_OK;
229 : }
230 :
231 : static RefPtr<mozilla::SharedBuffer>
232 0 : makeSamples(int16_t* aData, uint32_t aDataLen)
233 : {
234 : RefPtr<mozilla::SharedBuffer> samples =
235 0 : SharedBuffer::Create(aDataLen * sizeof(int16_t));
236 0 : int16_t* frames = static_cast<int16_t*>(samples->Data());
237 :
238 0 : for (uint32_t i = 0; i < aDataLen; i++) {
239 0 : frames[i] = aData[i];
240 : }
241 :
242 0 : return samples;
243 : }
244 :
245 : NS_IMETHODIMP
246 0 : nsSpeechTask::SendAudio(JS::Handle<JS::Value> aData, JS::Handle<JS::Value> aLandmarks,
247 : JSContext* aCx)
248 : {
249 0 : MOZ_ASSERT(XRE_IsParentProcess());
250 :
251 0 : if(NS_WARN_IF(!(mStream))) {
252 0 : return NS_ERROR_NOT_AVAILABLE;
253 : }
254 0 : if(NS_WARN_IF(mStream->IsDestroyed())) {
255 0 : return NS_ERROR_NOT_AVAILABLE;
256 : }
257 0 : if(NS_WARN_IF(!(mChannels))) {
258 0 : return NS_ERROR_FAILURE;
259 : }
260 0 : if(NS_WARN_IF(!(aData.isObject()))) {
261 0 : return NS_ERROR_INVALID_ARG;
262 : }
263 :
264 0 : if (mIndirectAudio) {
265 0 : NS_WARNING("Can't call SendAudio from an indirect audio speech service.");
266 0 : return NS_ERROR_FAILURE;
267 : }
268 :
269 0 : JS::Rooted<JSObject*> darray(aCx, &aData.toObject());
270 0 : JSAutoCompartment ac(aCx, darray);
271 :
272 0 : JS::Rooted<JSObject*> tsrc(aCx, nullptr);
273 :
274 : // Allow either Int16Array or plain JS Array
275 0 : if (JS_IsInt16Array(darray)) {
276 0 : tsrc = darray;
277 : } else {
278 : bool isArray;
279 0 : if (!JS_IsArrayObject(aCx, darray, &isArray)) {
280 0 : return NS_ERROR_UNEXPECTED;
281 : }
282 0 : if (isArray) {
283 0 : tsrc = JS_NewInt16ArrayFromArray(aCx, darray);
284 : }
285 : }
286 :
287 0 : if (!tsrc) {
288 0 : return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
289 : }
290 :
291 0 : uint32_t dataLen = JS_GetTypedArrayLength(tsrc);
292 0 : RefPtr<mozilla::SharedBuffer> samples;
293 : {
294 0 : JS::AutoCheckCannotGC nogc;
295 : bool isShared;
296 0 : int16_t* data = JS_GetInt16ArrayData(tsrc, &isShared, nogc);
297 0 : if (isShared) {
298 : // Must opt in to using shared data.
299 0 : return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
300 : }
301 0 : samples = makeSamples(data, dataLen);
302 : }
303 0 : SendAudioImpl(samples, dataLen);
304 :
305 0 : return NS_OK;
306 : }
307 :
308 : NS_IMETHODIMP
309 0 : nsSpeechTask::SendAudioNative(int16_t* aData, uint32_t aDataLen)
310 : {
311 0 : MOZ_ASSERT(XRE_IsParentProcess());
312 :
313 0 : if(NS_WARN_IF(!(mStream))) {
314 0 : return NS_ERROR_NOT_AVAILABLE;
315 : }
316 0 : if(NS_WARN_IF(mStream->IsDestroyed())) {
317 0 : return NS_ERROR_NOT_AVAILABLE;
318 : }
319 0 : if(NS_WARN_IF(!(mChannels))) {
320 0 : return NS_ERROR_FAILURE;
321 : }
322 :
323 0 : if (mIndirectAudio) {
324 0 : NS_WARNING("Can't call SendAudio from an indirect audio speech service.");
325 0 : return NS_ERROR_FAILURE;
326 : }
327 :
328 0 : RefPtr<mozilla::SharedBuffer> samples = makeSamples(aData, aDataLen);
329 0 : SendAudioImpl(samples, aDataLen);
330 :
331 0 : return NS_OK;
332 : }
333 :
334 : void
335 0 : nsSpeechTask::SendAudioImpl(RefPtr<mozilla::SharedBuffer>& aSamples, uint32_t aDataLen)
336 : {
337 0 : if (aDataLen == 0) {
338 0 : mStream->EndAllTrackAndFinish();
339 0 : return;
340 : }
341 :
342 0 : AudioSegment segment;
343 0 : AutoTArray<const int16_t*, 1> channelData;
344 0 : channelData.AppendElement(static_cast<int16_t*>(aSamples->Data()));
345 0 : segment.AppendFrames(aSamples.forget(), channelData, aDataLen,
346 0 : PRINCIPAL_HANDLE_NONE);
347 0 : mStream->AppendToTrack(1, &segment);
348 0 : mStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
349 : }
350 :
351 : NS_IMETHODIMP
352 0 : nsSpeechTask::DispatchStart()
353 : {
354 0 : if (!mIndirectAudio) {
355 0 : NS_WARNING("Can't call DispatchStart() from a direct audio speech service");
356 0 : return NS_ERROR_FAILURE;
357 : }
358 :
359 0 : return DispatchStartInner();
360 : }
361 :
362 : nsresult
363 0 : nsSpeechTask::DispatchStartInner()
364 : {
365 0 : nsSynthVoiceRegistry::GetInstance()->SetIsSpeaking(true);
366 0 : return DispatchStartImpl();
367 : }
368 :
369 : nsresult
370 0 : nsSpeechTask::DispatchStartImpl()
371 : {
372 0 : return DispatchStartImpl(mChosenVoiceURI);
373 : }
374 :
375 : nsresult
376 0 : nsSpeechTask::DispatchStartImpl(const nsAString& aUri)
377 : {
378 0 : LOG(LogLevel::Debug, ("nsSpeechTask::DispatchStart"));
379 :
380 0 : MOZ_ASSERT(mUtterance);
381 0 : if(NS_WARN_IF(!(mUtterance->mState == SpeechSynthesisUtterance::STATE_PENDING))) {
382 0 : return NS_ERROR_NOT_AVAILABLE;
383 : }
384 :
385 0 : CreateAudioChannelAgent();
386 :
387 0 : mUtterance->mState = SpeechSynthesisUtterance::STATE_SPEAKING;
388 0 : mUtterance->mChosenVoiceURI = aUri;
389 0 : mUtterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("start"), 0,
390 0 : nullptr, 0, EmptyString());
391 :
392 0 : return NS_OK;
393 : }
394 :
395 : NS_IMETHODIMP
396 0 : nsSpeechTask::DispatchEnd(float aElapsedTime, uint32_t aCharIndex)
397 : {
398 0 : if (!mIndirectAudio) {
399 0 : NS_WARNING("Can't call DispatchEnd() from a direct audio speech service");
400 0 : return NS_ERROR_FAILURE;
401 : }
402 :
403 0 : return DispatchEndInner(aElapsedTime, aCharIndex);
404 : }
405 :
406 : nsresult
407 0 : nsSpeechTask::DispatchEndInner(float aElapsedTime, uint32_t aCharIndex)
408 : {
409 0 : if (!mPreCanceled) {
410 0 : nsSynthVoiceRegistry::GetInstance()->SpeakNext();
411 : }
412 :
413 0 : return DispatchEndImpl(aElapsedTime, aCharIndex);
414 : }
415 :
416 : nsresult
417 0 : nsSpeechTask::DispatchEndImpl(float aElapsedTime, uint32_t aCharIndex)
418 : {
419 0 : LOG(LogLevel::Debug, ("nsSpeechTask::DispatchEnd\n"));
420 :
421 0 : DestroyAudioChannelAgent();
422 :
423 0 : MOZ_ASSERT(mUtterance);
424 0 : if(NS_WARN_IF(mUtterance->mState == SpeechSynthesisUtterance::STATE_ENDED)) {
425 0 : return NS_ERROR_NOT_AVAILABLE;
426 : }
427 :
428 : // XXX: This should not be here, but it prevents a crash in MSG.
429 0 : if (mStream) {
430 0 : mStream->Destroy();
431 : }
432 :
433 0 : RefPtr<SpeechSynthesisUtterance> utterance = mUtterance;
434 :
435 0 : if (mSpeechSynthesis) {
436 0 : mSpeechSynthesis->OnEnd(this);
437 : }
438 :
439 0 : if (utterance->mState == SpeechSynthesisUtterance::STATE_PENDING) {
440 0 : utterance->mState = SpeechSynthesisUtterance::STATE_NONE;
441 : } else {
442 0 : utterance->mState = SpeechSynthesisUtterance::STATE_ENDED;
443 0 : utterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("end"),
444 : aCharIndex, nullptr, aElapsedTime,
445 0 : EmptyString());
446 : }
447 :
448 0 : return NS_OK;
449 : }
450 :
451 : NS_IMETHODIMP
452 0 : nsSpeechTask::DispatchPause(float aElapsedTime, uint32_t aCharIndex)
453 : {
454 0 : if (!mIndirectAudio) {
455 0 : NS_WARNING("Can't call DispatchPause() from a direct audio speech service");
456 0 : return NS_ERROR_FAILURE;
457 : }
458 :
459 0 : return DispatchPauseImpl(aElapsedTime, aCharIndex);
460 : }
461 :
462 : nsresult
463 0 : nsSpeechTask::DispatchPauseImpl(float aElapsedTime, uint32_t aCharIndex)
464 : {
465 0 : LOG(LogLevel::Debug, ("nsSpeechTask::DispatchPause"));
466 0 : MOZ_ASSERT(mUtterance);
467 0 : if(NS_WARN_IF(mUtterance->mPaused)) {
468 0 : return NS_ERROR_NOT_AVAILABLE;
469 : }
470 0 : if(NS_WARN_IF(mUtterance->mState == SpeechSynthesisUtterance::STATE_ENDED)) {
471 0 : return NS_ERROR_NOT_AVAILABLE;
472 : }
473 :
474 0 : mUtterance->mPaused = true;
475 0 : if (mUtterance->mState == SpeechSynthesisUtterance::STATE_SPEAKING) {
476 0 : mUtterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("pause"),
477 : aCharIndex, nullptr, aElapsedTime,
478 0 : EmptyString());
479 : }
480 0 : return NS_OK;
481 : }
482 :
483 : NS_IMETHODIMP
484 0 : nsSpeechTask::DispatchResume(float aElapsedTime, uint32_t aCharIndex)
485 : {
486 0 : if (!mIndirectAudio) {
487 0 : NS_WARNING("Can't call DispatchResume() from a direct audio speech service");
488 0 : return NS_ERROR_FAILURE;
489 : }
490 :
491 0 : return DispatchResumeImpl(aElapsedTime, aCharIndex);
492 : }
493 :
494 : nsresult
495 0 : nsSpeechTask::DispatchResumeImpl(float aElapsedTime, uint32_t aCharIndex)
496 : {
497 0 : LOG(LogLevel::Debug, ("nsSpeechTask::DispatchResume"));
498 0 : MOZ_ASSERT(mUtterance);
499 0 : if(NS_WARN_IF(!(mUtterance->mPaused))) {
500 0 : return NS_ERROR_NOT_AVAILABLE;
501 : }
502 0 : if(NS_WARN_IF(mUtterance->mState == SpeechSynthesisUtterance::STATE_ENDED)) {
503 0 : return NS_ERROR_NOT_AVAILABLE;
504 : }
505 :
506 0 : mUtterance->mPaused = false;
507 0 : if (mUtterance->mState == SpeechSynthesisUtterance::STATE_SPEAKING) {
508 0 : mUtterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("resume"),
509 : aCharIndex, nullptr, aElapsedTime,
510 0 : EmptyString());
511 : }
512 :
513 0 : return NS_OK;
514 : }
515 :
516 : NS_IMETHODIMP
517 0 : nsSpeechTask::DispatchError(float aElapsedTime, uint32_t aCharIndex)
518 : {
519 0 : LOG(LogLevel::Debug, ("nsSpeechTask::DispatchError"));
520 :
521 0 : if (!mIndirectAudio) {
522 0 : NS_WARNING("Can't call DispatchError() from a direct audio speech service");
523 0 : return NS_ERROR_FAILURE;
524 : }
525 :
526 0 : if (!mPreCanceled) {
527 0 : nsSynthVoiceRegistry::GetInstance()->SpeakNext();
528 : }
529 :
530 0 : return DispatchErrorImpl(aElapsedTime, aCharIndex);
531 : }
532 :
533 : nsresult
534 0 : nsSpeechTask::DispatchErrorImpl(float aElapsedTime, uint32_t aCharIndex)
535 : {
536 0 : MOZ_ASSERT(mUtterance);
537 0 : if(NS_WARN_IF(mUtterance->mState == SpeechSynthesisUtterance::STATE_ENDED)) {
538 0 : return NS_ERROR_NOT_AVAILABLE;
539 : }
540 :
541 0 : if (mSpeechSynthesis) {
542 0 : mSpeechSynthesis->OnEnd(this);
543 : }
544 :
545 0 : mUtterance->mState = SpeechSynthesisUtterance::STATE_ENDED;
546 0 : mUtterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("error"),
547 : aCharIndex, nullptr, aElapsedTime,
548 0 : EmptyString());
549 0 : return NS_OK;
550 : }
551 :
552 : NS_IMETHODIMP
553 0 : nsSpeechTask::DispatchBoundary(const nsAString& aName,
554 : float aElapsedTime, uint32_t aCharIndex,
555 : uint32_t aCharLength, uint8_t argc)
556 : {
557 0 : if (!mIndirectAudio) {
558 0 : NS_WARNING("Can't call DispatchBoundary() from a direct audio speech service");
559 0 : return NS_ERROR_FAILURE;
560 : }
561 :
562 0 : return DispatchBoundaryImpl(aName, aElapsedTime, aCharIndex, aCharLength, argc);
563 : }
564 :
565 : nsresult
566 0 : nsSpeechTask::DispatchBoundaryImpl(const nsAString& aName,
567 : float aElapsedTime, uint32_t aCharIndex,
568 : uint32_t aCharLength, uint8_t argc)
569 : {
570 0 : MOZ_ASSERT(mUtterance);
571 0 : if(NS_WARN_IF(!(mUtterance->mState == SpeechSynthesisUtterance::STATE_SPEAKING))) {
572 0 : return NS_ERROR_NOT_AVAILABLE;
573 : }
574 0 : mUtterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("boundary"),
575 : aCharIndex,
576 0 : argc ? static_cast<Nullable<uint32_t> >(aCharLength) : nullptr,
577 0 : aElapsedTime, aName);
578 :
579 0 : return NS_OK;
580 : }
581 :
582 : NS_IMETHODIMP
583 0 : nsSpeechTask::DispatchMark(const nsAString& aName,
584 : float aElapsedTime, uint32_t aCharIndex)
585 : {
586 0 : if (!mIndirectAudio) {
587 0 : NS_WARNING("Can't call DispatchMark() from a direct audio speech service");
588 0 : return NS_ERROR_FAILURE;
589 : }
590 :
591 0 : return DispatchMarkImpl(aName, aElapsedTime, aCharIndex);
592 : }
593 :
594 : nsresult
595 0 : nsSpeechTask::DispatchMarkImpl(const nsAString& aName,
596 : float aElapsedTime, uint32_t aCharIndex)
597 : {
598 0 : MOZ_ASSERT(mUtterance);
599 0 : if(NS_WARN_IF(!(mUtterance->mState == SpeechSynthesisUtterance::STATE_SPEAKING))) {
600 0 : return NS_ERROR_NOT_AVAILABLE;
601 : }
602 :
603 0 : mUtterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("mark"),
604 : aCharIndex, nullptr, aElapsedTime,
605 0 : aName);
606 0 : return NS_OK;
607 : }
608 :
609 : void
610 0 : nsSpeechTask::Pause()
611 : {
612 0 : MOZ_ASSERT(XRE_IsParentProcess());
613 :
614 0 : if (mCallback) {
615 0 : DebugOnly<nsresult> rv = mCallback->OnPause();
616 0 : NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to call onPause() callback");
617 : }
618 :
619 0 : if (mStream) {
620 0 : mStream->Suspend();
621 : }
622 :
623 0 : if (!mInited) {
624 0 : mPrePaused = true;
625 : }
626 :
627 0 : if (!mIndirectAudio) {
628 0 : DispatchPauseImpl(GetCurrentTime(), GetCurrentCharOffset());
629 : }
630 0 : }
631 :
632 : void
633 0 : nsSpeechTask::Resume()
634 : {
635 0 : MOZ_ASSERT(XRE_IsParentProcess());
636 :
637 0 : if (mCallback) {
638 0 : DebugOnly<nsresult> rv = mCallback->OnResume();
639 0 : NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
640 : "Unable to call onResume() callback");
641 : }
642 :
643 0 : if (mStream) {
644 0 : mStream->Resume();
645 : }
646 :
647 0 : if (mPrePaused) {
648 0 : mPrePaused = false;
649 0 : nsSynthVoiceRegistry::GetInstance()->ResumeQueue();
650 : }
651 :
652 0 : if (!mIndirectAudio) {
653 0 : DispatchResumeImpl(GetCurrentTime(), GetCurrentCharOffset());
654 : }
655 0 : }
656 :
657 : void
658 0 : nsSpeechTask::Cancel()
659 : {
660 0 : MOZ_ASSERT(XRE_IsParentProcess());
661 :
662 0 : LOG(LogLevel::Debug, ("nsSpeechTask::Cancel"));
663 :
664 0 : if (mCallback) {
665 0 : DebugOnly<nsresult> rv = mCallback->OnCancel();
666 0 : NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
667 : "Unable to call onCancel() callback");
668 : }
669 :
670 0 : if (mStream) {
671 0 : mStream->Suspend();
672 : }
673 :
674 0 : if (!mInited) {
675 0 : mPreCanceled = true;
676 : }
677 :
678 0 : if (!mIndirectAudio) {
679 0 : DispatchEndInner(GetCurrentTime(), GetCurrentCharOffset());
680 : }
681 0 : }
682 :
683 : void
684 0 : nsSpeechTask::ForceEnd()
685 : {
686 0 : if (mStream) {
687 0 : mStream->Suspend();
688 : }
689 :
690 0 : if (!mInited) {
691 0 : mPreCanceled = true;
692 : }
693 :
694 0 : DispatchEndInner(GetCurrentTime(), GetCurrentCharOffset());
695 0 : }
696 :
697 : float
698 0 : nsSpeechTask::GetCurrentTime()
699 : {
700 0 : return mStream ? (float)(mStream->GetCurrentTime() / 1000000.0) : 0;
701 : }
702 :
703 : uint32_t
704 0 : nsSpeechTask::GetCurrentCharOffset()
705 : {
706 0 : return mStream && mStream->IsFinished() ? mText.Length() : 0;
707 : }
708 :
709 : void
710 0 : nsSpeechTask::SetSpeechSynthesis(SpeechSynthesis* aSpeechSynthesis)
711 : {
712 0 : mSpeechSynthesis = aSpeechSynthesis;
713 0 : }
714 :
715 : void
716 0 : nsSpeechTask::CreateAudioChannelAgent()
717 : {
718 0 : if (!mUtterance) {
719 0 : return;
720 : }
721 :
722 0 : if (mAudioChannelAgent) {
723 0 : mAudioChannelAgent->NotifyStoppedPlaying();
724 : }
725 :
726 0 : mAudioChannelAgent = new AudioChannelAgent();
727 0 : mAudioChannelAgent->InitWithWeakCallback(mUtterance->GetOwner(),
728 0 : static_cast<int32_t>(AudioChannelService::GetDefaultAudioChannel()),
729 0 : this);
730 :
731 0 : AudioPlaybackConfig config;
732 0 : nsresult rv = mAudioChannelAgent->NotifyStartedPlaying(&config,
733 0 : AudioChannelService::AudibleState::eAudible);
734 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
735 0 : return;
736 : }
737 :
738 0 : WindowVolumeChanged(config.mVolume, config.mMuted);
739 0 : WindowSuspendChanged(config.mSuspend);
740 : }
741 :
742 : void
743 0 : nsSpeechTask::DestroyAudioChannelAgent()
744 : {
745 0 : if (mAudioChannelAgent) {
746 0 : mAudioChannelAgent->NotifyStoppedPlaying();
747 0 : mAudioChannelAgent = nullptr;
748 : }
749 0 : }
750 :
751 : NS_IMETHODIMP
752 0 : nsSpeechTask::WindowVolumeChanged(float aVolume, bool aMuted)
753 : {
754 0 : SetAudioOutputVolume(aMuted ? 0.0 : mVolume * aVolume);
755 0 : return NS_OK;
756 : }
757 :
758 : NS_IMETHODIMP
759 0 : nsSpeechTask::WindowSuspendChanged(nsSuspendedTypes aSuspend)
760 : {
761 0 : if (!mUtterance) {
762 0 : return NS_OK;
763 : }
764 :
765 0 : if (aSuspend == nsISuspendedTypes::NONE_SUSPENDED &&
766 0 : mUtterance->mPaused) {
767 0 : Resume();
768 0 : } else if (aSuspend != nsISuspendedTypes::NONE_SUSPENDED &&
769 0 : !mUtterance->mPaused) {
770 0 : Pause();
771 : }
772 0 : return NS_OK;
773 : }
774 :
775 : NS_IMETHODIMP
776 0 : nsSpeechTask::WindowAudioCaptureChanged(bool aCapture)
777 : {
778 : // This is not supported yet.
779 0 : return NS_OK;
780 : }
781 :
782 : void
783 0 : nsSpeechTask::SetAudioOutputVolume(float aVolume)
784 : {
785 0 : if (mStream && !mStream->IsDestroyed()) {
786 0 : mStream->SetAudioOutputVolume(this, aVolume);
787 : }
788 0 : if (mIndirectAudio && mCallback) {
789 0 : mCallback->OnVolumeChanged(aVolume);
790 : }
791 0 : }
792 :
793 : } // namespace dom
794 : } // namespace mozilla
|