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

#include "services/audio/public/cpp/sounds/audio_stream_handler.h"

#include <stdint.h>

#include <memory>
#include <string>
#include <string_view>
#include <utility>

#include "base/cancelable_callback.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/synchronization/lock.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "media/audio/audio_handler.h"
#include "media/audio/flac_audio_handler.h"
#include "media/audio/wav_audio_handler.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_codecs.h"
#include "media/base/channel_layout.h"
#include "media/mojo/mojom/audio_output_stream.mojom.h"
#include "media/mojo/mojom/audio_stream_factory.mojom.h"
#include "services/audio/public/cpp/output_device.h"
#include "ui/base/resource/resource_bundle.h"

namespace audio {

namespace {

// Volume percent.
const double kOutputVolumePercent = 0.8;

// Keep alive timeout for audio stream.
const int kKeepAliveMs = 1500;

}  // namespace

class AudioStreamHandler::AudioStreamContainer
    : public media::AudioRendererSink::RenderCallback {
 public:
  AudioStreamContainer(SoundsManager::StreamFactoryBinder stream_factory_binder,
                       std::unique_ptr<media::AudioHandler> audio_handler,
                       bool loop)
      : stream_factory_binder_(std::move(stream_factory_binder)),
        audio_handler_(std::move(audio_handler)),
        loop_(loop) {
    DCHECK(audio_handler_);
    task_runner_ = base::SequencedTaskRunner::GetCurrentDefault();
  }

  AudioStreamContainer(const AudioStreamContainer&) = delete;
  AudioStreamContainer& operator=(const AudioStreamContainer&) = delete;

  ~AudioStreamContainer() override {
    DCHECK(task_runner_->RunsTasksInCurrentSequence());
  }

  void Play() {
    DCHECK(task_runner_->RunsTasksInCurrentSequence());

    // Create OutputDevice if it is the first time playing.
    if (!device_) {
      const media::AudioParameters params(
          media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
          media::ChannelLayoutConfig::Guess(audio_handler_->GetNumChannels()),
          audio_handler_->GetSampleRate(),
          media::AudioHandler::kDefaultFrameCount);
      mojo::PendingRemote<media::mojom::AudioStreamFactory> stream_factory;
      stream_factory_binder_.Run(
          stream_factory.InitWithNewPipeAndPassReceiver());
      device_ = std::make_unique<audio::OutputDevice>(
          std::move(stream_factory), params, this, std::string());
    }

    {
      base::AutoLock al(state_lock_);

      delayed_stop_posted_ = false;
      stop_closure_.Reset(base::BindRepeating(&AudioStreamContainer::Stop,
                                              base::Unretained(this)));

      if (started_) {
        if (audio_handler_->AtEnd()) {
          audio_handler_->Reset();
        }
        return;
      } else {
        device_->SetVolume(kOutputVolumePercent);
      }

      if (paused_) {
        paused_ = false;
      } else {
        audio_handler_->Reset();
      }
    }

    started_ = true;
    device_->Play();
  }

  void Stop() {
    DCHECK(task_runner_->RunsTasksInCurrentSequence());

    if (started_) {
      // Do not hold the |state_lock_| while stopping the output stream.
      device_->Pause();
    }

    started_ = false;
    paused_ = false;
    stop_closure_.Cancel();
    device_.reset();
  }

  void Pause() {
    DCHECK(task_runner_->RunsTasksInCurrentSequence());

    if (started_) {
      // Do not hold the |state_lock_| while stopping the output stream.
      device_->Pause();
      paused_ = true;
      started_ = false;
      stop_closure_.Cancel();
      // Release `OutputDevice` to avoid holding resources during an indefinite
      // pause.
      device_.reset();
    }
  }

 private:
  // media::AudioRendererSink::RenderCallback overrides:
  // Following methods could be called from *ANY* thread.
  int Render(base::TimeDelta /* delay */,
             base::TimeTicks /* delay_timestamp */,
             const media::AudioGlitchInfo& /* glitch_info */,
             media::AudioBus* dest) override {
    base::AutoLock al(state_lock_);
    int total_frames_written = 0;
    const int requested_frames = dest->frames();
    while (total_frames_written < requested_frames) {
      if (audio_handler_->AtEnd()) {
        if (loop_) {
          audio_handler_->Reset();
        } else {
          break;
        }
      }
      size_t frames_written = 0;
      if (!audio_handler_->CopyPartialFramesTo(
              dest, requested_frames - total_frames_written,
              total_frames_written, &frames_written) ||
          frames_written == 0) {
        DLOG(ERROR) << "Failed to copy frames to audio bus";
        // To avoid infinite loop if `CopyPartialFramesTo` encounters an error.
        break;
      }
      total_frames_written += frames_written;
    }

    if (total_frames_written == 0 && !delayed_stop_posted_) {
      delayed_stop_posted_ = true;
      task_runner_->PostDelayedTask(FROM_HERE, stop_closure_.callback(),
                                    base::Milliseconds(kKeepAliveMs));
    }
    return total_frames_written;
  }

  void OnRenderError() override {
    task_runner_->PostTask(FROM_HERE,
                           base::BindOnce(&AudioStreamContainer::Stop,
                                          weak_factory_.GetWeakPtr()));
  }

  bool started_ = false;
  bool paused_ = false;
  const SoundsManager::StreamFactoryBinder stream_factory_binder_;
  std::unique_ptr<audio::OutputDevice> device_;
  scoped_refptr<base::SequencedTaskRunner> task_runner_;

  // All variables below must be accessed under |state_lock_| when |started_|.
  base::Lock state_lock_;
  bool delayed_stop_posted_ = false;
  std::unique_ptr<media::AudioHandler> audio_handler_;
  const bool loop_;
  base::CancelableRepeatingClosure stop_closure_;

  base::WeakPtrFactory<AudioStreamHandler::AudioStreamContainer> weak_factory_{
      this};
};

AudioStreamHandler::AudioStreamHandler(
    SoundsManager::StreamFactoryBinder stream_factory_binder,
    int resource_id,
    media::AudioCodec codec,
    bool loop) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  const std::string_view audio_data =
      ui::ResourceBundle::GetSharedInstance().GetRawDataResource(resource_id);

  std::unique_ptr<media::AudioHandler> audio_handler;
  switch (codec) {
    case media::AudioCodec::kPCM: {
      audio_handler =
          media::WavAudioHandler::Create(base::as_byte_span(audio_data));
      if (!audio_handler || !audio_handler->Initialize()) {
        LOG(ERROR) << "wav_data is not valid";
        return;
      }
      break;
    }
    case media::AudioCodec::kFLAC: {
      auto tmp_handler = std::make_unique<media::FlacAudioHandler>(audio_data);
      if (!tmp_handler->Initialize()) {
        LOG(ERROR) << "flac_data is not valid";
        return;
      }
      audio_handler = std::move(tmp_handler);
      break;
    }
    default:
      NOTREACHED() << "Unsupported audio codec encountered: "
                   << media::GetCodecName(codec);
  }

  // Check params.
  const media::AudioParameters params(
      media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
      media::ChannelLayoutConfig::Guess(audio_handler->GetNumChannels()),
      audio_handler->GetSampleRate(), media::AudioHandler::kDefaultFrameCount);
  if (!params.IsValid()) {
    LOG(ERROR) << "Audio params are invalid.";
    return;
  }

  duration_ = audio_handler->GetDuration();
  stream_ = base::SequenceBound<AudioStreamContainer>(
      base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::WithBaseSyncPrimitives(),
           base::TaskPriority::USER_VISIBLE}),
      std::move(stream_factory_binder), std::move(audio_handler), loop);
}

AudioStreamHandler::~AudioStreamHandler() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (IsInitialized()) {
    stream_.AsyncCall(&AudioStreamContainer::Stop);
  }
}

bool AudioStreamHandler::IsInitialized() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return !!stream_;
}

bool AudioStreamHandler::Play() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!IsInitialized()) {
    return false;
  }

  stream_.AsyncCall(&AudioStreamContainer::Play);
  return true;
}

void AudioStreamHandler::Stop() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!IsInitialized()) {
    return;
  }

  stream_.AsyncCall(&AudioStreamContainer::Stop);
}

bool AudioStreamHandler::Pause() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!IsInitialized()) {
    return false;
  }

  stream_.AsyncCall(&AudioStreamContainer::Pause);
  return true;
}

base::TimeDelta AudioStreamHandler::duration() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(IsInitialized());
  return duration_;
}

}  // namespace audio
