Shaka Packager SDK
Loading...
Searching...
No Matches
simple_hls_notifier.cc
1// Copyright 2016 Google LLC. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file or at
5// https://developers.google.com/open-source/licenses/bsd
6
7#include <packager/hls/base/simple_hls_notifier.h>
8
9#include <algorithm>
10#include <cmath>
11#include <cstdint>
12#include <filesystem>
13#include <iterator>
14#include <memory>
15#include <optional>
16#include <string>
17#include <utility>
18#include <vector>
19
20#include <absl/flags/flag.h>
21#include <absl/log/check.h>
22#include <absl/log/log.h>
23#include <absl/strings/escaping.h>
24#include <absl/strings/string_view.h>
25#include <absl/synchronization/mutex.h>
26#include <absl/time/clock.h>
27
28#include <packager/cea_caption.h>
29#include <packager/file/file_util.h>
30#include <packager/hls/base/hls_notifier.h>
31#include <packager/hls/base/master_playlist.h>
32#include <packager/hls_params.h>
33#include <packager/media/base/protection_system_ids.h>
34#include <packager/media/base/protection_system_specific_info.h>
35#include <packager/media/base/proto_json_util.h>
36#include <packager/media/base/widevine_pssh_data.pb.h>
37#include <packager/mpd/base/media_info.pb.h>
38
39ABSL_FLAG(bool,
40 enable_legacy_widevine_hls_signaling,
41 false,
42 "Specifies whether Legacy Widevine HLS, i.e. v1 is signalled in "
43 "the media playlist. Applies to Widevine protection system in HLS "
44 "with SAMPLE-AES only.");
45
46namespace shaka {
47
48namespace hls {
49
50namespace {
51
52const char kUriBase64Prefix[] = "data:text/plain;base64,";
53const char kUriBase64Utf16Prefix[] = "data:text/plain;charset=UTF-16;base64,";
54const char kUriFairPlayPrefix[] = "skd://";
55const char kWidevineDashIfIopUUID[] =
56 "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";
57
58bool IsWidevineSystemId(const std::vector<uint8_t>& system_id) {
59 return system_id.size() == std::size(media::kWidevineSystemId) &&
60 std::equal(system_id.begin(), system_id.end(),
61 media::kWidevineSystemId);
62}
63
64bool IsCommonSystemId(const std::vector<uint8_t>& system_id) {
65 return system_id.size() == std::size(media::kCommonSystemId) &&
66 std::equal(system_id.begin(), system_id.end(), media::kCommonSystemId);
67}
68
69bool IsFairPlaySystemId(const std::vector<uint8_t>& system_id) {
70 return system_id.size() == std::size(media::kFairPlaySystemId) &&
71 std::equal(system_id.begin(), system_id.end(),
72 media::kFairPlaySystemId);
73}
74
75bool IsLegacyFairPlaySystemId(const std::vector<uint8_t>& system_id) {
76 return system_id.size() == std::size(media::kLegacyFairPlaySystemId) &&
77 std::equal(system_id.begin(), system_id.end(),
78 media::kLegacyFairPlaySystemId);
79}
80
81bool IsPlayReadySystemId(const std::vector<uint8_t>& system_id) {
82 return system_id.size() == std::size(media::kPlayReadySystemId) &&
83 std::equal(system_id.begin(), system_id.end(),
84 media::kPlayReadySystemId);
85}
86
87std::string Base64EncodeData(const std::string& prefix,
88 const std::string& data) {
89 std::string data_base64;
90 absl::Base64Escape(data, &data_base64);
91 return prefix + data_base64;
92}
93
94std::string VectorToString(const std::vector<uint8_t>& v) {
95 return std::string(v.begin(), v.end());
96}
97
98// Segment URL is relative to either output directory or the directory
99// containing the media playlist depends on whether base_url is set.
100std::string GenerateSegmentUrl(const std::string& segment_name,
101 const std::string& base_url,
102 const std::string& output_dir,
103 const std::string& playlist_file_name) {
104 auto output_path = std::filesystem::u8path(output_dir);
105 if (!base_url.empty()) {
106 // Media segment URL is base_url + segment path relative to output
107 // directory.
108 return base_url + MakePathRelative(segment_name, output_path);
109 }
110 // Media segment URL is segment path relative to the directory containing the
111 // playlist.
112 const std::filesystem::path playlist_dir =
113 (output_path / playlist_file_name).parent_path() / "";
114 return MakePathRelative(segment_name, playlist_dir);
115}
116
117MediaInfo MakeMediaInfoPathsRelativeToPlaylist(
118 const MediaInfo& media_info,
119 const std::string& base_url,
120 const std::string& output_dir,
121 const std::string& playlist_name) {
122 MediaInfo media_info_copy = media_info;
123 if (media_info_copy.has_init_segment_name()) {
124 media_info_copy.set_init_segment_url(
125 GenerateSegmentUrl(media_info_copy.init_segment_name(), base_url,
126 output_dir, playlist_name));
127 }
128 if (media_info_copy.has_media_file_name()) {
129 media_info_copy.set_media_file_url(
130 GenerateSegmentUrl(media_info_copy.media_file_name(), base_url,
131 output_dir, playlist_name));
132 }
133 if (media_info_copy.has_segment_template()) {
134 media_info_copy.set_segment_template_url(
135 GenerateSegmentUrl(media_info_copy.segment_template(), base_url,
136 output_dir, playlist_name));
137 }
138 return media_info_copy;
139}
140
141bool WidevinePsshToJson(const std::vector<uint8_t>& pssh_box,
142 const std::vector<uint8_t>& key_id,
143 std::string* pssh_json) {
144 std::unique_ptr<media::PsshBoxBuilder> pssh_builder =
145 media::PsshBoxBuilder::ParseFromBox(pssh_box.data(), pssh_box.size());
146 if (!pssh_builder) {
147 LOG(ERROR) << "Failed to parse PSSH box.";
148 return false;
149 }
150
151 media::WidevinePsshData pssh_proto;
152 if (!pssh_proto.ParseFromArray(pssh_builder->pssh_data().data(),
153 pssh_builder->pssh_data().size())) {
154 LOG(ERROR) << "Failed to parse protection_system_specific_data.";
155 return false;
156 }
157
158 media::WidevineHeader widevine_header;
159
160 if (pssh_proto.has_provider()) {
161 widevine_header.set_provider(pssh_proto.provider());
162 } else {
163 LOG(WARNING) << "Missing provider in Widevine PSSH. The content may not "
164 "play in some devices.";
165 }
166
167 if (pssh_proto.has_content_id()) {
168 widevine_header.set_content_id(pssh_proto.content_id());
169 } else {
170 LOG(WARNING) << "Missing content_id in Widevine PSSH. The content may not "
171 "play in some devices.";
172 }
173
174 // Place the current |key_id| to the front and converts all key_id to hex
175 // format.
176 widevine_header.add_key_ids(absl::BytesToHexString(absl::string_view(
177 reinterpret_cast<const char*>(key_id.data()), key_id.size())));
178 for (const std::string& key_id_in_pssh : pssh_proto.key_id()) {
179 const std::string key_id_hex = absl::BytesToHexString(
180 absl::string_view(reinterpret_cast<const char*>(key_id_in_pssh.data()),
181 key_id_in_pssh.size()));
182 if (widevine_header.key_ids(0) != key_id_hex)
183 widevine_header.add_key_ids(key_id_hex);
184 }
185
186 *pssh_json = media::MessageToJsonString(widevine_header);
187 return true;
188}
189
190std::optional<MediaPlaylist::EncryptionMethod> StringToEncryptionMethod(
191 const std::string& method) {
192 if (method == "cenc") {
193 return MediaPlaylist::EncryptionMethod::kSampleAesCenc;
194 }
195 if (method == "cbcs") {
196 return MediaPlaylist::EncryptionMethod::kSampleAes;
197 }
198 if (method == "cbca") {
199 // cbca is a place holder for sample aes.
200 return MediaPlaylist::EncryptionMethod::kSampleAes;
201 }
202 if (method == "aes8") {
203 // aes8 is the internal FourCC for HLS AES-128 full-segment encryption.
204 return MediaPlaylist::EncryptionMethod::kAes128;
205 }
206 return std::nullopt;
207}
208
209void NotifyEncryptionToMediaPlaylist(
210 MediaPlaylist::EncryptionMethod encryption_method,
211 const std::string& uri,
212 const std::vector<uint8_t>& key_id,
213 const std::vector<uint8_t>& iv,
214 const std::string& key_format,
215 const std::string& key_format_version,
216 MediaPlaylist* media_playlist) {
217 std::string iv_string;
218 if (!iv.empty()) {
219 iv_string =
220 "0x" + absl::BytesToHexString(absl::string_view(
221 reinterpret_cast<const char*>(iv.data()), iv.size()));
222 }
223 std::string key_id_string;
224 if (!key_id.empty()) {
225 key_id_string = "0x" + absl::BytesToHexString(absl::string_view(
226 reinterpret_cast<const char*>(key_id.data()),
227 key_id.size()));
228 }
229
230 media_playlist->AddEncryptionInfo(encryption_method, uri, key_id_string,
231 iv_string, key_format, key_format_version);
232}
233
234// Creates JSON format and the format similar to MPD.
235bool HandleWidevineKeyFormats(
236 MediaPlaylist::EncryptionMethod encryption_method,
237 const std::vector<uint8_t>& key_id,
238 const std::vector<uint8_t>& iv,
239 const std::vector<uint8_t>& protection_system_specific_data,
240 MediaPlaylist* media_playlist) {
241 if (absl::GetFlag(FLAGS_enable_legacy_widevine_hls_signaling) &&
242 encryption_method == MediaPlaylist::EncryptionMethod::kSampleAes) {
243 // This format allows SAMPLE-AES only.
244 std::string key_uri_data;
245 if (!WidevinePsshToJson(protection_system_specific_data, key_id,
246 &key_uri_data)) {
247 return false;
248 }
249 std::string key_uri_data_base64 =
250 Base64EncodeData(kUriBase64Prefix, key_uri_data);
251 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64,
252 std::vector<uint8_t>(), iv, "com.widevine",
253 "1", media_playlist);
254 }
255
256 std::string pssh_as_string(
257 reinterpret_cast<const char*>(protection_system_specific_data.data()),
258 protection_system_specific_data.size());
259 std::string key_uri_data_base64 =
260 Base64EncodeData(kUriBase64Prefix, pssh_as_string);
261 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64,
262 key_id, iv, kWidevineDashIfIopUUID, "1",
263 media_playlist);
264 return true;
265}
266
267bool WriteMediaPlaylist(const std::string& output_dir,
268 MediaPlaylist* playlist,
269 const bool event_to_vod_on_end_of_stream,
270 const bool end_stream) {
271 auto file_path = std::filesystem::u8path(output_dir) / playlist->file_name();
272 if (!playlist->WriteToFile(file_path, event_to_vod_on_end_of_stream,
273 end_stream)) {
274 LOG(ERROR) << "Failed to write playlist " << file_path.string();
275 return false;
276 }
277 return true;
278}
279
280} // namespace
281
282MediaPlaylistFactory::~MediaPlaylistFactory() {}
283
284std::unique_ptr<MediaPlaylist> MediaPlaylistFactory::Create(
285 const HlsParams& hls_params,
286 const std::string& file_name,
287 const std::string& name,
288 const std::string& group_id) {
289 return std::unique_ptr<MediaPlaylist>(
290 new MediaPlaylist(hls_params, file_name, name, group_id));
291}
292
293SimpleHlsNotifier::SimpleHlsNotifier(const HlsParams& hls_params)
294 : HlsNotifier(hls_params),
295 media_playlist_factory_(new MediaPlaylistFactory()) {
296 if (hls_params.add_program_date_time) {
297 reference_time_ = absl::Now();
298 }
299 const auto master_playlist_path =
300 std::filesystem::u8path(hls_params.master_playlist_output);
301 master_playlist_dir_ = master_playlist_path.parent_path().string();
302 const std::string& default_audio_langauge = hls_params.default_language;
303 const std::string& default_text_language =
304 hls_params.default_text_language.empty()
305 ? hls_params.default_language
306 : hls_params.default_text_language;
307
308 std::vector<CeaCaption> closed_captions;
309 for (const auto& caption : hls_params.closed_captions) {
310 closed_captions.push_back({caption.name, caption.language, caption.channel,
311 caption.is_default, caption.autoselect});
312 }
313
314 master_playlist_.reset(new MasterPlaylist(
315 master_playlist_path.filename(), default_audio_langauge,
316 default_text_language, closed_captions,
317 hls_params.is_independent_segments, hls_params.create_session_keys));
318}
319
320SimpleHlsNotifier::~SimpleHlsNotifier() {}
321
323 return true;
324}
325
326bool SimpleHlsNotifier::NotifyNewStream(const MediaInfo& media_info,
327 const std::string& playlist_name,
328 const std::string& name,
329 const std::string& group_id,
330 uint32_t* stream_id) {
331 DCHECK(stream_id);
332
333 const std::string relative_playlist_path = MakePathRelative(
334 playlist_name, std::filesystem::u8path(master_playlist_dir_));
335
336 std::unique_ptr<MediaPlaylist> media_playlist =
337 media_playlist_factory_->Create(hls_params(), relative_playlist_path,
338 name, group_id);
339 MediaInfo adjusted_media_info = MakeMediaInfoPathsRelativeToPlaylist(
340 media_info, hls_params().base_url, master_playlist_dir_,
341 media_playlist->file_name());
342 if (!media_playlist->SetMediaInfo(adjusted_media_info)) {
343 LOG(ERROR) << "Failed to set media info for playlist " << playlist_name;
344 return false;
345 }
346 media_playlist->SetReferenceTime(reference_time());
347
348 MediaPlaylist::EncryptionMethod encryption_method =
349 MediaPlaylist::EncryptionMethod::kNone;
350 if (media_info.protected_content().has_protection_scheme()) {
351 const std::string& protection_scheme =
352 media_info.protected_content().protection_scheme();
353 std::optional<MediaPlaylist::EncryptionMethod> enc_method =
354 StringToEncryptionMethod(protection_scheme);
355 if (!enc_method) {
356 LOG(ERROR) << "Failed to recognize protection scheme "
357 << protection_scheme;
358 return false;
359 }
360 encryption_method = enc_method.value();
361 }
362
363 absl::MutexLock lock(lock_);
364 *stream_id = sequence_number_++;
365 media_playlists_.push_back(media_playlist.get());
366 stream_map_[*stream_id].reset(
367 new StreamEntry{std::move(media_playlist), encryption_method});
368 return true;
369}
370
372 int32_t sample_duration) {
373 absl::MutexLock lock(lock_);
374 auto stream_iterator = stream_map_.find(stream_id);
375 if (stream_iterator == stream_map_.end()) {
376 LOG(ERROR) << "Cannot find stream with ID: " << stream_id;
377 return false;
378 }
379 auto& media_playlist = stream_iterator->second->media_playlist;
380 media_playlist->SetSampleDuration(sample_duration);
381 return true;
382}
383
385 const std::string& segment_name,
386 int64_t start_time,
387 int64_t duration,
388 uint64_t start_byte_offset,
389 uint64_t size) {
390 absl::MutexLock lock(lock_);
391 auto stream_iterator = stream_map_.find(stream_id);
392 if (stream_iterator == stream_map_.end()) {
393 LOG(ERROR) << "Cannot find stream with ID: " << stream_id;
394 return false;
395 }
396 auto& media_playlist = stream_iterator->second->media_playlist;
397 const std::string& segment_url =
398 GenerateSegmentUrl(segment_name, hls_params().base_url,
399 master_playlist_dir_, media_playlist->file_name());
400 media_playlist->AddSegment(segment_url, start_time, duration,
401 start_byte_offset, size);
402
403 // Update target duration.
404 int32_t longest_segment_duration =
405 static_cast<int32_t>(ceil(media_playlist->GetLongestSegmentDuration()));
406 bool target_duration_updated = false;
407 if (longest_segment_duration > target_duration_) {
408 target_duration_ = longest_segment_duration;
409 target_duration_updated = true;
410 }
411
412 // Update the playlists when there is new segments in live mode.
413 if (hls_params().playlist_type == HlsPlaylistType::kLive ||
414 hls_params().playlist_type == HlsPlaylistType::kEvent) {
415 // Update all playlists if target duration is updated.
416 if (target_duration_updated) {
417 for (MediaPlaylist* playlist : media_playlists_) {
418 playlist->SetTargetDuration(target_duration_);
419 if (!WriteMediaPlaylist(master_playlist_dir_, playlist,
420 hls_params().event_to_vod_on_end_of_stream,
421 end_stream))
422 return false;
423 }
424 } else {
425 if (!WriteMediaPlaylist(master_playlist_dir_, media_playlist.get(),
426 hls_params().event_to_vod_on_end_of_stream,
427 end_stream))
428 return false;
429 }
430 if (!master_playlist_->WriteMasterPlaylist(
431 hls_params().base_url, master_playlist_dir_, media_playlists_)) {
432 LOG(ERROR) << "Failed to write master playlist.";
433 return false;
434 }
435 }
436 return true;
437}
438
439bool SimpleHlsNotifier::NotifyKeyFrame(uint32_t stream_id,
440 int64_t timestamp,
441 uint64_t start_byte_offset,
442 uint64_t size) {
443 absl::MutexLock 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;
447 return false;
448 }
449 auto& media_playlist = stream_iterator->second->media_playlist;
450 media_playlist->AddKeyFrame(timestamp, start_byte_offset, size);
451 return true;
452}
453
454bool SimpleHlsNotifier::NotifyCueEvent(uint32_t stream_id, int64_t timestamp) {
455 absl::MutexLock lock(lock_);
456 auto stream_iterator = stream_map_.find(stream_id);
457 if (stream_iterator == stream_map_.end()) {
458 LOG(ERROR) << "Cannot find stream with ID: " << stream_id;
459 return false;
460 }
461 auto& media_playlist = stream_iterator->second->media_playlist;
462 media_playlist->AddPlacementOpportunity();
463 return true;
464}
465
467 uint32_t stream_id,
468 const std::vector<uint8_t>& key_id,
469 const std::vector<uint8_t>& system_id,
470 const std::vector<uint8_t>& iv,
471 const std::vector<uint8_t>& protection_system_specific_data) {
472 absl::MutexLock lock(lock_);
473 auto stream_iterator = stream_map_.find(stream_id);
474 if (stream_iterator == stream_map_.end()) {
475 LOG(ERROR) << "Cannot find stream with ID: " << stream_id;
476 return false;
477 }
478
479 std::unique_ptr<MediaPlaylist>& media_playlist =
480 stream_iterator->second->media_playlist;
481 const MediaPlaylist::EncryptionMethod encryption_method =
482 stream_iterator->second->encryption_method;
483 LOG_IF(WARNING, encryption_method == MediaPlaylist::EncryptionMethod::kNone)
484 << "Got encryption notification but the encryption method is NONE";
485
486 // AES-128 full-segment encryption: no DRM system, no KEYFORMAT attribute.
487 // The key URI is the raw 16-byte key file; players use it directly.
488 if (encryption_method == MediaPlaylist::EncryptionMethod::kAes128) {
489 const std::string key_uri = hls_params().key_uri;
490 if (key_uri.empty()) {
491 LOG(ERROR) << "AES-128 encryption requires --hls_key_uri to be set.";
492 return false;
493 }
494 // Pass empty key_id, empty key_format and key_format_versions so the
495 // EXT-X-KEY tag has only METHOD=AES-128, URI, and IV — as required by
496 // RFC 8216. KEYFORMAT defaults to "identity" when omitted.
497 const std::vector<uint8_t> empty_key_id;
498 NotifyEncryptionToMediaPlaylist(
499 encryption_method, key_uri, empty_key_id, iv, /*key_format=*/"",
500 /*key_format_versions=*/"", media_playlist.get());
501 return true;
502 }
503
504 if (IsWidevineSystemId(system_id)) {
505 return HandleWidevineKeyFormats(encryption_method, key_id, iv,
506 protection_system_specific_data,
507 media_playlist.get());
508 }
509
510 // Key Id does not need to be specified with "identity" and "sdk".
511 const std::vector<uint8_t> empty_key_id;
512
513 if (IsCommonSystemId(system_id)) {
514 const MediaPlaylist::EncryptionMethod encryption_method_from_stream =
515 stream_iterator->second->encryption_method;
516
517 if (encryption_method_from_stream ==
518 MediaPlaylist::EncryptionMethod::kSampleAesCenc) {
519 // We do NOT add the "identity" key format, because CENC must be managed
520 // by a specific DRM (like Widevine)
521 LOG(INFO) << "Skipping KEYFORMAT=\"identity\" for CENC content (stream "
522 << stream_id
523 << ") as it should be handled by a specific DRM system.";
524 return true;
525 }
526
527 std::string key_uri = hls_params().key_uri;
528 if (key_uri.empty()) {
529 // Use key_id as the key_uri. The player needs to have custom logic to
530 // convert it to the actual key uri.
531 std::string key_uri_data = VectorToString(key_id);
532 key_uri = Base64EncodeData(kUriBase64Prefix, key_uri_data);
533 }
534 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri, empty_key_id,
535 iv, "identity", "", media_playlist.get());
536 return true;
537 }
538 if (IsFairPlaySystemId(system_id) || IsLegacyFairPlaySystemId(system_id)) {
539 std::string key_uri = hls_params().key_uri;
540 if (key_uri.empty()) {
541 // Use key_id as the key_uri. The player needs to have custom logic to
542 // convert it to the actual key uri.
543 std::string key_uri_data = VectorToString(key_id);
544 key_uri = Base64EncodeData(kUriFairPlayPrefix, key_uri_data);
545 }
546
547 // FairPlay defines IV to be carried with the key, not the playlist.
548 const std::vector<uint8_t> empty_iv;
549 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri, empty_key_id,
550 empty_iv, "com.apple.streamingkeydelivery",
551 "1", media_playlist.get());
552 return true;
553 }
554 if (IsPlayReadySystemId(system_id)) {
555 std::unique_ptr<media::PsshBoxBuilder> b =
557 protection_system_specific_data.data(),
558 protection_system_specific_data.size());
559 std::string pssh_data(reinterpret_cast<const char*>(b->pssh_data().data()),
560 b->pssh_data().size());
561 std::string key_uri_data_base64 =
562 Base64EncodeData(kUriBase64Utf16Prefix, pssh_data);
563 NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64,
564 empty_key_id, iv, "com.microsoft.playready",
565 "1", media_playlist.get());
566 return true;
567 }
568
569 LOG(WARNING) << "HLS: Ignore unknown or unsupported system ID: "
570 << absl::BytesToHexString(absl::string_view(
571 reinterpret_cast<const char*>(system_id.data()),
572 system_id.size()));
573 return true;
574}
575
576bool SimpleHlsNotifier::NotifyEndOfStream() {
577 end_stream = true;
578 return true;
579}
580
582 absl::MutexLock lock(lock_);
583 for (MediaPlaylist* playlist : media_playlists_) {
584 if (hls_params().per_playlist_target_duration) {
585 playlist->SetTargetDuration(
586 static_cast<int32_t>(ceil(playlist->GetLongestSegmentDuration())));
587 } else {
588 playlist->SetTargetDuration(target_duration_);
589 }
590 if (!WriteMediaPlaylist(master_playlist_dir_, playlist,
591 hls_params().event_to_vod_on_end_of_stream,
592 end_stream))
593 return false;
594 }
595 if (!master_playlist_->WriteMasterPlaylist(
596 hls_params().base_url, master_playlist_dir_, media_playlists_)) {
597 LOG(ERROR) << "Failed to write master playlist.";
598 return false;
599 }
600 return true;
601}
602
603} // namespace hls
604} // namespace shaka
const HlsParams & hls_params() const
Methods are virtual for mocking.
virtual bool WriteToFile(const std::filesystem::path &file_path, bool event_to_vod_on_end_of_stream, bool end_stream)
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
static std::unique_ptr< PsshBoxBuilder > ParseFromBox(const uint8_t *data, size_t data_size)
All the methods that are virtual are virtual for mocking.