dom/audiochannel/AudioChannelService.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
     2 /* vim: set ts=2 et sw=2 tw=80: */
     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 file,
     5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "AudioChannelService.h"
     8 #include "AudioChannelServiceChild.h"
    10 #include "base/basictypes.h"
    12 #include "mozilla/Services.h"
    13 #include "mozilla/StaticPtr.h"
    14 #include "mozilla/unused.h"
    16 #include "mozilla/dom/ContentParent.h"
    18 #include "nsThreadUtils.h"
    19 #include "nsHashPropertyBag.h"
    20 #include "nsComponentManagerUtils.h"
    21 #include "nsPIDOMWindow.h"
    22 #include "nsServiceManagerUtils.h"
    24 #ifdef MOZ_WIDGET_GONK
    25 #include "nsJSUtils.h"
    26 #include "nsCxPusher.h"
    27 #include "nsIAudioManager.h"
    28 #include "SpeakerManagerService.h"
    29 #define NS_AUDIOMANAGER_CONTRACTID "@mozilla.org/telephony/audiomanager;1"
    30 #endif
    32 #include "mozilla/Preferences.h"
    34 using namespace mozilla;
    35 using namespace mozilla::dom;
    36 using namespace mozilla::hal;
    38 StaticRefPtr<AudioChannelService> gAudioChannelService;
    40 // Mappings from 'mozaudiochannel' attribute strings to an enumeration.
    41 static const nsAttrValue::EnumTable kMozAudioChannelAttributeTable[] = {
    42   { "normal",             (int16_t)AudioChannel::Normal },
    43   { "content",            (int16_t)AudioChannel::Content },
    44   { "notification",       (int16_t)AudioChannel::Notification },
    45   { "alarm",              (int16_t)AudioChannel::Alarm },
    46   { "telephony",          (int16_t)AudioChannel::Telephony },
    47   { "ringer",             (int16_t)AudioChannel::Ringer },
    48   { "publicnotification", (int16_t)AudioChannel::Publicnotification },
    49   { nullptr }
    50 };
    52 // static
    53 AudioChannelService*
    54 AudioChannelService::GetAudioChannelService()
    55 {
    56   MOZ_ASSERT(NS_IsMainThread());
    58   if (XRE_GetProcessType() != GeckoProcessType_Default) {
    59     return AudioChannelServiceChild::GetAudioChannelService();
    60   }
    62   // If we already exist, exit early
    63   if (gAudioChannelService) {
    64     return gAudioChannelService;
    65   }
    67   // Create new instance, register, return
    68   nsRefPtr<AudioChannelService> service = new AudioChannelService();
    69   NS_ENSURE_TRUE(service, nullptr);
    71   gAudioChannelService = service;
    72   return gAudioChannelService;
    73 }
    75 void
    76 AudioChannelService::Shutdown()
    77 {
    78   if (XRE_GetProcessType() != GeckoProcessType_Default) {
    79     return AudioChannelServiceChild::Shutdown();
    80   }
    82   if (gAudioChannelService) {
    83     gAudioChannelService = nullptr;
    84   }
    85 }
    87 NS_IMPL_ISUPPORTS(AudioChannelService, nsIObserver, nsITimerCallback)
    89 AudioChannelService::AudioChannelService()
    90 : mCurrentHigherChannel(-1)
    91 , mCurrentVisibleHigherChannel(-1)
    92 , mPlayableHiddenContentChildID(CONTENT_PROCESS_ID_UNKNOWN)
    93 , mDisabled(false)
    94 , mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN)
    95 {
    96   if (XRE_GetProcessType() == GeckoProcessType_Default) {
    97     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    98     if (obs) {
    99       obs->AddObserver(this, "ipc:content-shutdown", false);
   100       obs->AddObserver(this, "xpcom-shutdown", false);
   101 #ifdef MOZ_WIDGET_GONK
   102       // To monitor the volume settings based on audio channel.
   103       obs->AddObserver(this, "mozsettings-changed", false);
   104 #endif
   105     }
   106   }
   107 }
   109 AudioChannelService::~AudioChannelService()
   110 {
   111 }
   113 void
   114 AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
   115                                                AudioChannel aChannel,
   116                                                bool aWithVideo)
   117 {
   118   if (mDisabled) {
   119     return;
   120   }
   122   AudioChannelAgentData* data = new AudioChannelAgentData(aChannel,
   123                                 true /* aElementHidden */,
   124                                 AUDIO_CHANNEL_STATE_MUTED /* aState */,
   125                                 aWithVideo);
   126   mAgents.Put(aAgent, data);
   127   RegisterType(aChannel, CONTENT_PROCESS_ID_MAIN, aWithVideo);
   129   // If this is the first agent for this window, we must notify the observers.
   130   uint32_t count = CountWindow(aAgent->Window());
   131   if (count == 1) {
   132     nsCOMPtr<nsIObserverService> observerService =
   133       services::GetObserverService();
   134     if (observerService) {
   135       observerService->NotifyObservers(ToSupports(aAgent->Window()),
   136                                        "media-playback",
   137                                        NS_LITERAL_STRING("active").get());
   138     }
   139   }
   140 }
   142 void
   143 AudioChannelService::RegisterType(AudioChannel aChannel, uint64_t aChildID,
   144                                   bool aWithVideo)
   145 {
   146   if (mDisabled) {
   147     return;
   148   }
   150   AudioChannelInternalType type = GetInternalType(aChannel, true);
   151   mChannelCounters[type].AppendElement(aChildID);
   153   if (XRE_GetProcessType() == GeckoProcessType_Default) {
   154     // Since there is another telephony registered, we can unregister old one
   155     // immediately.
   156     if (mDeferTelChannelTimer && aChannel == AudioChannel::Telephony) {
   157       mDeferTelChannelTimer->Cancel();
   158       mDeferTelChannelTimer = nullptr;
   159       UnregisterTypeInternal(aChannel, mTimerElementHidden, mTimerChildID,
   160                              false);
   161     }
   163     if (aWithVideo) {
   164       mWithVideoChildIDs.AppendElement(aChildID);
   165     }
   167     // No hidden content channel can be playable if there is a content channel
   168     // in foreground (bug 855208), nor if there is a normal channel with video
   169     // in foreground (bug 894249).
   170     if (type == AUDIO_CHANNEL_INT_CONTENT ||
   171         (type == AUDIO_CHANNEL_INT_NORMAL &&
   172          mWithVideoChildIDs.Contains(aChildID))) {
   173       mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
   174     }
   175     // One hidden content channel can be playable only when there is no any
   176     // content channel in the foreground, and no normal channel with video in
   177     // foreground.
   178     else if (type == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
   179         mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
   180       mPlayableHiddenContentChildID = aChildID;
   181     }
   183     // In order to avoid race conditions, it's safer to notify any existing
   184     // agent any time a new one is registered.
   185     SendAudioChannelChangedNotification(aChildID);
   186     Notify();
   187   }
   188 }
   190 void
   191 AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
   192 {
   193   if (mDisabled) {
   194     return;
   195   }
   197   nsAutoPtr<AudioChannelAgentData> data;
   198   mAgents.RemoveAndForget(aAgent, data);
   200   if (data) {
   201     UnregisterType(data->mChannel, data->mElementHidden,
   202                    CONTENT_PROCESS_ID_MAIN, data->mWithVideo);
   203   }
   204 #ifdef MOZ_WIDGET_GONK
   205   bool active = AnyAudioChannelIsActive();
   206   for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
   207     mSpeakerManager[i]->SetAudioChannelActive(active);
   208   }
   209 #endif
   211   // If this is the last agent for this window, we must notify the observers.
   212   uint32_t count = CountWindow(aAgent->Window());
   213   if (count == 0) {
   214     nsCOMPtr<nsIObserverService> observerService =
   215       services::GetObserverService();
   216     if (observerService) {
   217       observerService->NotifyObservers(ToSupports(aAgent->Window()),
   218                                        "media-playback",
   219                                        NS_LITERAL_STRING("inactive").get());
   220     }
   221   }
   222 }
   224 void
   225 AudioChannelService::UnregisterType(AudioChannel aChannel,
   226                                     bool aElementHidden,
   227                                     uint64_t aChildID,
   228                                     bool aWithVideo)
   229 {
   230   if (mDisabled) {
   231     return;
   232   }
   234   // There are two reasons to defer the decrease of telephony channel.
   235   // 1. User can have time to remove device from his ear before music resuming.
   236   // 2. Give BT SCO to be disconnected before starting to connect A2DP.
   237   if (XRE_GetProcessType() == GeckoProcessType_Default &&
   238       aChannel == AudioChannel::Telephony &&
   239       (mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN].Length() +
   240        mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].Length()) == 1) {
   241     mTimerElementHidden = aElementHidden;
   242     mTimerChildID = aChildID;
   243     mDeferTelChannelTimer = do_CreateInstance("@mozilla.org/timer;1");
   244     mDeferTelChannelTimer->InitWithCallback(this, 1500, nsITimer::TYPE_ONE_SHOT);
   245     return;
   246   }
   248   UnregisterTypeInternal(aChannel, aElementHidden, aChildID, aWithVideo);
   249 }
   251 void
   252 AudioChannelService::UnregisterTypeInternal(AudioChannel aChannel,
   253                                             bool aElementHidden,
   254                                             uint64_t aChildID,
   255                                             bool aWithVideo)
   256 {
   257   // The array may contain multiple occurrence of this appId but
   258   // this should remove only the first one.
   259   AudioChannelInternalType type = GetInternalType(aChannel, aElementHidden);
   260   MOZ_ASSERT(mChannelCounters[type].Contains(aChildID));
   261   mChannelCounters[type].RemoveElement(aChildID);
   263   // In order to avoid race conditions, it's safer to notify any existing
   264   // agent any time a new one is registered.
   265   if (XRE_GetProcessType() == GeckoProcessType_Default) {
   266     // No hidden content channel is playable if the original playable hidden
   267     // process does not need to play audio from background anymore.
   268     if (aChannel == AudioChannel::Content &&
   269         mPlayableHiddenContentChildID == aChildID &&
   270         !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID)) {
   271       mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
   272     }
   274     if (aWithVideo) {
   275       MOZ_ASSERT(mWithVideoChildIDs.Contains(aChildID));
   276       mWithVideoChildIDs.RemoveElement(aChildID);
   277     }
   279     SendAudioChannelChangedNotification(aChildID);
   280     Notify();
   281   }
   282 }
   284 void
   285 AudioChannelService::UpdateChannelType(AudioChannel aChannel,
   286                                        uint64_t aChildID,
   287                                        bool aElementHidden,
   288                                        bool aElementWasHidden)
   289 {
   290   // Calculate the new and old internal type and update the hashtable if needed.
   291   AudioChannelInternalType newType = GetInternalType(aChannel, aElementHidden);
   292   AudioChannelInternalType oldType = GetInternalType(aChannel, aElementWasHidden);
   294   if (newType != oldType) {
   295     mChannelCounters[newType].AppendElement(aChildID);
   296     MOZ_ASSERT(mChannelCounters[oldType].Contains(aChildID));
   297     mChannelCounters[oldType].RemoveElement(aChildID);
   298   }
   300   // No hidden content channel can be playable if there is a content channel
   301   // in foreground (bug 855208), nor if there is a normal channel with video
   302   // in foreground (bug 894249).
   303   if (newType == AUDIO_CHANNEL_INT_CONTENT ||
   304       (newType == AUDIO_CHANNEL_INT_NORMAL &&
   305        mWithVideoChildIDs.Contains(aChildID))) {
   306     mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
   307   }
   308   // If there is no content channel in foreground and no normal channel with
   309   // video in foreground, the last content channel which goes from foreground
   310   // to background can be playable.
   311   else if (oldType == AUDIO_CHANNEL_INT_CONTENT &&
   312       newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
   313       mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
   314     mPlayableHiddenContentChildID = aChildID;
   315   }
   316 }
   318 AudioChannelState
   319 AudioChannelService::GetState(AudioChannelAgent* aAgent, bool aElementHidden)
   320 {
   321   AudioChannelAgentData* data;
   322   if (!mAgents.Get(aAgent, &data)) {
   323     return AUDIO_CHANNEL_STATE_MUTED;
   324   }
   326   bool oldElementHidden = data->mElementHidden;
   327   // Update visibility.
   328   data->mElementHidden = aElementHidden;
   330   data->mState = GetStateInternal(data->mChannel, CONTENT_PROCESS_ID_MAIN,
   331                                 aElementHidden, oldElementHidden);
   332   return data->mState;
   333 }
   335 AudioChannelState
   336 AudioChannelService::GetStateInternal(AudioChannel aChannel, uint64_t aChildID,
   337                                       bool aElementHidden,
   338                                       bool aElementWasHidden)
   339 {
   340   UpdateChannelType(aChannel, aChildID, aElementHidden, aElementWasHidden);
   342   // Calculating the new and old type and update the hashtable if needed.
   343   AudioChannelInternalType newType = GetInternalType(aChannel, aElementHidden);
   344   AudioChannelInternalType oldType = GetInternalType(aChannel,
   345                                                      aElementWasHidden);
   347   if (newType != oldType &&
   348       (aChannel == AudioChannel::Content ||
   349        (aChannel == AudioChannel::Normal &&
   350         mWithVideoChildIDs.Contains(aChildID)))) {
   351     Notify();
   352   }
   354   SendAudioChannelChangedNotification(aChildID);
   356   // Let play any visible audio channel.
   357   if (!aElementHidden) {
   358     if (CheckVolumeFadedCondition(newType, aElementHidden)) {
   359       return AUDIO_CHANNEL_STATE_FADED;
   360     }
   361     return AUDIO_CHANNEL_STATE_NORMAL;
   362   }
   364   // We are not visible, maybe we have to mute.
   365   if (newType == AUDIO_CHANNEL_INT_NORMAL_HIDDEN ||
   366       (newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
   367        // One process can have multiple content channels; and during the
   368        // transition from foreground to background, its content channels will be
   369        // updated with correct visibility status one by one. All its content
   370        // channels should remain playable until all of their visibility statuses
   371        // have been updated as hidden. After all its content channels have been
   372        // updated properly as hidden, mPlayableHiddenContentChildID is used to
   373        // check whether this background process is playable or not.
   374        !(mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) ||
   375          (mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() &&
   376           mPlayableHiddenContentChildID == aChildID)))) {
   377     return AUDIO_CHANNEL_STATE_MUTED;
   378   }
   380   // After checking the condition on normal & content channel, if the state
   381   // is not on muted then checking other higher channels type here.
   382   if (ChannelsActiveWithHigherPriorityThan(newType)) {
   383     MOZ_ASSERT(newType != AUDIO_CHANNEL_INT_NORMAL_HIDDEN);
   384     if (CheckVolumeFadedCondition(newType, aElementHidden)) {
   385       return AUDIO_CHANNEL_STATE_FADED;
   386     }
   387     return AUDIO_CHANNEL_STATE_MUTED;
   388   }
   390   return AUDIO_CHANNEL_STATE_NORMAL;
   391 }
   393 bool
   394 AudioChannelService::CheckVolumeFadedCondition(AudioChannelInternalType aType,
   395                                                bool aElementHidden)
   396 {
   397   // Only normal & content channels are considered
   398   if (aType > AUDIO_CHANNEL_INT_CONTENT_HIDDEN) {
   399     return false;
   400   }
   402   // Consider that audio from notification is with short duration
   403   // so just fade the volume not pause it
   404   if (mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION].IsEmpty() &&
   405       mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN].IsEmpty()) {
   406     return false;
   407   }
   409   // Since this element is on the foreground, it can be allowed to play always.
   410   // So return true directly when there is any notification channel alive.
   411   if (aElementHidden == false) {
   412    return true;
   413   }
   415   // If element is on the background, it is possible paused by channels higher
   416   // then notification.
   417   for (int i = AUDIO_CHANNEL_INT_LAST - 1;
   418     i != AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN; --i) {
   419     if (!mChannelCounters[i].IsEmpty()) {
   420       return false;
   421     }
   422   }
   424   return true;
   425 }
   427 bool
   428 AudioChannelService::ContentOrNormalChannelIsActive()
   429 {
   430   return !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() ||
   431          !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].IsEmpty() ||
   432          !mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty();
   433 }
   435 bool
   436 AudioChannelService::ProcessContentOrNormalChannelIsActive(uint64_t aChildID)
   437 {
   438   return mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) ||
   439          mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID) ||
   440          mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].Contains(aChildID);
   441 }
   443 void
   444 AudioChannelService::SetDefaultVolumeControlChannel(int32_t aChannel,
   445                                                     bool aHidden)
   446 {
   447   SetDefaultVolumeControlChannelInternal(aChannel, aHidden,
   448                                          CONTENT_PROCESS_ID_MAIN);
   449 }
   451 void
   452 AudioChannelService::SetDefaultVolumeControlChannelInternal(int32_t aChannel,
   453                                                             bool aHidden,
   454                                                             uint64_t aChildID)
   455 {
   456   if (XRE_GetProcessType() != GeckoProcessType_Default) {
   457     return;
   458   }
   460   // If this child is in the background and mDefChannelChildID is set to
   461   // others then it means other child in the foreground already set it's
   462   // own default channel already.
   463   if (!aHidden && mDefChannelChildID != aChildID) {
   464     return;
   465   }
   467   mDefChannelChildID = aChildID;
   468   nsString channelName;
   470   if (aChannel == -1) {
   471     channelName.AssignASCII("unknown");
   472   } else {
   473     GetAudioChannelString(static_cast<AudioChannel>(aChannel), channelName);
   474   }
   476   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   477   if (obs) {
   478     obs->NotifyObservers(nullptr, "default-volume-channel-changed",
   479                          channelName.get());
   480   }
   481 }
   483 void
   484 AudioChannelService::SendAudioChannelChangedNotification(uint64_t aChildID)
   485 {
   486   if (XRE_GetProcessType() != GeckoProcessType_Default) {
   487     return;
   488   }
   490   nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
   491   props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), aChildID);
   493   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   494   if (obs) {
   495     obs->NotifyObservers(static_cast<nsIWritablePropertyBag*>(props),
   496                          "audio-channel-process-changed", nullptr);
   497   }
   499   // Calculating the most important active channel.
   500   int32_t higher = -1;
   502   // Top-Down in the hierarchy for visible elements
   503   if (!mChannelCounters[AUDIO_CHANNEL_INT_PUBLICNOTIFICATION].IsEmpty()) {
   504     higher = static_cast<int32_t>(AudioChannel::Publicnotification);
   505   }
   507   else if (!mChannelCounters[AUDIO_CHANNEL_INT_RINGER].IsEmpty()) {
   508     higher = static_cast<int32_t>(AudioChannel::Ringer);
   509   }
   511   else if (!mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].IsEmpty()) {
   512     higher = static_cast<int32_t>(AudioChannel::Telephony);
   513   }
   515   else if (!mChannelCounters[AUDIO_CHANNEL_INT_ALARM].IsEmpty()) {
   516     higher = static_cast<int32_t>(AudioChannel::Alarm);
   517   }
   519   else if (!mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION].IsEmpty()) {
   520     higher = static_cast<int32_t>(AudioChannel::Notification);
   521   }
   523   else if (!mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
   524     higher = static_cast<int32_t>(AudioChannel::Content);
   525   }
   527   else if (!mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty()) {
   528     higher = static_cast<int32_t>(AudioChannel::Normal);
   529   }
   531   int32_t visibleHigher = higher;
   533   // Top-Down in the hierarchy for non-visible elements
   534   // And we can ignore normal channel because it can't play in the background.
   535   int32_t index;
   536   for (index = 0; kMozAudioChannelAttributeTable[index].tag; ++index);
   538   for (--index;
   539        kMozAudioChannelAttributeTable[index].value > higher &&
   540        kMozAudioChannelAttributeTable[index].value > (int16_t)AudioChannel::Normal;
   541        --index) {
   542     if (kMozAudioChannelAttributeTable[index].value == (int16_t)AudioChannel::Content &&
   543       mPlayableHiddenContentChildID != CONTENT_PROCESS_ID_UNKNOWN) {
   544       higher = kMozAudioChannelAttributeTable[index].value;
   545     }
   547     // Each channel type will be split to fg and bg for recording the state,
   548     // so here need to do a translation.
   549     if (!mChannelCounters[index * 2 + 1].IsEmpty()) {
   550       higher = kMozAudioChannelAttributeTable[index].value;
   551       break;
   552     }
   553   }
   555   if (higher != mCurrentHigherChannel) {
   556     mCurrentHigherChannel = higher;
   558     nsString channelName;
   559     if (mCurrentHigherChannel != -1) {
   560       GetAudioChannelString(static_cast<AudioChannel>(mCurrentHigherChannel),
   561                             channelName);
   562     } else {
   563       channelName.AssignLiteral("none");
   564     }
   566     if (obs) {
   567       obs->NotifyObservers(nullptr, "audio-channel-changed", channelName.get());
   568     }
   569   }
   571   if (visibleHigher != mCurrentVisibleHigherChannel) {
   572     mCurrentVisibleHigherChannel = visibleHigher;
   574     nsString channelName;
   575     if (mCurrentVisibleHigherChannel != -1) {
   576       GetAudioChannelString(static_cast<AudioChannel>(mCurrentVisibleHigherChannel),
   577                             channelName);
   578     } else {
   579       channelName.AssignLiteral("none");
   580     }
   582     if (obs) {
   583       obs->NotifyObservers(nullptr, "visible-audio-channel-changed", channelName.get());
   584     }
   585   }
   586 }
   588 PLDHashOperator
   589 AudioChannelService::NotifyEnumerator(AudioChannelAgent* aAgent,
   590                                       AudioChannelAgentData* aData, void* aUnused)
   591 {
   592   MOZ_ASSERT(aAgent);
   593   aAgent->NotifyAudioChannelStateChanged();
   594   return PL_DHASH_NEXT;
   595 }
   597 void
   598 AudioChannelService::Notify()
   599 {
   600   MOZ_ASSERT(NS_IsMainThread());
   602   // Notify any agent for the main process.
   603   mAgents.EnumerateRead(NotifyEnumerator, nullptr);
   605   // Notify for the child processes.
   606   nsTArray<ContentParent*> children;
   607   ContentParent::GetAll(children);
   608   for (uint32_t i = 0; i < children.Length(); i++) {
   609     unused << children[i]->SendAudioChannelNotify();
   610   }
   611 }
   613 NS_IMETHODIMP
   614 AudioChannelService::Notify(nsITimer* aTimer)
   615 {
   616   UnregisterTypeInternal(AudioChannel::Telephony, mTimerElementHidden,
   617                          mTimerChildID, false);
   618   mDeferTelChannelTimer = nullptr;
   619   return NS_OK;
   620 }
   622 bool
   623 AudioChannelService::AnyAudioChannelIsActive()
   624 {
   625   for (int i = AUDIO_CHANNEL_INT_LAST - 1;
   626        i >= AUDIO_CHANNEL_INT_NORMAL; --i) {
   627     if (!mChannelCounters[i].IsEmpty()) {
   628       return true;
   629     }
   630   }
   632   return false;
   633 }
   635 bool
   636 AudioChannelService::ChannelsActiveWithHigherPriorityThan(
   637   AudioChannelInternalType aType)
   638 {
   639   for (int i = AUDIO_CHANNEL_INT_LAST - 1;
   640        i != AUDIO_CHANNEL_INT_CONTENT_HIDDEN; --i) {
   641     if (i == aType) {
   642       return false;
   643     }
   645     if (!mChannelCounters[i].IsEmpty()) {
   646       return true;
   647     }
   648   }
   650   return false;
   651 }
   653 NS_IMETHODIMP
   654 AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
   655 {
   656   if (!strcmp(aTopic, "xpcom-shutdown")) {
   657     mDisabled = true;
   658   }
   660   if (!strcmp(aTopic, "ipc:content-shutdown")) {
   661     nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
   662     if (!props) {
   663       NS_WARNING("ipc:content-shutdown message without property bag as subject");
   664       return NS_OK;
   665     }
   667     int32_t index;
   668     uint64_t childID = 0;
   669     nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"),
   670                                              &childID);
   671     if (NS_SUCCEEDED(rv)) {
   672       for (int32_t type = AUDIO_CHANNEL_INT_NORMAL;
   673            type < AUDIO_CHANNEL_INT_LAST;
   674            ++type) {
   676         while ((index = mChannelCounters[type].IndexOf(childID)) != -1) {
   677           mChannelCounters[type].RemoveElementAt(index);
   678         }
   679       }
   681       // No hidden content channel is playable if the original playable hidden
   682       // process shuts down.
   683       if (mPlayableHiddenContentChildID == childID) {
   684         mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN;
   685       }
   687       while ((index = mWithVideoChildIDs.IndexOf(childID)) != -1) {
   688         mWithVideoChildIDs.RemoveElementAt(index);
   689       }
   691       // We don't have to remove the agents from the mAgents hashtable because if
   692       // that table contains only agents running on the same process.
   694       SendAudioChannelChangedNotification(childID);
   695       Notify();
   697       if (mDefChannelChildID == childID) {
   698         SetDefaultVolumeControlChannelInternal(-1, false, childID);
   699         mDefChannelChildID = CONTENT_PROCESS_ID_UNKNOWN;
   700       }
   701     } else {
   702       NS_WARNING("ipc:content-shutdown message without childID property");
   703     }
   704   }
   705 #ifdef MOZ_WIDGET_GONK
   706   // To process the volume control on each audio channel according to
   707   // change of settings
   708   else if (!strcmp(aTopic, "mozsettings-changed")) {
   709     AutoSafeJSContext cx;
   710     nsDependentString dataStr(aData);
   711     JS::Rooted<JS::Value> val(cx);
   712     if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) ||
   713         !val.isObject()) {
   714       return NS_OK;
   715     }
   717     JS::Rooted<JSObject*> obj(cx, &val.toObject());
   718     JS::Rooted<JS::Value> key(cx);
   719     if (!JS_GetProperty(cx, obj, "key", &key) ||
   720         !key.isString()) {
   721       return NS_OK;
   722     }
   724     JS::Rooted<JSString*> jsKey(cx, JS::ToString(cx, key));
   725     if (!jsKey) {
   726       return NS_OK;
   727     }
   728     nsDependentJSString keyStr;
   729     if (!keyStr.init(cx, jsKey) || keyStr.Find("audio.volume.", 0, false)) {
   730       return NS_OK;
   731     }
   733     JS::Rooted<JS::Value> value(cx);
   734     if (!JS_GetProperty(cx, obj, "value", &value) || !value.isInt32()) {
   735       return NS_OK;
   736     }
   738     nsCOMPtr<nsIAudioManager> audioManager = do_GetService(NS_AUDIOMANAGER_CONTRACTID);
   739     NS_ENSURE_TRUE(audioManager, NS_OK);
   741     int32_t index = value.toInt32();
   742     if (keyStr.EqualsLiteral("audio.volume.content")) {
   743       audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Content, index);
   744     } else if (keyStr.EqualsLiteral("audio.volume.notification")) {
   745       audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Notification, index);
   746     } else if (keyStr.EqualsLiteral("audio.volume.alarm")) {
   747       audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Alarm, index);
   748     } else if (keyStr.EqualsLiteral("audio.volume.telephony")) {
   749       audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Telephony, index);
   750     } else if (!keyStr.EqualsLiteral("audio.volume.bt_sco")) {
   751       // bt_sco is not a valid audio channel so we manipulate it in
   752       // AudioManager.cpp. And the others should not be used.
   753       // We didn't use MOZ_ASSUME_UNREACHABLE here because any web content who
   754       // has permission of mozSettings can set any names then it can be easy to
   755       // crash the B2G.
   756       NS_WARNING("unexpected audio channel for volume control");
   757     }
   758   }
   759 #endif
   761   return NS_OK;
   762 }
   764 AudioChannelService::AudioChannelInternalType
   765 AudioChannelService::GetInternalType(AudioChannel aChannel,
   766                                      bool aElementHidden)
   767 {
   768   switch (aChannel) {
   769     case AudioChannel::Normal:
   770       return aElementHidden
   771                ? AUDIO_CHANNEL_INT_NORMAL_HIDDEN
   772                : AUDIO_CHANNEL_INT_NORMAL;
   774     case AudioChannel::Content:
   775       return aElementHidden
   776                ? AUDIO_CHANNEL_INT_CONTENT_HIDDEN
   777                : AUDIO_CHANNEL_INT_CONTENT;
   779     case AudioChannel::Notification:
   780       return aElementHidden
   781                ? AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN
   782                : AUDIO_CHANNEL_INT_NOTIFICATION;
   784     case AudioChannel::Alarm:
   785       return aElementHidden
   786                ? AUDIO_CHANNEL_INT_ALARM_HIDDEN
   787                : AUDIO_CHANNEL_INT_ALARM;
   789     case AudioChannel::Telephony:
   790       return aElementHidden
   791                ? AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN
   792                : AUDIO_CHANNEL_INT_TELEPHONY;
   794     case AudioChannel::Ringer:
   795       return aElementHidden
   796                ? AUDIO_CHANNEL_INT_RINGER_HIDDEN
   797                : AUDIO_CHANNEL_INT_RINGER;
   799     case AudioChannel::Publicnotification:
   800       return aElementHidden
   801                ? AUDIO_CHANNEL_INT_PUBLICNOTIFICATION_HIDDEN
   802                : AUDIO_CHANNEL_INT_PUBLICNOTIFICATION;
   804     default:
   805       break;
   806   }
   808   MOZ_CRASH("unexpected audio channel");
   809 }
   811 struct RefreshAgentsVolumeData
   812 {
   813   RefreshAgentsVolumeData(nsPIDOMWindow* aWindow)
   814     : mWindow(aWindow)
   815   {}
   817   nsPIDOMWindow* mWindow;
   818   nsTArray<nsRefPtr<AudioChannelAgent>> mAgents;
   819 };
   821 PLDHashOperator
   822 AudioChannelService::RefreshAgentsVolumeEnumerator(AudioChannelAgent* aAgent,
   823                                                    AudioChannelAgentData* aUnused,
   824                                                    void* aPtr)
   825 {
   826   MOZ_ASSERT(aAgent);
   827   RefreshAgentsVolumeData* data = static_cast<RefreshAgentsVolumeData*>(aPtr);
   828   MOZ_ASSERT(data);
   830   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aAgent->Window());
   831   if (window && !window->IsInnerWindow()) {
   832     window = window->GetCurrentInnerWindow();
   833   }
   835   if (window == data->mWindow) {
   836     data->mAgents.AppendElement(aAgent);
   837   }
   839   return PL_DHASH_NEXT;
   840 }
   841 void
   842 AudioChannelService::RefreshAgentsVolume(nsPIDOMWindow* aWindow)
   843 {
   844   RefreshAgentsVolumeData data(aWindow);
   845   mAgents.EnumerateRead(RefreshAgentsVolumeEnumerator, &data);
   847   for (uint32_t i = 0; i < data.mAgents.Length(); ++i) {
   848     data.mAgents[i]->WindowVolumeChanged();
   849   }
   850 }
   852 struct CountWindowData
   853 {
   854   CountWindowData(nsIDOMWindow* aWindow)
   855     : mWindow(aWindow)
   856     , mCount(0)
   857   {}
   859   nsIDOMWindow* mWindow;
   860   uint32_t mCount;
   861 };
   863 PLDHashOperator
   864 AudioChannelService::CountWindowEnumerator(AudioChannelAgent* aAgent,
   865                                            AudioChannelAgentData* aUnused,
   866                                            void* aPtr)
   867 {
   868   CountWindowData* data = static_cast<CountWindowData*>(aPtr);
   869   MOZ_ASSERT(aAgent);
   871   if (aAgent->Window() == data->mWindow) {
   872     ++data->mCount;
   873   }
   875   return PL_DHASH_NEXT;
   876 }
   878 uint32_t
   879 AudioChannelService::CountWindow(nsIDOMWindow* aWindow)
   880 {
   881   CountWindowData data(aWindow);
   882   mAgents.EnumerateRead(CountWindowEnumerator, &data);
   883   return data.mCount;
   884 }
   886 /* static */ const nsAttrValue::EnumTable*
   887 AudioChannelService::GetAudioChannelTable()
   888 {
   889   return kMozAudioChannelAttributeTable;
   890 }
   892 /* static */ AudioChannel
   893 AudioChannelService::GetAudioChannel(const nsAString& aChannel)
   894 {
   895   for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
   896     if (aChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
   897       return static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value);
   898     }
   899   }
   901   return AudioChannel::Normal;
   902 }
   904 /* static */ AudioChannel
   905 AudioChannelService::GetDefaultAudioChannel()
   906 {
   907   nsString audioChannel = Preferences::GetString("media.defaultAudioChannel");
   908   if (audioChannel.IsEmpty()) {
   909     return AudioChannel::Normal;
   910   }
   912   for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
   913     if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
   914       return static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value);
   915     }
   916   }
   918   return AudioChannel::Normal;
   919 }
   921 /* static */ void
   922 AudioChannelService::GetAudioChannelString(AudioChannel aChannel,
   923                                            nsAString& aString)
   924 {
   925   aString.AssignASCII("normal");
   927   for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
   928     if (aChannel ==
   929         static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value)) {
   930       aString.AssignASCII(kMozAudioChannelAttributeTable[i].tag);
   931       break;
   932     }
   933   }
   934 }
   936 /* static */ void
   937 AudioChannelService::GetDefaultAudioChannelString(nsAString& aString)
   938 {
   939   aString.AssignASCII("normal");
   941   nsString audioChannel = Preferences::GetString("media.defaultAudioChannel");
   942   if (!audioChannel.IsEmpty()) {
   943     for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
   944       if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
   945         aString = audioChannel;
   946         break;
   947       }
   948     }
   949   }
   950 }

mercurial