Bug 1188099 - (Part 1) Enable/disable global queue depending on voices and pref. r=smaug r=kdavis
authorEitan Isaacson <eitan@monotonous.org>
Sat, 08 Aug 2015 10:30:46 -0700
changeset 256988 21fc5e503f210cd57244ecaca45d90d2d9fb9388
parent 256987 dbeadce8cb5fe539691e5ac88e7aec3e31df9102
child 256989 51b76297c3d33f0cc40ec95a887b141a7f89d678
push id14559
push userphilringnalda@gmail.com
push dateSun, 09 Aug 2015 23:41:14 +0000
treeherderfx-team@0e269a1f1beb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, kdavis
bugs1188099
milestone42.0a1
Bug 1188099 - (Part 1) Enable/disable global queue depending on voices and pref. r=smaug r=kdavis
dom/media/webspeech/synth/ipc/PSpeechSynthesis.ipdl
dom/media/webspeech/synth/nsISynthVoiceRegistry.idl
dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp
dom/media/webspeech/synth/nsSynthVoiceRegistry.h
dom/media/webspeech/synth/pico/nsPicoService.cpp
dom/media/webspeech/synth/test/nsFakeSynthServices.cpp
dom/media/webspeech/synth/windows/SapiService.cpp
--- a/dom/media/webspeech/synth/ipc/PSpeechSynthesis.ipdl
+++ b/dom/media/webspeech/synth/ipc/PSpeechSynthesis.ipdl
@@ -10,16 +10,17 @@ include protocol PSpeechSynthesisRequest
 namespace mozilla {
 namespace dom {
 
 struct RemoteVoice {
   nsString voiceURI;
   nsString name;
   nsString lang;
   bool localService;
+  bool queued;
 };
 
 sync protocol PSpeechSynthesis
 {
   manager PContent;
   manages PSpeechSynthesisRequest;
 
 child:
--- a/dom/media/webspeech/synth/nsISynthVoiceRegistry.idl
+++ b/dom/media/webspeech/synth/nsISynthVoiceRegistry.idl
@@ -2,31 +2,32 @@
 /* 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 "nsISupports.idl"
 
 interface nsISpeechService;
 
-[scriptable, builtinclass, uuid(53dcc868-4193-4c3c-a1d9-fe5a0a6af2fb)]
+[scriptable, builtinclass, uuid(dac09c3a-156e-4025-a4ab-bc88b0ea92e7)]
 interface nsISynthVoiceRegistry : nsISupports
 {
   /**
    * Register a speech synthesis voice.
    *
-   * @param aService      the service that provides this voice.
-   * @param aUri          a unique identifier for this voice.
-   * @param aName         human-readable name for this voice.
-   * @param aLang         a BCP 47 language tag.
-   * @param aLocalService true if service does not require network.
+   * @param aService          the service that provides this voice.
+   * @param aUri              a unique identifier for this voice.
+   * @param aName             human-readable name for this voice.
+   * @param aLang             a BCP 47 language tag.
+   * @param aLocalService     true if service does not require network.
+   * @param aQueuesUtterances true if voice only speaks one utterance at a time
    */
   void addVoice(in nsISpeechService aService, in DOMString aUri,
                 in DOMString aName, in DOMString aLang,
-                in boolean aLocalService);
+                in boolean aLocalService, in boolean aQueuesUtterances);
 
   /**
    * Remove a speech synthesis voice.
    *
    * @param aService the service that was used to add the voice.
    * @param aUri     a unique identifier of an existing voice.
    */
   void removeVoice(in nsISpeechService aService, in DOMString aUri);
--- a/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp
+++ b/dom/media/webspeech/synth/nsSynthVoiceRegistry.cpp
@@ -12,16 +12,17 @@
 #include "SpeechSynthesisVoice.h"
 #include "nsSynthVoiceRegistry.h"
 #include "nsSpeechTask.h"
 
 #include "nsString.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
+#include "mozilla/Preferences.h"
 #include "mozilla/unused.h"
 
 #include "SpeechSynthesisChild.h"
 #include "SpeechSynthesisParent.h"
 
 #undef LOG
 extern PRLogModuleInfo* GetSpeechSynthLog();
 #define LOG(type, msg) MOZ_LOG(GetSpeechSynthLog(), type, msg)
@@ -67,60 +68,66 @@ namespace dom {
 class VoiceData final
 {
 private:
   // Private destructor, to discourage deletion outside of Release():
   ~VoiceData() {}
 
 public:
   VoiceData(nsISpeechService* aService, const nsAString& aUri,
-            const nsAString& aName, const nsAString& aLang, bool aIsLocal)
+            const nsAString& aName, const nsAString& aLang,
+            bool aIsLocal, bool aQueuesUtterances)
     : mService(aService)
     , mUri(aUri)
     , mName(aName)
     , mLang(aLang)
-    , mIsLocal(aIsLocal) {}
+    , mIsLocal(aIsLocal)
+    , mIsQueued(aQueuesUtterances) {}
 
   NS_INLINE_DECL_REFCOUNTING(VoiceData)
 
   nsCOMPtr<nsISpeechService> mService;
 
   nsString mUri;
 
   nsString mName;
 
   nsString mLang;
 
   bool mIsLocal;
+
+  bool mIsQueued;
 };
 
 // nsSynthVoiceRegistry
 
 static StaticRefPtr<nsSynthVoiceRegistry> gSynthVoiceRegistry;
+static bool sForceGlobalQueue = false;
 
 NS_IMPL_ISUPPORTS(nsSynthVoiceRegistry, nsISynthVoiceRegistry)
 
 nsSynthVoiceRegistry::nsSynthVoiceRegistry()
   : mSpeechSynthChild(nullptr)
+  , mUseGlobalQueue(false)
 {
   if (XRE_IsContentProcess()) {
 
     mSpeechSynthChild = new SpeechSynthesisChild();
     ContentChild::GetSingleton()->SendPSpeechSynthesisConstructor(mSpeechSynthChild);
 
     InfallibleTArray<RemoteVoice> voices;
     InfallibleTArray<nsString> defaults;
 
     mSpeechSynthChild->SendReadVoiceList(&voices, &defaults);
 
     for (uint32_t i = 0; i < voices.Length(); ++i) {
       RemoteVoice voice = voices[i];
       AddVoiceImpl(nullptr, voice.voiceURI(),
                    voice.name(), voice.lang(),
-                   voice.localService());
+                   voice.localService(), voice.queued());
     }
 
     for (uint32_t i = 0; i < defaults.Length(); ++i) {
       SetDefaultVoice(defaults[i], true);
     }
   }
 }
 
@@ -144,16 +151,18 @@ nsSynthVoiceRegistry::~nsSynthVoiceRegis
 
 nsSynthVoiceRegistry*
 nsSynthVoiceRegistry::GetInstance()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!gSynthVoiceRegistry) {
     gSynthVoiceRegistry = new nsSynthVoiceRegistry();
+    Preferences::AddBoolVarCache(&sForceGlobalQueue,
+                                 "media.webspeech.synth.force_global_queue");
   }
 
   return gSynthVoiceRegistry;
 }
 
 already_AddRefed<nsSynthVoiceRegistry>
 nsSynthVoiceRegistry::GetInstanceForService()
 {
@@ -173,17 +182,17 @@ nsSynthVoiceRegistry::Shutdown()
 void
 nsSynthVoiceRegistry::SendVoices(InfallibleTArray<RemoteVoice>* aVoices,
                                  InfallibleTArray<nsString>* aDefaults)
 {
   for (uint32_t i=0; i < mVoices.Length(); ++i) {
     nsRefPtr<VoiceData> voice = mVoices[i];
 
     aVoices->AppendElement(RemoteVoice(voice->mUri, voice->mName, voice->mLang,
-                                       voice->mIsLocal));
+                                       voice->mIsLocal, voice->mIsQueued));
   }
 
   for (uint32_t i=0; i < mDefaultVoices.Length(); ++i) {
     aDefaults->AppendElement(mDefaultVoices[i]->mUri);
   }
 }
 
 void
@@ -204,17 +213,17 @@ nsSynthVoiceRegistry::RecvAddVoice(const
   // If we dont have a local instance of the registry yet, we will recieve current
   // voices at contruction time.
   if(!gSynthVoiceRegistry) {
     return;
   }
 
   gSynthVoiceRegistry->AddVoiceImpl(nullptr, aVoice.voiceURI(),
                                     aVoice.name(), aVoice.lang(),
-                                    aVoice.localService());
+                                    aVoice.localService(), aVoice.queued());
 }
 
 void
 nsSynthVoiceRegistry::RecvSetDefaultVoice(const nsAString& aUri, bool aIsDefault)
 {
   // If we dont have a local instance of the registry yet, we will recieve current
   // voices at contruction time.
   if(!gSynthVoiceRegistry) {
@@ -224,30 +233,31 @@ nsSynthVoiceRegistry::RecvSetDefaultVoic
   gSynthVoiceRegistry->SetDefaultVoice(aUri, aIsDefault);
 }
 
 NS_IMETHODIMP
 nsSynthVoiceRegistry::AddVoice(nsISpeechService* aService,
                                const nsAString& aUri,
                                const nsAString& aName,
                                const nsAString& aLang,
-                               bool aLocalService)
+                               bool aLocalService,
+                               bool aQueuesUtterances)
 {
   LOG(LogLevel::Debug,
-      ("nsSynthVoiceRegistry::AddVoice uri='%s' name='%s' lang='%s' local=%s",
+      ("nsSynthVoiceRegistry::AddVoice uri='%s' name='%s' lang='%s' local=%s queued=%s",
        NS_ConvertUTF16toUTF8(aUri).get(), NS_ConvertUTF16toUTF8(aName).get(),
        NS_ConvertUTF16toUTF8(aLang).get(),
-       aLocalService ? "true" : "false"));
+       aLocalService ? "true" : "false",
+       aQueuesUtterances ? "true" : "false"));
 
   if(NS_WARN_IF(XRE_IsContentProcess())) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  return AddVoiceImpl(aService, aUri, aName, aLang,
-                      aLocalService);
+  return AddVoiceImpl(aService, aUri, aName, aLang, aLocalService, aQueuesUtterances);
 }
 
 NS_IMETHODIMP
 nsSynthVoiceRegistry::RemoveVoice(nsISpeechService* aService,
                                   const nsAString& aUri)
 {
   LOG(LogLevel::Debug,
       ("nsSynthVoiceRegistry::RemoveVoice uri='%s' (%s)",
@@ -263,16 +273,32 @@ nsSynthVoiceRegistry::RemoveVoice(nsISpe
   if(NS_WARN_IF(!(aService == retval->mService))) {
     return NS_ERROR_INVALID_ARG;
   }
 
   mVoices.RemoveElement(retval);
   mDefaultVoices.RemoveElement(retval);
   mUriVoiceMap.Remove(aUri);
 
+  if (retval->mIsQueued && !sForceGlobalQueue) {
+    // Check if this is the last queued voice, and disable the global queue if
+    // it is.
+    bool queued = false;
+    for (uint32_t i = 0; i < mVoices.Length(); i++) {
+      VoiceData* voice = mVoices[i];
+      if (voice->mIsQueued) {
+        queued = true;
+        break;
+      }
+    }
+    if (!queued) {
+      mUseGlobalQueue = false;
+    }
+  }
+
   nsTArray<SpeechSynthesisParent*> ssplist;
   GetAllSpeechSynthActors(ssplist);
 
   for (uint32_t i = 0; i < ssplist.Length(); ++i)
     unused << ssplist[i]->SendVoiceRemoved(nsString(aUri));
 
   return NS_OK;
 }
@@ -390,38 +416,41 @@ nsSynthVoiceRegistry::GetVoiceName(const
   return NS_OK;
 }
 
 nsresult
 nsSynthVoiceRegistry::AddVoiceImpl(nsISpeechService* aService,
                                    const nsAString& aUri,
                                    const nsAString& aName,
                                    const nsAString& aLang,
-                                   bool aLocalService)
+                                   bool aLocalService,
+                                   bool aQueuesUtterances)
 {
   bool found = false;
   mUriVoiceMap.GetWeak(aUri, &found);
   if(NS_WARN_IF(found)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsRefPtr<VoiceData> voice = new VoiceData(aService, aUri, aName, aLang,
-                                            aLocalService);
+                                            aLocalService, aQueuesUtterances);
 
   mVoices.AppendElement(voice);
   mUriVoiceMap.Put(aUri, voice);
+  mUseGlobalQueue |= aQueuesUtterances;
 
   nsTArray<SpeechSynthesisParent*> ssplist;
   GetAllSpeechSynthActors(ssplist);
 
   if (!ssplist.IsEmpty()) {
     mozilla::dom::RemoteVoice ssvoice(nsString(aUri),
                                       nsString(aName),
                                       nsString(aLang),
-                                      aLocalService);
+                                      aLocalService,
+                                      aQueuesUtterances);
 
     for (uint32_t i = 0; i < ssplist.Length(); ++i) {
       unused << ssplist[i]->SendVoiceAdded(ssvoice);
     }
   }
 
   return NS_OK;
 }
--- a/dom/media/webspeech/synth/nsSynthVoiceRegistry.h
+++ b/dom/media/webspeech/synth/nsSynthVoiceRegistry.h
@@ -60,25 +60,28 @@ private:
   VoiceData* FindBestMatch(const nsAString& aUri, const nsAString& lang);
 
   bool FindVoiceByLang(const nsAString& aLang, VoiceData** aRetval);
 
   nsresult AddVoiceImpl(nsISpeechService* aService,
                         const nsAString& aUri,
                         const nsAString& aName,
                         const nsAString& aLang,
-                        bool aLocalService);
+                        bool aLocalService,
+                        bool aQueuesUtterances);
 
   nsTArray<nsRefPtr<VoiceData> > mVoices;
 
   nsTArray<nsRefPtr<VoiceData> > mDefaultVoices;
 
   nsRefPtrHashtable<nsStringHashKey, VoiceData> mUriVoiceMap;
 
   SpeechSynthesisChild* mSpeechSynthChild;
 
   nsRefPtr<ProcessedMediaStream> mStream;
+
+  bool mUseGlobalQueue;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
--- a/dom/media/webspeech/synth/pico/nsPicoService.cpp
+++ b/dom/media/webspeech/synth/pico/nsPicoService.cpp
@@ -514,19 +514,21 @@ PicoAddVoiceTraverser(const nsAString& a
   }
 
   VoiceTraverserData* data = static_cast<VoiceTraverserData*>(aUserArg);
 
   nsAutoString name;
   name.AssignLiteral("Pico ");
   name.Append(aVoice->mLanguage);
 
+  // This service is multi-threaded and can handle more than one utterance at a
+  // time before previous utterances end. So, aQueuesUtterances == false
   DebugOnly<nsresult> rv =
     data->mRegistry->AddVoice(
-      data->mService, aUri, name, aVoice->mLanguage, true);
+      data->mService, aUri, name, aVoice->mLanguage, true, false);
   NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to add voice");
 
   return PL_DHASH_NEXT;
 }
 
 void
 nsPicoService::Init()
 {
--- a/dom/media/webspeech/synth/test/nsFakeSynthServices.cpp
+++ b/dom/media/webspeech/synth/test/nsFakeSynthServices.cpp
@@ -282,17 +282,19 @@ nsFakeSynthServices::~nsFakeSynthService
 static void
 AddVoices(nsISpeechService* aService, const VoiceDetails* aVoices, uint32_t aLength)
 {
   nsSynthVoiceRegistry* registry = nsSynthVoiceRegistry::GetInstance();
   for (uint32_t i = 0; i < aLength; i++) {
     NS_ConvertUTF8toUTF16 name(aVoices[i].name);
     NS_ConvertUTF8toUTF16 uri(aVoices[i].uri);
     NS_ConvertUTF8toUTF16 lang(aVoices[i].lang);
-    registry->AddVoice(aService, uri, name, lang, true);
+    // These services can handle more than one utterance at a time and have
+    // several speaking simultaniously. So, aQueuesUtterances == false
+    registry->AddVoice(aService, uri, name, lang, true, false);
     if (aVoices[i].defaultVoice) {
       registry->SetDefaultVoice(uri, true);
     }
   }
 }
 
 void
 nsFakeSynthServices::Init()
--- a/dom/media/webspeech/synth/windows/SapiService.cpp
+++ b/dom/media/webspeech/synth/windows/SapiService.cpp
@@ -273,18 +273,21 @@ SapiService::RegisterVoices()
     }
 
     nsAutoString uri;
     uri.AssignLiteral("urn:moz-tts:sapi:");
     uri.Append(description);
     uri.AppendLiteral("?");
     uri.Append(locale);
 
+    // This service can only speak one utterance at a time, se we set
+    // aQueuesUtterances to true in order to track global state and schedule
+    // access to this service.
     rv = registry->AddVoice(this, uri, nsDependentString(description), locale,
-                            true);
+                            true, true);
     CoTaskMemFree(description);
     if (NS_FAILED(rv)) {
       continue;
     }
 
     mVoices.Put(uri, voiceToken);
   }