// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/media/forwarding_audio_stream_factory.h"

#include <utility>

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/no_destructor.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/guest_page_holder_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/audio_service.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "media/base/audio_parameters.h"
#include "media/mojo/mojom/audio_processing.mojom.h"
#include "ui/gfx/geometry/size.h"

namespace content {

namespace {

ForwardingAudioStreamFactory::AudioStreamFactoryBinder&
GetAudioStreamFactoryBinderOverride() {
  static base::NoDestructor<
      ForwardingAudioStreamFactory::AudioStreamFactoryBinder>
      binder;
  return *binder;
}

void BindStreamFactoryFromUIThread(
    mojo::PendingReceiver<media::mojom::AudioStreamFactory> receiver) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  const auto& binder_override = GetAudioStreamFactoryBinderOverride();
  if (binder_override) {
    binder_override.Run(std::move(receiver));
    return;
  }

  GetAudioService().BindStreamFactory(std::move(receiver));
}

}  // namespace

ForwardingAudioStreamFactory::Core::Core(
    base::WeakPtr<ForwardingAudioStreamFactory> owner,
    std::unique_ptr<AudioStreamBrokerFactory> broker_factory)
    : owner_(std::move(owner)),
      broker_factory_(std::move(broker_factory)),
      group_id_(base::UnguessableToken::Create()) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(owner_);
  DCHECK(broker_factory_);
}

ForwardingAudioStreamFactory::Core::~Core() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  for (AudioStreamBroker::LoopbackSink* sink : loopback_sinks_)
    sink->OnSourceGone();
}

base::WeakPtr<ForwardingAudioStreamFactory::Core>
ForwardingAudioStreamFactory::Core::AsWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

void ForwardingAudioStreamFactory::Core::CreateInputStream(
    int render_process_id,
    int render_frame_id,
    const std::string& device_id,
    const media::AudioParameters& params,
    uint32_t shared_memory_count,
    bool enable_agc,
    media::mojom::AudioProcessingConfigPtr processing_config,
    mojo::PendingRemote<blink::mojom::RendererAudioInputStreamFactoryClient>
        renderer_factory_client) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  // |this| owns |inputs_|, so Unretained is safe.
  inputs_
      .insert(broker_factory_->CreateAudioInputStreamBroker(
          render_process_id, render_frame_id, device_id, params, group_id_,
          shared_memory_count, enable_agc, std::move(processing_config),
          base::BindOnce(&ForwardingAudioStreamFactory::Core::RemoveInput,
                         base::Unretained(this)),
          std::move(renderer_factory_client)))
      .first->get()
      ->CreateStream(GetFactory());
}

void ForwardingAudioStreamFactory::Core::AssociateInputAndOutputForAec(
    const base::UnguessableToken& input_stream_id,
    const std::string& raw_output_device_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  // Avoid spawning a factory if this for some reason gets called with an
  // invalid |input_stream_id| before any streams are created.
  if (!inputs_.empty()) {
    GetFactory()->AssociateInputAndOutputForAec(input_stream_id,
                                                raw_output_device_id);
  }
}

void ForwardingAudioStreamFactory::Core::CreateOutputStream(
    int render_process_id,
    int render_frame_id,
    const GlobalRenderFrameHostToken& main_frame_token,
    const std::string& device_id,
    const media::AudioParameters& params,
    mojo::PendingRemote<media::mojom::AudioOutputStreamProviderClient> client) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  // |this| owns |outputs_|, so Unretained is safe.
  outputs_
      .insert(broker_factory_->CreateAudioOutputStreamBroker(
          render_process_id, render_frame_id, main_frame_token,
          ++stream_id_counter_, device_id, params, group_id_,
          base::BindOnce(&ForwardingAudioStreamFactory::Core::RemoveOutput,
                         base::Unretained(this)),
          std::move(client)))
      .first->get()
      ->CreateStream(GetFactory());
}

void ForwardingAudioStreamFactory::Core::CreateLoopbackStream(
    int render_process_id,
    int render_frame_id,
    AudioStreamBroker::LoopbackSource* loopback_source,
    const media::AudioParameters& params,
    uint32_t shared_memory_count,
    bool mute_source,
    mojo::PendingRemote<blink::mojom::RendererAudioInputStreamFactoryClient>
        renderer_factory_client) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DCHECK(loopback_source);

  TRACE_EVENT_BEGIN("audio", "CreateLoopbackStream", "group",
                    group_id_.GetLowForSerialization());

  // |this| owns |inputs_|, so Unretained is safe.
  inputs_
      .insert(broker_factory_->CreateAudioLoopbackStreamBroker(
          render_process_id, render_frame_id, loopback_source, params,
          shared_memory_count, mute_source,
          base::BindOnce(&ForwardingAudioStreamFactory::Core::RemoveInput,
                         base::Unretained(this)),
          std::move(renderer_factory_client)))
      .first->get()
      ->CreateStream(GetFactory());
  TRACE_EVENT_END("audio", "source",
                  loopback_source->GetGroupID().GetLowForSerialization());
}

void ForwardingAudioStreamFactory::Core::SetMuted(bool muted) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DCHECK_NE(muted, !!muter_);
  TRACE_EVENT_INSTANT("audio", "SetMuted", "group",
                      group_id_.GetLowForSerialization(), "muted", muted);

  if (!muted) {
    muter_.reset();
    return;
  }

  muter_.emplace(group_id_);
  if (remote_factory_)
    muter_->Connect(remote_factory_.get());
}

void ForwardingAudioStreamFactory::Core::AddLoopbackSink(
    AudioStreamBroker::LoopbackSink* sink) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  loopback_sinks_.insert(sink);
  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&ForwardingAudioStreamFactory::LoopbackStreamStarted,
                     owner_));
}

void ForwardingAudioStreamFactory::Core::RemoveLoopbackSink(
    AudioStreamBroker::LoopbackSink* sink) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  loopback_sinks_.erase(sink);
  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&ForwardingAudioStreamFactory::LoopbackStreamStopped,
                     owner_));
}

const base::UnguessableToken& ForwardingAudioStreamFactory::Core::GetGroupID() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  return group_id();
}

// static
ForwardingAudioStreamFactory* ForwardingAudioStreamFactory::ForFrame(
    RenderFrameHost* frame) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (!frame) {
    return nullptr;
  }

  GuestPageHolderImpl* guest_holder = GuestPageHolderImpl::FromRenderFrameHost(
      static_cast<RenderFrameHostImpl&>(*frame));
  if (guest_holder) {
    return guest_holder->GetAudioStreamFactory();
  }

  auto* contents =
      static_cast<WebContentsImpl*>(WebContents::FromRenderFrameHost(frame));
  if (!contents) {
    return nullptr;
  }

  return contents->GetAudioStreamFactory();
}

// static
ForwardingAudioStreamFactory::Core* ForwardingAudioStreamFactory::CoreForFrame(
    RenderFrameHost* frame) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  ForwardingAudioStreamFactory* forwarding_factory =
      ForwardingAudioStreamFactory::ForFrame(frame);
  return forwarding_factory ? forwarding_factory->core() : nullptr;
}

ForwardingAudioStreamFactory::ForwardingAudioStreamFactory(
    WebContents* web_contents,
    std::unique_ptr<AudioStreamBrokerFactory> broker_factory)
    : WebContentsObserver(web_contents), core_() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  core_ = std::make_unique<Core>(weak_ptr_factory_.GetWeakPtr(),
                                 std::move(broker_factory));
}

ForwardingAudioStreamFactory::~ForwardingAudioStreamFactory() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  // Ensure |core_| is deleted on the right thread. DeleteOnIOThread isn't used
  // as it doesn't post in case it is already executed on the right thread. That
  // causes issues in unit tests where the UI thread and the IO thread are the
  // same.
  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE, base::DoNothingWithBoundArgs(std::move(core_)));
}

void ForwardingAudioStreamFactory::LoopbackStreamStarted() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  capture_handle_ = web_contents()->IncrementCapturerCount(
      gfx::Size(), /*stay_hidden=*/false,
      /*stay_awake=*/true, /*is_activity=*/true);
}

void ForwardingAudioStreamFactory::LoopbackStreamStopped() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  capture_handle_.RunAndReset();
}

void ForwardingAudioStreamFactory::SetMuted(bool muted) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (is_muted_ != muted) {
    is_muted_ = muted;

    // Unretained is safe since the destruction of |core_| will be posted to the
    // IO thread later.
    GetIOThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(&Core::SetMuted, base::Unretained(core_.get()), muted));
  }
}

bool ForwardingAudioStreamFactory::IsMuted() const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  return is_muted_;
}

void ForwardingAudioStreamFactory::RenderFrameDeleted(
    RenderFrameHost* render_frame_host) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(render_frame_host);

  // Unretained is safe since the destruction of |core_| will be posted to the
  // IO thread later.
  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&Core::CleanupStreamsBelongingTo,
                     base::Unretained(core_.get()),
                     render_frame_host->GetProcess()->GetDeprecatedID(),
                     render_frame_host->GetRoutingID()));
}

void ForwardingAudioStreamFactory::OverrideAudioStreamFactoryBinderForTesting(
    AudioStreamFactoryBinder binder) {
  GetAudioStreamFactoryBinderOverride() = std::move(binder);
}

void ForwardingAudioStreamFactory::Core::CleanupStreamsBelongingTo(
    int render_process_id,
    int render_frame_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  TRACE_EVENT_BEGIN("audio", "CleanupStreamsBelongingTo", "group",
                    group_id_.GetLowForSerialization(), "process id",
                    render_process_id);

  auto match_rfh =
      [render_process_id, render_frame_id](
          const std::unique_ptr<AudioStreamBroker>& broker) -> bool {
    return broker->render_process_id() == render_process_id &&
           broker->render_frame_id() == render_frame_id;
  };

  base::EraseIf(outputs_, match_rfh);
  base::EraseIf(inputs_, match_rfh);

  ResetRemoteFactoryPtrIfIdle();

  TRACE_EVENT_END("audio", "frame_id", render_frame_id);
}

void ForwardingAudioStreamFactory::Core::RemoveInput(
    AudioStreamBroker* broker) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  size_t removed = inputs_.erase(broker);
  DCHECK_EQ(1u, removed);

  ResetRemoteFactoryPtrIfIdle();
}

void ForwardingAudioStreamFactory::Core::RemoveOutput(
    AudioStreamBroker* broker) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  size_t removed = outputs_.erase(broker);
  DCHECK_EQ(1u, removed);

  ResetRemoteFactoryPtrIfIdle();
}

media::mojom::AudioStreamFactory*
ForwardingAudioStreamFactory::Core::GetFactory() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (!remote_factory_) {
    TRACE_EVENT_INSTANT("audio",
                        "ForwardingAudioStreamFactory: Binding new factory",
                        "group", group_id_.GetLowForSerialization());
    GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(&BindStreamFactoryFromUIThread,
                       remote_factory_.BindNewPipeAndPassReceiver()));
    // Unretained is safe because |this| owns |remote_factory_|.
    remote_factory_.set_disconnect_handler(base::BindOnce(
        &ForwardingAudioStreamFactory::Core::ResetRemoteFactoryPtr,
        base::Unretained(this)));

    // Restore the muting session on reconnect.
    if (muter_)
      muter_->Connect(remote_factory_.get());
  }

  return remote_factory_.get();
}

void ForwardingAudioStreamFactory::Core::ResetRemoteFactoryPtrIfIdle() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (inputs_.empty() && outputs_.empty())
    ResetRemoteFactoryPtr();
}

void ForwardingAudioStreamFactory::Core::ResetRemoteFactoryPtr() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (remote_factory_) {
    TRACE_EVENT_INSTANT("audio",
                        "ForwardingAudioStreamFactory: Resetting factory",
                        "group", group_id_.GetLowForSerialization());
  }
  remote_factory_.reset();
  // The stream brokers will call a callback to be deleted soon, give them a
  // chance to signal an error to the client first.
}

}  // namespace content
