Skip to content

Commit

Permalink
feat: EXT-X-SESSION-KEY support (#36) (shaka-project#1427)
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveR-PMP authored Oct 25, 2024
1 parent ddeacb2 commit d88ed27
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 65 deletions.
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
23 changes: 21 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,23 @@ 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) {
auto encryption_entry = dynamic_cast<EncryptionInfoEntry*>(entry.get());
session_keys.emplace(encryption_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
114 changes: 55 additions & 59 deletions packager/hls/base/media_playlist.cc
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@ std::string CreatePlaylistHeader(
return header;
}


} // namespace

HlsEntry::HlsEntry(HlsEntry::EntryType type) : type_(type) {}
HlsEntry::~HlsEntry() {}

class SegmentInfoEntry : public HlsEntry {
public:
// If |use_byte_range| true then this will append EXT-X-BYTERANGE
Expand Down Expand Up @@ -233,29 +239,44 @@ std::string SegmentInfoEntry::ToString() {
return result;
}

class EncryptionInfoEntry : public HlsEntry {

class DiscontinuityEntry : public HlsEntry {
public:
EncryptionInfoEntry(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);
DiscontinuityEntry();

std::string ToString() override;

private:
EncryptionInfoEntry(const EncryptionInfoEntry&) = delete;
EncryptionInfoEntry& operator=(const EncryptionInfoEntry&) = delete;

const 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_;
DiscontinuityEntry(const DiscontinuityEntry&) = delete;
DiscontinuityEntry& operator=(const DiscontinuityEntry&) = delete;
};

DiscontinuityEntry::DiscontinuityEntry()
: HlsEntry(HlsEntry::EntryType::kExtDiscontinuity) {}

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

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

std::string ToString() override;

private:
PlacementOpportunityEntry(const PlacementOpportunityEntry&) = delete;
PlacementOpportunityEntry& operator=(const PlacementOpportunityEntry&) =
delete;
};

PlacementOpportunityEntry::PlacementOpportunityEntry()
: HlsEntry(HlsEntry::EntryType::kExtPlacementOpportunity) {}

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

EncryptionInfoEntry::EncryptionInfoEntry(MediaPlaylist::EncryptionMethod method,
const std::string& url,
const std::string& key_id,
Expand All @@ -271,8 +292,14 @@ EncryptionInfoEntry::EncryptionInfoEntry(MediaPlaylist::EncryptionMethod method,
key_format_versions_(key_format_versions) {}

std::string EncryptionInfoEntry::ToString() {
return 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 @@ -303,48 +330,6 @@ std::string EncryptionInfoEntry::ToString() {
return tag_string;
}

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

std::string ToString() override;

private:
DiscontinuityEntry(const DiscontinuityEntry&) = delete;
DiscontinuityEntry& operator=(const DiscontinuityEntry&) = delete;
};

DiscontinuityEntry::DiscontinuityEntry()
: HlsEntry(HlsEntry::EntryType::kExtDiscontinuity) {}

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

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

std::string ToString() override;

private:
PlacementOpportunityEntry(const PlacementOpportunityEntry&) = delete;
PlacementOpportunityEntry& operator=(const PlacementOpportunityEntry&) =
delete;
};

PlacementOpportunityEntry::PlacementOpportunityEntry()
: HlsEntry(HlsEntry::EntryType::kExtPlacementOpportunity) {}

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

} // namespace

HlsEntry::HlsEntry(HlsEntry::EntryType type) : type_(type) {}
HlsEntry::~HlsEntry() {}

MediaPlaylist::MediaPlaylist(const HlsParams& hls_params,
const std::string& file_name,
const std::string& name,
Expand Down Expand Up @@ -383,6 +368,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
35 changes: 35 additions & 0 deletions packager/hls/base/media_playlist.h
Original file line number Diff line number Diff line change
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 Expand Up @@ -310,6 +321,30 @@ class MediaPlaylist {
DISALLOW_COPY_AND_ASSIGN(MediaPlaylist);
};

class EncryptionInfoEntry : public HlsEntry {
public:
EncryptionInfoEntry(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);

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

private:
EncryptionInfoEntry(const EncryptionInfoEntry&) = delete;
EncryptionInfoEntry& operator=(const EncryptionInfoEntry&) = delete;

const 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_;
};

} // namespace hls
} // namespace shaka

Expand Down
Loading

0 comments on commit d88ed27

Please sign in to comment.