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: Add support for the EXT-X-START tag #973

Merged
merged 7 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions docs/source/options/hls_options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ HLS options
The EXT-X-MEDIA-SEQUENCE documentation can be read here:
https://tools.ietf.org/html/rfc8216#section-4.3.3.2.

--hls_start_time_offset <seconds>

Sets EXT-X-START on the media playlists to specify the preferred point
at wich the player should start playing.
A positive number indicates a time offset from the beginning of the playlist.
A negative number indicates a negative time offset from the end of the
last media segment in the playlist.

--hls_only=0|1

Optional. Defaults to 0 if not specified. If it is set to 1, indicates the
Expand Down
7 changes: 7 additions & 0 deletions include/packager/hls_params.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#define PACKAGER_PUBLIC_HLS_PARAMS_H_

#include <cstdint>
#include <optional>
#include <string>

namespace shaka {
Expand Down Expand Up @@ -63,6 +64,12 @@ struct HlsParams {
/// Custom EXT-X-MEDIA-SEQUENCE value to allow continuous media playback
/// across packager restarts. See #691 for details.
uint32_t media_sequence_number = 0;
/// Sets EXT-X-START on the media playlists to specify the preferred point
/// at wich the player should start playing.
/// A positive number indicates a time offset from the beginning of the
/// 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;
};

} // namespace shaka
Expand Down
11 changes: 11 additions & 0 deletions packager/app/hls_flags.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include <packager/app/hls_flags.h>

#include <optional>

ABSL_FLAG(std::string,
hls_master_playlist_output,
"",
Expand Down Expand Up @@ -35,3 +37,12 @@ ABSL_FLAG(int32_t,
"EXT-X-MEDIA-SEQUENCE value, which allows continuous media "
"sequence across packager restarts. See #691 for more "
"information about the reasoning of this and its use cases.");
ABSL_FLAG(std::optional<double>,
hls_start_time_offset,
std::nullopt,
"Floating-point number. Sets EXT-X-START on the media playlists "
"to specify the preferred point at wich the player should start "
"playing. A positive number indicates a time offset from the "
"beginning of the playlist. A negative number indicates a "
"negative time offset from the end of the last media segment "
"in the playlist.");
1 change: 1 addition & 0 deletions packager/app/hls_flags.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ ABSL_DECLARE_FLAG(std::string, hls_base_url);
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);

#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 @@ -532,6 +532,7 @@ std::optional<PackagingParams> GetPackagingParams() {
hls_params.default_text_language = absl::GetFlag(FLAGS_default_text_language);
hls_params.media_sequence_number =
absl::GetFlag(FLAGS_hls_media_sequence_number);
hls_params.start_time_offset = absl::GetFlag(FLAGS_hls_start_time_offset);

TestParams& test_params = packaging_params.test_params;
test_params.dump_stream_info = absl::GetFlag(FLAGS_dump_stream_info);
Expand Down
11 changes: 9 additions & 2 deletions packager/hls/base/media_playlist.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <cinttypes>
#include <cmath>
#include <memory>
#include <optional>

#include <absl/log/check.h>
#include <absl/log/log.h>
Expand Down Expand Up @@ -109,7 +110,8 @@ std::string CreatePlaylistHeader(
HlsPlaylistType type,
MediaPlaylist::MediaPlaylistStreamType stream_type,
uint32_t media_sequence_number,
int discontinuity_sequence_number) {
int discontinuity_sequence_number,
std::optional<double> start_time_offset) {
const std::string version = GetPackagerVersion();
std::string version_line;
if (!version.empty()) {
Expand Down Expand Up @@ -151,6 +153,10 @@ std::string CreatePlaylistHeader(
MediaPlaylist::MediaPlaylistStreamType::kVideoIFramesOnly) {
absl::StrAppendFormat(&header, "#EXT-X-I-FRAMES-ONLY\n");
}
if (start_time_offset.has_value()) {
absl::StrAppendFormat(&header, "#EXT-X-START:TIME-OFFSET=%f\n",
start_time_offset.value());
}

// Put EXT-X-MAP at the end since the rest of the playlist is about the
// segment and key info.
Expand Down Expand Up @@ -485,7 +491,8 @@ bool MediaPlaylist::WriteToFile(const std::filesystem::path& file_path) {

std::string content = CreatePlaylistHeader(
media_info_, target_duration_, hls_params_.playlist_type, stream_type_,
media_sequence_number_, discontinuity_sequence_number_);
media_sequence_number_, discontinuity_sequence_number_,
hls_params_.start_time_offset);

for (const auto& entry : entries_)
absl::StrAppendFormat(&content, "%s\n", entry->ToString().c_str());
Expand Down
88 changes: 88 additions & 0 deletions packager/hls/base/media_playlist_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ class MediaPlaylistTest : public ::testing::Test {
default_group_id_("default_group_id") {
hls_params_.playlist_type = type;
hls_params_.time_shift_buffer_depth = kTimeShiftBufferDepth;

// NOTE: hls_params_ is passed by and stored by reference in MediaPlaylist,
// so changed made to it through mutable_hls_params() after this point
// still affect what the playlist see in its own hls_params_ later.
media_playlist_.reset(new MediaPlaylist(hls_params_, default_file_name_,
default_name_, default_group_id_));
}
Expand Down Expand Up @@ -658,6 +662,90 @@ TEST_F(MediaPlaylistMultiSegmentTest, MultipleEncryptionInfo) {
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
}

TEST_F(MediaPlaylistSingleSegmentTest, StartTimeEmpty) {
const std::string kExpectedOutput =
"#EXTM3U\n"
"#EXT-X-VERSION:6\n"
"## Generated with https:/shaka-project/shaka-packager "
"version test\n"
"#EXT-X-TARGETDURATION:0\n"
"#EXT-X-PLAYLIST-TYPE:VOD\n"
"#EXT-X-ENDLIST\n";

// Because this is std::nullopt, the tag isn't in the playlist at all.
mutable_hls_params()->start_time_offset = std::nullopt;

ASSERT_TRUE(media_playlist_->SetMediaInfo(valid_video_media_info_));

const char kMemoryFilePath[] = "memory://media.m3u8";
EXPECT_TRUE(media_playlist_->WriteToFile(kMemoryFilePath));

ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
}

TEST_F(MediaPlaylistSingleSegmentTest, StartTimeZero) {
const std::string kExpectedOutput =
"#EXTM3U\n"
"#EXT-X-VERSION:6\n"
"## Generated with https:/shaka-project/shaka-packager "
"version test\n"
"#EXT-X-TARGETDURATION:0\n"
"#EXT-X-PLAYLIST-TYPE:VOD\n"
"#EXT-X-START:TIME-OFFSET=0.000000\n"
"#EXT-X-ENDLIST\n";

mutable_hls_params()->start_time_offset = 0;

ASSERT_TRUE(media_playlist_->SetMediaInfo(valid_video_media_info_));

const char kMemoryFilePath[] = "memory://media.m3u8";
EXPECT_TRUE(media_playlist_->WriteToFile(kMemoryFilePath));

ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
}

TEST_F(MediaPlaylistSingleSegmentTest, StartTimePositive) {
const std::string kExpectedOutput =
"#EXTM3U\n"
"#EXT-X-VERSION:6\n"
"## Generated with https:/shaka-project/shaka-packager "
"version test\n"
"#EXT-X-TARGETDURATION:0\n"
"#EXT-X-PLAYLIST-TYPE:VOD\n"
"#EXT-X-START:TIME-OFFSET=20.000000\n"
"#EXT-X-ENDLIST\n";

mutable_hls_params()->start_time_offset = 20;

ASSERT_TRUE(media_playlist_->SetMediaInfo(valid_video_media_info_));

const char kMemoryFilePath[] = "memory://media.m3u8";
EXPECT_TRUE(media_playlist_->WriteToFile(kMemoryFilePath));

ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
}

TEST_F(MediaPlaylistSingleSegmentTest, StartTimeNegative) {
const std::string kExpectedOutput =
"#EXTM3U\n"
"#EXT-X-VERSION:6\n"
"## Generated with https:/shaka-project/shaka-packager "
"version test\n"
"#EXT-X-TARGETDURATION:0\n"
"#EXT-X-PLAYLIST-TYPE:VOD\n"
"#EXT-X-START:TIME-OFFSET=-3.141590\n"
"#EXT-X-ENDLIST\n";

mutable_hls_params()->start_time_offset = -3.14159;

ASSERT_TRUE(media_playlist_->SetMediaInfo(valid_video_media_info_));

const char kMemoryFilePath[] = "memory://media.m3u8";
EXPECT_TRUE(media_playlist_->WriteToFile(kMemoryFilePath));

ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
}

class LiveMediaPlaylistTest : public MediaPlaylistMultiSegmentTest {
protected:
LiveMediaPlaylistTest()
Expand Down
Loading