7 #include "packager/hls/base/simple_hls_notifier.h" 11 #include "packager/base/base64.h" 12 #include "packager/base/files/file_path.h" 13 #include "packager/base/json/json_writer.h" 14 #include "packager/base/logging.h" 15 #include "packager/base/optional.h" 16 #include "packager/base/strings/string_number_conversions.h" 17 #include "packager/base/strings/stringprintf.h" 18 #include "packager/hls/base/media_playlist.h" 19 #include "packager/media/base/protection_system_specific_info.h" 20 #include "packager/media/base/raw_key_source.h" 21 #include "packager/media/base/widevine_key_source.h" 22 #include "packager/media/base/widevine_pssh_data.pb.h" 32 const char kUriBase64Prefix[] =
"data:text/plain;base64,";
33 const char kUriFairplayPrefix[] =
"skd://";
34 const char kWidevineDashIfIopUUID[] =
35 "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
37 bool IsWidevineSystemId(
const std::vector<uint8_t>& system_id) {
38 return system_id.size() == arraysize(media::kWidevineSystemId) &&
39 std::equal(system_id.begin(), system_id.end(),
40 media::kWidevineSystemId);
43 bool IsCommonSystemId(
const std::vector<uint8_t>& system_id) {
44 return system_id.size() == arraysize(media::kCommonSystemId) &&
45 std::equal(system_id.begin(), system_id.end(), media::kCommonSystemId);
48 bool IsFairplaySystemId(
const std::vector<uint8_t>& system_id) {
49 return system_id.size() == arraysize(media::kFairplaySystemId) &&
50 std::equal(system_id.begin(), system_id.end(), media::kFairplaySystemId);
53 std::string Base64EncodeData(
const std::string& prefix,
54 const std::string& data) {
55 std::string data_base64;
56 base::Base64Encode(data, &data_base64);
57 return prefix + data_base64;
60 std::string VectorToString(
const std::vector<uint8_t>& v) {
61 return std::string(v.begin(), v.end());
73 std::string MakePathRelative(
const std::string& media_path,
74 const FilePath& parent_path) {
75 FilePath relative_path;
76 const FilePath child_path = FilePath::FromUTF8Unsafe(media_path);
78 parent_path.AppendRelativePath(child_path, &relative_path);
80 relative_path = child_path;
81 return relative_path.NormalizePathSeparatorsTo(
'/').AsUTF8Unsafe();
86 std::string GenerateSegmentUrl(
const std::string& segment_name,
87 const std::string& base_url,
88 const std::string& output_dir,
89 const std::string& playlist_file_name) {
90 FilePath output_path = FilePath::FromUTF8Unsafe(output_dir);
91 if (!base_url.empty()) {
94 return base_url + MakePathRelative(segment_name, output_path);
98 const FilePath playlist_dir =
99 output_path.Append(FilePath::FromUTF8Unsafe(playlist_file_name))
101 .AsEndingWithSeparator();
102 return MakePathRelative(segment_name, playlist_dir);
105 bool WidevinePsshToJson(
const std::vector<uint8_t>& pssh_box,
106 const std::vector<uint8_t>& key_id,
107 std::string* pssh_json) {
108 media::ProtectionSystemSpecificInfo pssh_info;
109 if (!pssh_info.Parse(pssh_box.data(), pssh_box.size())) {
110 LOG(ERROR) <<
"Failed to parse PSSH box.";
114 media::WidevinePsshData pssh_proto;
115 if (!pssh_proto.ParseFromArray(pssh_info.pssh_data().data(),
116 pssh_info.pssh_data().size())) {
117 LOG(ERROR) <<
"Failed to parse protection_system_specific_data.";
120 if (!pssh_proto.has_provider() ||
121 (!pssh_proto.has_content_id() && pssh_proto.key_id_size() == 0)) {
122 LOG(ERROR) <<
"Missing fields to generate URI.";
126 base::DictionaryValue pssh_dict;
127 pssh_dict.SetString(
"provider", pssh_proto.provider());
128 if (pssh_proto.has_content_id()) {
129 std::string content_id_base64;
130 base::Base64Encode(base::StringPiece(pssh_proto.content_id().data(),
131 pssh_proto.content_id().size()),
133 pssh_dict.SetString(
"content_id", content_id_base64);
135 base::ListValue* key_ids =
new base::ListValue();
137 key_ids->AppendString(base::HexEncode(key_id.data(), key_id.size()));
138 for (
const std::string&
id : pssh_proto.key_id()) {
139 if (key_id.size() ==
id.size() &&
140 memcmp(key_id.data(),
id.data(),
id.size()) == 0) {
143 key_ids->AppendString(base::HexEncode(
id.data(),
id.size()));
145 pssh_dict.Set(
"key_ids", key_ids);
147 if (!base::JSONWriter::Write(pssh_dict, pssh_json)) {
148 LOG(ERROR) <<
"Failed to write to JSON.";
154 base::Optional<MediaPlaylist::EncryptionMethod> StringToEncryptionMethod(
155 const std::string& method) {
156 if (method ==
"cenc") {
157 return MediaPlaylist::EncryptionMethod::kSampleAesCenc;
158 }
else if (method ==
"cbcs") {
159 return MediaPlaylist::EncryptionMethod::kSampleAes;
160 }
else if (method ==
"cbca") {
162 return MediaPlaylist::EncryptionMethod::kSampleAes;
164 return base::nullopt;
168 void NotifyEncryptionToMediaPlaylist(
169 MediaPlaylist::EncryptionMethod encryption_method,
170 const std::string& uri,
171 const std::vector<uint8_t>& key_id,
172 const std::vector<uint8_t>& iv,
173 const std::string& key_format,
174 const std::string& key_format_version,
175 MediaPlaylist* media_playlist) {
176 std::string iv_string;
178 iv_string =
"0x" + base::HexEncode(iv.data(), iv.size());
180 std::string key_id_string;
181 if (!key_id.empty()) {
182 key_id_string =
"0x" + base::HexEncode(key_id.data(), key_id.size());
185 media_playlist->AddEncryptionInfo(
187 uri, key_id_string, iv_string,
188 key_format, key_format_version);
192 bool HandleWidevineKeyFormats(
193 MediaPlaylist::EncryptionMethod encryption_method,
194 const std::vector<uint8_t>& key_id,
195 const std::vector<uint8_t>& iv,
196 const std::vector<uint8_t>& protection_system_specific_data,
197 MediaPlaylist* media_playlist) {
198 if (encryption_method == MediaPlaylist::EncryptionMethod::kSampleAes) {
200 std::string key_uri_data;
201 if (!WidevinePsshToJson(protection_system_specific_data, key_id,
205 std::string key_uri_data_base64 =
206 Base64EncodeData(kUriBase64Prefix, key_uri_data);
207 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64,
208 std::vector<uint8_t>(), iv,
"com.widevine",
209 "1", media_playlist);
212 std::string pssh_as_string(
213 reinterpret_cast<const char*>(protection_system_specific_data.data()),
214 protection_system_specific_data.size());
215 std::string key_uri_data_base64 =
216 Base64EncodeData(kUriBase64Prefix, pssh_as_string);
217 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64,
218 key_id, iv, kWidevineDashIfIopUUID,
"1",
223 bool WriteMediaPlaylist(
const std::string& output_dir,
224 MediaPlaylist* playlist) {
225 std::string file_path =
226 FilePath::FromUTF8Unsafe(output_dir)
227 .Append(FilePath::FromUTF8Unsafe(playlist->file_name()))
229 if (!playlist->WriteToFile(file_path)) {
230 LOG(ERROR) <<
"Failed to write playlist " << file_path;
238 MediaPlaylistFactory::~MediaPlaylistFactory() {}
240 std::unique_ptr<MediaPlaylist> MediaPlaylistFactory::Create(
242 double time_shift_buffer_depth,
243 const std::string& file_name,
244 const std::string& name,
245 const std::string& group_id) {
246 return std::unique_ptr<MediaPlaylist>(
new MediaPlaylist(
247 type, time_shift_buffer_depth, file_name, name, group_id));
252 time_shift_buffer_depth_(hls_params.time_shift_buffer_depth),
253 prefix_(hls_params.base_url),
254 key_uri_(hls_params.key_uri),
256 const base::FilePath master_playlist_path(
258 output_dir_ = master_playlist_path.DirName().AsUTF8Unsafe();
259 master_playlist_.reset(
260 new MasterPlaylist(master_playlist_path.BaseName().AsUTF8Unsafe(),
264 SimpleHlsNotifier::~SimpleHlsNotifier() {}
271 const std::string& playlist_name,
272 const std::string& name,
273 const std::string& group_id,
274 uint32_t* stream_id) {
277 std::unique_ptr<MediaPlaylist> media_playlist =
278 media_playlist_factory_->Create(
playlist_type(), time_shift_buffer_depth_,
279 playlist_name, name, group_id);
282 MediaInfo media_info_copy = media_info;
283 if (media_info_copy.has_init_segment_name()) {
284 media_info_copy.set_init_segment_name(
285 GenerateSegmentUrl(media_info_copy.init_segment_name(), prefix_,
286 output_dir_, media_playlist->file_name()));
288 if (media_info_copy.has_media_file_name()) {
289 media_info_copy.set_media_file_name(
290 GenerateSegmentUrl(media_info_copy.media_file_name(), prefix_,
291 output_dir_, media_playlist->file_name()));
293 if (!media_playlist->SetMediaInfo(media_info_copy)) {
294 LOG(ERROR) <<
"Failed to set media info for playlist " << playlist_name;
298 MediaPlaylist::EncryptionMethod encryption_method =
299 MediaPlaylist::EncryptionMethod::kNone;
300 if (media_info.protected_content().has_protection_scheme()) {
301 const std::string& protection_scheme =
302 media_info.protected_content().protection_scheme();
303 base::Optional<MediaPlaylist::EncryptionMethod> enc_method =
304 StringToEncryptionMethod(protection_scheme);
306 LOG(ERROR) <<
"Failed to recognize protection scheme " 307 << protection_scheme;
310 encryption_method = enc_method.value();
313 *stream_id = sequence_number_.GetNext();
314 base::AutoLock auto_lock(lock_);
315 media_playlists_.push_back(media_playlist.get());
316 stream_map_[*stream_id].reset(
317 new StreamEntry{std::move(media_playlist), encryption_method});
322 const std::string& segment_name,
325 uint64_t start_byte_offset,
327 base::AutoLock auto_lock(lock_);
328 auto stream_iterator = stream_map_.find(stream_id);
329 if (stream_iterator == stream_map_.end()) {
330 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
333 auto& media_playlist = stream_iterator->second->media_playlist;
334 const std::string& segment_url = GenerateSegmentUrl(
335 segment_name, prefix_, output_dir_, media_playlist->file_name());
336 media_playlist->AddSegment(segment_url, start_time, duration,
337 start_byte_offset, size);
340 uint32_t longest_segment_duration =
341 static_cast<uint32_t
>(ceil(media_playlist->GetLongestSegmentDuration()));
342 bool target_duration_updated =
false;
343 if (longest_segment_duration > target_duration_) {
344 target_duration_ = longest_segment_duration;
345 target_duration_updated =
true;
352 if (target_duration_updated) {
354 playlist->SetTargetDuration(target_duration_);
355 if (!WriteMediaPlaylist(output_dir_, playlist))
359 if (!WriteMediaPlaylist(output_dir_, media_playlist.get()))
362 if (!master_playlist_->WriteMasterPlaylist(prefix_, output_dir_,
364 LOG(ERROR) <<
"Failed to write master playlist.";
373 uint64_t start_byte_offset,
375 base::AutoLock auto_lock(lock_);
376 auto stream_iterator = stream_map_.find(stream_id);
377 if (stream_iterator == stream_map_.end()) {
378 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
381 auto& media_playlist = stream_iterator->second->media_playlist;
382 media_playlist->AddKeyFrame(timestamp, start_byte_offset, size);
387 base::AutoLock auto_lock(lock_);
388 auto stream_iterator = stream_map_.find(stream_id);
389 if (stream_iterator == stream_map_.end()) {
390 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
393 auto& media_playlist = stream_iterator->second->media_playlist;
394 media_playlist->AddPlacementOpportunity();
400 const std::vector<uint8_t>& key_id,
401 const std::vector<uint8_t>& system_id,
402 const std::vector<uint8_t>& iv,
403 const std::vector<uint8_t>& protection_system_specific_data) {
404 base::AutoLock auto_lock(lock_);
405 auto stream_iterator = stream_map_.find(stream_id);
406 if (stream_iterator == stream_map_.end()) {
407 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
411 std::unique_ptr<MediaPlaylist>& media_playlist =
412 stream_iterator->second->media_playlist;
413 const MediaPlaylist::EncryptionMethod encryption_method =
414 stream_iterator->second->encryption_method;
415 LOG_IF(WARNING, encryption_method == MediaPlaylist::EncryptionMethod::kNone)
416 <<
"Got encryption notification but the encryption method is NONE";
417 if (IsWidevineSystemId(system_id)) {
418 return HandleWidevineKeyFormats(encryption_method,
419 key_id, iv, protection_system_specific_data,
420 media_playlist.get());
424 const std::vector<uint8_t> empty_key_id;
426 if (IsCommonSystemId(system_id)) {
428 if (!key_uri_.empty()) {
433 std::string key_uri_data = VectorToString(key_id);
434 key_uri = Base64EncodeData(kUriBase64Prefix, key_uri_data);
436 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri, empty_key_id,
437 iv,
"identity",
"", media_playlist.get());
439 }
else if (IsFairplaySystemId(system_id)) {
441 if (!key_uri_.empty()) {
446 std::string key_uri_data = VectorToString(key_id);
447 key_uri = Base64EncodeData(kUriFairplayPrefix, key_uri_data);
451 const std::vector<uint8_t> empty_iv;
452 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri, empty_key_id,
453 empty_iv,
"com.apple.streamingkeydelivery",
454 "1", media_playlist.get());
458 LOG(ERROR) <<
"Unknown system ID: " 459 << base::HexEncode(system_id.data(), system_id.size());
464 base::AutoLock auto_lock(lock_);
466 playlist->SetTargetDuration(target_duration_);
467 if (!WriteMediaPlaylist(output_dir_, playlist))
470 if (!master_playlist_->WriteMasterPlaylist(prefix_, output_dir_,
472 LOG(ERROR) <<
"Failed to write master playlist.";
std::string master_playlist_output
HLS master playlist output path.
bool NotifyNewSegment(uint32_t stream_id, const std::string &segment_name, uint64_t start_time, uint64_t duration, uint64_t start_byte_offset, uint64_t size) override
SimpleHlsNotifier(const HlsParams &hls_params)
All the methods that are virtual are virtual for mocking.
bool NotifyCueEvent(uint32_t container_id, uint64_t timestamp) override
bool NotifyNewStream(const MediaInfo &media_info, const std::string &playlist_name, const std::string &stream_name, const std::string &group_id, uint32_t *stream_id) override
HlsPlaylistType playlist_type() const
std::string default_language
bool NotifyKeyFrame(uint32_t stream_id, uint64_t timestamp, uint64_t start_byte_offset, uint64_t size) override
bool 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) override