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; };