|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
|
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/. */ |
|
6 |
|
7 #include <windows.h> |
|
8 #include <audiopolicy.h> |
|
9 #include <mmdeviceapi.h> |
|
10 |
|
11 #include "nsIStringBundle.h" |
|
12 #include "nsIUUIDGenerator.h" |
|
13 #include "nsIXULAppInfo.h" |
|
14 |
|
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" |
|
23 |
|
24 #include <objbase.h> |
|
25 |
|
26 namespace mozilla { |
|
27 namespace widget { |
|
28 |
|
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) |
|
33 */ |
|
34 class AudioSession MOZ_FINAL : public IAudioSessionEvents { |
|
35 private: |
|
36 AudioSession(); |
|
37 ~AudioSession(); |
|
38 public: |
|
39 static AudioSession* GetSingleton(); |
|
40 |
|
41 // COM IUnknown |
|
42 STDMETHODIMP_(ULONG) AddRef(); |
|
43 STDMETHODIMP QueryInterface(REFIID, void**); |
|
44 STDMETHODIMP_(ULONG) Release(); |
|
45 |
|
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); |
|
62 |
|
63 nsresult Start(); |
|
64 nsresult Stop(); |
|
65 void StopInternal(); |
|
66 |
|
67 nsresult GetSessionData(nsID& aID, |
|
68 nsString& aSessionName, |
|
69 nsString& aIconPath); |
|
70 |
|
71 nsresult SetSessionData(const nsID& aID, |
|
72 const nsString& aSessionName, |
|
73 const nsString& aIconPath); |
|
74 |
|
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 |
|
82 }; |
|
83 protected: |
|
84 nsRefPtr<IAudioSessionControl> mAudioSessionControl; |
|
85 nsString mDisplayName; |
|
86 nsString mIconPath; |
|
87 nsID mSessionGroupingParameter; |
|
88 SessionState mState; |
|
89 |
|
90 ThreadSafeAutoRefCnt mRefCnt; |
|
91 NS_DECL_OWNINGTHREAD |
|
92 |
|
93 static AudioSession* sService; |
|
94 }; |
|
95 |
|
96 nsresult |
|
97 StartAudioSession() |
|
98 { |
|
99 return AudioSession::GetSingleton()->Start(); |
|
100 } |
|
101 |
|
102 nsresult |
|
103 StopAudioSession() |
|
104 { |
|
105 return AudioSession::GetSingleton()->Stop(); |
|
106 } |
|
107 |
|
108 nsresult |
|
109 GetAudioSessionData(nsID& aID, |
|
110 nsString& aSessionName, |
|
111 nsString& aIconPath) |
|
112 { |
|
113 return AudioSession::GetSingleton()->GetSessionData(aID, |
|
114 aSessionName, |
|
115 aIconPath); |
|
116 } |
|
117 |
|
118 nsresult |
|
119 RecvAudioSessionData(const nsID& aID, |
|
120 const nsString& aSessionName, |
|
121 const nsString& aIconPath) |
|
122 { |
|
123 return AudioSession::GetSingleton()->SetSessionData(aID, |
|
124 aSessionName, |
|
125 aIconPath); |
|
126 } |
|
127 |
|
128 AudioSession* AudioSession::sService = nullptr; |
|
129 |
|
130 AudioSession::AudioSession() |
|
131 { |
|
132 mState = UNINITIALIZED; |
|
133 } |
|
134 |
|
135 AudioSession::~AudioSession() |
|
136 { |
|
137 |
|
138 } |
|
139 |
|
140 AudioSession* |
|
141 AudioSession::GetSingleton() |
|
142 { |
|
143 if (!(AudioSession::sService)) { |
|
144 nsRefPtr<AudioSession> service = new AudioSession(); |
|
145 service.forget(&AudioSession::sService); |
|
146 } |
|
147 |
|
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; |
|
151 } |
|
152 |
|
153 // It appears Windows will use us on a background thread ... |
|
154 NS_IMPL_ADDREF(AudioSession) |
|
155 NS_IMPL_RELEASE(AudioSession) |
|
156 |
|
157 STDMETHODIMP |
|
158 AudioSession::QueryInterface(REFIID iid, void **ppv) |
|
159 { |
|
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; |
|
166 } |
|
167 |
|
168 return E_NOINTERFACE; |
|
169 } |
|
170 |
|
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() |
|
176 { |
|
177 NS_ABORT_IF_FALSE(mState == UNINITIALIZED || |
|
178 mState == CLONED || |
|
179 mState == AUDIO_SESSION_DISCONNECTED, |
|
180 "State invariants violated"); |
|
181 |
|
182 const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); |
|
183 const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); |
|
184 const IID IID_IAudioSessionManager = __uuidof(IAudioSessionManager); |
|
185 |
|
186 HRESULT hr; |
|
187 |
|
188 // Don't check for errors in case something already initialized COM |
|
189 // on this thread. |
|
190 CoInitialize(nullptr); |
|
191 |
|
192 if (mState == UNINITIALIZED) { |
|
193 mState = FAILED; |
|
194 |
|
195 // XXXkhuey implement this for content processes |
|
196 if (XRE_GetProcessType() == GeckoProcessType_Content) |
|
197 return NS_ERROR_FAILURE; |
|
198 |
|
199 NS_ABORT_IF_FALSE(XRE_GetProcessType() == GeckoProcessType_Default, |
|
200 "Should only get here in a chrome process!"); |
|
201 |
|
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); |
|
209 |
|
210 bundle->GetStringFromName(MOZ_UTF16("brandFullName"), |
|
211 getter_Copies(mDisplayName)); |
|
212 |
|
213 wchar_t *buffer; |
|
214 mIconPath.GetMutableData(&buffer, MAX_PATH); |
|
215 |
|
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); |
|
219 |
|
220 nsCOMPtr<nsIUUIDGenerator> uuidgen = |
|
221 do_GetService("@mozilla.org/uuid-generator;1"); |
|
222 NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE); |
|
223 uuidgen->GenerateUUIDInPlace(&mSessionGroupingParameter); |
|
224 } |
|
225 |
|
226 mState = FAILED; |
|
227 |
|
228 NS_ABORT_IF_FALSE(!mDisplayName.IsEmpty() || !mIconPath.IsEmpty(), |
|
229 "Should never happen ..."); |
|
230 |
|
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; |
|
239 |
|
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; |
|
248 } |
|
249 |
|
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; |
|
257 |
|
258 hr = manager->GetAudioSessionControl(nullptr, |
|
259 FALSE, |
|
260 getter_AddRefs(mAudioSessionControl)); |
|
261 if (FAILED(hr)) |
|
262 return NS_ERROR_FAILURE; |
|
263 |
|
264 hr = mAudioSessionControl->SetGroupingParam((LPCGUID)&mSessionGroupingParameter, |
|
265 nullptr); |
|
266 if (FAILED(hr)) { |
|
267 StopInternal(); |
|
268 return NS_ERROR_FAILURE; |
|
269 } |
|
270 |
|
271 hr = mAudioSessionControl->SetDisplayName(mDisplayName.get(), nullptr); |
|
272 if (FAILED(hr)) { |
|
273 StopInternal(); |
|
274 return NS_ERROR_FAILURE; |
|
275 } |
|
276 |
|
277 hr = mAudioSessionControl->SetIconPath(mIconPath.get(), nullptr); |
|
278 if (FAILED(hr)) { |
|
279 StopInternal(); |
|
280 return NS_ERROR_FAILURE; |
|
281 } |
|
282 |
|
283 hr = mAudioSessionControl->RegisterAudioSessionNotification(this); |
|
284 if (FAILED(hr)) { |
|
285 StopInternal(); |
|
286 return NS_ERROR_FAILURE; |
|
287 } |
|
288 |
|
289 mState = STARTED; |
|
290 |
|
291 return NS_OK; |
|
292 } |
|
293 |
|
294 void |
|
295 AudioSession::StopInternal() |
|
296 { |
|
297 static const nsID blankId = {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0} }; |
|
298 |
|
299 if (mAudioSessionControl) { |
|
300 mAudioSessionControl->SetGroupingParam((LPCGUID)&blankId, nullptr); |
|
301 mAudioSessionControl->UnregisterAudioSessionNotification(this); |
|
302 mAudioSessionControl = nullptr; |
|
303 } |
|
304 } |
|
305 |
|
306 nsresult |
|
307 AudioSession::Stop() |
|
308 { |
|
309 NS_ABORT_IF_FALSE(mState == STARTED || |
|
310 mState == UNINITIALIZED || // XXXremove this |
|
311 mState == FAILED, |
|
312 "State invariants violated"); |
|
313 mState = STOPPED; |
|
314 |
|
315 nsRefPtr<AudioSession> kungFuDeathGrip; |
|
316 kungFuDeathGrip.swap(sService); |
|
317 |
|
318 if (XRE_GetProcessType() != GeckoProcessType_Content) |
|
319 StopInternal(); |
|
320 |
|
321 // At this point kungFuDeathGrip should be the only reference to AudioSession |
|
322 |
|
323 ::CoUninitialize(); |
|
324 |
|
325 return NS_OK; |
|
326 } |
|
327 |
|
328 void CopynsID(nsID& lhs, const nsID& rhs) |
|
329 { |
|
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]; |
|
335 } |
|
336 } |
|
337 |
|
338 nsresult |
|
339 AudioSession::GetSessionData(nsID& aID, |
|
340 nsString& aSessionName, |
|
341 nsString& aIconPath) |
|
342 { |
|
343 NS_ABORT_IF_FALSE(mState == FAILED || |
|
344 mState == STARTED || |
|
345 mState == CLONED, |
|
346 "State invariants violated"); |
|
347 |
|
348 CopynsID(aID, mSessionGroupingParameter); |
|
349 aSessionName = mDisplayName; |
|
350 aIconPath = mIconPath; |
|
351 |
|
352 if (mState == FAILED) |
|
353 return NS_ERROR_FAILURE; |
|
354 |
|
355 return NS_OK; |
|
356 } |
|
357 |
|
358 nsresult |
|
359 AudioSession::SetSessionData(const nsID& aID, |
|
360 const nsString& aSessionName, |
|
361 const nsString& aIconPath) |
|
362 { |
|
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; |
|
368 |
|
369 CopynsID(mSessionGroupingParameter, aID); |
|
370 mDisplayName = aSessionName; |
|
371 mIconPath = aIconPath; |
|
372 return NS_OK; |
|
373 } |
|
374 |
|
375 STDMETHODIMP |
|
376 AudioSession::OnChannelVolumeChanged(DWORD aChannelCount, |
|
377 float aChannelVolumeArray[], |
|
378 DWORD aChangedChannel, |
|
379 LPCGUID aContext) |
|
380 { |
|
381 return S_OK; // NOOP |
|
382 } |
|
383 |
|
384 STDMETHODIMP |
|
385 AudioSession::OnDisplayNameChanged(LPCWSTR aDisplayName, |
|
386 LPCGUID aContext) |
|
387 { |
|
388 return S_OK; // NOOP |
|
389 } |
|
390 |
|
391 STDMETHODIMP |
|
392 AudioSession::OnGroupingParamChanged(LPCGUID aGroupingParam, |
|
393 LPCGUID aContext) |
|
394 { |
|
395 return S_OK; // NOOP |
|
396 } |
|
397 |
|
398 STDMETHODIMP |
|
399 AudioSession::OnIconPathChanged(LPCWSTR aIconPath, |
|
400 LPCGUID aContext) |
|
401 { |
|
402 return S_OK; // NOOP |
|
403 } |
|
404 |
|
405 STDMETHODIMP |
|
406 AudioSession::OnSessionDisconnected(AudioSessionDisconnectReason aReason) |
|
407 { |
|
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; |
|
414 } |
|
415 |
|
416 nsresult |
|
417 AudioSession::OnSessionDisconnectedInternal() |
|
418 { |
|
419 if (!mAudioSessionControl) |
|
420 return NS_OK; |
|
421 |
|
422 mAudioSessionControl->UnregisterAudioSessionNotification(this); |
|
423 mAudioSessionControl = nullptr; |
|
424 |
|
425 mState = AUDIO_SESSION_DISCONNECTED; |
|
426 CoUninitialize(); |
|
427 Start(); // If it fails there's not much we can do. |
|
428 return NS_OK; |
|
429 } |
|
430 |
|
431 STDMETHODIMP |
|
432 AudioSession::OnSimpleVolumeChanged(float aVolume, |
|
433 BOOL aMute, |
|
434 LPCGUID aContext) |
|
435 { |
|
436 return S_OK; // NOOP |
|
437 } |
|
438 |
|
439 STDMETHODIMP |
|
440 AudioSession::OnStateChanged(AudioSessionState aState) |
|
441 { |
|
442 return S_OK; // NOOP |
|
443 } |
|
444 |
|
445 } // namespace widget |
|
446 } // namespace mozilla |