Commit 05c95bb1 authored by Alex St-Onge's avatar Alex St-Onge Committed by Commit Bot

Add browser test for EME persistent-usage-record session

This change adds a test to exercice the persistent-usage-record path
for EME. The new test create a PUR session and wait for the video to
end and verify that the license-release message contain the usage
record.

Bug: 1091502
Change-Id: I2d6347f39981d9eb1025d37ced254eb6aae766b1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2285250Reviewed-by: default avatarJohn Rummell <jrummell@chromium.org>
Reviewed-by: default avatarXiaohan Wang <xhwang@chromium.org>
Commit-Queue: Alex St-Onge <alstonge@chromium.org>
Cr-Commit-Position: refs/heads/master@{#788870}
parent 3e6d2626
...@@ -79,6 +79,7 @@ const char kExternalClearKeyStorageIdTestKeySystem[] = ...@@ -79,6 +79,7 @@ const char kExternalClearKeyStorageIdTestKeySystem[] =
const char kNoSessionToLoad[] = ""; const char kNoSessionToLoad[] = "";
#if BUILDFLAG(ENABLE_LIBRARY_CDMS) #if BUILDFLAG(ENABLE_LIBRARY_CDMS)
const char kPersistentLicense[] = "PersistentLicense"; const char kPersistentLicense[] = "PersistentLicense";
const char kPersistentUsageRecord[] = "PersistentUsageRecord";
const char kUnknownSession[] = "UnknownSession"; const char kUnknownSession[] = "UnknownSession";
#endif #endif
...@@ -354,6 +355,9 @@ class ECKEncryptedMediaTest : public EncryptedMediaTestBase, ...@@ -354,6 +355,9 @@ class ECKEncryptedMediaTest : public EncryptedMediaTestBase,
command_line->AppendSwitchASCII( command_line->AppendSwitchASCII(
switches::kOverrideEnabledCdmInterfaceVersion, switches::kOverrideEnabledCdmInterfaceVersion,
base::NumberToString(GetCdmInterfaceVersion())); base::NumberToString(GetCdmInterfaceVersion()));
command_line->AppendSwitchASCII(
switches::kEnableBlinkFeatures,
"EncryptedMediaPersistentUsageRecordSession");
} }
}; };
...@@ -876,6 +880,11 @@ IN_PROC_BROWSER_TEST_P(ECKEncryptedMediaTest, LoadSessionAfterClose) { ...@@ -876,6 +880,11 @@ IN_PROC_BROWSER_TEST_P(ECKEncryptedMediaTest, LoadSessionAfterClose) {
media::kEnded); media::kEnded);
} }
IN_PROC_BROWSER_TEST_P(ECKEncryptedMediaTest, VerifyPersistentUsageRecord) {
TestPlaybackCase(kExternalClearKeyKeySystem, kPersistentUsageRecord,
media::kEnded);
}
const char kExternalClearKeyDecryptOnlyKeySystem[] = const char kExternalClearKeyDecryptOnlyKeySystem[] =
"org.chromium.externalclearkey.decryptonly"; "org.chromium.externalclearkey.decryptonly";
......
...@@ -407,15 +407,17 @@ void AesDecryptor::RemoveSession(const std::string& session_id, ...@@ -407,15 +407,17 @@ void AesDecryptor::RemoveSession(const std::string& session_id,
// "persistent-license" // "persistent-license"
// Let message be a message containing or reflecting the record // Let message be a message containing or reflecting the record
// of license destruction. // of license destruction.
// "persistent-usage-record"
// Not supported by AesDecryptor.
std::vector<uint8_t> message; std::vector<uint8_t> message;
if (it->second != CdmSessionType::kTemporary) { if (it->second == CdmSessionType::kPersistentLicense) {
// The license release message is specified in the spec: // The license release message is specified in the spec:
// https://w3c.github.io/encrypted-media/#clear-key-release-format. // https://w3c.github.io/encrypted-media/#clear-key-release-format.
KeyIdList key_ids; KeyIdList key_ids;
key_ids.reserve(keys_info.size()); key_ids.reserve(keys_info.size());
for (const auto& key_info : keys_info) for (const auto& key_info : keys_info)
key_ids.push_back(key_info->key_id); key_ids.push_back(key_info->key_id);
CreateKeyIdsInitData(key_ids, &message); message = CreateLicenseReleaseMessage(key_ids);
} }
// 4.5. Queue a task to run the following steps: // 4.5. Queue a task to run the following steps:
...@@ -487,6 +489,12 @@ void AesDecryptor::Decrypt(StreamType stream_type, ...@@ -487,6 +489,12 @@ void AesDecryptor::Decrypt(StreamType stream_type,
return; return;
} }
auto now = base::Time::Now();
if (first_decryption_time_.is_null())
first_decryption_time_ = now;
latest_decryption_time_ = now;
DCHECK_EQ(decrypted->timestamp(), encrypted->timestamp()); DCHECK_EQ(decrypted->timestamp(), encrypted->timestamp());
DCHECK_EQ(decrypted->duration(), encrypted->duration()); DCHECK_EQ(decrypted->duration(), encrypted->duration());
std::move(decrypt_cb).Run(kSuccess, std::move(decrypted)); std::move(decrypt_cb).Run(kSuccess, std::move(decrypted));
...@@ -642,6 +650,27 @@ CdmKeysInfo AesDecryptor::GenerateKeysInfoList( ...@@ -642,6 +650,27 @@ CdmKeysInfo AesDecryptor::GenerateKeysInfoList(
return keys_info; return keys_info;
} }
void AesDecryptor::GetRecordOfKeyUsage(const std::string& session_id,
KeyIdList& key_ids,
base::Time& first_decryption_time,
base::Time& latest_decryption_time) {
auto it = open_sessions_.find(session_id);
if (it == open_sessions_.end() ||
it->second != CdmSessionType::kPersistentUsageRecord) {
return;
}
base::AutoLock auto_lock(key_map_lock_);
for (const auto& item : key_map_) {
if (item.second->Contains(session_id)) {
key_ids.emplace_back(item.first.begin(), item.first.end());
}
}
first_decryption_time = first_decryption_time_;
latest_decryption_time = latest_decryption_time_;
}
AesDecryptor::DecryptionKey::DecryptionKey(const std::string& secret) AesDecryptor::DecryptionKey::DecryptionKey(const std::string& secret)
: secret_(secret) {} : secret_(secret) {}
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include "media/base/content_decryption_module.h" #include "media/base/content_decryption_module.h"
#include "media/base/decryptor.h" #include "media/base/decryptor.h"
#include "media/base/media_export.h" #include "media/base/media_export.h"
#include "media/cdm/json_web_key.h"
namespace crypto { namespace crypto {
class SymmetricKey; class SymmetricKey;
...@@ -176,6 +177,13 @@ class MEDIA_EXPORT AesDecryptor : public ContentDecryptionModule, ...@@ -176,6 +177,13 @@ class MEDIA_EXPORT AesDecryptor : public ContentDecryptionModule,
CdmKeysInfo GenerateKeysInfoList(const std::string& session_id, CdmKeysInfo GenerateKeysInfoList(const std::string& session_id,
CdmKeyInformation::KeyStatus status); CdmKeyInformation::KeyStatus status);
// Returns the record of key usage for persistent-usage-record session. Used
// by ClearKeyPersistentSessionCdm.
void GetRecordOfKeyUsage(const std::string& session_id,
KeyIdList& key_ids,
base::Time& first_decryption_time,
base::Time& latest_decryption_time);
// Callbacks for firing session events. // Callbacks for firing session events.
SessionMessageCB session_message_cb_; SessionMessageCB session_message_cb_;
SessionClosedCB session_closed_cb_; SessionClosedCB session_closed_cb_;
...@@ -196,6 +204,10 @@ class MEDIA_EXPORT AesDecryptor : public ContentDecryptionModule, ...@@ -196,6 +204,10 @@ class MEDIA_EXPORT AesDecryptor : public ContentDecryptionModule,
CallbackRegistry<EventCB::RunType> event_callbacks_; CallbackRegistry<EventCB::RunType> event_callbacks_;
// First and latest decryption time for persistent-usage-record
base::Time first_decryption_time_ GUARDED_BY(key_map_lock_);
base::Time latest_decryption_time_ GUARDED_BY(key_map_lock_);
DISALLOW_COPY_AND_ASSIGN(AesDecryptor); DISALLOW_COPY_AND_ASSIGN(AesDecryptor);
}; };
......
...@@ -338,10 +338,8 @@ void CreateLicenseRequest(const KeyIdList& key_ids, ...@@ -338,10 +338,8 @@ void CreateLicenseRequest(const KeyIdList& key_ids,
license->swap(result); license->swap(result);
} }
void CreateKeyIdsInitData(const KeyIdList& key_ids, void AddKeyIdsToDictionary(const KeyIdList& key_ids,
std::vector<uint8_t>* init_data) { base::DictionaryValue* dictionary) {
// Create the init_data.
auto dictionary = std::make_unique<base::DictionaryValue>();
auto list = std::make_unique<base::ListValue>(); auto list = std::make_unique<base::ListValue>();
for (const auto& key_id : key_ids) { for (const auto& key_id : key_ids) {
std::string key_id_string; std::string key_id_string;
...@@ -353,15 +351,64 @@ void CreateKeyIdsInitData(const KeyIdList& key_ids, ...@@ -353,15 +351,64 @@ void CreateKeyIdsInitData(const KeyIdList& key_ids,
list->AppendString(key_id_string); list->AppendString(key_id_string);
} }
dictionary->Set(kKeyIdsTag, std::move(list)); dictionary->Set(kKeyIdsTag, std::move(list));
}
std::vector<uint8_t> SerializeDictionaryToVector(
const base::DictionaryValue* dictionary) {
// Serialize the dictionary as a string. // Serialize the dictionary as a string.
std::string json; std::string json;
JSONStringValueSerializer serializer(&json); JSONStringValueSerializer serializer(&json);
serializer.Serialize(*dictionary); serializer.Serialize(*dictionary);
// Convert the serialized data into std::vector and return it. // Convert the serialized data into std::vector and return it.
std::vector<uint8_t> result(json.begin(), json.end()); return std::vector<uint8_t>(json.begin(), json.end());
init_data->swap(result); }
void CreateKeyIdsInitData(const KeyIdList& key_ids,
std::vector<uint8_t>* init_data) {
// Create the init_data.
auto dictionary = std::make_unique<base::DictionaryValue>();
AddKeyIdsToDictionary(key_ids, dictionary.get());
auto data = SerializeDictionaryToVector(dictionary.get());
init_data->swap(data);
}
// The format is a JSON object. For sessions of type "persistent-license" and
// "persistent-usage-record", the object shall contain the following member:
//
// "kids"
// An array of key IDs. Each element of the array is the base64url encoding
// of the octet sequence containing the key ID value.
//
// For sessions of type "persistent-usage-record" the object shall also contain
// the following members:
//
// "firstTime"
// The first decryption time expressed as a number giving the time, in
// milliseconds since 01 January, 1970 UTC.
// "latestTime"
// The latest decryption time expressed as a number giving the time, in
// milliseconds since 01 January,
// 1970 UTC. https://w3c.github.io/encrypted-media/#clear-key-release-format
std::vector<uint8_t> CreateLicenseReleaseMessage(
const KeyIdList& key_ids,
const base::Time first_decrypt_time,
const base::Time latest_decrypt_time) {
// Create the init_data.
auto dictionary = std::make_unique<base::DictionaryValue>();
AddKeyIdsToDictionary(key_ids, dictionary.get());
if (!first_decrypt_time.is_null() && !latest_decrypt_time.is_null()) {
// Persistent-Usage-Record
// Time need to be millisecond since 01 January, 1970 UTC
dictionary->SetDouble("firstTime",
first_decrypt_time.ToJsTimeIgnoringNull());
dictionary->SetDouble("latestTime",
latest_decrypt_time.ToJsTimeIgnoringNull());
}
return SerializeDictionaryToVector(dictionary.get());
} }
bool ExtractFirstKeyIdFromLicenseRequest(const std::vector<uint8_t>& license, bool ExtractFirstKeyIdFromLicenseRequest(const std::vector<uint8_t>& license,
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "base/time/time.h"
#include "media/base/media_export.h" #include "media/base/media_export.h"
namespace media { namespace media {
...@@ -94,6 +95,11 @@ MEDIA_EXPORT void CreateLicenseRequest(const KeyIdList& key_ids, ...@@ -94,6 +95,11 @@ MEDIA_EXPORT void CreateLicenseRequest(const KeyIdList& key_ids,
MEDIA_EXPORT void CreateKeyIdsInitData(const KeyIdList& key_ids, MEDIA_EXPORT void CreateKeyIdsInitData(const KeyIdList& key_ids,
std::vector<uint8_t>* key_ids_init_data); std::vector<uint8_t>* key_ids_init_data);
MEDIA_EXPORT std::vector<uint8_t> CreateLicenseReleaseMessage(
const KeyIdList& key_ids,
const base::Time first_decrypt_time = base::Time(),
const base::Time latest_decrypt_time = base::Time());
// Extract the first key from the license request message. Returns true if // Extract the first key from the license request message. Returns true if
// |license| is a valid license request and contains at least one key, // |license| is a valid license request and contains at least one key,
// otherwise false and |first_key| is not touched. // otherwise false and |first_key| is not touched.
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "media/base/cdm_promise.h" #include "media/base/cdm_promise.h"
#include "media/cdm/json_web_key.h"
namespace media { namespace media {
...@@ -94,9 +95,12 @@ ClearKeyPersistentSessionCdm::ClearKeyPersistentSessionCdm( ...@@ -94,9 +95,12 @@ ClearKeyPersistentSessionCdm::ClearKeyPersistentSessionCdm(
const SessionClosedCB& session_closed_cb, const SessionClosedCB& session_closed_cb,
const SessionKeysChangeCB& session_keys_change_cb, const SessionKeysChangeCB& session_keys_change_cb,
const SessionExpirationUpdateCB& session_expiration_update_cb) const SessionExpirationUpdateCB& session_expiration_update_cb)
: cdm_host_proxy_(cdm_host_proxy), session_closed_cb_(session_closed_cb) { : cdm_host_proxy_(cdm_host_proxy),
session_message_cb_(session_message_cb),
session_closed_cb_(session_closed_cb) {
cdm_ = base::MakeRefCounted<AesDecryptor>( cdm_ = base::MakeRefCounted<AesDecryptor>(
session_message_cb, base::Bind(&ClearKeyPersistentSessionCdm::OnSessionMessage,
weak_factory_.GetWeakPtr()),
base::Bind(&ClearKeyPersistentSessionCdm::OnSessionClosed, base::Bind(&ClearKeyPersistentSessionCdm::OnSessionClosed,
weak_factory_.GetWeakPtr()), weak_factory_.GetWeakPtr()),
session_keys_change_cb, session_expiration_update_cb); session_keys_change_cb, session_expiration_update_cb);
...@@ -116,7 +120,10 @@ void ClearKeyPersistentSessionCdm::CreateSessionAndGenerateRequest( ...@@ -116,7 +120,10 @@ void ClearKeyPersistentSessionCdm::CreateSessionAndGenerateRequest(
const std::vector<uint8_t>& init_data, const std::vector<uint8_t>& init_data,
std::unique_ptr<NewSessionCdmPromise> promise) { std::unique_ptr<NewSessionCdmPromise> promise) {
std::unique_ptr<NewSessionCdmPromise> new_promise; std::unique_ptr<NewSessionCdmPromise> new_promise;
if (session_type != CdmSessionType::kPersistentLicense) { // TODO(crbug.com/1102976) Add browser test for loading EME
// persistent-usage-record session
if (session_type == CdmSessionType::kTemporary ||
session_type == CdmSessionType::kPersistentUsageRecord) {
new_promise = std::move(promise); new_promise = std::move(promise);
} else { } else {
// Since it's a persistent session, we need to save the session ID after // Since it's a persistent session, we need to save the session ID after
...@@ -294,7 +301,32 @@ void ClearKeyPersistentSessionCdm::RemoveSession( ...@@ -294,7 +301,32 @@ void ClearKeyPersistentSessionCdm::RemoveSession(
auto it = persistent_sessions_.find(session_id); auto it = persistent_sessions_.find(session_id);
if (it == persistent_sessions_.end()) { if (it == persistent_sessions_.end()) {
// Not a persistent session, so simply pass the request on. // Not a persistent session, so simply pass the request on.
// TODO(crbug.com/1102976) Add test for loading PUR session
// Query the record of key usage before calling remove as RemoveSession will
// delete the keys. Steps from
// https://w3c.github.io/encrypted-media/#remove. 4.4.1.2 Follow the steps
// for the value of this object's session type
// "persistent-usage-record"
// Let message be a message containing or reflecting this
// object's record of key usage.
KeyIdList key_ids;
base::Time first_decryption_time;
base::Time latest_decryption_time;
cdm_->GetRecordOfKeyUsage(session_id, key_ids, first_decryption_time,
latest_decryption_time);
cdm_->RemoveSession(session_id, std::move(promise)); cdm_->RemoveSession(session_id, std::move(promise));
// Both times will be null if the session type is not PUR.
if (!first_decryption_time.is_null() && !latest_decryption_time.is_null()) {
std::vector<uint8_t> message = CreateLicenseReleaseMessage(
key_ids, first_decryption_time, latest_decryption_time);
// EME spec specifies that the message event should be fired before the
// promise is resolve but since this is only for testing we can leave this
// here.
session_message_cb_.Run(session_id, CdmMessageType::LICENSE_RELEASE,
message);
}
return; return;
} }
...@@ -336,7 +368,6 @@ void ClearKeyPersistentSessionCdm::OnFileWrittenForRemoveSession( ...@@ -336,7 +368,6 @@ void ClearKeyPersistentSessionCdm::OnFileWrittenForRemoveSession(
std::unique_ptr<SimpleCdmPromise> promise, std::unique_ptr<SimpleCdmPromise> promise,
bool success) { bool success) {
DCHECK(success); DCHECK(success);
cdm_->RemoveSession(session_id, std::move(promise));
} }
CdmContext* ClearKeyPersistentSessionCdm::GetCdmContext() { CdmContext* ClearKeyPersistentSessionCdm::GetCdmContext() {
...@@ -354,4 +385,11 @@ void ClearKeyPersistentSessionCdm::OnSessionClosed( ...@@ -354,4 +385,11 @@ void ClearKeyPersistentSessionCdm::OnSessionClosed(
session_closed_cb_.Run(session_id); session_closed_cb_.Run(session_id);
} }
void ClearKeyPersistentSessionCdm::OnSessionMessage(
const std::string& session_id,
CdmMessageType message_type,
const std::vector<uint8_t>& message) {
session_message_cb_.Run(session_id, message_type, message);
}
} // namespace media } // namespace media
...@@ -101,10 +101,15 @@ class ClearKeyPersistentSessionCdm : public ContentDecryptionModule { ...@@ -101,10 +101,15 @@ class ClearKeyPersistentSessionCdm : public ContentDecryptionModule {
// sessions if it was a persistent session. // sessions if it was a persistent session.
void OnSessionClosed(const std::string& session_id); void OnSessionClosed(const std::string& session_id);
void OnSessionMessage(const std::string& session_id,
CdmMessageType message_type,
const std::vector<uint8_t>& message);
scoped_refptr<AesDecryptor> cdm_; scoped_refptr<AesDecryptor> cdm_;
CdmHostProxy* const cdm_host_proxy_ = nullptr; CdmHostProxy* const cdm_host_proxy_ = nullptr;
// Callbacks for firing session events. Other events aren't intercepted. // Callbacks for firing session events. Other events aren't intercepted.
SessionMessageCB session_message_cb_;
SessionClosedCB session_closed_cb_; SessionClosedCB session_closed_cb_;
// Keep track of current open persistent sessions. // Keep track of current open persistent sessions.
......
...@@ -103,7 +103,11 @@ ...@@ -103,7 +103,11 @@
Utils.timeLog('waiting for video to end.'); Utils.timeLog('waiting for video to end.');
video.removeEventListener('ended', Utils.failTest); video.removeEventListener('ended', Utils.failTest);
if (testConfig.sessionToLoad == "PersistentUsageRecord") {
video.addEventListener('ended', onEnded);
} else {
Utils.installTitleEventHandler(video, 'ended'); Utils.installTitleEventHandler(video, 'ended');
}
video.removeEventListener('timeupdate', onTimeUpdate); video.removeEventListener('timeupdate', onTimeUpdate);
} }
...@@ -129,6 +133,16 @@ ...@@ -129,6 +133,16 @@
Utils.timeLog('Event: ' + e.type + ', hidden: ' + document.hidden); Utils.timeLog('Event: ' + e.type + ', hidden: ' + document.hidden);
} }
function onEnded(e) {
Utils.timeLog('Event: ' + e.type);
PlayerUtils.removeSession(player).then(function() {
Utils.setResultInTitle('ENDED');
}).catch(function(error) {
Utils.timeLog(error);
Utils.failTest('Failed PlayerUtils.removeSession');
});
}
function play(video, playTwice) { function play(video, playTwice) {
Utils.timeLog('Starting play, hidden: ' + document.hidden); Utils.timeLog('Starting play, hidden: ' + document.hidden);
video.addEventListener('canplay', onLogEvent); video.addEventListener('canplay', onLogEvent);
......
...@@ -54,6 +54,7 @@ PlayerUtils.registerEMEEventListeners = function(player) { ...@@ -54,6 +54,7 @@ PlayerUtils.registerEMEEventListeners = function(player) {
player.video.receivedKeyMessage = true; player.video.receivedKeyMessage = true;
if (message.messageType == 'license-request' || if (message.messageType == 'license-request' ||
message.messageType == 'license-renewal' || message.messageType == 'license-renewal' ||
message.messageType == 'license-release' ||
message.messageType == 'individualization-request') { message.messageType == 'individualization-request') {
player.video.receivedMessageTypes.add(message.messageType); player.video.receivedMessageTypes.add(message.messageType);
} else { } else {
...@@ -140,7 +141,8 @@ PlayerUtils.registerEMEEventListeners = function(player) { ...@@ -140,7 +141,8 @@ PlayerUtils.registerEMEEventListeners = function(player) {
} }
try { try {
if (player.testConfig.sessionToLoad) { if (player.testConfig.sessionToLoad &&
player.testConfig.sessionToLoad != 'PersistentUsageRecord') {
// Create a session to load using a new MediaKeys. // Create a session to load using a new MediaKeys.
// TODO(jrummell): Add a test that covers remove(). // TODO(jrummell): Add a test that covers remove().
player.access.createMediaKeys() player.access.createMediaKeys()
...@@ -190,10 +192,16 @@ PlayerUtils.registerEMEEventListeners = function(player) { ...@@ -190,10 +192,16 @@ PlayerUtils.registerEMEEventListeners = function(player) {
Utils.failTest(error, UNIT_TEST_FAILURE); Utils.failTest(error, UNIT_TEST_FAILURE);
}); });
} else { } else {
Utils.timeLog('Creating new media key session for initDataType: ' + Utils.timeLog(
'Creating new media key session for initDataType: ' +
message.initDataType + ', initData: ' + message.initDataType + ', initData: ' +
Utils.getHexString(new Uint8Array(message.initData))); Utils.getHexString(new Uint8Array(message.initData)));
if (player.testConfig.sessionToLoad == 'PersistentUsageRecord') {
player.session =
message.target.mediaKeys.createSession('persistent-usage-record');
} else {
player.session = message.target.mediaKeys.createSession(); player.session = message.target.mediaKeys.createSession();
}
addMediaKeySessionListeners(player.session); addMediaKeySessionListeners(player.session);
player.session.generateRequest(message.initDataType, message.initData) player.session.generateRequest(message.initDataType, message.initData)
.catch(function(error) { .catch(function(error) {
...@@ -259,6 +267,9 @@ PlayerUtils.registerEMEEventListeners = function(player) { ...@@ -259,6 +267,9 @@ PlayerUtils.registerEMEEventListeners = function(player) {
player.testConfig.keySystem == STORAGE_ID_TEST_KEYSYSTEM) { player.testConfig.keySystem == STORAGE_ID_TEST_KEYSYSTEM) {
config.persistentState = 'required'; config.persistentState = 'required';
config.sessionTypes = ['temporary', 'persistent-license']; config.sessionTypes = ['temporary', 'persistent-license'];
if (player.testConfig.sessionToLoad == 'PersistentUsageRecord') {
config.sessionTypes.push('persistent-usage-record');
}
} }
return navigator return navigator
...@@ -326,3 +337,27 @@ PlayerUtils.createPlayer = function(video, testConfig) { ...@@ -326,3 +337,27 @@ PlayerUtils.createPlayer = function(video, testConfig) {
var Player = getPlayerType(testConfig.keySystem); var Player = getPlayerType(testConfig.keySystem);
return new Player(video, testConfig); return new Player(video, testConfig);
}; };
PlayerUtils.removeSession = async function(player) {
// Once remove() is called, another 'keystatuseschange' and 'message' events
// will happen.
const waitForKeyStatusChangePromise =
Utils.waitForEvent(player.session, 'keystatuseschange');
const waitForMessagePromise = Utils.waitForEvent(
player.session, 'message', function(e, resolve, reject) {
Utils.timeLog(e.messageType);
if (e.messageType == 'license-release' &&
player.testConfig.sessionToLoad == 'PersistentUsageRecord') {
Utils.verifyUsageRecord(e.message);
}
// TODO: verify license-release message for persistent-license session
resolve();
});
Utils.timeLog('Calling remove()');
const removePromise = player.session.remove();
return Promise.all(
[removePromise, waitForKeyStatusChangePromise, waitForMessagePromise]);
}
...@@ -86,16 +86,16 @@ Utils.createKeyIdsInitializationData = function(keyId) { ...@@ -86,16 +86,16 @@ Utils.createKeyIdsInitializationData = function(keyId) {
return Utils.convertToUint8Array(initData); return Utils.convertToUint8Array(initData);
}; };
function convertToString(data) {
return String.fromCharCode.apply(null, Utils.convertToUint8Array(data));
}
Utils.extractFirstLicenseKeyId = function(message) { Utils.extractFirstLicenseKeyId = function(message) {
// Decodes data (Uint8Array) from base64url string. // Decodes data (Uint8Array) from base64url string.
function base64urlDecode(data) { function base64urlDecode(data) {
return atob(data.replace(/\-/g, "+").replace(/\_/g, "/")); return atob(data.replace(/\-/g, "+").replace(/\_/g, "/"));
} }
function convertToString(data) {
return String.fromCharCode.apply(null, Utils.convertToUint8Array(data));
}
try { try {
var json = JSON.parse(convertToString(message)); var json = JSON.parse(convertToString(message));
// Decode the first element of 'kids', return it as an Uint8Array. // Decode the first element of 'kids', return it as an Uint8Array.
...@@ -106,7 +106,31 @@ Utils.extractFirstLicenseKeyId = function(message) { ...@@ -106,7 +106,31 @@ Utils.extractFirstLicenseKeyId = function(message) {
} }
}; };
Utils.documentLog = function(log, success, time) { Utils.verifyUsageRecord =
function(message) {
try {
var json = JSON.parse(convertToString(message));
var first_decrypt_time = new Date(json.firstTime);
var last_decrypt_time = new Date(json.latestTime);
Utils.timeLog('First decrypt time: ' + first_decrypt_time.toISOString());
Utils.timeLog('Last decrypt time: ' + last_decrypt_time.toISOString());
var delta = json.latestTime - json.firstTime;
// The video used for the tests is roughly 2.5 seconds.
if (delta < 2000 || delta > 3000) {
Utils.failTest(
'The usage record reported by the CDM was not in the' +
'expected range')
}
} catch (error) {
Utils.failTest(
'Fail to extract first decrypt time from license-release' +
'message');
}
}
Utils.documentLog = function(log, success, time) {
if (!docLogs) if (!docLogs)
return; return;
time = time || Utils.getCurrentTimeString(); time = time || Utils.getCurrentTimeString();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment