dom/media/AudioNotificationSender.cpp
author Mike Hommey <mh+mozilla@glandium.org>
Fri, 11 Jan 2019 16:01:15 +0000
changeset 453570 daf50f25895db073e44d50fecf2e4f6fe873865d
parent 446960 0ceae9db9ec0be18daa1a279511ad305723185d4
child 461887 f41cee9bf14931b453838d1bbcbda528e3b064e8
permissions -rw-r--r--
Bug 1519307 - Add a new project to build useful parts of breakpad independently. r=froydnj With `ac_add_options --enable-project=tools/crashreporter` in a mozconfig, `./mach build` builds minidump_stackwalk, dump_syms and fileid. One caveat is that due to limitation in how the build system works currently, it's cumbersome to keep dump_syms as a host program for Gecko, and to make it a target program for this project. For now, keep it as a host program. We're not going to use it on automation, but it's still convenient to have for quick local builds (I've had to resort to awful hacks downstream). Differential Revision: https://phabricator.services.mozilla.com/D16299

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "AudioNotificationSender.h"
#include "mozilla/ClearOnShutdown.h"    // for ClearOnShutdown
#include "mozilla/dom/ContentParent.h"  // for ContentParent
#include "mozilla/Logging.h"            // for LazyLogModule
#include "mozilla/RefPtr.h"             // for RefPtr
#include "mozilla/StaticPtr.h"          // for StaticAutoPtr
#include "nsAppRunner.h"                // for XRE_IsParentProcess
#include "nsTArray.h"                   // for nsTArray
#include <mmdeviceapi.h>                // for IMMNotificationClient interface

static mozilla::LazyLogModule sLogger("AudioNotificationSender");

#undef ANS_LOG
#define ANS_LOG(...) MOZ_LOG(sLogger, mozilla::LogLevel::Debug, (__VA_ARGS__))
#undef ANS_LOGW
#define ANS_LOGW(...) \
  MOZ_LOG(sLogger, mozilla::LogLevel::Warning, (__VA_ARGS__))

namespace mozilla {
namespace audio {

/*
 * A runnable task to notify the audio device-changed event.
 */
class AudioDeviceChangedRunnable final : public Runnable {
 public:
  explicit AudioDeviceChangedRunnable()
      : Runnable("AudioDeviceChangedRunnable") {}

  NS_IMETHOD Run() override {
    MOZ_ASSERT(NS_IsMainThread());

    nsTArray<dom::ContentParent*> parents;
    dom::ContentParent::GetAll(parents);
    for (dom::ContentParent* p : parents) {
      Unused << p->SendAudioDefaultDeviceChange();
    }
    return NS_OK;
  }
};  // class AudioDeviceChangedRunnable

/*
 * An observer for receiving audio device events from Windows.
 */
typedef void (*DefaultDeviceChangedCallback)();
class AudioNotification final : public IMMNotificationClient {
 public:
  explicit AudioNotification(DefaultDeviceChangedCallback aCallback)
      : mCallback(aCallback), mRefCt(0), mIsRegistered(false) {
    MOZ_COUNT_CTOR(AudioNotification);
    MOZ_ASSERT(mCallback);
    const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
    const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
    HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr,
                                  CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator,
                                  getter_AddRefs(mDeviceEnumerator));

    if (FAILED(hr)) {
      ANS_LOGW("Cannot create an IMMDeviceEnumerator instance.");
      return;
    }

    hr = mDeviceEnumerator->RegisterEndpointNotificationCallback(this);
    if (FAILED(hr)) {
      ANS_LOGW("Cannot register notification callback.");
      return;
    }

    ANS_LOG("Register notification callback successfully.");
    mIsRegistered = true;
  }

  ~AudioNotification() {
    MOZ_COUNT_DTOR(AudioNotification);
    // Assert mIsRegistered is true when we have mDeviceEnumerator.
    // Don't care mIsRegistered if there is no mDeviceEnumerator.
    MOZ_ASSERT(!mDeviceEnumerator || mIsRegistered);
    if (!mDeviceEnumerator) {
      ANS_LOG("No device enumerator in use.");
      return;
    }

    HRESULT hr =
        mDeviceEnumerator->UnregisterEndpointNotificationCallback(this);
    if (FAILED(hr)) {
      // We can't really do anything here, so we just add a log for debugging.
      ANS_LOGW("Unregister notification failed.");
    } else {
      ANS_LOG("Unregister notification callback successfully.");
    }

    mIsRegistered = false;
  }

  // True whenever the notification server is set to report events to this
  // object.
  bool IsRegistered() const { return mIsRegistered; }

  // IMMNotificationClient Implementation
  HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow aFlow, ERole aRole,
                                                   LPCWSTR aDeviceId) override {
    ANS_LOG("Default device has changed: flow %d, role: %d\n", aFlow, aRole);
    mCallback();
    return S_OK;
  }

  // The remaining methods are not implemented. they simply log when called
  // (if log is enabled), for debugging.
  HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR aDeviceId) override {
    ANS_LOG("Audio device added.");
    return S_OK;
  };

  HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR aDeviceId) override {
    ANS_LOG("Audio device removed.");
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR aDeviceId,
                                                 DWORD aNewState) override {
    ANS_LOG("Audio device state changed.");
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE
  OnPropertyValueChanged(LPCWSTR aDeviceId, const PROPERTYKEY aKey) override {
    ANS_LOG("Audio device property value changed.");
    return S_OK;
  }

  // IUnknown Implementation
  ULONG STDMETHODCALLTYPE AddRef() override {
    return InterlockedIncrement(&mRefCt);
  }

  ULONG STDMETHODCALLTYPE Release() override {
    ULONG ulRef = InterlockedDecrement(&mRefCt);
    if (0 == ulRef) {
      delete this;
    }
    return ulRef;
  }

  HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,
                                           VOID** ppvInterface) override {
    if (__uuidof(IUnknown) == riid) {
      AddRef();
      *ppvInterface = static_cast<IUnknown*>(this);
    } else if (__uuidof(IMMNotificationClient) == riid) {
      AddRef();
      *ppvInterface = static_cast<IMMNotificationClient*>(this);
    } else {
      *ppvInterface = NULL;
      return E_NOINTERFACE;
    }
    return S_OK;
  }

 private:
  RefPtr<IMMDeviceEnumerator> mDeviceEnumerator;
  DefaultDeviceChangedCallback mCallback;
  LONG mRefCt;
  bool mIsRegistered;
};  // class AudioNotification

/*
 * A singleton observer for audio device changed events.
 */
static StaticAutoPtr<AudioNotification> sAudioNotification;

/*
 * AudioNotificationSender Implementation
 */
/* static */ nsresult AudioNotificationSender::Init() {
  MOZ_ASSERT(XRE_IsParentProcess());
  MOZ_ASSERT(NS_IsMainThread());

  if (!sAudioNotification) {
    sAudioNotification = new AudioNotification(NotifyDefaultDeviceChanged);
    ClearOnShutdown(&sAudioNotification);

    if (!sAudioNotification->IsRegistered()) {
      ANS_LOGW("The notification sender cannot be initialized.");
      return NS_ERROR_FAILURE;
    }
    ANS_LOG("The notification sender is initailized successfully.");
  }

  return NS_OK;
}

/* static */ void AudioNotificationSender::NotifyDefaultDeviceChanged() {
  // This is running on the callback thread (from OnDefaultDeviceChanged).
  MOZ_ASSERT(XRE_IsParentProcess());
  ANS_LOG("Notify the default device-changed event.");

  RefPtr<AudioDeviceChangedRunnable> runnable =
      new AudioDeviceChangedRunnable();
  NS_DispatchToMainThread(runnable);
}

}  // namespace audio
}  // namespace mozilla