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

#include "third_party/blink/renderer/modules/webcodecs/decoder_selector.h"

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/notreached.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "media/base/channel_layout.h"
#include "media/base/demuxer_stream.h"
#include "media/base/media_switches.h"
#include "media/base/sample_format.h"
#include "media/filters/decrypting_demuxer_stream.h"
#include "third_party/blink/renderer/modules/modules_export.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"

namespace blink {

// Demuxing isn't part of WebCodecs. This shim allows us to reuse decoder
// selection logic from <video>.
// TODO(chcunningham): Maybe refactor DecoderSelector to separate dependency on
// media::DemuxerStream. DecoderSelection doesn't conceptually require a
// Demuxer. The tough part is re-working Decryptingmedia::DemuxerStream.
template <media::DemuxerStream::Type StreamType>
class NullDemuxerStream : public media::DemuxerStream {
 public:
  using DecoderConfigType =
      typename media::DecoderStreamTraits<StreamType>::DecoderConfigType;

  ~NullDemuxerStream() override = default;

  void Read(uint32_t count, ReadCB read_cb) override { NOTREACHED(); }

  void Configure(DecoderConfigType config);

  media::AudioDecoderConfig audio_decoder_config() override {
    DCHECK_EQ(type(), media::DemuxerStream::AUDIO);
    return audio_decoder_config_;
  }

  media::VideoDecoderConfig video_decoder_config() override {
    DCHECK_EQ(type(), media::DemuxerStream::VIDEO);
    return video_decoder_config_;
  }

  Type type() const override { return stream_type; }

  bool SupportsConfigChanges() override {
    // NOTE: Returning false here causes the DecoderSelector to select based on
    // resolution when hardwareAcceleration == "no-preference". If it's instead
    // "prefer-hardware" or "prefer-software" the flag has no effect since only
    // the hardware or software factory is returned via `create_decoders_cb`.
    return !base::FeatureList::IsEnabled(
        media::kResolutionBasedDecoderPriority);
  }

  void set_low_delay(bool low_delay) { low_delay_ = low_delay; }
  media::StreamLiveness liveness() const override {
    return low_delay_ ? media::StreamLiveness::kLive
                      : media::StreamLiveness::kUnknown;
  }

 private:
  static const media::DemuxerStream::Type stream_type = StreamType;

  media::AudioDecoderConfig audio_decoder_config_;
  media::VideoDecoderConfig video_decoder_config_;
  bool low_delay_ = false;
};

template <>
void NullDemuxerStream<media::DemuxerStream::AUDIO>::Configure(
    DecoderConfigType config) {
  audio_decoder_config_ = config;
}

template <>
void NullDemuxerStream<media::DemuxerStream::VIDEO>::Configure(
    DecoderConfigType config) {
  video_decoder_config_ = config;
}

template <media::DemuxerStream::Type StreamType>
DecoderSelector<StreamType>::DecoderSelector(
    scoped_refptr<base::SequencedTaskRunner> task_runner,
    CreateDecodersCB create_decoders_cb,
    media::MediaLog* media_log,
    typename Decoder::OutputCB output_cb)
    : media_log_(media_log),
      impl_(std::move(task_runner),
            std::move(create_decoders_cb),
            media_log_,
            /*enable_priority_based_selection=*/true),
      demuxer_stream_(new NullDemuxerStream<StreamType>()),
      stream_traits_(CreateStreamTraits()),
      output_cb_(output_cb) {
  CHECK(media_log_);
  impl_.Initialize(stream_traits_.get(), demuxer_stream_.get(),
                   nullptr /*CdmContext*/, media::WaitingCB());
}

template <media::DemuxerStream::Type StreamType>
DecoderSelector<StreamType>::~DecoderSelector() = default;

template <media::DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::SelectDecoder(
    const DecoderConfig& config,
    bool low_delay,
    SelectDecoderCB select_decoder_cb) {
  // |impl_| will internally use this the |config| from our NullDemuxerStream.
  demuxer_stream_->Configure(config);
  demuxer_stream_->set_low_delay(low_delay);

  // media::DecoderSelector will call back with a DecoderStatus if selection is
  // in progress when it is destructed.
  impl_.BeginDecoderSelection(
      blink::BindOnce(&DecoderSelector<StreamType>::OnDecoderSelected,
                      weak_factory_.GetWeakPtr(), std::move(select_decoder_cb)),
      output_cb_);
}

template <>
std::unique_ptr<WebCodecsAudioDecoderSelector::StreamTraits>
DecoderSelector<media::DemuxerStream::AUDIO>::CreateStreamTraits() {
  // TODO(chcunningham): Consider plumbing real hw channel layout.
  return std::make_unique<DecoderSelector::StreamTraits>(
      media_log_, media::ChannelLayoutConfig(), media::kUnknownSampleFormat);
}

template <>
std::unique_ptr<WebCodecsVideoDecoderSelector::StreamTraits>
DecoderSelector<media::DemuxerStream::VIDEO>::CreateStreamTraits() {
  return std::make_unique<DecoderSelector::StreamTraits>(media_log_);
}

template <media::DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::OnDecoderSelected(
    SelectDecoderCB select_decoder_cb,
    DecoderOrError decoder_or_error,
    std::unique_ptr<media::DecryptingDemuxerStream> decrypting_demuxer_stream) {
  DCHECK(!decrypting_demuxer_stream);

  // We immediately finalize decoder selection.
  // TODO(chcunningham): Rework this to do finalize after first frame
  // successfully decoded. This updates to match latest plans for spec
  // (configure() no longer takes a promise).
  impl_.FinalizeDecoderSelection();

  std::move(select_decoder_cb).Run(std::move(decoder_or_error));
}

template class MODULES_EXPORT DecoderSelector<media::DemuxerStream::VIDEO>;
template class MODULES_EXPORT DecoderSelector<media::DemuxerStream::AUDIO>;

}  // namespace blink
