7#include <packager/hls/base/simple_hls_notifier.h>
13#include <absl/flags/flag.h>
14#include <absl/log/check.h>
15#include <absl/log/log.h>
16#include <absl/strings/escaping.h>
17#include <absl/strings/numbers.h>
19#include <packager/file/file_util.h>
20#include <packager/hls/base/master_playlist.h>
21#include <packager/media/base/protection_system_ids.h>
22#include <packager/media/base/protection_system_specific_info.h>
23#include <packager/media/base/proto_json_util.h>
24#include <packager/media/base/widevine_pssh_data.pb.h>
27 enable_legacy_widevine_hls_signaling,
29 "Specifies whether Legacy Widevine HLS, i.e. v1 is signalled in "
30 "the media playlist. Applies to Widevine protection system in HLS "
31 "with SAMPLE-AES only.");
39const char kUriBase64Prefix[] =
"data:text/plain;base64,";
40const char kUriBase64Utf16Prefix[] =
"data:text/plain;charset=UTF-16;base64,";
41const char kUriFairPlayPrefix[] =
"skd://";
42const char kWidevineDashIfIopUUID[] =
43 "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
45bool IsWidevineSystemId(
const std::vector<uint8_t>& system_id) {
46 return system_id.size() == std::size(media::kWidevineSystemId) &&
47 std::equal(system_id.begin(), system_id.end(),
48 media::kWidevineSystemId);
51bool IsCommonSystemId(
const std::vector<uint8_t>& system_id) {
52 return system_id.size() == std::size(media::kCommonSystemId) &&
53 std::equal(system_id.begin(), system_id.end(), media::kCommonSystemId);
56bool IsFairPlaySystemId(
const std::vector<uint8_t>& system_id) {
57 return system_id.size() == std::size(media::kFairPlaySystemId) &&
58 std::equal(system_id.begin(), system_id.end(),
59 media::kFairPlaySystemId);
62bool IsLegacyFairPlaySystemId(
const std::vector<uint8_t>& system_id) {
63 return system_id.size() == std::size(media::kLegacyFairPlaySystemId) &&
64 std::equal(system_id.begin(), system_id.end(),
65 media::kLegacyFairPlaySystemId);
68bool IsPlayReadySystemId(
const std::vector<uint8_t>& system_id) {
69 return system_id.size() == std::size(media::kPlayReadySystemId) &&
70 std::equal(system_id.begin(), system_id.end(),
71 media::kPlayReadySystemId);
74std::string Base64EncodeData(
const std::string& prefix,
75 const std::string& data) {
76 std::string data_base64;
77 absl::Base64Escape(data, &data_base64);
78 return prefix + data_base64;
81std::string VectorToString(
const std::vector<uint8_t>& v) {
82 return std::string(v.begin(), v.end());
87std::string GenerateSegmentUrl(
const std::string& segment_name,
88 const std::string& base_url,
89 const std::string& output_dir,
90 const std::string& playlist_file_name) {
91 auto output_path = std::filesystem::u8path(output_dir);
92 if (!base_url.empty()) {
95 return base_url + MakePathRelative(segment_name, output_path);
99 const std::filesystem::path playlist_dir =
100 (output_path / playlist_file_name).parent_path() /
"";
101 return MakePathRelative(segment_name, playlist_dir);
104MediaInfo MakeMediaInfoPathsRelativeToPlaylist(
105 const MediaInfo& media_info,
106 const std::string& base_url,
107 const std::string& output_dir,
108 const std::string& playlist_name) {
109 MediaInfo media_info_copy = media_info;
110 if (media_info_copy.has_init_segment_name()) {
111 media_info_copy.set_init_segment_url(
112 GenerateSegmentUrl(media_info_copy.init_segment_name(), base_url,
113 output_dir, playlist_name));
115 if (media_info_copy.has_media_file_name()) {
116 media_info_copy.set_media_file_url(
117 GenerateSegmentUrl(media_info_copy.media_file_name(), base_url,
118 output_dir, playlist_name));
120 if (media_info_copy.has_segment_template()) {
121 media_info_copy.set_segment_template_url(
122 GenerateSegmentUrl(media_info_copy.segment_template(), base_url,
123 output_dir, playlist_name));
125 return media_info_copy;
128bool WidevinePsshToJson(
const std::vector<uint8_t>& pssh_box,
129 const std::vector<uint8_t>& key_id,
130 std::string* pssh_json) {
131 std::unique_ptr<media::PsshBoxBuilder> pssh_builder =
134 LOG(ERROR) <<
"Failed to parse PSSH box.";
138 media::WidevinePsshData pssh_proto;
139 if (!pssh_proto.ParseFromArray(pssh_builder->pssh_data().data(),
140 pssh_builder->pssh_data().size())) {
141 LOG(ERROR) <<
"Failed to parse protection_system_specific_data.";
145 media::WidevineHeader widevine_header;
147 if (pssh_proto.has_provider()) {
148 widevine_header.set_provider(pssh_proto.provider());
150 LOG(WARNING) <<
"Missing provider in Widevine PSSH. The content may not "
151 "play in some devices.";
154 if (pssh_proto.has_content_id()) {
155 widevine_header.set_content_id(pssh_proto.content_id());
157 LOG(WARNING) <<
"Missing content_id in Widevine PSSH. The content may not "
158 "play in some devices.";
163 widevine_header.add_key_ids(absl::BytesToHexString(absl::string_view(
164 reinterpret_cast<const char*
>(key_id.data()), key_id.size())));
165 for (
const std::string& key_id_in_pssh : pssh_proto.key_id()) {
166 const std::string key_id_hex = absl::BytesToHexString(
167 absl::string_view(
reinterpret_cast<const char*
>(key_id_in_pssh.data()),
168 key_id_in_pssh.size()));
169 if (widevine_header.key_ids(0) != key_id_hex)
170 widevine_header.add_key_ids(key_id_hex);
173 *pssh_json = media::MessageToJsonString(widevine_header);
177std::optional<MediaPlaylist::EncryptionMethod> StringToEncryptionMethod(
178 const std::string& method) {
179 if (method ==
"cenc") {
180 return MediaPlaylist::EncryptionMethod::kSampleAesCenc;
182 if (method ==
"cbcs") {
183 return MediaPlaylist::EncryptionMethod::kSampleAes;
185 if (method ==
"cbca") {
187 return MediaPlaylist::EncryptionMethod::kSampleAes;
189 if (method ==
"aes8") {
191 return MediaPlaylist::EncryptionMethod::kAes128;
196void NotifyEncryptionToMediaPlaylist(
197 MediaPlaylist::EncryptionMethod encryption_method,
198 const std::string& uri,
199 const std::vector<uint8_t>& key_id,
200 const std::vector<uint8_t>& iv,
201 const std::string& key_format,
202 const std::string& key_format_version,
203 MediaPlaylist* media_playlist) {
204 std::string iv_string;
207 "0x" + absl::BytesToHexString(absl::string_view(
208 reinterpret_cast<const char*
>(iv.data()), iv.size()));
210 std::string key_id_string;
211 if (!key_id.empty()) {
212 key_id_string =
"0x" + absl::BytesToHexString(absl::string_view(
213 reinterpret_cast<const char*
>(key_id.data()),
217 media_playlist->AddEncryptionInfo(encryption_method, uri, key_id_string,
218 iv_string, key_format, key_format_version);
222bool HandleWidevineKeyFormats(
223 MediaPlaylist::EncryptionMethod encryption_method,
224 const std::vector<uint8_t>& key_id,
225 const std::vector<uint8_t>& iv,
226 const std::vector<uint8_t>& protection_system_specific_data,
227 MediaPlaylist* media_playlist) {
228 if (absl::GetFlag(FLAGS_enable_legacy_widevine_hls_signaling) &&
229 encryption_method == MediaPlaylist::EncryptionMethod::kSampleAes) {
231 std::string key_uri_data;
232 if (!WidevinePsshToJson(protection_system_specific_data, key_id,
236 std::string key_uri_data_base64 =
237 Base64EncodeData(kUriBase64Prefix, key_uri_data);
238 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64,
239 std::vector<uint8_t>(), iv,
"com.widevine",
240 "1", media_playlist);
243 std::string pssh_as_string(
244 reinterpret_cast<const char*
>(protection_system_specific_data.data()),
245 protection_system_specific_data.size());
246 std::string key_uri_data_base64 =
247 Base64EncodeData(kUriBase64Prefix, pssh_as_string);
248 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64,
249 key_id, iv, kWidevineDashIfIopUUID,
"1",
254bool WriteMediaPlaylist(
const std::string& output_dir,
255 MediaPlaylist* playlist,
256 const bool event_to_vod_on_end_of_stream,
257 const bool end_stream) {
258 auto file_path = std::filesystem::u8path(output_dir) / playlist->file_name();
259 if (!playlist->
WriteToFile(file_path, event_to_vod_on_end_of_stream,
261 LOG(ERROR) <<
"Failed to write playlist " << file_path.string();
269MediaPlaylistFactory::~MediaPlaylistFactory() {}
271std::unique_ptr<MediaPlaylist> MediaPlaylistFactory::Create(
272 const HlsParams& hls_params,
273 const std::string& file_name,
274 const std::string& name,
275 const std::string& group_id) {
276 return std::unique_ptr<MediaPlaylist>(
277 new MediaPlaylist(hls_params, file_name, name, group_id));
284 reference_time_ = absl::Now();
286 const auto master_playlist_path =
287 std::filesystem::u8path(
hls_params.master_playlist_output);
288 master_playlist_dir_ = master_playlist_path.parent_path().string();
289 const std::string& default_audio_langauge =
hls_params.default_language;
290 const std::string& default_text_language =
295 std::vector<CeaCaption> closed_captions;
296 for (
const auto& caption :
hls_params.closed_captions) {
297 closed_captions.push_back({caption.name, caption.language, caption.channel,
298 caption.is_default, caption.autoselect});
302 master_playlist_path.filename(), default_audio_langauge,
303 default_text_language, closed_captions,
307SimpleHlsNotifier::~SimpleHlsNotifier() {}
314 const std::string& playlist_name,
315 const std::string& name,
316 const std::string& group_id,
317 uint32_t* stream_id) {
320 const std::string relative_playlist_path = MakePathRelative(
321 playlist_name, std::filesystem::u8path(master_playlist_dir_));
323 std::unique_ptr<MediaPlaylist> media_playlist =
324 media_playlist_factory_->Create(
hls_params(), relative_playlist_path,
326 MediaInfo adjusted_media_info = MakeMediaInfoPathsRelativeToPlaylist(
327 media_info,
hls_params().base_url, master_playlist_dir_,
328 media_playlist->file_name());
329 if (!media_playlist->SetMediaInfo(adjusted_media_info)) {
330 LOG(ERROR) <<
"Failed to set media info for playlist " << playlist_name;
335 MediaPlaylist::EncryptionMethod encryption_method =
336 MediaPlaylist::EncryptionMethod::kNone;
337 if (media_info.protected_content().has_protection_scheme()) {
338 const std::string& protection_scheme =
339 media_info.protected_content().protection_scheme();
340 std::optional<MediaPlaylist::EncryptionMethod> enc_method =
341 StringToEncryptionMethod(protection_scheme);
343 LOG(ERROR) <<
"Failed to recognize protection scheme "
344 << protection_scheme;
347 encryption_method = enc_method.value();
350 absl::MutexLock lock(lock_);
351 *stream_id = sequence_number_++;
352 media_playlists_.push_back(media_playlist.get());
353 stream_map_[*stream_id].reset(
354 new StreamEntry{std::move(media_playlist), encryption_method});
359 int32_t sample_duration) {
360 absl::MutexLock lock(lock_);
361 auto stream_iterator = stream_map_.find(stream_id);
362 if (stream_iterator == stream_map_.end()) {
363 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
366 auto& media_playlist = stream_iterator->second->media_playlist;
367 media_playlist->SetSampleDuration(sample_duration);
372 const std::string& segment_name,
375 uint64_t start_byte_offset,
377 absl::MutexLock lock(lock_);
378 auto stream_iterator = stream_map_.find(stream_id);
379 if (stream_iterator == stream_map_.end()) {
380 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
383 auto& media_playlist = stream_iterator->second->media_playlist;
384 const std::string& segment_url =
385 GenerateSegmentUrl(segment_name,
hls_params().base_url,
386 master_playlist_dir_, media_playlist->file_name());
387 media_playlist->AddSegment(segment_url, start_time, duration,
388 start_byte_offset, size);
391 int32_t longest_segment_duration =
392 static_cast<int32_t
>(ceil(media_playlist->GetLongestSegmentDuration()));
393 bool target_duration_updated =
false;
394 if (longest_segment_duration > target_duration_) {
395 target_duration_ = longest_segment_duration;
396 target_duration_updated =
true;
400 if (
hls_params().playlist_type == HlsPlaylistType::kLive ||
401 hls_params().playlist_type == HlsPlaylistType::kEvent) {
403 if (target_duration_updated) {
405 playlist->SetTargetDuration(target_duration_);
406 if (!WriteMediaPlaylist(master_playlist_dir_, playlist,
412 if (!WriteMediaPlaylist(master_playlist_dir_, media_playlist.get(),
417 if (!master_playlist_->WriteMasterPlaylist(
418 hls_params().base_url, master_playlist_dir_, media_playlists_)) {
419 LOG(ERROR) <<
"Failed to write master playlist.";
428 uint64_t start_byte_offset,
430 absl::MutexLock lock(lock_);
431 auto stream_iterator = stream_map_.find(stream_id);
432 if (stream_iterator == stream_map_.end()) {
433 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
436 auto& media_playlist = stream_iterator->second->media_playlist;
437 media_playlist->AddKeyFrame(timestamp, start_byte_offset, size);
442 absl::MutexLock lock(lock_);
443 auto stream_iterator = stream_map_.find(stream_id);
444 if (stream_iterator == stream_map_.end()) {
445 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
448 auto& media_playlist = stream_iterator->second->media_playlist;
449 media_playlist->AddPlacementOpportunity();
455 const std::vector<uint8_t>& key_id,
456 const std::vector<uint8_t>& system_id,
457 const std::vector<uint8_t>& iv,
458 const std::vector<uint8_t>& protection_system_specific_data) {
459 absl::MutexLock lock(lock_);
460 auto stream_iterator = stream_map_.find(stream_id);
461 if (stream_iterator == stream_map_.end()) {
462 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
466 std::unique_ptr<MediaPlaylist>& media_playlist =
467 stream_iterator->second->media_playlist;
468 const MediaPlaylist::EncryptionMethod encryption_method =
469 stream_iterator->second->encryption_method;
470 LOG_IF(WARNING, encryption_method == MediaPlaylist::EncryptionMethod::kNone)
471 <<
"Got encryption notification but the encryption method is NONE";
475 if (encryption_method == MediaPlaylist::EncryptionMethod::kAes128) {
476 const std::string key_uri =
hls_params().key_uri;
477 if (key_uri.empty()) {
478 LOG(ERROR) <<
"AES-128 encryption requires --hls_key_uri to be set.";
484 const std::vector<uint8_t> empty_key_id;
485 NotifyEncryptionToMediaPlaylist(
486 encryption_method, key_uri, empty_key_id, iv,
"",
487 "", media_playlist.get());
491 if (IsWidevineSystemId(system_id)) {
492 return HandleWidevineKeyFormats(encryption_method, key_id, iv,
493 protection_system_specific_data,
494 media_playlist.get());
498 const std::vector<uint8_t> empty_key_id;
500 if (IsCommonSystemId(system_id)) {
501 const MediaPlaylist::EncryptionMethod encryption_method_from_stream =
502 stream_iterator->second->encryption_method;
504 if (encryption_method_from_stream ==
505 MediaPlaylist::EncryptionMethod::kSampleAesCenc) {
508 LOG(INFO) <<
"Skipping KEYFORMAT=\"identity\" for CENC content (stream "
510 <<
") as it should be handled by a specific DRM system.";
515 if (key_uri.empty()) {
518 std::string key_uri_data = VectorToString(key_id);
519 key_uri = Base64EncodeData(kUriBase64Prefix, key_uri_data);
521 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri, empty_key_id,
522 iv,
"identity",
"", media_playlist.get());
525 if (IsFairPlaySystemId(system_id) || IsLegacyFairPlaySystemId(system_id)) {
527 if (key_uri.empty()) {
530 std::string key_uri_data = VectorToString(key_id);
531 key_uri = Base64EncodeData(kUriFairPlayPrefix, key_uri_data);
535 const std::vector<uint8_t> empty_iv;
536 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri, empty_key_id,
537 empty_iv,
"com.apple.streamingkeydelivery",
538 "1", media_playlist.get());
541 if (IsPlayReadySystemId(system_id)) {
542 std::unique_ptr<media::PsshBoxBuilder> b =
544 protection_system_specific_data.data(),
545 protection_system_specific_data.size());
546 std::string pssh_data(
reinterpret_cast<const char*
>(b->pssh_data().data()),
547 b->pssh_data().size());
548 std::string key_uri_data_base64 =
549 Base64EncodeData(kUriBase64Utf16Prefix, pssh_data);
550 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64,
551 empty_key_id, iv,
"com.microsoft.playready",
552 "1", media_playlist.get());
556 LOG(WARNING) <<
"HLS: Ignore unknown or unsupported system ID: "
557 << absl::BytesToHexString(absl::string_view(
558 reinterpret_cast<const char*
>(system_id.data()),
563bool SimpleHlsNotifier::NotifyEndOfStream() {
569 absl::MutexLock lock(lock_);
571 if (
hls_params().per_playlist_target_duration) {
572 playlist->SetTargetDuration(
573 static_cast<int32_t
>(ceil(playlist->GetLongestSegmentDuration())));
575 playlist->SetTargetDuration(target_duration_);
577 if (!WriteMediaPlaylist(master_playlist_dir_, playlist,
582 if (!master_playlist_->WriteMasterPlaylist(
583 hls_params().base_url, master_playlist_dir_, media_playlists_)) {
584 LOG(ERROR) <<
"Failed to write master playlist.";
const HlsParams & hls_params() const
bool NotifyNewSegment(uint32_t stream_id, const std::string &segment_name, int64_t start_time, int64_t duration, uint64_t start_byte_offset, uint64_t size) override
SimpleHlsNotifier(const HlsParams &hls_params)
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
bool NotifyKeyFrame(uint32_t stream_id, int64_t timestamp, uint64_t start_byte_offset, uint64_t size) override
const absl::Time & reference_time() const
}@
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
bool NotifyCueEvent(uint32_t container_id, int64_t timestamp) override
bool NotifySampleDuration(uint32_t stream_id, int32_t sample_duration) override
All the methods that are virtual are virtual for mocking.