widget/windows/AudioSession.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/widget/windows/AudioSession.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,446 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     1.5 + *
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include <windows.h>
    1.11 +#include <audiopolicy.h>
    1.12 +#include <mmdeviceapi.h>
    1.13 +
    1.14 +#include "nsIStringBundle.h"
    1.15 +#include "nsIUUIDGenerator.h"
    1.16 +#include "nsIXULAppInfo.h"
    1.17 +
    1.18 +//#include "AudioSession.h"
    1.19 +#include "nsCOMPtr.h"
    1.20 +#include "nsAutoPtr.h"
    1.21 +#include "nsServiceManagerUtils.h"
    1.22 +#include "nsString.h"
    1.23 +#include "nsThreadUtils.h"
    1.24 +#include "nsXULAppAPI.h"
    1.25 +#include "mozilla/Attributes.h"
    1.26 +
    1.27 +#include <objbase.h>
    1.28 +
    1.29 +namespace mozilla {
    1.30 +namespace widget {
    1.31 +
    1.32 +/* 
    1.33 + * To take advantage of what Vista+ have to offer with respect to audio,
    1.34 + * we need to maintain an audio session.  This class wraps IAudioSessionControl
    1.35 + * and implements IAudioSessionEvents (for callbacks from Windows)
    1.36 + */
    1.37 +class AudioSession MOZ_FINAL : public IAudioSessionEvents {
    1.38 +private:
    1.39 +  AudioSession();
    1.40 +  ~AudioSession();
    1.41 +public:
    1.42 +  static AudioSession* GetSingleton();
    1.43 +
    1.44 +  // COM IUnknown
    1.45 +  STDMETHODIMP_(ULONG) AddRef();
    1.46 +  STDMETHODIMP QueryInterface(REFIID, void**);
    1.47 +  STDMETHODIMP_(ULONG) Release();
    1.48 +
    1.49 +  // IAudioSessionEvents
    1.50 +  STDMETHODIMP OnChannelVolumeChanged(DWORD aChannelCount,
    1.51 +                                      float aChannelVolumeArray[],
    1.52 +                                      DWORD aChangedChannel,
    1.53 +                                      LPCGUID aContext);
    1.54 +  STDMETHODIMP OnDisplayNameChanged(LPCWSTR aDisplayName, LPCGUID aContext);
    1.55 +  STDMETHODIMP OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext);
    1.56 +  STDMETHODIMP OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext);
    1.57 +  STDMETHODIMP OnSessionDisconnected(AudioSessionDisconnectReason aReason);
    1.58 +private:
    1.59 +  nsresult OnSessionDisconnectedInternal();
    1.60 +public:
    1.61 +  STDMETHODIMP OnSimpleVolumeChanged(float aVolume,
    1.62 +                                     BOOL aMute,
    1.63 +                                     LPCGUID aContext);
    1.64 +  STDMETHODIMP OnStateChanged(AudioSessionState aState);
    1.65 +
    1.66 +  nsresult Start();
    1.67 +  nsresult Stop();
    1.68 +  void StopInternal();
    1.69 +
    1.70 +  nsresult GetSessionData(nsID& aID,
    1.71 +                          nsString& aSessionName,
    1.72 +                          nsString& aIconPath);
    1.73 +
    1.74 +  nsresult SetSessionData(const nsID& aID,
    1.75 +                          const nsString& aSessionName,
    1.76 +                          const nsString& aIconPath);
    1.77 +
    1.78 +  enum SessionState {
    1.79 +    UNINITIALIZED, // Has not been initialized yet
    1.80 +    STARTED, // Started
    1.81 +    CLONED, // SetSessionInfoCalled, Start not called
    1.82 +    FAILED, // The autdio session failed to start
    1.83 +    STOPPED, // Stop called
    1.84 +    AUDIO_SESSION_DISCONNECTED // Audio session disconnected
    1.85 +  };
    1.86 +protected:
    1.87 +  nsRefPtr<IAudioSessionControl> mAudioSessionControl;
    1.88 +  nsString mDisplayName;
    1.89 +  nsString mIconPath;
    1.90 +  nsID mSessionGroupingParameter;
    1.91 +  SessionState mState;
    1.92 +
    1.93 +  ThreadSafeAutoRefCnt mRefCnt;
    1.94 +  NS_DECL_OWNINGTHREAD
    1.95 +
    1.96 +  static AudioSession* sService;
    1.97 +};
    1.98 +
    1.99 +nsresult
   1.100 +StartAudioSession()
   1.101 +{
   1.102 +  return AudioSession::GetSingleton()->Start();
   1.103 +}
   1.104 +
   1.105 +nsresult
   1.106 +StopAudioSession()
   1.107 +{
   1.108 +  return AudioSession::GetSingleton()->Stop();
   1.109 +}
   1.110 +
   1.111 +nsresult
   1.112 +GetAudioSessionData(nsID& aID,
   1.113 +                    nsString& aSessionName,
   1.114 +                    nsString& aIconPath)
   1.115 +{
   1.116 +  return AudioSession::GetSingleton()->GetSessionData(aID,
   1.117 +                                                      aSessionName,
   1.118 +                                                      aIconPath);
   1.119 +}
   1.120 +
   1.121 +nsresult
   1.122 +RecvAudioSessionData(const nsID& aID,
   1.123 +                     const nsString& aSessionName,
   1.124 +                     const nsString& aIconPath)
   1.125 +{
   1.126 +  return AudioSession::GetSingleton()->SetSessionData(aID,
   1.127 +                                                      aSessionName,
   1.128 +                                                      aIconPath);
   1.129 +}
   1.130 +
   1.131 +AudioSession* AudioSession::sService = nullptr;
   1.132 +
   1.133 +AudioSession::AudioSession()
   1.134 +{
   1.135 +  mState = UNINITIALIZED;
   1.136 +}
   1.137 +
   1.138 +AudioSession::~AudioSession()
   1.139 +{
   1.140 +
   1.141 +}
   1.142 +
   1.143 +AudioSession*
   1.144 +AudioSession::GetSingleton()
   1.145 +{
   1.146 +  if (!(AudioSession::sService)) {
   1.147 +    nsRefPtr<AudioSession> service = new AudioSession();
   1.148 +    service.forget(&AudioSession::sService);
   1.149 +  }
   1.150 +
   1.151 +  // We don't refcount AudioSession on the Gecko side, we hold one single ref
   1.152 +  // as long as the appshell is running.
   1.153 +  return AudioSession::sService;
   1.154 +}
   1.155 +
   1.156 +// It appears Windows will use us on a background thread ...
   1.157 +NS_IMPL_ADDREF(AudioSession)
   1.158 +NS_IMPL_RELEASE(AudioSession)
   1.159 +
   1.160 +STDMETHODIMP
   1.161 +AudioSession::QueryInterface(REFIID iid, void **ppv)
   1.162 +{
   1.163 +  const IID IID_IAudioSessionEvents = __uuidof(IAudioSessionEvents);
   1.164 +  if ((IID_IUnknown == iid) ||
   1.165 +      (IID_IAudioSessionEvents == iid)) {
   1.166 +    *ppv = static_cast<IAudioSessionEvents*>(this);
   1.167 +    AddRef();
   1.168 +    return S_OK;
   1.169 +  }
   1.170 +
   1.171 +  return E_NOINTERFACE;
   1.172 +}
   1.173 +
   1.174 +// Once we are started Windows will hold a reference to us through our
   1.175 +// IAudioSessionEvents interface that will keep us alive until the appshell
   1.176 +// calls Stop.
   1.177 +nsresult
   1.178 +AudioSession::Start()
   1.179 +{
   1.180 +  NS_ABORT_IF_FALSE(mState == UNINITIALIZED || 
   1.181 +                    mState == CLONED ||
   1.182 +                    mState == AUDIO_SESSION_DISCONNECTED,
   1.183 +                    "State invariants violated");
   1.184 +
   1.185 +  const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
   1.186 +  const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
   1.187 +  const IID IID_IAudioSessionManager = __uuidof(IAudioSessionManager);
   1.188 +
   1.189 +  HRESULT hr;
   1.190 +
   1.191 +  // Don't check for errors in case something already initialized COM
   1.192 +  // on this thread.
   1.193 +  CoInitialize(nullptr);
   1.194 +
   1.195 +  if (mState == UNINITIALIZED) {
   1.196 +    mState = FAILED;
   1.197 +
   1.198 +    // XXXkhuey implement this for content processes
   1.199 +    if (XRE_GetProcessType() == GeckoProcessType_Content)
   1.200 +      return NS_ERROR_FAILURE;
   1.201 +
   1.202 +    NS_ABORT_IF_FALSE(XRE_GetProcessType() == GeckoProcessType_Default,
   1.203 +                      "Should only get here in a chrome process!");
   1.204 +
   1.205 +    nsCOMPtr<nsIStringBundleService> bundleService = 
   1.206 +      do_GetService(NS_STRINGBUNDLE_CONTRACTID);
   1.207 +    NS_ENSURE_TRUE(bundleService, NS_ERROR_FAILURE);
   1.208 +    nsCOMPtr<nsIStringBundle> bundle;
   1.209 +    bundleService->CreateBundle("chrome://branding/locale/brand.properties",
   1.210 +                                getter_AddRefs(bundle));
   1.211 +    NS_ENSURE_TRUE(bundle, NS_ERROR_FAILURE);
   1.212 +
   1.213 +    bundle->GetStringFromName(MOZ_UTF16("brandFullName"),
   1.214 +                              getter_Copies(mDisplayName));
   1.215 +
   1.216 +    wchar_t *buffer;
   1.217 +    mIconPath.GetMutableData(&buffer, MAX_PATH);
   1.218 +
   1.219 +    // XXXkhuey we should provide a way for a xulrunner app to specify an icon
   1.220 +    // that's not in the product binary.
   1.221 +    ::GetModuleFileNameW(nullptr, buffer, MAX_PATH);
   1.222 +
   1.223 +    nsCOMPtr<nsIUUIDGenerator> uuidgen =
   1.224 +      do_GetService("@mozilla.org/uuid-generator;1");
   1.225 +    NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE);
   1.226 +    uuidgen->GenerateUUIDInPlace(&mSessionGroupingParameter);
   1.227 +  }
   1.228 +
   1.229 +  mState = FAILED;
   1.230 +
   1.231 +  NS_ABORT_IF_FALSE(!mDisplayName.IsEmpty() || !mIconPath.IsEmpty(),
   1.232 +                    "Should never happen ...");
   1.233 +
   1.234 +  nsRefPtr<IMMDeviceEnumerator> enumerator;
   1.235 +  hr = ::CoCreateInstance(CLSID_MMDeviceEnumerator,
   1.236 +                          nullptr,
   1.237 +                          CLSCTX_ALL,
   1.238 +                          IID_IMMDeviceEnumerator,
   1.239 +                          getter_AddRefs(enumerator));
   1.240 +  if (FAILED(hr))
   1.241 +    return NS_ERROR_NOT_AVAILABLE;
   1.242 +
   1.243 +  nsRefPtr<IMMDevice> device;
   1.244 +  hr = enumerator->GetDefaultAudioEndpoint(EDataFlow::eRender,
   1.245 +                                           ERole::eMultimedia,
   1.246 +                                           getter_AddRefs(device));
   1.247 +  if (FAILED(hr)) {
   1.248 +    if (hr == E_NOTFOUND)
   1.249 +      return NS_ERROR_NOT_AVAILABLE;
   1.250 +    return NS_ERROR_FAILURE;
   1.251 +  }
   1.252 +
   1.253 +  nsRefPtr<IAudioSessionManager> manager;
   1.254 +  hr = device->Activate(IID_IAudioSessionManager,
   1.255 +                        CLSCTX_ALL,
   1.256 +                        nullptr,
   1.257 +                        getter_AddRefs(manager));
   1.258 +  if (FAILED(hr))
   1.259 +    return NS_ERROR_FAILURE;
   1.260 +
   1.261 +  hr = manager->GetAudioSessionControl(nullptr,
   1.262 +                                       FALSE,
   1.263 +                                       getter_AddRefs(mAudioSessionControl));
   1.264 +  if (FAILED(hr))
   1.265 +    return NS_ERROR_FAILURE;
   1.266 +
   1.267 +  hr = mAudioSessionControl->SetGroupingParam((LPCGUID)&mSessionGroupingParameter,
   1.268 +                                              nullptr);
   1.269 +  if (FAILED(hr)) {
   1.270 +    StopInternal();
   1.271 +    return NS_ERROR_FAILURE;
   1.272 +  }
   1.273 +
   1.274 +  hr = mAudioSessionControl->SetDisplayName(mDisplayName.get(), nullptr);
   1.275 +  if (FAILED(hr)) {
   1.276 +    StopInternal();
   1.277 +    return NS_ERROR_FAILURE;
   1.278 +  }
   1.279 +
   1.280 +  hr = mAudioSessionControl->SetIconPath(mIconPath.get(), nullptr);
   1.281 +  if (FAILED(hr)) {
   1.282 +    StopInternal();
   1.283 +    return NS_ERROR_FAILURE;
   1.284 +  }
   1.285 +
   1.286 +  hr = mAudioSessionControl->RegisterAudioSessionNotification(this);
   1.287 +  if (FAILED(hr)) {
   1.288 +    StopInternal();
   1.289 +    return NS_ERROR_FAILURE;
   1.290 +  }
   1.291 +
   1.292 +  mState = STARTED;
   1.293 +
   1.294 +  return NS_OK;
   1.295 +}
   1.296 +
   1.297 +void
   1.298 +AudioSession::StopInternal()
   1.299 +{
   1.300 +  static const nsID blankId = {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0} };
   1.301 +
   1.302 +  if (mAudioSessionControl) {
   1.303 +    mAudioSessionControl->SetGroupingParam((LPCGUID)&blankId, nullptr);
   1.304 +    mAudioSessionControl->UnregisterAudioSessionNotification(this);
   1.305 +    mAudioSessionControl = nullptr;
   1.306 +  }
   1.307 +}
   1.308 +
   1.309 +nsresult
   1.310 +AudioSession::Stop()
   1.311 +{
   1.312 +  NS_ABORT_IF_FALSE(mState == STARTED ||
   1.313 +                    mState == UNINITIALIZED || // XXXremove this
   1.314 +                    mState == FAILED,
   1.315 +                    "State invariants violated");
   1.316 +  mState = STOPPED;
   1.317 +
   1.318 +  nsRefPtr<AudioSession> kungFuDeathGrip;
   1.319 +  kungFuDeathGrip.swap(sService);
   1.320 +
   1.321 +  if (XRE_GetProcessType() != GeckoProcessType_Content)
   1.322 +    StopInternal();
   1.323 +
   1.324 +  // At this point kungFuDeathGrip should be the only reference to AudioSession
   1.325 +
   1.326 +  ::CoUninitialize();
   1.327 +
   1.328 +  return NS_OK;
   1.329 +}
   1.330 +
   1.331 +void CopynsID(nsID& lhs, const nsID& rhs)
   1.332 +{
   1.333 +  lhs.m0 = rhs.m0;
   1.334 +  lhs.m1 = rhs.m1;
   1.335 +  lhs.m2 = rhs.m2;
   1.336 +  for (int i = 0; i < 8; i++ ) {
   1.337 +    lhs.m3[i] = rhs.m3[i];
   1.338 +  }
   1.339 +}
   1.340 +
   1.341 +nsresult
   1.342 +AudioSession::GetSessionData(nsID& aID,
   1.343 +                             nsString& aSessionName,
   1.344 +                             nsString& aIconPath)
   1.345 +{
   1.346 +  NS_ABORT_IF_FALSE(mState == FAILED ||
   1.347 +                    mState == STARTED ||
   1.348 +                    mState == CLONED,
   1.349 +                    "State invariants violated");
   1.350 +
   1.351 +  CopynsID(aID, mSessionGroupingParameter);
   1.352 +  aSessionName = mDisplayName;
   1.353 +  aIconPath = mIconPath;
   1.354 +
   1.355 +  if (mState == FAILED)
   1.356 +    return NS_ERROR_FAILURE;
   1.357 +
   1.358 +  return NS_OK;
   1.359 +}
   1.360 +
   1.361 +nsresult
   1.362 +AudioSession::SetSessionData(const nsID& aID,
   1.363 +                             const nsString& aSessionName,
   1.364 +                             const nsString& aIconPath)
   1.365 +{
   1.366 +  NS_ABORT_IF_FALSE(mState == UNINITIALIZED,
   1.367 +                    "State invariants violated");
   1.368 +  NS_ABORT_IF_FALSE(XRE_GetProcessType() != GeckoProcessType_Default,
   1.369 +                    "Should never get here in a chrome process!");
   1.370 +  mState = CLONED;
   1.371 +
   1.372 +  CopynsID(mSessionGroupingParameter, aID);
   1.373 +  mDisplayName = aSessionName;
   1.374 +  mIconPath = aIconPath;
   1.375 +  return NS_OK;
   1.376 +}
   1.377 +
   1.378 +STDMETHODIMP
   1.379 +AudioSession::OnChannelVolumeChanged(DWORD aChannelCount,
   1.380 +                                     float aChannelVolumeArray[],
   1.381 +                                     DWORD aChangedChannel,
   1.382 +                                     LPCGUID aContext)
   1.383 +{
   1.384 +  return S_OK; // NOOP
   1.385 +}
   1.386 +
   1.387 +STDMETHODIMP
   1.388 +AudioSession::OnDisplayNameChanged(LPCWSTR aDisplayName,
   1.389 +                                   LPCGUID aContext)
   1.390 +{
   1.391 +  return S_OK; // NOOP
   1.392 +}
   1.393 +
   1.394 +STDMETHODIMP
   1.395 +AudioSession::OnGroupingParamChanged(LPCGUID aGroupingParam,
   1.396 +                                     LPCGUID aContext)
   1.397 +{
   1.398 +  return S_OK; // NOOP
   1.399 +}
   1.400 +
   1.401 +STDMETHODIMP
   1.402 +AudioSession::OnIconPathChanged(LPCWSTR aIconPath,
   1.403 +                                LPCGUID aContext)
   1.404 +{
   1.405 +  return S_OK; // NOOP
   1.406 +}
   1.407 +
   1.408 +STDMETHODIMP
   1.409 +AudioSession::OnSessionDisconnected(AudioSessionDisconnectReason aReason)
   1.410 +{
   1.411 +  // Run our code asynchronously.  Per MSDN we can't do anything interesting
   1.412 +  // in this callback.
   1.413 +  nsCOMPtr<nsIRunnable> runnable =
   1.414 +    NS_NewRunnableMethod(this, &AudioSession::OnSessionDisconnectedInternal);
   1.415 +  NS_DispatchToMainThread(runnable);
   1.416 +  return S_OK;
   1.417 +}
   1.418 +
   1.419 +nsresult
   1.420 +AudioSession::OnSessionDisconnectedInternal()
   1.421 +{
   1.422 +  if (!mAudioSessionControl)
   1.423 +    return NS_OK;
   1.424 +
   1.425 +  mAudioSessionControl->UnregisterAudioSessionNotification(this);
   1.426 +  mAudioSessionControl = nullptr;
   1.427 +
   1.428 +  mState = AUDIO_SESSION_DISCONNECTED;
   1.429 +  CoUninitialize();
   1.430 +  Start(); // If it fails there's not much we can do.
   1.431 +  return NS_OK;
   1.432 +}
   1.433 +
   1.434 +STDMETHODIMP
   1.435 +AudioSession::OnSimpleVolumeChanged(float aVolume,
   1.436 +                                    BOOL aMute,
   1.437 +                                    LPCGUID aContext)
   1.438 +{
   1.439 +  return S_OK; // NOOP
   1.440 +}
   1.441 +
   1.442 +STDMETHODIMP
   1.443 +AudioSession::OnStateChanged(AudioSessionState aState)
   1.444 +{
   1.445 +  return S_OK; // NOOP
   1.446 +}
   1.447 +
   1.448 +} // namespace widget
   1.449 +} // namespace mozilla

mercurial