media_foundation_cdm.cc 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "media/cdm/win/media_foundation_cdm.h"
  5. #include <mferror.h>
  6. #include <stdlib.h>
  7. #include <vector>
  8. #include "base/bind.h"
  9. #include "base/logging.h"
  10. #include "base/metrics/histogram_functions.h"
  11. #include "base/strings/stringprintf.h"
  12. #include "base/win/scoped_co_mem.h"
  13. #include "base/win/scoped_propvariant.h"
  14. #include "base/win/win_util.h"
  15. #include "base/win/windows_version.h"
  16. #include "media/base/cdm_promise.h"
  17. #include "media/base/win/media_foundation_cdm_proxy.h"
  18. #include "media/base/win/mf_helpers.h"
  19. #include "media/cdm/win/media_foundation_cdm_module.h"
  20. #include "media/cdm/win/media_foundation_cdm_session.h"
  21. namespace media {
  22. namespace {
  23. using Microsoft::WRL::ClassicCom;
  24. using Microsoft::WRL::ComPtr;
  25. using Microsoft::WRL::Make;
  26. using Microsoft::WRL::RuntimeClass;
  27. using Microsoft::WRL::RuntimeClassFlags;
  28. using Exception = CdmPromise::Exception;
  29. HRESULT CreatePolicySetEvent(ComPtr<IMFMediaEvent>& policy_set_event) {
  30. base::win::ScopedPropVariant policy_set_prop;
  31. PROPVARIANT* var_to_set = policy_set_prop.Receive();
  32. var_to_set->vt = VT_UI4;
  33. var_to_set->ulVal = 0;
  34. RETURN_IF_FAILED(MFCreateMediaEvent(
  35. MEPolicySet, GUID_NULL, S_OK, policy_set_prop.ptr(), &policy_set_event));
  36. return S_OK;
  37. }
  38. // Notifies the Decryptor about the last key ID so the decryptor can prefetch
  39. // the corresponding key to reduce start-to-play time when resuming playback.
  40. // This is done by sending a MEContentProtectionMetadata event.
  41. HRESULT RefreshDecryptor(IMFTransform* decryptor,
  42. const GUID& protection_system_id,
  43. const GUID& last_key_id) {
  44. // The MFT_MESSAGE_NOTIFY_START_OF_STREAM message is usually sent by the MF
  45. // pipeline when starting playback. Here we send it out-of-band as it is a
  46. // pre-requisite for getting the decryptor to process the
  47. // MEContentProtectionMetadata event.
  48. RETURN_IF_FAILED(
  49. decryptor->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0));
  50. // After receiving a MEContentProtectionMetadata event, the Decryptor
  51. // requires that it is notified of a MEPolicySet event to continue decryption.
  52. ComPtr<IMFMediaEvent> policy_set_event;
  53. RETURN_IF_FAILED(CreatePolicySetEvent(policy_set_event));
  54. RETURN_IF_FAILED(decryptor->ProcessMessage(
  55. MFT_MESSAGE_NOTIFY_EVENT,
  56. reinterpret_cast<ULONG_PTR>(policy_set_event.Get())));
  57. // Prepare the MEContentProtectionMetadata event.
  58. ComPtr<IMFMediaEvent> key_rotation_event;
  59. RETURN_IF_FAILED(MFCreateMediaEvent(MEContentProtectionMetadata, GUID_NULL,
  60. S_OK, nullptr, &key_rotation_event));
  61. // MF_EVENT_STREAM_METADATA_SYSTEMID expects the system ID (GUID) to be in
  62. // little endian order. So no need to call `ByteArrayFromGUID()`.
  63. RETURN_IF_FAILED(key_rotation_event->SetBlob(
  64. MF_EVENT_STREAM_METADATA_SYSTEMID,
  65. reinterpret_cast<const uint8_t*>(&protection_system_id), sizeof(GUID)));
  66. std::vector<uint8_t> last_key_id_byte_array = ByteArrayFromGUID(last_key_id);
  67. RETURN_IF_FAILED(key_rotation_event->SetBlob(
  68. MF_EVENT_STREAM_METADATA_CONTENT_KEYIDS, last_key_id_byte_array.data(),
  69. last_key_id_byte_array.size()));
  70. // The `dwInputStreamID` refers to a local stream ID of the Decryptor. Since
  71. // Decryptors typically only support a single stream, always pass 0 here.
  72. RETURN_IF_FAILED(
  73. decryptor->ProcessEvent(/*dwInputStreamID=*/0, key_rotation_event.Get()));
  74. return S_OK;
  75. }
  76. // The HDCP value follows the feature value in
  77. // https://docs.microsoft.com/en-us/uwp/api/windows.media.protection.protectioncapabilities.istypesupported?view=winrt-19041
  78. // - 0 (off)
  79. // - 1 (on without HDCP 2.2 Type 1 restriction)
  80. // - 2 (on with HDCP 2.2 Type 1 restriction)
  81. int GetHdcpValue(HdcpVersion hdcp_version) {
  82. switch (hdcp_version) {
  83. case HdcpVersion::kHdcpVersionNone:
  84. return 0;
  85. case HdcpVersion::kHdcpVersion1_0:
  86. case HdcpVersion::kHdcpVersion1_1:
  87. case HdcpVersion::kHdcpVersion1_2:
  88. case HdcpVersion::kHdcpVersion1_3:
  89. case HdcpVersion::kHdcpVersion1_4:
  90. case HdcpVersion::kHdcpVersion2_0:
  91. case HdcpVersion::kHdcpVersion2_1:
  92. return 1;
  93. case HdcpVersion::kHdcpVersion2_2:
  94. case HdcpVersion::kHdcpVersion2_3:
  95. return 2;
  96. }
  97. }
  98. class CdmProxyImpl : public MediaFoundationCdmProxy {
  99. public:
  100. CdmProxyImpl(ComPtr<IMFContentDecryptionModule> mf_cdm,
  101. base::RepeatingClosure hardware_context_reset_cb,
  102. MediaFoundationCdm::CdmEventCB cdm_event_cb)
  103. : mf_cdm_(mf_cdm),
  104. hardware_context_reset_cb_(std::move(hardware_context_reset_cb)),
  105. cdm_event_cb_(std::move(cdm_event_cb)) {}
  106. // MediaFoundationCdmProxy implementation
  107. HRESULT GetPMPServer(REFIID riid, LPVOID* object_result) override {
  108. DVLOG_FUNC(1);
  109. ComPtr<IMFGetService> cdm_services;
  110. RETURN_IF_FAILED(mf_cdm_.As(&cdm_services));
  111. RETURN_IF_FAILED(cdm_services->GetService(
  112. MF_CONTENTDECRYPTIONMODULE_SERVICE, riid, object_result));
  113. return S_OK;
  114. }
  115. HRESULT GetInputTrustAuthority(uint32_t stream_id,
  116. uint32_t /*stream_count*/,
  117. const uint8_t* content_init_data,
  118. uint32_t content_init_data_size,
  119. REFIID riid,
  120. IUnknown** object_out) override {
  121. DVLOG_FUNC(1);
  122. if (input_trust_authorities_.count(stream_id)) {
  123. RETURN_IF_FAILED(input_trust_authorities_[stream_id].CopyTo(object_out));
  124. return S_OK;
  125. }
  126. if (!trusted_input_) {
  127. RETURN_IF_FAILED(mf_cdm_->CreateTrustedInput(
  128. content_init_data, content_init_data_size, &trusted_input_));
  129. }
  130. // GetInputTrustAuthority takes IUnknown* as the output. Using other COM
  131. // interface will have a v-table mismatch issue.
  132. ComPtr<IUnknown> unknown;
  133. RETURN_IF_FAILED(
  134. trusted_input_->GetInputTrustAuthority(stream_id, riid, &unknown));
  135. ComPtr<IMFInputTrustAuthority> input_trust_authority;
  136. RETURN_IF_FAILED(unknown.As(&input_trust_authority));
  137. RETURN_IF_FAILED(unknown.CopyTo(object_out));
  138. // Success! Store ITA in the map.
  139. input_trust_authorities_[stream_id] = input_trust_authority;
  140. return S_OK;
  141. }
  142. HRESULT SetLastKeyId(uint32_t stream_id, REFGUID key_id) override {
  143. DVLOG_FUNC(1);
  144. last_key_ids_[stream_id] = key_id;
  145. return S_OK;
  146. }
  147. HRESULT RefreshTrustedInput() override {
  148. DVLOG_FUNC(1);
  149. // Refresh all decryptors of the last key IDs.
  150. for (const auto& entry : input_trust_authorities_) {
  151. const auto& stream_id = entry.first;
  152. const auto& input_trust_authority = entry.second;
  153. const auto& last_key_id = last_key_ids_[stream_id];
  154. if (last_key_id == GUID_NULL)
  155. continue;
  156. ComPtr<IMFTransform> decryptor;
  157. RETURN_IF_FAILED(
  158. input_trust_authority->GetDecrypter(IID_PPV_ARGS(&decryptor)));
  159. GUID protection_system_id;
  160. RETURN_IF_FAILED(GetProtectionSystemId(&protection_system_id));
  161. RETURN_IF_FAILED(
  162. RefreshDecryptor(decryptor.Get(), protection_system_id, last_key_id));
  163. }
  164. input_trust_authorities_.clear();
  165. last_key_ids_.clear();
  166. return S_OK;
  167. }
  168. HRESULT
  169. ProcessContentEnabler(IUnknown* request, IMFAsyncResult* result) override {
  170. DVLOG_FUNC(1);
  171. ComPtr<IMFContentEnabler> content_enabler;
  172. RETURN_IF_FAILED(request->QueryInterface(IID_PPV_ARGS(&content_enabler)));
  173. return mf_cdm_->SetContentEnabler(content_enabler.Get(), result);
  174. }
  175. void OnHardwareContextReset() override {
  176. // Hardware context reset happens, all the crypto sessions are in invalid
  177. // states. So drop everything here.
  178. // TODO(xhwang): Keep the `last_key_ids_` here for faster resume.
  179. trusted_input_.Reset();
  180. input_trust_authorities_.clear();
  181. last_key_ids_.clear();
  182. // Must be the last call because `this` could be destructed when running
  183. // the callback. We are not certain because `this` is ref-counted.
  184. hardware_context_reset_cb_.Run();
  185. }
  186. void OnSignificantPlayback() override {
  187. cdm_event_cb_.Run(CdmEvent::kSignificantPlayback);
  188. }
  189. void OnPlaybackError() override {
  190. cdm_event_cb_.Run(CdmEvent::kPlaybackError);
  191. }
  192. private:
  193. ~CdmProxyImpl() override = default;
  194. HRESULT GetProtectionSystemId(GUID* protection_system_id) {
  195. // Typically the CDM should only return one protection system ID. So just
  196. // use the first one if available.
  197. base::win::ScopedCoMem<GUID> protection_system_ids;
  198. DWORD count = 0;
  199. RETURN_IF_FAILED(
  200. mf_cdm_->GetProtectionSystemIds(&protection_system_ids, &count));
  201. if (count == 0)
  202. return E_FAIL;
  203. *protection_system_id = *protection_system_ids;
  204. DVLOG(2) << __func__ << " protection_system_id="
  205. << base::win::WStringFromGUID(*protection_system_id);
  206. return S_OK;
  207. }
  208. ComPtr<IMFContentDecryptionModule> mf_cdm_;
  209. // Callbacks to notify hardware context reset and playback error.
  210. base::RepeatingClosure hardware_context_reset_cb_;
  211. MediaFoundationCdm::CdmEventCB cdm_event_cb_;
  212. // Store IMFTrustedInput to avoid potential performance cost.
  213. ComPtr<IMFTrustedInput> trusted_input_;
  214. // |stream_id| to IMFInputTrustAuthority (ITA) mapping. Serves two purposes:
  215. // 1. The same ITA should always be returned in GetInputTrustAuthority() for
  216. // the same |stream_id|.
  217. // 2. The ITA must keep alive for decryptors to work.
  218. std::map<uint32_t, ComPtr<IMFInputTrustAuthority>> input_trust_authorities_;
  219. // |stream_id| to last used key ID mapping.
  220. std::map<uint32_t, GUID> last_key_ids_;
  221. };
  222. } // namespace
  223. // static
  224. bool MediaFoundationCdm::IsAvailable() {
  225. return base::win::GetVersion() >= base::win::Version::WIN10_20H1;
  226. }
  227. MediaFoundationCdm::MediaFoundationCdm(
  228. const std::string& uma_prefix,
  229. const CreateMFCdmCB& create_mf_cdm_cb,
  230. const IsTypeSupportedCB& is_type_supported_cb,
  231. const StoreClientTokenCB& store_client_token_cb,
  232. const CdmEventCB& cdm_event_cb,
  233. const SessionMessageCB& session_message_cb,
  234. const SessionClosedCB& session_closed_cb,
  235. const SessionKeysChangeCB& session_keys_change_cb,
  236. const SessionExpirationUpdateCB& session_expiration_update_cb)
  237. : uma_prefix_(uma_prefix),
  238. create_mf_cdm_cb_(create_mf_cdm_cb),
  239. is_type_supported_cb_(is_type_supported_cb),
  240. store_client_token_cb_(store_client_token_cb),
  241. cdm_event_cb_(cdm_event_cb),
  242. session_message_cb_(session_message_cb),
  243. session_closed_cb_(session_closed_cb),
  244. session_keys_change_cb_(session_keys_change_cb),
  245. session_expiration_update_cb_(session_expiration_update_cb) {
  246. DVLOG_FUNC(1);
  247. DCHECK(!uma_prefix_.empty());
  248. DCHECK(create_mf_cdm_cb_);
  249. DCHECK(is_type_supported_cb_);
  250. DCHECK(session_message_cb_);
  251. DCHECK(session_closed_cb_);
  252. DCHECK(session_keys_change_cb_);
  253. DCHECK(session_expiration_update_cb_);
  254. }
  255. MediaFoundationCdm::~MediaFoundationCdm() {
  256. DVLOG_FUNC(1);
  257. }
  258. HRESULT MediaFoundationCdm::Initialize() {
  259. HRESULT hresult = E_FAIL;
  260. ComPtr<IMFContentDecryptionModule> mf_cdm;
  261. create_mf_cdm_cb_.Run(hresult, mf_cdm);
  262. if (!mf_cdm) {
  263. DCHECK(FAILED(hresult));
  264. // Only report CdmEvent::kCdmError here as this is where most failures
  265. // happen, and other errors can be easily triggered by sites, e.g. a bad
  266. // server certificate or a bad license.
  267. OnCdmEvent(CdmEvent::kCdmError);
  268. return hresult;
  269. }
  270. mf_cdm_.Swap(mf_cdm);
  271. return S_OK;
  272. }
  273. void MediaFoundationCdm::SetServerCertificate(
  274. const std::vector<uint8_t>& certificate,
  275. std::unique_ptr<SimpleCdmPromise> promise) {
  276. DVLOG_FUNC(1);
  277. if (!mf_cdm_) {
  278. promise->reject(Exception::INVALID_STATE_ERROR, 0, "CDM Unavailable");
  279. return;
  280. }
  281. auto hr =
  282. mf_cdm_->SetServerCertificate(certificate.data(), certificate.size());
  283. base::UmaHistogramSparse(uma_prefix_ + "SetServerCertificate", hr);
  284. if (FAILED(hr)) {
  285. promise->reject(Exception::NOT_SUPPORTED_ERROR, 0, "Failed to set cert");
  286. return;
  287. }
  288. promise->resolve();
  289. }
  290. void MediaFoundationCdm::GetStatusForPolicy(
  291. HdcpVersion min_hdcp_version,
  292. std::unique_ptr<KeyStatusCdmPromise> promise) {
  293. if (!mf_cdm_) {
  294. promise->reject(Exception::INVALID_STATE_ERROR, 0, "CDM Unavailable");
  295. return;
  296. }
  297. // Keys should be always usable when there is no HDCP requirement.
  298. if (min_hdcp_version == HdcpVersion::kHdcpVersionNone) {
  299. promise->resolve(CdmKeyInformation::KeyStatus::USABLE);
  300. return;
  301. }
  302. // HDCP is independent to the codec. So query H.264, which is always supported
  303. // by MFCDM.
  304. const std::string content_type =
  305. base::StringPrintf("video/mp4;codecs=\"avc1\";features=\"hdcp=%d\"",
  306. GetHdcpValue(min_hdcp_version));
  307. is_type_supported_cb_.Run(
  308. content_type,
  309. base::BindOnce(&MediaFoundationCdm::OnIsTypeSupportedResult,
  310. weak_factory_.GetWeakPtr(), std::move(promise)));
  311. }
  312. void MediaFoundationCdm::CreateSessionAndGenerateRequest(
  313. CdmSessionType session_type,
  314. EmeInitDataType init_data_type,
  315. const std::vector<uint8_t>& init_data,
  316. std::unique_ptr<NewSessionCdmPromise> promise) {
  317. DVLOG_FUNC(1);
  318. if (!mf_cdm_) {
  319. promise->reject(Exception::INVALID_STATE_ERROR, 0, "CDM Unavailable");
  320. return;
  321. }
  322. // TODO(xhwang): Implement session expiration update.
  323. auto session = std::make_unique<MediaFoundationCdmSession>(
  324. uma_prefix_, session_message_cb_, session_keys_change_cb_,
  325. session_expiration_update_cb_);
  326. if (FAILED(session->Initialize(mf_cdm_.Get(), session_type))) {
  327. promise->reject(Exception::INVALID_STATE_ERROR, 0,
  328. "Failed to create session");
  329. return;
  330. }
  331. int session_token = next_session_token_++;
  332. // Keep a raw pointer since the |promise| will be moved to the callback.
  333. // Use base::Unretained() is safe because |session| is owned by |this|.
  334. auto* raw_promise = promise.get();
  335. auto session_id_cb =
  336. base::BindOnce(&MediaFoundationCdm::OnSessionId, base::Unretained(this),
  337. session_token, std::move(promise));
  338. if (FAILED(session->GenerateRequest(init_data_type, init_data,
  339. std::move(session_id_cb)))) {
  340. raw_promise->reject(Exception::INVALID_STATE_ERROR, 0, "Init failure");
  341. return;
  342. }
  343. pending_sessions_.emplace(session_token, std::move(session));
  344. }
  345. void MediaFoundationCdm::LoadSession(
  346. CdmSessionType session_type,
  347. const std::string& session_id,
  348. std::unique_ptr<NewSessionCdmPromise> promise) {
  349. DVLOG_FUNC(1);
  350. if (!mf_cdm_) {
  351. promise->reject(Exception::INVALID_STATE_ERROR, 0, "CDM Unavailable");
  352. return;
  353. }
  354. NOTIMPLEMENTED();
  355. promise->reject(Exception::NOT_SUPPORTED_ERROR, 0, "Load not supported");
  356. }
  357. void MediaFoundationCdm::UpdateSession(
  358. const std::string& session_id,
  359. const std::vector<uint8_t>& response,
  360. std::unique_ptr<SimpleCdmPromise> promise) {
  361. DVLOG_FUNC(1);
  362. if (!mf_cdm_) {
  363. promise->reject(Exception::INVALID_STATE_ERROR, 0, "CDM Unavailable");
  364. return;
  365. }
  366. auto* session = GetSession(session_id);
  367. if (!session) {
  368. promise->reject(Exception::INVALID_STATE_ERROR, 0, "Session not found");
  369. return;
  370. }
  371. if (FAILED(session->Update(response))) {
  372. promise->reject(Exception::INVALID_STATE_ERROR, 0, "Update failed");
  373. return;
  374. }
  375. // Failure to store the client token will not prevent the CDM from correctly
  376. // functioning.
  377. StoreClientTokenIfNeeded();
  378. promise->resolve();
  379. }
  380. void MediaFoundationCdm::CloseSession(
  381. const std::string& session_id,
  382. std::unique_ptr<SimpleCdmPromise> promise) {
  383. DVLOG_FUNC(1);
  384. CloseSessionInternal(session_id, CdmSessionClosedReason::kClose,
  385. std::move(promise));
  386. }
  387. void MediaFoundationCdm::RemoveSession(
  388. const std::string& session_id,
  389. std::unique_ptr<SimpleCdmPromise> promise) {
  390. DVLOG_FUNC(1);
  391. if (!mf_cdm_) {
  392. promise->reject(Exception::INVALID_STATE_ERROR, 0, "CDM Unavailable");
  393. return;
  394. }
  395. auto* session = GetSession(session_id);
  396. if (!session) {
  397. promise->reject(Exception::INVALID_STATE_ERROR, 0, "Session not found");
  398. return;
  399. }
  400. if (FAILED(session->Remove())) {
  401. promise->reject(Exception::INVALID_STATE_ERROR, 0, "Remove failed");
  402. return;
  403. }
  404. promise->resolve();
  405. }
  406. CdmContext* MediaFoundationCdm::GetCdmContext() {
  407. return this;
  408. }
  409. bool MediaFoundationCdm::RequiresMediaFoundationRenderer() {
  410. return true;
  411. }
  412. scoped_refptr<MediaFoundationCdmProxy>
  413. MediaFoundationCdm::GetMediaFoundationCdmProxy() {
  414. DVLOG_FUNC(1);
  415. if (!mf_cdm_) {
  416. DLOG(ERROR) << __func__ << ": Invalid state with null `mf_cdm_`";
  417. return nullptr;
  418. }
  419. if (!cdm_proxy_) {
  420. cdm_proxy_ = base::MakeRefCounted<CdmProxyImpl>(
  421. mf_cdm_,
  422. base::BindRepeating(&MediaFoundationCdm::OnHardwareContextReset,
  423. weak_factory_.GetWeakPtr()),
  424. base::BindRepeating(&MediaFoundationCdm::OnCdmEvent,
  425. weak_factory_.GetWeakPtr()));
  426. }
  427. return cdm_proxy_;
  428. }
  429. bool MediaFoundationCdm::OnSessionId(
  430. int session_token,
  431. std::unique_ptr<NewSessionCdmPromise> promise,
  432. const std::string& session_id) {
  433. DVLOG_FUNC(1) << "session_token=" << session_token
  434. << ", session_id=" << session_id;
  435. auto itr = pending_sessions_.find(session_token);
  436. DCHECK(itr != pending_sessions_.end());
  437. auto session = std::move(itr->second);
  438. DCHECK(session);
  439. pending_sessions_.erase(itr);
  440. if (session_id.empty() || sessions_.count(session_id)) {
  441. promise->reject(Exception::INVALID_STATE_ERROR, 0,
  442. "Empty or duplicate session ID");
  443. return false;
  444. }
  445. sessions_.emplace(session_id, std::move(session));
  446. promise->resolve(session_id);
  447. return true;
  448. }
  449. MediaFoundationCdmSession* MediaFoundationCdm::GetSession(
  450. const std::string& session_id) {
  451. auto itr = sessions_.find(session_id);
  452. if (itr == sessions_.end())
  453. return nullptr;
  454. return itr->second.get();
  455. }
  456. void MediaFoundationCdm::CloseSessionInternal(
  457. const std::string& session_id,
  458. CdmSessionClosedReason reason,
  459. std::unique_ptr<SimpleCdmPromise> promise) {
  460. DVLOG_FUNC(1);
  461. if (!mf_cdm_) {
  462. promise->reject(Exception::INVALID_STATE_ERROR, 0, "CDM Unavailable");
  463. return;
  464. }
  465. // Validate that this is a reference to an open session. close() shouldn't
  466. // be called if the session is already closed. However, the operation is
  467. // asynchronous, so there is a window where close() was called a second time
  468. // just before the closed event arrives. As a result it is possible that the
  469. // session is already closed, so assume that the session is closed if it
  470. // doesn't exist. https://github.com/w3c/encrypted-media/issues/365.
  471. //
  472. // close() is called from a MediaKeySession object, so it is unlikely that
  473. // this method will be called with a previously unseen |session_id|.
  474. auto* session = GetSession(session_id);
  475. if (!session) {
  476. promise->resolve();
  477. return;
  478. }
  479. if (FAILED(session->Close())) {
  480. sessions_.erase(session_id);
  481. promise->reject(Exception::INVALID_STATE_ERROR, 0, "Close failed");
  482. return;
  483. }
  484. // EME requires running session closed algorithm before resolving the
  485. // promise.
  486. sessions_.erase(session_id);
  487. session_closed_cb_.Run(session_id, reason);
  488. promise->resolve();
  489. }
  490. // When hardware context is reset, all sessions are in a bad state. Close all
  491. // the sessions and hopefully the player will create new sessions to resume.
  492. void MediaFoundationCdm::OnHardwareContextReset() {
  493. DVLOG_FUNC(1);
  494. // Collect all the session IDs to avoid iterating the map while we delete
  495. // entries in the map (in `CloseSession()`).
  496. std::vector<std::string> session_ids;
  497. for (const auto& s : sessions_)
  498. session_ids.push_back(s.first);
  499. for (const auto& session_id : session_ids) {
  500. CloseSessionInternal(session_id,
  501. CdmSessionClosedReason::kHardwareContextReset,
  502. std::make_unique<DoNothingCdmPromise<>>());
  503. }
  504. cdm_proxy_.reset();
  505. // Reset IMFContentDecryptionModule which also holds the old ITA.
  506. mf_cdm_.Reset();
  507. // Recreates IMFContentDecryptionModule so we can create new sessions.
  508. if (FAILED(Initialize())) {
  509. DLOG(ERROR) << __func__ << ": Re-initialization failed";
  510. DCHECK(!mf_cdm_);
  511. }
  512. }
  513. void MediaFoundationCdm::OnCdmEvent(CdmEvent event) {
  514. DVLOG_FUNC(1);
  515. cdm_event_cb_.Run(event);
  516. }
  517. void MediaFoundationCdm::OnIsTypeSupportedResult(
  518. std::unique_ptr<KeyStatusCdmPromise> promise,
  519. bool is_supported) {
  520. if (is_supported) {
  521. promise->resolve(CdmKeyInformation::KeyStatus::USABLE);
  522. } else {
  523. promise->resolve(CdmKeyInformation::KeyStatus::OUTPUT_RESTRICTED);
  524. }
  525. }
  526. void MediaFoundationCdm::StoreClientTokenIfNeeded() {
  527. DVLOG_FUNC(1);
  528. ComPtr<IMFAttributes> attributes;
  529. if (FAILED(mf_cdm_.As(&attributes))) {
  530. DLOG(ERROR) << "Failed to access the CDM's IMFAttribute store";
  531. return;
  532. }
  533. base::win::ScopedCoMem<uint8_t> client_token;
  534. uint32_t client_token_size;
  535. HRESULT hr = attributes->GetAllocatedBlob(
  536. EME_CONTENTDECRYPTIONMODULE_CLIENT_TOKEN.fmtid, &client_token,
  537. &client_token_size);
  538. if (FAILED(hr)) {
  539. if (hr != MF_E_ATTRIBUTENOTFOUND)
  540. DLOG(ERROR) << "Failed to get the client token blob. hr=" << hr;
  541. return;
  542. }
  543. DVLOG(2) << "Got client token of size " << client_token_size;
  544. std::vector<uint8_t> client_token_vector;
  545. client_token_vector.assign(client_token.get(),
  546. client_token.get() + client_token_size);
  547. // The store operation is cross-process so only run it if we have a new
  548. // client token.
  549. if (client_token_vector == cached_client_token_)
  550. return;
  551. cached_client_token_ = client_token_vector;
  552. store_client_token_cb_.Run(cached_client_token_);
  553. }
  554. } // namespace media