forked from mirrors/gecko-dev
		
	 fffb25b74f
			
		
	
	
		fffb25b74f
		
	
	
	
	
		
			
			This was done automatically replacing: s/mozilla::Move/std::move/ s/ Move(/ std::move(/ s/(Move(/(std::move(/ Removing the 'using mozilla::Move;' lines. And then with a few manual fixups, see the bug for the split series.. MozReview-Commit-ID: Jxze3adipUh
		
			
				
	
	
		
			518 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			518 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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 "mozilla/Mutex.h"
 | |
| #include "mozilla/WindowsVersion.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();
 | |
|   nsresult CommitAudioSessionData();
 | |
| 
 | |
| 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;
 | |
|   // Guards the IAudioSessionControl
 | |
|   mozilla::Mutex mMutex;
 | |
| 
 | |
|   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() :
 | |
|   mMutex("AudioSessionControl")
 | |
| {
 | |
|   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("brandFullName", 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;
 | |
|   }
 | |
| 
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   hr = manager->GetAudioSessionControl(&GUID_NULL,
 | |
|                                        0,
 | |
|                                        getter_AddRefs(mAudioSessionControl));
 | |
| 
 | |
|   if (FAILED(hr)) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   // Increments refcount of 'this'.
 | |
|   hr = mAudioSessionControl->RegisterAudioSessionNotification(this);
 | |
|   if (FAILED(hr)) {
 | |
|     StopInternal();
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIRunnable> runnable =
 | |
|     NewRunnableMethod("AudioSession::CommitAudioSessionData",
 | |
|                       this, &AudioSession::CommitAudioSessionData);
 | |
|   NS_DispatchToMainThread(runnable);
 | |
| 
 | |
|   mState = STARTED;
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void
 | |
| SpawnASCReleaseThread(RefPtr<IAudioSessionControl>&& aASC)
 | |
| {
 | |
|   // Fake moving to the other thread by circumventing the ref count.
 | |
|   // (RefPtrs don't play well with C++11 lambdas and we don't want to use
 | |
|   // XPCOM here.)
 | |
|   IAudioSessionControl* rawPtr = nullptr;
 | |
|   aASC.forget(&rawPtr);
 | |
|   MOZ_ASSERT(rawPtr);
 | |
|   PRThread* thread =
 | |
|     PR_CreateThread(PR_USER_THREAD,
 | |
|                     [](void* aRawPtr) { static_cast<IAudioSessionControl*>(aRawPtr)->Release(); },
 | |
|                     rawPtr, PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_UNJOINABLE_THREAD, 0);
 | |
|   if (!thread) {
 | |
|     // We can't make a thread so just destroy the IAudioSessionControl here.
 | |
|     rawPtr->Release();
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| void
 | |
| AudioSession::StopInternal()
 | |
| {
 | |
|   mMutex.AssertCurrentThreadOwns();
 | |
| 
 | |
|   if (mAudioSessionControl &&
 | |
|       (mState == STARTED || mState == STOPPED)) {
 | |
|     // Decrement refcount of 'this'
 | |
|     mAudioSessionControl->UnregisterAudioSessionNotification(this);
 | |
|   }
 | |
| 
 | |
|   // Win7 is the only Windows version supported before Win8.
 | |
|   if (mAudioSessionControl && !IsWin8OrLater()) {
 | |
|     // bug 1419488: Avoid hanging due to Win7 race condition when destroying
 | |
|     // AudioSessionControl.  We do that by Moving the AudioSessionControl
 | |
|     // to a worker thread (that we never 'join') for destruction.
 | |
|     SpawnASCReleaseThread(std::move(mAudioSessionControl));
 | |
|   } else {
 | |
|     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);
 | |
| 
 | |
|     MutexAutoLock lock(mMutex);
 | |
|     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;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| AudioSession::CommitAudioSessionData()
 | |
| {
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
|   if (!mAudioSessionControl) {
 | |
|     // Stop() was called before we had a chance to do this.
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   HRESULT 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;
 | |
|   }
 | |
| 
 | |
|   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("widget::AudioSession::OnSessionDisconnectedInternal",
 | |
|                       this, &AudioSession::OnSessionDisconnectedInternal);
 | |
|   NS_DispatchToMainThread(runnable);
 | |
|   return S_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| AudioSession::OnSessionDisconnectedInternal()
 | |
| {
 | |
|   // 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);
 | |
| 
 | |
|   {
 | |
|     // We need to release the mutex before we call Start().
 | |
|     MutexAutoLock lock(mMutex);
 | |
| 
 | |
|     if (!mAudioSessionControl)
 | |
|       return NS_OK;
 | |
| 
 | |
|     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
 |