summaryrefslogtreecommitdiffstats
path: root/dom/audiochannel
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/audiochannel
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/audiochannel')
-rw-r--r--dom/audiochannel/AudioChannelAgent.cpp370
-rw-r--r--dom/audiochannel/AudioChannelAgent.h86
-rw-r--r--dom/audiochannel/AudioChannelService.cpp1437
-rw-r--r--dom/audiochannel/AudioChannelService.h376
-rw-r--r--dom/audiochannel/crashtests/1223734.html17
-rw-r--r--dom/audiochannel/crashtests/crashtests.list1
-rw-r--r--dom/audiochannel/moz.build30
-rw-r--r--dom/audiochannel/nsIAudioChannelAgent.idl187
-rw-r--r--dom/audiochannel/nsIAudioChannelService.idl29
9 files changed, 2533 insertions, 0 deletions
diff --git a/dom/audiochannel/AudioChannelAgent.cpp b/dom/audiochannel/AudioChannelAgent.cpp
new file mode 100644
index 000000000..700ecc378
--- /dev/null
+++ b/dom/audiochannel/AudioChannelAgent.cpp
@@ -0,0 +1,370 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 "AudioChannelAgent.h"
+#include "AudioChannelService.h"
+#include "mozilla/Preferences.h"
+#include "nsContentUtils.h"
+#include "nsIDocument.h"
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIURI.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(AudioChannelAgent)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioChannelAgent)
+ tmp->Shutdown();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioChannelAgent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioChannelAgent)
+ NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgent)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(AudioChannelAgent)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(AudioChannelAgent)
+
+AudioChannelAgent::AudioChannelAgent()
+ : mAudioChannelType(AUDIO_AGENT_CHANNEL_ERROR)
+ , mInnerWindowID(0)
+ , mIsRegToService(false)
+{
+}
+
+AudioChannelAgent::~AudioChannelAgent()
+{
+ Shutdown();
+}
+
+void
+AudioChannelAgent::Shutdown()
+{
+ if (mIsRegToService) {
+ NotifyStoppedPlaying();
+ }
+}
+
+NS_IMETHODIMP AudioChannelAgent::GetAudioChannelType(int32_t *aAudioChannelType)
+{
+ *aAudioChannelType = mAudioChannelType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AudioChannelAgent::Init(mozIDOMWindow* aWindow, int32_t aChannelType,
+ nsIAudioChannelAgentCallback *aCallback)
+{
+ return InitInternal(nsPIDOMWindowInner::From(aWindow), aChannelType,
+ aCallback, /* useWeakRef = */ false);
+}
+
+NS_IMETHODIMP
+AudioChannelAgent::InitWithWeakCallback(mozIDOMWindow* aWindow,
+ int32_t aChannelType,
+ nsIAudioChannelAgentCallback *aCallback)
+{
+ return InitInternal(nsPIDOMWindowInner::From(aWindow), aChannelType,
+ aCallback, /* useWeakRef = */ true);
+}
+
+nsresult
+AudioChannelAgent::FindCorrectWindow(nsPIDOMWindowInner* aWindow)
+{
+ MOZ_ASSERT(aWindow->IsInnerWindow());
+
+ mWindow = aWindow->GetScriptableTop();
+ if (NS_WARN_IF(!mWindow)) {
+ return NS_OK;
+ }
+
+ // From here we do an hack for nested iframes.
+ // The system app doesn't have access to the nested iframe objects so it
+ // cannot control the volume of the agents running in nested apps. What we do
+ // here is to assign those Agents to the top scriptable window of the parent
+ // iframe (what is controlled by the system app).
+ // For doing this we go recursively back into the chain of windows until we
+ // find apps that are not the system one.
+ nsCOMPtr<nsPIDOMWindowOuter> outerParent = mWindow->GetParent();
+ if (!outerParent || outerParent == mWindow) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> parent = outerParent->GetCurrentInnerWindow();
+ if (!parent) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDocument> doc = parent->GetExtantDoc();
+ if (!doc) {
+ return NS_OK;
+ }
+
+ if (nsContentUtils::IsChromeDoc(doc)) {
+ return NS_OK;
+ }
+
+ nsAdoptingCString systemAppUrl =
+ mozilla::Preferences::GetCString("b2g.system_startup_url");
+ if (!systemAppUrl) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+ nsCOMPtr<nsIURI> uri;
+ principal->GetURI(getter_AddRefs(uri));
+
+ if (uri) {
+ nsAutoCString spec;
+ uri->GetSpec(spec);
+
+ if (spec.Equals(systemAppUrl)) {
+ return NS_OK;
+ }
+ }
+
+ return FindCorrectWindow(parent);
+}
+
+nsresult
+AudioChannelAgent::InitInternal(nsPIDOMWindowInner* aWindow,
+ int32_t aChannelType,
+ nsIAudioChannelAgentCallback *aCallback,
+ bool aUseWeakRef)
+{
+ // We syncd the enum of channel type between nsIAudioChannelAgent.idl and
+ // AudioChannelBinding.h the same.
+ MOZ_ASSERT(int(AUDIO_AGENT_CHANNEL_NORMAL) == int(AudioChannel::Normal) &&
+ int(AUDIO_AGENT_CHANNEL_CONTENT) == int(AudioChannel::Content) &&
+ int(AUDIO_AGENT_CHANNEL_NOTIFICATION) == int(AudioChannel::Notification) &&
+ int(AUDIO_AGENT_CHANNEL_ALARM) == int(AudioChannel::Alarm) &&
+ int(AUDIO_AGENT_CHANNEL_TELEPHONY) == int(AudioChannel::Telephony) &&
+ int(AUDIO_AGENT_CHANNEL_RINGER) == int(AudioChannel::Ringer) &&
+ int(AUDIO_AGENT_CHANNEL_SYSTEM) == int(AudioChannel::System) &&
+ int(AUDIO_AGENT_CHANNEL_PUBLICNOTIFICATION) == int(AudioChannel::Publicnotification),
+ "Enum of channel on nsIAudioChannelAgent.idl should be the same with AudioChannelBinding.h");
+
+ if (mAudioChannelType != AUDIO_AGENT_CHANNEL_ERROR ||
+ aChannelType > AUDIO_AGENT_CHANNEL_SYSTEM ||
+ aChannelType < AUDIO_AGENT_CHANNEL_NORMAL) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(!aWindow)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(aWindow->IsInnerWindow());
+ mInnerWindowID = aWindow->WindowID();
+
+ nsresult rv = FindCorrectWindow(aWindow);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mAudioChannelType = aChannelType;
+
+ if (aUseWeakRef) {
+ mWeakCallback = do_GetWeakReference(aCallback);
+ } else {
+ mCallback = aCallback;
+ }
+
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioChannelAgent, InitInternal, this = %p, type = %d, "
+ "owner = %p, hasCallback = %d\n", this, mAudioChannelType,
+ mWindow.get(), (!!mCallback || !!mWeakCallback)));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AudioChannelAgent::NotifyStartedPlaying(AudioPlaybackConfig* aConfig,
+ uint8_t aAudible)
+{
+ if (NS_WARN_IF(!aConfig)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+ if (mAudioChannelType == AUDIO_AGENT_CHANNEL_ERROR ||
+ service == nullptr || mIsRegToService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(AudioChannelService::AudibleState::eNotAudible == 0 &&
+ AudioChannelService::AudibleState::eMaybeAudible == 1 &&
+ AudioChannelService::AudibleState::eAudible == 2);
+ service->RegisterAudioChannelAgent(this,
+ static_cast<AudioChannelService::AudibleState>(aAudible));
+
+ AudioPlaybackConfig config = service->GetMediaConfig(mWindow,
+ mAudioChannelType);
+
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioChannelAgent, NotifyStartedPlaying, this = %p, "
+ "audible = %d, mute = %d, volume = %f, suspend = %d\n", this,
+ aAudible, config.mMuted, config.mVolume, config.mSuspend));
+
+ aConfig->SetConfig(config.mVolume, config.mMuted, config.mSuspend);
+ mIsRegToService = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AudioChannelAgent::NotifyStoppedPlaying()
+{
+ if (mAudioChannelType == AUDIO_AGENT_CHANNEL_ERROR ||
+ !mIsRegToService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioChannelAgent, NotifyStoppedPlaying, this = %p\n", this));
+
+ RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+ if (service) {
+ service->UnregisterAudioChannelAgent(this);
+ }
+
+ mIsRegToService = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AudioChannelAgent::NotifyStartedAudible(uint8_t aAudible, uint32_t aReason)
+{
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioChannelAgent, NotifyStartedAudible, this = %p, "
+ "audible = %d, reason = %d\n", this, aAudible, aReason));
+
+ RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+ if (NS_WARN_IF(!service)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ service->AudioAudibleChanged(
+ this,
+ static_cast<AudioChannelService::AudibleState>(aAudible),
+ static_cast<AudioChannelService::AudibleChangedReasons>(aReason));
+ return NS_OK;
+}
+
+already_AddRefed<nsIAudioChannelAgentCallback>
+AudioChannelAgent::GetCallback()
+{
+ nsCOMPtr<nsIAudioChannelAgentCallback> callback = mCallback;
+ if (!callback) {
+ callback = do_QueryReferent(mWeakCallback);
+ }
+ return callback.forget();
+}
+
+void
+AudioChannelAgent::WindowVolumeChanged()
+{
+ nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
+ if (!callback) {
+ return;
+ }
+
+ AudioPlaybackConfig config = GetMediaConfig();
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioChannelAgent, WindowVolumeChanged, this = %p, mute = %d, "
+ "volume = %f\n", this, config.mMuted, config.mVolume));
+
+ callback->WindowVolumeChanged(config.mVolume, config.mMuted);
+}
+
+void
+AudioChannelAgent::WindowSuspendChanged(nsSuspendedTypes aSuspend)
+{
+ nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
+ if (!callback) {
+ return;
+ }
+
+ if (!IsDisposableSuspend(aSuspend)) {
+ aSuspend = GetMediaConfig().mSuspend;
+ }
+
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioChannelAgent, WindowSuspendChanged, this = %p, "
+ "suspended = %d\n", this, aSuspend));
+
+ callback->WindowSuspendChanged(aSuspend);
+}
+
+AudioPlaybackConfig
+AudioChannelAgent::GetMediaConfig()
+{
+ RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+ AudioPlaybackConfig config(1.0, false, nsISuspendedTypes::NONE_SUSPENDED);
+ if (service) {
+ config = service->GetMediaConfig(mWindow, mAudioChannelType);
+ }
+ return config;
+}
+
+bool
+AudioChannelAgent::IsDisposableSuspend(nsSuspendedTypes aSuspend) const
+{
+ return (aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE ||
+ aSuspend == nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE);
+}
+
+uint64_t
+AudioChannelAgent::WindowID() const
+{
+ return mWindow ? mWindow->WindowID() : 0;
+}
+
+uint64_t
+AudioChannelAgent::InnerWindowID() const
+{
+ return mInnerWindowID;
+}
+
+void
+AudioChannelAgent::WindowAudioCaptureChanged(uint64_t aInnerWindowID,
+ bool aCapture)
+{
+ if (aInnerWindowID != mInnerWindowID) {
+ return;
+ }
+
+ nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
+ if (!callback) {
+ return;
+ }
+
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioChannelAgent, WindowAudioCaptureChanged, this = %p, "
+ "capture = %d\n", this, aCapture));
+
+ callback->WindowAudioCaptureChanged(aCapture);
+}
+
+bool
+AudioChannelAgent::IsPlayingStarted() const
+{
+ return mIsRegToService;
+}
+
+bool
+AudioChannelAgent::ShouldBlockMedia() const
+{
+ return mWindow ?
+ mWindow->GetMediaSuspend() == nsISuspendedTypes::SUSPENDED_BLOCK : false;
+}
diff --git a/dom/audiochannel/AudioChannelAgent.h b/dom/audiochannel/AudioChannelAgent.h
new file mode 100644
index 000000000..f7e776d85
--- /dev/null
+++ b/dom/audiochannel/AudioChannelAgent.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+#ifndef mozilla_dom_audio_channel_agent_h__
+#define mozilla_dom_audio_channel_agent_h__
+
+#include "nsIAudioChannelAgent.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+#include "nsWeakPtr.h"
+
+#define NS_AUDIOCHANNELAGENT_CONTRACTID "@mozilla.org/audiochannelagent;1"
+// f27688e2-3dd7-11e2-904e-10bf48d64bd4
+#define NS_AUDIOCHANNELAGENT_CID {0xf27688e2, 0x3dd7, 0x11e2, \
+ {0x90, 0x4e, 0x10, 0xbf, 0x48, 0xd6, 0x4b, 0xd4}}
+
+class nsPIDOMWindowInner;
+class nsPIDOMWindowOuter;
+
+namespace mozilla {
+namespace dom {
+
+class AudioPlaybackConfig;
+
+/* Header file */
+class AudioChannelAgent : public nsIAudioChannelAgent
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIAUDIOCHANNELAGENT
+
+ NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgent)
+
+ AudioChannelAgent();
+
+ void WindowVolumeChanged();
+ void WindowSuspendChanged(nsSuspendedTypes aSuspend);
+ void WindowAudioCaptureChanged(uint64_t aInnerWindowID, bool aCapture);
+
+ nsPIDOMWindowOuter* Window() const
+ {
+ return mWindow;
+ }
+
+ uint64_t WindowID() const;
+ uint64_t InnerWindowID() const;
+
+ bool IsPlayingStarted() const;
+ bool ShouldBlockMedia() const;
+
+private:
+ virtual ~AudioChannelAgent();
+
+ AudioPlaybackConfig GetMediaConfig();
+ bool IsDisposableSuspend(nsSuspendedTypes aSuspend) const;
+
+ // Returns mCallback if that's non-null, or otherwise tries to get an
+ // nsIAudioChannelAgentCallback out of mWeakCallback.
+ already_AddRefed<nsIAudioChannelAgentCallback> GetCallback();
+
+ nsresult InitInternal(nsPIDOMWindowInner* aWindow, int32_t aAudioAgentType,
+ nsIAudioChannelAgentCallback* aCallback,
+ bool aUseWeakRef);
+
+ void Shutdown();
+
+ nsresult FindCorrectWindow(nsPIDOMWindowInner* aWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+ nsCOMPtr<nsIAudioChannelAgentCallback> mCallback;
+
+ nsWeakPtr mWeakCallback;
+
+ int32_t mAudioChannelType;
+ uint64_t mInnerWindowID;
+ bool mIsRegToService;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+
+#endif
diff --git a/dom/audiochannel/AudioChannelService.cpp b/dom/audiochannel/AudioChannelService.cpp
new file mode 100644
index 000000000..87cde41e9
--- /dev/null
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -0,0 +1,1437 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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"
+
+#include "base/basictypes.h"
+
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Unused.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/TabParent.h"
+
+#include "nsContentUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsISupportsPrimitives.h"
+#include "nsThreadUtils.h"
+#include "nsHashPropertyBag.h"
+#include "nsComponentManagerUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
+
+#ifdef MOZ_WIDGET_GONK
+#include "nsJSUtils.h"
+#include "SpeakerManagerService.h"
+#endif
+
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::hal;
+
+namespace {
+
+// If true, any new AudioChannelAgent will be muted when created.
+bool sAudioChannelMutedByDefault = false;
+bool sAudioChannelCompeting = false;
+bool sAudioChannelCompetingAllAgents = false;
+bool sXPCOMShuttingDown = false;
+
+class NotifyChannelActiveRunnable final : public Runnable
+{
+public:
+ NotifyChannelActiveRunnable(uint64_t aWindowID, AudioChannel aAudioChannel,
+ bool aActive)
+ : mWindowID(aWindowID)
+ , mAudioChannel(aAudioChannel)
+ , mActive(aActive)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (NS_WARN_IF(!observerService)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsISupportsPRUint64> wrapper =
+ do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
+ if (NS_WARN_IF(!wrapper)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ wrapper->SetData(mWindowID);
+
+ nsAutoString name;
+ AudioChannelService::GetAudioChannelString(mAudioChannel, name);
+
+ nsAutoCString topic;
+ topic.Assign("audiochannel-activity-");
+ topic.Append(NS_ConvertUTF16toUTF8(name));
+
+ observerService->NotifyObservers(wrapper, topic.get(),
+ mActive
+ ? u"active"
+ : u"inactive");
+
+ // TODO : remove b2g related event in bug1299390.
+ observerService->NotifyObservers(wrapper,
+ "media-playback",
+ mActive
+ ? u"active"
+ : u"inactive");
+
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("NotifyChannelActiveRunnable, type = %d, active = %d\n",
+ mAudioChannel, mActive));
+
+ return NS_OK;
+ }
+
+private:
+ const uint64_t mWindowID;
+ const AudioChannel mAudioChannel;
+ const bool mActive;
+};
+
+bool
+IsParentProcess()
+{
+ return XRE_GetProcessType() == GeckoProcessType_Default;
+}
+
+class AudioPlaybackRunnable final : public Runnable
+{
+public:
+ AudioPlaybackRunnable(nsPIDOMWindowOuter* aWindow, bool aActive,
+ AudioChannelService::AudibleChangedReasons aReason)
+ : mWindow(aWindow)
+ , mActive(aActive)
+ , mReason(aReason)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (NS_WARN_IF(!observerService)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString state;
+ GetActiveState(state);
+
+ observerService->NotifyObservers(ToSupports(mWindow),
+ "audio-playback",
+ state.get());
+
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioPlaybackRunnable, active = %d, reason = %d\n",
+ mActive, mReason));
+
+ return NS_OK;
+ }
+
+private:
+ void GetActiveState(nsAString& astate)
+ {
+ if (mActive) {
+ CopyASCIItoUTF16("active", astate);
+ } else {
+ if(mReason == AudioChannelService::AudibleChangedReasons::ePauseStateChanged) {
+ CopyASCIItoUTF16("inactive-pause", astate);
+ } else {
+ CopyASCIItoUTF16("inactive-nonaudible", astate);
+ }
+ }
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+ bool mActive;
+ AudioChannelService::AudibleChangedReasons mReason;
+};
+
+bool
+IsEnableAudioCompetingForAllAgents()
+{
+ // In general, the audio competing should only be for audible media and it
+ // helps user can focus on one media at the same time. However, we hope to
+ // treat all media as the same in the mobile device. First reason is we have
+ // media control on fennec and we just want to control one media at once time.
+ // Second reason is to reduce the bandwidth, avoiding to play any non-audible
+ // media in background which user doesn't notice about.
+#ifdef MOZ_WIDGET_ANDROID
+ return true;
+#else
+ return sAudioChannelCompetingAllAgents;
+#endif
+}
+
+} // anonymous namespace
+
+StaticRefPtr<AudioChannelService> gAudioChannelService;
+
+// Mappings from 'mozaudiochannel' attribute strings to an enumeration.
+static const nsAttrValue::EnumTable kMozAudioChannelAttributeTable[] = {
+ { "normal", (int16_t)AudioChannel::Normal },
+ { "content", (int16_t)AudioChannel::Content },
+ { "notification", (int16_t)AudioChannel::Notification },
+ { "alarm", (int16_t)AudioChannel::Alarm },
+ { "telephony", (int16_t)AudioChannel::Telephony },
+ { "ringer", (int16_t)AudioChannel::Ringer },
+ { "publicnotification", (int16_t)AudioChannel::Publicnotification },
+ { "system", (int16_t)AudioChannel::System },
+ { nullptr, 0 }
+};
+
+/* static */ void
+AudioChannelService::CreateServiceIfNeeded()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gAudioChannelService) {
+ gAudioChannelService = new AudioChannelService();
+ }
+}
+
+/* static */ already_AddRefed<AudioChannelService>
+AudioChannelService::GetOrCreate()
+{
+ if (sXPCOMShuttingDown) {
+ return nullptr;
+ }
+
+ CreateServiceIfNeeded();
+ RefPtr<AudioChannelService> service = gAudioChannelService.get();
+ return service.forget();
+}
+
+/* static */ PRLogModuleInfo*
+AudioChannelService::GetAudioChannelLog()
+{
+ static PRLogModuleInfo *gAudioChannelLog;
+ if (!gAudioChannelLog) {
+ gAudioChannelLog = PR_NewLogModule("AudioChannel");
+ }
+ return gAudioChannelLog;
+}
+
+/* static */ void
+AudioChannelService::Shutdown()
+{
+ if (gAudioChannelService) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(gAudioChannelService, "xpcom-shutdown");
+ obs->RemoveObserver(gAudioChannelService, "outer-window-destroyed");
+
+ if (IsParentProcess()) {
+ obs->RemoveObserver(gAudioChannelService, "ipc:content-shutdown");
+
+#ifdef MOZ_WIDGET_GONK
+ // To monitor the volume settings based on audio channel.
+ obs->RemoveObserver(gAudioChannelService, "mozsettings-changed");
+#endif
+ }
+ }
+
+ gAudioChannelService->mWindows.Clear();
+ gAudioChannelService->mPlayingChildren.Clear();
+ gAudioChannelService->mTabParents.Clear();
+#ifdef MOZ_WIDGET_GONK
+ gAudioChannelService->mSpeakerManager.Clear();
+#endif
+
+ gAudioChannelService = nullptr;
+ }
+}
+
+/* static */ bool
+AudioChannelService::IsEnableAudioCompeting()
+{
+ CreateServiceIfNeeded();
+ return sAudioChannelCompeting;
+}
+
+NS_INTERFACE_MAP_BEGIN(AudioChannelService)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAudioChannelService)
+ NS_INTERFACE_MAP_ENTRY(nsIAudioChannelService)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(AudioChannelService)
+NS_IMPL_RELEASE(AudioChannelService)
+
+AudioChannelService::AudioChannelService()
+ : mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN)
+ , mTelephonyChannel(false)
+ , mContentOrNormalChannel(false)
+ , mAnyChannel(false)
+{
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(this, "xpcom-shutdown", false);
+ obs->AddObserver(this, "outer-window-destroyed", false);
+ if (IsParentProcess()) {
+ obs->AddObserver(this, "ipc:content-shutdown", false);
+
+#ifdef MOZ_WIDGET_GONK
+ // To monitor the volume settings based on audio channel.
+ obs->AddObserver(this, "mozsettings-changed", false);
+#endif
+ }
+ }
+
+ Preferences::AddBoolVarCache(&sAudioChannelMutedByDefault,
+ "dom.audiochannel.mutedByDefault");
+ Preferences::AddBoolVarCache(&sAudioChannelCompeting,
+ "dom.audiochannel.audioCompeting");
+ Preferences::AddBoolVarCache(&sAudioChannelCompetingAllAgents,
+ "dom.audiochannel.audioCompeting.allAgents");
+}
+
+AudioChannelService::~AudioChannelService()
+{
+}
+
+void
+AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
+ AudibleState aAudible)
+{
+ MOZ_ASSERT(aAgent);
+
+ uint64_t windowID = aAgent->WindowID();
+ AudioChannelWindow* winData = GetWindowData(windowID);
+ if (!winData) {
+ winData = new AudioChannelWindow(windowID);
+ mWindows.AppendElement(winData);
+ }
+
+ // To make sure agent would be alive because AppendAgent() would trigger the
+ // callback function of AudioChannelAgentOwner that means the agent might be
+ // released in their callback.
+ RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
+ winData->AppendAgent(aAgent, aAudible);
+
+ MaybeSendStatusUpdate();
+}
+
+void
+AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
+{
+ MOZ_ASSERT(aAgent);
+
+ AudioChannelWindow* winData = GetWindowData(aAgent->WindowID());
+ if (!winData) {
+ return;
+ }
+
+ // To make sure agent would be alive because AppendAgent() would trigger the
+ // callback function of AudioChannelAgentOwner that means the agent might be
+ // released in their callback.
+ RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
+ winData->RemoveAgent(aAgent);
+
+#ifdef MOZ_WIDGET_GONK
+ bool active = AnyAudioChannelIsActive();
+ for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
+ mSpeakerManager[i]->SetAudioChannelActive(active);
+ }
+#endif
+
+ MaybeSendStatusUpdate();
+}
+
+void
+AudioChannelService::RegisterTabParent(TabParent* aTabParent)
+{
+ MOZ_ASSERT(aTabParent);
+ MOZ_ASSERT(!mTabParents.Contains(aTabParent));
+ mTabParents.AppendElement(aTabParent);
+}
+
+void
+AudioChannelService::UnregisterTabParent(TabParent* aTabParent)
+{
+ MOZ_ASSERT(aTabParent);
+ mTabParents.RemoveElement(aTabParent);
+}
+
+AudioPlaybackConfig
+AudioChannelService::GetMediaConfig(nsPIDOMWindowOuter* aWindow,
+ uint32_t aAudioChannel) const
+{
+ MOZ_ASSERT(!aWindow || aWindow->IsOuterWindow());
+ MOZ_ASSERT(aAudioChannel < NUMBER_OF_AUDIO_CHANNELS);
+
+ AudioPlaybackConfig config(1.0, false,
+ nsISuspendedTypes::NONE_SUSPENDED);
+
+ if (!aWindow || !aWindow->IsOuterWindow()) {
+ config.SetConfig(0.0, true,
+ nsISuspendedTypes::SUSPENDED_BLOCK);
+ return config;
+ }
+
+ AudioChannelWindow* winData = nullptr;
+ nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
+
+ // The volume must be calculated based on the window hierarchy. Here we go up
+ // to the top window and we calculate the volume and the muted flag.
+ do {
+ winData = GetWindowData(window->WindowID());
+ if (winData) {
+ config.mVolume *= winData->mChannels[aAudioChannel].mVolume;
+ config.mMuted = config.mMuted || winData->mChannels[aAudioChannel].mMuted;
+ config.mSuspend = winData->mOwningAudioFocus ?
+ config.mSuspend : nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE;
+ }
+
+ config.mVolume *= window->GetAudioVolume();
+ config.mMuted = config.mMuted || window->GetAudioMuted();
+ if (window->GetMediaSuspend() != nsISuspendedTypes::NONE_SUSPENDED) {
+ config.mSuspend = window->GetMediaSuspend();
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = window->GetScriptableParentOrNull();
+ if (!win) {
+ break;
+ }
+
+ window = do_QueryInterface(win);
+
+ // If there is no parent, or we are the toplevel we don't continue.
+ } while (window && window != aWindow);
+
+ return config;
+}
+
+void
+AudioChannelService::AudioAudibleChanged(AudioChannelAgent* aAgent,
+ AudibleState aAudible,
+ AudibleChangedReasons aReason)
+{
+ MOZ_ASSERT(aAgent);
+
+ uint64_t windowID = aAgent->WindowID();
+ AudioChannelWindow* winData = GetWindowData(windowID);
+ if (winData) {
+ winData->AudioAudibleChanged(aAgent, aAudible, aReason);
+ }
+}
+
+bool
+AudioChannelService::TelephonyChannelIsActive()
+{
+ nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator windowsIter(mWindows);
+ while (windowsIter.HasMore()) {
+ AudioChannelWindow* next = windowsIter.GetNext();
+ if (next->mChannels[(uint32_t)AudioChannel::Telephony].mNumberOfAgents != 0 &&
+ !next->mChannels[(uint32_t)AudioChannel::Telephony].mMuted) {
+ return true;
+ }
+ }
+
+ if (IsParentProcess()) {
+ nsTObserverArray<nsAutoPtr<AudioChannelChildStatus>>::ForwardIterator
+ childrenIter(mPlayingChildren);
+ while (childrenIter.HasMore()) {
+ AudioChannelChildStatus* child = childrenIter.GetNext();
+ if (child->mActiveTelephonyChannel) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool
+AudioChannelService::ContentOrNormalChannelIsActive()
+{
+ // This method is meant to be used just by the child to send status update.
+ MOZ_ASSERT(!IsParentProcess());
+
+ nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator iter(mWindows);
+ while (iter.HasMore()) {
+ AudioChannelWindow* next = iter.GetNext();
+ if (next->mChannels[(uint32_t)AudioChannel::Content].mNumberOfAgents > 0 ||
+ next->mChannels[(uint32_t)AudioChannel::Normal].mNumberOfAgents > 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+AudioChannelService::AudioChannelChildStatus*
+AudioChannelService::GetChildStatus(uint64_t aChildID) const
+{
+ nsTObserverArray<nsAutoPtr<AudioChannelChildStatus>>::ForwardIterator
+ iter(mPlayingChildren);
+ while (iter.HasMore()) {
+ AudioChannelChildStatus* child = iter.GetNext();
+ if (child->mChildID == aChildID) {
+ return child;
+ }
+ }
+
+ return nullptr;
+}
+
+void
+AudioChannelService::RemoveChildStatus(uint64_t aChildID)
+{
+ nsTObserverArray<nsAutoPtr<AudioChannelChildStatus>>::ForwardIterator
+ iter(mPlayingChildren);
+ while (iter.HasMore()) {
+ nsAutoPtr<AudioChannelChildStatus>& child = iter.GetNext();
+ if (child->mChildID == aChildID) {
+ mPlayingChildren.RemoveElement(child);
+ break;
+ }
+ }
+}
+
+bool
+AudioChannelService::ProcessContentOrNormalChannelIsActive(uint64_t aChildID)
+{
+ AudioChannelChildStatus* child = GetChildStatus(aChildID);
+ if (!child) {
+ return false;
+ }
+
+ return child->mActiveContentOrNormalChannel;
+}
+
+bool
+AudioChannelService::AnyAudioChannelIsActive()
+{
+ nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator iter(mWindows);
+ while (iter.HasMore()) {
+ AudioChannelWindow* next = iter.GetNext();
+ for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
+ if (next->mChannels[kMozAudioChannelAttributeTable[i].value].mNumberOfAgents
+ != 0) {
+ return true;
+ }
+ }
+ }
+
+ if (IsParentProcess()) {
+ return !mPlayingChildren.IsEmpty();
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ if (!strcmp(aTopic, "xpcom-shutdown")) {
+ sXPCOMShuttingDown = true;
+ Shutdown();
+ } else if (!strcmp(aTopic, "outer-window-destroyed")) {
+ nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+ NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+
+ uint64_t outerID;
+ nsresult rv = wrapper->GetData(&outerID);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoPtr<AudioChannelWindow> winData;
+ {
+ nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator
+ iter(mWindows);
+ while (iter.HasMore()) {
+ nsAutoPtr<AudioChannelWindow>& next = iter.GetNext();
+ if (next->mWindowID == outerID) {
+ uint32_t pos = mWindows.IndexOf(next);
+ winData = next.forget();
+ mWindows.RemoveElementAt(pos);
+ break;
+ }
+ }
+ }
+
+ if (winData) {
+ nsTObserverArray<AudioChannelAgent*>::ForwardIterator
+ iter(winData->mAgents);
+ while (iter.HasMore()) {
+ iter.GetNext()->WindowVolumeChanged();
+ }
+ }
+
+#ifdef MOZ_WIDGET_GONK
+ bool active = AnyAudioChannelIsActive();
+ for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) {
+ mSpeakerManager[i]->SetAudioChannelActive(active);
+ }
+#endif
+ } else 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;
+ }
+
+ uint64_t childID = 0;
+ nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"),
+ &childID);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mDefChannelChildID == childID) {
+ SetDefaultVolumeControlChannelInternal(-1, false, childID);
+ mDefChannelChildID = CONTENT_PROCESS_ID_UNKNOWN;
+ }
+
+ RemoveChildStatus(childID);
+ }
+
+ return NS_OK;
+}
+
+void
+AudioChannelService::RefreshAgentsVolumeAndPropagate(AudioChannel aAudioChannel,
+ nsPIDOMWindowOuter* aWindow)
+{
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsOuterWindow());
+
+ nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
+ if (!topWindow) {
+ return;
+ }
+
+ AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
+ if (!winData) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < mTabParents.Length(); ++i) {
+ mTabParents[i]->AudioChannelChangeNotification(aWindow, aAudioChannel,
+ winData->mChannels[(uint32_t)aAudioChannel].mVolume,
+ winData->mChannels[(uint32_t)aAudioChannel].mMuted);
+ }
+
+ RefreshAgentsVolume(aWindow);
+}
+
+void
+AudioChannelService::RefreshAgents(nsPIDOMWindowOuter* aWindow,
+ mozilla::function<void(AudioChannelAgent*)> aFunc)
+{
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsOuterWindow());
+
+ nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
+ if (!topWindow) {
+ return;
+ }
+
+ AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
+ if (!winData) {
+ return;
+ }
+
+ nsTObserverArray<AudioChannelAgent*>::ForwardIterator
+ iter(winData->mAgents);
+ while (iter.HasMore()) {
+ aFunc(iter.GetNext());
+ }
+}
+
+void
+AudioChannelService::RefreshAgentsVolume(nsPIDOMWindowOuter* aWindow)
+{
+ RefreshAgents(aWindow, [] (AudioChannelAgent* agent) {
+ agent->WindowVolumeChanged();
+ });
+}
+
+void
+AudioChannelService::RefreshAgentsSuspend(nsPIDOMWindowOuter* aWindow,
+ nsSuspendedTypes aSuspend)
+{
+ RefreshAgents(aWindow, [aSuspend] (AudioChannelAgent* agent) {
+ agent->WindowSuspendChanged(aSuspend);
+ });
+}
+
+void
+AudioChannelService::SetWindowAudioCaptured(nsPIDOMWindowOuter* aWindow,
+ uint64_t aInnerWindowID,
+ bool aCapture)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsOuterWindow());
+
+ MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioChannelService, SetWindowAudioCaptured, window = %p, "
+ "aCapture = %d\n", aWindow, aCapture));
+
+ nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
+ if (!topWindow) {
+ return;
+ }
+
+ AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
+
+ // This can happen, but only during shutdown, because the the outer window
+ // changes ScriptableTop, so that its ID is different.
+ // In this case either we are capturing, and it's too late because the window
+ // has been closed anyways, or we are un-capturing, and everything has already
+ // been cleaned up by the HTMLMediaElements or the AudioContexts.
+ if (!winData) {
+ return;
+ }
+
+ if (aCapture != winData->mIsAudioCaptured) {
+ winData->mIsAudioCaptured = aCapture;
+ nsTObserverArray<AudioChannelAgent*>::ForwardIterator
+ iter(winData->mAgents);
+ while (iter.HasMore()) {
+ iter.GetNext()->WindowAudioCaptureChanged(aInnerWindowID, aCapture);
+ }
+ }
+}
+
+/* static */ const nsAttrValue::EnumTable*
+AudioChannelService::GetAudioChannelTable()
+{
+ return kMozAudioChannelAttributeTable;
+}
+
+/* static */ AudioChannel
+AudioChannelService::GetAudioChannel(const nsAString& aChannel)
+{
+ for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
+ if (aChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
+ return static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value);
+ }
+ }
+
+ return AudioChannel::Normal;
+}
+
+/* static */ AudioChannel
+AudioChannelService::GetDefaultAudioChannel()
+{
+ nsAutoString audioChannel(Preferences::GetString("media.defaultAudioChannel"));
+ if (audioChannel.IsEmpty()) {
+ return AudioChannel::Normal;
+ }
+
+ for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
+ if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
+ return static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value);
+ }
+ }
+
+ return AudioChannel::Normal;
+}
+
+/* static */ void
+AudioChannelService::GetAudioChannelString(AudioChannel aChannel,
+ nsAString& aString)
+{
+ aString.AssignASCII("normal");
+
+ for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
+ if (aChannel ==
+ static_cast<AudioChannel>(kMozAudioChannelAttributeTable[i].value)) {
+ aString.AssignASCII(kMozAudioChannelAttributeTable[i].tag);
+ break;
+ }
+ }
+}
+
+/* static */ void
+AudioChannelService::GetDefaultAudioChannelString(nsAString& aString)
+{
+ aString.AssignASCII("normal");
+
+ nsAutoString audioChannel(Preferences::GetString("media.defaultAudioChannel"));
+ if (!audioChannel.IsEmpty()) {
+ for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) {
+ if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) {
+ aString = audioChannel;
+ break;
+ }
+ }
+ }
+}
+
+AudioChannelService::AudioChannelWindow*
+AudioChannelService::GetOrCreateWindowData(nsPIDOMWindowOuter* aWindow)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsOuterWindow());
+
+ AudioChannelWindow* winData = GetWindowData(aWindow->WindowID());
+ if (!winData) {
+ winData = new AudioChannelWindow(aWindow->WindowID());
+ mWindows.AppendElement(winData);
+ }
+
+ return winData;
+}
+
+AudioChannelService::AudioChannelWindow*
+AudioChannelService::GetWindowData(uint64_t aWindowID) const
+{
+ nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator
+ iter(mWindows);
+ while (iter.HasMore()) {
+ AudioChannelWindow* next = iter.GetNext();
+ if (next->mWindowID == aWindowID) {
+ return next;
+ }
+ }
+
+ return nullptr;
+}
+
+float
+AudioChannelService::GetAudioChannelVolume(nsPIDOMWindowOuter* aWindow,
+ AudioChannel aAudioChannel)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsOuterWindow());
+
+ AudioChannelWindow* winData = GetOrCreateWindowData(aWindow);
+ return winData->mChannels[(uint32_t)aAudioChannel].mVolume;
+}
+
+NS_IMETHODIMP
+AudioChannelService::GetAudioChannelVolume(mozIDOMWindowProxy* aWindow,
+ unsigned short aAudioChannel,
+ float* aVolume)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
+ *aVolume = GetAudioChannelVolume(window, (AudioChannel)aAudioChannel);
+ return NS_OK;
+}
+
+void
+AudioChannelService::SetAudioChannelVolume(nsPIDOMWindowOuter* aWindow,
+ AudioChannel aAudioChannel,
+ float aVolume)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsOuterWindow());
+
+ MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioChannelService, SetAudioChannelVolume, window = %p, type = %d, "
+ "volume = %f\n", aWindow, aAudioChannel, aVolume));
+
+ AudioChannelWindow* winData = GetOrCreateWindowData(aWindow);
+ winData->mChannels[(uint32_t)aAudioChannel].mVolume = aVolume;
+ RefreshAgentsVolumeAndPropagate(aAudioChannel, aWindow);
+}
+
+NS_IMETHODIMP
+AudioChannelService::SetAudioChannelVolume(mozIDOMWindowProxy* aWindow,
+ unsigned short aAudioChannel,
+ float aVolume)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
+ SetAudioChannelVolume(window, (AudioChannel)aAudioChannel, aVolume);
+ return NS_OK;
+}
+
+bool
+AudioChannelService::GetAudioChannelMuted(nsPIDOMWindowOuter* aWindow,
+ AudioChannel aAudioChannel)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsOuterWindow());
+
+ AudioChannelWindow* winData = GetOrCreateWindowData(aWindow);
+ return winData->mChannels[(uint32_t)aAudioChannel].mMuted;
+}
+
+NS_IMETHODIMP
+AudioChannelService::GetAudioChannelMuted(mozIDOMWindowProxy* aWindow,
+ unsigned short aAudioChannel,
+ bool* aMuted)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
+ *aMuted = GetAudioChannelMuted(window, (AudioChannel)aAudioChannel);
+ return NS_OK;
+}
+
+void
+AudioChannelService::SetAudioChannelMuted(nsPIDOMWindowOuter* aWindow,
+ AudioChannel aAudioChannel,
+ bool aMuted)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsOuterWindow());
+
+ MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioChannelService, SetAudioChannelMuted, window = %p, type = %d, "
+ "mute = %d\n", aWindow, aAudioChannel, aMuted));
+
+ if (aAudioChannel == AudioChannel::System) {
+ // Workaround for bug1183033, system channel type can always playback.
+ return;
+ }
+
+ AudioChannelWindow* winData = GetOrCreateWindowData(aWindow);
+ winData->mChannels[(uint32_t)aAudioChannel].mMuted = aMuted;
+ RefreshAgentsVolumeAndPropagate(aAudioChannel, aWindow);
+}
+
+NS_IMETHODIMP
+AudioChannelService::SetAudioChannelMuted(mozIDOMWindowProxy* aWindow,
+ unsigned short aAudioChannel,
+ bool aMuted)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
+ SetAudioChannelMuted(window, (AudioChannel)aAudioChannel, aMuted);
+ return NS_OK;
+}
+
+bool
+AudioChannelService::IsAudioChannelActive(nsPIDOMWindowOuter* aWindow,
+ AudioChannel aAudioChannel)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aWindow->IsOuterWindow());
+
+ AudioChannelWindow* winData = GetOrCreateWindowData(aWindow);
+ return !!winData->mChannels[(uint32_t)aAudioChannel].mNumberOfAgents;
+}
+
+NS_IMETHODIMP
+AudioChannelService::IsAudioChannelActive(mozIDOMWindowProxy* aWindow,
+ unsigned short aAudioChannel,
+ bool* aActive)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
+ *aActive = IsAudioChannelActive(window, (AudioChannel)aAudioChannel);
+ return NS_OK;
+}
+void
+AudioChannelService::SetDefaultVolumeControlChannel(int32_t aChannel,
+ bool aVisible)
+{
+ SetDefaultVolumeControlChannelInternal(aChannel, aVisible,
+ CONTENT_PROCESS_ID_MAIN);
+}
+
+void
+AudioChannelService::SetDefaultVolumeControlChannelInternal(int32_t aChannel,
+ bool aVisible,
+ uint64_t aChildID)
+{
+ if (!IsParentProcess()) {
+ ContentChild* cc = ContentChild::GetSingleton();
+ if (cc) {
+ cc->SendAudioChannelChangeDefVolChannel(aChannel, aVisible);
+ }
+
+ 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.
+ if (!aVisible && mDefChannelChildID != aChildID) {
+ return;
+ }
+
+ // Workaround for the call screen app. The call screen app is running on the
+ // main process, that will results in wrong visible state. Because we use the
+ // docshell's active state as visible state, the main process is always
+ // active. Therefore, we will see the strange situation that the visible
+ // state of the call screen is always true. If the mDefChannelChildID is set
+ // to others then it means other child in the foreground already set it's
+ // own default channel already.
+ // Summary :
+ // Child process : foreground app always can set type.
+ // Parent process : check the mDefChannelChildID.
+ else if (aChildID == CONTENT_PROCESS_ID_MAIN &&
+ mDefChannelChildID != CONTENT_PROCESS_ID_UNKNOWN) {
+ return;
+ }
+
+ mDefChannelChildID = aVisible ? aChildID : CONTENT_PROCESS_ID_UNKNOWN;
+ nsAutoString channelName;
+
+ if (aChannel == -1) {
+ channelName.AssignASCII("unknown");
+ } else {
+ GetAudioChannelString(static_cast<AudioChannel>(aChannel), channelName);
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, "default-volume-channel-changed",
+ channelName.get());
+ }
+}
+
+void
+AudioChannelService::MaybeSendStatusUpdate()
+{
+ if (IsParentProcess()) {
+ return;
+ }
+
+ bool telephonyChannel = TelephonyChannelIsActive();
+ bool contentOrNormalChannel = ContentOrNormalChannelIsActive();
+ bool anyChannel = AnyAudioChannelIsActive();
+
+ if (telephonyChannel == mTelephonyChannel &&
+ contentOrNormalChannel == mContentOrNormalChannel &&
+ anyChannel == mAnyChannel) {
+ return;
+ }
+
+ mTelephonyChannel = telephonyChannel;
+ mContentOrNormalChannel = contentOrNormalChannel;
+ mAnyChannel = anyChannel;
+
+ ContentChild* cc = ContentChild::GetSingleton();
+ if (cc) {
+ cc->SendAudioChannelServiceStatus(telephonyChannel, contentOrNormalChannel,
+ anyChannel);
+ }
+}
+
+void
+AudioChannelService::ChildStatusReceived(uint64_t aChildID,
+ bool aTelephonyChannel,
+ bool aContentOrNormalChannel,
+ bool aAnyChannel)
+{
+ if (!aAnyChannel) {
+ RemoveChildStatus(aChildID);
+ return;
+ }
+
+ AudioChannelChildStatus* data = GetChildStatus(aChildID);
+ if (!data) {
+ data = new AudioChannelChildStatus(aChildID);
+ mPlayingChildren.AppendElement(data);
+ }
+
+ data->mActiveTelephonyChannel = aTelephonyChannel;
+ data->mActiveContentOrNormalChannel = aContentOrNormalChannel;
+}
+
+void
+AudioChannelService::RefreshAgentsAudioFocusChanged(AudioChannelAgent* aAgent)
+{
+ MOZ_ASSERT(aAgent);
+
+ nsTObserverArray<nsAutoPtr<AudioChannelWindow>>::ForwardIterator
+ iter(mWindows);
+ while (iter.HasMore()) {
+ AudioChannelWindow* winData = iter.GetNext();
+ if (winData->mOwningAudioFocus) {
+ winData->AudioFocusChanged(aAgent);
+ }
+ }
+}
+
+void
+AudioChannelService::AudioChannelWindow::RequestAudioFocus(AudioChannelAgent* aAgent)
+{
+ MOZ_ASSERT(aAgent);
+
+ // Don't need to check audio focus for window-less agent.
+ if (!aAgent->Window()) {
+ return;
+ }
+
+ // We already have the audio focus. No operation is needed.
+ if (mOwningAudioFocus) {
+ return;
+ }
+
+ // Only foreground window can request audio focus, but it would still own the
+ // audio focus even it goes to background. Audio focus would be abandoned
+ // only when other foreground window starts audio competing.
+ // One exception is if the pref "media.block-autoplay-until-in-foreground"
+ // is on and the background page is the non-visited before. Because the media
+ // in that page would be blocked until the page is going to foreground.
+ mOwningAudioFocus = (!(aAgent->Window()->IsBackground()) ||
+ aAgent->Window()->GetMediaSuspend() == nsISuspendedTypes::SUSPENDED_BLOCK) ;
+
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioChannelWindow, RequestAudioFocus, this = %p, "
+ "agent = %p, owning audio focus = %d\n",
+ this, aAgent, mOwningAudioFocus));
+}
+
+void
+AudioChannelService::AudioChannelWindow::NotifyAudioCompetingChanged(AudioChannelAgent* aAgent)
+{
+ // This function may be called after RemoveAgentAndReduceAgentsNum(), so the
+ // agent may be not contained in mAgent. In addition, the agent would still
+ // be alive because we have kungFuDeathGrip in UnregisterAudioChannelAgent().
+ MOZ_ASSERT(aAgent);
+
+ RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+ MOZ_ASSERT(service);
+
+ if (!service->IsEnableAudioCompeting()) {
+ return;
+ }
+
+ if (!IsAgentInvolvingInAudioCompeting(aAgent)) {
+ return;
+ }
+
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioChannelWindow, NotifyAudioCompetingChanged, this = %p, "
+ "agent = %p\n",
+ this, aAgent));
+
+ service->RefreshAgentsAudioFocusChanged(aAgent);
+}
+
+bool
+AudioChannelService::AudioChannelWindow::IsAgentInvolvingInAudioCompeting(AudioChannelAgent* aAgent) const
+{
+ MOZ_ASSERT(aAgent);
+
+ if(!mOwningAudioFocus) {
+ return false;
+ }
+
+ if (IsAudioCompetingInSameTab()) {
+ return false;
+ }
+
+ // TODO : add MediaSession::ambient kind, because it doens't interact with
+ // other kinds.
+ return true;
+}
+
+bool
+AudioChannelService::AudioChannelWindow::IsAudioCompetingInSameTab() const
+{
+ bool hasMultipleActiveAgents = IsEnableAudioCompetingForAllAgents() ?
+ mAgents.Length() > 1 : mAudibleAgents.Length() > 1;
+ return mOwningAudioFocus && hasMultipleActiveAgents;
+}
+
+void
+AudioChannelService::AudioChannelWindow::AudioFocusChanged(AudioChannelAgent* aNewPlayingAgent)
+{
+ // This agent isn't always known for the current window, because it can comes
+ // from other window.
+ MOZ_ASSERT(aNewPlayingAgent);
+
+ if (IsInactiveWindow()) {
+ // These would happen in two situations,
+ // (1) Audio in page A was ended, and another page B want to play audio.
+ // Page A should abandon its focus.
+ // (2) Audio was paused by remote-control, page should still own the focus.
+ mOwningAudioFocus = IsContainingPlayingAgent(aNewPlayingAgent);
+ } else {
+ nsTObserverArray<AudioChannelAgent*>::ForwardIterator
+ iter(IsEnableAudioCompetingForAllAgents() ? mAgents : mAudibleAgents);
+ while (iter.HasMore()) {
+ AudioChannelAgent* agent = iter.GetNext();
+ MOZ_ASSERT(agent);
+
+ // Don't need to update the playing state of new playing agent.
+ if (agent == aNewPlayingAgent) {
+ continue;
+ }
+
+ uint32_t type = GetCompetingBehavior(agent,
+ aNewPlayingAgent->AudioChannelType());
+
+ // If window will be suspended, it needs to abandon the audio focus
+ // because only one window can own audio focus at a time. However, we
+ // would support multiple audio focus at the same time in the future.
+ mOwningAudioFocus = (type == nsISuspendedTypes::NONE_SUSPENDED);
+
+ // TODO : support other behaviors which are definded in MediaSession API.
+ switch (type) {
+ case nsISuspendedTypes::NONE_SUSPENDED:
+ case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE:
+ agent->WindowSuspendChanged(type);
+ break;
+ }
+ }
+ }
+
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioChannelWindow, AudioFocusChanged, this = %p, "
+ "OwningAudioFocus = %d\n", this, mOwningAudioFocus));
+}
+
+bool
+AudioChannelService::AudioChannelWindow::IsContainingPlayingAgent(AudioChannelAgent* aAgent) const
+{
+ return (aAgent->WindowID() == mWindowID);
+}
+
+uint32_t
+AudioChannelService::AudioChannelWindow::GetCompetingBehavior(AudioChannelAgent* aAgent,
+ int32_t aIncomingChannelType) const
+{
+ MOZ_ASSERT(aAgent);
+ MOZ_ASSERT(IsEnableAudioCompetingForAllAgents() ?
+ mAgents.Contains(aAgent) : mAudibleAgents.Contains(aAgent));
+
+ uint32_t competingBehavior = nsISuspendedTypes::NONE_SUSPENDED;
+ int32_t presentChannelType = aAgent->AudioChannelType();
+
+ // TODO : add other competing cases for MediaSession API
+ if (presentChannelType == int32_t(AudioChannel::Normal) &&
+ aIncomingChannelType == int32_t(AudioChannel::Normal)) {
+ competingBehavior = nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE;
+ }
+
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("AudioChannelWindow, GetCompetingBehavior, this = %p, "
+ "present type = %d, incoming channel = %d, behavior = %d\n",
+ this, presentChannelType, aIncomingChannelType, competingBehavior));
+
+ return competingBehavior;
+}
+
+/* static */ bool
+AudioChannelService::IsAudioChannelMutedByDefault()
+{
+ CreateServiceIfNeeded();
+ return sAudioChannelMutedByDefault;
+}
+
+void
+AudioChannelService::AudioChannelWindow::AppendAgent(AudioChannelAgent* aAgent,
+ AudibleState aAudible)
+{
+ MOZ_ASSERT(aAgent);
+
+ RequestAudioFocus(aAgent);
+ AppendAgentAndIncreaseAgentsNum(aAgent);
+ AudioCapturedChanged(aAgent, AudioCaptureState::eCapturing);
+ if (aAudible == AudibleState::eAudible) {
+ AudioAudibleChanged(aAgent,
+ AudibleState::eAudible,
+ AudibleChangedReasons::eDataAudibleChanged);
+ } else if (IsEnableAudioCompetingForAllAgents() &&
+ aAudible != AudibleState::eAudible) {
+ NotifyAudioCompetingChanged(aAgent);
+ }
+}
+
+void
+AudioChannelService::AudioChannelWindow::RemoveAgent(AudioChannelAgent* aAgent)
+{
+ MOZ_ASSERT(aAgent);
+
+ RemoveAgentAndReduceAgentsNum(aAgent);
+ AudioCapturedChanged(aAgent, AudioCaptureState::eNotCapturing);
+ AudioAudibleChanged(aAgent,
+ AudibleState::eNotAudible,
+ AudibleChangedReasons::ePauseStateChanged);
+}
+
+void
+AudioChannelService::AudioChannelWindow::AppendAgentAndIncreaseAgentsNum(AudioChannelAgent* aAgent)
+{
+ MOZ_ASSERT(aAgent);
+ MOZ_ASSERT(!mAgents.Contains(aAgent));
+
+ int32_t channel = aAgent->AudioChannelType();
+ mAgents.AppendElement(aAgent);
+
+ ++mChannels[channel].mNumberOfAgents;
+
+ // The first one, we must inform the BrowserElementAudioChannel.
+ if (mChannels[channel].mNumberOfAgents == 1) {
+ NotifyChannelActive(aAgent->WindowID(),
+ static_cast<AudioChannel>(channel),
+ true);
+ }
+}
+
+void
+AudioChannelService::AudioChannelWindow::RemoveAgentAndReduceAgentsNum(AudioChannelAgent* aAgent)
+{
+ MOZ_ASSERT(aAgent);
+ MOZ_ASSERT(mAgents.Contains(aAgent));
+
+ int32_t channel = aAgent->AudioChannelType();
+ mAgents.RemoveElement(aAgent);
+
+ MOZ_ASSERT(mChannels[channel].mNumberOfAgents > 0);
+ --mChannels[channel].mNumberOfAgents;
+
+ if (mChannels[channel].mNumberOfAgents == 0) {
+ NotifyChannelActive(aAgent->WindowID(),
+ static_cast<AudioChannel>(channel),
+ false);
+ }
+}
+
+void
+AudioChannelService::AudioChannelWindow::AudioCapturedChanged(AudioChannelAgent* aAgent,
+ AudioCaptureState aCapture)
+{
+ MOZ_ASSERT(aAgent);
+
+ if (mIsAudioCaptured) {
+ aAgent->WindowAudioCaptureChanged(aAgent->InnerWindowID(), aCapture);
+ }
+}
+
+void
+AudioChannelService::AudioChannelWindow::AudioAudibleChanged(AudioChannelAgent* aAgent,
+ AudibleState aAudible,
+ AudibleChangedReasons aReason)
+{
+ MOZ_ASSERT(aAgent);
+
+ if (aAudible == AudibleState::eAudible) {
+ AppendAudibleAgentIfNotContained(aAgent, aReason);
+ } else {
+ RemoveAudibleAgentIfContained(aAgent, aReason);
+ }
+
+ if (aAudible == AudibleState::eAudible) {
+ NotifyAudioCompetingChanged(aAgent);
+ } else if (aAudible != AudibleState::eNotAudible) {
+ MaybeNotifyMediaBlocked(aAgent);
+ }
+}
+
+void
+AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent,
+ AudibleChangedReasons aReason)
+{
+ MOZ_ASSERT(aAgent);
+ MOZ_ASSERT(mAgents.Contains(aAgent));
+
+ if (!mAudibleAgents.Contains(aAgent)) {
+ mAudibleAgents.AppendElement(aAgent);
+ if (IsFirstAudibleAgent()) {
+ NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eAudible, aReason);
+ }
+ }
+}
+
+void
+AudioChannelService::AudioChannelWindow::RemoveAudibleAgentIfContained(AudioChannelAgent* aAgent,
+ AudibleChangedReasons aReason)
+{
+ MOZ_ASSERT(aAgent);
+
+ if (mAudibleAgents.Contains(aAgent)) {
+ mAudibleAgents.RemoveElement(aAgent);
+ if (IsLastAudibleAgent()) {
+ NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eNotAudible, aReason);
+ }
+ }
+}
+
+bool
+AudioChannelService::AudioChannelWindow::IsFirstAudibleAgent() const
+{
+ return (mAudibleAgents.Length() == 1);
+}
+
+bool
+AudioChannelService::AudioChannelWindow::IsLastAudibleAgent() const
+{
+ return mAudibleAgents.IsEmpty();
+}
+
+bool
+AudioChannelService::AudioChannelWindow::IsInactiveWindow() const
+{
+ return IsEnableAudioCompetingForAllAgents() ?
+ mAudibleAgents.IsEmpty() && mAgents.IsEmpty() : mAudibleAgents.IsEmpty();
+}
+
+void
+AudioChannelService::AudioChannelWindow::NotifyAudioAudibleChanged(nsPIDOMWindowOuter* aWindow,
+ AudibleState aAudible,
+ AudibleChangedReasons aReason)
+{
+ RefPtr<AudioPlaybackRunnable> runnable =
+ new AudioPlaybackRunnable(aWindow,
+ aAudible == AudibleState::eAudible,
+ aReason);
+ DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
+}
+
+void
+AudioChannelService::AudioChannelWindow::NotifyChannelActive(uint64_t aWindowID,
+ AudioChannel aChannel,
+ bool aActive)
+{
+ RefPtr<NotifyChannelActiveRunnable> runnable =
+ new NotifyChannelActiveRunnable(aWindowID, aChannel, aActive);
+ DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
+}
+
+void
+AudioChannelService::AudioChannelWindow::MaybeNotifyMediaBlocked(AudioChannelAgent* aAgent)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = aAgent->Window();
+ if (!window) {
+ return;
+ }
+
+ MOZ_ASSERT(window->IsOuterWindow());
+ if (window->GetMediaSuspend() != nsISuspendedTypes::SUSPENDED_BLOCK) {
+ return;
+ }
+
+ NS_DispatchToCurrentThread(NS_NewRunnableFunction([window] () -> void {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (NS_WARN_IF(!observerService)) {
+ return;
+ }
+
+ observerService->NotifyObservers(ToSupports(window),
+ "audio-playback",
+ u"block");
+ })
+ );
+}
diff --git a/dom/audiochannel/AudioChannelService.h b/dom/audiochannel/AudioChannelService.h
new file mode 100644
index 000000000..b16832b5f
--- /dev/null
+++ b/dom/audiochannel/AudioChannelService.h
@@ -0,0 +1,376 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+#ifndef mozilla_dom_audiochannelservice_h__
+#define mozilla_dom_audiochannelservice_h__
+
+#include "nsIAudioChannelService.h"
+#include "nsAutoPtr.h"
+#include "nsIObserver.h"
+#include "nsTObserverArray.h"
+#include "nsTArray.h"
+
+#include "AudioChannelAgent.h"
+#include "nsAttrValue.h"
+#include "mozilla/dom/AudioChannelBinding.h"
+#include "mozilla/Function.h"
+
+class nsIRunnable;
+class nsPIDOMWindowOuter;
+struct PRLogModuleInfo;
+
+namespace mozilla {
+namespace dom {
+
+#ifdef MOZ_WIDGET_GONK
+class SpeakerManagerService;
+#endif
+
+class TabParent;
+
+#define NUMBER_OF_AUDIO_CHANNELS (uint32_t)AudioChannel::EndGuard_
+
+class AudioPlaybackConfig
+{
+public:
+ AudioPlaybackConfig()
+ : mVolume(1.0)
+ , mMuted(false)
+ , mSuspend(nsISuspendedTypes::NONE_SUSPENDED)
+ {}
+
+ AudioPlaybackConfig(float aVolume, bool aMuted, uint32_t aSuspended)
+ : mVolume(aVolume)
+ , mMuted(aMuted)
+ , mSuspend(aSuspended)
+ {}
+
+ void SetConfig(float aVolume, bool aMuted, uint32_t aSuspended)
+ {
+ mVolume = aVolume;
+ mMuted = aMuted;
+ mSuspend = aSuspended;
+ }
+
+ float mVolume;
+ bool mMuted;
+ uint32_t mSuspend;
+};
+
+class AudioChannelService final : public nsIAudioChannelService
+ , public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIAUDIOCHANNELSERVICE
+
+ /**
+ * eNotAudible : agent is not audible
+ * eMaybeAudible : agent is not audible now, but it might be audible later
+ * eAudible : agent is audible now
+ */
+ enum AudibleState : uint8_t {
+ eNotAudible = 0,
+ eMaybeAudible = 1,
+ eAudible = 2
+ };
+
+ enum AudioCaptureState : bool {
+ eCapturing = true,
+ eNotCapturing = false
+ };
+
+ enum AudibleChangedReasons : uint32_t {
+ eVolumeChanged = 0,
+ eDataAudibleChanged = 1,
+ ePauseStateChanged = 2
+ };
+
+ /**
+ * Returns the AudioChannelServce singleton.
+ * If AudioChannelServce is not exist, create and return new one.
+ * Only to be called from main thread.
+ */
+ static already_AddRefed<AudioChannelService> GetOrCreate();
+
+ static bool IsAudioChannelMutedByDefault();
+
+ static PRLogModuleInfo* GetAudioChannelLog();
+
+ static bool IsEnableAudioCompeting();
+
+ /**
+ * Any audio channel agent that starts playing should register itself to
+ * this service, sharing the AudioChannel.
+ */
+ void RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
+ AudibleState aAudible);
+
+ /**
+ * Any audio channel agent that stops playing should unregister itself to
+ * this service.
+ */
+ void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent);
+
+ /**
+ * For nested iframes.
+ */
+ void RegisterTabParent(TabParent* aTabParent);
+ void UnregisterTabParent(TabParent* aTabParent);
+
+ /**
+ * Return the state to indicate this audioChannel for his window should keep
+ * playing/muted/suspended.
+ */
+ AudioPlaybackConfig GetMediaConfig(nsPIDOMWindowOuter* aWindow,
+ uint32_t aAudioChannel) const;
+
+ /**
+ * Called this method when the audible state of the audio playback changed,
+ * it would dispatch the playback event to observers which want to know the
+ * actual audible state of the window.
+ */
+ void AudioAudibleChanged(AudioChannelAgent* aAgent,
+ AudibleState aAudible,
+ AudibleChangedReasons aReason);
+
+ /* Methods for the BrowserElementAudioChannel */
+ float GetAudioChannelVolume(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel);
+
+ void SetAudioChannelVolume(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel,
+ float aVolume);
+
+ bool GetAudioChannelMuted(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel);
+
+ void SetAudioChannelMuted(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel,
+ bool aMuted);
+
+ bool IsAudioChannelActive(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel);
+
+ /**
+ * Return true if there is a telephony channel active in this process
+ * or one of its subprocesses.
+ */
+ bool TelephonyChannelIsActive();
+
+ /**
+ * Return true if a normal or content channel is active for the given
+ * process ID.
+ */
+ bool ProcessContentOrNormalChannelIsActive(uint64_t aChildID);
+
+ /***
+ * AudioChannelManager calls this function to notify the default channel used
+ * to adjust volume when there is no any active channel. if aChannel is -1,
+ * the default audio channel will be used. Otherwise aChannel is casted to
+ * AudioChannel enum.
+ */
+ virtual void SetDefaultVolumeControlChannel(int32_t aChannel,
+ bool aVisible);
+
+ bool AnyAudioChannelIsActive();
+
+ void RefreshAgentsVolume(nsPIDOMWindowOuter* aWindow);
+ void RefreshAgentsSuspend(nsPIDOMWindowOuter* aWindow,
+ nsSuspendedTypes aSuspend);
+
+ void RefreshAgentsVolumeAndPropagate(AudioChannel aAudioChannel,
+ nsPIDOMWindowOuter* aWindow);
+
+ // This method needs to know the inner window that wants to capture audio. We
+ // group agents per top outer window, but we can have multiple innerWindow per
+ // top outerWindow (subiframes, etc.) and we have to identify all the agents
+ // just for a particular innerWindow.
+ void SetWindowAudioCaptured(nsPIDOMWindowOuter* aWindow,
+ uint64_t aInnerWindowID,
+ bool aCapture);
+
+#ifdef MOZ_WIDGET_GONK
+ void RegisterSpeakerManager(SpeakerManagerService* aSpeakerManager)
+ {
+ if (!mSpeakerManager.Contains(aSpeakerManager)) {
+ mSpeakerManager.AppendElement(aSpeakerManager);
+ }
+ }
+
+ void UnregisterSpeakerManager(SpeakerManagerService* aSpeakerManager)
+ {
+ mSpeakerManager.RemoveElement(aSpeakerManager);
+ }
+#endif
+
+ static const nsAttrValue::EnumTable* GetAudioChannelTable();
+ static AudioChannel GetAudioChannel(const nsAString& aString);
+ static AudioChannel GetDefaultAudioChannel();
+ static void GetAudioChannelString(AudioChannel aChannel, nsAString& aString);
+ static void GetDefaultAudioChannelString(nsAString& aString);
+
+ void Notify(uint64_t aWindowID);
+
+ void ChildStatusReceived(uint64_t aChildID, bool aTelephonyChannel,
+ bool aContentOrNormalChannel, bool aAnyChannel);
+
+private:
+ AudioChannelService();
+ ~AudioChannelService();
+
+ void RefreshAgents(nsPIDOMWindowOuter* aWindow,
+ mozilla::function<void(AudioChannelAgent*)> aFunc);
+
+ static void CreateServiceIfNeeded();
+
+ /**
+ * Shutdown the singleton.
+ */
+ static void Shutdown();
+
+ void MaybeSendStatusUpdate();
+
+ bool ContentOrNormalChannelIsActive();
+
+ /* Send the default-volume-channel-changed notification */
+ void SetDefaultVolumeControlChannelInternal(int32_t aChannel,
+ bool aVisible, uint64_t aChildID);
+
+ void RefreshAgentsAudioFocusChanged(AudioChannelAgent* aAgent);
+
+ class AudioChannelConfig final : public AudioPlaybackConfig
+ {
+ public:
+ AudioChannelConfig()
+ : AudioPlaybackConfig(1.0, IsAudioChannelMutedByDefault(),
+ nsISuspendedTypes::NONE_SUSPENDED)
+ , mNumberOfAgents(0)
+ {}
+
+ uint32_t mNumberOfAgents;
+ };
+
+ class AudioChannelWindow final
+ {
+ public:
+ explicit AudioChannelWindow(uint64_t aWindowID)
+ : mWindowID(aWindowID)
+ , mIsAudioCaptured(false)
+ , mOwningAudioFocus(!AudioChannelService::IsEnableAudioCompeting())
+ {
+ // Workaround for bug1183033, system channel type can always playback.
+ mChannels[(int16_t)AudioChannel::System].mMuted = false;
+ }
+
+ void AudioFocusChanged(AudioChannelAgent* aNewPlayingAgent);
+ void AudioAudibleChanged(AudioChannelAgent* aAgent,
+ AudibleState aAudible,
+ AudibleChangedReasons aReason);
+
+ void AppendAgent(AudioChannelAgent* aAgent, AudibleState aAudible);
+ void RemoveAgent(AudioChannelAgent* aAgent);
+
+ uint64_t mWindowID;
+ bool mIsAudioCaptured;
+ AudioChannelConfig mChannels[NUMBER_OF_AUDIO_CHANNELS];
+
+ // Raw pointer because the AudioChannelAgent must unregister itself.
+ nsTObserverArray<AudioChannelAgent*> mAgents;
+ nsTObserverArray<AudioChannelAgent*> mAudibleAgents;
+
+ // Owning audio focus when the window starts playing audible sound, and
+ // lose audio focus when other windows starts playing.
+ bool mOwningAudioFocus;
+
+ private:
+ void AudioCapturedChanged(AudioChannelAgent* aAgent,
+ AudioCaptureState aCapture);
+
+ void AppendAudibleAgentIfNotContained(AudioChannelAgent* aAgent,
+ AudibleChangedReasons aReason);
+ void RemoveAudibleAgentIfContained(AudioChannelAgent* aAgent,
+ AudibleChangedReasons aReason);
+
+ void AppendAgentAndIncreaseAgentsNum(AudioChannelAgent* aAgent);
+ void RemoveAgentAndReduceAgentsNum(AudioChannelAgent* aAgent);
+
+ bool IsFirstAudibleAgent() const;
+ bool IsLastAudibleAgent() const;
+
+ void NotifyAudioAudibleChanged(nsPIDOMWindowOuter* aWindow,
+ AudibleState aAudible,
+ AudibleChangedReasons aReason);
+
+ void NotifyChannelActive(uint64_t aWindowID, AudioChannel aChannel,
+ bool aActive);
+ void MaybeNotifyMediaBlocked(AudioChannelAgent* aAgent);
+
+ void RequestAudioFocus(AudioChannelAgent* aAgent);
+ // We need to do audio competing only when the new incoming agent started.
+ void NotifyAudioCompetingChanged(AudioChannelAgent* aAgent);
+
+ uint32_t GetCompetingBehavior(AudioChannelAgent* aAgent,
+ int32_t aIncomingChannelType) const;
+ bool IsAgentInvolvingInAudioCompeting(AudioChannelAgent* aAgent) const;
+ bool IsAudioCompetingInSameTab() const;
+ bool IsContainingPlayingAgent(AudioChannelAgent* aAgent) const;
+
+ bool IsInactiveWindow() const;
+ };
+
+ AudioChannelWindow*
+ GetOrCreateWindowData(nsPIDOMWindowOuter* aWindow);
+
+ AudioChannelWindow*
+ GetWindowData(uint64_t aWindowID) const;
+
+ struct AudioChannelChildStatus final
+ {
+ explicit AudioChannelChildStatus(uint64_t aChildID)
+ : mChildID(aChildID)
+ , mActiveTelephonyChannel(false)
+ , mActiveContentOrNormalChannel(false)
+ {}
+
+ uint64_t mChildID;
+ bool mActiveTelephonyChannel;
+ bool mActiveContentOrNormalChannel;
+ };
+
+ AudioChannelChildStatus*
+ GetChildStatus(uint64_t aChildID) const;
+
+ void
+ RemoveChildStatus(uint64_t aChildID);
+
+ nsTObserverArray<nsAutoPtr<AudioChannelWindow>> mWindows;
+
+ nsTObserverArray<nsAutoPtr<AudioChannelChildStatus>> mPlayingChildren;
+
+#ifdef MOZ_WIDGET_GONK
+ nsTArray<SpeakerManagerService*> mSpeakerManager;
+#endif
+
+ // Raw pointers because TabParents must unregister themselves.
+ nsTArray<TabParent*> mTabParents;
+
+ nsCOMPtr<nsIRunnable> mRunnable;
+
+ uint64_t mDefChannelChildID;
+
+ // These boolean are used to know if we have to send an status update to the
+ // service running in the main process.
+ bool mTelephonyChannel;
+ bool mContentOrNormalChannel;
+ bool mAnyChannel;
+
+ // This is needed for IPC comunication between
+ // AudioChannelServiceChild and this class.
+ friend class ContentParent;
+ friend class ContentChild;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/audiochannel/crashtests/1223734.html b/dom/audiochannel/crashtests/1223734.html
new file mode 100644
index 000000000..1d001eccc
--- /dev/null
+++ b/dom/audiochannel/crashtests/1223734.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom() {
+ var audio = document.createElement('audio');
+ audio.loop = true;
+ audio.play();
+ document.implementation.createDocument("", "", null).adoptNode(audio);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/dom/audiochannel/crashtests/crashtests.list b/dom/audiochannel/crashtests/crashtests.list
new file mode 100644
index 000000000..f87793941
--- /dev/null
+++ b/dom/audiochannel/crashtests/crashtests.list
@@ -0,0 +1 @@
+load 1223734.html
diff --git a/dom/audiochannel/moz.build b/dom/audiochannel/moz.build
new file mode 100644
index 000000000..46923db3c
--- /dev/null
+++ b/dom/audiochannel/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+ 'nsIAudioChannelAgent.idl',
+ 'nsIAudioChannelService.idl',
+]
+
+XPIDL_MODULE = 'dom_audiochannel'
+
+EXPORTS += [
+ 'AudioChannelAgent.h',
+ 'AudioChannelService.h',
+]
+
+UNIFIED_SOURCES += [
+ 'AudioChannelAgent.cpp',
+ 'AudioChannelService.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/dom/base/',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/audiochannel/nsIAudioChannelAgent.idl b/dom/audiochannel/nsIAudioChannelAgent.idl
new file mode 100644
index 000000000..820f8da85
--- /dev/null
+++ b/dom/audiochannel/nsIAudioChannelAgent.idl
@@ -0,0 +1,187 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindow;
+
+typedef uint32_t nsSuspendedTypes;
+
+[scriptable, builtinclass, uuid(2822a840-f009-11e5-a837-0800200c9a66)]
+interface nsISuspendedTypes : nsISupports
+{
+ /**
+ * The suspended enum is used in three different situations,
+ * - platform audio focus (Fennec/B2G)
+ * - remote media control (Fennec)
+ * - block auto-play video in non-active page
+ *
+ * Note: the "remote side" must control the AudioChannelAgent using
+ * nsIAudioChannelAgentCallback.windowSuspendChanged() callback instead using
+ * play/pause methods or any button in the webpage.
+ *
+ * - SUSPENDED_PAUSE :
+ * It's used when transiently losing audio focus, the media can't be resumed
+ * until we gain the audio focus again. It would change the internal state of
+ * MediaElement when it's being suspended/resumed, and it would trigger the
+ * related JS event. eg. "play" and "pause" event.
+ *
+ * - SUSPENDED_BLOCK
+ * It's used to prevent auto-playing media in inactive page in order to
+ * reduce the power consumption, and the media can't be resumed until the
+ * page becomes active again. It would change the internal state of
+ * MediaElement when it's being blocked/resumed, so it won't trigger the
+ * related JS event. eg. "play" and "pause" event.
+ *
+ * - SUSPENDED_PAUSE_DISPOSABLE
+ * It's used for remote media-control to pause the playing media and when we
+ * lose audio focus permanently. It's disposable suspended, so the media can
+ * be resumed arbitrary after that. Same as SUSPENDED_PAUSE, it would change
+ * the internal state of MediaElement when it's being suspended.
+ *
+ * - SUSPENDED_STOP_DISPOSABLE
+ * It's used for remote media-control to stop the playing media. The remote
+ * control would disappear after stopping the media, so we would disconnect
+ * the audio channel agent. It's disposable suspended, so the media can be
+ * resumed arbitrary after that. Same as SUSPENDED_PAUSE, it would change
+ * the internal state of MediaElement when it's being suspended.
+ */
+
+ const uint32_t NONE_SUSPENDED = 0;
+ const uint32_t SUSPENDED_PAUSE = 1;
+ const uint32_t SUSPENDED_BLOCK = 2;
+ const uint32_t SUSPENDED_PAUSE_DISPOSABLE = 3;
+ const uint32_t SUSPENDED_STOP_DISPOSABLE = 4;
+};
+
+%{C++
+namespace mozilla {
+namespace dom {
+// It's defined in dom/audiochannel/AudioChannelService.h.
+class AudioPlaybackConfig;
+}
+}
+%}
+[ptr] native AudioPlaybackConfig(mozilla::dom::AudioPlaybackConfig);
+
+[uuid(15c05894-408e-4798-b527-a8c32d9c5f8c)]
+interface nsIAudioChannelAgentCallback : nsISupports
+{
+ /**
+ * Notified when the window volume/mute is changed
+ */
+ void windowVolumeChanged(in float aVolume, in bool aMuted);
+
+ /**
+ * Notified when the window needs to be suspended or resumed.
+ */
+ void windowSuspendChanged(in uint32_t aSuspend);
+
+ /**
+ * Notified when the capture state is changed.
+ */
+ void windowAudioCaptureChanged(in bool aCapture);
+};
+
+/**
+ * This interface provides an agent for gecko components to participate
+ * in the audio channel service. Gecko components are responsible for
+ * 1. Indicating what channel type they are using (via the init() member
+ * function).
+ * 2. Notifying the agent when they start/stop using this channel.
+ * 3. Notifying the agent when they are audible.
+ *
+ * The agent will invoke a callback to notify Gecko components of
+ * 1. Changes to the playable status of this channel.
+ */
+
+[uuid(ab7e21c0-970c-11e5-a837-0800200c9a66)]
+interface nsIAudioChannelAgent : nsISupports
+{
+ const long AUDIO_AGENT_CHANNEL_NORMAL = 0;
+ const long AUDIO_AGENT_CHANNEL_CONTENT = 1;
+ const long AUDIO_AGENT_CHANNEL_NOTIFICATION = 2;
+ const long AUDIO_AGENT_CHANNEL_ALARM = 3;
+ const long AUDIO_AGENT_CHANNEL_TELEPHONY = 4;
+ const long AUDIO_AGENT_CHANNEL_RINGER = 5;
+ const long AUDIO_AGENT_CHANNEL_PUBLICNOTIFICATION = 6;
+ const long AUDIO_AGENT_CHANNEL_SYSTEM = 7;
+
+ const long AUDIO_AGENT_CHANNEL_ERROR = 1000;
+
+ const long AUDIO_AGENT_STATE_NORMAL = 0;
+ const long AUDIO_AGENT_STATE_MUTED = 1;
+ const long AUDIO_AGENT_STATE_FADED = 2;
+
+ /**
+ * Before init() is called, this returns AUDIO_AGENT_CHANNEL_ERROR.
+ */
+ readonly attribute long audioChannelType;
+
+ %{C++
+ inline int32_t AudioChannelType() {
+ int32_t channel;
+ return NS_SUCCEEDED(GetAudioChannelType(&channel)) ? channel : AUDIO_AGENT_CHANNEL_ERROR;
+ }
+ %}
+
+ /**
+ * Initialize the agent with a channel type.
+ * Note: This function should only be called once.
+ *
+ * @param window
+ * The window
+ * @param channelType
+ * Audio Channel Type listed as above
+ * @param callback
+ * 1. Once the playable status changes, agent uses this callback function
+ * to notify Gecko component.
+ * 2. The callback is allowed to be null. Ex: telephony doesn't need to
+ * listen change of the playable status.
+ * 3. The AudioChannelAgent keeps a strong reference to the callback
+ * object.
+ */
+ void init(in mozIDOMWindow window, in long channelType,
+ in nsIAudioChannelAgentCallback callback);
+
+ /**
+ * This method is just like init(), except the audio channel agent keeps a
+ * weak reference to the callback object.
+ *
+ * In order for this to work, |callback| must implement
+ * nsISupportsWeakReference.
+ */
+ void initWithWeakCallback(in mozIDOMWindow window, in long channelType,
+ in nsIAudioChannelAgentCallback callback);
+
+ /**
+ * Notify the agent that we want to start playing.
+ * Note: Gecko component SHOULD call this function first then start to
+ * play audio stream only when return value is true.
+ *
+ * @param config
+ * It contains the playback related states (volume/mute/suspend)
+ */
+ void notifyStartedPlaying(in AudioPlaybackConfig config, in uint8_t audible);
+
+ /**
+ * Notify the agent we no longer want to play.
+ *
+ * Note : even if notifyStartedPlaying() returned false, the agent would
+ * still be registered with the audio channel service and receive callbacks
+ * for status changes. So notifyStoppedPlaying must still eventually be
+ * called to unregister the agent with the channel service.
+ */
+ void notifyStoppedPlaying();
+
+
+ /**
+ * Notify agent that we already start producing audible data.
+ *
+ * Note : sometime audio might become silent during playing, this method is used to
+ * notify the actually audible state to other services which want to know
+ * about that, ex. tab sound indicator.
+ */
+ void notifyStartedAudible(in uint8_t audible, in uint32_t reason);
+};
diff --git a/dom/audiochannel/nsIAudioChannelService.idl b/dom/audiochannel/nsIAudioChannelService.idl
new file mode 100644
index 000000000..2c154a84b
--- /dev/null
+++ b/dom/audiochannel/nsIAudioChannelService.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+
+[scriptable, builtinclass, uuid(5cb24dbc-36c7-46a4-9966-ac73141dc795)]
+interface nsIAudioChannelService : nsISupports
+{
+ float getAudioChannelVolume(in mozIDOMWindowProxy window,
+ in unsigned short audioChannel);
+
+ void setAudioChannelVolume(in mozIDOMWindowProxy window,
+ in unsigned short audioChannel,
+ in float volume);
+
+ boolean getAudioChannelMuted(in mozIDOMWindowProxy window,
+ in unsigned short audioChannel);
+
+ void setAudioChannelMuted(in mozIDOMWindowProxy window,
+ in unsigned short audioChannel,
+ in boolean muted);
+
+ boolean isAudioChannelActive(in mozIDOMWindowProxy window,
+ in unsigned short audioChannel);
+};