michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "nsIStringBundle.h" michael@0: #include "nsIUUIDGenerator.h" michael@0: #include "nsIXULAppInfo.h" michael@0: michael@0: //#include "AudioSession.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "mozilla/Attributes.h" michael@0: michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: namespace widget { michael@0: michael@0: /* michael@0: * To take advantage of what Vista+ have to offer with respect to audio, michael@0: * we need to maintain an audio session. This class wraps IAudioSessionControl michael@0: * and implements IAudioSessionEvents (for callbacks from Windows) michael@0: */ michael@0: class AudioSession MOZ_FINAL : public IAudioSessionEvents { michael@0: private: michael@0: AudioSession(); michael@0: ~AudioSession(); michael@0: public: michael@0: static AudioSession* GetSingleton(); michael@0: michael@0: // COM IUnknown michael@0: STDMETHODIMP_(ULONG) AddRef(); michael@0: STDMETHODIMP QueryInterface(REFIID, void**); michael@0: STDMETHODIMP_(ULONG) Release(); michael@0: michael@0: // IAudioSessionEvents michael@0: STDMETHODIMP OnChannelVolumeChanged(DWORD aChannelCount, michael@0: float aChannelVolumeArray[], michael@0: DWORD aChangedChannel, michael@0: LPCGUID aContext); michael@0: STDMETHODIMP OnDisplayNameChanged(LPCWSTR aDisplayName, LPCGUID aContext); michael@0: STDMETHODIMP OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext); michael@0: STDMETHODIMP OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext); michael@0: STDMETHODIMP OnSessionDisconnected(AudioSessionDisconnectReason aReason); michael@0: private: michael@0: nsresult OnSessionDisconnectedInternal(); michael@0: public: michael@0: STDMETHODIMP OnSimpleVolumeChanged(float aVolume, michael@0: BOOL aMute, michael@0: LPCGUID aContext); michael@0: STDMETHODIMP OnStateChanged(AudioSessionState aState); michael@0: michael@0: nsresult Start(); michael@0: nsresult Stop(); michael@0: void StopInternal(); michael@0: michael@0: nsresult GetSessionData(nsID& aID, michael@0: nsString& aSessionName, michael@0: nsString& aIconPath); michael@0: michael@0: nsresult SetSessionData(const nsID& aID, michael@0: const nsString& aSessionName, michael@0: const nsString& aIconPath); michael@0: michael@0: enum SessionState { michael@0: UNINITIALIZED, // Has not been initialized yet michael@0: STARTED, // Started michael@0: CLONED, // SetSessionInfoCalled, Start not called michael@0: FAILED, // The autdio session failed to start michael@0: STOPPED, // Stop called michael@0: AUDIO_SESSION_DISCONNECTED // Audio session disconnected michael@0: }; michael@0: protected: michael@0: nsRefPtr mAudioSessionControl; michael@0: nsString mDisplayName; michael@0: nsString mIconPath; michael@0: nsID mSessionGroupingParameter; michael@0: SessionState mState; michael@0: michael@0: ThreadSafeAutoRefCnt mRefCnt; michael@0: NS_DECL_OWNINGTHREAD michael@0: michael@0: static AudioSession* sService; michael@0: }; michael@0: michael@0: nsresult michael@0: StartAudioSession() michael@0: { michael@0: return AudioSession::GetSingleton()->Start(); michael@0: } michael@0: michael@0: nsresult michael@0: StopAudioSession() michael@0: { michael@0: return AudioSession::GetSingleton()->Stop(); michael@0: } michael@0: michael@0: nsresult michael@0: GetAudioSessionData(nsID& aID, michael@0: nsString& aSessionName, michael@0: nsString& aIconPath) michael@0: { michael@0: return AudioSession::GetSingleton()->GetSessionData(aID, michael@0: aSessionName, michael@0: aIconPath); michael@0: } michael@0: michael@0: nsresult michael@0: RecvAudioSessionData(const nsID& aID, michael@0: const nsString& aSessionName, michael@0: const nsString& aIconPath) michael@0: { michael@0: return AudioSession::GetSingleton()->SetSessionData(aID, michael@0: aSessionName, michael@0: aIconPath); michael@0: } michael@0: michael@0: AudioSession* AudioSession::sService = nullptr; michael@0: michael@0: AudioSession::AudioSession() michael@0: { michael@0: mState = UNINITIALIZED; michael@0: } michael@0: michael@0: AudioSession::~AudioSession() michael@0: { michael@0: michael@0: } michael@0: michael@0: AudioSession* michael@0: AudioSession::GetSingleton() michael@0: { michael@0: if (!(AudioSession::sService)) { michael@0: nsRefPtr service = new AudioSession(); michael@0: service.forget(&AudioSession::sService); michael@0: } michael@0: michael@0: // We don't refcount AudioSession on the Gecko side, we hold one single ref michael@0: // as long as the appshell is running. michael@0: return AudioSession::sService; michael@0: } michael@0: michael@0: // It appears Windows will use us on a background thread ... michael@0: NS_IMPL_ADDREF(AudioSession) michael@0: NS_IMPL_RELEASE(AudioSession) michael@0: michael@0: STDMETHODIMP michael@0: AudioSession::QueryInterface(REFIID iid, void **ppv) michael@0: { michael@0: const IID IID_IAudioSessionEvents = __uuidof(IAudioSessionEvents); michael@0: if ((IID_IUnknown == iid) || michael@0: (IID_IAudioSessionEvents == iid)) { michael@0: *ppv = static_cast(this); michael@0: AddRef(); michael@0: return S_OK; michael@0: } michael@0: michael@0: return E_NOINTERFACE; michael@0: } michael@0: michael@0: // Once we are started Windows will hold a reference to us through our michael@0: // IAudioSessionEvents interface that will keep us alive until the appshell michael@0: // calls Stop. michael@0: nsresult michael@0: AudioSession::Start() michael@0: { michael@0: NS_ABORT_IF_FALSE(mState == UNINITIALIZED || michael@0: mState == CLONED || michael@0: mState == AUDIO_SESSION_DISCONNECTED, michael@0: "State invariants violated"); michael@0: michael@0: const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); michael@0: const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); michael@0: const IID IID_IAudioSessionManager = __uuidof(IAudioSessionManager); michael@0: michael@0: HRESULT hr; michael@0: michael@0: // Don't check for errors in case something already initialized COM michael@0: // on this thread. michael@0: CoInitialize(nullptr); michael@0: michael@0: if (mState == UNINITIALIZED) { michael@0: mState = FAILED; michael@0: michael@0: // XXXkhuey implement this for content processes michael@0: if (XRE_GetProcessType() == GeckoProcessType_Content) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: NS_ABORT_IF_FALSE(XRE_GetProcessType() == GeckoProcessType_Default, michael@0: "Should only get here in a chrome process!"); michael@0: michael@0: nsCOMPtr bundleService = michael@0: do_GetService(NS_STRINGBUNDLE_CONTRACTID); michael@0: NS_ENSURE_TRUE(bundleService, NS_ERROR_FAILURE); michael@0: nsCOMPtr bundle; michael@0: bundleService->CreateBundle("chrome://branding/locale/brand.properties", michael@0: getter_AddRefs(bundle)); michael@0: NS_ENSURE_TRUE(bundle, NS_ERROR_FAILURE); michael@0: michael@0: bundle->GetStringFromName(MOZ_UTF16("brandFullName"), michael@0: getter_Copies(mDisplayName)); michael@0: michael@0: wchar_t *buffer; michael@0: mIconPath.GetMutableData(&buffer, MAX_PATH); michael@0: michael@0: // XXXkhuey we should provide a way for a xulrunner app to specify an icon michael@0: // that's not in the product binary. michael@0: ::GetModuleFileNameW(nullptr, buffer, MAX_PATH); michael@0: michael@0: nsCOMPtr uuidgen = michael@0: do_GetService("@mozilla.org/uuid-generator;1"); michael@0: NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE); michael@0: uuidgen->GenerateUUIDInPlace(&mSessionGroupingParameter); michael@0: } michael@0: michael@0: mState = FAILED; michael@0: michael@0: NS_ABORT_IF_FALSE(!mDisplayName.IsEmpty() || !mIconPath.IsEmpty(), michael@0: "Should never happen ..."); michael@0: michael@0: nsRefPtr enumerator; michael@0: hr = ::CoCreateInstance(CLSID_MMDeviceEnumerator, michael@0: nullptr, michael@0: CLSCTX_ALL, michael@0: IID_IMMDeviceEnumerator, michael@0: getter_AddRefs(enumerator)); michael@0: if (FAILED(hr)) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: nsRefPtr device; michael@0: hr = enumerator->GetDefaultAudioEndpoint(EDataFlow::eRender, michael@0: ERole::eMultimedia, michael@0: getter_AddRefs(device)); michael@0: if (FAILED(hr)) { michael@0: if (hr == E_NOTFOUND) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsRefPtr manager; michael@0: hr = device->Activate(IID_IAudioSessionManager, michael@0: CLSCTX_ALL, michael@0: nullptr, michael@0: getter_AddRefs(manager)); michael@0: if (FAILED(hr)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: hr = manager->GetAudioSessionControl(nullptr, michael@0: FALSE, michael@0: getter_AddRefs(mAudioSessionControl)); michael@0: if (FAILED(hr)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: hr = mAudioSessionControl->SetGroupingParam((LPCGUID)&mSessionGroupingParameter, michael@0: nullptr); michael@0: if (FAILED(hr)) { michael@0: StopInternal(); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: hr = mAudioSessionControl->SetDisplayName(mDisplayName.get(), nullptr); michael@0: if (FAILED(hr)) { michael@0: StopInternal(); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: hr = mAudioSessionControl->SetIconPath(mIconPath.get(), nullptr); michael@0: if (FAILED(hr)) { michael@0: StopInternal(); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: hr = mAudioSessionControl->RegisterAudioSessionNotification(this); michael@0: if (FAILED(hr)) { michael@0: StopInternal(); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mState = STARTED; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: AudioSession::StopInternal() michael@0: { michael@0: static const nsID blankId = {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0} }; michael@0: michael@0: if (mAudioSessionControl) { michael@0: mAudioSessionControl->SetGroupingParam((LPCGUID)&blankId, nullptr); michael@0: mAudioSessionControl->UnregisterAudioSessionNotification(this); michael@0: mAudioSessionControl = nullptr; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: AudioSession::Stop() michael@0: { michael@0: NS_ABORT_IF_FALSE(mState == STARTED || michael@0: mState == UNINITIALIZED || // XXXremove this michael@0: mState == FAILED, michael@0: "State invariants violated"); michael@0: mState = STOPPED; michael@0: michael@0: nsRefPtr kungFuDeathGrip; michael@0: kungFuDeathGrip.swap(sService); michael@0: michael@0: if (XRE_GetProcessType() != GeckoProcessType_Content) michael@0: StopInternal(); michael@0: michael@0: // At this point kungFuDeathGrip should be the only reference to AudioSession michael@0: michael@0: ::CoUninitialize(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void CopynsID(nsID& lhs, const nsID& rhs) michael@0: { michael@0: lhs.m0 = rhs.m0; michael@0: lhs.m1 = rhs.m1; michael@0: lhs.m2 = rhs.m2; michael@0: for (int i = 0; i < 8; i++ ) { michael@0: lhs.m3[i] = rhs.m3[i]; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: AudioSession::GetSessionData(nsID& aID, michael@0: nsString& aSessionName, michael@0: nsString& aIconPath) michael@0: { michael@0: NS_ABORT_IF_FALSE(mState == FAILED || michael@0: mState == STARTED || michael@0: mState == CLONED, michael@0: "State invariants violated"); michael@0: michael@0: CopynsID(aID, mSessionGroupingParameter); michael@0: aSessionName = mDisplayName; michael@0: aIconPath = mIconPath; michael@0: michael@0: if (mState == FAILED) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: AudioSession::SetSessionData(const nsID& aID, michael@0: const nsString& aSessionName, michael@0: const nsString& aIconPath) michael@0: { michael@0: NS_ABORT_IF_FALSE(mState == UNINITIALIZED, michael@0: "State invariants violated"); michael@0: NS_ABORT_IF_FALSE(XRE_GetProcessType() != GeckoProcessType_Default, michael@0: "Should never get here in a chrome process!"); michael@0: mState = CLONED; michael@0: michael@0: CopynsID(mSessionGroupingParameter, aID); michael@0: mDisplayName = aSessionName; michael@0: mIconPath = aIconPath; michael@0: return NS_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: AudioSession::OnChannelVolumeChanged(DWORD aChannelCount, michael@0: float aChannelVolumeArray[], michael@0: DWORD aChangedChannel, michael@0: LPCGUID aContext) michael@0: { michael@0: return S_OK; // NOOP michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: AudioSession::OnDisplayNameChanged(LPCWSTR aDisplayName, michael@0: LPCGUID aContext) michael@0: { michael@0: return S_OK; // NOOP michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: AudioSession::OnGroupingParamChanged(LPCGUID aGroupingParam, michael@0: LPCGUID aContext) michael@0: { michael@0: return S_OK; // NOOP michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: AudioSession::OnIconPathChanged(LPCWSTR aIconPath, michael@0: LPCGUID aContext) michael@0: { michael@0: return S_OK; // NOOP michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: AudioSession::OnSessionDisconnected(AudioSessionDisconnectReason aReason) michael@0: { michael@0: // Run our code asynchronously. Per MSDN we can't do anything interesting michael@0: // in this callback. michael@0: nsCOMPtr runnable = michael@0: NS_NewRunnableMethod(this, &AudioSession::OnSessionDisconnectedInternal); michael@0: NS_DispatchToMainThread(runnable); michael@0: return S_OK; michael@0: } michael@0: michael@0: nsresult michael@0: AudioSession::OnSessionDisconnectedInternal() michael@0: { michael@0: if (!mAudioSessionControl) michael@0: return NS_OK; michael@0: michael@0: mAudioSessionControl->UnregisterAudioSessionNotification(this); michael@0: mAudioSessionControl = nullptr; michael@0: michael@0: mState = AUDIO_SESSION_DISCONNECTED; michael@0: CoUninitialize(); michael@0: Start(); // If it fails there's not much we can do. michael@0: return NS_OK; michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: AudioSession::OnSimpleVolumeChanged(float aVolume, michael@0: BOOL aMute, michael@0: LPCGUID aContext) michael@0: { michael@0: return S_OK; // NOOP michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: AudioSession::OnStateChanged(AudioSessionState aState) michael@0: { michael@0: return S_OK; // NOOP michael@0: } michael@0: michael@0: } // namespace widget michael@0: } // namespace mozilla