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

#include "media/mojo/services/media_foundation_service.h"

#include <map>
#include <memory>
#include <optional>

#include "base/check.h"
#include "base/feature_list.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/path_service.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "base/unguessable_token.h"
#include "media/base/audio_codecs.h"
#include "media/base/cdm_capability.h"
#include "media/base/content_decryption_module.h"
#include "media/base/encryption_scheme.h"
#include "media/base/key_system_capability.h"
#include "media/base/key_systems.h"
#include "media/base/media_switches.h"
#include "media/base/video_codecs.h"
#include "media/base/win/media_foundation_package_runtime_locator.h"
#include "media/cdm/win/media_foundation_cdm_module.h"
#include "media/cdm/win/media_foundation_cdm_util.h"
#include "media/media_buildflags.h"
#include "media/mojo/mojom/interface_factory.mojom.h"
#include "media/mojo/mojom/key_system_support.mojom.h"
#include "media/mojo/services/interface_factory_impl.h"

using Microsoft::WRL::ComPtr;

namespace media {

namespace {

// The feature parameters follow Windows API documentation:
// https://docs.microsoft.com/en-us/uwp/api/windows.media.protection.protectioncapabilities.istypesupported?view=winrt-19041
// This default feature string is required to query capability related to video
// decoder. Since we only care about the codec support rather than the specific
// resolution or bitrate capability, we use the following typical values which
// should be supported by most devices for a certain video codec.
const char kDefaultFeatures[] =
    "decode-bpp=8,decode-res-x=1920,decode-res-y=1080,decode-bitrate=10000000,"
    "decode-fps=30";

// These three parameters are an extension of the parameters supported
// in the above documentation to support the encryption capability query.
const char kEncryptionSchemeQueryName[] = "encryption-type";
const char kEncryptionIvQueryName[] = "encryption-iv-size";
#if BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_DOLBY_VISION)
const char kHdrQueryName[] = "hdr";
const char kDolbyVisionSupportUmaPrefix[] =
    "Media.EME.MediaFoundationService.DolbyVisionSupport";
#endif  // BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_DOLBY_VISION)

const char kPlayReadyKeySystemRecommendationHwSecure[] =
    "com.microsoft.playready.recommendation.3000";
// We need this char array to query the Windows Media Foundation API
// to know which codecs have the clear lead fix enabled on the computer.
// We do not check cbcs-clearlead because clearlead fix itself should be
// orthogonal to encryption scheme support.
// https://docs.microsoft.com/en-us/windows/win32/api/mfmediaengine/nf-mfmediaengine-imfextendeddrmtypesupport-istypesupportedex
const char kClearLeadEncryptionScheme[] = "cenc-clearlead";

// The followings define the supported codecs and encryption schemes that we try
// to query.
constexpr VideoCodec kAllVideoCodecs[] = {
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
    VideoCodec::kH264,
#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
    VideoCodec::kHEVC,
#if BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
    VideoCodec::kDolbyVision,
#endif  // BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
#endif  // BUILDFLAG(ENABLE_PLATFORM_HEVC)
#endif  // BUILDFLAG(USE_PROPRIETARY_CODECS)
    VideoCodec::kVP9, VideoCodec::kAV1};

// Only a subset of audio codecs is queried here. Vorbis, FLAC and Opus are
// intentionally excluded since the OS PlayReady CDM does not support them, and
// there are no plans to add support because those codecs are not used by
// content providers for encrypted content.
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
constexpr AudioCodec kAllAudioCodecs[] = {
    AudioCodec::kAAC,
#if BUILDFLAG(ENABLE_PLATFORM_AC3_EAC3_AUDIO)
    AudioCodec::kEAC3,       AudioCodec::kAC3,
#endif  // BUILDFLAG(ENABLE_PLATFORM_AC3_EAC3_AUDIO)
#if BUILDFLAG(ENABLE_PLATFORM_AC4_AUDIO)
    AudioCodec::kAC4,
#endif  // BUILDFLAG(ENABLE_PLATFORM_AC4_AUDIO)
#if BUILDFLAG(ENABLE_PLATFORM_MPEG_H_AUDIO)
    AudioCodec::kMpegHAudio,
#endif  // BUILDFLAG(ENABLE_PLATFORM_MPEG_H_AUDIO)
};
#endif  // BUILDFLAG(USE_PROPRIETARY_CODECS)

constexpr EncryptionScheme kAllEncryptionSchemes[] = {EncryptionScheme::kCenc,
                                                      EncryptionScheme::kCbcs};

using IsTypeSupportedCallback =
    base::RepeatingCallback<bool(bool is_hw_secure,
                                 const std::string& content_type)>;

bool IsTypeSupported(ComPtr<IMFExtendedDRMTypeSupport> mf_type_support,
                     const std::string& key_system,
                     bool is_hw_secure,
                     const std::string& content_type) {
  const base::TimeTicks start_time = base::TimeTicks::Now();
  // Force the use of the hardware based PlayReady key system.
  auto is_supported_or_error = IsMediaFoundationContentTypeSupported(
      mf_type_support, kPlayReadyKeySystemRecommendationHwSecure, content_type);
  bool supported = is_supported_or_error.value_or(false);
  // The above function may take seconds to run. Report UMA to understand the
  // actual performance impact. Report UMA only for success cases.
  if (supported) {
    auto uma_name = "Media.EME.MediaFoundationService." +
                    GetKeySystemNameForUMA(key_system, is_hw_secure) +
                    ".IsTypeSupportedEx";
    base::UmaHistogramTimes(uma_name, base::TimeTicks::Now() - start_time);
  }

  DVLOG(3) << __func__ << " " << (supported ? "[yes]" : "[no]") << ": "
           << key_system << ", " << content_type;

  return supported;
}

std::string GetFourCCString(VideoCodec codec) {
  switch (codec) {
    case VideoCodec::kH264:
      return "avc1";
    case VideoCodec::kVP9:
      return "vp09";
    case VideoCodec::kHEVC:
    case VideoCodec::kDolbyVision:
      return "hvc1";
    case VideoCodec::kAV1:
      return "av01";
    default:
      NOTREACHED()
          << "This video codec is not supported by MediaFoundationCDM. codec="
          << GetCodecName(codec);
  }
}

// Returns an "ext-profile" feature query (with ending comma) for a video codec.
// Returns an empty string if "ext-profile" is not needed.
std::string GetExtProfile(VideoCodec codec) {
  if (codec == VideoCodec::kDolbyVision)
    return "ext-profile=dvhe.05,";

  return "";
}

std::string GetFourCCString(AudioCodec codec) {
  switch (codec) {
    case AudioCodec::kAAC:
      return "mp4a";
    case AudioCodec::kEAC3:
      return "ec-3";
    case AudioCodec::kAC3:
      return "ac-3";
    case AudioCodec::kAC4:
      return "ac-4";
    case AudioCodec::kMpegHAudio:
      return "mhm1";
    default:
      NOTREACHED()
          << "This audio codec is not supported by MediaFoundationCDM. codec="
          << GetCodecName(codec);
  }
}

std::string GetName(EncryptionScheme scheme) {
  switch (scheme) {
    case EncryptionScheme::kCenc:
      return "cenc";
    case EncryptionScheme::kCbcs:
      return "cbcs";
    default:
      NOTREACHED() << "Only cenc and cbcs are supported";
  }
}

// According to the common encryption spec, both 8 and 16 bytes IV are allowed
// for CENC and CBCS. But some platforms may only support 8 byte IV CENC and
// Chromium does not differentiate IV size for each encryption scheme, so we use
// 8 for CENC and 16 for CBCS to provide the best coverage as those combination
// are recommended.
int GetIvSize(EncryptionScheme scheme) {
  switch (scheme) {
    case EncryptionScheme::kCenc:
      return 8;
    case EncryptionScheme::kCbcs:
      return 16;
    default:
      NOTREACHED() << "Only cenc and cbcs are supported";
  }
}

// Feature name:value mapping.
using FeatureMap = std::map<std::string, std::string>;

// Construct the query type string based on `video_codec`, optional
// `audio_codec`, `kDefaultFeatures` and `extra_features`.
std::string GetTypeString(VideoCodec video_codec,
                          std::optional<AudioCodec> audio_codec,
                          const FeatureMap& extra_features) {
  auto codec_string = GetFourCCString(video_codec);
  if (audio_codec.has_value())
    codec_string += "," + GetFourCCString(audio_codec.value());

  auto feature_string = GetExtProfile(video_codec) + kDefaultFeatures;
  DCHECK(!feature_string.empty()) << "default feature cannot be empty";
  for (const auto& feature : extra_features) {
    DCHECK(!feature.first.empty() && !feature.second.empty());
    feature_string += "," + feature.first + "=" + feature.second;
  }

  return base::ReplaceStringPlaceholders(
      "video/mp4;codecs=\"$1\";features=\"$2\"", {codec_string, feature_string},
      /*offsets=*/nullptr);
}

base::flat_set<EncryptionScheme> GetSupportedEncryptionSchemes(
    bool is_hw_secure,
    VideoCodec video_codec,
    IsTypeSupportedCallback is_type_supported_cb,
    const base::flat_set<EncryptionScheme>& schemes_to_query) {
  base::flat_set<EncryptionScheme> supported_schemes;

  for (const auto scheme : schemes_to_query) {
    FeatureMap extra_features = {
        {kEncryptionSchemeQueryName, GetName(scheme)},
        {kEncryptionIvQueryName, base::NumberToString(GetIvSize(scheme))}};

    if (is_type_supported_cb.Run(
            is_hw_secure,
            GetTypeString(video_codec, /*audio_codec=*/std::nullopt,
                          extra_features))) {
      supported_schemes.insert(scheme);
    }
  }
  return supported_schemes;
}

HRESULT CreateDummyMediaFoundationCdm(
    ComPtr<IMFContentDecryptionModuleFactory> cdm_factory,
    const std::string& key_system) {
  // Set `use_hw_secure_codecs` to indicate this for hardware secure mode,
  // which typically requires identifier and persistent storage.
  CdmConfig cdm_config = {key_system, /*allow_distinctive_identifier=*/true,
                          /*allow_persistent_state=*/true,
                          /*use_hw_secure_codecs=*/true};

  // Use a random CDM origin.
  auto cdm_origin_id = base::UnguessableToken::Create();

  // Use a dummy CDM store path root under the temp dir here. Since this code
  // runs in the LPAC process, the temp dir will be something like:
  //   C:\Users\<user>\AppData\Local\Packages\cr.sb.cdm<...>\AC\Temp
  // This folder is specifically for the CDM app container, so there's no need
  // to set ACL explicitly.
  // Use a short name for the store path to help avoid hitting the MAX_PATH
  // limitation. Note, this won't fix all scenarios since the path is still
  // dependent on the username length.
  base::FilePath temp_dir;
  base::PathService::Get(base::DIR_TEMP, &temp_dir);
  auto dummy_cdm_store_path_root = temp_dir.AppendASCII("DummyCdm");

  // Create the dummy CDM.
  Microsoft::WRL::ComPtr<IMFContentDecryptionModule> mf_cdm;
  auto hr = CreateMediaFoundationCdm(cdm_factory, cdm_config, cdm_origin_id,
                                     /*cdm_client_token=*/std::nullopt,
                                     dummy_cdm_store_path_root, mf_cdm);
  DLOG_IF(ERROR, FAILED(hr)) << __func__ << ": Failed for " << key_system;
  mf_cdm.Reset();

  // Delete the dummy CDM store folder so we don't leave files behind. This may
  // fail since the CDM and related objects may have the files open longer than
  // the total delete retry period or before the process terminates. This is
  // fine since they will be cleaned next time so files will not accumulate.
  // Ignore the `reply_callback` since nothing can be done with the result.
  base::ThreadPool::PostTask(
      FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
      base::GetDeletePathRecursivelyCallback(dummy_cdm_store_path_root));

  return hr;
}

// Reports the HRESULT of the CDM capability query status.
void ReportCapabilityQueryStatusHresultUMA(const std::string& key_system,
                                           const std::string& uma_name_postfix,
                                           HRESULT hresult) {
  auto uma_prefix =
      "Media.EME." + media::GetKeySystemNameForUMA(key_system, std::nullopt);
  base::UmaHistogramSparse(
      uma_prefix + ".CdmCapabilityQueryStatus." + uma_name_postfix, hresult);
}

CdmCapabilityOrStatus GetCdmCapability(
    ComPtr<IMFContentDecryptionModuleFactory> cdm_factory,
    const std::string& key_system,
    bool is_hw_secure,
    IsTypeSupportedCallback is_type_supported_cb) {
  DVLOG(2) << __func__ << ": key_system=" << key_system
           << ", is_hw_secure=" << is_hw_secure;

  const auto start_time = base::TimeTicks::Now();

  // For hardware secure decryption, even when the IsTypeSupported query says
  // it's supported, CDM creation could fail immediately. Therefore, create a
  // dummy CDM instance to detect this case.
  HRESULT hresult = S_OK;
  if (is_hw_secure && FAILED(hresult = CreateDummyMediaFoundationCdm(
                                 cdm_factory, key_system))) {
    DVLOG(1) << __func__
             << ": CreateDummyMediaFoundationCdm() failed with hresult="
             << hresult;
    ReportCapabilityQueryStatusHresultUMA(
        key_system, kCreateDummyMediaFoundationCdmHresultUmaPostfix, hresult);
    return base::unexpected(
        CdmCapabilityQueryStatus::kCreateDummyMediaFoundationCdmFailed);
  }

  FeatureMap extra_features = {};
  CdmCapability capability;

  // Check for clear lead support for hardware security, as we only support
  // codecs that support clear lead for CDMs in HW security. Software
  // security always supports clear lead.
  // For Audio Codecs:
  // The contract of the API is such that the encryption scheme is applied
  // to both audio and video. In terms of the current implementation, the
  // encryption type is essentially ignored for audio, but that's because
  // all encryption types should be supported for audio.
  // `cenc-clearlead` and `cenc` are equivalent for audio in all cases.
  // Software vs Hardware Clearlead Codec Enforcement:
  // SWDRM and HWDRM are enforced the same for cenc-clearlead checking,
  // which can cause issues since clear lead should always be supported
  // for SWDRM. So, if the CDM is software secure, do not pass in the
  // cenc-clearlead because the codec checking might result in an unsupported
  // value, which is an oversight in the current PR impl.
  if (is_hw_secure) {
    extra_features.insert(
        {{kEncryptionSchemeQueryName, kClearLeadEncryptionScheme},
         {kEncryptionIvQueryName,
          base::NumberToString(GetIvSize(EncryptionScheme::kCenc))}});
  }

  // Query video codecs.
  for (const auto video_codec : kAllVideoCodecs) {
#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
    // Only query encrypted HEVC when the feature is enabled.
    if (video_codec == VideoCodec::kHEVC &&
        !base::FeatureList::IsEnabled(kPlatformHEVCDecoderSupport)) {
      continue;
    }
#endif

#if BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_DOLBY_VISION)
    // Only query encrypted Dolby Vision when the feature is enabled.
    if (video_codec == VideoCodec::kDolbyVision &&
        !base::FeatureList::IsEnabled(kPlatformEncryptedDolbyVision)) {
      continue;
    }
#endif

    if (is_hw_secure) {
      // Remove VP9/AV1 from the hardware secure CDM capabilities check
      // if the feature is disabled.
      if ((video_codec == VideoCodec::kVP9 &&
           !base::FeatureList::IsEnabled(kHardwareSecureDecryptionVp9)) ||
          (video_codec == VideoCodec::kAV1 &&
           !base::FeatureList::IsEnabled(kHardwareSecureDecryptionAv1))) {
        continue;
      }
    }

    std::optional<bool> is_type_supported_result = std::nullopt;
#if BUILDFLAG(ENABLE_PLATFORM_ENCRYPTED_DOLBY_VISION)
    if (is_hw_secure && video_codec == VideoCodec::kDolbyVision) {
      // 1. Query without HDR support.
      bool dv_support_without_hdr = is_type_supported_cb.Run(
          is_hw_secure, GetTypeString(video_codec, /*audio_codec=*/std::nullopt,
                                      extra_features));

      // 2. Query with HDR support. When multiple displays are connected to the
      // device, the query result is expected to return TRUE if the primary
      // display (internal) is HDR. If the query result without HDR check is
      // FALSE, we know the quer result with HDR check is expected to return
      // FALSE as well.
      bool dv_support_with_hdr = false;
      if (dv_support_without_hdr) {
        extra_features.insert({{kHdrQueryName, "1"}});
        dv_support_with_hdr = is_type_supported_cb.Run(
            is_hw_secure,
            GetTypeString(video_codec, /*audio_codec=*/std::nullopt,
                          extra_features));
        extra_features.erase(kHdrQueryName);
      }

      DVLOG(3) << __func__ << ": Dolby Vision support - dv_support_with_hdr="
               << dv_support_with_hdr
               << ", dv_support_without_hdr=" << dv_support_without_hdr;

      base::UmaHistogramBoolean(
          std::string(kDolbyVisionSupportUmaPrefix) + ".WithHdrCheck",
          dv_support_with_hdr);
      // TODO(crbug.com/536954447): Remove the ".WithoutHdrCheck" histogram once
      // we verify that the Dolby Vision capability query with the HDR display
      // check always is working as expected.
      base::UmaHistogramBoolean(
          std::string(kDolbyVisionSupportUmaPrefix) + ".WithoutHdrCheck",
          dv_support_without_hdr);

      // 3. Determine the final support. We always require the HDR display
      // check for Dolby Vision (via MediaFoundation's IsTypeSupported query)
      // for the following reasons:
      // - Content providers often check for HDR display support using their
      //   own methods (e.g., via CSS Media Queries).
      // - MediaFoundation's IsTypeSupported query with "hdr=1" considers
      //   EDR (Enhanced Dynamic Range) as HDR.
      // - Dolby Vision content is expected to be played on an HDR-capable
      //   display.
      // - Querying Dolby Vision support with the HDR display check always
      //   prevents complexity and confusion across player implementations.
      is_type_supported_result = dv_support_with_hdr;
    }
#endif

    bool is_video_codec_supported =
        is_type_supported_result.has_value()
            ? is_type_supported_result.value()
            : is_type_supported_cb.Run(
                  is_hw_secure,
                  GetTypeString(video_codec, /*audio_codec=*/std::nullopt,
                                extra_features));
    if (is_video_codec_supported) {
      // IsTypeSupported() does not support querying profiling, in general
      // assume all relevant profiles are supported.
      VideoCodecInfo video_codec_info;

#if BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
      // Dolby Vision on Windows only support profile 4/5/8 now. But profile 4
      // is rarely used and being deprecated, so only declare the support for
      // profile 5/8.
      if (video_codec == VideoCodec::kDolbyVision) {
        video_codec_info.supported_profiles = {
            VideoCodecProfile::DOLBYVISION_PROFILE5,
            VideoCodecProfile::DOLBYVISION_PROFILE8};
      }
#endif

      capability.video_codecs.emplace(video_codec, video_codec_info);
    }
  }

  // IsTypeSupported query string requires video codec, so stops if no video
  // codecs are supported.
  if (capability.video_codecs.empty()) {
    DVLOG(2) << "No video codecs supported for is_hw_secure=" << is_hw_secure;
    return base::unexpected(CdmCapabilityQueryStatus::kNoSupportedVideoCodec);
  }

#if BUILDFLAG(USE_PROPRIETARY_CODECS)
  // Query audio codecs.
  // Audio is usually independent to the video codec. So we use <one of the
  // supported video codecs> + <audio codec> to query the audio capability.
  for (const auto audio_codec : kAllAudioCodecs) {
    const auto& video_codec = capability.video_codecs.begin()->first;

    if (is_type_supported_cb.Run(
            is_hw_secure,
            GetTypeString(video_codec, audio_codec, extra_features))) {
      capability.audio_codecs.emplace(audio_codec);
    }
  }
#endif  // BUILDFLAG(USE_PROPRIETARY_CODECS)

  // Query encryption scheme.

  // Note that the CdmCapability assumes all `video_codecs` + `encryption_
  // schemes` combinations are supported. However, in Media Foundation,
  // encryption scheme may be dependent on video codecs, so we query the
  // encryption scheme for all supported video codecs and get the intersection
  // of the encryption schemes which work for all codecs.
  base::flat_set<EncryptionScheme> intersection(
      std::begin(kAllEncryptionSchemes), std::end(kAllEncryptionSchemes));

  for (const auto& [video_codec, codec_info] : capability.video_codecs) {
    base::flat_set<EncryptionScheme> schemes_to_query = intersection;
    base::flat_set<EncryptionScheme> supported_schemes;

    // If this codec supports cenc-clearlead (we check only for HW secure
    // CDMs), we know it supports cenc without querying and only need to query
    // for the CBCS encryption scheme.
    if (is_hw_secure && codec_info.supports_clear_lead) {
      supported_schemes.insert(EncryptionScheme::kCenc);
      schemes_to_query.erase(EncryptionScheme::kCenc);
    }

    // This stores all the schemes that are supported via the IsTypeSupportedEx
    // query. We then insert it into the supported schemes, which may or may not
    // have been populated previously depending if we are querying for clearlead
    // support on hardware secure OS CDMs.
    const auto queried_schemes = GetSupportedEncryptionSchemes(
        is_hw_secure, video_codec, is_type_supported_cb, schemes_to_query);
    supported_schemes.insert(queried_schemes.begin(), queried_schemes.end());

    intersection = base::STLSetIntersection<base::flat_set<EncryptionScheme>>(
        intersection, supported_schemes);

    // Check after every codec's intersection for encryption scheme is computed.
    // If the intersection is empty for one codec, do not loop and check
    // encryption scheme support for all other codecs, and return early.
    if (intersection.empty()) {
      // Fail if no supported encryption scheme.
      return base::unexpected(
          CdmCapabilityQueryStatus::kNoSupportedEncryptionScheme);
    }
  }

  capability.encryption_schemes = intersection;

  // IsTypeSupported does not support session type yet. So just use temporary
  // session which is required by EME spec.
  capability.session_types.insert(CdmSessionType::kTemporary);

  auto uma_name = "Media.EME.MediaFoundationService." +
                  GetKeySystemNameForUMA(key_system, is_hw_secure) +
                  ".GetCdmCapability";
  base::UmaHistogramTimes(uma_name, base::TimeTicks::Now() - start_time);

  return std::move(capability);
}

}  // namespace

MediaFoundationService::MediaFoundationService(
    mojo::PendingReceiver<mojom::MediaFoundationService> receiver)
    : receiver_(this, std::move(receiver)) {
  DVLOG(1) << __func__;
  mojo_media_client_.Initialize();
}

MediaFoundationService::~MediaFoundationService() {
  DVLOG(1) << __func__;
}

void MediaFoundationService::IsKeySystemSupported(
    const std::string& key_system,
    bool is_hw_secure,
    IsKeySystemSupportedCallback callback) {
  DVLOG(1) << __func__ << ": key_system=" << key_system;

  SCOPED_UMA_HISTOGRAM_TIMER(
      "Media.EME.MediaFoundationService.IsKeySystemSupported");

  ComPtr<IMFContentDecryptionModuleFactory> cdm_factory;
  HRESULT hresult = MediaFoundationCdmModule::GetInstance()->GetCdmFactory(
      key_system, cdm_factory);

  if (FAILED(hresult)) {
    DLOG(ERROR) << __func__
                << ": Failed to GetCdmFactory with hresult=" << hresult;
    ReportCapabilityQueryStatusHresultUMA(
        key_system, kMediaFoundationGetCdmFactoryHresultUmaPostfix, hresult);
    std::move(callback).Run(
        false,
        KeySystemCapability(
            base::unexpected(
                CdmCapabilityQueryStatus::kMediaFoundationGetCdmFactoryFailed),
            base::unexpected(CdmCapabilityQueryStatus::
                                 kMediaFoundationGetCdmFactoryFailed)));
    return;
  }

  media::ReportMediaFoundationPackageDecoderStatus();

  // `IMFContentDecryptionModuleFactory::IsTypeSupported()` returns
  // 'supported' for OS PlayReady backed implementation regardless of the
  // value passed in for the `contentType` parameter. Use
  // IMFExtendedDRMTypeSupport::IsTypeSupportedEx() instead.
  ComPtr<IMFExtendedDRMTypeSupport> mf_type_support;
  HRESULT hr =
      CoCreateInstance(CLSID_MFMediaEngineClassFactory, nullptr,
                       CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&mf_type_support));
  if (FAILED(hr)) {
    DLOG(ERROR) << __func__
                << ": Failed to create class factory for "
                   "IMFExtendedDRMTypeSupport::IsTypeSupportedEx. hr="
                << hr;
    std::move(callback).Run(
        false, KeySystemCapability(
                   base::unexpected(
                       CdmCapabilityQueryStatus::
                           kMediaFoundationGetExtendedDRMTypeSupportFailed),
                   base::unexpected(
                       CdmCapabilityQueryStatus::
                           kMediaFoundationGetExtendedDRMTypeSupportFailed)));
    return;
  }

  // Use empty software secure capability as it is not used.
  auto sw_cdm_capability_or_status =
      base::unexpected(CdmCapabilityQueryStatus::kNoSupportedVideoCodec);
  auto hw_cdm_capability_or_status = GetCdmCapability(
      cdm_factory, key_system, is_hw_secure,
      base::BindRepeating(&IsTypeSupported, mf_type_support, key_system));
  auto key_system_capability = KeySystemCapability(sw_cdm_capability_or_status,
                                                   hw_cdm_capability_or_status);
  if (!key_system_capability.sw_cdm_capability_or_status.has_value() &&
      !key_system_capability.hw_cdm_capability_or_status.has_value()) {
    DVLOG(2)
        << __func__
        << ": Get empty CdmCapability. sw_cdm_capability_or_status.error()="
        << CdmCapabilityQueryStatusToString(
               key_system_capability.sw_cdm_capability_or_status.error())
        << ", hw_cdm_capability_or_status.error()="
        << CdmCapabilityQueryStatusToString(
               key_system_capability.hw_cdm_capability_or_status.error());
    std::move(callback).Run(false, std::move(key_system_capability));
    return;
  }

  std::move(callback).Run(true, std::move(key_system_capability));
}

void MediaFoundationService::CreateInterfaceFactory(
    mojo::PendingReceiver<mojom::InterfaceFactory> receiver,
    mojo::PendingRemote<mojom::FrameInterfaceFactory> frame_interfaces) {
  DVLOG(2) << __func__;
  interface_factory_receivers_.Add(
      std::make_unique<InterfaceFactoryImpl>(std::move(frame_interfaces),
                                             &mojo_media_client_),
      std::move(receiver));
}

}  // namespace media
