Add Subtitle Support to HLS Playlists
The master playlist and media playlist did not have implementations for handling text streams. This change adds support for both. Bug: #205 Change-Id: I1329b8cc2585f15b89959071db9dd16d35847cba
This commit is contained in:
parent
82735be58d
commit
e2dae2d960
|
@ -22,10 +22,13 @@ namespace shaka {
|
||||||
namespace hls {
|
namespace hls {
|
||||||
namespace {
|
namespace {
|
||||||
const char* kDefaultAudioGroupId = "default-audio-group";
|
const char* kDefaultAudioGroupId = "default-audio-group";
|
||||||
|
const char* kDefaultSubtitleGroupId = "default-text-group";
|
||||||
|
const char* kUnexpectedGroupId = "unexpected-group";
|
||||||
|
|
||||||
struct Variant {
|
struct Variant {
|
||||||
std::string audio_codec;
|
std::string audio_codec;
|
||||||
const std::string* audio_group_id = nullptr;
|
const std::string* audio_group_id = nullptr;
|
||||||
|
const std::string* text_group_id = nullptr;
|
||||||
uint64_t audio_bitrate = 0;
|
uint64_t audio_bitrate = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -66,10 +69,72 @@ std::list<Variant> AudioGroupsToVariants(
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* GetGroupId(const MediaPlaylist& playlist) {
|
const char* GetGroupId(const MediaPlaylist& playlist) {
|
||||||
// TODO(vaage): Add support to get a subtitle group id when text support
|
|
||||||
// is added.
|
|
||||||
const std::string& group_id = playlist.group_id();
|
const std::string& group_id = playlist.group_id();
|
||||||
return group_id.empty() ? kDefaultAudioGroupId : group_id.c_str();
|
|
||||||
|
if (!group_id.empty()) {
|
||||||
|
return group_id.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (playlist.stream_type()) {
|
||||||
|
case MediaPlaylist::MediaPlaylistStreamType::kAudio:
|
||||||
|
return kDefaultAudioGroupId;
|
||||||
|
|
||||||
|
case MediaPlaylist::MediaPlaylistStreamType::kSubtitle:
|
||||||
|
return kDefaultSubtitleGroupId;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return kUnexpectedGroupId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::list<Variant> SubtitleGroupsToVariants(
|
||||||
|
const std::map<std::string, std::list<const MediaPlaylist*>>& groups) {
|
||||||
|
std::list<Variant> variants;
|
||||||
|
|
||||||
|
for (const auto& group : groups) {
|
||||||
|
Variant variant;
|
||||||
|
variant.text_group_id = &group.first;
|
||||||
|
|
||||||
|
variants.push_back(variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we return at least one variant so create a null variant if there
|
||||||
|
// are no variants.
|
||||||
|
if (variants.empty()) {
|
||||||
|
variants.emplace_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
return variants;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::list<Variant> BuildVariants(
|
||||||
|
const std::map<std::string, std::list<const MediaPlaylist*>>& audio_groups,
|
||||||
|
const std::map<std::string, std::list<const MediaPlaylist*>>&
|
||||||
|
subtitle_groups) {
|
||||||
|
std::list<Variant> audio_variants = AudioGroupsToVariants(audio_groups);
|
||||||
|
std::list<Variant> subtitle_variants =
|
||||||
|
SubtitleGroupsToVariants(subtitle_groups);
|
||||||
|
|
||||||
|
DCHECK_GE(audio_variants.size(), 1u);
|
||||||
|
DCHECK_GE(subtitle_variants.size(), 1u);
|
||||||
|
|
||||||
|
std::list<Variant> merged;
|
||||||
|
|
||||||
|
for (const auto& audio_variant : audio_variants) {
|
||||||
|
for (const auto& subtitle_variant : subtitle_variants) {
|
||||||
|
Variant variant;
|
||||||
|
variant.audio_codec = audio_variant.audio_codec;
|
||||||
|
variant.audio_group_id = audio_variant.audio_group_id;
|
||||||
|
variant.text_group_id = subtitle_variant.text_group_id;
|
||||||
|
variant.audio_bitrate = audio_variant.audio_bitrate;
|
||||||
|
|
||||||
|
merged.push_back(variant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DCHECK_GE(merged.size(), 1u);
|
||||||
|
|
||||||
|
return merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BuildAudioTag(const std::string& base_url,
|
void BuildAudioTag(const std::string& base_url,
|
||||||
|
@ -105,6 +170,7 @@ void BuildVideoTag(const MediaPlaylist& playlist,
|
||||||
uint64_t max_audio_bitrate,
|
uint64_t max_audio_bitrate,
|
||||||
const std::string& audio_codec,
|
const std::string& audio_codec,
|
||||||
const std::string* audio_group_id,
|
const std::string* audio_group_id,
|
||||||
|
const std::string* text_group_id,
|
||||||
const std::string& base_url,
|
const std::string& base_url,
|
||||||
std::string* out) {
|
std::string* out) {
|
||||||
DCHECK(out);
|
DCHECK(out);
|
||||||
|
@ -130,9 +196,31 @@ void BuildVideoTag(const MediaPlaylist& playlist,
|
||||||
tag.AddQuotedString("AUDIO", *audio_group_id);
|
tag.AddQuotedString("AUDIO", *audio_group_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (text_group_id) {
|
||||||
|
tag.AddQuotedString("SUBTITLES", *text_group_id);
|
||||||
|
}
|
||||||
|
|
||||||
base::StringAppendF(out, "\n%s%s\n", base_url.c_str(),
|
base::StringAppendF(out, "\n%s%s\n", base_url.c_str(),
|
||||||
playlist.file_name().c_str());
|
playlist.file_name().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BuildSubtitleTag(const MediaPlaylist& playlist,
|
||||||
|
const std::string& base_url,
|
||||||
|
const std::string& group_id,
|
||||||
|
std::string* out) {
|
||||||
|
Tag tag("#EXT-X-MEDIA", out);
|
||||||
|
|
||||||
|
tag.AddString("TYPE", "SUBTITLES");
|
||||||
|
tag.AddQuotedString("URI", base_url + playlist.file_name());
|
||||||
|
tag.AddQuotedString("GROUP-ID", group_id);
|
||||||
|
const std::string& language = playlist.GetLanguage();
|
||||||
|
if (!language.empty()) {
|
||||||
|
tag.AddQuotedString("LANGUAGE", language);
|
||||||
|
}
|
||||||
|
tag.AddQuotedString("NAME", playlist.name());
|
||||||
|
|
||||||
|
out->append("\n");
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
MasterPlaylist::MasterPlaylist(const std::string& file_name,
|
MasterPlaylist::MasterPlaylist(const std::string& file_name,
|
||||||
|
@ -152,6 +240,11 @@ void MasterPlaylist::AddMediaPlaylist(MediaPlaylist* media_playlist) {
|
||||||
video_playlists_.push_back(media_playlist);
|
video_playlists_.push_back(media_playlist);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case MediaPlaylist::MediaPlaylistStreamType::kSubtitle: {
|
||||||
|
std::string group_id = GetGroupId(*media_playlist);
|
||||||
|
subtitle_playlist_groups_[group_id].push_back(media_playlist);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
NOTIMPLEMENTED() << static_cast<int>(media_playlist->stream_type())
|
NOTIMPLEMENTED() << static_cast<int>(media_playlist->stream_type())
|
||||||
<< " not handled.";
|
<< " not handled.";
|
||||||
|
@ -168,6 +261,7 @@ bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url,
|
||||||
// TODO(rkuroiwa): Handle audio only.
|
// TODO(rkuroiwa): Handle audio only.
|
||||||
std::string audio_output;
|
std::string audio_output;
|
||||||
std::string video_output;
|
std::string video_output;
|
||||||
|
std::string subtitle_output;
|
||||||
|
|
||||||
// Write out all the audio tags.
|
// Write out all the audio tags.
|
||||||
for (const auto& group : audio_playlist_groups_) {
|
for (const auto& group : audio_playlist_groups_) {
|
||||||
|
@ -203,13 +297,24 @@ bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::list<Variant> variants = AudioGroupsToVariants(audio_playlist_groups_);
|
// Write out all the text tags.
|
||||||
|
for (const auto& group : subtitle_playlist_groups_) {
|
||||||
|
const auto& group_id = group.first;
|
||||||
|
const auto& playlists = group.second;
|
||||||
|
for (const auto& playlist : playlists) {
|
||||||
|
BuildSubtitleTag(*playlist, base_url, group_id, &subtitle_output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::list<Variant> variants =
|
||||||
|
BuildVariants(audio_playlist_groups_, subtitle_playlist_groups_);
|
||||||
|
|
||||||
// Write all the video tags out.
|
// Write all the video tags out.
|
||||||
for (const auto& playlist : video_playlists_) {
|
for (const auto& playlist : video_playlists_) {
|
||||||
for (const auto& variant : variants) {
|
for (const auto& variant : variants) {
|
||||||
BuildVideoTag(*playlist, variant.audio_bitrate, variant.audio_codec,
|
BuildVideoTag(*playlist, variant.audio_bitrate, variant.audio_codec,
|
||||||
variant.audio_group_id, base_url, &video_output);
|
variant.audio_group_id, variant.text_group_id, base_url,
|
||||||
|
&video_output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,8 +327,9 @@ bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url,
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string content = "";
|
std::string content = "";
|
||||||
base::StringAppendF(&content, "#EXTM3U\n%s%s%s", version_line.c_str(),
|
base::StringAppendF(&content, "#EXTM3U\n%s%s%s%s", version_line.c_str(),
|
||||||
audio_output.c_str(), video_output.c_str());
|
audio_output.c_str(), subtitle_output.c_str(),
|
||||||
|
video_output.c_str());
|
||||||
|
|
||||||
// Skip if the playlist is already written.
|
// Skip if the playlist is already written.
|
||||||
if (content == written_playlist_)
|
if (content == written_playlist_)
|
||||||
|
|
|
@ -52,8 +52,13 @@ class MasterPlaylist {
|
||||||
const std::string default_language_;
|
const std::string default_language_;
|
||||||
std::list<MediaPlaylist*> all_playlists_;
|
std::list<MediaPlaylist*> all_playlists_;
|
||||||
std::list<const MediaPlaylist*> video_playlists_;
|
std::list<const MediaPlaylist*> video_playlists_;
|
||||||
// The key is the audio group name.
|
|
||||||
|
// The ID is the group name, and the value is the list of all media playlists
|
||||||
|
// in that group. Keep audio and subtitle separate as they are processed
|
||||||
|
// separately.
|
||||||
std::map<std::string, std::list<const MediaPlaylist*>> audio_playlist_groups_;
|
std::map<std::string, std::list<const MediaPlaylist*>> audio_playlist_groups_;
|
||||||
|
std::map<std::string, std::list<const MediaPlaylist*>>
|
||||||
|
subtitle_playlist_groups_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(MasterPlaylist);
|
DISALLOW_COPY_AND_ASSIGN(MasterPlaylist);
|
||||||
};
|
};
|
||||||
|
|
|
@ -84,6 +84,20 @@ std::unique_ptr<MockMediaPlaylist> CreateAudioPlaylist(
|
||||||
|
|
||||||
return playlist;
|
return playlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<MockMediaPlaylist> MakeText(const std::string& filename,
|
||||||
|
const std::string& name,
|
||||||
|
const std::string& group,
|
||||||
|
const std::string& language) {
|
||||||
|
std::unique_ptr<MockMediaPlaylist> playlist(
|
||||||
|
new MockMediaPlaylist(kVodPlaylist, filename, name, group));
|
||||||
|
|
||||||
|
EXPECT_CALL(*playlist, GetLanguage()).WillRepeatedly(Return(language));
|
||||||
|
playlist->SetStreamTypeForTesting(
|
||||||
|
MediaPlaylist::MediaPlaylistStreamType::kSubtitle);
|
||||||
|
|
||||||
|
return playlist;
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
class MasterPlaylistTest : public ::testing::Test {
|
class MasterPlaylistTest : public ::testing::Test {
|
||||||
|
@ -279,5 +293,214 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistSameAudioGroupSameLanguage) {
|
||||||
|
|
||||||
ASSERT_EQ(expected, actual);
|
ASSERT_EQ(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideosAndTexts) {
|
||||||
|
// Video, sd.m3u8.
|
||||||
|
std::unique_ptr<MockMediaPlaylist> video1 =
|
||||||
|
CreateVideoPlaylist("sd.m3u8", "sdvideocodec", 300000);
|
||||||
|
master_playlist_.AddMediaPlaylist(video1.get());
|
||||||
|
|
||||||
|
// Video, hd.m3u8.
|
||||||
|
std::unique_ptr<MockMediaPlaylist> video2 =
|
||||||
|
CreateVideoPlaylist("hd.m3u8", "sdvideocodec", 600000);
|
||||||
|
master_playlist_.AddMediaPlaylist(video2.get());
|
||||||
|
|
||||||
|
// Text, eng.m3u8.
|
||||||
|
std::unique_ptr<MockMediaPlaylist> text_eng =
|
||||||
|
MakeText("eng.m3u8", "english", "textgroup", "en");
|
||||||
|
master_playlist_.AddMediaPlaylist(text_eng.get());
|
||||||
|
|
||||||
|
// Text, fr.m3u8.
|
||||||
|
std::unique_ptr<MockMediaPlaylist> text_fr =
|
||||||
|
MakeText("fr.m3u8", "french", "textgroup", "fr");
|
||||||
|
master_playlist_.AddMediaPlaylist(text_fr.get());
|
||||||
|
|
||||||
|
const char kBaseUrl[] = "http://playlists.org/";
|
||||||
|
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_));
|
||||||
|
|
||||||
|
std::string actual;
|
||||||
|
ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual));
|
||||||
|
|
||||||
|
const std::string expected =
|
||||||
|
"#EXTM3U\n"
|
||||||
|
"## Generated with https://github.com/google/shaka-packager version "
|
||||||
|
"test\n"
|
||||||
|
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\","
|
||||||
|
"GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\"\n"
|
||||||
|
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/fr.m3u8\","
|
||||||
|
"GROUP-ID=\"textgroup\",LANGUAGE=\"fr\",NAME=\"french\"\n"
|
||||||
|
"#EXT-X-STREAM-INF:BANDWIDTH=300000,CODECS=\"sdvideocodec\","
|
||||||
|
"RESOLUTION=800x600,SUBTITLES=\"textgroup\"\n"
|
||||||
|
"http://playlists.org/sd.m3u8\n"
|
||||||
|
"#EXT-X-STREAM-INF:BANDWIDTH=600000,CODECS=\"sdvideocodec\","
|
||||||
|
"RESOLUTION=800x600,SUBTITLES=\"textgroup\"\n"
|
||||||
|
"http://playlists.org/hd.m3u8\n";
|
||||||
|
|
||||||
|
ASSERT_EQ(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndTextGroups) {
|
||||||
|
// Video, sd.m3u8.
|
||||||
|
std::unique_ptr<MockMediaPlaylist> video =
|
||||||
|
CreateVideoPlaylist("sd.m3u8", "sdvideocodec", 300000);
|
||||||
|
master_playlist_.AddMediaPlaylist(video.get());
|
||||||
|
|
||||||
|
// Text, eng.m3u8.
|
||||||
|
std::unique_ptr<MockMediaPlaylist> text_eng =
|
||||||
|
MakeText("eng.m3u8", "english", "en-text-group", "en");
|
||||||
|
master_playlist_.AddMediaPlaylist(text_eng.get());
|
||||||
|
|
||||||
|
// Text, fr.m3u8.
|
||||||
|
std::unique_ptr<MockMediaPlaylist> text_fr =
|
||||||
|
MakeText("fr.m3u8", "french", "fr-text-group", "fr");
|
||||||
|
master_playlist_.AddMediaPlaylist(text_fr.get());
|
||||||
|
|
||||||
|
const char kBaseUrl[] = "http://playlists.org/";
|
||||||
|
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_));
|
||||||
|
|
||||||
|
std::string actual;
|
||||||
|
ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual));
|
||||||
|
|
||||||
|
const std::string expected =
|
||||||
|
"#EXTM3U\n"
|
||||||
|
"## Generated with https://github.com/google/shaka-packager version "
|
||||||
|
"test\n"
|
||||||
|
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\","
|
||||||
|
"GROUP-ID=\"en-text-group\",LANGUAGE=\"en\",NAME=\"english\"\n"
|
||||||
|
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/fr.m3u8\","
|
||||||
|
"GROUP-ID=\"fr-text-group\",LANGUAGE=\"fr\",NAME=\"french\"\n"
|
||||||
|
"#EXT-X-STREAM-INF:BANDWIDTH=300000,CODECS=\"sdvideocodec\","
|
||||||
|
"RESOLUTION=800x600,SUBTITLES=\"en-text-group\"\n"
|
||||||
|
"http://playlists.org/sd.m3u8\n"
|
||||||
|
"#EXT-X-STREAM-INF:BANDWIDTH=300000,CODECS=\"sdvideocodec\","
|
||||||
|
"RESOLUTION=800x600,SUBTITLES=\"fr-text-group\"\n"
|
||||||
|
"http://playlists.org/sd.m3u8\n";
|
||||||
|
|
||||||
|
ASSERT_EQ(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudioAndText) {
|
||||||
|
// Video, sd.m3u8.
|
||||||
|
std::unique_ptr<MockMediaPlaylist> video =
|
||||||
|
CreateVideoPlaylist("sd.m3u8", "sdvideocodec", 300000);
|
||||||
|
master_playlist_.AddMediaPlaylist(video.get());
|
||||||
|
|
||||||
|
// Audio, english.m3u8.
|
||||||
|
std::unique_ptr<MockMediaPlaylist> audio = CreateAudioPlaylist(
|
||||||
|
"eng.m3u8", "english", "audiogroup", "audiocodec", "en", 2, 50000);
|
||||||
|
master_playlist_.AddMediaPlaylist(audio.get());
|
||||||
|
|
||||||
|
// Text, english.m3u8.
|
||||||
|
std::unique_ptr<MockMediaPlaylist> text =
|
||||||
|
MakeText("eng.m3u8", "english", "textgroup", "en");
|
||||||
|
master_playlist_.AddMediaPlaylist(text.get());
|
||||||
|
|
||||||
|
const char kBaseUrl[] = "http://playlists.org/";
|
||||||
|
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_));
|
||||||
|
|
||||||
|
std::string actual;
|
||||||
|
ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual));
|
||||||
|
|
||||||
|
const std::string expected =
|
||||||
|
"#EXTM3U\n"
|
||||||
|
"## Generated with https://github.com/google/shaka-packager version "
|
||||||
|
"test\n"
|
||||||
|
"#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/eng.m3u8\","
|
||||||
|
"GROUP-ID=\"audiogroup\",LANGUAGE=\"en\",NAME=\"english\","
|
||||||
|
"DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"2\"\n"
|
||||||
|
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/eng.m3u8\","
|
||||||
|
"GROUP-ID=\"textgroup\",LANGUAGE=\"en\",NAME=\"english\"\n"
|
||||||
|
"#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\","
|
||||||
|
"RESOLUTION=800x600,AUDIO=\"audiogroup\",SUBTITLES=\"textgroup\"\n"
|
||||||
|
"http://playlists.org/sd.m3u8\n";
|
||||||
|
|
||||||
|
ASSERT_EQ(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MasterPlaylistTest, WriteMasterPlaylistVidesAudiosTextsDifferentGroups) {
|
||||||
|
const uint64_t kAudioChannels = 2;
|
||||||
|
const uint64_t kAudioBitRate = 50000;
|
||||||
|
const uint64_t kVideoBitRate = 300000;
|
||||||
|
|
||||||
|
std::unique_ptr<MockMediaPlaylist> media_playlists[] = {
|
||||||
|
// AUDIO
|
||||||
|
CreateAudioPlaylist("audio-1.m3u8", "audio 1", "audio-group-1",
|
||||||
|
"audiocodec", "en", kAudioChannels, kAudioBitRate),
|
||||||
|
CreateAudioPlaylist("audio-2.m3u8", "audio 2", "audio-group-2",
|
||||||
|
"audiocodec", "en", kAudioChannels, kAudioBitRate),
|
||||||
|
|
||||||
|
// SUBTITLES
|
||||||
|
MakeText("text-1.m3u8", "text 1", "text-group-1", "en"),
|
||||||
|
MakeText("text-2.m3u8", "text 2", "text-group-2", "en"),
|
||||||
|
|
||||||
|
// VIDEO
|
||||||
|
CreateVideoPlaylist("video-1.m3u8", "sdvideocodec", kVideoBitRate),
|
||||||
|
CreateVideoPlaylist("video-2.m3u8", "sdvideocodec", kVideoBitRate),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add all the media playlists to the master playlist.
|
||||||
|
for (const auto& media_playlist : media_playlists) {
|
||||||
|
master_playlist_.AddMediaPlaylist(media_playlist.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
const char kBaseUrl[] = "http://playlists.org/";
|
||||||
|
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_));
|
||||||
|
|
||||||
|
std::string actual;
|
||||||
|
ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual));
|
||||||
|
|
||||||
|
const std::string expected =
|
||||||
|
"#EXTM3U\n"
|
||||||
|
"## Generated with https://github.com/google/shaka-packager version "
|
||||||
|
"test\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"
|
||||||
|
|
||||||
|
"#EXT-X-MEDIA:TYPE=AUDIO,URI=\"http://playlists.org/audio-2.m3u8\","
|
||||||
|
"GROUP-ID=\"audio-group-2\",LANGUAGE=\"en\",NAME=\"audio 2\","
|
||||||
|
"DEFAULT=YES,AUTOSELECT=YES,CHANNELS=\"2\"\n"
|
||||||
|
|
||||||
|
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/text-1.m3u8\","
|
||||||
|
"GROUP-ID=\"text-group-1\",LANGUAGE=\"en\",NAME=\"text 1\"\n"
|
||||||
|
|
||||||
|
"#EXT-X-MEDIA:TYPE=SUBTITLES,URI=\"http://playlists.org/text-2.m3u8\","
|
||||||
|
"GROUP-ID=\"text-group-2\",LANGUAGE=\"en\",NAME=\"text 2\"\n"
|
||||||
|
|
||||||
|
"#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\","
|
||||||
|
"RESOLUTION=800x600,AUDIO=\"audio-group-1\",SUBTITLES=\"text-group-1\"\n"
|
||||||
|
"http://playlists.org/video-1.m3u8\n"
|
||||||
|
|
||||||
|
"#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\","
|
||||||
|
"RESOLUTION=800x600,AUDIO=\"audio-group-1\",SUBTITLES=\"text-group-2\"\n"
|
||||||
|
"http://playlists.org/video-1.m3u8\n"
|
||||||
|
|
||||||
|
"#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\","
|
||||||
|
"RESOLUTION=800x600,AUDIO=\"audio-group-2\",SUBTITLES=\"text-group-1\"\n"
|
||||||
|
"http://playlists.org/video-1.m3u8\n"
|
||||||
|
|
||||||
|
"#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\","
|
||||||
|
"RESOLUTION=800x600,AUDIO=\"audio-group-2\",SUBTITLES=\"text-group-2\"\n"
|
||||||
|
"http://playlists.org/video-1.m3u8\n"
|
||||||
|
|
||||||
|
"#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\","
|
||||||
|
"RESOLUTION=800x600,AUDIO=\"audio-group-1\",SUBTITLES=\"text-group-1\"\n"
|
||||||
|
"http://playlists.org/video-2.m3u8\n"
|
||||||
|
|
||||||
|
"#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\","
|
||||||
|
"RESOLUTION=800x600,AUDIO=\"audio-group-1\",SUBTITLES=\"text-group-2\"\n"
|
||||||
|
"http://playlists.org/video-2.m3u8\n"
|
||||||
|
|
||||||
|
"#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\","
|
||||||
|
"RESOLUTION=800x600,AUDIO=\"audio-group-2\",SUBTITLES=\"text-group-1\"\n"
|
||||||
|
"http://playlists.org/video-2.m3u8\n"
|
||||||
|
|
||||||
|
"#EXT-X-STREAM-INF:BANDWIDTH=350000,CODECS=\"sdvideocodec,audiocodec\","
|
||||||
|
"RESOLUTION=800x600,AUDIO=\"audio-group-2\",SUBTITLES=\"text-group-2\"\n"
|
||||||
|
"http://playlists.org/video-2.m3u8\n";
|
||||||
|
|
||||||
|
ASSERT_EQ(expected, actual);
|
||||||
|
}
|
||||||
} // namespace hls
|
} // namespace hls
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -221,7 +221,6 @@ std::string EncryptionInfoEntry::ToString() {
|
||||||
std::string tag_string;
|
std::string tag_string;
|
||||||
Tag tag("#EXT-X-KEY", &tag_string);
|
Tag tag("#EXT-X-KEY", &tag_string);
|
||||||
|
|
||||||
std::string method_attribute;
|
|
||||||
if (method_ == MediaPlaylist::EncryptionMethod::kSampleAes) {
|
if (method_ == MediaPlaylist::EncryptionMethod::kSampleAes) {
|
||||||
tag.AddString("METHOD", "SAMPLE-AES");
|
tag.AddString("METHOD", "SAMPLE-AES");
|
||||||
} else if (method_ == MediaPlaylist::EncryptionMethod::kAes128) {
|
} else if (method_ == MediaPlaylist::EncryptionMethod::kAes128) {
|
||||||
|
@ -342,8 +341,8 @@ bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
|
||||||
stream_type_ = MediaPlaylistStreamType::kAudio;
|
stream_type_ = MediaPlaylistStreamType::kAudio;
|
||||||
codec_ = media_info.audio_info().codec();
|
codec_ = media_info.audio_info().codec();
|
||||||
} else {
|
} else {
|
||||||
NOTIMPLEMENTED();
|
stream_type_ = MediaPlaylistStreamType::kSubtitle;
|
||||||
return false;
|
codec_ = media_info.text_info().format();
|
||||||
}
|
}
|
||||||
|
|
||||||
time_scale_ = time_scale;
|
time_scale_ = time_scale;
|
||||||
|
|
|
@ -99,13 +99,12 @@ TEST_F(MediaPlaylistMultiSegmentTest, NoTimeScale) {
|
||||||
EXPECT_FALSE(media_playlist_.SetMediaInfo(media_info));
|
EXPECT_FALSE(media_playlist_.SetMediaInfo(media_info));
|
||||||
}
|
}
|
||||||
|
|
||||||
// The current implementation only handles video and audio.
|
TEST_F(MediaPlaylistMultiSegmentTest, SetMediaInfoText) {
|
||||||
TEST_F(MediaPlaylistMultiSegmentTest, NoAudioOrVideo) {
|
|
||||||
MediaInfo media_info;
|
MediaInfo media_info;
|
||||||
media_info.set_reference_time_scale(kTimeScale);
|
media_info.set_reference_time_scale(kTimeScale);
|
||||||
MediaInfo::TextInfo* text_info = media_info.mutable_text_info();
|
MediaInfo::TextInfo* text_info = media_info.mutable_text_info();
|
||||||
text_info->set_format("vtt");
|
text_info->set_format("vtt");
|
||||||
EXPECT_FALSE(media_playlist_.SetMediaInfo(media_info));
|
EXPECT_TRUE(media_playlist_.SetMediaInfo(media_info));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MediaPlaylistMultiSegmentTest, SetMediaInfo) {
|
TEST_F(MediaPlaylistMultiSegmentTest, SetMediaInfo) {
|
||||||
|
|
Loading…
Reference in New Issue