Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp
Line
Count
Source (jump to first uncovered line)
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 "nsISpeechService.h"
8
#include "nsServiceManagerUtils.h"
9
#include "nsCategoryManagerUtils.h"
10
11
#include "SpeechSynthesisUtterance.h"
12
#include "SpeechSynthesisVoice.h"
13
#include "nsSynthVoiceRegistry.h"
14
#include "nsSpeechTask.h"
15
#include "AudioChannelService.h"
16
17
#include "nsString.h"
18
#include "mozilla/ClearOnShutdown.h"
19
#include "mozilla/dom/ContentChild.h"
20
#include "mozilla/dom/ContentParent.h"
21
#include "mozilla/intl/LocaleService.h"
22
#include "mozilla/StaticPrefs.h"
23
#include "mozilla/StaticPtr.h"
24
#include "mozilla/Unused.h"
25
26
#include "SpeechSynthesisChild.h"
27
#include "SpeechSynthesisParent.h"
28
29
using mozilla::intl::LocaleService;
30
31
#undef LOG
32
extern mozilla::LogModule* GetSpeechSynthLog();
33
0
#define LOG(type, msg) MOZ_LOG(GetSpeechSynthLog(), type, msg)
34
35
namespace {
36
37
void
38
GetAllSpeechSynthActors(InfallibleTArray<mozilla::dom::SpeechSynthesisParent*>& aActors)
39
0
{
40
0
  MOZ_ASSERT(NS_IsMainThread());
41
0
  MOZ_ASSERT(aActors.IsEmpty());
42
0
43
0
  AutoTArray<mozilla::dom::ContentParent*, 20> contentActors;
44
0
  mozilla::dom::ContentParent::GetAll(contentActors);
45
0
46
0
  for (uint32_t contentIndex = 0;
47
0
       contentIndex < contentActors.Length();
48
0
       ++contentIndex) {
49
0
    MOZ_ASSERT(contentActors[contentIndex]);
50
0
51
0
    AutoTArray<mozilla::dom::PSpeechSynthesisParent*, 5> speechsynthActors;
52
0
    contentActors[contentIndex]->ManagedPSpeechSynthesisParent(speechsynthActors);
53
0
54
0
    for (uint32_t speechsynthIndex = 0;
55
0
         speechsynthIndex < speechsynthActors.Length();
56
0
         ++speechsynthIndex) {
57
0
      MOZ_ASSERT(speechsynthActors[speechsynthIndex]);
58
0
59
0
      mozilla::dom::SpeechSynthesisParent* actor =
60
0
        static_cast<mozilla::dom::SpeechSynthesisParent*>(speechsynthActors[speechsynthIndex]);
61
0
      aActors.AppendElement(actor);
62
0
    }
63
0
  }
64
0
}
65
66
} // namespace
67
68
namespace mozilla {
69
namespace dom {
70
71
// VoiceData
72
73
class VoiceData final
74
{
75
private:
76
  // Private destructor, to discourage deletion outside of Release():
77
0
  ~VoiceData() {}
78
79
public:
80
  VoiceData(nsISpeechService* aService, const nsAString& aUri,
81
            const nsAString& aName, const nsAString& aLang,
82
            bool aIsLocal, bool aQueuesUtterances)
83
    : mService(aService)
84
    , mUri(aUri)
85
    , mName(aName)
86
    , mLang(aLang)
87
    , mIsLocal(aIsLocal)
88
0
    , mIsQueued(aQueuesUtterances) {}
89
90
  NS_INLINE_DECL_REFCOUNTING(VoiceData)
91
92
  nsCOMPtr<nsISpeechService> mService;
93
94
  nsString mUri;
95
96
  nsString mName;
97
98
  nsString mLang;
99
100
  bool mIsLocal;
101
102
  bool mIsQueued;
103
};
104
105
// GlobalQueueItem
106
107
class GlobalQueueItem final
108
{
109
private:
110
  // Private destructor, to discourage deletion outside of Release():
111
0
  ~GlobalQueueItem() {}
112
113
public:
114
  GlobalQueueItem(VoiceData* aVoice, nsSpeechTask* aTask, const nsAString& aText,
115
                  const float& aVolume, const float& aRate, const float& aPitch)
116
    : mVoice(aVoice)
117
    , mTask(aTask)
118
    , mText(aText)
119
    , mVolume(aVolume)
120
    , mRate(aRate)
121
    , mPitch(aPitch)
122
0
    , mIsLocal(false) {}
123
124
  NS_INLINE_DECL_REFCOUNTING(GlobalQueueItem)
125
126
  RefPtr<VoiceData> mVoice;
127
128
  RefPtr<nsSpeechTask> mTask;
129
130
  nsString mText;
131
132
  float mVolume;
133
134
  float mRate;
135
136
  float mPitch;
137
138
  bool mIsLocal;
139
};
140
141
// nsSynthVoiceRegistry
142
143
static StaticRefPtr<nsSynthVoiceRegistry> gSynthVoiceRegistry;
144
145
NS_IMPL_ISUPPORTS(nsSynthVoiceRegistry, nsISynthVoiceRegistry)
146
147
nsSynthVoiceRegistry::nsSynthVoiceRegistry()
148
  : mSpeechSynthChild(nullptr)
149
  , mUseGlobalQueue(false)
150
  , mIsSpeaking(false)
151
0
{
152
0
  if (XRE_IsContentProcess()) {
153
0
154
0
    mSpeechSynthChild = new SpeechSynthesisChild();
155
0
    ContentChild::GetSingleton()->SendPSpeechSynthesisConstructor(mSpeechSynthChild);
156
0
  }
157
0
}
158
159
nsSynthVoiceRegistry::~nsSynthVoiceRegistry()
160
0
{
161
0
  LOG(LogLevel::Debug, ("~nsSynthVoiceRegistry"));
162
0
163
0
  // mSpeechSynthChild's lifecycle is managed by the Content protocol.
164
0
  mSpeechSynthChild = nullptr;
165
0
166
0
  mUriVoiceMap.Clear();
167
0
}
168
169
nsSynthVoiceRegistry*
170
nsSynthVoiceRegistry::GetInstance()
171
0
{
172
0
  MOZ_ASSERT(NS_IsMainThread());
173
0
174
0
  if (!gSynthVoiceRegistry) {
175
0
    gSynthVoiceRegistry = new nsSynthVoiceRegistry();
176
0
    ClearOnShutdown(&gSynthVoiceRegistry);
177
0
    if (XRE_IsParentProcess()) {
178
0
      // Start up all speech synth services.
179
0
      NS_CreateServicesFromCategory(NS_SPEECH_SYNTH_STARTED, nullptr,
180
0
        NS_SPEECH_SYNTH_STARTED);
181
0
    }
182
0
  }
183
0
184
0
  return gSynthVoiceRegistry;
185
0
}
186
187
already_AddRefed<nsSynthVoiceRegistry>
188
nsSynthVoiceRegistry::GetInstanceForService()
189
0
{
190
0
  RefPtr<nsSynthVoiceRegistry> registry = GetInstance();
191
0
192
0
  return registry.forget();
193
0
}
194
195
bool
196
nsSynthVoiceRegistry::SendInitialVoicesAndState(SpeechSynthesisParent* aParent)
197
0
{
198
0
  MOZ_ASSERT(XRE_IsParentProcess());
199
0
200
0
  InfallibleTArray<RemoteVoice> voices;
201
0
  InfallibleTArray<nsString> defaults;
202
0
203
0
  for (uint32_t i=0; i < mVoices.Length(); ++i) {
204
0
    RefPtr<VoiceData> voice = mVoices[i];
205
0
206
0
    voices.AppendElement(RemoteVoice(voice->mUri, voice->mName, voice->mLang,
207
0
                                     voice->mIsLocal, voice->mIsQueued));
208
0
  }
209
0
210
0
  for (uint32_t i=0; i < mDefaultVoices.Length(); ++i) {
211
0
    defaults.AppendElement(mDefaultVoices[i]->mUri);
212
0
  }
213
0
214
0
  return aParent->SendInitialVoicesAndState(voices, defaults, IsSpeaking());
215
0
}
216
217
void
218
nsSynthVoiceRegistry::RecvInitialVoicesAndState(const nsTArray<RemoteVoice>& aVoices,
219
                                                const nsTArray<nsString>& aDefaults,
220
                                                const bool& aIsSpeaking)
221
0
{
222
0
  // We really should have a local instance since this is a directed response to
223
0
  // an Init() call.
224
0
  MOZ_ASSERT(gSynthVoiceRegistry);
225
0
226
0
  for (uint32_t i = 0; i < aVoices.Length(); ++i) {
227
0
    RemoteVoice voice = aVoices[i];
228
0
    gSynthVoiceRegistry->AddVoiceImpl(nullptr, voice.voiceURI(),
229
0
                                      voice.name(), voice.lang(),
230
0
                                      voice.localService(), voice.queued());
231
0
  }
232
0
233
0
  for (uint32_t i = 0; i < aDefaults.Length(); ++i) {
234
0
    gSynthVoiceRegistry->SetDefaultVoice(aDefaults[i], true);
235
0
  }
236
0
237
0
  gSynthVoiceRegistry->mIsSpeaking = aIsSpeaking;
238
0
239
0
  if (aVoices.Length()) {
240
0
    gSynthVoiceRegistry->NotifyVoicesChanged();
241
0
  }
242
0
}
243
244
void
245
nsSynthVoiceRegistry::RecvRemoveVoice(const nsAString& aUri)
246
0
{
247
0
  // If we dont have a local instance of the registry yet, we will recieve current
248
0
  // voices at contruction time.
249
0
  if(!gSynthVoiceRegistry) {
250
0
    return;
251
0
  }
252
0
253
0
  gSynthVoiceRegistry->RemoveVoice(nullptr, aUri);
254
0
}
255
256
void
257
nsSynthVoiceRegistry::RecvAddVoice(const RemoteVoice& aVoice)
258
0
{
259
0
  // If we dont have a local instance of the registry yet, we will recieve current
260
0
  // voices at contruction time.
261
0
  if(!gSynthVoiceRegistry) {
262
0
    return;
263
0
  }
264
0
265
0
  gSynthVoiceRegistry->AddVoiceImpl(nullptr, aVoice.voiceURI(),
266
0
                                    aVoice.name(), aVoice.lang(),
267
0
                                    aVoice.localService(), aVoice.queued());
268
0
}
269
270
void
271
nsSynthVoiceRegistry::RecvSetDefaultVoice(const nsAString& aUri, bool aIsDefault)
272
0
{
273
0
  // If we dont have a local instance of the registry yet, we will recieve current
274
0
  // voices at contruction time.
275
0
  if(!gSynthVoiceRegistry) {
276
0
    return;
277
0
  }
278
0
279
0
  gSynthVoiceRegistry->SetDefaultVoice(aUri, aIsDefault);
280
0
}
281
282
void
283
nsSynthVoiceRegistry::RecvIsSpeakingChanged(bool aIsSpeaking)
284
0
{
285
0
  // If we dont have a local instance of the registry yet, we will get the
286
0
  // speaking state on construction.
287
0
  if(!gSynthVoiceRegistry) {
288
0
    return;
289
0
  }
290
0
291
0
  gSynthVoiceRegistry->mIsSpeaking = aIsSpeaking;
292
0
}
293
294
void
295
nsSynthVoiceRegistry::RecvNotifyVoicesChanged()
296
0
{
297
0
  // If we dont have a local instance of the registry yet, we don't care.
298
0
  if(!gSynthVoiceRegistry) {
299
0
    return;
300
0
  }
301
0
302
0
  gSynthVoiceRegistry->NotifyVoicesChanged();
303
0
}
304
305
NS_IMETHODIMP
306
nsSynthVoiceRegistry::AddVoice(nsISpeechService* aService,
307
                               const nsAString& aUri,
308
                               const nsAString& aName,
309
                               const nsAString& aLang,
310
                               bool aLocalService,
311
                               bool aQueuesUtterances)
312
0
{
313
0
  LOG(LogLevel::Debug,
314
0
      ("nsSynthVoiceRegistry::AddVoice uri='%s' name='%s' lang='%s' local=%s queued=%s",
315
0
       NS_ConvertUTF16toUTF8(aUri).get(), NS_ConvertUTF16toUTF8(aName).get(),
316
0
       NS_ConvertUTF16toUTF8(aLang).get(),
317
0
       aLocalService ? "true" : "false",
318
0
       aQueuesUtterances ? "true" : "false"));
319
0
320
0
  if(NS_WARN_IF(XRE_IsContentProcess())) {
321
0
    return NS_ERROR_NOT_AVAILABLE;
322
0
  }
323
0
324
0
  return AddVoiceImpl(aService, aUri, aName, aLang, aLocalService, aQueuesUtterances);
325
0
}
326
327
NS_IMETHODIMP
328
nsSynthVoiceRegistry::RemoveVoice(nsISpeechService* aService,
329
                                  const nsAString& aUri)
330
0
{
331
0
  LOG(LogLevel::Debug,
332
0
      ("nsSynthVoiceRegistry::RemoveVoice uri='%s' (%s)",
333
0
       NS_ConvertUTF16toUTF8(aUri).get(),
334
0
       (XRE_IsContentProcess()) ? "child" : "parent"));
335
0
336
0
  bool found = false;
337
0
  VoiceData* retval = mUriVoiceMap.GetWeak(aUri, &found);
338
0
339
0
  if(NS_WARN_IF(!(found))) {
340
0
    return NS_ERROR_NOT_AVAILABLE;
341
0
  }
342
0
  if(NS_WARN_IF(!(aService == retval->mService))) {
343
0
    return NS_ERROR_INVALID_ARG;
344
0
  }
345
0
346
0
  mVoices.RemoveElement(retval);
347
0
  mDefaultVoices.RemoveElement(retval);
348
0
  mUriVoiceMap.Remove(aUri);
349
0
350
0
  if (retval->mIsQueued &&
351
0
      !StaticPrefs::MediaWebspeechSynthForceGlobalQueue()) {
352
0
    // Check if this is the last queued voice, and disable the global queue if
353
0
    // it is.
354
0
    bool queued = false;
355
0
    for (uint32_t i = 0; i < mVoices.Length(); i++) {
356
0
      VoiceData* voice = mVoices[i];
357
0
      if (voice->mIsQueued) {
358
0
        queued = true;
359
0
        break;
360
0
      }
361
0
    }
362
0
    if (!queued) {
363
0
      mUseGlobalQueue = false;
364
0
    }
365
0
  }
366
0
367
0
  nsTArray<SpeechSynthesisParent*> ssplist;
368
0
  GetAllSpeechSynthActors(ssplist);
369
0
370
0
  for (uint32_t i = 0; i < ssplist.Length(); ++i)
371
0
    Unused << ssplist[i]->SendVoiceRemoved(nsString(aUri));
372
0
373
0
  return NS_OK;
374
0
}
375
376
NS_IMETHODIMP
377
nsSynthVoiceRegistry::NotifyVoicesChanged()
378
0
{
379
0
  if (XRE_IsParentProcess()) {
380
0
    nsTArray<SpeechSynthesisParent*> ssplist;
381
0
    GetAllSpeechSynthActors(ssplist);
382
0
383
0
    for (uint32_t i = 0; i < ssplist.Length(); ++i)
384
0
      Unused << ssplist[i]->SendNotifyVoicesChanged();
385
0
  }
386
0
387
0
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
388
0
  if(NS_WARN_IF(!(obs))) {
389
0
    return NS_ERROR_NOT_AVAILABLE;
390
0
  }
391
0
392
0
  obs->NotifyObservers(nullptr, "synth-voices-changed", nullptr);
393
0
394
0
  return NS_OK;
395
0
}
396
397
NS_IMETHODIMP
398
nsSynthVoiceRegistry::SetDefaultVoice(const nsAString& aUri,
399
                                      bool aIsDefault)
400
0
{
401
0
  bool found = false;
402
0
  VoiceData* retval = mUriVoiceMap.GetWeak(aUri, &found);
403
0
  if(NS_WARN_IF(!(found))) {
404
0
    return NS_ERROR_NOT_AVAILABLE;
405
0
  }
406
0
407
0
  mDefaultVoices.RemoveElement(retval);
408
0
409
0
  LOG(LogLevel::Debug, ("nsSynthVoiceRegistry::SetDefaultVoice %s %s",
410
0
                     NS_ConvertUTF16toUTF8(aUri).get(),
411
0
                     aIsDefault ? "true" : "false"));
412
0
413
0
  if (aIsDefault) {
414
0
    mDefaultVoices.AppendElement(retval);
415
0
  }
416
0
417
0
  if (XRE_IsParentProcess()) {
418
0
    nsTArray<SpeechSynthesisParent*> ssplist;
419
0
    GetAllSpeechSynthActors(ssplist);
420
0
421
0
    for (uint32_t i = 0; i < ssplist.Length(); ++i) {
422
0
      Unused << ssplist[i]->SendSetDefaultVoice(nsString(aUri), aIsDefault);
423
0
    }
424
0
  }
425
0
426
0
  return NS_OK;
427
0
}
428
429
NS_IMETHODIMP
430
nsSynthVoiceRegistry::GetVoiceCount(uint32_t* aRetval)
431
0
{
432
0
  *aRetval = mVoices.Length();
433
0
434
0
  return NS_OK;
435
0
}
436
437
NS_IMETHODIMP
438
nsSynthVoiceRegistry::GetVoice(uint32_t aIndex, nsAString& aRetval)
439
0
{
440
0
  if(NS_WARN_IF(!(aIndex < mVoices.Length()))) {
441
0
    return NS_ERROR_INVALID_ARG;
442
0
  }
443
0
444
0
  aRetval = mVoices[aIndex]->mUri;
445
0
446
0
  return NS_OK;
447
0
}
448
449
NS_IMETHODIMP
450
nsSynthVoiceRegistry::IsDefaultVoice(const nsAString& aUri, bool* aRetval)
451
0
{
452
0
  bool found;
453
0
  VoiceData* voice = mUriVoiceMap.GetWeak(aUri, &found);
454
0
  if(NS_WARN_IF(!(found))) {
455
0
    return NS_ERROR_NOT_AVAILABLE;
456
0
  }
457
0
458
0
  for (int32_t i = mDefaultVoices.Length(); i > 0; ) {
459
0
    VoiceData* defaultVoice = mDefaultVoices[--i];
460
0
461
0
    if (voice->mLang.Equals(defaultVoice->mLang)) {
462
0
      *aRetval = voice == defaultVoice;
463
0
      return NS_OK;
464
0
    }
465
0
  }
466
0
467
0
  *aRetval = false;
468
0
  return NS_OK;
469
0
}
470
471
NS_IMETHODIMP
472
nsSynthVoiceRegistry::IsLocalVoice(const nsAString& aUri, bool* aRetval)
473
0
{
474
0
  bool found;
475
0
  VoiceData* voice = mUriVoiceMap.GetWeak(aUri, &found);
476
0
  if(NS_WARN_IF(!(found))) {
477
0
    return NS_ERROR_NOT_AVAILABLE;
478
0
  }
479
0
480
0
  *aRetval = voice->mIsLocal;
481
0
  return NS_OK;
482
0
}
483
484
NS_IMETHODIMP
485
nsSynthVoiceRegistry::GetVoiceLang(const nsAString& aUri, nsAString& aRetval)
486
0
{
487
0
  bool found;
488
0
  VoiceData* voice = mUriVoiceMap.GetWeak(aUri, &found);
489
0
  if(NS_WARN_IF(!(found))) {
490
0
    return NS_ERROR_NOT_AVAILABLE;
491
0
  }
492
0
493
0
  aRetval = voice->mLang;
494
0
  return NS_OK;
495
0
}
496
497
NS_IMETHODIMP
498
nsSynthVoiceRegistry::GetVoiceName(const nsAString& aUri, nsAString& aRetval)
499
0
{
500
0
  bool found;
501
0
  VoiceData* voice = mUriVoiceMap.GetWeak(aUri, &found);
502
0
  if(NS_WARN_IF(!(found))) {
503
0
    return NS_ERROR_NOT_AVAILABLE;
504
0
  }
505
0
506
0
  aRetval = voice->mName;
507
0
  return NS_OK;
508
0
}
509
510
nsresult
511
nsSynthVoiceRegistry::AddVoiceImpl(nsISpeechService* aService,
512
                                   const nsAString& aUri,
513
                                   const nsAString& aName,
514
                                   const nsAString& aLang,
515
                                   bool aLocalService,
516
                                   bool aQueuesUtterances)
517
0
{
518
0
  bool found = false;
519
0
  mUriVoiceMap.GetWeak(aUri, &found);
520
0
  if(NS_WARN_IF(found)) {
521
0
    return NS_ERROR_INVALID_ARG;
522
0
  }
523
0
524
0
  RefPtr<VoiceData> voice = new VoiceData(aService, aUri, aName, aLang,
525
0
                                            aLocalService, aQueuesUtterances);
526
0
527
0
  mVoices.AppendElement(voice);
528
0
  mUriVoiceMap.Put(aUri, voice);
529
0
  mUseGlobalQueue |= aQueuesUtterances;
530
0
531
0
  nsTArray<SpeechSynthesisParent*> ssplist;
532
0
  GetAllSpeechSynthActors(ssplist);
533
0
534
0
  if (!ssplist.IsEmpty()) {
535
0
    mozilla::dom::RemoteVoice ssvoice(nsString(aUri),
536
0
                                      nsString(aName),
537
0
                                      nsString(aLang),
538
0
                                      aLocalService,
539
0
                                      aQueuesUtterances);
540
0
541
0
    for (uint32_t i = 0; i < ssplist.Length(); ++i) {
542
0
      Unused << ssplist[i]->SendVoiceAdded(ssvoice);
543
0
    }
544
0
  }
545
0
546
0
  return NS_OK;
547
0
}
548
549
bool
550
nsSynthVoiceRegistry::FindVoiceByLang(const nsAString& aLang,
551
                                      VoiceData** aRetval)
552
0
{
553
0
  nsAString::const_iterator dashPos, start, end;
554
0
  aLang.BeginReading(start);
555
0
  aLang.EndReading(end);
556
0
557
0
  while (true) {
558
0
    nsAutoString langPrefix(Substring(start, end));
559
0
560
0
    for (int32_t i = mDefaultVoices.Length(); i > 0; ) {
561
0
      VoiceData* voice = mDefaultVoices[--i];
562
0
563
0
      if (StringBeginsWith(voice->mLang, langPrefix)) {
564
0
        *aRetval = voice;
565
0
        return true;
566
0
      }
567
0
    }
568
0
569
0
    for (int32_t i = mVoices.Length(); i > 0; ) {
570
0
      VoiceData* voice = mVoices[--i];
571
0
572
0
      if (StringBeginsWith(voice->mLang, langPrefix)) {
573
0
        *aRetval = voice;
574
0
        return true;
575
0
      }
576
0
    }
577
0
578
0
    dashPos = end;
579
0
    end = start;
580
0
581
0
    if (!RFindInReadable(NS_LITERAL_STRING("-"), end, dashPos)) {
582
0
      break;
583
0
    }
584
0
  }
585
0
586
0
  return false;
587
0
}
588
589
VoiceData*
590
nsSynthVoiceRegistry::FindBestMatch(const nsAString& aUri,
591
                                    const nsAString& aLang)
592
0
{
593
0
  if (mVoices.IsEmpty()) {
594
0
    return nullptr;
595
0
  }
596
0
597
0
  bool found = false;
598
0
  VoiceData* retval = mUriVoiceMap.GetWeak(aUri, &found);
599
0
600
0
  if (found) {
601
0
    LOG(LogLevel::Debug, ("nsSynthVoiceRegistry::FindBestMatch - Matched URI"));
602
0
    return retval;
603
0
  }
604
0
605
0
  // Try finding a match for given voice.
606
0
  if (!aLang.IsVoid() && !aLang.IsEmpty()) {
607
0
    if (FindVoiceByLang(aLang, &retval)) {
608
0
      LOG(LogLevel::Debug,
609
0
          ("nsSynthVoiceRegistry::FindBestMatch - Matched language (%s ~= %s)",
610
0
           NS_ConvertUTF16toUTF8(aLang).get(),
611
0
           NS_ConvertUTF16toUTF8(retval->mLang).get()));
612
0
613
0
      return retval;
614
0
    }
615
0
  }
616
0
617
0
  // Try UI language.
618
0
  nsAutoCString uiLang;
619
0
  LocaleService::GetInstance()->GetAppLocaleAsLangTag(uiLang);
620
0
621
0
  if (FindVoiceByLang(NS_ConvertASCIItoUTF16(uiLang), &retval)) {
622
0
    LOG(LogLevel::Debug,
623
0
        ("nsSynthVoiceRegistry::FindBestMatch - Matched UI language (%s ~= %s)",
624
0
         uiLang.get(),
625
0
         NS_ConvertUTF16toUTF8(retval->mLang).get()));
626
0
627
0
    return retval;
628
0
  }
629
0
630
0
  // Try en-US, the language of locale "C"
631
0
  if (FindVoiceByLang(NS_LITERAL_STRING("en-US"), &retval)) {
632
0
    LOG(LogLevel::Debug,
633
0
        ("nsSynthVoiceRegistry::FindBestMatch - Matched C locale language (en-US ~= %s)",
634
0
         NS_ConvertUTF16toUTF8(retval->mLang).get()));
635
0
636
0
    return retval;
637
0
  }
638
0
639
0
  // The top default voice is better than nothing...
640
0
  if (!mDefaultVoices.IsEmpty()) {
641
0
    return mDefaultVoices.LastElement();
642
0
  }
643
0
644
0
  return nullptr;
645
0
}
646
647
already_AddRefed<nsSpeechTask>
648
nsSynthVoiceRegistry::SpeakUtterance(SpeechSynthesisUtterance& aUtterance,
649
                                     const nsAString& aDocLang)
650
0
{
651
0
  nsString lang = nsString(aUtterance.mLang.IsEmpty() ? aDocLang : aUtterance.mLang);
652
0
  nsAutoString uri;
653
0
654
0
  if (aUtterance.mVoice) {
655
0
    aUtterance.mVoice->GetVoiceURI(uri);
656
0
  }
657
0
658
0
  // Get current audio volume to apply speech call
659
0
  float volume = aUtterance.Volume();
660
0
  RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
661
0
  if (service) {
662
0
    if (nsCOMPtr<nsPIDOMWindowInner> topWindow = aUtterance.GetOwner()) {
663
0
      // TODO : use audio channel agent, open new bug to fix it.
664
0
      AudioPlaybackConfig config = service->GetMediaConfig(topWindow->GetOuterWindow());
665
0
      volume = config.mMuted ? 0.0f : config.mVolume * volume;
666
0
    }
667
0
  }
668
0
669
0
  nsCOMPtr<nsPIDOMWindowInner> window = aUtterance.GetOwner();
670
0
  nsCOMPtr<nsIDocument> doc = window ? window->GetDoc() : nullptr;
671
0
672
0
  bool isChrome = nsContentUtils::IsChromeDoc(doc);
673
0
674
0
  RefPtr<nsSpeechTask> task;
675
0
  if (XRE_IsContentProcess()) {
676
0
    task = new SpeechTaskChild(&aUtterance, isChrome);
677
0
    SpeechSynthesisRequestChild* actor =
678
0
      new SpeechSynthesisRequestChild(static_cast<SpeechTaskChild*>(task.get()));
679
0
    mSpeechSynthChild->SendPSpeechSynthesisRequestConstructor(actor,
680
0
                                                              aUtterance.mText,
681
0
                                                              lang,
682
0
                                                              uri,
683
0
                                                              volume,
684
0
                                                              aUtterance.Rate(),
685
0
                                                              aUtterance.Pitch(),
686
0
                                                              isChrome);
687
0
  } else {
688
0
    task = new nsSpeechTask(&aUtterance, isChrome);
689
0
    Speak(aUtterance.mText, lang, uri,
690
0
          volume, aUtterance.Rate(), aUtterance.Pitch(), task);
691
0
  }
692
0
693
0
  return task.forget();
694
0
}
695
696
void
697
nsSynthVoiceRegistry::Speak(const nsAString& aText,
698
                            const nsAString& aLang,
699
                            const nsAString& aUri,
700
                            const float& aVolume,
701
                            const float& aRate,
702
                            const float& aPitch,
703
                            nsSpeechTask* aTask)
704
0
{
705
0
  MOZ_ASSERT(XRE_IsParentProcess());
706
0
707
0
  if (!aTask->IsChrome() && nsContentUtils::ShouldResistFingerprinting()) {
708
0
    aTask->ForceError(0, 0);
709
0
    return;
710
0
  }
711
0
712
0
  VoiceData* voice = FindBestMatch(aUri, aLang);
713
0
714
0
  if (!voice) {
715
0
    NS_WARNING("No voices found.");
716
0
    aTask->ForceError(0, 0);
717
0
    return;
718
0
  }
719
0
720
0
  aTask->SetChosenVoiceURI(voice->mUri);
721
0
722
0
  if (mUseGlobalQueue ||
723
0
      StaticPrefs::MediaWebspeechSynthForceGlobalQueue()) {
724
0
    LOG(LogLevel::Debug,
725
0
        ("nsSynthVoiceRegistry::Speak queueing text='%s' lang='%s' uri='%s' rate=%f pitch=%f",
726
0
         NS_ConvertUTF16toUTF8(aText).get(), NS_ConvertUTF16toUTF8(aLang).get(),
727
0
         NS_ConvertUTF16toUTF8(aUri).get(), aRate, aPitch));
728
0
    RefPtr<GlobalQueueItem> item = new GlobalQueueItem(voice, aTask, aText,
729
0
                                                         aVolume, aRate, aPitch);
730
0
    mGlobalQueue.AppendElement(item);
731
0
732
0
    if (mGlobalQueue.Length() == 1) {
733
0
      SpeakImpl(item->mVoice, item->mTask, item->mText, item->mVolume, item->mRate,
734
0
                item->mPitch);
735
0
    }
736
0
  } else {
737
0
    SpeakImpl(voice, aTask, aText, aVolume, aRate, aPitch);
738
0
  }
739
0
}
740
741
void
742
nsSynthVoiceRegistry::SpeakNext()
743
0
{
744
0
  MOZ_ASSERT(XRE_IsParentProcess());
745
0
746
0
  LOG(LogLevel::Debug,
747
0
      ("nsSynthVoiceRegistry::SpeakNext %d", mGlobalQueue.IsEmpty()));
748
0
749
0
  SetIsSpeaking(false);
750
0
751
0
  if (mGlobalQueue.IsEmpty()) {
752
0
    return;
753
0
  }
754
0
755
0
  mGlobalQueue.RemoveElementAt(0);
756
0
757
0
  while (!mGlobalQueue.IsEmpty()) {
758
0
    RefPtr<GlobalQueueItem> item = mGlobalQueue.ElementAt(0);
759
0
    if (item->mTask->IsPreCanceled()) {
760
0
      mGlobalQueue.RemoveElementAt(0);
761
0
      continue;
762
0
    }
763
0
    if (!item->mTask->IsPrePaused()) {
764
0
      SpeakImpl(item->mVoice, item->mTask, item->mText, item->mVolume,
765
0
                item->mRate, item->mPitch);
766
0
    }
767
0
    break;
768
0
  }
769
0
}
770
771
void
772
nsSynthVoiceRegistry::ResumeQueue()
773
0
{
774
0
  MOZ_ASSERT(XRE_IsParentProcess());
775
0
  LOG(LogLevel::Debug,
776
0
      ("nsSynthVoiceRegistry::ResumeQueue %d", mGlobalQueue.IsEmpty()));
777
0
778
0
  if (mGlobalQueue.IsEmpty()) {
779
0
    return;
780
0
  }
781
0
782
0
  RefPtr<GlobalQueueItem> item = mGlobalQueue.ElementAt(0);
783
0
  if (!item->mTask->IsPrePaused()) {
784
0
    SpeakImpl(item->mVoice, item->mTask, item->mText, item->mVolume,
785
0
              item->mRate, item->mPitch);
786
0
  }
787
0
}
788
789
bool
790
nsSynthVoiceRegistry::IsSpeaking()
791
0
{
792
0
  return mIsSpeaking;
793
0
}
794
795
void
796
nsSynthVoiceRegistry::SetIsSpeaking(bool aIsSpeaking)
797
0
{
798
0
  MOZ_ASSERT(XRE_IsParentProcess());
799
0
800
0
  // Only set to 'true' if global queue is enabled.
801
0
  mIsSpeaking =
802
0
    aIsSpeaking && (mUseGlobalQueue ||
803
0
                    StaticPrefs::MediaWebspeechSynthForceGlobalQueue());
804
0
805
0
  nsTArray<SpeechSynthesisParent*> ssplist;
806
0
  GetAllSpeechSynthActors(ssplist);
807
0
  for (uint32_t i = 0; i < ssplist.Length(); ++i) {
808
0
    Unused << ssplist[i]->SendIsSpeakingChanged(aIsSpeaking);
809
0
  }
810
0
}
811
812
void
813
nsSynthVoiceRegistry::SpeakImpl(VoiceData* aVoice,
814
                                nsSpeechTask* aTask,
815
                                const nsAString& aText,
816
                                const float& aVolume,
817
                                const float& aRate,
818
                                const float& aPitch)
819
0
{
820
0
  LOG(LogLevel::Debug,
821
0
      ("nsSynthVoiceRegistry::SpeakImpl queueing text='%s' uri='%s' rate=%f pitch=%f",
822
0
       NS_ConvertUTF16toUTF8(aText).get(), NS_ConvertUTF16toUTF8(aVoice->mUri).get(),
823
0
       aRate, aPitch));
824
0
825
0
  aTask->Init();
826
0
827
0
  if (NS_FAILED(aVoice->mService->Speak(aText, aVoice->mUri, aVolume, aRate,
828
0
                                        aPitch, aTask))) {
829
0
    aTask->DispatchError(0, 0);
830
0
  }
831
0
}
832
833
} // namespace dom
834
} // namespace mozilla