/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 |