Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / windows / AudioSession.cpp
blobc14278f56c63090d064cb5f7a715587853693f07
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 <atomic>
8 #include <audiopolicy.h>
9 #include <windows.h>
10 #include <mmdeviceapi.h>
12 #include "mozilla/ClearOnShutdown.h"
13 #include "mozilla/RefPtr.h"
14 #include "mozilla/ScopeExit.h"
15 #include "mozilla/StaticPtr.h"
16 #include "nsIStringBundle.h"
18 #include "nsCOMPtr.h"
19 #include "nsID.h"
20 #include "nsServiceManagerUtils.h"
21 #include "nsString.h"
22 #include "nsThreadUtils.h"
23 #include "nsXULAppAPI.h"
24 #include "mozilla/Attributes.h"
25 #include "mozilla/mscom/AgileReference.h"
26 #include "mozilla/mscom/Utils.h"
27 #include "mozilla/Mutex.h"
28 #include "mozilla/WindowsVersion.h"
30 namespace mozilla {
31 namespace widget {
34 * To take advantage of what Vista+ have to offer with respect to audio,
35 * we need to maintain an audio session. This class wraps IAudioSessionControl
36 * and implements IAudioSessionEvents (for callbacks from Windows)
38 class AudioSession final : public IAudioSessionEvents {
39 public:
40 AudioSession();
42 static AudioSession* GetSingleton();
44 // COM IUnknown
45 STDMETHODIMP_(ULONG) AddRef();
46 STDMETHODIMP QueryInterface(REFIID, void**);
47 STDMETHODIMP_(ULONG) Release();
49 // IAudioSessionEvents
50 STDMETHODIMP OnChannelVolumeChanged(DWORD aChannelCount,
51 float aChannelVolumeArray[],
52 DWORD aChangedChannel, LPCGUID aContext);
53 STDMETHODIMP OnDisplayNameChanged(LPCWSTR aDisplayName, LPCGUID aContext);
54 STDMETHODIMP OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext);
55 STDMETHODIMP OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext);
56 STDMETHODIMP OnSessionDisconnected(AudioSessionDisconnectReason aReason);
57 STDMETHODIMP OnSimpleVolumeChanged(float aVolume, BOOL aMute,
58 LPCGUID aContext);
59 STDMETHODIMP OnStateChanged(AudioSessionState aState);
61 void Start();
62 void Stop(bool shouldRestart = false);
64 nsresult GetSessionData(nsID& aID, nsString& aSessionName,
65 nsString& aIconPath);
66 nsresult SetSessionData(const nsID& aID, const nsString& aSessionName,
67 const nsString& aIconPath);
69 private:
70 ~AudioSession() = default;
72 void StopInternal(const MutexAutoLock& aProofOfLock,
73 bool shouldRestart = false);
75 protected:
76 RefPtr<IAudioSessionControl> mAudioSessionControl;
77 nsString mDisplayName;
78 nsString mIconPath;
79 nsID mSessionGroupingParameter;
80 // Guards the IAudioSessionControl
81 mozilla::Mutex mMutex MOZ_UNANNOTATED;
83 ThreadSafeAutoRefCnt mRefCnt;
84 NS_DECL_OWNINGTHREAD
87 StaticRefPtr<AudioSession> sService;
89 void StartAudioSession() {
90 MOZ_ASSERT(NS_IsMainThread());
91 MOZ_ASSERT(!sService);
92 sService = new AudioSession();
94 // Destroy AudioSession only after any background task threads have been
95 // stopped or abandoned.
96 ClearOnShutdown(&sService, ShutdownPhase::XPCOMShutdownFinal);
98 NS_DispatchBackgroundTask(
99 NS_NewCancelableRunnableFunction("StartAudioSession", []() -> void {
100 MOZ_ASSERT(AudioSession::GetSingleton(),
101 "AudioSession should outlive background threads");
102 AudioSession::GetSingleton()->Start();
103 }));
106 void StopAudioSession() {
107 MOZ_ASSERT(NS_IsMainThread());
108 MOZ_ASSERT(sService);
109 NS_DispatchBackgroundTask(
110 NS_NewRunnableFunction("StopAudioSession", []() -> void {
111 MOZ_ASSERT(AudioSession::GetSingleton(),
112 "AudioSession should outlive background threads");
113 AudioSession::GetSingleton()->Stop();
114 }));
117 AudioSession* AudioSession::GetSingleton() {
118 MOZ_ASSERT(mscom::IsCurrentThreadMTA());
119 return sService;
122 // It appears Windows will use us on a background thread ...
123 NS_IMPL_ADDREF(AudioSession)
124 NS_IMPL_RELEASE(AudioSession)
126 STDMETHODIMP
127 AudioSession::QueryInterface(REFIID iid, void** ppv) {
128 const IID IID_IAudioSessionEvents = __uuidof(IAudioSessionEvents);
129 if ((IID_IUnknown == iid) || (IID_IAudioSessionEvents == iid)) {
130 *ppv = static_cast<IAudioSessionEvents*>(this);
131 AddRef();
132 return S_OK;
135 return E_NOINTERFACE;
138 AudioSession::AudioSession() : mMutex("AudioSessionControl") {
139 // This func must be run on the main thread as
140 // nsStringBundle is not thread safe otherwise
141 MOZ_ASSERT(NS_IsMainThread());
143 MOZ_ASSERT(XRE_IsParentProcess(),
144 "Should only get here in a chrome process!");
146 nsCOMPtr<nsIStringBundleService> bundleService =
147 do_GetService(NS_STRINGBUNDLE_CONTRACTID);
148 MOZ_ASSERT(bundleService);
150 nsCOMPtr<nsIStringBundle> bundle;
151 bundleService->CreateBundle("chrome://branding/locale/brand.properties",
152 getter_AddRefs(bundle));
153 MOZ_ASSERT(bundle);
154 bundle->GetStringFromName("brandFullName", mDisplayName);
156 wchar_t* buffer;
157 mIconPath.GetMutableData(&buffer, MAX_PATH);
158 ::GetModuleFileNameW(nullptr, buffer, MAX_PATH);
160 [[maybe_unused]] nsresult rv =
161 nsID::GenerateUUIDInPlace(mSessionGroupingParameter);
162 MOZ_ASSERT(rv == NS_OK);
165 // Once we are started Windows will hold a reference to us through our
166 // IAudioSessionEvents interface that will keep us alive until the appshell
167 // calls Stop.
168 void AudioSession::Start() {
169 MOZ_ASSERT(mscom::IsCurrentThreadMTA());
171 const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
172 const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
173 const IID IID_IAudioSessionManager = __uuidof(IAudioSessionManager);
175 MutexAutoLock lock(mMutex);
176 MOZ_ASSERT(!mAudioSessionControl);
177 MOZ_ASSERT(!mDisplayName.IsEmpty() || !mIconPath.IsEmpty(),
178 "Should never happen ...");
180 auto scopeExit = MakeScopeExit([&] { StopInternal(lock); });
182 RefPtr<IMMDeviceEnumerator> enumerator;
183 HRESULT hr =
184 ::CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL,
185 IID_IMMDeviceEnumerator, getter_AddRefs(enumerator));
186 if (FAILED(hr)) {
187 return;
190 RefPtr<IMMDevice> device;
191 hr = enumerator->GetDefaultAudioEndpoint(
192 EDataFlow::eRender, ERole::eMultimedia, getter_AddRefs(device));
193 if (FAILED(hr)) {
194 return;
197 RefPtr<IAudioSessionManager> manager;
198 hr = device->Activate(IID_IAudioSessionManager, CLSCTX_ALL, nullptr,
199 getter_AddRefs(manager));
200 if (FAILED(hr)) {
201 return;
204 hr = manager->GetAudioSessionControl(&GUID_NULL, 0,
205 getter_AddRefs(mAudioSessionControl));
207 if (FAILED(hr) || !mAudioSessionControl) {
208 return;
211 // Increments refcount of 'this'.
212 hr = mAudioSessionControl->RegisterAudioSessionNotification(this);
213 if (FAILED(hr)) {
214 return;
217 hr = mAudioSessionControl->SetGroupingParam(
218 (LPGUID) & (mSessionGroupingParameter), nullptr);
219 if (FAILED(hr)) {
220 return;
223 hr = mAudioSessionControl->SetDisplayName(mDisplayName.get(), nullptr);
224 if (FAILED(hr)) {
225 return;
228 hr = mAudioSessionControl->SetIconPath(mIconPath.get(), nullptr);
229 if (FAILED(hr)) {
230 return;
233 scopeExit.release();
236 void AudioSession::Stop(bool shouldRestart) {
237 MOZ_ASSERT(mscom::IsCurrentThreadMTA());
239 MutexAutoLock lock(mMutex);
240 StopInternal(lock, shouldRestart);
243 void AudioSession::StopInternal(const MutexAutoLock& aProofOfLock,
244 bool shouldRestart) {
245 if (!mAudioSessionControl) {
246 return;
249 // Decrement refcount of 'this'
250 mAudioSessionControl->UnregisterAudioSessionNotification(this);
252 // Deleting the IAudioSessionControl COM object requires the STA/main thread.
253 // Audio code may concurrently be running on the main thread and it may
254 // block waiting for this to complete, creating deadlock. So we destroy the
255 // IAudioSessionControl on the main thread instead. In order to do that, we
256 // need to marshall the object to the main thread's apartment with an
257 // AgileReference.
258 mscom::AgileReference agileAsc(mAudioSessionControl);
259 mAudioSessionControl = nullptr;
260 NS_DispatchToMainThread(NS_NewRunnableFunction(
261 "FreeAudioSession",
262 [agileAsc = std::move(agileAsc), shouldRestart]() mutable {
263 // Now release the AgileReference which holds our only reference to the
264 // IAudioSessionControl, then maybe restart.
265 agileAsc = nullptr;
266 if (shouldRestart) {
267 NS_DispatchBackgroundTask(
268 NS_NewCancelableRunnableFunction("RestartAudioSession", [] {
269 AudioSession* as = AudioSession::GetSingleton();
270 MOZ_ASSERT(as);
271 as->Start();
272 }));
274 }));
277 void CopynsID(nsID& lhs, const nsID& rhs) {
278 lhs.m0 = rhs.m0;
279 lhs.m1 = rhs.m1;
280 lhs.m2 = rhs.m2;
281 for (int i = 0; i < 8; i++) {
282 lhs.m3[i] = rhs.m3[i];
286 nsresult AudioSession::GetSessionData(nsID& aID, nsString& aSessionName,
287 nsString& aIconPath) {
288 CopynsID(aID, mSessionGroupingParameter);
289 aSessionName = mDisplayName;
290 aIconPath = mIconPath;
292 return NS_OK;
295 nsresult AudioSession::SetSessionData(const nsID& aID,
296 const nsString& aSessionName,
297 const nsString& aIconPath) {
298 MOZ_ASSERT(!XRE_IsParentProcess(),
299 "Should never get here in a chrome process!");
300 CopynsID(mSessionGroupingParameter, aID);
301 mDisplayName = aSessionName;
302 mIconPath = aIconPath;
303 return NS_OK;
306 STDMETHODIMP
307 AudioSession::OnChannelVolumeChanged(DWORD aChannelCount,
308 float aChannelVolumeArray[],
309 DWORD aChangedChannel, LPCGUID aContext) {
310 return S_OK; // NOOP
313 STDMETHODIMP
314 AudioSession::OnDisplayNameChanged(LPCWSTR aDisplayName, LPCGUID aContext) {
315 return S_OK; // NOOP
318 STDMETHODIMP
319 AudioSession::OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext) {
320 return S_OK; // NOOP
323 STDMETHODIMP
324 AudioSession::OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext) {
325 return S_OK; // NOOP
328 STDMETHODIMP
329 AudioSession::OnSessionDisconnected(AudioSessionDisconnectReason aReason) {
330 Stop(true /* shouldRestart */);
331 return S_OK;
334 STDMETHODIMP
335 AudioSession::OnSimpleVolumeChanged(float aVolume, BOOL aMute,
336 LPCGUID aContext) {
337 return S_OK; // NOOP
340 STDMETHODIMP
341 AudioSession::OnStateChanged(AudioSessionState aState) {
342 return S_OK; // NOOP
345 } // namespace widget
346 } // namespace mozilla