2016-03-25 08:40:15 +00:00
|
|
|
// Copyright 2016 Google Inc. All rights reserved.
|
|
|
|
//
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file or at
|
|
|
|
// https://developers.google.com/open-source/licenses/bsd
|
|
|
|
|
|
|
|
#include "packager/hls/base/simple_hls_notifier.h"
|
|
|
|
|
|
|
|
#include "packager/base/base64.h"
|
2016-04-16 22:58:47 +00:00
|
|
|
#include "packager/base/files/file_path.h"
|
2016-06-24 00:22:37 +00:00
|
|
|
#include "packager/base/json/json_writer.h"
|
2016-03-25 08:40:15 +00:00
|
|
|
#include "packager/base/logging.h"
|
|
|
|
#include "packager/base/strings/string_number_conversions.h"
|
|
|
|
#include "packager/base/strings/stringprintf.h"
|
|
|
|
#include "packager/hls/base/media_playlist.h"
|
2016-06-24 00:22:37 +00:00
|
|
|
#include "packager/media/base/fixed_key_source.h"
|
|
|
|
#include "packager/media/base/widevine_key_source.h"
|
2016-03-25 08:40:15 +00:00
|
|
|
#include "packager/media/base/widevine_pssh_data.pb.h"
|
|
|
|
|
2016-05-20 21:19:33 +00:00
|
|
|
namespace shaka {
|
2016-03-25 08:40:15 +00:00
|
|
|
namespace hls {
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
bool IsWidevineSystemId(const std::vector<uint8_t>& system_id) {
|
2016-06-24 00:22:37 +00:00
|
|
|
return system_id.size() == arraysize(media::kWidevineSystemId) &&
|
|
|
|
std::equal(system_id.begin(), system_id.end(),
|
|
|
|
media::kWidevineSystemId);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool IsCommonSystemId(const std::vector<uint8_t>& system_id) {
|
|
|
|
return system_id.size() == arraysize(media::kCommonSystemId) &&
|
|
|
|
std::equal(system_id.begin(), system_id.end(), media::kCommonSystemId);
|
2016-03-25 08:40:15 +00:00
|
|
|
}
|
2016-04-16 22:58:47 +00:00
|
|
|
|
|
|
|
// TODO(rkuroiwa): Dedup these with the functions in MpdBuilder.
|
|
|
|
std::string MakePathRelative(const std::string& original_path,
|
|
|
|
const std::string& output_dir) {
|
|
|
|
return (original_path.find(output_dir) == 0)
|
|
|
|
? original_path.substr(output_dir.size())
|
|
|
|
: original_path;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MakePathsRelativeToOutputDirectory(const std::string& output_dir,
|
|
|
|
MediaInfo* media_info) {
|
|
|
|
DCHECK(media_info);
|
|
|
|
const std::string kFileProtocol("file://");
|
|
|
|
std::string prefix_stripped_output_dir =
|
|
|
|
(output_dir.find(kFileProtocol) == 0)
|
|
|
|
? output_dir.substr(kFileProtocol.size())
|
|
|
|
: output_dir;
|
|
|
|
|
|
|
|
if (prefix_stripped_output_dir.empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
std::string directory_with_separator(
|
2016-08-14 22:28:21 +00:00
|
|
|
base::FilePath::FromUTF8Unsafe(prefix_stripped_output_dir)
|
|
|
|
.AsEndingWithSeparator()
|
|
|
|
.AsUTF8Unsafe());
|
2016-04-16 22:58:47 +00:00
|
|
|
if (directory_with_separator.empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (media_info->has_media_file_name()) {
|
|
|
|
media_info->set_media_file_name(MakePathRelative(
|
|
|
|
media_info->media_file_name(), directory_with_separator));
|
|
|
|
}
|
|
|
|
if (media_info->has_segment_template()) {
|
|
|
|
media_info->set_segment_template(MakePathRelative(
|
|
|
|
media_info->segment_template(), directory_with_separator));
|
|
|
|
}
|
|
|
|
}
|
2016-06-24 00:22:37 +00:00
|
|
|
|
|
|
|
bool WidevinePsshToJson(const std::vector<uint8_t>& pssh_data,
|
|
|
|
const std::vector<uint8_t>& key_id,
|
|
|
|
std::string* pssh_json) {
|
|
|
|
media::WidevinePsshData pssh_proto;
|
|
|
|
if (!pssh_proto.ParseFromArray(pssh_data.data(), pssh_data.size())) {
|
|
|
|
LOG(ERROR) << "Failed to parse protection_system_specific_data.";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!pssh_proto.has_provider() ||
|
|
|
|
(!pssh_proto.has_content_id() && pssh_proto.key_id_size() == 0)) {
|
|
|
|
LOG(ERROR) << "Missing fields to generate URI.";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
base::DictionaryValue pssh_dict;
|
|
|
|
pssh_dict.SetString("provider", pssh_proto.provider());
|
|
|
|
if (pssh_proto.has_content_id()) {
|
|
|
|
std::string content_id_base64;
|
|
|
|
base::Base64Encode(base::StringPiece(pssh_proto.content_id().data(),
|
|
|
|
pssh_proto.content_id().size()),
|
|
|
|
&content_id_base64);
|
|
|
|
pssh_dict.SetString("content_id", content_id_base64);
|
|
|
|
}
|
|
|
|
base::ListValue* key_ids = new base::ListValue();
|
|
|
|
key_ids->AppendString(base::HexEncode(key_id.data(), key_id.size()));
|
|
|
|
for (const std::string& id : pssh_proto.key_id()) {
|
|
|
|
if (key_id.size() == id.size() &&
|
|
|
|
memcmp(key_id.data(), id.data(), id.size()) == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
key_ids->AppendString(base::HexEncode(id.data(), id.size()));
|
|
|
|
}
|
|
|
|
pssh_dict.Set("key_ids", key_ids);
|
|
|
|
|
|
|
|
if (!base::JSONWriter::Write(pssh_dict, pssh_json)) {
|
|
|
|
LOG(ERROR) << "Failed to write to JSON.";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-03-25 08:40:15 +00:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
MediaPlaylistFactory::~MediaPlaylistFactory() {}
|
|
|
|
|
2016-08-17 17:41:40 +00:00
|
|
|
std::unique_ptr<MediaPlaylist> MediaPlaylistFactory::Create(
|
2016-04-20 22:23:19 +00:00
|
|
|
MediaPlaylist::MediaPlaylistType type,
|
2016-03-25 08:40:15 +00:00
|
|
|
const std::string& file_name,
|
|
|
|
const std::string& name,
|
|
|
|
const std::string& group_id) {
|
2016-08-17 17:41:40 +00:00
|
|
|
return std::unique_ptr<MediaPlaylist>(
|
2016-04-20 22:23:19 +00:00
|
|
|
new MediaPlaylist(type, file_name, name, group_id));
|
2016-03-25 08:40:15 +00:00
|
|
|
}
|
|
|
|
|
2016-04-20 22:23:19 +00:00
|
|
|
SimpleHlsNotifier::SimpleHlsNotifier(HlsProfile profile,
|
|
|
|
const std::string& prefix,
|
2016-03-25 08:40:15 +00:00
|
|
|
const std::string& output_dir,
|
|
|
|
const std::string& master_playlist_name)
|
2016-04-20 22:23:19 +00:00
|
|
|
: HlsNotifier(profile),
|
2016-03-25 08:40:15 +00:00
|
|
|
prefix_(prefix),
|
|
|
|
output_dir_(output_dir),
|
|
|
|
media_playlist_factory_(new MediaPlaylistFactory()),
|
2016-08-30 23:01:19 +00:00
|
|
|
master_playlist_(new MasterPlaylist(master_playlist_name)) {}
|
2016-03-25 08:40:15 +00:00
|
|
|
|
|
|
|
SimpleHlsNotifier::~SimpleHlsNotifier() {}
|
|
|
|
|
|
|
|
bool SimpleHlsNotifier::Init() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SimpleHlsNotifier::NotifyNewStream(const MediaInfo& media_info,
|
|
|
|
const std::string& playlist_name,
|
|
|
|
const std::string& name,
|
|
|
|
const std::string& group_id,
|
|
|
|
uint32_t* stream_id) {
|
|
|
|
DCHECK(stream_id);
|
|
|
|
|
2016-04-20 22:23:19 +00:00
|
|
|
MediaPlaylist::MediaPlaylistType type;
|
|
|
|
switch (profile()) {
|
|
|
|
case HlsProfile::kLiveProfile:
|
|
|
|
type = MediaPlaylist::MediaPlaylistType::kLive;
|
|
|
|
break;
|
|
|
|
case HlsProfile::kOnDemandProfile:
|
|
|
|
type = MediaPlaylist::MediaPlaylistType::kVod;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
NOTREACHED();
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-16 22:58:47 +00:00
|
|
|
|
|
|
|
MediaInfo adjusted_media_info(media_info);
|
|
|
|
MakePathsRelativeToOutputDirectory(output_dir_, &adjusted_media_info);
|
|
|
|
|
2016-08-17 17:41:40 +00:00
|
|
|
std::unique_ptr<MediaPlaylist> media_playlist =
|
2016-04-20 22:23:19 +00:00
|
|
|
media_playlist_factory_->Create(type, playlist_name, name, group_id);
|
2016-04-16 22:58:47 +00:00
|
|
|
if (!media_playlist->SetMediaInfo(adjusted_media_info)) {
|
2016-03-25 08:40:15 +00:00
|
|
|
LOG(ERROR) << "Failed to set media info for playlist " << playlist_name;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-07-05 23:32:19 +00:00
|
|
|
*stream_id = sequence_number_.GetNext();
|
2016-03-25 08:40:15 +00:00
|
|
|
base::AutoLock auto_lock(lock_);
|
|
|
|
master_playlist_->AddMediaPlaylist(media_playlist.get());
|
2016-04-17 07:12:10 +00:00
|
|
|
media_playlist_map_.insert(
|
2016-08-30 23:01:19 +00:00
|
|
|
std::make_pair(*stream_id, std::move(media_playlist)));
|
2016-03-25 08:40:15 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SimpleHlsNotifier::NotifyNewSegment(uint32_t stream_id,
|
|
|
|
const std::string& segment_name,
|
|
|
|
uint64_t start_time,
|
|
|
|
uint64_t duration,
|
|
|
|
uint64_t size) {
|
|
|
|
base::AutoLock auto_lock(lock_);
|
|
|
|
auto result = media_playlist_map_.find(stream_id);
|
|
|
|
if (result == media_playlist_map_.end()) {
|
|
|
|
LOG(ERROR) << "Cannot find stream with ID: " << stream_id;
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-16 22:58:47 +00:00
|
|
|
const std::string relative_segment_name =
|
|
|
|
MakePathRelative(segment_name, output_dir_);
|
|
|
|
|
2016-03-25 08:40:15 +00:00
|
|
|
auto& media_playlist = result->second;
|
2016-04-16 22:58:47 +00:00
|
|
|
media_playlist->AddSegment(prefix_ + relative_segment_name, duration, size);
|
2016-03-25 08:40:15 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SimpleHlsNotifier::NotifyEncryptionUpdate(
|
|
|
|
uint32_t stream_id,
|
|
|
|
const std::vector<uint8_t>& key_id,
|
|
|
|
const std::vector<uint8_t>& system_id,
|
|
|
|
const std::vector<uint8_t>& iv,
|
|
|
|
const std::vector<uint8_t>& protection_system_specific_data) {
|
|
|
|
base::AutoLock auto_lock(lock_);
|
|
|
|
auto result = media_playlist_map_.find(stream_id);
|
|
|
|
if (result == media_playlist_map_.end()) {
|
|
|
|
LOG(ERROR) << "Cannot find stream with ID: " << stream_id;
|
|
|
|
return false;
|
|
|
|
}
|
2016-06-24 00:22:37 +00:00
|
|
|
|
|
|
|
std::string key_format;
|
|
|
|
std::string key_uri_data;
|
|
|
|
if (IsWidevineSystemId(system_id)) {
|
|
|
|
key_format = "com.widevine";
|
|
|
|
if (!WidevinePsshToJson(protection_system_specific_data, key_id,
|
|
|
|
&key_uri_data)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else if (IsCommonSystemId(system_id)) {
|
|
|
|
key_format = "identity";
|
|
|
|
// Use key_id as the key_uri. The player needs to have custom logic to
|
|
|
|
// convert it to the actual key url.
|
|
|
|
key_uri_data.assign(key_id.begin(), key_id.end());
|
|
|
|
} else {
|
2016-03-25 08:40:15 +00:00
|
|
|
LOG(ERROR) << "Unknown system ID: "
|
|
|
|
<< base::HexEncode(system_id.data(), system_id.size());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto& media_playlist = result->second;
|
|
|
|
std::string iv_string;
|
|
|
|
if (!iv.empty()) {
|
|
|
|
iv_string = "0x" + base::HexEncode(iv.data(), iv.size());
|
|
|
|
}
|
2016-06-24 00:22:37 +00:00
|
|
|
std::string key_uri_data_base64;
|
|
|
|
base::Base64Encode(key_uri_data, &key_uri_data_base64);
|
2016-03-25 08:40:15 +00:00
|
|
|
media_playlist->AddEncryptionInfo(
|
|
|
|
MediaPlaylist::EncryptionMethod::kSampleAes,
|
2016-06-24 00:22:37 +00:00
|
|
|
"data:text/plain;base64," + key_uri_data_base64, iv_string, key_format,
|
|
|
|
"" /* key_format_versions */);
|
2016-03-25 08:40:15 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SimpleHlsNotifier::Flush() {
|
|
|
|
base::AutoLock auto_lock(lock_);
|
|
|
|
return master_playlist_->WriteAllPlaylists(prefix_, output_dir_);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace hls
|
2016-05-20 21:19:33 +00:00
|
|
|
} // namespace shaka
|