diff options
Diffstat (limited to 'dom/media/platforms/wmf/MFCDMSession.cpp')
-rw-r--r-- | dom/media/platforms/wmf/MFCDMSession.cpp | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/dom/media/platforms/wmf/MFCDMSession.cpp b/dom/media/platforms/wmf/MFCDMSession.cpp new file mode 100644 index 0000000000..d693c5a731 --- /dev/null +++ b/dom/media/platforms/wmf/MFCDMSession.cpp @@ -0,0 +1,314 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MFCDMSession.h" + +#include <limits> +#include <vcruntime.h> +#include <winerror.h> + +#include "MFMediaEngineUtils.h" +#include "GMPUtils.h" // ToHexString +#include "mozilla/EMEUtils.h" +#include "mozilla/dom/MediaKeyMessageEventBinding.h" +#include "mozilla/dom/MediaKeyStatusMapBinding.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +using Microsoft::WRL::ComPtr; +using Microsoft::WRL::MakeAndInitialize; + +#define LOG(msg, ...) EME_LOG("MFCDMSession=%p, " msg, this, ##__VA_ARGS__) + +static inline MF_MEDIAKEYSESSION_TYPE ConvertSessionType( + KeySystemConfig::SessionType aType) { + switch (aType) { + case KeySystemConfig::SessionType::Temporary: + return MF_MEDIAKEYSESSION_TYPE_TEMPORARY; + case KeySystemConfig::SessionType::PersistentLicense: + return MF_MEDIAKEYSESSION_TYPE_PERSISTENT_LICENSE; + } +} + +static inline LPCWSTR InitDataTypeToString(const nsAString& aInitDataType) { + // The strings are defined in https://www.w3.org/TR/eme-initdata-registry/ + if (aInitDataType.EqualsLiteral("webm")) { + return L"webm"; + } else if (aInitDataType.EqualsLiteral("cenc")) { + return L"cenc"; + } else if (aInitDataType.EqualsLiteral("keyids")) { + return L"keyids"; + } else { + return L"unknown"; + } +} + +// The callback interface which IMFContentDecryptionModuleSession uses for +// communicating with the session. +class MFCDMSession::SessionCallbacks final + : public Microsoft::WRL::RuntimeClass< + Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, + IMFContentDecryptionModuleSessionCallbacks> { + public: + SessionCallbacks() = default; + SessionCallbacks(const SessionCallbacks&) = delete; + SessionCallbacks& operator=(const SessionCallbacks&) = delete; + ~SessionCallbacks() = default; + + HRESULT RuntimeClassInitialize() { return S_OK; } + + // IMFContentDecryptionModuleSessionCallbacks + STDMETHODIMP KeyMessage(MF_MEDIAKEYSESSION_MESSAGETYPE aType, + const BYTE* aMessage, DWORD aMessageSize, + LPCWSTR aUrl) final { + CopyableTArray<uint8_t> msg{static_cast<const uint8_t*>(aMessage), + aMessageSize}; + mKeyMessageEvent.Notify(aType, std::move(msg)); + return S_OK; + } + + STDMETHODIMP KeyStatusChanged() final { + mKeyChangeEvent.Notify(); + return S_OK; + } + + MediaEventSource<MF_MEDIAKEYSESSION_MESSAGETYPE, nsTArray<uint8_t>>& + KeyMessageEvent() { + return mKeyMessageEvent; + } + MediaEventSource<void>& KeyChangeEvent() { return mKeyChangeEvent; } + + private: + MediaEventProducer<MF_MEDIAKEYSESSION_MESSAGETYPE, nsTArray<uint8_t>> + mKeyMessageEvent; + MediaEventProducer<void> mKeyChangeEvent; +}; + +/* static*/ +MFCDMSession* MFCDMSession::Create(KeySystemConfig::SessionType aSessionType, + IMFContentDecryptionModule* aCdm, + nsISerialEventTarget* aManagerThread) { + MOZ_ASSERT(aCdm); + MOZ_ASSERT(aManagerThread); + ComPtr<SessionCallbacks> callbacks; + RETURN_PARAM_IF_FAILED(MakeAndInitialize<SessionCallbacks>(&callbacks), + nullptr); + + ComPtr<IMFContentDecryptionModuleSession> session; + RETURN_PARAM_IF_FAILED(aCdm->CreateSession(ConvertSessionType(aSessionType), + callbacks.Get(), &session), + nullptr); + return new MFCDMSession(session.Get(), callbacks.Get(), aManagerThread); +} + +MFCDMSession::MFCDMSession(IMFContentDecryptionModuleSession* aSession, + SessionCallbacks* aCallback, + nsISerialEventTarget* aManagerThread) + : mSession(aSession), + mManagerThread(aManagerThread), + mExpiredTimeMilliSecondsSinceEpoch( + std::numeric_limits<double>::quiet_NaN()) { + MOZ_ASSERT(aSession); + MOZ_ASSERT(aCallback); + MOZ_ASSERT(aManagerThread); + mKeyMessageListener = aCallback->KeyMessageEvent().Connect( + mManagerThread, this, &MFCDMSession::OnSessionKeyMessage); + mKeyChangeListener = aCallback->KeyChangeEvent().Connect( + mManagerThread, this, &MFCDMSession::OnSessionKeysChange); +} + +MFCDMSession::~MFCDMSession() { + // TODO : maybe disconnect them in `Close()`? + mKeyChangeListener.DisconnectIfExists(); + mKeyMessageListener.DisconnectIfExists(); +} + +HRESULT MFCDMSession::GenerateRequest(const nsAString& aInitDataType, + const uint8_t* aInitData, + uint32_t aInitDataSize) { + AssertOnManagerThread(); + LOG("GenerateRequest for %s (init sz=%u)", + NS_ConvertUTF16toUTF8(aInitDataType).get(), aInitDataSize); + RETURN_IF_FAILED(mSession->GenerateRequest( + InitDataTypeToString(aInitDataType), aInitData, aInitDataSize)); + Unused << RetrieveSessionId(); + return S_OK; +} + +HRESULT MFCDMSession::Load(const nsAString& aSessionId) { + AssertOnManagerThread(); + // TODO : do we need to implement this? Chromium doesn't implement this one. + // Also, how do we know is this given session ID is equal to the session Id + // asked from CDM session or not? + BOOL rv = FALSE; + mSession->Load(char16ptr_t(aSessionId.BeginReading()), &rv); + LOG("Load, id=%s, rv=%s", NS_ConvertUTF16toUTF8(aSessionId).get(), + rv ? "success" : "fail"); + return rv ? S_OK : S_FALSE; +} + +HRESULT MFCDMSession::Update(const nsTArray<uint8_t>& aMessage) { + AssertOnManagerThread(); + LOG("Update"); + RETURN_IF_FAILED(mSession->Update( + static_cast<const BYTE*>(aMessage.Elements()), aMessage.Length())); + RETURN_IF_FAILED(UpdateExpirationIfNeeded()); + return S_OK; +} + +HRESULT MFCDMSession::Close() { + AssertOnManagerThread(); + LOG("Close"); + RETURN_IF_FAILED(mSession->Close()); + return S_OK; +} + +HRESULT MFCDMSession::Remove() { + AssertOnManagerThread(); + LOG("Remove"); + RETURN_IF_FAILED(mSession->Remove()); + RETURN_IF_FAILED(UpdateExpirationIfNeeded()); + return S_OK; +} + +bool MFCDMSession::RetrieveSessionId() { + AssertOnManagerThread(); + if (mSessionId) { + return true; + } + ScopedCoMem<wchar_t> sessionId; + if (FAILED(mSession->GetSessionId(&sessionId)) || !sessionId) { + LOG("Can't get session id or empty session ID!"); + return false; + } + LOG("Set session Id %ls", sessionId.Get()); + mSessionId = Some(sessionId.Get()); + return true; +} + +void MFCDMSession::OnSessionKeysChange() { + AssertOnManagerThread(); + LOG("OnSessionKeysChange"); + + if (!mSessionId) { + LOG("Unexpected session keys change ignored"); + return; + } + + ScopedCoMem<MFMediaKeyStatus> keyStatuses; + UINT count = 0; + RETURN_VOID_IF_FAILED(mSession->GetKeyStatuses(&keyStatuses, &count)); + + static auto ToMediaKeyStatus = [](MF_MEDIAKEY_STATUS aStatus) { + // https://learn.microsoft.com/en-us/windows/win32/api/mfidl/ne-mfidl-mf_mediakey_status + switch (aStatus) { + case MF_MEDIAKEY_STATUS_USABLE: + return dom::MediaKeyStatus::Usable; + case MF_MEDIAKEY_STATUS_EXPIRED: + return dom::MediaKeyStatus::Expired; + case MF_MEDIAKEY_STATUS_OUTPUT_DOWNSCALED: + return dom::MediaKeyStatus::Output_downscaled; + // This is for legacy use and should not happen in normal cases. Map it to + // internal error in case it happens. + case MF_MEDIAKEY_STATUS_OUTPUT_NOT_ALLOWED: + return dom::MediaKeyStatus::Internal_error; + case MF_MEDIAKEY_STATUS_STATUS_PENDING: + return dom::MediaKeyStatus::Status_pending; + case MF_MEDIAKEY_STATUS_INTERNAL_ERROR: + return dom::MediaKeyStatus::Internal_error; + case MF_MEDIAKEY_STATUS_RELEASED: + return dom::MediaKeyStatus::Released; + case MF_MEDIAKEY_STATUS_OUTPUT_RESTRICTED: + return dom::MediaKeyStatus::Output_restricted; + } + MOZ_ASSERT_UNREACHABLE("Invalid MF_MEDIAKEY_STATUS enum value"); + return dom::MediaKeyStatus::Internal_error; + }; + + CopyableTArray<MFCDMKeyInformation> keyInfos; + for (uint32_t idx = 0; idx < count; idx++) { + const MFMediaKeyStatus& keyStatus = keyStatuses[idx]; + if (keyStatus.cbKeyId != sizeof(GUID)) { + LOG("Key ID with unsupported size ignored"); + continue; + } + CopyableTArray<uint8_t> keyId; + ByteArrayFromGUID(reinterpret_cast<REFGUID>(keyStatus.pbKeyId), keyId); + + nsAutoCString keyIdString(ToHexString(keyId)); + LOG("Append keyid-sz=%u, keyid=%s, status=%s", keyStatus.cbKeyId, + keyIdString.get(), + ToMediaKeyStatusStr(ToMediaKeyStatus(keyStatus.eMediaKeyStatus))); + keyInfos.AppendElement(MFCDMKeyInformation{ + std::move(keyId), ToMediaKeyStatus(keyStatus.eMediaKeyStatus)}); + } + LOG("Notify 'keychange' for %s", NS_ConvertUTF16toUTF8(*mSessionId).get()); + mKeyChangeEvent.Notify( + MFCDMKeyStatusChange{*mSessionId, std::move(keyInfos)}); + + // ScopedCoMem<MFMediaKeyStatus> only releases memory for |keyStatuses|. We + // need to manually release memory for |pbKeyId| here. + for (size_t idx = 0; idx < count; idx++) { + if (const auto& keyStatus = keyStatuses[idx]; keyStatus.pbKeyId) { + CoTaskMemFree(keyStatus.pbKeyId); + } + } +} + +HRESULT MFCDMSession::UpdateExpirationIfNeeded() { + AssertOnManagerThread(); + MOZ_ASSERT(mSessionId); + + // The msdn document doesn't mention the unit for the expiration time, + // follow chromium's implementation to treat them as millisecond. + double newExpiredEpochTimeMs = 0.0; + RETURN_IF_FAILED(mSession->GetExpiration(&newExpiredEpochTimeMs)); + + if (newExpiredEpochTimeMs == mExpiredTimeMilliSecondsSinceEpoch || + (std::isnan(newExpiredEpochTimeMs) && + std::isnan(mExpiredTimeMilliSecondsSinceEpoch))) { + return S_OK; + } + + LOG("Session expiration change from %f to %f, notify 'expiration' for %s", + mExpiredTimeMilliSecondsSinceEpoch, newExpiredEpochTimeMs, + NS_ConvertUTF16toUTF8(*mSessionId).get()); + mExpiredTimeMilliSecondsSinceEpoch = newExpiredEpochTimeMs; + mExpirationEvent.Notify( + MFCDMKeyExpiration{*mSessionId, mExpiredTimeMilliSecondsSinceEpoch}); + return S_OK; +} + +void MFCDMSession::OnSessionKeyMessage( + const MF_MEDIAKEYSESSION_MESSAGETYPE& aType, + const nsTArray<uint8_t>& aMessage) { + AssertOnManagerThread(); + // Only send key message after the session Id is ready. + if (!RetrieveSessionId()) { + return; + } + static auto ToMediaKeyMessageType = [](MF_MEDIAKEYSESSION_MESSAGETYPE aType) { + switch (aType) { + case MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_REQUEST: + return dom::MediaKeyMessageType::License_request; + case MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_RENEWAL: + return dom::MediaKeyMessageType::License_renewal; + case MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_RELEASE: + return dom::MediaKeyMessageType::License_release; + case MF_MEDIAKEYSESSION_MESSAGETYPE_INDIVIDUALIZATION_REQUEST: + return dom::MediaKeyMessageType::Individualization_request; + default: + MOZ_ASSERT_UNREACHABLE("Unknown session message type"); + return dom::MediaKeyMessageType::EndGuard_; + } + }; + LOG("Notify 'keymessage' for %s", NS_ConvertUTF16toUTF8(*mSessionId).get()); + mKeyMessageEvent.Notify(MFCDMKeyMessage{ + *mSessionId, ToMediaKeyMessageType(aType), std::move(aMessage)}); +} + +#undef LOG + +} // namespace mozilla |