gecko-dev/widget/windows/AudioSession.cpp
Gabriele Svelto 5dc21d568c Bug 1600545 - Remove useless inclusions of header files generated from IDL files in modules/, netwerk/, parser/, security/, startupcache/, storage/, toolkit/, tools/, uriloader/, widget/, xpcom/ and xpfe/ r=Ehsan
The inclusions were removed with the following very crude script and the
resulting breakage was fixed up by hand. The manual fixups did either
revert the changes done by the script, replace a generic header with a more
specific one or replace a header with a forward declaration.

find . -name "*.idl" | grep -v web-platform | grep -v third_party | while read path; do
    interfaces=$(grep "^\(class\|interface\).*:.*" "$path" | cut -d' ' -f2)
    if [ -n "$interfaces" ]; then
        if [[ "$interfaces" == *$'\n'* ]]; then
          regexp="\("
          for i in $interfaces; do regexp="$regexp$i\|"; done
          regexp="${regexp%%\\\|}\)"
        else
          regexp="$interfaces"
        fi
        interface=$(basename "$path")
        rg -l "#include.*${interface%%.idl}.h" . | while read path2; do
            hits=$(grep -v "#include.*${interface%%.idl}.h" "$path2" | grep -c "$regexp" )
            if [ $hits -eq 0 ]; then
                echo "Removing ${interface} from ${path2}"
                grep -v "#include.*${interface%%.idl}.h" "$path2" > "$path2".tmp
                mv -f "$path2".tmp "$path2"
            fi
        done
    fi
done

Differential Revision: https://phabricator.services.mozilla.com/D55444

--HG--
extra : moz-landing-system : lando
2019-12-06 09:17:57 +00:00

447 lines
13 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <windows.h>
#include <audiopolicy.h>
#include <mmdeviceapi.h>
#include "mozilla/RefPtr.h"
#include "nsIStringBundle.h"
#include "nsIUUIDGenerator.h"
//#include "AudioSession.h"
#include "nsCOMPtr.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "mozilla/Attributes.h"
#include "mozilla/Mutex.h"
#include "mozilla/WindowsVersion.h"
#include <objbase.h>
namespace mozilla {
namespace widget {
/*
* To take advantage of what Vista+ have to offer with respect to audio,
* we need to maintain an audio session. This class wraps IAudioSessionControl
* and implements IAudioSessionEvents (for callbacks from Windows)
*/
class AudioSession final : public IAudioSessionEvents {
private:
AudioSession();
~AudioSession();
public:
static AudioSession* GetSingleton();
// COM IUnknown
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP QueryInterface(REFIID, void**);
STDMETHODIMP_(ULONG) Release();
// IAudioSessionEvents
STDMETHODIMP OnChannelVolumeChanged(DWORD aChannelCount,
float aChannelVolumeArray[],
DWORD aChangedChannel, LPCGUID aContext);
STDMETHODIMP OnDisplayNameChanged(LPCWSTR aDisplayName, LPCGUID aContext);
STDMETHODIMP OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext);
STDMETHODIMP OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext);
STDMETHODIMP OnSessionDisconnected(AudioSessionDisconnectReason aReason);
private:
nsresult OnSessionDisconnectedInternal();
nsresult CommitAudioSessionData();
public:
STDMETHODIMP OnSimpleVolumeChanged(float aVolume, BOOL aMute,
LPCGUID aContext);
STDMETHODIMP OnStateChanged(AudioSessionState aState);
nsresult Start();
nsresult Stop();
void StopInternal();
nsresult GetSessionData(nsID& aID, nsString& aSessionName,
nsString& aIconPath);
nsresult SetSessionData(const nsID& aID, const nsString& aSessionName,
const nsString& aIconPath);
enum SessionState {
UNINITIALIZED, // Has not been initialized yet
STARTED, // Started
CLONED, // SetSessionInfoCalled, Start not called
FAILED, // The audio session failed to start
STOPPED, // Stop called
AUDIO_SESSION_DISCONNECTED // Audio session disconnected
};
protected:
RefPtr<IAudioSessionControl> mAudioSessionControl;
nsString mDisplayName;
nsString mIconPath;
nsID mSessionGroupingParameter;
SessionState mState;
// Guards the IAudioSessionControl
mozilla::Mutex mMutex;
ThreadSafeAutoRefCnt mRefCnt;
NS_DECL_OWNINGTHREAD
static AudioSession* sService;
};
nsresult StartAudioSession() { return AudioSession::GetSingleton()->Start(); }
nsresult StopAudioSession() { return AudioSession::GetSingleton()->Stop(); }
nsresult GetAudioSessionData(nsID& aID, nsString& aSessionName,
nsString& aIconPath) {
return AudioSession::GetSingleton()->GetSessionData(aID, aSessionName,
aIconPath);
}
nsresult RecvAudioSessionData(const nsID& aID, const nsString& aSessionName,
const nsString& aIconPath) {
return AudioSession::GetSingleton()->SetSessionData(aID, aSessionName,
aIconPath);
}
AudioSession* AudioSession::sService = nullptr;
AudioSession::AudioSession() : mMutex("AudioSessionControl") {
mState = UNINITIALIZED;
}
AudioSession::~AudioSession() {}
AudioSession* AudioSession::GetSingleton() {
if (!(AudioSession::sService)) {
RefPtr<AudioSession> service = new AudioSession();
service.forget(&AudioSession::sService);
}
// We don't refcount AudioSession on the Gecko side, we hold one single ref
// as long as the appshell is running.
return AudioSession::sService;
}
// It appears Windows will use us on a background thread ...
NS_IMPL_ADDREF(AudioSession)
NS_IMPL_RELEASE(AudioSession)
STDMETHODIMP
AudioSession::QueryInterface(REFIID iid, void** ppv) {
const IID IID_IAudioSessionEvents = __uuidof(IAudioSessionEvents);
if ((IID_IUnknown == iid) || (IID_IAudioSessionEvents == iid)) {
*ppv = static_cast<IAudioSessionEvents*>(this);
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
// Once we are started Windows will hold a reference to us through our
// IAudioSessionEvents interface that will keep us alive until the appshell
// calls Stop.
nsresult AudioSession::Start() {
MOZ_ASSERT(mState == UNINITIALIZED || mState == CLONED ||
mState == AUDIO_SESSION_DISCONNECTED,
"State invariants violated");
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioSessionManager = __uuidof(IAudioSessionManager);
HRESULT hr;
// There's a matching CoUninit in Stop() for this tied to a state of
// UNINITIALIZED.
hr = CoInitialize(nullptr);
MOZ_ASSERT(SUCCEEDED(hr),
"CoInitialize failure in audio session control, unexpected");
if (mState == UNINITIALIZED) {
mState = FAILED;
// Content processes should be CLONED
if (XRE_IsContentProcess()) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(XRE_IsParentProcess(),
"Should only get here in a chrome process!");
nsCOMPtr<nsIStringBundleService> bundleService =
do_GetService(NS_STRINGBUNDLE_CONTRACTID);
NS_ENSURE_TRUE(bundleService, NS_ERROR_FAILURE);
nsCOMPtr<nsIStringBundle> bundle;
bundleService->CreateBundle("chrome://branding/locale/brand.properties",
getter_AddRefs(bundle));
NS_ENSURE_TRUE(bundle, NS_ERROR_FAILURE);
bundle->GetStringFromName("brandFullName", mDisplayName);
wchar_t* buffer;
mIconPath.GetMutableData(&buffer, MAX_PATH);
::GetModuleFileNameW(nullptr, buffer, MAX_PATH);
nsCOMPtr<nsIUUIDGenerator> uuidgen =
do_GetService("@mozilla.org/uuid-generator;1");
NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE);
uuidgen->GenerateUUIDInPlace(&mSessionGroupingParameter);
}
mState = FAILED;
MOZ_ASSERT(!mDisplayName.IsEmpty() || !mIconPath.IsEmpty(),
"Should never happen ...");
RefPtr<IMMDeviceEnumerator> enumerator;
hr = ::CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL,
IID_IMMDeviceEnumerator, getter_AddRefs(enumerator));
if (FAILED(hr)) return NS_ERROR_NOT_AVAILABLE;
RefPtr<IMMDevice> device;
hr = enumerator->GetDefaultAudioEndpoint(
EDataFlow::eRender, ERole::eMultimedia, getter_AddRefs(device));
if (FAILED(hr)) {
if (hr == E_NOTFOUND) return NS_ERROR_NOT_AVAILABLE;
return NS_ERROR_FAILURE;
}
RefPtr<IAudioSessionManager> manager;
hr = device->Activate(IID_IAudioSessionManager, CLSCTX_ALL, nullptr,
getter_AddRefs(manager));
if (FAILED(hr)) {
return NS_ERROR_FAILURE;
}
MutexAutoLock lock(mMutex);
hr = manager->GetAudioSessionControl(&GUID_NULL, 0,
getter_AddRefs(mAudioSessionControl));
if (FAILED(hr)) {
return NS_ERROR_FAILURE;
}
// Increments refcount of 'this'.
hr = mAudioSessionControl->RegisterAudioSessionNotification(this);
if (FAILED(hr)) {
StopInternal();
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIRunnable> runnable =
NewRunnableMethod("AudioSession::CommitAudioSessionData", this,
&AudioSession::CommitAudioSessionData);
NS_DispatchToMainThread(runnable);
mState = STARTED;
return NS_OK;
}
void SpawnASCReleaseThread(RefPtr<IAudioSessionControl>&& aASC) {
// Fake moving to the other thread by circumventing the ref count.
// (RefPtrs don't play well with C++11 lambdas and we don't want to use
// XPCOM here.)
IAudioSessionControl* rawPtr = nullptr;
aASC.forget(&rawPtr);
MOZ_ASSERT(rawPtr);
PRThread* thread = PR_CreateThread(
PR_USER_THREAD,
[](void* aRawPtr) {
NS_SetCurrentThreadName("AudioASCReleaser");
static_cast<IAudioSessionControl*>(aRawPtr)->Release();
},
rawPtr, PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_UNJOINABLE_THREAD, 0);
if (!thread) {
// We can't make a thread so just destroy the IAudioSessionControl here.
rawPtr->Release();
}
}
void AudioSession::StopInternal() {
mMutex.AssertCurrentThreadOwns();
if (mAudioSessionControl && (mState == STARTED || mState == STOPPED)) {
// Decrement refcount of 'this'
mAudioSessionControl->UnregisterAudioSessionNotification(this);
}
// Win7 is the only Windows version supported before Win8.
if (mAudioSessionControl && !IsWin8OrLater()) {
// bug 1419488: Avoid hanging due to Win7 race condition when destroying
// AudioSessionControl. We do that by Moving the AudioSessionControl
// to a worker thread (that we never 'join') for destruction.
SpawnASCReleaseThread(std::move(mAudioSessionControl));
} else {
mAudioSessionControl = nullptr;
}
}
nsresult AudioSession::Stop() {
MOZ_ASSERT(mState == STARTED || mState == UNINITIALIZED || // XXXremove this
mState == FAILED,
"State invariants violated");
SessionState state = mState;
mState = STOPPED;
{
RefPtr<AudioSession> kungFuDeathGrip;
kungFuDeathGrip.swap(sService);
MutexAutoLock lock(mMutex);
StopInternal();
}
if (state != UNINITIALIZED) {
::CoUninitialize();
}
return NS_OK;
}
void CopynsID(nsID& lhs, const nsID& rhs) {
lhs.m0 = rhs.m0;
lhs.m1 = rhs.m1;
lhs.m2 = rhs.m2;
for (int i = 0; i < 8; i++) {
lhs.m3[i] = rhs.m3[i];
}
}
nsresult AudioSession::GetSessionData(nsID& aID, nsString& aSessionName,
nsString& aIconPath) {
MOZ_ASSERT(mState == FAILED || mState == STARTED || mState == CLONED,
"State invariants violated");
CopynsID(aID, mSessionGroupingParameter);
aSessionName = mDisplayName;
aIconPath = mIconPath;
if (mState == FAILED) return NS_ERROR_FAILURE;
return NS_OK;
}
nsresult AudioSession::SetSessionData(const nsID& aID,
const nsString& aSessionName,
const nsString& aIconPath) {
MOZ_ASSERT(mState == UNINITIALIZED, "State invariants violated");
MOZ_ASSERT(!XRE_IsParentProcess(),
"Should never get here in a chrome process!");
mState = CLONED;
CopynsID(mSessionGroupingParameter, aID);
mDisplayName = aSessionName;
mIconPath = aIconPath;
return NS_OK;
}
nsresult AudioSession::CommitAudioSessionData() {
MutexAutoLock lock(mMutex);
if (!mAudioSessionControl) {
// Stop() was called before we had a chance to do this.
return NS_OK;
}
HRESULT hr = mAudioSessionControl->SetGroupingParam(
(LPGUID)&mSessionGroupingParameter, nullptr);
if (FAILED(hr)) {
StopInternal();
return NS_ERROR_FAILURE;
}
hr = mAudioSessionControl->SetDisplayName(mDisplayName.get(), nullptr);
if (FAILED(hr)) {
StopInternal();
return NS_ERROR_FAILURE;
}
hr = mAudioSessionControl->SetIconPath(mIconPath.get(), nullptr);
if (FAILED(hr)) {
StopInternal();
return NS_ERROR_FAILURE;
}
return NS_OK;
}
STDMETHODIMP
AudioSession::OnChannelVolumeChanged(DWORD aChannelCount,
float aChannelVolumeArray[],
DWORD aChangedChannel, LPCGUID aContext) {
return S_OK; // NOOP
}
STDMETHODIMP
AudioSession::OnDisplayNameChanged(LPCWSTR aDisplayName, LPCGUID aContext) {
return S_OK; // NOOP
}
STDMETHODIMP
AudioSession::OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext) {
return S_OK; // NOOP
}
STDMETHODIMP
AudioSession::OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext) {
return S_OK; // NOOP
}
STDMETHODIMP
AudioSession::OnSessionDisconnected(AudioSessionDisconnectReason aReason) {
// Run our code asynchronously. Per MSDN we can't do anything interesting
// in this callback.
nsCOMPtr<nsIRunnable> runnable =
NewRunnableMethod("widget::AudioSession::OnSessionDisconnectedInternal",
this, &AudioSession::OnSessionDisconnectedInternal);
NS_DispatchToMainThread(runnable);
return S_OK;
}
nsresult AudioSession::OnSessionDisconnectedInternal() {
// When successful, UnregisterAudioSessionNotification will decrement the
// refcount of 'this'. Start will re-increment it. In the interim,
// we'll need to reference ourselves.
RefPtr<AudioSession> kungFuDeathGrip(this);
{
// We need to release the mutex before we call Start().
MutexAutoLock lock(mMutex);
if (!mAudioSessionControl) return NS_OK;
mAudioSessionControl->UnregisterAudioSessionNotification(this);
mAudioSessionControl = nullptr;
}
mState = AUDIO_SESSION_DISCONNECTED;
CoUninitialize();
Start(); // If it fails there's not much we can do.
return NS_OK;
}
STDMETHODIMP
AudioSession::OnSimpleVolumeChanged(float aVolume, BOOL aMute,
LPCGUID aContext) {
return S_OK; // NOOP
}
STDMETHODIMP
AudioSession::OnStateChanged(AudioSessionState aState) {
return S_OK; // NOOP
}
} // namespace widget
} // namespace mozilla