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