HLS media playlist EXT-X-KEY format changes
[ Merged of http://go/wvgerrit/16576 ] The WV EXT-X-KEY attribute list earlier expected a cenc PSSH box in the URI field, in a hexadecimal sequence format. To ease the burden on content providers, the URI field will now contain init data in a json format and base64 encoded. The platform will assume responsibility to parse this data and create a widevine init data protobuf that can be included in the license request. b/20630275 Change-Id: I49e270bedbe96791fc9b282214a9a358d95d163e
This commit is contained in:
@@ -5,16 +5,40 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "buffer_reader.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
#include "string_conversions.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace {
|
||||
const char kKeyFormatVersionsSeparator = '/';
|
||||
const char kColon = ':';
|
||||
const char kDoubleQuote = '\"';
|
||||
const char kLeftBracket = '[';
|
||||
const char kRightBracket = ']';
|
||||
const std::string kBase64String = "base64,";
|
||||
|
||||
// json init data key values
|
||||
const std::string kProvider = "provider";
|
||||
const std::string kContentId = "content_id";
|
||||
const std::string kKeyIds = "key_ids";
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Protobuf generated classes.
|
||||
using video_widevine_server::sdk::WidevineCencHeader;
|
||||
using video_widevine_server::sdk::WidevineCencHeader_Algorithm;
|
||||
using video_widevine_server::sdk::WidevineCencHeader_Algorithm_AESCTR;
|
||||
|
||||
InitializationData::InitializationData(const std::string& type,
|
||||
const CdmInitData& data)
|
||||
: type_(type), is_cenc_(false), is_hls_(false), is_webm_(false) {
|
||||
: type_(type),
|
||||
is_cenc_(false),
|
||||
is_hls_(false),
|
||||
is_webm_(false),
|
||||
hls_method_(kHlsMethodNone) {
|
||||
if (type == ISO_BMFF_VIDEO_MIME_TYPE || type == ISO_BMFF_AUDIO_MIME_TYPE ||
|
||||
type == CENC_INIT_DATA_FORMAT) {
|
||||
is_cenc_ = true;
|
||||
@@ -31,10 +55,9 @@ InitializationData::InitializationData(const std::string& type,
|
||||
} else if (is_webm()) {
|
||||
data_ = data;
|
||||
} else if (is_hls()) {
|
||||
CdmInitData init_data;
|
||||
if (ExtractHlsAttributes(data, &hls_data_.method, &hls_data_.iv,
|
||||
&hls_data_.uri, &init_data)) {
|
||||
ExtractWidevinePssh(init_data, &data_);
|
||||
std::string uri;
|
||||
if (ExtractHlsAttributes(data, &hls_method_, &hls_iv_, &uri)) {
|
||||
ConstructWidevineInitData(uri, &data_);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,17 +223,18 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
|
||||
//
|
||||
// An example of a widevine supported attribute list from an HLS media playlist
|
||||
// is,
|
||||
// "EXT-X-KEY: METHOD=SAMPLE-AES, \"
|
||||
// "URI=”http://www.youtube.com/license-service”, \"
|
||||
// "IV=0x6df49213a781e338628d0e9c812d328e, \"
|
||||
// "KEYFORMAT=”com.widevine.alpha”, \"
|
||||
// "KEYFORMATVERSION=”1”, \"
|
||||
// "X-WV-INITDATA=0x9e1a3af3de74ae3606e931fee285e3402e172732aba9a16a0e1441f1e"
|
||||
// "EXT-X-KEY: METHOD=SAMPLE-AES, \"
|
||||
// "URI=”data:text/plain;base64,eyANCiAgICJwcm92aWRlciI6Im1sYmFtaGJvIiwNCiAg"
|
||||
// "ICJjb250ZW50X2lkIjoiMjAxNV9UZWFycyIsDQogICAia2V5X2lkcyI6DQogICBbDQo"
|
||||
// "gICAgICAiMzcxZTEzNWUxYTk4NWQ3NWQxOThhN2Y0MTAyMGRjMjMiDQogICBdDQp9DQ"
|
||||
// "o=", \
|
||||
// "IV=0x6df49213a781e338628d0e9c812d328e, \"
|
||||
// "KEYFORMAT=”com.widevine”, \"
|
||||
// "KEYFORMATVERSIONS=”1”"
|
||||
bool InitializationData::ExtractHlsAttributes(const std::string& attribute_list,
|
||||
CdmHlsMethod* method,
|
||||
std::vector<uint8_t>* iv,
|
||||
std::string* uri,
|
||||
CdmInitData* init_data) {
|
||||
std::string* uri) {
|
||||
std::string value;
|
||||
if (!ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_ATTRIBUTE,
|
||||
&value)) {
|
||||
@@ -228,20 +252,25 @@ bool InitializationData::ExtractHlsAttributes(const std::string& attribute_list,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_VERSION_ATTRIBUTE,
|
||||
&value)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractHlsInitDataAtttribute: Unable to read HLS "
|
||||
"keyformat version");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value.compare(HLS_KEYFORMAT_VERSION_VALUE_1)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractHlsInitDataAtttribute: HLS keyformat "
|
||||
"version is not supported: %s",
|
||||
value.c_str());
|
||||
return false;
|
||||
// KEYFORMATVERSIONS is an optional parameter. If absent its
|
||||
// value defaults to "1"
|
||||
if (ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_VERSIONS_ATTRIBUTE,
|
||||
&value)) {
|
||||
std::vector<std::string> versions = ExtractKeyFormatVersions(value);
|
||||
bool supported = false;
|
||||
for (size_t i = 0; i < versions.size(); ++i) {
|
||||
if (versions[i].compare(HLS_KEYFORMAT_VERSION_VALUE_1) == 0) {
|
||||
supported = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!supported) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractHlsInitDataAtttribute: HLS keyformat "
|
||||
"version is not supported: %s",
|
||||
value.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ExtractAttribute(attribute_list, HLS_METHOD_ATTRIBUTE, &value)) {
|
||||
@@ -279,17 +308,91 @@ bool InitializationData::ExtractHlsAttributes(const std::string& attribute_list,
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> init_data_hex;
|
||||
if (!ExtractHexAttribute(attribute_list, HLS_INITDATA_ATTRIBUTE,
|
||||
&init_data_hex)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractHlsInitDataAtttribute: HLS initdata "
|
||||
"attribute not present");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Extracts a base64 encoded string from URI data. This is then base64 decoded
|
||||
// and the Json formatted init data is then parsed. The information is used
|
||||
// to generate a Widevine init data protobuf (WidevineCencHeader).
|
||||
//
|
||||
// An example of a widevine supported json formatted init data string is,
|
||||
//
|
||||
// {
|
||||
// "provider":"mlbamhbo",
|
||||
// "content_id":"2015_Tears",
|
||||
// "key_ids":
|
||||
// [
|
||||
// "371e135e1a985d75d198a7f41020dc23"
|
||||
// ]
|
||||
// }
|
||||
bool InitializationData::ConstructWidevineInitData(
|
||||
const std::string& uri, CdmInitData* init_data_proto) {
|
||||
if (!init_data_proto) {
|
||||
LOGV("InitializationData::ConstructWidevineInitData: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
|
||||
init_data->assign(reinterpret_cast<char*>(init_data_hex.data()),
|
||||
init_data_hex.size());
|
||||
size_t pos = uri.find(kBase64String);
|
||||
if (pos == std::string::npos) {
|
||||
LOGV(
|
||||
"InitializationData::ConstructWidevineInitData: URI attribute "
|
||||
"unexpected format: %s",
|
||||
uri.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> json_init_data =
|
||||
Base64Decode(uri.substr(pos + kBase64String.size()));
|
||||
if (json_init_data.size() == 0) {
|
||||
LOGV(
|
||||
"InitializationData::ConstructWidevineInitData: Base64 decode of json "
|
||||
"data failed");
|
||||
return false;
|
||||
}
|
||||
std::string json_string((const char*)(&json_init_data[0]),
|
||||
json_init_data.size());
|
||||
|
||||
// Parse json data
|
||||
std::string provider;
|
||||
if (!ExtractJsonValue(json_string, kProvider, &provider)) {
|
||||
LOGV(
|
||||
"InitializationData::ConstructWidevineInitData: Unable to extract "
|
||||
"provider value");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string content_id;
|
||||
if (!ExtractJsonValue(json_string, kContentId, &content_id)) {
|
||||
LOGV(
|
||||
"InitializationData::ConstructWidevineInitData: Unable to extract "
|
||||
"content_id value");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string key_id;
|
||||
if (!ExtractJsonValue(json_string, kKeyIds, &key_id)) {
|
||||
LOGV(
|
||||
"InitializationData::ConstructWidevineInitData: Unable to extract "
|
||||
"key_id values");
|
||||
return false;
|
||||
}
|
||||
|
||||
key_id = a2bs_hex(key_id);
|
||||
if (key_id.size() != 16) {
|
||||
LOGV(
|
||||
"InitializationData::ConstructWidevineInitData: Invalid key_id size: "
|
||||
"%ld",
|
||||
key_id.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now format as Widevine init data protobuf
|
||||
WidevineCencHeader cenc_header;
|
||||
cenc_header.set_algorithm(WidevineCencHeader_Algorithm_AESCTR);
|
||||
cenc_header.add_key_id(key_id);
|
||||
cenc_header.set_provider(provider);
|
||||
cenc_header.set_content_id(content_id);
|
||||
cenc_header.SerializeToString(init_data_proto);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -365,4 +468,63 @@ bool InitializationData::ExtractAttribute(const std::string& attribute_list,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InitializationData::ExtractJsonValue(const std::string& json,
|
||||
const std::string& key,
|
||||
std::string* value) {
|
||||
std::string quoted_key = key;
|
||||
quoted_key.insert(0, 1, kDoubleQuote);
|
||||
quoted_key.append(1, kDoubleQuote);
|
||||
|
||||
bool found = false;
|
||||
size_t pos = 0;
|
||||
// Find the key followed by ':'
|
||||
while (!found) {
|
||||
pos = json.find(quoted_key, pos);
|
||||
if (pos == std::string::npos) return false;
|
||||
pos += quoted_key.size();
|
||||
while (pos < json.size() && isspace(json[pos])) ++pos;
|
||||
if (pos >= json.size()) return false;
|
||||
if (json[pos] != kColon) continue;
|
||||
found = true;
|
||||
}
|
||||
|
||||
++pos;
|
||||
while (pos < json.size() && isspace(json[pos])) ++pos;
|
||||
if (pos >= json.size()) return false;
|
||||
|
||||
if (json[pos] == kLeftBracket) {
|
||||
++pos;
|
||||
while (pos < json.size() && isspace(json[pos])) ++pos;
|
||||
if (pos >= json.size()) return false;
|
||||
}
|
||||
|
||||
if (json[pos] != kDoubleQuote) return false;
|
||||
++pos;
|
||||
size_t end_pos = json.find(kDoubleQuote, pos);
|
||||
if (end_pos == std::string::npos) return false;
|
||||
--end_pos;
|
||||
*value = json.substr(pos, end_pos - pos + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Key format versions are individual values or multiple versions
|
||||
// separated by '/'. "1" or "1/2/5"
|
||||
std::vector<std::string> InitializationData::ExtractKeyFormatVersions(
|
||||
const std::string& key_format_versions) {
|
||||
std::vector<std::string> versions;
|
||||
size_t pos = 0;
|
||||
while (pos < key_format_versions.size()) {
|
||||
size_t next_pos =
|
||||
key_format_versions.find(kKeyFormatVersionsSeparator, pos);
|
||||
if (next_pos == std::string::npos) {
|
||||
versions.push_back(key_format_versions.substr(pos));
|
||||
break;
|
||||
} else {
|
||||
versions.push_back(key_format_versions.substr(pos, next_pos - pos));
|
||||
pos = next_pos + 1;
|
||||
}
|
||||
}
|
||||
return versions;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
Reference in New Issue
Block a user