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/logging.h" 14 #include "packager/base/optional.h" 15 #include "packager/base/strings/string_number_conversions.h" 16 #include "packager/base/strings/stringprintf.h" 17 #include "packager/hls/base/media_playlist.h" 18 #include "packager/media/base/protection_system_specific_info.h" 19 #include "packager/media/base/proto_json_util.h" 20 #include "packager/media/base/raw_key_pssh_generator.h" 21 #include "packager/media/base/raw_key_source.h" 22 #include "packager/media/base/widevine_key_source.h" 23 #include "packager/media/base/widevine_pssh_data.pb.h" 24 #include "packager/media/base/widevine_pssh_generator.h" 34 const char kUriBase64Prefix[] =
"data:text/plain;base64,";
35 const char kUriFairplayPrefix[] =
"skd://";
36 const char kWidevineDashIfIopUUID[] =
37 "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
39 bool IsWidevineSystemId(
const std::vector<uint8_t>& system_id) {
40 return system_id.size() == arraysize(media::kWidevineSystemId) &&
41 std::equal(system_id.begin(), system_id.end(),
42 media::kWidevineSystemId);
45 bool IsCommonSystemId(
const std::vector<uint8_t>& system_id) {
46 return system_id.size() == arraysize(media::kCommonSystemId) &&
47 std::equal(system_id.begin(), system_id.end(), media::kCommonSystemId);
50 bool IsFairplaySystemId(
const std::vector<uint8_t>& system_id) {
51 return system_id.size() == arraysize(media::kFairplaySystemId) &&
52 std::equal(system_id.begin(), system_id.end(), media::kFairplaySystemId);
55 std::string Base64EncodeData(
const std::string& prefix,
56 const std::string& data) {
57 std::string data_base64;
58 base::Base64Encode(data, &data_base64);
59 return prefix + data_base64;
62 std::string VectorToString(
const std::vector<uint8_t>& v) {
63 return std::string(v.begin(), v.end());
75 std::string MakePathRelative(
const std::string& media_path,
76 const FilePath& parent_path) {
77 FilePath relative_path;
78 const FilePath child_path = FilePath::FromUTF8Unsafe(media_path);
80 parent_path.AppendRelativePath(child_path, &relative_path);
82 relative_path = child_path;
83 return relative_path.NormalizePathSeparatorsTo(
'/').AsUTF8Unsafe();
88 std::string GenerateSegmentUrl(
const std::string& segment_name,
89 const std::string& base_url,
90 const std::string& output_dir,
91 const std::string& playlist_file_name) {
92 FilePath output_path = FilePath::FromUTF8Unsafe(output_dir);
93 if (!base_url.empty()) {
96 return base_url + MakePathRelative(segment_name, output_path);
100 const FilePath playlist_dir =
101 output_path.Append(FilePath::FromUTF8Unsafe(playlist_file_name))
103 .AsEndingWithSeparator();
104 return MakePathRelative(segment_name, playlist_dir);
107 MediaInfo MakeMediaInfoPathsRelativeToPlaylist(
108 const MediaInfo& media_info,
109 const std::string& base_url,
110 const std::string& output_dir,
111 const std::string& playlist_name) {
112 MediaInfo media_info_copy = media_info;
113 if (media_info_copy.has_init_segment_name()) {
114 media_info_copy.set_init_segment_url(
115 GenerateSegmentUrl(media_info_copy.init_segment_name(), base_url,
116 output_dir, playlist_name));
118 if (media_info_copy.has_media_file_name()) {
119 media_info_copy.set_media_file_url(
120 GenerateSegmentUrl(media_info_copy.media_file_name(), base_url,
121 output_dir, playlist_name));
123 if (media_info_copy.has_segment_template()) {
124 media_info_copy.set_segment_template_url(
125 GenerateSegmentUrl(media_info_copy.segment_template(), base_url,
126 output_dir, playlist_name));
128 return media_info_copy;
131 bool WidevinePsshToJson(
const std::vector<uint8_t>& pssh_box,
132 const std::vector<uint8_t>& key_id,
133 std::string* pssh_json) {
134 std::unique_ptr<media::PsshBoxBuilder> pssh_builder =
137 LOG(ERROR) <<
"Failed to parse PSSH box.";
141 media::WidevinePsshData pssh_proto;
142 if (!pssh_proto.ParseFromArray(pssh_builder->pssh_data().data(),
143 pssh_builder->pssh_data().size())) {
144 LOG(ERROR) <<
"Failed to parse protection_system_specific_data.";
147 if (!pssh_proto.has_provider() ||
148 (!pssh_proto.has_content_id() && pssh_proto.key_id_size() == 0)) {
149 LOG(ERROR) <<
"Missing fields to generate URI.";
153 media::WidevineHeader widevine_header;
154 widevine_header.set_provider(pssh_proto.provider());
155 if (pssh_proto.has_content_id())
156 widevine_header.set_content_id(pssh_proto.content_id());
159 widevine_header.add_key_ids(base::HexEncode(key_id.data(), key_id.size()));
160 for (
const std::string& key_id_in_pssh : pssh_proto.key_id()) {
161 const std::string key_id_hex =
162 base::HexEncode(key_id_in_pssh.data(), key_id_in_pssh.size());
163 if (widevine_header.key_ids(0) != key_id_hex)
164 widevine_header.add_key_ids(key_id_hex);
167 *pssh_json = media::MessageToJsonString(widevine_header);
171 base::Optional<MediaPlaylist::EncryptionMethod> StringToEncryptionMethod(
172 const std::string& method) {
173 if (method ==
"cenc") {
174 return MediaPlaylist::EncryptionMethod::kSampleAesCenc;
175 }
else if (method ==
"cbcs") {
176 return MediaPlaylist::EncryptionMethod::kSampleAes;
177 }
else if (method ==
"cbca") {
179 return MediaPlaylist::EncryptionMethod::kSampleAes;
181 return base::nullopt;
185 void NotifyEncryptionToMediaPlaylist(
186 MediaPlaylist::EncryptionMethod encryption_method,
187 const std::string& uri,
188 const std::vector<uint8_t>& key_id,
189 const std::vector<uint8_t>& iv,
190 const std::string& key_format,
191 const std::string& key_format_version,
192 MediaPlaylist* media_playlist) {
193 std::string iv_string;
195 iv_string =
"0x" + base::HexEncode(iv.data(), iv.size());
197 std::string key_id_string;
198 if (!key_id.empty()) {
199 key_id_string =
"0x" + base::HexEncode(key_id.data(), key_id.size());
202 media_playlist->AddEncryptionInfo(
204 uri, key_id_string, iv_string,
205 key_format, key_format_version);
209 bool HandleWidevineKeyFormats(
210 MediaPlaylist::EncryptionMethod encryption_method,
211 const std::vector<uint8_t>& key_id,
212 const std::vector<uint8_t>& iv,
213 const std::vector<uint8_t>& protection_system_specific_data,
214 MediaPlaylist* media_playlist) {
215 if (encryption_method == MediaPlaylist::EncryptionMethod::kSampleAes) {
217 std::string key_uri_data;
218 if (!WidevinePsshToJson(protection_system_specific_data, key_id,
222 std::string key_uri_data_base64 =
223 Base64EncodeData(kUriBase64Prefix, key_uri_data);
224 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64,
225 std::vector<uint8_t>(), iv,
"com.widevine",
226 "1", media_playlist);
229 std::string pssh_as_string(
230 reinterpret_cast<const char*>(protection_system_specific_data.data()),
231 protection_system_specific_data.size());
232 std::string key_uri_data_base64 =
233 Base64EncodeData(kUriBase64Prefix, pssh_as_string);
234 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64,
235 key_id, iv, kWidevineDashIfIopUUID,
"1",
240 bool WriteMediaPlaylist(
const std::string& output_dir,
241 MediaPlaylist* playlist) {
242 std::string file_path =
243 FilePath::FromUTF8Unsafe(output_dir)
244 .Append(FilePath::FromUTF8Unsafe(playlist->file_name()))
246 if (!playlist->WriteToFile(file_path)) {
247 LOG(ERROR) <<
"Failed to write playlist " << file_path;
255 MediaPlaylistFactory::~MediaPlaylistFactory() {}
257 std::unique_ptr<MediaPlaylist> MediaPlaylistFactory::Create(
258 const HlsParams& hls_params,
259 const std::string& file_name,
260 const std::string& name,
261 const std::string& group_id) {
262 return std::unique_ptr<MediaPlaylist>(
263 new MediaPlaylist(hls_params, file_name, name, group_id));
269 const base::FilePath master_playlist_path(
271 output_dir_ = master_playlist_path.DirName().AsUTF8Unsafe();
272 master_playlist_.reset(
273 new MasterPlaylist(master_playlist_path.BaseName().AsUTF8Unsafe(),
277 SimpleHlsNotifier::~SimpleHlsNotifier() {}
284 const std::string& playlist_name,
285 const std::string& name,
286 const std::string& group_id,
287 uint32_t* stream_id) {
290 std::unique_ptr<MediaPlaylist> media_playlist =
291 media_playlist_factory_->Create(
hls_params(), playlist_name, name,
293 MediaInfo adjusted_media_info = MakeMediaInfoPathsRelativeToPlaylist(
294 media_info,
hls_params().base_url, output_dir_,
295 media_playlist->file_name());
296 if (!media_playlist->SetMediaInfo(adjusted_media_info)) {
297 LOG(ERROR) <<
"Failed to set media info for playlist " << playlist_name;
301 MediaPlaylist::EncryptionMethod encryption_method =
302 MediaPlaylist::EncryptionMethod::kNone;
303 if (media_info.protected_content().has_protection_scheme()) {
304 const std::string& protection_scheme =
305 media_info.protected_content().protection_scheme();
306 base::Optional<MediaPlaylist::EncryptionMethod> enc_method =
307 StringToEncryptionMethod(protection_scheme);
309 LOG(ERROR) <<
"Failed to recognize protection scheme " 310 << protection_scheme;
313 encryption_method = enc_method.value();
316 *stream_id = sequence_number_.GetNext();
317 base::AutoLock auto_lock(lock_);
318 media_playlists_.push_back(media_playlist.get());
319 stream_map_[*stream_id].reset(
320 new StreamEntry{std::move(media_playlist), encryption_method});
325 const std::string& segment_name,
328 uint64_t start_byte_offset,
330 base::AutoLock auto_lock(lock_);
331 auto stream_iterator = stream_map_.find(stream_id);
332 if (stream_iterator == stream_map_.end()) {
333 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
336 auto& media_playlist = stream_iterator->second->media_playlist;
337 const std::string& segment_url =
338 GenerateSegmentUrl(segment_name,
hls_params().base_url, output_dir_,
339 media_playlist->file_name());
340 media_playlist->AddSegment(segment_url, start_time, duration,
341 start_byte_offset, size);
344 uint32_t longest_segment_duration =
345 static_cast<uint32_t
>(ceil(media_playlist->GetLongestSegmentDuration()));
346 bool target_duration_updated =
false;
347 if (longest_segment_duration > target_duration_) {
348 target_duration_ = longest_segment_duration;
349 target_duration_updated =
true;
356 if (target_duration_updated) {
358 playlist->SetTargetDuration(target_duration_);
359 if (!WriteMediaPlaylist(output_dir_, playlist))
363 if (!WriteMediaPlaylist(output_dir_, media_playlist.get()))
366 if (!master_playlist_->WriteMasterPlaylist(
hls_params().base_url,
367 output_dir_, media_playlists_)) {
368 LOG(ERROR) <<
"Failed to write master playlist.";
377 uint64_t start_byte_offset,
379 base::AutoLock auto_lock(lock_);
380 auto stream_iterator = stream_map_.find(stream_id);
381 if (stream_iterator == stream_map_.end()) {
382 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
385 auto& media_playlist = stream_iterator->second->media_playlist;
386 media_playlist->AddKeyFrame(timestamp, start_byte_offset, size);
391 base::AutoLock auto_lock(lock_);
392 auto stream_iterator = stream_map_.find(stream_id);
393 if (stream_iterator == stream_map_.end()) {
394 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
397 auto& media_playlist = stream_iterator->second->media_playlist;
398 media_playlist->AddPlacementOpportunity();
404 const std::vector<uint8_t>& key_id,
405 const std::vector<uint8_t>& system_id,
406 const std::vector<uint8_t>& iv,
407 const std::vector<uint8_t>& protection_system_specific_data) {
408 base::AutoLock auto_lock(lock_);
409 auto stream_iterator = stream_map_.find(stream_id);
410 if (stream_iterator == stream_map_.end()) {
411 LOG(ERROR) <<
"Cannot find stream with ID: " << stream_id;
415 std::unique_ptr<MediaPlaylist>& media_playlist =
416 stream_iterator->second->media_playlist;
417 const MediaPlaylist::EncryptionMethod encryption_method =
418 stream_iterator->second->encryption_method;
419 LOG_IF(WARNING, encryption_method == MediaPlaylist::EncryptionMethod::kNone)
420 <<
"Got encryption notification but the encryption method is NONE";
421 if (IsWidevineSystemId(system_id)) {
422 return HandleWidevineKeyFormats(encryption_method,
423 key_id, iv, protection_system_specific_data,
424 media_playlist.get());
428 const std::vector<uint8_t> empty_key_id;
430 if (IsCommonSystemId(system_id)) {
432 if (key_uri.empty()) {
435 std::string key_uri_data = VectorToString(key_id);
436 key_uri = Base64EncodeData(kUriBase64Prefix, key_uri_data);
438 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri, empty_key_id,
439 iv,
"identity",
"", media_playlist.get());
441 }
else if (IsFairplaySystemId(system_id)) {
443 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(
hls_params().base_url, 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
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 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.