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