7 #include "packager/hls/base/simple_hls_notifier.h" 9 #include <gflags/gflags.h> 12 #include "packager/base/base64.h" 13 #include "packager/base/files/file_path.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_ids.h" 20 #include "packager/media/base/protection_system_specific_info.h" 21 #include "packager/media/base/proto_json_util.h" 22 #include "packager/media/base/widevine_pssh_data.pb.h" 24 DEFINE_bool(enable_legacy_widevine_hls_signaling,
26 "Specifies whether Legacy Widevine HLS, i.e. v1 is signalled in " 27 "the media playlist. Applies to Widevine protection system in HLS " 28 "with SAMPLE-AES only.");
38 const char kUriBase64Prefix[] =
"data:text/plain;base64,";
39 const char kUriFairPlayPrefix[] =
"skd://";
40 const char kWidevineDashIfIopUUID[] =
41 "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
43 bool IsWidevineSystemId(
const std::vector<uint8_t>& system_id) {
44 return system_id.size() == arraysize(media::kWidevineSystemId) &&
45 std::equal(system_id.begin(), system_id.end(),
46 media::kWidevineSystemId);
49 bool IsCommonSystemId(
const std::vector<uint8_t>& system_id) {
50 return system_id.size() == arraysize(media::kCommonSystemId) &&
51 std::equal(system_id.begin(), system_id.end(), media::kCommonSystemId);
54 bool IsFairPlaySystemId(
const std::vector<uint8_t>& system_id) {
55 return system_id.size() == arraysize(media::kFairPlaySystemId) &&
56 std::equal(system_id.begin(), system_id.end(),
57 media::kFairPlaySystemId);
60 std::string Base64EncodeData(
const std::string& prefix,
61 const std::string& data) {
62 std::string data_base64;
63 base::Base64Encode(data, &data_base64);
64 return prefix + data_base64;
67 std::string VectorToString(
const std::vector<uint8_t>& v) {
68 return std::string(v.begin(), v.end());
80 std::string MakePathRelative(
const std::string& media_path,
81 const FilePath& parent_path) {
82 FilePath relative_path;
83 const FilePath child_path = FilePath::FromUTF8Unsafe(media_path);
85 parent_path.AppendRelativePath(child_path, &relative_path);
87 relative_path = child_path;
88 return relative_path.NormalizePathSeparatorsTo(
'/').AsUTF8Unsafe();
93 std::string GenerateSegmentUrl(
const std::string& segment_name,
94 const std::string& base_url,
95 const std::string& output_dir,
96 const std::string& playlist_file_name) {
97 FilePath output_path = FilePath::FromUTF8Unsafe(output_dir);
98 if (!base_url.empty()) {
101 return base_url + MakePathRelative(segment_name, output_path);
105 const FilePath playlist_dir =
106 output_path.Append(FilePath::FromUTF8Unsafe(playlist_file_name))
108 .AsEndingWithSeparator();
109 return MakePathRelative(segment_name, playlist_dir);
112 MediaInfo MakeMediaInfoPathsRelativeToPlaylist(
113 const MediaInfo& media_info,
114 const std::string& base_url,
115 const std::string& output_dir,
116 const std::string& playlist_name) {
117 MediaInfo media_info_copy = media_info;
118 if (media_info_copy.has_init_segment_name()) {
119 media_info_copy.set_init_segment_url(
120 GenerateSegmentUrl(media_info_copy.init_segment_name(), base_url,
121 output_dir, playlist_name));
123 if (media_info_copy.has_media_file_name()) {
124 media_info_copy.set_media_file_url(
125 GenerateSegmentUrl(media_info_copy.media_file_name(), base_url,
126 output_dir, playlist_name));
128 if (media_info_copy.has_segment_template()) {
129 media_info_copy.set_segment_template_url(
130 GenerateSegmentUrl(media_info_copy.segment_template(), base_url,
131 output_dir, playlist_name));
133 return media_info_copy;
136 bool WidevinePsshToJson(
const std::vector<uint8_t>& pssh_box,
137 const std::vector<uint8_t>& key_id,
138 std::string* pssh_json) {
139 std::unique_ptr<media::PsshBoxBuilder> pssh_builder =
142 LOG(ERROR) <<
"Failed to parse PSSH box.";
146 media::WidevinePsshData pssh_proto;
147 if (!pssh_proto.ParseFromArray(pssh_builder->pssh_data().data(),
148 pssh_builder->pssh_data().size())) {
149 LOG(ERROR) <<
"Failed to parse protection_system_specific_data.";
153 media::WidevineHeader widevine_header;
155 if (pssh_proto.has_provider()) {
156 widevine_header.set_provider(pssh_proto.provider());
158 LOG(WARNING) <<
"Missing provider in Widevine PSSH. The content may not " 159 "play in some devices.";
162 if (pssh_proto.has_content_id()) {
163 widevine_header.set_content_id(pssh_proto.content_id());
165 LOG(WARNING) <<
"Missing content_id in Widevine PSSH. The content may not " 166 "play in some devices.";
171 widevine_header.add_key_ids(base::HexEncode(key_id.data(), key_id.size()));
172 for (
const std::string& key_id_in_pssh : pssh_proto.key_id()) {
173 const std::string key_id_hex =
174 base::HexEncode(key_id_in_pssh.data(), key_id_in_pssh.size());
175 if (widevine_header.key_ids(0) != key_id_hex)
176 widevine_header.add_key_ids(key_id_hex);
179 *pssh_json = media::MessageToJsonString(widevine_header);
183 base::Optional<MediaPlaylist::EncryptionMethod> StringToEncryptionMethod(
184 const std::string& method) {
185 if (method ==
"cenc") {
186 return MediaPlaylist::EncryptionMethod::kSampleAesCenc;
188 if (method ==
"cbcs") {
189 return MediaPlaylist::EncryptionMethod::kSampleAes;
191 if (method ==
"cbca") {
193 return MediaPlaylist::EncryptionMethod::kSampleAes;
195 return base::nullopt;
198 void NotifyEncryptionToMediaPlaylist(
199 MediaPlaylist::EncryptionMethod encryption_method,
200 const std::string& uri,
201 const std::vector<uint8_t>& key_id,
202 const std::vector<uint8_t>& iv,
203 const std::string& key_format,
204 const std::string& key_format_version,
205 MediaPlaylist* media_playlist) {
206 std::string iv_string;
208 iv_string =
"0x" + base::HexEncode(iv.data(), iv.size());
210 std::string key_id_string;
211 if (!key_id.empty()) {
212 key_id_string =
"0x" + base::HexEncode(key_id.data(), key_id.size());
215 media_playlist->AddEncryptionInfo(
217 uri, key_id_string, iv_string,
218 key_format, key_format_version);
222 bool 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 (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",
254 bool WriteMediaPlaylist(
const std::string& output_dir,
255 MediaPlaylist* playlist) {
256 std::string file_path =
257 FilePath::FromUTF8Unsafe(output_dir)
258 .Append(FilePath::FromUTF8Unsafe(playlist->file_name()))
260 if (!playlist->WriteToFile(file_path)) {
261 LOG(ERROR) <<
"Failed to write playlist " << file_path;
269 MediaPlaylistFactory::~MediaPlaylistFactory() {}
271 std::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));
283 const base::FilePath master_playlist_path(
285 master_playlist_dir_ = master_playlist_path.DirName().AsUTF8Unsafe();
287 const std::string& default_text_language =
291 master_playlist_.reset(
292 new MasterPlaylist(master_playlist_path.BaseName().AsUTF8Unsafe(),
293 default_audio_langauge, default_text_language));
296 SimpleHlsNotifier::~SimpleHlsNotifier() {}
303 const std::string& playlist_name,
304 const std::string& name,
305 const std::string& group_id,
306 uint32_t* stream_id) {
309 const std::string relative_playlist_path = MakePathRelative(
310 playlist_name, FilePath::FromUTF8Unsafe(master_playlist_dir_));
312 std::unique_ptr<MediaPlaylist> media_playlist =
313 media_playlist_factory_->Create(
hls_params(), relative_playlist_path,
315 MediaInfo adjusted_media_info = MakeMediaInfoPathsRelativeToPlaylist(
316 media_info,
hls_params().base_url, master_playlist_dir_,
317 media_playlist->file_name());
318 if (!media_playlist->SetMediaInfo(adjusted_media_info)) {
319 LOG(ERROR) <<
"Failed to set media info for playlist " << playlist_name;
323 MediaPlaylist::EncryptionMethod encryption_method =
324 MediaPlaylist::EncryptionMethod::kNone;
325 if (media_info.protected_content().has_protection_scheme()) {
326 const std::string& protection_scheme =
327 media_info.protected_content().protection_scheme();
328 base::Optional<MediaPlaylist::EncryptionMethod> enc_method =
329 StringToEncryptionMethod(protection_scheme);
331 LOG(ERROR) <<
"Failed to recognize protection scheme " 332 << protection_scheme;
335 encryption_method = enc_method.value();
338 base::AutoLock auto_lock(lock_);
339 *stream_id = sequence_number_++;
340 media_playlists_.push_back(media_playlist.get());
341 stream_map_[*stream_id].reset(
342 new StreamEntry{std::move(media_playlist), encryption_method});
347 uint32_t sample_duration) {
348 base::AutoLock auto_lock(lock_);
349 auto stream_iterator = stream_map_.find(stream_id);
350 if (stream_iterator == stream_map_.end()) {
351 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
354 auto& media_playlist = stream_iterator->second->media_playlist;
355 media_playlist->SetSampleDuration(sample_duration);
360 const std::string& segment_name,
363 uint64_t start_byte_offset,
365 base::AutoLock auto_lock(lock_);
366 auto stream_iterator = stream_map_.find(stream_id);
367 if (stream_iterator == stream_map_.end()) {
368 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
371 auto& media_playlist = stream_iterator->second->media_playlist;
372 const std::string& segment_url =
373 GenerateSegmentUrl(segment_name,
hls_params().base_url,
374 master_playlist_dir_, media_playlist->file_name());
375 media_playlist->AddSegment(segment_url, start_time, duration,
376 start_byte_offset, size);
379 uint32_t longest_segment_duration =
380 static_cast<uint32_t
>(ceil(media_playlist->GetLongestSegmentDuration()));
381 bool target_duration_updated =
false;
382 if (longest_segment_duration > target_duration_) {
383 target_duration_ = longest_segment_duration;
384 target_duration_updated =
true;
391 if (target_duration_updated) {
393 playlist->SetTargetDuration(target_duration_);
394 if (!WriteMediaPlaylist(master_playlist_dir_, playlist))
398 if (!WriteMediaPlaylist(master_playlist_dir_, media_playlist.get()))
401 if (!master_playlist_->WriteMasterPlaylist(
402 hls_params().base_url, master_playlist_dir_, media_playlists_)) {
403 LOG(ERROR) <<
"Failed to write master playlist.";
412 uint64_t start_byte_offset,
414 base::AutoLock auto_lock(lock_);
415 auto stream_iterator = stream_map_.find(stream_id);
416 if (stream_iterator == stream_map_.end()) {
417 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
420 auto& media_playlist = stream_iterator->second->media_playlist;
421 media_playlist->AddKeyFrame(timestamp, start_byte_offset, size);
426 base::AutoLock auto_lock(lock_);
427 auto stream_iterator = stream_map_.find(stream_id);
428 if (stream_iterator == stream_map_.end()) {
429 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
432 auto& media_playlist = stream_iterator->second->media_playlist;
433 media_playlist->AddPlacementOpportunity();
439 const std::vector<uint8_t>& key_id,
440 const std::vector<uint8_t>& system_id,
441 const std::vector<uint8_t>& iv,
442 const std::vector<uint8_t>& protection_system_specific_data) {
443 base::AutoLock auto_lock(lock_);
444 auto stream_iterator = stream_map_.find(stream_id);
445 if (stream_iterator == stream_map_.end()) {
446 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
450 std::unique_ptr<MediaPlaylist>& media_playlist =
451 stream_iterator->second->media_playlist;
452 const MediaPlaylist::EncryptionMethod encryption_method =
453 stream_iterator->second->encryption_method;
454 LOG_IF(WARNING, encryption_method == MediaPlaylist::EncryptionMethod::kNone)
455 <<
"Got encryption notification but the encryption method is NONE";
456 if (IsWidevineSystemId(system_id)) {
457 return HandleWidevineKeyFormats(encryption_method,
458 key_id, iv, protection_system_specific_data,
459 media_playlist.get());
463 const std::vector<uint8_t> empty_key_id;
465 if (IsCommonSystemId(system_id)) {
467 if (key_uri.empty()) {
470 std::string key_uri_data = VectorToString(key_id);
471 key_uri = Base64EncodeData(kUriBase64Prefix, key_uri_data);
473 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri, empty_key_id,
474 iv,
"identity",
"", media_playlist.get());
477 if (IsFairPlaySystemId(system_id)) {
479 if (key_uri.empty()) {
482 std::string key_uri_data = VectorToString(key_id);
483 key_uri = Base64EncodeData(kUriFairPlayPrefix, key_uri_data);
487 const std::vector<uint8_t> empty_iv;
488 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri, empty_key_id,
489 empty_iv,
"com.apple.streamingkeydelivery",
490 "1", media_playlist.get());
494 LOG(WARNING) <<
"HLS: Ignore unknown or unsupported system ID: " 495 << base::HexEncode(system_id.data(), system_id.size());
500 base::AutoLock auto_lock(lock_);
502 playlist->SetTargetDuration(target_duration_);
503 if (!WriteMediaPlaylist(master_playlist_dir_, playlist))
506 if (!master_playlist_->WriteMasterPlaylist(
507 hls_params().base_url, master_playlist_dir_, media_playlists_)) {
508 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
std::string default_text_language
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
const HlsParams & hls_params() const
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
std::string default_language
bool NotifyKeyFrame(uint32_t stream_id, uint64_t timestamp, uint64_t start_byte_offset, uint64_t size) override
bool NotifySampleDuration(uint32_t stream_id, uint32_t sample_duration) 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
HlsPlaylistType playlist_type
HLS playlist type. See HLS specification for details.