From e322ded7424c8d7a6915eb5c1c70d76ba6376e75 Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Fri, 24 Jun 2011 07:20:03 -0700 Subject: [PATCH] Bug 441197: Maintain an audio session across processes. r=jimm --- dom/plugins/ipc/PPluginModule.ipdl | 6 + dom/plugins/ipc/PluginModuleChild.cpp | 24 ++++ dom/plugins/ipc/PluginModuleChild.h | 5 + dom/plugins/ipc/PluginModuleParent.cpp | 18 +++ widget/src/windows/AudioSession.cpp | 156 +++++++++++++++++++++---- widget/src/windows/AudioSession.h | 14 +++ 6 files changed, 202 insertions(+), 21 deletions(-) diff --git a/dom/plugins/ipc/PPluginModule.ipdl b/dom/plugins/ipc/PPluginModule.ipdl index 30ca01417a9..fbb6e722f4f 100644 --- a/dom/plugins/ipc/PPluginModule.ipdl +++ b/dom/plugins/ipc/PPluginModule.ipdl @@ -48,6 +48,7 @@ using NPNVariable; using base::FileDescriptor; using mozilla::plugins::NativeThreadId; using mac_plugin_interposing::NSCursorInfo; +using nsID; namespace mozilla { namespace plugins { @@ -105,6 +106,11 @@ child: rpc NPP_GetSitesWithData() returns (nsCString[] sites); + // Windows specific message to set up an audio session in the plugin process + async SetAudioSessionData(nsID aID, + nsString aDisplayName, + nsString aIconPath); + parent: /** * This message is only used on X11 platforms. diff --git a/dom/plugins/ipc/PluginModuleChild.cpp b/dom/plugins/ipc/PluginModuleChild.cpp index a7d1e4d204a..424c5523325 100644 --- a/dom/plugins/ipc/PluginModuleChild.cpp +++ b/dom/plugins/ipc/PluginModuleChild.cpp @@ -71,6 +71,7 @@ #ifdef XP_WIN #include "COMMessageFilter.h" #include "nsWindowsDllInterceptor.h" +#include "mozilla/widget/AudioSession.h" #endif #ifdef OS_MACOSX @@ -594,6 +595,10 @@ PluginModuleChild::AnswerNP_Shutdown(NPError *rv) { AssertPluginThread(); +#if defined XP_WIN && MOZ_WINSDK_TARGETVER >= MOZ_NTDDI_LONGHORN + mozilla::widget::StopAudioSession(); +#endif + // the PluginModuleParent shuts down this process after this RPC // call pops off its stack @@ -649,6 +654,25 @@ PluginModuleChild::AnswerNPP_GetSitesWithData(InfallibleTArray* aResu return true; } +bool +PluginModuleChild::RecvSetAudioSessionData(const nsID& aId, + const nsString& aDisplayName, + const nsString& aIconPath) +{ + nsresult rv; +#if !defined XP_WIN || MOZ_WINSDK_TARGETVER < MOZ_NTDDI_LONGHORN + NS_RUNTIMEABORT("Not Reached!"); + return false; +#else + rv = mozilla::widget::RecvAudioSessionData(aId, aDisplayName, aIconPath); + NS_ENSURE_SUCCESS(rv, true); // Bail early if this fails + + // Ignore failures here; we can't really do anything about them + mozilla::widget::StartAudioSession(); + return true; +#endif +} + void PluginModuleChild::QuickExit() { diff --git a/dom/plugins/ipc/PluginModuleChild.h b/dom/plugins/ipc/PluginModuleChild.h index 559a168d44e..3ed2d35fbd1 100644 --- a/dom/plugins/ipc/PluginModuleChild.h +++ b/dom/plugins/ipc/PluginModuleChild.h @@ -163,6 +163,11 @@ protected: virtual bool AnswerNPP_GetSitesWithData(InfallibleTArray* aResult); + virtual bool + RecvSetAudioSessionData(const nsID& aId, + const nsString& aDisplayName, + const nsString& aIconPath); + virtual void ActorDestroy(ActorDestroyReason why); diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp index c93c706f7c2..5316eccbbc8 100644 --- a/dom/plugins/ipc/PluginModuleParent.cpp +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -66,6 +66,10 @@ #include "nsNPAPIPlugin.h" #include "nsILocalFile.h" +#ifdef XP_WIN +#include "mozilla/widget/AudioSession.h" +#endif + using base::KillProcess; using mozilla::PluginLibrary; @@ -750,6 +754,8 @@ PluginModuleParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs { PLUGIN_LOG_DEBUG_METHOD; + nsresult rv; + mNPNIface = bFuncs; if (mShutdown) { @@ -784,6 +790,18 @@ PluginModuleParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) if (!CallNP_Initialize(&mPluginThread, error)) return NS_ERROR_FAILURE; +#if defined XP_WIN && MOZ_WINSDK_TARGETVER >= MOZ_NTDDI_LONGHORN + // Send the info needed to join the chrome process's audio session to the + // plugin process + nsID id; + nsString sessionName; + nsString iconPath; + + if (NS_SUCCEEDED(mozilla::widget::GetAudioSessionData(id, sessionName, + iconPath))) + SendSetAudioSessionData(id, sessionName, iconPath); +#endif + return NS_OK; } #endif diff --git a/widget/src/windows/AudioSession.cpp b/widget/src/windows/AudioSession.cpp index fe398b5770b..54120abd640 100644 --- a/widget/src/windows/AudioSession.cpp +++ b/widget/src/windows/AudioSession.cpp @@ -51,6 +51,7 @@ #include "nsAutoPtr.h" #include "nsServiceManagerUtils.h" #include "nsString.h" +#include "nsXULAppApi.h" #include @@ -90,12 +91,29 @@ public: nsresult Start(); nsresult Stop(); + void StopInternal(); + nsresult GetSessionData(nsID& aID, + nsString& aSessionName, + nsString& aIconPath); + + nsresult SetSessionData(const nsID& aID, + const nsString& aSessionName, + const nsString& aIconPath); + + enum SessionState { + UNINITIALIZED, // Has not been initialized yet + STARTED, // Started + CLONED, // SetSessionInfoCalled, Start not called + FAILED, // The autdio session failed to start + STOPPED // Stop called + }; protected: nsRefPtr mAudioSessionControl; nsString mDisplayName; nsString mIconPath; nsID mSessionGroupingParameter; + SessionState mState; nsAutoRefCnt mRefCnt; NS_DECL_OWNINGTHREAD @@ -115,11 +133,31 @@ StopAudioSession() return AudioSession::GetSingleton()->Stop(); } +nsresult +GetAudioSessionData(nsID& aID, + nsString& aSessionName, + nsString& aIconPath) +{ + return AudioSession::GetSingleton()->GetSessionData(aID, + aSessionName, + aIconPath); +} + +nsresult +RecvAudioSessionData(const nsID& aID, + const nsString& aSessionName, + const nsString& aIconPath) +{ + return AudioSession::GetSingleton()->SetSessionData(aID, + aSessionName, + aIconPath); +} + AudioSession* AudioSession::sService = NULL; AudioSession::AudioSession() { - + mState = UNINITIALIZED; } AudioSession::~AudioSession() @@ -164,6 +202,9 @@ AudioSession::QueryInterface(REFIID iid, void **ppv) nsresult AudioSession::Start() { + NS_ABORT_IF_FALSE(mState == UNINITIALIZED || mState == CLONED, + "State invariants violated"); + const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); const IID IID_IAudioSessionManager = __uuidof(IAudioSessionManager); @@ -173,30 +214,46 @@ AudioSession::Start() if (FAILED(::CoInitialize(NULL))) return NS_ERROR_FAILURE; - nsCOMPtr bundleService = - do_GetService(NS_STRINGBUNDLE_CONTRACTID); - NS_ENSURE_TRUE(bundleService, NS_ERROR_FAILURE); + if (mState == UNINITIALIZED) { + mState = FAILED; - nsCOMPtr bundle; - bundleService->CreateBundle("chrome://branding/locale/brand.properties", - getter_AddRefs(bundle)); - NS_ENSURE_TRUE(bundle, NS_ERROR_FAILURE); + // XXXkhuey implement this for content processes + if (XRE_GetProcessType() == GeckoProcessType_Content) + return NS_ERROR_FAILURE; - bundle->GetStringFromName(NS_LITERAL_STRING("brandFullName").get(), - getter_Copies(mDisplayName)); + NS_ABORT_IF_FALSE(XRE_GetProcessType() == GeckoProcessType_Default, + "Should only get here in a chrome process!"); - PRUnichar *buffer; - mIconPath.GetMutableData(&buffer, MAX_PATH); + nsCOMPtr bundleService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID); + NS_ENSURE_TRUE(bundleService, NS_ERROR_FAILURE); - // XXXkhuey we should provide a way for a xulrunner app to specify an icon - // that's not in the product binary. - ::GetModuleFileNameW(NULL, buffer, MAX_PATH); + nsCOMPtr bundle; + bundleService->CreateBundle("chrome://branding/locale/brand.properties", + getter_AddRefs(bundle)); + NS_ENSURE_TRUE(bundle, NS_ERROR_FAILURE); - nsCOMPtr uuidgen = - do_GetService("@mozilla.org/uuid-generator;1"); - NS_ASSERTION(uuidgen, "No UUID-Generator?!?"); + bundle->GetStringFromName(NS_LITERAL_STRING("brandFullName").get(), + getter_Copies(mDisplayName)); - uuidgen->GenerateUUIDInPlace(&mSessionGroupingParameter); + PRUnichar *buffer; + mIconPath.GetMutableData(&buffer, MAX_PATH); + + // XXXkhuey we should provide a way for a xulrunner app to specify an icon + // that's not in the product binary. + ::GetModuleFileNameW(NULL, buffer, MAX_PATH); + + nsCOMPtr uuidgen = + do_GetService("@mozilla.org/uuid-generator;1"); + NS_ASSERTION(uuidgen, "No UUID-Generator?!?"); + + uuidgen->GenerateUUIDInPlace(&mSessionGroupingParameter); + } + + mState = FAILED; + + NS_ABORT_IF_FALSE(!mDisplayName.IsEmpty() || !mIconPath.IsEmpty(), + "Should never happen ..."); nsRefPtr enumerator; hr = ::CoCreateInstance(CLSID_MMDeviceEnumerator, @@ -256,12 +313,16 @@ AudioSession::Start() return NS_ERROR_FAILURE; } + mState = STARTED; + return NS_OK; } void AudioSession::StopInternal() { + static const nsID blankId = {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0} }; + if (mAudioSessionControl) { mAudioSessionControl->SetGroupingParam((LPCGUID)&blankId, NULL); mAudioSessionControl->UnregisterAudioSessionNotification(this); @@ -272,11 +333,17 @@ AudioSession::StopInternal() nsresult AudioSession::Stop() { - const nsID blankId = {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0} }; + NS_ABORT_IF_FALSE(mState == STARTED || + mState == UNINITIALIZED || // XXXremove this + mState == FAILED, + "State invariants violated"); + mState = STOPPED; + nsRefPtr kungFuDeathGrip; kungFuDeathGrip.swap(sService); - StopInternal(); + if (XRE_GetProcessType() != GeckoProcessType_Content) + StopInternal(); // At this point kungFuDeathGrip should be the only reference to AudioSession @@ -285,6 +352,53 @@ AudioSession::Stop() return NS_OK; } +void CopynsID(nsID& lhs, const nsID& rhs) +{ + lhs.m0 = rhs.m0; + lhs.m1 = rhs.m1; + lhs.m2 = rhs.m2; + for (int i = 0; i < 8; i++ ) { + lhs.m3[i] = rhs.m3[i]; + } +} + +nsresult +AudioSession::GetSessionData(nsID& aID, + nsString& aSessionName, + nsString& aIconPath) +{ + NS_ABORT_IF_FALSE(mState == FAILED || + mState == STARTED || + mState == CLONED, + "State invariants violated"); + + CopynsID(aID, mSessionGroupingParameter); + aSessionName = mDisplayName; + aIconPath = mIconPath; + + if (mState == FAILED) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult +AudioSession::SetSessionData(const nsID& aID, + const nsString& aSessionName, + const nsString& aIconPath) +{ + NS_ABORT_IF_FALSE(mState == UNINITIALIZED, + "State invariants violated"); + NS_ABORT_IF_FALSE(XRE_GetProcessType() != GeckoProcessType_Default, + "Should never get here in a chrome process!"); + mState = CLONED; + + CopynsID(mSessionGroupingParameter, aID); + mDisplayName = aSessionName; + mIconPath = aIconPath; + return NS_OK; +} + STDMETHODIMP AudioSession::OnChannelVolumeChanged(DWORD aChannelCount, float aChannelVolumeArray[], diff --git a/widget/src/windows/AudioSession.h b/widget/src/windows/AudioSession.h index e31e4b45e66..f9245d0b48b 100644 --- a/widget/src/windows/AudioSession.h +++ b/widget/src/windows/AudioSession.h @@ -41,7 +41,21 @@ namespace mozilla { namespace widget { +// Start the audio session in the current process nsresult StartAudioSession(); + +// Pass the information necessary to start an audio session in another process +nsresult GetAudioSessionData(nsID& aID, + nsString& aSessionName, + nsString& aIconPath); + +// Receive the information necessary to start an audio session in a non-chrome +// process +nsresult RecvAudioSessionData(const nsID& aID, + const nsString& aSessionName, + const nsString& aIconPath); + +// Stop the audio session in the current process nsresult StopAudioSession(); } // namespace widget