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/. */
8 #include <audiopolicy.h>
9 #include <mmdeviceapi.h>
11 #include "nsIStringBundle.h"
12 #include "nsIUUIDGenerator.h"
13 #include "nsIXULAppInfo.h"
15 //#include "AudioSession.h"
17 #include "nsAutoPtr.h"
18 #include "nsServiceManagerUtils.h"
20 #include "nsThreadUtils.h"
21 #include "nsXULAppAPI.h"
22 #include "mozilla/Attributes.h"
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
{
39 static AudioSession
* GetSingleton();
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
,
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
);
56 nsresult
OnSessionDisconnectedInternal();
58 STDMETHODIMP
OnSimpleVolumeChanged(float aVolume
,
61 STDMETHODIMP
OnStateChanged(AudioSessionState aState
);
67 nsresult
GetSessionData(nsID
& aID
,
68 nsString
& aSessionName
,
71 nsresult
SetSessionData(const nsID
& aID
,
72 const nsString
& aSessionName
,
73 const nsString
& aIconPath
);
76 UNINITIALIZED
, // Has not been initialized yet
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
84 nsRefPtr
<IAudioSessionControl
> mAudioSessionControl
;
85 nsString mDisplayName
;
87 nsID mSessionGroupingParameter
;
90 ThreadSafeAutoRefCnt mRefCnt
;
93 static AudioSession
* sService
;
99 return AudioSession::GetSingleton()->Start();
105 return AudioSession::GetSingleton()->Stop();
109 GetAudioSessionData(nsID
& aID
,
110 nsString
& aSessionName
,
113 return AudioSession::GetSingleton()->GetSessionData(aID
,
119 RecvAudioSessionData(const nsID
& aID
,
120 const nsString
& aSessionName
,
121 const nsString
& aIconPath
)
123 return AudioSession::GetSingleton()->SetSessionData(aID
,
128 AudioSession
* AudioSession::sService
= nullptr;
130 AudioSession::AudioSession()
132 mState
= UNINITIALIZED
;
135 AudioSession::~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
)
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);
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
175 AudioSession::Start()
177 NS_ABORT_IF_FALSE(mState
== UNINITIALIZED
||
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
);
188 // Don't check for errors in case something already initialized COM
190 CoInitialize(nullptr);
192 if (mState
== UNINITIALIZED
) {
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
));
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
);
228 NS_ABORT_IF_FALSE(!mDisplayName
.IsEmpty() || !mIconPath
.IsEmpty(),
229 "Should never happen ...");
231 nsRefPtr
<IMMDeviceEnumerator
> enumerator
;
232 hr
= ::CoCreateInstance(CLSID_MMDeviceEnumerator
,
235 IID_IMMDeviceEnumerator
,
236 getter_AddRefs(enumerator
));
238 return NS_ERROR_NOT_AVAILABLE
;
240 nsRefPtr
<IMMDevice
> device
;
241 hr
= enumerator
->GetDefaultAudioEndpoint(EDataFlow::eRender
,
243 getter_AddRefs(device
));
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
,
254 getter_AddRefs(manager
));
256 return NS_ERROR_FAILURE
;
258 hr
= manager
->GetAudioSessionControl(nullptr,
260 getter_AddRefs(mAudioSessionControl
));
262 return NS_ERROR_FAILURE
;
264 hr
= mAudioSessionControl
->SetGroupingParam((LPCGUID
)&mSessionGroupingParameter
,
268 return NS_ERROR_FAILURE
;
271 hr
= mAudioSessionControl
->SetDisplayName(mDisplayName
.get(), nullptr);
274 return NS_ERROR_FAILURE
;
277 hr
= mAudioSessionControl
->SetIconPath(mIconPath
.get(), nullptr);
280 return NS_ERROR_FAILURE
;
283 hr
= mAudioSessionControl
->RegisterAudioSessionNotification(this);
286 return NS_ERROR_FAILURE
;
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;
309 NS_ABORT_IF_FALSE(mState
== STARTED
||
310 mState
== UNINITIALIZED
|| // XXXremove this
312 "State invariants violated");
315 nsRefPtr
<AudioSession
> kungFuDeathGrip
;
316 kungFuDeathGrip
.swap(sService
);
318 if (XRE_GetProcessType() != GeckoProcessType_Content
)
321 // At this point kungFuDeathGrip should be the only reference to AudioSession
328 void CopynsID(nsID
& lhs
, const nsID
& rhs
)
333 for (int i
= 0; i
< 8; i
++ ) {
334 lhs
.m3
[i
] = rhs
.m3
[i
];
339 AudioSession::GetSessionData(nsID
& aID
,
340 nsString
& aSessionName
,
343 NS_ABORT_IF_FALSE(mState
== FAILED
||
346 "State invariants violated");
348 CopynsID(aID
, mSessionGroupingParameter
);
349 aSessionName
= mDisplayName
;
350 aIconPath
= mIconPath
;
352 if (mState
== FAILED
)
353 return NS_ERROR_FAILURE
;
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!");
369 CopynsID(mSessionGroupingParameter
, aID
);
370 mDisplayName
= aSessionName
;
371 mIconPath
= aIconPath
;
376 AudioSession::OnChannelVolumeChanged(DWORD aChannelCount
,
377 float aChannelVolumeArray
[],
378 DWORD aChangedChannel
,
385 AudioSession::OnDisplayNameChanged(LPCWSTR aDisplayName
,
392 AudioSession::OnGroupingParamChanged(LPCGUID aGroupingParam
,
399 AudioSession::OnIconPathChanged(LPCWSTR aIconPath
,
406 AudioSession::OnSessionDisconnected(AudioSessionDisconnectReason aReason
)
408 // Run our code asynchronously. Per MSDN we can't do anything interesting
410 nsCOMPtr
<nsIRunnable
> runnable
=
411 NS_NewRunnableMethod(this, &AudioSession::OnSessionDisconnectedInternal
);
412 NS_DispatchToMainThread(runnable
);
417 AudioSession::OnSessionDisconnectedInternal()
419 if (!mAudioSessionControl
)
422 mAudioSessionControl
->UnregisterAudioSessionNotification(this);
423 mAudioSessionControl
= nullptr;
425 mState
= AUDIO_SESSION_DISCONNECTED
;
427 Start(); // If it fails there's not much we can do.
432 AudioSession::OnSimpleVolumeChanged(float aVolume
,
440 AudioSession::OnStateChanged(AudioSessionState aState
)
445 } // namespace widget
446 } // namespace mozilla