mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-23 02:05:42 +00:00
5dc21d568c
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
447 lines
13 KiB
C++
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
|