#pragma once
#if ENABLE(ENCRYPTED_MEDIA)
#include "CDMInstance.h"
#include "CDMInstanceSession.h"
#include "MediaPlayerPrivate.h"
#include "SharedBuffer.h"
#include <wtf/BoxPtr.h>
#include <wtf/Condition.h>
#include <wtf/VectorHash.h>
#include <wtf/WeakHashSet.h>
#if ENABLE(THUNDER)
#include "CDMOpenCDMTypes.h"
#endif
namespace WebCore {
using KeyIDType = Vector<uint8_t>;
using KeyHandleValueVariant = Variant<
Vector<uint8_t>
#if ENABLE(THUNDER)
, BoxPtr<OpenCDMSession>
#endif
>;
class KeyHandle : public ThreadSafeRefCounted<KeyHandle> {
public:
using KeyStatus = CDMInstanceSession::KeyStatus;
static RefPtr<KeyHandle> create(KeyStatus status, KeyIDType&& keyID, KeyHandleValueVariant&& keyHandleValue)
{
return adoptRef(*new KeyHandle(status, WTFMove(keyID), WTFMove(keyHandleValue)));
}
Ref<SharedBuffer> idAsSharedBuffer() const { return SharedBuffer::create(m_id.data(), m_id.size()); }
bool takeValueIfDifferent(KeyHandleValueVariant&&);
const KeyIDType& id() const { return m_id; }
const KeyHandleValueVariant& value() const { return m_value; }
KeyHandleValueVariant& value() { return m_value; }
KeyStatus status() const { return m_status; }
bool isStatusCurrentlyValid()
{
return m_status == CDMInstanceSession::KeyStatus::Usable || m_status == CDMInstanceSession::KeyStatus::OutputRestricted
|| m_status == CDMInstanceSession::KeyStatus::OutputDownscaled;
}
String idAsString() const;
friend bool operator==(const KeyHandle &k1, const KeyHandle &k2) { return k1.m_id == k2.m_id; }
friend bool operator==(const KeyHandle &k, const KeyIDType& keyID) { return k.m_id == keyID; }
friend bool operator==(const KeyIDType& keyID, const KeyHandle &k) { return k == keyID; }
friend bool operator<(const KeyHandle& k1, const KeyHandle& k2)
{
int isDifference = memcmp(k1.m_id.data(), k2.m_id.data(), std::min(k1.m_id.size(), k2.m_id.size()));
if (isDifference)
return isDifference < 0;
return k1.m_id.size() < k2.m_id.size();
}
private:
void addSessionReference() { ASSERT(isMainThread()); m_numSessionReferences++; }
void removeSessionReference() { ASSERT(isMainThread()); m_numSessionReferences--; }
unsigned numSessionReferences() const { ASSERT(isMainThread()); return m_numSessionReferences; }
friend class KeyStore;
KeyHandle(KeyStatus status, KeyIDType&& keyID, KeyHandleValueVariant&& keyHandleValue)
: m_status(status), m_id(WTFMove(keyID)), m_value(WTFMove(keyHandleValue)) { }
KeyStatus m_status;
KeyIDType m_id;
KeyHandleValueVariant m_value;
unsigned m_numSessionReferences { 0 };
};
class KeyStore {
public:
using KeyStatusVector = CDMInstanceSession::KeyStatusVector;
KeyStore() = default;
bool containsKeyID(const KeyIDType&) const;
void merge(const KeyStore&);
void removeAllKeysFrom(const KeyStore&);
void removeAllKeys() { m_keys.clear(); }
bool addKeys(Vector<RefPtr<KeyHandle>>&&);
bool add(RefPtr<KeyHandle>&&);
bool remove(const RefPtr<KeyHandle>&);
bool hasKeys() const { return m_keys.size(); }
unsigned numKeys() const { return m_keys.size(); }
const RefPtr<KeyHandle>& keyHandle(const KeyIDType&) const;
KeyStatusVector allKeysAs(CDMInstanceSession::KeyStatus) const;
KeyStatusVector convertToJSKeyStatusVector() const;
bool isEmpty() const { return m_keys.isEmpty(); }
auto begin() { return m_keys.begin(); }
auto begin() const { return m_keys.begin(); }
auto end() { return m_keys.end(); }
auto end() const { return m_keys.end(); }
auto rbegin() { return m_keys.rbegin(); }
auto rbegin() const { return m_keys.rbegin(); }
auto rend() { return m_keys.rend(); }
auto rend() const { return m_keys.rend(); }
private:
Vector<RefPtr<KeyHandle>> m_keys;
};
class CDMInstanceProxy;
class CDMProxy : public ThreadSafeRefCounted<CDMProxy> {
public:
static constexpr Seconds MaxKeyWaitTimeSeconds = 7_s;
virtual ~CDMProxy() = default;
void updateKeyStore(const KeyStore& newKeyStore);
void setInstance(CDMInstanceProxy*);
virtual void releaseDecryptionResources()
{
ASSERT(isMainThread());
m_keyStore.removeAllKeys();
}
protected:
RefPtr<KeyHandle> keyHandle(const KeyIDType&) const;
bool keyAvailable(const KeyIDType&) const;
bool keyAvailableUnlocked(const KeyIDType&) const;
Optional<Ref<KeyHandle>> tryWaitForKeyHandle(const KeyIDType&) const;
Optional<Ref<KeyHandle>> getOrWaitForKeyHandle(const KeyIDType&) const;
Optional<KeyHandleValueVariant> getOrWaitForKeyValue(const KeyIDType&) const;
void startedWaitingForKey() const;
void stoppedWaitingForKey() const;
const CDMInstanceProxy* instance() const { return m_instance; }
private:
mutable Lock m_instanceMutex;
CDMInstanceProxy* m_instance;
mutable Lock m_keysMutex;
mutable Condition m_keysCondition;
KeyStore m_keyStore;
};
class CDMProxyFactory {
public:
virtual ~CDMProxyFactory()
{
unregisterFactory(*this);
};
WEBCORE_EXPORT static void registerFactory(CDMProxyFactory&);
WEBCORE_EXPORT static void unregisterFactory(CDMProxyFactory&);
WEBCORE_EXPORT static WARN_UNUSED_RETURN RefPtr<CDMProxy> createCDMProxyForKeySystem(const String&);
protected:
virtual RefPtr<CDMProxy> createCDMProxy(const String&) = 0;
virtual bool supportsKeySystem(const String&) = 0;
private:
static Vector<CDMProxyFactory*> platformRegisterFactories();
WEBCORE_EXPORT static Vector<CDMProxyFactory*>& registeredFactories();
};
class CDMInstanceProxy;
class CDMInstanceSessionProxy : public CDMInstanceSession, public CanMakeWeakPtr<CDMInstanceSessionProxy, WeakPtrFactoryInitialization::Eager> {
public:
virtual void releaseDecryptionResources() { m_instance.clear(); }
void removeFromInstanceProxy();
protected:
CDMInstanceSessionProxy(CDMInstanceProxy&);
const WeakPtr<CDMInstanceProxy>& cdmInstanceProxy() const { return m_instance; }
private:
WeakPtr<CDMInstanceProxy> m_instance;
};
class CDMInstanceProxy : public CDMInstance, public CanMakeWeakPtr<CDMInstanceProxy> {
public:
explicit CDMInstanceProxy(const String& keySystem)
{
ASSERT(isMainThread());
m_cdmProxy = CDMProxyFactory::createCDMProxyForKeySystem(keySystem);
if (m_cdmProxy)
m_cdmProxy->setInstance(this);
}
virtual ~CDMInstanceProxy() = default;
void mergeKeysFrom(const KeyStore&);
void removeAllKeysFrom(const KeyStore&);
const RefPtr<CDMProxy>& proxy() const { ASSERT(isMainThread()); return m_cdmProxy; }
virtual bool isWaitingForKey() const { ASSERT(isMainThread()); return m_numDecryptorsWaitingForKey > 0; }
void setPlayer(MediaPlayer* player) { ASSERT(isMainThread()); m_player = player; }
void startedWaitingForKey();
void stoppedWaitingForKey();
void removeSession(const CDMInstanceSessionProxy& session) { m_sessions.remove(session); }
virtual void releaseDecryptionResources()
{
ASSERT(isMainThread());
m_keyStore.removeAllKeys();
for (auto& session : m_sessions)
session.releaseDecryptionResources();
m_sessions.clear();
if (m_cdmProxy) {
m_cdmProxy->releaseDecryptionResources();
m_cdmProxy = nullptr;
}
}
protected:
void trackSession(const CDMInstanceSessionProxy&);
private:
RefPtr<CDMProxy> m_cdmProxy;
MediaPlayer* m_player { nullptr };
std::atomic<int> m_numDecryptorsWaitingForKey { 0 };
WeakHashSet<CDMInstanceSessionProxy> m_sessions;
KeyStore m_keyStore;
};
}
#endif // ENABLE(ENCRYPTED_MEDIA)