michael@0: /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "AudioChannelService.h" michael@0: #include "AudioChannelServiceChild.h" michael@0: michael@0: #include "base/basictypes.h" michael@0: michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "mozilla/unused.h" michael@0: michael@0: #include "mozilla/dom/ContentParent.h" michael@0: michael@0: #include "nsThreadUtils.h" michael@0: #include "nsHashPropertyBag.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: #include "nsJSUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsIAudioManager.h" michael@0: #include "SpeakerManagerService.h" michael@0: #define NS_AUDIOMANAGER_CONTRACTID "@mozilla.org/telephony/audiomanager;1" michael@0: #endif michael@0: michael@0: #include "mozilla/Preferences.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: using namespace mozilla::hal; michael@0: michael@0: StaticRefPtr gAudioChannelService; michael@0: michael@0: // Mappings from 'mozaudiochannel' attribute strings to an enumeration. michael@0: static const nsAttrValue::EnumTable kMozAudioChannelAttributeTable[] = { michael@0: { "normal", (int16_t)AudioChannel::Normal }, michael@0: { "content", (int16_t)AudioChannel::Content }, michael@0: { "notification", (int16_t)AudioChannel::Notification }, michael@0: { "alarm", (int16_t)AudioChannel::Alarm }, michael@0: { "telephony", (int16_t)AudioChannel::Telephony }, michael@0: { "ringer", (int16_t)AudioChannel::Ringer }, michael@0: { "publicnotification", (int16_t)AudioChannel::Publicnotification }, michael@0: { nullptr } michael@0: }; michael@0: michael@0: // static michael@0: AudioChannelService* michael@0: AudioChannelService::GetAudioChannelService() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default) { michael@0: return AudioChannelServiceChild::GetAudioChannelService(); michael@0: } michael@0: michael@0: // If we already exist, exit early michael@0: if (gAudioChannelService) { michael@0: return gAudioChannelService; michael@0: } michael@0: michael@0: // Create new instance, register, return michael@0: nsRefPtr service = new AudioChannelService(); michael@0: NS_ENSURE_TRUE(service, nullptr); michael@0: michael@0: gAudioChannelService = service; michael@0: return gAudioChannelService; michael@0: } michael@0: michael@0: void michael@0: AudioChannelService::Shutdown() michael@0: { michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default) { michael@0: return AudioChannelServiceChild::Shutdown(); michael@0: } michael@0: michael@0: if (gAudioChannelService) { michael@0: gAudioChannelService = nullptr; michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(AudioChannelService, nsIObserver, nsITimerCallback) michael@0: michael@0: AudioChannelService::AudioChannelService() michael@0: : mCurrentHigherChannel(-1) michael@0: , mCurrentVisibleHigherChannel(-1) michael@0: , mPlayableHiddenContentChildID(CONTENT_PROCESS_ID_UNKNOWN) michael@0: , mDisabled(false) michael@0: , mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN) michael@0: { michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (obs) { michael@0: obs->AddObserver(this, "ipc:content-shutdown", false); michael@0: obs->AddObserver(this, "xpcom-shutdown", false); michael@0: #ifdef MOZ_WIDGET_GONK michael@0: // To monitor the volume settings based on audio channel. michael@0: obs->AddObserver(this, "mozsettings-changed", false); michael@0: #endif michael@0: } michael@0: } michael@0: } michael@0: michael@0: AudioChannelService::~AudioChannelService() michael@0: { michael@0: } michael@0: michael@0: void michael@0: AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent, michael@0: AudioChannel aChannel, michael@0: bool aWithVideo) michael@0: { michael@0: if (mDisabled) { michael@0: return; michael@0: } michael@0: michael@0: AudioChannelAgentData* data = new AudioChannelAgentData(aChannel, michael@0: true /* aElementHidden */, michael@0: AUDIO_CHANNEL_STATE_MUTED /* aState */, michael@0: aWithVideo); michael@0: mAgents.Put(aAgent, data); michael@0: RegisterType(aChannel, CONTENT_PROCESS_ID_MAIN, aWithVideo); michael@0: michael@0: // If this is the first agent for this window, we must notify the observers. michael@0: uint32_t count = CountWindow(aAgent->Window()); michael@0: if (count == 1) { michael@0: nsCOMPtr observerService = michael@0: services::GetObserverService(); michael@0: if (observerService) { michael@0: observerService->NotifyObservers(ToSupports(aAgent->Window()), michael@0: "media-playback", michael@0: NS_LITERAL_STRING("active").get()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: AudioChannelService::RegisterType(AudioChannel aChannel, uint64_t aChildID, michael@0: bool aWithVideo) michael@0: { michael@0: if (mDisabled) { michael@0: return; michael@0: } michael@0: michael@0: AudioChannelInternalType type = GetInternalType(aChannel, true); michael@0: mChannelCounters[type].AppendElement(aChildID); michael@0: michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: // Since there is another telephony registered, we can unregister old one michael@0: // immediately. michael@0: if (mDeferTelChannelTimer && aChannel == AudioChannel::Telephony) { michael@0: mDeferTelChannelTimer->Cancel(); michael@0: mDeferTelChannelTimer = nullptr; michael@0: UnregisterTypeInternal(aChannel, mTimerElementHidden, mTimerChildID, michael@0: false); michael@0: } michael@0: michael@0: if (aWithVideo) { michael@0: mWithVideoChildIDs.AppendElement(aChildID); michael@0: } michael@0: michael@0: // No hidden content channel can be playable if there is a content channel michael@0: // in foreground (bug 855208), nor if there is a normal channel with video michael@0: // in foreground (bug 894249). michael@0: if (type == AUDIO_CHANNEL_INT_CONTENT || michael@0: (type == AUDIO_CHANNEL_INT_NORMAL && michael@0: mWithVideoChildIDs.Contains(aChildID))) { michael@0: mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN; michael@0: } michael@0: // One hidden content channel can be playable only when there is no any michael@0: // content channel in the foreground, and no normal channel with video in michael@0: // foreground. michael@0: else if (type == AUDIO_CHANNEL_INT_CONTENT_HIDDEN && michael@0: mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) { michael@0: mPlayableHiddenContentChildID = aChildID; michael@0: } michael@0: michael@0: // In order to avoid race conditions, it's safer to notify any existing michael@0: // agent any time a new one is registered. michael@0: SendAudioChannelChangedNotification(aChildID); michael@0: Notify(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent) michael@0: { michael@0: if (mDisabled) { michael@0: return; michael@0: } michael@0: michael@0: nsAutoPtr data; michael@0: mAgents.RemoveAndForget(aAgent, data); michael@0: michael@0: if (data) { michael@0: UnregisterType(data->mChannel, data->mElementHidden, michael@0: CONTENT_PROCESS_ID_MAIN, data->mWithVideo); michael@0: } michael@0: #ifdef MOZ_WIDGET_GONK michael@0: bool active = AnyAudioChannelIsActive(); michael@0: for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) { michael@0: mSpeakerManager[i]->SetAudioChannelActive(active); michael@0: } michael@0: #endif michael@0: michael@0: // If this is the last agent for this window, we must notify the observers. michael@0: uint32_t count = CountWindow(aAgent->Window()); michael@0: if (count == 0) { michael@0: nsCOMPtr observerService = michael@0: services::GetObserverService(); michael@0: if (observerService) { michael@0: observerService->NotifyObservers(ToSupports(aAgent->Window()), michael@0: "media-playback", michael@0: NS_LITERAL_STRING("inactive").get()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: AudioChannelService::UnregisterType(AudioChannel aChannel, michael@0: bool aElementHidden, michael@0: uint64_t aChildID, michael@0: bool aWithVideo) michael@0: { michael@0: if (mDisabled) { michael@0: return; michael@0: } michael@0: michael@0: // There are two reasons to defer the decrease of telephony channel. michael@0: // 1. User can have time to remove device from his ear before music resuming. michael@0: // 2. Give BT SCO to be disconnected before starting to connect A2DP. michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default && michael@0: aChannel == AudioChannel::Telephony && michael@0: (mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN].Length() + michael@0: mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].Length()) == 1) { michael@0: mTimerElementHidden = aElementHidden; michael@0: mTimerChildID = aChildID; michael@0: mDeferTelChannelTimer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: mDeferTelChannelTimer->InitWithCallback(this, 1500, nsITimer::TYPE_ONE_SHOT); michael@0: return; michael@0: } michael@0: michael@0: UnregisterTypeInternal(aChannel, aElementHidden, aChildID, aWithVideo); michael@0: } michael@0: michael@0: void michael@0: AudioChannelService::UnregisterTypeInternal(AudioChannel aChannel, michael@0: bool aElementHidden, michael@0: uint64_t aChildID, michael@0: bool aWithVideo) michael@0: { michael@0: // The array may contain multiple occurrence of this appId but michael@0: // this should remove only the first one. michael@0: AudioChannelInternalType type = GetInternalType(aChannel, aElementHidden); michael@0: MOZ_ASSERT(mChannelCounters[type].Contains(aChildID)); michael@0: mChannelCounters[type].RemoveElement(aChildID); michael@0: michael@0: // In order to avoid race conditions, it's safer to notify any existing michael@0: // agent any time a new one is registered. michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: // No hidden content channel is playable if the original playable hidden michael@0: // process does not need to play audio from background anymore. michael@0: if (aChannel == AudioChannel::Content && michael@0: mPlayableHiddenContentChildID == aChildID && michael@0: !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID)) { michael@0: mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN; michael@0: } michael@0: michael@0: if (aWithVideo) { michael@0: MOZ_ASSERT(mWithVideoChildIDs.Contains(aChildID)); michael@0: mWithVideoChildIDs.RemoveElement(aChildID); michael@0: } michael@0: michael@0: SendAudioChannelChangedNotification(aChildID); michael@0: Notify(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: AudioChannelService::UpdateChannelType(AudioChannel aChannel, michael@0: uint64_t aChildID, michael@0: bool aElementHidden, michael@0: bool aElementWasHidden) michael@0: { michael@0: // Calculate the new and old internal type and update the hashtable if needed. michael@0: AudioChannelInternalType newType = GetInternalType(aChannel, aElementHidden); michael@0: AudioChannelInternalType oldType = GetInternalType(aChannel, aElementWasHidden); michael@0: michael@0: if (newType != oldType) { michael@0: mChannelCounters[newType].AppendElement(aChildID); michael@0: MOZ_ASSERT(mChannelCounters[oldType].Contains(aChildID)); michael@0: mChannelCounters[oldType].RemoveElement(aChildID); michael@0: } michael@0: michael@0: // No hidden content channel can be playable if there is a content channel michael@0: // in foreground (bug 855208), nor if there is a normal channel with video michael@0: // in foreground (bug 894249). michael@0: if (newType == AUDIO_CHANNEL_INT_CONTENT || michael@0: (newType == AUDIO_CHANNEL_INT_NORMAL && michael@0: mWithVideoChildIDs.Contains(aChildID))) { michael@0: mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN; michael@0: } michael@0: // If there is no content channel in foreground and no normal channel with michael@0: // video in foreground, the last content channel which goes from foreground michael@0: // to background can be playable. michael@0: else if (oldType == AUDIO_CHANNEL_INT_CONTENT && michael@0: newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN && michael@0: mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) { michael@0: mPlayableHiddenContentChildID = aChildID; michael@0: } michael@0: } michael@0: michael@0: AudioChannelState michael@0: AudioChannelService::GetState(AudioChannelAgent* aAgent, bool aElementHidden) michael@0: { michael@0: AudioChannelAgentData* data; michael@0: if (!mAgents.Get(aAgent, &data)) { michael@0: return AUDIO_CHANNEL_STATE_MUTED; michael@0: } michael@0: michael@0: bool oldElementHidden = data->mElementHidden; michael@0: // Update visibility. michael@0: data->mElementHidden = aElementHidden; michael@0: michael@0: data->mState = GetStateInternal(data->mChannel, CONTENT_PROCESS_ID_MAIN, michael@0: aElementHidden, oldElementHidden); michael@0: return data->mState; michael@0: } michael@0: michael@0: AudioChannelState michael@0: AudioChannelService::GetStateInternal(AudioChannel aChannel, uint64_t aChildID, michael@0: bool aElementHidden, michael@0: bool aElementWasHidden) michael@0: { michael@0: UpdateChannelType(aChannel, aChildID, aElementHidden, aElementWasHidden); michael@0: michael@0: // Calculating the new and old type and update the hashtable if needed. michael@0: AudioChannelInternalType newType = GetInternalType(aChannel, aElementHidden); michael@0: AudioChannelInternalType oldType = GetInternalType(aChannel, michael@0: aElementWasHidden); michael@0: michael@0: if (newType != oldType && michael@0: (aChannel == AudioChannel::Content || michael@0: (aChannel == AudioChannel::Normal && michael@0: mWithVideoChildIDs.Contains(aChildID)))) { michael@0: Notify(); michael@0: } michael@0: michael@0: SendAudioChannelChangedNotification(aChildID); michael@0: michael@0: // Let play any visible audio channel. michael@0: if (!aElementHidden) { michael@0: if (CheckVolumeFadedCondition(newType, aElementHidden)) { michael@0: return AUDIO_CHANNEL_STATE_FADED; michael@0: } michael@0: return AUDIO_CHANNEL_STATE_NORMAL; michael@0: } michael@0: michael@0: // We are not visible, maybe we have to mute. michael@0: if (newType == AUDIO_CHANNEL_INT_NORMAL_HIDDEN || michael@0: (newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN && michael@0: // One process can have multiple content channels; and during the michael@0: // transition from foreground to background, its content channels will be michael@0: // updated with correct visibility status one by one. All its content michael@0: // channels should remain playable until all of their visibility statuses michael@0: // have been updated as hidden. After all its content channels have been michael@0: // updated properly as hidden, mPlayableHiddenContentChildID is used to michael@0: // check whether this background process is playable or not. michael@0: !(mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) || michael@0: (mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() && michael@0: mPlayableHiddenContentChildID == aChildID)))) { michael@0: return AUDIO_CHANNEL_STATE_MUTED; michael@0: } michael@0: michael@0: // After checking the condition on normal & content channel, if the state michael@0: // is not on muted then checking other higher channels type here. michael@0: if (ChannelsActiveWithHigherPriorityThan(newType)) { michael@0: MOZ_ASSERT(newType != AUDIO_CHANNEL_INT_NORMAL_HIDDEN); michael@0: if (CheckVolumeFadedCondition(newType, aElementHidden)) { michael@0: return AUDIO_CHANNEL_STATE_FADED; michael@0: } michael@0: return AUDIO_CHANNEL_STATE_MUTED; michael@0: } michael@0: michael@0: return AUDIO_CHANNEL_STATE_NORMAL; michael@0: } michael@0: michael@0: bool michael@0: AudioChannelService::CheckVolumeFadedCondition(AudioChannelInternalType aType, michael@0: bool aElementHidden) michael@0: { michael@0: // Only normal & content channels are considered michael@0: if (aType > AUDIO_CHANNEL_INT_CONTENT_HIDDEN) { michael@0: return false; michael@0: } michael@0: michael@0: // Consider that audio from notification is with short duration michael@0: // so just fade the volume not pause it michael@0: if (mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION].IsEmpty() && michael@0: mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN].IsEmpty()) { michael@0: return false; michael@0: } michael@0: michael@0: // Since this element is on the foreground, it can be allowed to play always. michael@0: // So return true directly when there is any notification channel alive. michael@0: if (aElementHidden == false) { michael@0: return true; michael@0: } michael@0: michael@0: // If element is on the background, it is possible paused by channels higher michael@0: // then notification. michael@0: for (int i = AUDIO_CHANNEL_INT_LAST - 1; michael@0: i != AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN; --i) { michael@0: if (!mChannelCounters[i].IsEmpty()) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: AudioChannelService::ContentOrNormalChannelIsActive() michael@0: { michael@0: return !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() || michael@0: !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].IsEmpty() || michael@0: !mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty(); michael@0: } michael@0: michael@0: bool michael@0: AudioChannelService::ProcessContentOrNormalChannelIsActive(uint64_t aChildID) michael@0: { michael@0: return mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) || michael@0: mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID) || michael@0: mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].Contains(aChildID); michael@0: } michael@0: michael@0: void michael@0: AudioChannelService::SetDefaultVolumeControlChannel(int32_t aChannel, michael@0: bool aHidden) michael@0: { michael@0: SetDefaultVolumeControlChannelInternal(aChannel, aHidden, michael@0: CONTENT_PROCESS_ID_MAIN); michael@0: } michael@0: michael@0: void michael@0: AudioChannelService::SetDefaultVolumeControlChannelInternal(int32_t aChannel, michael@0: bool aHidden, michael@0: uint64_t aChildID) michael@0: { michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default) { michael@0: return; michael@0: } michael@0: michael@0: // If this child is in the background and mDefChannelChildID is set to michael@0: // others then it means other child in the foreground already set it's michael@0: // own default channel already. michael@0: if (!aHidden && mDefChannelChildID != aChildID) { michael@0: return; michael@0: } michael@0: michael@0: mDefChannelChildID = aChildID; michael@0: nsString channelName; michael@0: michael@0: if (aChannel == -1) { michael@0: channelName.AssignASCII("unknown"); michael@0: } else { michael@0: GetAudioChannelString(static_cast(aChannel), channelName); michael@0: } michael@0: michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (obs) { michael@0: obs->NotifyObservers(nullptr, "default-volume-channel-changed", michael@0: channelName.get()); michael@0: } michael@0: } michael@0: michael@0: void michael@0: AudioChannelService::SendAudioChannelChangedNotification(uint64_t aChildID) michael@0: { michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default) { michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr props = new nsHashPropertyBag(); michael@0: props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), aChildID); michael@0: michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (obs) { michael@0: obs->NotifyObservers(static_cast(props), michael@0: "audio-channel-process-changed", nullptr); michael@0: } michael@0: michael@0: // Calculating the most important active channel. michael@0: int32_t higher = -1; michael@0: michael@0: // Top-Down in the hierarchy for visible elements michael@0: if (!mChannelCounters[AUDIO_CHANNEL_INT_PUBLICNOTIFICATION].IsEmpty()) { michael@0: higher = static_cast(AudioChannel::Publicnotification); michael@0: } michael@0: michael@0: else if (!mChannelCounters[AUDIO_CHANNEL_INT_RINGER].IsEmpty()) { michael@0: higher = static_cast(AudioChannel::Ringer); michael@0: } michael@0: michael@0: else if (!mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].IsEmpty()) { michael@0: higher = static_cast(AudioChannel::Telephony); michael@0: } michael@0: michael@0: else if (!mChannelCounters[AUDIO_CHANNEL_INT_ALARM].IsEmpty()) { michael@0: higher = static_cast(AudioChannel::Alarm); michael@0: } michael@0: michael@0: else if (!mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION].IsEmpty()) { michael@0: higher = static_cast(AudioChannel::Notification); michael@0: } michael@0: michael@0: else if (!mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) { michael@0: higher = static_cast(AudioChannel::Content); michael@0: } michael@0: michael@0: else if (!mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty()) { michael@0: higher = static_cast(AudioChannel::Normal); michael@0: } michael@0: michael@0: int32_t visibleHigher = higher; michael@0: michael@0: // Top-Down in the hierarchy for non-visible elements michael@0: // And we can ignore normal channel because it can't play in the background. michael@0: int32_t index; michael@0: for (index = 0; kMozAudioChannelAttributeTable[index].tag; ++index); michael@0: michael@0: for (--index; michael@0: kMozAudioChannelAttributeTable[index].value > higher && michael@0: kMozAudioChannelAttributeTable[index].value > (int16_t)AudioChannel::Normal; michael@0: --index) { michael@0: if (kMozAudioChannelAttributeTable[index].value == (int16_t)AudioChannel::Content && michael@0: mPlayableHiddenContentChildID != CONTENT_PROCESS_ID_UNKNOWN) { michael@0: higher = kMozAudioChannelAttributeTable[index].value; michael@0: } michael@0: michael@0: // Each channel type will be split to fg and bg for recording the state, michael@0: // so here need to do a translation. michael@0: if (!mChannelCounters[index * 2 + 1].IsEmpty()) { michael@0: higher = kMozAudioChannelAttributeTable[index].value; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (higher != mCurrentHigherChannel) { michael@0: mCurrentHigherChannel = higher; michael@0: michael@0: nsString channelName; michael@0: if (mCurrentHigherChannel != -1) { michael@0: GetAudioChannelString(static_cast(mCurrentHigherChannel), michael@0: channelName); michael@0: } else { michael@0: channelName.AssignLiteral("none"); michael@0: } michael@0: michael@0: if (obs) { michael@0: obs->NotifyObservers(nullptr, "audio-channel-changed", channelName.get()); michael@0: } michael@0: } michael@0: michael@0: if (visibleHigher != mCurrentVisibleHigherChannel) { michael@0: mCurrentVisibleHigherChannel = visibleHigher; michael@0: michael@0: nsString channelName; michael@0: if (mCurrentVisibleHigherChannel != -1) { michael@0: GetAudioChannelString(static_cast(mCurrentVisibleHigherChannel), michael@0: channelName); michael@0: } else { michael@0: channelName.AssignLiteral("none"); michael@0: } michael@0: michael@0: if (obs) { michael@0: obs->NotifyObservers(nullptr, "visible-audio-channel-changed", channelName.get()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: PLDHashOperator michael@0: AudioChannelService::NotifyEnumerator(AudioChannelAgent* aAgent, michael@0: AudioChannelAgentData* aData, void* aUnused) michael@0: { michael@0: MOZ_ASSERT(aAgent); michael@0: aAgent->NotifyAudioChannelStateChanged(); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: AudioChannelService::Notify() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Notify any agent for the main process. michael@0: mAgents.EnumerateRead(NotifyEnumerator, nullptr); michael@0: michael@0: // Notify for the child processes. michael@0: nsTArray children; michael@0: ContentParent::GetAll(children); michael@0: for (uint32_t i = 0; i < children.Length(); i++) { michael@0: unused << children[i]->SendAudioChannelNotify(); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AudioChannelService::Notify(nsITimer* aTimer) michael@0: { michael@0: UnregisterTypeInternal(AudioChannel::Telephony, mTimerElementHidden, michael@0: mTimerChildID, false); michael@0: mDeferTelChannelTimer = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: AudioChannelService::AnyAudioChannelIsActive() michael@0: { michael@0: for (int i = AUDIO_CHANNEL_INT_LAST - 1; michael@0: i >= AUDIO_CHANNEL_INT_NORMAL; --i) { michael@0: if (!mChannelCounters[i].IsEmpty()) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: AudioChannelService::ChannelsActiveWithHigherPriorityThan( michael@0: AudioChannelInternalType aType) michael@0: { michael@0: for (int i = AUDIO_CHANNEL_INT_LAST - 1; michael@0: i != AUDIO_CHANNEL_INT_CONTENT_HIDDEN; --i) { michael@0: if (i == aType) { michael@0: return false; michael@0: } michael@0: michael@0: if (!mChannelCounters[i].IsEmpty()) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) michael@0: { michael@0: if (!strcmp(aTopic, "xpcom-shutdown")) { michael@0: mDisabled = true; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, "ipc:content-shutdown")) { michael@0: nsCOMPtr props = do_QueryInterface(aSubject); michael@0: if (!props) { michael@0: NS_WARNING("ipc:content-shutdown message without property bag as subject"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t index; michael@0: uint64_t childID = 0; michael@0: nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), michael@0: &childID); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: for (int32_t type = AUDIO_CHANNEL_INT_NORMAL; michael@0: type < AUDIO_CHANNEL_INT_LAST; michael@0: ++type) { michael@0: michael@0: while ((index = mChannelCounters[type].IndexOf(childID)) != -1) { michael@0: mChannelCounters[type].RemoveElementAt(index); michael@0: } michael@0: } michael@0: michael@0: // No hidden content channel is playable if the original playable hidden michael@0: // process shuts down. michael@0: if (mPlayableHiddenContentChildID == childID) { michael@0: mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN; michael@0: } michael@0: michael@0: while ((index = mWithVideoChildIDs.IndexOf(childID)) != -1) { michael@0: mWithVideoChildIDs.RemoveElementAt(index); michael@0: } michael@0: michael@0: // We don't have to remove the agents from the mAgents hashtable because if michael@0: // that table contains only agents running on the same process. michael@0: michael@0: SendAudioChannelChangedNotification(childID); michael@0: Notify(); michael@0: michael@0: if (mDefChannelChildID == childID) { michael@0: SetDefaultVolumeControlChannelInternal(-1, false, childID); michael@0: mDefChannelChildID = CONTENT_PROCESS_ID_UNKNOWN; michael@0: } michael@0: } else { michael@0: NS_WARNING("ipc:content-shutdown message without childID property"); michael@0: } michael@0: } michael@0: #ifdef MOZ_WIDGET_GONK michael@0: // To process the volume control on each audio channel according to michael@0: // change of settings michael@0: else if (!strcmp(aTopic, "mozsettings-changed")) { michael@0: AutoSafeJSContext cx; michael@0: nsDependentString dataStr(aData); michael@0: JS::Rooted val(cx); michael@0: if (!JS_ParseJSON(cx, dataStr.get(), dataStr.Length(), &val) || michael@0: !val.isObject()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: JS::Rooted obj(cx, &val.toObject()); michael@0: JS::Rooted key(cx); michael@0: if (!JS_GetProperty(cx, obj, "key", &key) || michael@0: !key.isString()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: JS::Rooted jsKey(cx, JS::ToString(cx, key)); michael@0: if (!jsKey) { michael@0: return NS_OK; michael@0: } michael@0: nsDependentJSString keyStr; michael@0: if (!keyStr.init(cx, jsKey) || keyStr.Find("audio.volume.", 0, false)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: JS::Rooted value(cx); michael@0: if (!JS_GetProperty(cx, obj, "value", &value) || !value.isInt32()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr audioManager = do_GetService(NS_AUDIOMANAGER_CONTRACTID); michael@0: NS_ENSURE_TRUE(audioManager, NS_OK); michael@0: michael@0: int32_t index = value.toInt32(); michael@0: if (keyStr.EqualsLiteral("audio.volume.content")) { michael@0: audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Content, index); michael@0: } else if (keyStr.EqualsLiteral("audio.volume.notification")) { michael@0: audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Notification, index); michael@0: } else if (keyStr.EqualsLiteral("audio.volume.alarm")) { michael@0: audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Alarm, index); michael@0: } else if (keyStr.EqualsLiteral("audio.volume.telephony")) { michael@0: audioManager->SetAudioChannelVolume((int32_t)AudioChannel::Telephony, index); michael@0: } else if (!keyStr.EqualsLiteral("audio.volume.bt_sco")) { michael@0: // bt_sco is not a valid audio channel so we manipulate it in michael@0: // AudioManager.cpp. And the others should not be used. michael@0: // We didn't use MOZ_ASSUME_UNREACHABLE here because any web content who michael@0: // has permission of mozSettings can set any names then it can be easy to michael@0: // crash the B2G. michael@0: NS_WARNING("unexpected audio channel for volume control"); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: AudioChannelService::AudioChannelInternalType michael@0: AudioChannelService::GetInternalType(AudioChannel aChannel, michael@0: bool aElementHidden) michael@0: { michael@0: switch (aChannel) { michael@0: case AudioChannel::Normal: michael@0: return aElementHidden michael@0: ? AUDIO_CHANNEL_INT_NORMAL_HIDDEN michael@0: : AUDIO_CHANNEL_INT_NORMAL; michael@0: michael@0: case AudioChannel::Content: michael@0: return aElementHidden michael@0: ? AUDIO_CHANNEL_INT_CONTENT_HIDDEN michael@0: : AUDIO_CHANNEL_INT_CONTENT; michael@0: michael@0: case AudioChannel::Notification: michael@0: return aElementHidden michael@0: ? AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN michael@0: : AUDIO_CHANNEL_INT_NOTIFICATION; michael@0: michael@0: case AudioChannel::Alarm: michael@0: return aElementHidden michael@0: ? AUDIO_CHANNEL_INT_ALARM_HIDDEN michael@0: : AUDIO_CHANNEL_INT_ALARM; michael@0: michael@0: case AudioChannel::Telephony: michael@0: return aElementHidden michael@0: ? AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN michael@0: : AUDIO_CHANNEL_INT_TELEPHONY; michael@0: michael@0: case AudioChannel::Ringer: michael@0: return aElementHidden michael@0: ? AUDIO_CHANNEL_INT_RINGER_HIDDEN michael@0: : AUDIO_CHANNEL_INT_RINGER; michael@0: michael@0: case AudioChannel::Publicnotification: michael@0: return aElementHidden michael@0: ? AUDIO_CHANNEL_INT_PUBLICNOTIFICATION_HIDDEN michael@0: : AUDIO_CHANNEL_INT_PUBLICNOTIFICATION; michael@0: michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: MOZ_CRASH("unexpected audio channel"); michael@0: } michael@0: michael@0: struct RefreshAgentsVolumeData michael@0: { michael@0: RefreshAgentsVolumeData(nsPIDOMWindow* aWindow) michael@0: : mWindow(aWindow) michael@0: {} michael@0: michael@0: nsPIDOMWindow* mWindow; michael@0: nsTArray> mAgents; michael@0: }; michael@0: michael@0: PLDHashOperator michael@0: AudioChannelService::RefreshAgentsVolumeEnumerator(AudioChannelAgent* aAgent, michael@0: AudioChannelAgentData* aUnused, michael@0: void* aPtr) michael@0: { michael@0: MOZ_ASSERT(aAgent); michael@0: RefreshAgentsVolumeData* data = static_cast(aPtr); michael@0: MOZ_ASSERT(data); michael@0: michael@0: nsCOMPtr window = do_QueryInterface(aAgent->Window()); michael@0: if (window && !window->IsInnerWindow()) { michael@0: window = window->GetCurrentInnerWindow(); michael@0: } michael@0: michael@0: if (window == data->mWindow) { michael@0: data->mAgents.AppendElement(aAgent); michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: void michael@0: AudioChannelService::RefreshAgentsVolume(nsPIDOMWindow* aWindow) michael@0: { michael@0: RefreshAgentsVolumeData data(aWindow); michael@0: mAgents.EnumerateRead(RefreshAgentsVolumeEnumerator, &data); michael@0: michael@0: for (uint32_t i = 0; i < data.mAgents.Length(); ++i) { michael@0: data.mAgents[i]->WindowVolumeChanged(); michael@0: } michael@0: } michael@0: michael@0: struct CountWindowData michael@0: { michael@0: CountWindowData(nsIDOMWindow* aWindow) michael@0: : mWindow(aWindow) michael@0: , mCount(0) michael@0: {} michael@0: michael@0: nsIDOMWindow* mWindow; michael@0: uint32_t mCount; michael@0: }; michael@0: michael@0: PLDHashOperator michael@0: AudioChannelService::CountWindowEnumerator(AudioChannelAgent* aAgent, michael@0: AudioChannelAgentData* aUnused, michael@0: void* aPtr) michael@0: { michael@0: CountWindowData* data = static_cast(aPtr); michael@0: MOZ_ASSERT(aAgent); michael@0: michael@0: if (aAgent->Window() == data->mWindow) { michael@0: ++data->mCount; michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: uint32_t michael@0: AudioChannelService::CountWindow(nsIDOMWindow* aWindow) michael@0: { michael@0: CountWindowData data(aWindow); michael@0: mAgents.EnumerateRead(CountWindowEnumerator, &data); michael@0: return data.mCount; michael@0: } michael@0: michael@0: /* static */ const nsAttrValue::EnumTable* michael@0: AudioChannelService::GetAudioChannelTable() michael@0: { michael@0: return kMozAudioChannelAttributeTable; michael@0: } michael@0: michael@0: /* static */ AudioChannel michael@0: AudioChannelService::GetAudioChannel(const nsAString& aChannel) michael@0: { michael@0: for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) { michael@0: if (aChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) { michael@0: return static_cast(kMozAudioChannelAttributeTable[i].value); michael@0: } michael@0: } michael@0: michael@0: return AudioChannel::Normal; michael@0: } michael@0: michael@0: /* static */ AudioChannel michael@0: AudioChannelService::GetDefaultAudioChannel() michael@0: { michael@0: nsString audioChannel = Preferences::GetString("media.defaultAudioChannel"); michael@0: if (audioChannel.IsEmpty()) { michael@0: return AudioChannel::Normal; michael@0: } michael@0: michael@0: for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) { michael@0: if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) { michael@0: return static_cast(kMozAudioChannelAttributeTable[i].value); michael@0: } michael@0: } michael@0: michael@0: return AudioChannel::Normal; michael@0: } michael@0: michael@0: /* static */ void michael@0: AudioChannelService::GetAudioChannelString(AudioChannel aChannel, michael@0: nsAString& aString) michael@0: { michael@0: aString.AssignASCII("normal"); michael@0: michael@0: for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) { michael@0: if (aChannel == michael@0: static_cast(kMozAudioChannelAttributeTable[i].value)) { michael@0: aString.AssignASCII(kMozAudioChannelAttributeTable[i].tag); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: AudioChannelService::GetDefaultAudioChannelString(nsAString& aString) michael@0: { michael@0: aString.AssignASCII("normal"); michael@0: michael@0: nsString audioChannel = Preferences::GetString("media.defaultAudioChannel"); michael@0: if (!audioChannel.IsEmpty()) { michael@0: for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) { michael@0: if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) { michael@0: aString = audioChannel; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: }