Backed out changeset 8517afe50156 (bug 540456) for reftest failures.
[gecko.git] / widget / windows / AudioSession.cpp
blobe5e6ac1988a81d07cfe0edf108667d60e13034fb
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include <windows.h>
8 #include <audiopolicy.h>
9 #include <mmdeviceapi.h>
11 #include "nsIStringBundle.h"
12 #include "nsIUUIDGenerator.h"
13 #include "nsIXULAppInfo.h"
15 //#include "AudioSession.h"
16 #include "nsCOMPtr.h"
17 #include "nsAutoPtr.h"
18 #include "nsServiceManagerUtils.h"
19 #include "nsString.h"
20 #include "nsThreadUtils.h"
21 #include "nsXULAppAPI.h"
22 #include "mozilla/Attributes.h"
24 #include <objbase.h>
26 namespace mozilla {
27 namespace widget {
29 /*
30 * To take advantage of what Vista+ have to offer with respect to audio,
31 * we need to maintain an audio session. This class wraps IAudioSessionControl
32 * and implements IAudioSessionEvents (for callbacks from Windows)
34 class AudioSession MOZ_FINAL : public IAudioSessionEvents {
35 private:
36 AudioSession();
37 ~AudioSession();
38 public:
39 static AudioSession* GetSingleton();
41 // COM IUnknown
42 STDMETHODIMP_(ULONG) AddRef();
43 STDMETHODIMP QueryInterface(REFIID, void**);
44 STDMETHODIMP_(ULONG) Release();
46 // IAudioSessionEvents
47 STDMETHODIMP OnChannelVolumeChanged(DWORD aChannelCount,
48 float aChannelVolumeArray[],
49 DWORD aChangedChannel,
50 LPCGUID aContext);
51 STDMETHODIMP OnDisplayNameChanged(LPCWSTR aDisplayName, LPCGUID aContext);
52 STDMETHODIMP OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext);
53 STDMETHODIMP OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext);
54 STDMETHODIMP OnSessionDisconnected(AudioSessionDisconnectReason aReason);
55 private:
56 nsresult OnSessionDisconnectedInternal();
57 public:
58 STDMETHODIMP OnSimpleVolumeChanged(float aVolume,
59 BOOL aMute,
60 LPCGUID aContext);
61 STDMETHODIMP OnStateChanged(AudioSessionState aState);
63 nsresult Start();
64 nsresult Stop();
65 void StopInternal();
67 nsresult GetSessionData(nsID& aID,
68 nsString& aSessionName,
69 nsString& aIconPath);
71 nsresult SetSessionData(const nsID& aID,
72 const nsString& aSessionName,
73 const nsString& aIconPath);
75 enum SessionState {
76 UNINITIALIZED, // Has not been initialized yet
77 STARTED, // Started
78 CLONED, // SetSessionInfoCalled, Start not called
79 FAILED, // The autdio session failed to start
80 STOPPED, // Stop called
81 AUDIO_SESSION_DISCONNECTED // Audio session disconnected
83 protected:
84 nsRefPtr<IAudioSessionControl> mAudioSessionControl;
85 nsString mDisplayName;
86 nsString mIconPath;
87 nsID mSessionGroupingParameter;
88 SessionState mState;
90 ThreadSafeAutoRefCnt mRefCnt;
91 NS_DECL_OWNINGTHREAD
93 static AudioSession* sService;
96 nsresult
97 StartAudioSession()
99 return AudioSession::GetSingleton()->Start();
102 nsresult
103 StopAudioSession()
105 return AudioSession::GetSingleton()->Stop();
108 nsresult
109 GetAudioSessionData(nsID& aID,
110 nsString& aSessionName,
111 nsString& aIconPath)
113 return AudioSession::GetSingleton()->GetSessionData(aID,
114 aSessionName,
115 aIconPath);
118 nsresult
119 RecvAudioSessionData(const nsID& aID,
120 const nsString& aSessionName,
121 const nsString& aIconPath)
123 return AudioSession::GetSingleton()->SetSessionData(aID,
124 aSessionName,
125 aIconPath);
128 AudioSession* AudioSession::sService = nullptr;
130 AudioSession::AudioSession()
132 mState = UNINITIALIZED;
135 AudioSession::~AudioSession()
140 AudioSession*
141 AudioSession::GetSingleton()
143 if (!(AudioSession::sService)) {
144 nsRefPtr<AudioSession> service = new AudioSession();
145 service.forget(&AudioSession::sService);
148 // We don't refcount AudioSession on the Gecko side, we hold one single ref
149 // as long as the appshell is running.
150 return AudioSession::sService;
153 // It appears Windows will use us on a background thread ...
154 NS_IMPL_ADDREF(AudioSession)
155 NS_IMPL_RELEASE(AudioSession)
157 STDMETHODIMP
158 AudioSession::QueryInterface(REFIID iid, void **ppv)
160 const IID IID_IAudioSessionEvents = __uuidof(IAudioSessionEvents);
161 if ((IID_IUnknown == iid) ||
162 (IID_IAudioSessionEvents == iid)) {
163 *ppv = static_cast<IAudioSessionEvents*>(this);
164 AddRef();
165 return S_OK;
168 return E_NOINTERFACE;
171 // Once we are started Windows will hold a reference to us through our
172 // IAudioSessionEvents interface that will keep us alive until the appshell
173 // calls Stop.
174 nsresult
175 AudioSession::Start()
177 NS_ABORT_IF_FALSE(mState == UNINITIALIZED ||
178 mState == CLONED ||
179 mState == AUDIO_SESSION_DISCONNECTED,
180 "State invariants violated");
182 const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
183 const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
184 const IID IID_IAudioSessionManager = __uuidof(IAudioSessionManager);
186 HRESULT hr;
188 // Don't check for errors in case something already initialized COM
189 // on this thread.
190 CoInitialize(nullptr);
192 if (mState == UNINITIALIZED) {
193 mState = FAILED;
195 // XXXkhuey implement this for content processes
196 if (XRE_GetProcessType() == GeckoProcessType_Content)
197 return NS_ERROR_FAILURE;
199 NS_ABORT_IF_FALSE(XRE_GetProcessType() == GeckoProcessType_Default,
200 "Should only get here in a chrome process!");
202 nsCOMPtr<nsIStringBundleService> bundleService =
203 do_GetService(NS_STRINGBUNDLE_CONTRACTID);
204 NS_ENSURE_TRUE(bundleService, NS_ERROR_FAILURE);
205 nsCOMPtr<nsIStringBundle> bundle;
206 bundleService->CreateBundle("chrome://branding/locale/brand.properties",
207 getter_AddRefs(bundle));
208 NS_ENSURE_TRUE(bundle, NS_ERROR_FAILURE);
210 bundle->GetStringFromName(NS_LITERAL_STRING("brandFullName").get(),
211 getter_Copies(mDisplayName));
213 PRUnichar *buffer;
214 mIconPath.GetMutableData(&buffer, MAX_PATH);
216 // XXXkhuey we should provide a way for a xulrunner app to specify an icon
217 // that's not in the product binary.
218 ::GetModuleFileNameW(nullptr, buffer, MAX_PATH);
220 nsCOMPtr<nsIUUIDGenerator> uuidgen =
221 do_GetService("@mozilla.org/uuid-generator;1");
222 NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE);
223 uuidgen->GenerateUUIDInPlace(&mSessionGroupingParameter);
226 mState = FAILED;
228 NS_ABORT_IF_FALSE(!mDisplayName.IsEmpty() || !mIconPath.IsEmpty(),
229 "Should never happen ...");
231 nsRefPtr<IMMDeviceEnumerator> enumerator;
232 hr = ::CoCreateInstance(CLSID_MMDeviceEnumerator,
233 nullptr,
234 CLSCTX_ALL,
235 IID_IMMDeviceEnumerator,
236 getter_AddRefs(enumerator));
237 if (FAILED(hr))
238 return NS_ERROR_NOT_AVAILABLE;
240 nsRefPtr<IMMDevice> device;
241 hr = enumerator->GetDefaultAudioEndpoint(EDataFlow::eRender,
242 ERole::eMultimedia,
243 getter_AddRefs(device));
244 if (FAILED(hr)) {
245 if (hr == E_NOTFOUND)
246 return NS_ERROR_NOT_AVAILABLE;
247 return NS_ERROR_FAILURE;
250 nsRefPtr<IAudioSessionManager> manager;
251 hr = device->Activate(IID_IAudioSessionManager,
252 CLSCTX_ALL,
253 nullptr,
254 getter_AddRefs(manager));
255 if (FAILED(hr))
256 return NS_ERROR_FAILURE;
258 hr = manager->GetAudioSessionControl(nullptr,
259 FALSE,
260 getter_AddRefs(mAudioSessionControl));
261 if (FAILED(hr))
262 return NS_ERROR_FAILURE;
264 hr = mAudioSessionControl->SetGroupingParam((LPCGUID)&mSessionGroupingParameter,
265 nullptr);
266 if (FAILED(hr)) {
267 StopInternal();
268 return NS_ERROR_FAILURE;
271 hr = mAudioSessionControl->SetDisplayName(mDisplayName.get(), nullptr);
272 if (FAILED(hr)) {
273 StopInternal();
274 return NS_ERROR_FAILURE;
277 hr = mAudioSessionControl->SetIconPath(mIconPath.get(), nullptr);
278 if (FAILED(hr)) {
279 StopInternal();
280 return NS_ERROR_FAILURE;
283 hr = mAudioSessionControl->RegisterAudioSessionNotification(this);
284 if (FAILED(hr)) {
285 StopInternal();
286 return NS_ERROR_FAILURE;
289 mState = STARTED;
291 return NS_OK;
294 void
295 AudioSession::StopInternal()
297 static const nsID blankId = {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0} };
299 if (mAudioSessionControl) {
300 mAudioSessionControl->SetGroupingParam((LPCGUID)&blankId, nullptr);
301 mAudioSessionControl->UnregisterAudioSessionNotification(this);
302 mAudioSessionControl = nullptr;
306 nsresult
307 AudioSession::Stop()
309 NS_ABORT_IF_FALSE(mState == STARTED ||
310 mState == UNINITIALIZED || // XXXremove this
311 mState == FAILED,
312 "State invariants violated");
313 mState = STOPPED;
315 nsRefPtr<AudioSession> kungFuDeathGrip;
316 kungFuDeathGrip.swap(sService);
318 if (XRE_GetProcessType() != GeckoProcessType_Content)
319 StopInternal();
321 // At this point kungFuDeathGrip should be the only reference to AudioSession
323 ::CoUninitialize();
325 return NS_OK;
328 void CopynsID(nsID& lhs, const nsID& rhs)
330 lhs.m0 = rhs.m0;
331 lhs.m1 = rhs.m1;
332 lhs.m2 = rhs.m2;
333 for (int i = 0; i < 8; i++ ) {
334 lhs.m3[i] = rhs.m3[i];
338 nsresult
339 AudioSession::GetSessionData(nsID& aID,
340 nsString& aSessionName,
341 nsString& aIconPath)
343 NS_ABORT_IF_FALSE(mState == FAILED ||
344 mState == STARTED ||
345 mState == CLONED,
346 "State invariants violated");
348 CopynsID(aID, mSessionGroupingParameter);
349 aSessionName = mDisplayName;
350 aIconPath = mIconPath;
352 if (mState == FAILED)
353 return NS_ERROR_FAILURE;
355 return NS_OK;
358 nsresult
359 AudioSession::SetSessionData(const nsID& aID,
360 const nsString& aSessionName,
361 const nsString& aIconPath)
363 NS_ABORT_IF_FALSE(mState == UNINITIALIZED,
364 "State invariants violated");
365 NS_ABORT_IF_FALSE(XRE_GetProcessType() != GeckoProcessType_Default,
366 "Should never get here in a chrome process!");
367 mState = CLONED;
369 CopynsID(mSessionGroupingParameter, aID);
370 mDisplayName = aSessionName;
371 mIconPath = aIconPath;
372 return NS_OK;
375 STDMETHODIMP
376 AudioSession::OnChannelVolumeChanged(DWORD aChannelCount,
377 float aChannelVolumeArray[],
378 DWORD aChangedChannel,
379 LPCGUID aContext)
381 return S_OK; // NOOP
384 STDMETHODIMP
385 AudioSession::OnDisplayNameChanged(LPCWSTR aDisplayName,
386 LPCGUID aContext)
388 return S_OK; // NOOP
391 STDMETHODIMP
392 AudioSession::OnGroupingParamChanged(LPCGUID aGroupingParam,
393 LPCGUID aContext)
395 return S_OK; // NOOP
398 STDMETHODIMP
399 AudioSession::OnIconPathChanged(LPCWSTR aIconPath,
400 LPCGUID aContext)
402 return S_OK; // NOOP
405 STDMETHODIMP
406 AudioSession::OnSessionDisconnected(AudioSessionDisconnectReason aReason)
408 // Run our code asynchronously. Per MSDN we can't do anything interesting
409 // in this callback.
410 nsCOMPtr<nsIRunnable> runnable =
411 NS_NewRunnableMethod(this, &AudioSession::OnSessionDisconnectedInternal);
412 NS_DispatchToMainThread(runnable);
413 return S_OK;
416 nsresult
417 AudioSession::OnSessionDisconnectedInternal()
419 if (!mAudioSessionControl)
420 return NS_OK;
422 mAudioSessionControl->UnregisterAudioSessionNotification(this);
423 mAudioSessionControl = nullptr;
425 mState = AUDIO_SESSION_DISCONNECTED;
426 CoUninitialize();
427 Start(); // If it fails there's not much we can do.
428 return NS_OK;
431 STDMETHODIMP
432 AudioSession::OnSimpleVolumeChanged(float aVolume,
433 BOOL aMute,
434 LPCGUID aContext)
436 return S_OK; // NOOP
439 STDMETHODIMP
440 AudioSession::OnStateChanged(AudioSessionState aState)
442 return S_OK; // NOOP
445 } // namespace widget
446 } // namespace mozilla