forked from mirrors/gecko-dev
		
	When we UnregisterAudioSessionNotification in response to an OnSessionDisconnected message, we unintentionally decrement the refcount before restoring it. This KungFu grips us for the duration of that operation. --HG-- extra : rebase_source : 7c7e786064811d040d23741c143d9eef04edb5e0
		
			
				
	
	
		
			453 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			453 lines
		
	
	
	
		
			12 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 <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("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;
 | 
						|
  }
 | 
						|
 | 
						|
  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("widget::AudioSession::OnSessionDisconnectedInternal",
 | 
						|
                      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
 |