Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: EXT-X-SESSION-KEY support (#36) #1427

Merged
merged 3 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion docs/source/options/hls_options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,9 @@ HLS options
--force_cl_index

True forces the muxer to order streams in the order given
on the command-line. False uses the previous unordered behavior.
on the command-line. False uses the previous unordered behavior.

--create_session_keys

Playback of Offline HLS assets shall use EXT-X-SESSION-KEY to declare all
eligible content keys in the master playlist.
2 changes: 2 additions & 0 deletions include/packager/hls_params.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ struct HlsParams {
/// playlist. A negative number indicates a negative time offset from the end
/// of the last media segment in the playlist.
std::optional<double> start_time_offset;
/// Create EXT-X-SESSION-KEY in master playlist
bool create_session_keys;
};

} // namespace shaka
Expand Down
5 changes: 5 additions & 0 deletions packager/app/hls_flags.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,8 @@ ABSL_FLAG(std::optional<double>,
"beginning of the playlist. A negative number indicates a "
"negative time offset from the end of the last media segment "
"in the playlist.");
ABSL_FLAG(bool,
create_session_keys,
false,
"Playback of Offline HLS assets shall use EXT-X-SESSION-KEY "
"to declare all eligible content keys in the master playlist.");
1 change: 1 addition & 0 deletions packager/app/hls_flags.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ ABSL_DECLARE_FLAG(std::string, hls_key_uri);
ABSL_DECLARE_FLAG(std::string, hls_playlist_type);
ABSL_DECLARE_FLAG(int32_t, hls_media_sequence_number);
ABSL_DECLARE_FLAG(std::optional<double>, hls_start_time_offset);
ABSL_DECLARE_FLAG(bool, create_session_keys);

#endif // PACKAGER_APP_HLS_FLAGS_H_
1 change: 1 addition & 0 deletions packager/app/packager_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ std::optional<PackagingParams> GetPackagingParams() {
hls_params.media_sequence_number =
absl::GetFlag(FLAGS_hls_media_sequence_number);
hls_params.start_time_offset = absl::GetFlag(FLAGS_hls_start_time_offset);
hls_params.create_session_keys = absl::GetFlag(FLAGS_create_session_keys);

TestParams& test_params = packaging_params.test_params;
test_params.dump_stream_info = absl::GetFlag(FLAGS_dump_stream_info);
Expand Down
21 changes: 19 additions & 2 deletions packager/hls/base/master_playlist.cc
Original file line number Diff line number Diff line change
Expand Up @@ -550,11 +550,13 @@ void AppendPlaylists(const std::string& default_audio_language,
MasterPlaylist::MasterPlaylist(const std::filesystem::path& file_name,
const std::string& default_audio_language,
const std::string& default_text_language,
bool is_independent_segments)
bool is_independent_segments,
bool create_session_keys)
: file_name_(file_name),
default_audio_language_(default_audio_language),
default_text_language_(default_text_language),
is_independent_segments_(is_independent_segments) {}
is_independent_segments_(is_independent_segments),
create_session_keys_(create_session_keys) {}

MasterPlaylist::~MasterPlaylist() {}

Expand All @@ -568,6 +570,21 @@ bool MasterPlaylist::WriteMasterPlaylist(
if (is_independent_segments_) {
content.append("\n#EXT-X-INDEPENDENT-SEGMENTS\n");
}

// Iterate over the playlists and add the session keys to the master playlist.
if (create_session_keys_) {
std::set<std::string> session_keys;
for (const auto& playlist : playlists) {
for (const auto& entry : playlist->entries()) {
if (entry->type() == HlsEntry::EntryType::kExtKey)
session_keys.emplace(entry->ToString("#EXT-X-SESSION-KEY"));
}
}
// session_keys will now contain all the unique session keys.
for (const auto& session_key : session_keys)
content.append(session_key + "\n");
}

AppendPlaylists(default_audio_language_, default_text_language_, base_url,
playlists, &content);

Expand Down
4 changes: 3 additions & 1 deletion packager/hls/base/master_playlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class MasterPlaylist {
MasterPlaylist(const std::filesystem::path& file_name,
const std::string& default_audio_language,
const std::string& default_text_language,
const bool is_independent_segments);
const bool is_independent_segments,
const bool create_session_keys = false);
virtual ~MasterPlaylist();

/// Writes Master Playlist to output_dir + <name of playlist>.
Expand All @@ -53,6 +54,7 @@ class MasterPlaylist {
const std::string default_audio_language_;
const std::string default_text_language_;
bool is_independent_segments_;
bool create_session_keys_;
};

} // namespace hls
Expand Down
53 changes: 52 additions & 1 deletion packager/hls/base/master_playlist_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const uint32_t kEC3JocComplexityZero = 0;
const uint32_t kEC3JocComplexity = 16;
const bool kAC4IMSFlagEnabled = true;
const bool kAC4CBIFlagEnabled = true;
const bool kCreateSessionKeys = true;

std::unique_ptr<MockMediaPlaylist> CreateVideoPlaylist(
const std::string& filename,
Expand Down Expand Up @@ -143,7 +144,8 @@ class MasterPlaylistTest : public ::testing::Test {
: master_playlist_(new MasterPlaylist(kDefaultMasterPlaylistName,
kDefaultAudioLanguage,
kDefaultTextLanguage,
!kIsIndependentSegments)),
!kIsIndependentSegments,
kCreateSessionKeys)),
test_output_dir_("memory://test_dir"),
master_playlist_path_(std::filesystem::u8path(test_output_dir_) /
kDefaultMasterPlaylistName) {}
Expand Down Expand Up @@ -849,6 +851,55 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistAudioOnly) {
ASSERT_EQ(expected, actual);
}

TEST_F(MasterPlaylistTest, WriteMasterPlaylistWithEncryption) {
std::unique_ptr<MockMediaPlaylist> media_playlists[] = {
// VIDEO
CreateVideoPlaylist("video-1.m3u8", "sdvideocodec", 300000, 200000),

// AUDIO
CreateAudioPlaylist("audio-1.m3u8", "audio 1", "audio-group-1",
"audiocodec", "en", 2, 50000, 30000,
kEC3JocComplexityZero, !kAC4IMSFlagEnabled,
!kAC4CBIFlagEnabled),
};

// Add all the media playlists to the master playlist.
std::list<MediaPlaylist*> media_playlist_list;
for (const auto& media_playlist : media_playlists) {
media_playlist.get()->AddEncryptionInfoForTesting(
MediaPlaylist::EncryptionMethod::kSampleAes, "http://example.com", "",
"0x12345678", "com.widevine", "1/2/4");
media_playlist_list.push_back(media_playlist.get());
}

const char kBaseUrl[] = "http://playlists.org/";
EXPECT_TRUE(master_playlist_->WriteMasterPlaylist(kBaseUrl, test_output_dir_,
media_playlist_list));

std::string actual;
ASSERT_TRUE(
File::ReadFileToString(master_playlist_path_.string().c_str(), &actual));

// Expected master playlist content with encryption.
std::string expected =
"#EXTM3U\n"
"## Generated with https://github.com/shaka-project/shaka-packager "
"version test\n"
"#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES,URI=\"http://example.com\","
"IV=0x12345678,KEYFORMATVERSIONS=\"1/2/4\",KEYFORMAT=\"com.widevine\"\n"
"\n"
"#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/audio-1.m3u8\","
"GROUP-ID=\"audio-group-1\",LANGUAGE=\"en\",NAME=\"audio 1\","
"DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"2\"\n"
"\n"
"#EXT-X-STREAM-INF:BANDWIDTH=350000,AVERAGE-BANDWIDTH=230000,"
"CODECS=\"sdvideocodec,audiocodec\",RESOLUTION=800x600,"
"AUDIO=\"audio-group-1\",CLOSED-CAPTIONS=NONE\n"
"http://playlists.org/video-1.m3u8\n";

ASSERT_EQ(expected, actual);
}

TEST_F(MasterPlaylistTest, WriteMasterPlaylistAudioOnlyJOC) {
const uint64_t kAudioChannels = 6;
const uint64_t kAudioMaxBitrate = 50000;
Expand Down
31 changes: 22 additions & 9 deletions packager/hls/base/media_playlist.cc
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ class SegmentInfoEntry : public HlsEntry {
uint64_t segment_file_size,
uint64_t previous_segment_end_offset);

std::string ToString() override;
std::string ToString(std::string) override;
SteveR-PMP marked this conversation as resolved.
Show resolved Hide resolved
int64_t start_time() const { return start_time_; }
double duration_seconds() const { return duration_seconds_; }
void set_duration_seconds(double duration_seconds) {
Expand Down Expand Up @@ -217,7 +217,7 @@ SegmentInfoEntry::SegmentInfoEntry(const std::string& file_name,
segment_file_size_(segment_file_size),
previous_segment_end_offset_(previous_segment_end_offset) {}

std::string SegmentInfoEntry::ToString() {
std::string SegmentInfoEntry::ToString(std::string) {
std::string result = absl::StrFormat("#EXTINF:%.3f,", duration_seconds_);

if (use_byte_range_) {
Expand All @@ -242,7 +242,7 @@ class EncryptionInfoEntry : public HlsEntry {
const std::string& key_format,
const std::string& key_format_versions);

std::string ToString() override;
std::string ToString(std::string) override;

private:
EncryptionInfoEntry(const EncryptionInfoEntry&) = delete;
Expand Down Expand Up @@ -270,9 +270,11 @@ EncryptionInfoEntry::EncryptionInfoEntry(MediaPlaylist::EncryptionMethod method,
key_format_(key_format),
key_format_versions_(key_format_versions) {}

std::string EncryptionInfoEntry::ToString() {
std::string EncryptionInfoEntry::ToString(std::string tag_name) {
std::string tag_string;
Tag tag("#EXT-X-KEY", &tag_string);
if (tag_name.empty())
tag_name = "#EXT-X-KEY";
Tag tag(tag_name, &tag_string);

if (method_ == MediaPlaylist::EncryptionMethod::kSampleAes) {
tag.AddString("METHOD", "SAMPLE-AES");
Expand Down Expand Up @@ -307,7 +309,7 @@ class DiscontinuityEntry : public HlsEntry {
public:
DiscontinuityEntry();

std::string ToString() override;
std::string ToString(std::string) override;

private:
DiscontinuityEntry(const DiscontinuityEntry&) = delete;
Expand All @@ -317,15 +319,15 @@ class DiscontinuityEntry : public HlsEntry {
DiscontinuityEntry::DiscontinuityEntry()
: HlsEntry(HlsEntry::EntryType::kExtDiscontinuity) {}

std::string DiscontinuityEntry::ToString() {
std::string DiscontinuityEntry::ToString(std::string) {
return "#EXT-X-DISCONTINUITY";
}

class PlacementOpportunityEntry : public HlsEntry {
public:
PlacementOpportunityEntry();

std::string ToString() override;
std::string ToString(std::string) override;

private:
PlacementOpportunityEntry(const PlacementOpportunityEntry&) = delete;
Expand All @@ -336,7 +338,7 @@ class PlacementOpportunityEntry : public HlsEntry {
PlacementOpportunityEntry::PlacementOpportunityEntry()
: HlsEntry(HlsEntry::EntryType::kExtPlacementOpportunity) {}

std::string PlacementOpportunityEntry::ToString() {
std::string PlacementOpportunityEntry::ToString(std::string) {
return "#EXT-X-PLACEMENT-OPPORTUNITY";
}

Expand Down Expand Up @@ -383,6 +385,17 @@ void MediaPlaylist::SetForcedSubtitleForTesting(const bool forced_subtitle) {
forced_subtitle_ = forced_subtitle;
}

void MediaPlaylist::AddEncryptionInfoForTesting(
MediaPlaylist::EncryptionMethod method,
const std::string& url,
const std::string& key_id,
const std::string& iv,
const std::string& key_format,
const std::string& key_format_versions) {
entries_.emplace_back(new EncryptionInfoEntry(
method, url, key_id, iv, key_format, key_format_versions));
}

bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
const int32_t time_scale = GetTimeScale(media_info);
if (time_scale == 0) {
Expand Down
13 changes: 12 additions & 1 deletion packager/hls/base/media_playlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class HlsEntry {
virtual ~HlsEntry();

EntryType type() const { return type_; }
virtual std::string ToString() = 0;
virtual std::string ToString(std::string tag_name = "") = 0;

protected:
explicit HlsEntry(EntryType type);
Expand Down Expand Up @@ -83,6 +83,9 @@ class MediaPlaylist {
const std::string& codec() const { return codec_; }
const std::string& supplemental_codec() const { return supplemental_codec_; }
const media::FourCC& compatible_brand() const { return compatible_brand_; }
const std::list<std::unique_ptr<HlsEntry>>& entries() const {
return entries_;
}

/// For testing only.
void SetStreamTypeForTesting(MediaPlaylistStreamType stream_type);
Expand All @@ -100,6 +103,14 @@ class MediaPlaylist {
void SetCharacteristicsForTesting(
const std::vector<std::string>& characteristics);

/// For testing only.
void AddEncryptionInfoForTesting(MediaPlaylist::EncryptionMethod method,
const std::string& url,
const std::string& key_id,
const std::string& iv,
const std::string& key_format,
const std::string& key_format_versions);

/// This must succeed before calling any other public methods.
/// @param media_info is the info of the segments that are going to be added
/// to this playlist.
Expand Down
3 changes: 2 additions & 1 deletion packager/hls/base/simple_hls_notifier.cc
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,8 @@ SimpleHlsNotifier::SimpleHlsNotifier(const HlsParams& hls_params)
: hls_params.default_text_language;
master_playlist_.reset(new MasterPlaylist(
master_playlist_path.filename(), default_audio_langauge,
default_text_language, hls_params.is_independent_segments));
default_text_language, hls_params.is_independent_segments,
hls_params.create_session_keys));
}

SimpleHlsNotifier::~SimpleHlsNotifier() {}
Expand Down
Loading