summaryrefslogtreecommitdiff
path: root/widget/windows/AudioSession.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/windows/AudioSession.cpp')
-rw-r--r--widget/windows/AudioSession.cpp453
1 files changed, 453 insertions, 0 deletions
diff --git a/widget/windows/AudioSession.cpp b/widget/windows/AudioSession.cpp
new file mode 100644
index 0000000000..11e5ba50cd
--- /dev/null
+++ b/widget/windows/AudioSession.cpp
@@ -0,0 +1,453 @@
+/* -*- Mode: C++; 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 <windows.h>
+#include <audiopolicy.h>
+#include <mmdeviceapi.h>
+
+#include "mozilla/RefPtr.h"
+#include "nsIStringBundle.h"
+#include "nsIUUIDGenerator.h"
+#include "nsIXULAppInfo.h"
+
+//#include "AudioSession.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Attributes.h"
+
+#include <objbase.h>
+
+namespace mozilla {
+namespace widget {
+
+/*
+ * To take advantage of what Vista+ have to offer with respect to audio,
+ * we need to maintain an audio session. This class wraps IAudioSessionControl
+ * and implements IAudioSessionEvents (for callbacks from Windows)
+ */
+class AudioSession final : public IAudioSessionEvents {
+private:
+ AudioSession();
+ ~AudioSession();
+public:
+ static AudioSession* GetSingleton();
+
+ // COM IUnknown
+ STDMETHODIMP_(ULONG) AddRef();
+ STDMETHODIMP QueryInterface(REFIID, void**);
+ STDMETHODIMP_(ULONG) Release();
+
+ // IAudioSessionEvents
+ STDMETHODIMP OnChannelVolumeChanged(DWORD aChannelCount,
+ float aChannelVolumeArray[],
+ DWORD aChangedChannel,
+ LPCGUID aContext);
+ STDMETHODIMP OnDisplayNameChanged(LPCWSTR aDisplayName, LPCGUID aContext);
+ STDMETHODIMP OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext);
+ STDMETHODIMP OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext);
+ STDMETHODIMP OnSessionDisconnected(AudioSessionDisconnectReason aReason);
+private:
+ nsresult OnSessionDisconnectedInternal();
+public:
+ STDMETHODIMP OnSimpleVolumeChanged(float aVolume,
+ BOOL aMute,
+ LPCGUID aContext);
+ STDMETHODIMP OnStateChanged(AudioSessionState aState);
+
+ 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 audio session failed to start
+ STOPPED, // Stop called
+ AUDIO_SESSION_DISCONNECTED // Audio session disconnected
+ };
+protected:
+ RefPtr<IAudioSessionControl> mAudioSessionControl;
+ nsString mDisplayName;
+ nsString mIconPath;
+ nsID mSessionGroupingParameter;
+ SessionState mState;
+
+ ThreadSafeAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+
+ static AudioSession* sService;
+};
+
+nsresult
+StartAudioSession()
+{
+ return AudioSession::GetSingleton()->Start();
+}
+
+nsresult
+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 = nullptr;
+
+AudioSession::AudioSession()
+{
+ mState = UNINITIALIZED;
+}
+
+AudioSession::~AudioSession()
+{
+
+}
+
+AudioSession*
+AudioSession::GetSingleton()
+{
+ if (!(AudioSession::sService)) {
+ RefPtr<AudioSession> service = new AudioSession();
+ service.forget(&AudioSession::sService);
+ }
+
+ // We don't refcount AudioSession on the Gecko side, we hold one single ref
+ // as long as the appshell is running.
+ return AudioSession::sService;
+}
+
+// It appears Windows will use us on a background thread ...
+NS_IMPL_ADDREF(AudioSession)
+NS_IMPL_RELEASE(AudioSession)
+
+STDMETHODIMP
+AudioSession::QueryInterface(REFIID iid, void **ppv)
+{
+ const IID IID_IAudioSessionEvents = __uuidof(IAudioSessionEvents);
+ if ((IID_IUnknown == iid) ||
+ (IID_IAudioSessionEvents == iid)) {
+ *ppv = static_cast<IAudioSessionEvents*>(this);
+ AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+// Once we are started Windows will hold a reference to us through our
+// IAudioSessionEvents interface that will keep us alive until the appshell
+// calls Stop.
+nsresult
+AudioSession::Start()
+{
+ MOZ_ASSERT(mState == UNINITIALIZED ||
+ mState == CLONED ||
+ mState == AUDIO_SESSION_DISCONNECTED,
+ "State invariants violated");
+
+ const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
+ const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
+ const IID IID_IAudioSessionManager = __uuidof(IAudioSessionManager);
+
+ HRESULT hr;
+
+ // There's a matching CoUninit in Stop() for this tied to a state of
+ // UNINITIALIZED.
+ hr = CoInitialize(nullptr);
+ MOZ_ASSERT(SUCCEEDED(hr), "CoInitialize failure in audio session control, unexpected");
+
+ if (mState == UNINITIALIZED) {
+ mState = FAILED;
+
+ // Content processes should be CLONED
+ if (XRE_IsContentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess(),
+ "Should only get here in a chrome process!");
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_FAILURE);
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_TRUE(bundle, NS_ERROR_FAILURE);
+
+ bundle->GetStringFromName(u"brandFullName",
+ getter_Copies(mDisplayName));
+
+ wchar_t *buffer;
+ mIconPath.GetMutableData(&buffer, MAX_PATH);
+ ::GetModuleFileNameW(nullptr, buffer, MAX_PATH);
+
+ nsCOMPtr<nsIUUIDGenerator> uuidgen =
+ do_GetService("@mozilla.org/uuid-generator;1");
+ NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE);
+ uuidgen->GenerateUUIDInPlace(&mSessionGroupingParameter);
+ }
+
+ mState = FAILED;
+
+ MOZ_ASSERT(!mDisplayName.IsEmpty() || !mIconPath.IsEmpty(),
+ "Should never happen ...");
+
+ RefPtr<IMMDeviceEnumerator> enumerator;
+ hr = ::CoCreateInstance(CLSID_MMDeviceEnumerator,
+ nullptr,
+ CLSCTX_ALL,
+ IID_IMMDeviceEnumerator,
+ getter_AddRefs(enumerator));
+ if (FAILED(hr))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ RefPtr<IMMDevice> device;
+ hr = enumerator->GetDefaultAudioEndpoint(EDataFlow::eRender,
+ ERole::eMultimedia,
+ getter_AddRefs(device));
+ if (FAILED(hr)) {
+ if (hr == E_NOTFOUND)
+ return NS_ERROR_NOT_AVAILABLE;
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<IAudioSessionManager> manager;
+ hr = device->Activate(IID_IAudioSessionManager,
+ CLSCTX_ALL,
+ nullptr,
+ getter_AddRefs(manager));
+ if (FAILED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ hr = manager->GetAudioSessionControl(&GUID_NULL,
+ 0,
+ getter_AddRefs(mAudioSessionControl));
+
+ if (FAILED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ hr = mAudioSessionControl->SetGroupingParam((LPGUID)&mSessionGroupingParameter,
+ nullptr);
+ if (FAILED(hr)) {
+ StopInternal();
+ return NS_ERROR_FAILURE;
+ }
+
+ hr = mAudioSessionControl->SetDisplayName(mDisplayName.get(), nullptr);
+ if (FAILED(hr)) {
+ StopInternal();
+ return NS_ERROR_FAILURE;
+ }
+
+ hr = mAudioSessionControl->SetIconPath(mIconPath.get(), nullptr);
+ if (FAILED(hr)) {
+ StopInternal();
+ return NS_ERROR_FAILURE;
+ }
+
+ // Increments refcount of 'this'.
+ hr = mAudioSessionControl->RegisterAudioSessionNotification(this);
+ if (FAILED(hr)) {
+ StopInternal();
+ return NS_ERROR_FAILURE;
+ }
+
+ mState = STARTED;
+
+ return NS_OK;
+}
+
+void
+AudioSession::StopInternal()
+{
+ if (mAudioSessionControl &&
+ (mState == STARTED || mState == STOPPED)) {
+ // Decrement refcount of 'this'
+ mAudioSessionControl->UnregisterAudioSessionNotification(this);
+ }
+ mAudioSessionControl = nullptr;
+}
+
+nsresult
+AudioSession::Stop()
+{
+ MOZ_ASSERT(mState == STARTED ||
+ mState == UNINITIALIZED || // XXXremove this
+ mState == FAILED,
+ "State invariants violated");
+ SessionState state = mState;
+ mState = STOPPED;
+
+ {
+ RefPtr<AudioSession> kungFuDeathGrip;
+ kungFuDeathGrip.swap(sService);
+
+ StopInternal();
+ }
+
+ if (state != UNINITIALIZED) {
+ ::CoUninitialize();
+ }
+ 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)
+{
+ MOZ_ASSERT(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)
+{
+ MOZ_ASSERT(mState == UNINITIALIZED,
+ "State invariants violated");
+ MOZ_ASSERT(!XRE_IsParentProcess(),
+ "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[],
+ DWORD aChangedChannel,
+ LPCGUID aContext)
+{
+ return S_OK; // NOOP
+}
+
+STDMETHODIMP
+AudioSession::OnDisplayNameChanged(LPCWSTR aDisplayName,
+ LPCGUID aContext)
+{
+ return S_OK; // NOOP
+}
+
+STDMETHODIMP
+AudioSession::OnGroupingParamChanged(LPCGUID aGroupingParam,
+ LPCGUID aContext)
+{
+ return S_OK; // NOOP
+}
+
+STDMETHODIMP
+AudioSession::OnIconPathChanged(LPCWSTR aIconPath,
+ LPCGUID aContext)
+{
+ return S_OK; // NOOP
+}
+
+STDMETHODIMP
+AudioSession::OnSessionDisconnected(AudioSessionDisconnectReason aReason)
+{
+ // Run our code asynchronously. Per MSDN we can't do anything interesting
+ // in this callback.
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod(this, &AudioSession::OnSessionDisconnectedInternal);
+ NS_DispatchToMainThread(runnable);
+ return S_OK;
+}
+
+nsresult
+AudioSession::OnSessionDisconnectedInternal()
+{
+ if (!mAudioSessionControl)
+ return NS_OK;
+
+ // When successful, UnregisterAudioSessionNotification will decrement the
+ // refcount of 'this'. Start will re-increment it. In the interim,
+ // we'll need to reference ourselves.
+ RefPtr<AudioSession> kungFuDeathGrip(this);
+ mAudioSessionControl->UnregisterAudioSessionNotification(this);
+ mAudioSessionControl = nullptr;
+
+ mState = AUDIO_SESSION_DISCONNECTED;
+ CoUninitialize();
+ Start(); // If it fails there's not much we can do.
+ return NS_OK;
+}
+
+STDMETHODIMP
+AudioSession::OnSimpleVolumeChanged(float aVolume,
+ BOOL aMute,
+ LPCGUID aContext)
+{
+ return S_OK; // NOOP
+}
+
+STDMETHODIMP
+AudioSession::OnStateChanged(AudioSessionState aState)
+{
+ return S_OK; // NOOP
+}
+
+} // namespace widget
+} // namespace mozilla