diff --git a/content/html/content/src/HTMLAudioElement.cpp b/content/html/content/src/HTMLAudioElement.cpp
index c5897c41543..52839396ca9 100644
--- a/content/html/content/src/HTMLAudioElement.cpp
+++ b/content/html/content/src/HTMLAudioElement.cpp
@@ -245,6 +245,12 @@ HTMLAudioElement::CanPlayChanged(int32_t canPlay)
return NS_OK;
}
+NS_IMETHODIMP
+HTMLAudioElement::WindowVolumeChanged()
+{
+ return HTMLMediaElement::WindowVolumeChanged();
+}
+
NS_IMETHODIMP
HTMLAudioElement::Notify(nsITimer* aTimer)
{
diff --git a/content/html/content/src/HTMLMediaElement.cpp b/content/html/content/src/HTMLMediaElement.cpp
index c5cc64da193..05117b7377e 100644
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -1729,6 +1729,14 @@ void HTMLMediaElement::SetVolumeInternal()
float effectiveVolume = mMuted ? 0.0f :
mAudioChannelFaded ? float(mVolume) * FADED_VOLUME_RATIO : float(mVolume);
+ if (mAudioChannelAgent) {
+ float volume;
+ nsresult rv = mAudioChannelAgent->GetWindowVolume(&volume);
+ if (NS_SUCCEEDED(rv)) {
+ effectiveVolume *= volume;
+ }
+ }
+
if (mDecoder) {
mDecoder->SetVolume(effectiveVolume);
} else if (mAudioStream) {
@@ -3921,6 +3929,12 @@ NS_IMETHODIMP HTMLMediaElement::CanPlayChanged(int32_t canPlay)
return NS_OK;
}
+NS_IMETHODIMP HTMLMediaElement::WindowVolumeChanged()
+{
+ SetVolumeInternal();
+ return NS_OK;
+}
+
/* readonly attribute TextTrackList textTracks; */
TextTrackList*
HTMLMediaElement::TextTracks() const
diff --git a/content/media/webaudio/AudioContext.cpp b/content/media/webaudio/AudioContext.cpp
index 5e2b05c9060..6e1643cbdab 100644
--- a/content/media/webaudio/AudioContext.cpp
+++ b/content/media/webaudio/AudioContext.cpp
@@ -64,8 +64,6 @@ NS_IMPL_RELEASE_INHERITED(AudioContext, nsDOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioContext)
NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
-static uint8_t gWebAudioOutputKey;
-
static float GetSampleRateForAudioContext(bool aIsOffline, float aSampleRate)
{
if (aIsOffline) {
@@ -95,7 +93,6 @@ AudioContext::AudioContext(nsPIDOMWindow* aWindow,
// bound to the window.
mDestination = new AudioDestinationNode(this, aIsOffline, aNumberOfChannels,
aLength, aSampleRate);
- mDestination->Stream()->AddAudioOutput(&gWebAudioOutputKey);
// We skip calling SetIsOnlyNodeForContext during mDestination's constructor,
// because we can only call SetIsOnlyNodeForContext after mDestination has
// been set up.
diff --git a/content/media/webaudio/AudioDestinationNode.cpp b/content/media/webaudio/AudioDestinationNode.cpp
index 5884ed1f5a4..3f685db9380 100644
--- a/content/media/webaudio/AudioDestinationNode.cpp
+++ b/content/media/webaudio/AudioDestinationNode.cpp
@@ -23,6 +23,8 @@
namespace mozilla {
namespace dom {
+static uint8_t gWebAudioOutputKey;
+
class OfflineDestinationNodeEngine : public AudioNodeEngine
{
public:
@@ -233,6 +235,7 @@ AudioDestinationNode::AudioDestinationNode(AudioContext* aContext,
mStream = graph->CreateAudioNodeStream(engine, MediaStreamGraph::EXTERNAL_STREAM);
mStream->AddMainThreadListener(this);
+ mStream->AddAudioOutput(&gWebAudioOutputKey);
if (!aIsOffline && UseAudioChannelService()) {
nsCOMPtr target = do_QueryInterface(GetOwner());
@@ -380,6 +383,23 @@ AudioDestinationNode::CanPlayChanged(int32_t aCanPlay)
return NS_OK;
}
+NS_IMETHODIMP
+AudioDestinationNode::WindowVolumeChanged()
+{
+ MOZ_ASSERT(mAudioChannelAgent);
+
+ if (!mStream) {
+ return NS_OK;
+ }
+
+ float volume;
+ nsresult rv = mAudioChannelAgent->GetWindowVolume(&volume);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStream->SetAudioOutputVolume(&gWebAudioOutputKey, volume);
+ return NS_OK;
+}
+
AudioChannel
AudioDestinationNode::MozAudioChannelType() const
{
diff --git a/content/media/webaudio/AudioDestinationNode.h b/content/media/webaudio/AudioDestinationNode.h
index 3b78eec53d3..6299cdf56ad 100644
--- a/content/media/webaudio/AudioDestinationNode.h
+++ b/content/media/webaudio/AudioDestinationNode.h
@@ -38,6 +38,7 @@ public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioDestinationNode, AudioNode)
+ NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle aScope) MOZ_OVERRIDE;
@@ -61,9 +62,6 @@ public:
// nsIDOMEventListener
NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
- // nsIAudioChannelAgentCallback
- NS_IMETHOD CanPlayChanged(int32_t aCanPlay);
-
AudioChannel MozAudioChannelType() const;
void SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv);
diff --git a/dom/audiochannel/AudioChannelAgent.cpp b/dom/audiochannel/AudioChannelAgent.cpp
index 840e106825e..af8f8a306fd 100644
--- a/dom/audiochannel/AudioChannelAgent.cpp
+++ b/dom/audiochannel/AudioChannelAgent.cpp
@@ -6,6 +6,7 @@
#include "AudioChannelCommon.h"
#include "AudioChannelService.h"
#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
#include "nsXULAppAPI.h"
using namespace mozilla::dom;
@@ -183,3 +184,29 @@ AudioChannelAgent::GetCallback()
}
return callback.forget();
}
+
+void
+AudioChannelAgent::WindowVolumeChanged()
+{
+ nsCOMPtr callback = GetCallback();
+ if (!callback) {
+ return;
+ }
+
+ callback->WindowVolumeChanged();
+}
+
+NS_IMETHODIMP
+AudioChannelAgent::GetWindowVolume(float* aVolume)
+{
+ NS_ENSURE_ARG_POINTER(aVolume);
+
+ nsCOMPtr win = do_QueryInterface(mWindow);
+ if (!win) {
+ *aVolume = 1.0f;
+ return NS_OK;
+ }
+
+ *aVolume = win->GetAudioGlobalVolume();
+ return NS_OK;
+}
diff --git a/dom/audiochannel/AudioChannelAgent.h b/dom/audiochannel/AudioChannelAgent.h
index c4ed413b069..efb67843003 100644
--- a/dom/audiochannel/AudioChannelAgent.h
+++ b/dom/audiochannel/AudioChannelAgent.h
@@ -34,6 +34,13 @@ public:
AudioChannelAgent();
virtual void NotifyAudioChannelStateChanged();
+ void WindowVolumeChanged();
+
+ nsIDOMWindow* Window() const
+ {
+ return mWindow;
+ }
+
private:
virtual ~AudioChannelAgent();
diff --git a/dom/audiochannel/AudioChannelService.cpp b/dom/audiochannel/AudioChannelService.cpp
index 42d6ea0dbf3..78d42803d21 100644
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -780,3 +780,44 @@ AudioChannelService::GetInternalType(AudioChannelType aType,
MOZ_CRASH("unexpected audio channel type");
}
+
+struct RefreshAgentsVolumeData
+{
+ RefreshAgentsVolumeData(nsPIDOMWindow* aWindow)
+ : mWindow(aWindow)
+ {}
+
+ nsPIDOMWindow* mWindow;
+ nsTArray> mAgents;
+};
+
+PLDHashOperator
+AudioChannelService::RefreshAgentsVolumeEnumerator(AudioChannelAgent* aAgent,
+ AudioChannelAgentData* aUnused,
+ void* aPtr)
+{
+ MOZ_ASSERT(aAgent);
+ RefreshAgentsVolumeData* data = static_cast(aPtr);
+ MOZ_ASSERT(data);
+
+ nsCOMPtr 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();
+ }
+}
diff --git a/dom/audiochannel/AudioChannelService.h b/dom/audiochannel/AudioChannelService.h
index fdd97099d86..7aa7d98003e 100644
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -16,6 +16,8 @@
#include "AudioChannelAgent.h"
#include "nsClassHashtable.h"
+class nsPIDOMWindow;
+
namespace mozilla {
namespace dom {
#ifdef MOZ_WIDGET_GONK
@@ -85,6 +87,8 @@ public:
bool AnyAudioChannelIsActive();
+ void RefreshAgentsVolume(nsPIDOMWindow* aWindow);
+
#ifdef MOZ_WIDGET_GONK
void RegisterSpeakerManager(SpeakerManagerService* aSpeakerManager)
{
@@ -179,6 +183,11 @@ protected:
NotifyEnumerator(AudioChannelAgent* aAgent,
AudioChannelAgentData* aData, void *aUnused);
+ static PLDHashOperator
+ RefreshAgentsVolumeEnumerator(AudioChannelAgent* aAgent,
+ AudioChannelAgentData* aUnused,
+ void *aPtr);
+
nsClassHashtable< nsPtrHashKey, AudioChannelAgentData > mAgents;
#ifdef MOZ_WIDGET_GONK
nsTArray mSpeakerManager;
diff --git a/dom/audiochannel/nsIAudioChannelAgent.idl b/dom/audiochannel/nsIAudioChannelAgent.idl
index ad7603029a1..ef146cb5ec0 100644
--- a/dom/audiochannel/nsIAudioChannelAgent.idl
+++ b/dom/audiochannel/nsIAudioChannelAgent.idl
@@ -6,7 +6,7 @@
interface nsIDOMWindow;
-[function, scriptable, uuid(86975108-cd78-4796-8fc8-6cd777cd6eba)]
+[scriptable, uuid(194b55d9-39c0-45c6-b8ef-b8049f978ea5)]
interface nsIAudioChannelAgentCallback : nsISupports
{
/**
@@ -19,6 +19,11 @@ interface nsIAudioChannelAgentCallback : nsISupports
* it is faded state then the volume of media should be reduced.
*/
void canPlayChanged(in long canPlay);
+
+ /**
+ * Notified when the window volume/mute is changed
+ */
+ void windowVolumeChanged();
};
/**
@@ -35,7 +40,7 @@ interface nsIAudioChannelAgentCallback : nsISupports
* 1. Changes to the playable status of this channel.
*/
-[scriptable, uuid(86ef883d-9cec-4c04-994f-5de198286e7c)]
+[scriptable, uuid(2b0222a5-8f7b-49d2-9ab8-cd01b744b23e)]
interface nsIAudioChannelAgent : nsISupports
{
const long AUDIO_AGENT_CHANNEL_NORMAL = 0;
@@ -128,5 +133,10 @@ interface nsIAudioChannelAgent : nsISupports
* True if the window associated with the agent is visible.
*/
void setVisibilityState(in boolean visible);
+
+ /**
+ * Retrieve the volume from the window.
+ */
+ readonly attribute float windowVolume;
};
diff --git a/dom/audiochannel/tests/TestAudioChannelService.cpp b/dom/audiochannel/tests/TestAudioChannelService.cpp
index c645c19c2a0..17ad5d81f2e 100644
--- a/dom/audiochannel/tests/TestAudioChannelService.cpp
+++ b/dom/audiochannel/tests/TestAudioChannelService.cpp
@@ -104,6 +104,11 @@ public:
return NS_OK;
}
+ NS_IMETHODIMP WindowVolumeChanged()
+ {
+ return NS_OK;
+ }
+
nsresult GetCanPlay(AudioChannelState *_ret)
{
int loop = 0;
diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp
index b9ab0545a64..7579022cfcb 100644
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3732,3 +3732,53 @@ nsDOMWindowUtils::GetOMTAOrComputedStyle(nsIDOMElement* aElement,
return style->GetPropertyValue(aProperty, aResult);
}
+NS_IMETHODIMP
+nsDOMWindowUtils::GetAudioMuted(bool* aMuted)
+{
+ if (!nsContentUtils::IsCallerChrome()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ nsCOMPtr window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ *aMuted = window->GetAudioMuted();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetAudioMuted(bool aMuted)
+{
+ if (!nsContentUtils::IsCallerChrome()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ nsCOMPtr window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ window->SetAudioMuted(aMuted);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetAudioVolume(float* aVolume)
+{
+ if (!nsContentUtils::IsCallerChrome()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ nsCOMPtr window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ *aVolume = window->GetAudioVolume();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SetAudioVolume(float aVolume)
+{
+ if (!nsContentUtils::IsCallerChrome()) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ nsCOMPtr window = do_QueryReferent(mWindow);
+ NS_ENSURE_STATE(window);
+
+ return window->SetAudioVolume(aVolume);
+}
diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
index 39a802174c0..92d362e7805 100644
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -60,6 +60,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/Debug.h"
#include "mozilla/MouseEvents.h"
+#include "AudioChannelService.h"
// Interfaces Needed
#include "nsIFrame.h"
@@ -564,6 +565,7 @@ nsPIDOMWindow::nsPIDOMWindow(nsPIDOMWindow *aOuterWindow)
mMayHavePointerEnterLeaveEventListener(false),
mIsModalContentWindow(false),
mIsActive(false), mIsBackground(false),
+ mAudioMuted(false), mAudioVolume(1.0),
mInnerWindow(nullptr), mOuterWindow(aOuterWindow),
// Make sure no actual window ends up with mWindowID == 0
mWindowID(++gNextWindowID), mHasNotifiedGlobalCreated(false),
@@ -3582,6 +3584,100 @@ nsPIDOMWindow::CreatePerformanceObjectIfNeeded()
}
}
+bool
+nsPIDOMWindow::GetAudioMuted() const
+{
+ if (!IsInnerWindow()) {
+ return mInnerWindow->GetAudioMuted();
+ }
+
+ return mAudioMuted;
+}
+
+void
+nsPIDOMWindow::SetAudioMuted(bool aMuted)
+{
+ if (!IsInnerWindow()) {
+ mInnerWindow->SetAudioMuted(aMuted);
+ return;
+ }
+
+ if (mAudioMuted == aMuted) {
+ return;
+ }
+
+ mAudioMuted = aMuted;
+ RefreshMediaElements();
+}
+
+float
+nsPIDOMWindow::GetAudioVolume() const
+{
+ if (!IsInnerWindow()) {
+ return mInnerWindow->GetAudioVolume();
+ }
+
+ return mAudioVolume;
+}
+
+nsresult
+nsPIDOMWindow::SetAudioVolume(float aVolume)
+{
+ if (!IsInnerWindow()) {
+ return mInnerWindow->SetAudioVolume(aVolume);
+ }
+
+ if (aVolume < 0.0) {
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+ }
+
+ if (mAudioVolume == aVolume) {
+ return NS_OK;
+ }
+
+ mAudioVolume = aVolume;
+ RefreshMediaElements();
+ return NS_OK;
+}
+
+float
+nsPIDOMWindow::GetAudioGlobalVolume()
+{
+ float globalVolume = 1.0;
+ nsCOMPtr window = this;
+
+ do {
+ if (window->GetAudioMuted()) {
+ return 0;
+ }
+
+ globalVolume *= window->GetAudioVolume();
+
+ nsCOMPtr win;
+ window->GetParent(getter_AddRefs(win));
+ if (window == win) {
+ break;
+ }
+
+ window = do_QueryInterface(win);
+
+ // If there is not parent, or we are the toplevel or the volume is
+ // already 0.0, we don't continue.
+ } while (window && window != this && globalVolume);
+
+ return globalVolume;
+}
+
+void
+nsPIDOMWindow::RefreshMediaElements()
+{
+ nsRefPtr service =
+ AudioChannelService::GetAudioChannelService();
+ if (service) {
+ service->RefreshAgentsVolume(this);
+ }
+}
+
// nsISpeechSynthesisGetter
#ifdef MOZ_WEBSPEECH
diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h
index 1a0e7d26740..b9b30f5b23c 100644
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -190,11 +190,23 @@ public:
virtual NS_HIDDEN_(bool) IsRunningTimeout() = 0;
+ // Audio API
+ bool GetAudioMuted() const;
+ void SetAudioMuted(bool aMuted);
+
+ float GetAudioVolume() const;
+ nsresult SetAudioVolume(float aVolume);
+
+ float GetAudioGlobalVolume();
+
protected:
// Lazily instantiate an about:blank document if necessary, and if
// we have what it takes to do so.
void MaybeCreateDoc();
+ float GetAudioGlobalVolumeInternal(float aVolume);
+ void RefreshMediaElements();
+
public:
// Internal getter/setter for the frame element, this version of the
// getter crosses chrome boundaries whereas the public scriptable
@@ -768,6 +780,9 @@ protected:
// "active". Only used on outer windows.
bool mIsBackground;
+ bool mAudioMuted;
+ float mAudioVolume;
+
// And these are the references between inner and outer windows.
nsPIDOMWindow *mInnerWindow;
nsCOMPtr mOuterWindow;
diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini
index 54ee069419e..f5613e88a14 100644
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -8,6 +8,7 @@ support-files =
[test_Image_constructor.html]
[test_appname_override.html]
+[test_audioWindowUtils.html]
[test_bug913761.html]
[test_bug978522.html]
[test_bug979109.html]
diff --git a/dom/base/test/test_audioWindowUtils.html b/dom/base/test/test_audioWindowUtils.html
new file mode 100644
index 00000000000..4018ce6aef4
--- /dev/null
+++ b/dom/base/test/test_audioWindowUtils.html
@@ -0,0 +1,92 @@
+
+
+
+ Test for audio controller in windows
+
+
+
+
+
+
+
+
diff --git a/dom/fmradio/FMRadio.cpp b/dom/fmradio/FMRadio.cpp
index b324f5cd924..8016e82661b 100644
--- a/dom/fmradio/FMRadio.cpp
+++ b/dom/fmradio/FMRadio.cpp
@@ -389,6 +389,12 @@ FMRadio::CanPlayChanged(int32_t aCanPlay)
return NS_OK;
}
+NS_IMETHODIMP
+FMRadio::WindowVolumeChanged(float aVolume)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
void
FMRadio::SetCanPlay(bool aCanPlay)
{
diff --git a/dom/fmradio/FMRadio.h b/dom/fmradio/FMRadio.h
index 39b5a736f9f..64d51bbab27 100644
--- a/dom/fmradio/FMRadio.h
+++ b/dom/fmradio/FMRadio.h
@@ -34,6 +34,7 @@ public:
FMRadio();
NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK
NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper)
@@ -82,9 +83,6 @@ public:
IMPL_EVENT_HANDLER(antennaavailablechange);
IMPL_EVENT_HANDLER(frequencychange);
- // nsIAudioChannelAgentCallback
- NS_IMETHOD CanPlayChanged(int32_t aCanPlay);
-
// nsIDOMEventListener
NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl
index 061a9d2bc09..7fbb7b0f671 100644
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -43,7 +43,7 @@ interface nsIDOMEventTarget;
interface nsIRunnable;
interface nsICompositionStringSynthesizer;
-[scriptable, uuid(27efada9-b8ea-4d70-a2e6-f46b9ba905f4)]
+[scriptable, uuid(ef70a299-033c-4adc-b214-6649aed9d828)]
interface nsIDOMWindowUtils : nsISupports {
/**
@@ -1582,4 +1582,17 @@ interface nsIDOMWindowUtils : nsISupports {
*/
AString getOMTAOrComputedStyle(in nsIDOMElement aElement,
in AString aProperty);
+
+ /**
+ * With this it's possible to mute all the MediaElements in this window.
+ * We have audioMuted and audioVolume to preserve the volume across
+ * mute/umute.
+ */
+ attribute boolean audioMuted;
+
+ /**
+ * range: greater or equal to 0. The real volume level is affected by the
+ * volume of all ancestor windows.
+ */
+ attribute float audioVolume;
};